Beispiel #1
0
        internal Debuggee(string executable,
                          IntPtr procHandle, uint procId,
                          IntPtr mainThreadHandle, uint mainThreadId,
                          ExecutableMetaInfo emi = null)
        {
            // Note: The CodeView information extraction will be done per module, i.e. when the module/process is loaded into the memory.
            Memory       = new MemoryManagement(this);
            Breakpoints  = new BreakpointManagement(this);
            CodeStepping = new Stepping(this);

            var mProc = new DebugProcess(this, executable, procHandle, procId, mainThreadHandle, mainThreadId, emi);

            CurrentThread = mProc.MainThread;
            processes.Add(mProc);
        }
Beispiel #2
0
        /// <summary>
        /// Executes until the currently executed method's point of return has been reached.
        /// </summary>
        public void StepOut(DebugThread th)
        {
            var returnPtr = APIIntermediate.Read<IntPtr>(th.OwnerProcess.Handle, new IntPtr( th.Context.lastReadCtxt.ebp + 4));

            var tempBreakPoint = Breakpoints.ByAddress(returnPtr);
            bool keepBpAfterStepComplete = false;
            if (keepBpAfterStepComplete = tempBreakPoint == null)
                tempBreakPoint = Breakpoints.CreateBreakpoint(returnPtr);

            th.ContinueDebugging();
            Debuggee.WaitForDebugEvent();

            if (!keepBpAfterStepComplete)
                Breakpoints.Remove(tempBreakPoint);
        }
Beispiel #3
0
        public DebugProcess(Debuggee dbg,
			string executableFile,
			IntPtr processHandle, uint processId,
			IntPtr mainThreadHandle, uint mainThreadId,
			ExecutableMetaInfo emi)
        {
            this.Debuggee = dbg;
            Handle = processHandle;
            Id = processId;

            MainModule = new DebugProcessModule(new IntPtr(emi.PEHeader.OptionalHeader32.ImageBase),executableFile, emi);
            RegModule(MainModule);

            MainThread = new DebugThread(this, mainThreadHandle, mainThreadId, MainModule.StartAddress, IntPtr.Zero);
            RegThread(MainThread);
        }
Beispiel #4
0
        public DebugProcess(Debuggee dbg,
                            string executableFile,
                            IntPtr processHandle, uint processId,
                            IntPtr mainThreadHandle, uint mainThreadId,
                            ExecutableMetaInfo emi)
        {
            this.Debuggee = dbg;
            Handle        = processHandle;
            Id            = processId;

            MainModule = new DebugProcessModule(new IntPtr(emi.PEHeader.OptionalHeader32.ImageBase), executableFile, emi);
            RegModule(MainModule);

            MainThread = new DebugThread(this, mainThreadHandle, mainThreadId, MainModule.StartAddress, IntPtr.Zero);
            RegThread(MainThread);
        }
Beispiel #5
0
        public DebugProcess(Debuggee dbg, Win32.CREATE_PROCESS_DEBUG_INFO info, uint id, uint threadId)
        {
            this.Debuggee = dbg;
            Handle        = info.hProcess;
            Id            = id == 0 ? API.GetProcessId(Handle) : id;

            var moduleFile = APIIntermediate.GetModulePath(Handle, info.lpBaseOfImage, info.hFile);

            // Deduce main module
            MainModule = new DebugProcessModule(info.lpBaseOfImage, moduleFile, ExecutableMetaInfo.ExtractFrom(moduleFile));
            RegModule(MainModule);

            // Create main thread
            MainThread = new DebugThread(this,
                                         info.hThread,
                                         threadId == 0 ? API.GetThreadId(info.hThread) : threadId,
                                         info.lpStartAddress,
                                         info.lpThreadLocalBase);
            RegThread(MainThread);
        }
Beispiel #6
0
        public DebugProcess(Debuggee dbg, Win32.CREATE_PROCESS_DEBUG_INFO info, uint id, uint threadId)
        {
            this.Debuggee = dbg;
            Handle = info.hProcess;
            Id = id == 0 ? API.GetProcessId(Handle) : id;

            var moduleFile = APIIntermediate.GetModulePath(Handle, info.lpBaseOfImage, info.hFile);

            // Deduce main module
            MainModule = new DebugProcessModule(info.lpBaseOfImage, moduleFile, ExecutableMetaInfo.ExtractFrom(moduleFile));
            RegModule(MainModule);

            // Create main thread
            MainThread = new DebugThread(this,
                info.hThread,
                threadId == 0 ? API.GetThreadId(info.hThread) : threadId,
                info.lpStartAddress,
                info.lpThreadLocalBase);
            RegThread(MainThread);
        }
Beispiel #7
0
 internal bool RemThread(DebugThread th)
 {
     return(threads.Remove(th));
 }
Beispiel #8
0
        void HandleDebugEvent(DebugEventData de)
        {
            var p  = ProcessById(de.dwProcessId);
            var th = CurrentThread = p.ThreadById(de.dwThreadId);

            switch (de.dwDebugEventCode)
            {
            case DebugEventCode.EXCEPTION_DEBUG_EVENT:
                HandleException(th, de.Exception);
                break;


            case DebugEventCode.CREATE_PROCESS_DEBUG_EVENT:
                var cpi = de.CreateProcessInfo;

                if (MainProcess != null && de.dwProcessId == MainProcess.Id)
                {
                    API.CloseHandle(cpi.hProcess);
                    API.CloseHandle(cpi.hThread);
                    API.CloseHandle(cpi.hFile);

                    foreach (var l in DDebugger.EventListeners)
                    {
                        l.OnCreateProcess(MainProcess);
                    }
                    break;
                }

                // After a new process was created (also occurs after initial WaitForDebugEvent()!!),
                p = new DebugProcess(this, cpi, de.dwProcessId, de.dwThreadId);

                API.CloseHandle(cpi.hFile);

                // enlist it
                processes.Add(p);

                // and call the listeners
                foreach (var l in DDebugger.EventListeners)
                {
                    l.OnCreateProcess(p);
                }
                break;


            case DebugEventCode.CREATE_THREAD_DEBUG_EVENT:
                p = ProcessById(de.dwProcessId);

                // Create new thread wrapper
                th = CurrentThread = new DebugThread(p,
                                                     de.CreateThread.hThread,
                                                     de.dwThreadId,
                                                     de.CreateThread.lpStartAddress,
                                                     de.CreateThread.lpThreadLocalBase);
                // Register it to main process
                p.RegThread(th);

                // Call listeners
                foreach (var l in DDebugger.EventListeners)
                {
                    l.OnCreateThread(th);
                }
                break;


            case DebugEventCode.EXIT_PROCESS_DEBUG_EVENT:

                foreach (var l in DDebugger.EventListeners)
                {
                    l.OnProcessExit(p, de.ExitProcess.dwExitCode);
                }

                processes.Remove(p);
                p.Dispose();
                break;


            case DebugEventCode.EXIT_THREAD_DEBUG_EVENT:

                foreach (var l in DDebugger.EventListeners)
                {
                    l.OnThreadExit(th, de.ExitThread.dwExitCode);
                }

                p.RemThread(th);
                th.Dispose();
                break;


            case DebugEventCode.LOAD_DLL_DEBUG_EVENT:
                var loadParam = de.LoadDll;

                var modName = APIIntermediate.GetModulePath(p.Handle, loadParam.lpBaseOfDll, loadParam.hFile);
                API.CloseHandle(loadParam.hFile);

                var mod = new DebugProcessModule(loadParam.lpBaseOfDll, modName, ExecutableMetaInfo.ExtractFrom(modName));
                p.RegModule(mod);

                foreach (var l in DDebugger.EventListeners)
                {
                    l.OnModuleLoaded(p, mod);
                }
                break;


            case DebugEventCode.UNLOAD_DLL_DEBUG_EVENT:
                mod = p.ModuleByBase(de.UnloadDll.lpBaseOfDll);

                foreach (var l in DDebugger.EventListeners)
                {
                    l.OnModuleUnloaded(p, mod);
                }

                p.RemModule(mod);
                break;


            case DebugEventCode.OUTPUT_DEBUG_STRING_EVENT:
                var message = APIIntermediate.ReadString(p.Handle,
                                                         de.DebugString.lpDebugStringData,
                                                         de.DebugString.fUnicode == 0 ? Encoding.ASCII : Encoding.Unicode,
                                                         (int)de.DebugString.nDebugStringLength);

                foreach (var l in DDebugger.EventListeners)
                {
                    l.OnDebugOutput(th, message);
                }
                break;
            }
        }
Beispiel #9
0
 public virtual void OnBreakpoint(DebugThread thread, Breakpoint breakpoint)
 {
 }
Beispiel #10
0
 public override void OnDebugOutput(DebugThread thread, string outputString)
 {
     base.OnDebugOutput(thread, outputString);
 }
Beispiel #11
0
 public override void OnStepComplete(DebugThread thread)
 {
     form.HighlightCurrentInstruction(thread);
 }
Beispiel #12
0
 public virtual void OnDebugOutput(DebugThread thread, string outputString)
 {
 }
Beispiel #13
0
 public override void OnBreakpoint(DebugThread thread, DDebugger.Breakpoints.Breakpoint breakpoint)
 {
     form.HighlightCurrentInstruction(thread);
 }
Beispiel #14
0
        /// <summary>
        /// Executes the next single code instruction.
        /// Returns false if the single step could be executed but wasn't completed due to a breakpoint or an other exception/debug event.
        /// </summary>
        bool StepToNextInstruction(DebugThread th)
        {
            if (!Debuggee.IsAlive)
                return false;

            if (lastUnhandledBreakpoint != null)
                SkipAndRestoreLastBreakpoint(false);

            bool lastStepState = th.Context.TrapFlagSet;
            if (!lastStepState)
                th.Context.TrapFlagSet = true;

            th.ContinueDebugging();

            expectsSingleStep = true;
            Debuggee.WaitForDebugEvent();

            //TODO: What if there's a non-ss exception?
            // Answer: return false

            if (!lastStepState)
                th.Context.TrapFlagSet = false;

            return !expectsSingleStep;
        }
Beispiel #15
0
 /// <summary>
 /// Executes the next instruction.
 /// </summary>
 public void StepIn(DebugThread th)
 {
     DoSingleStep(th);
 }
Beispiel #16
0
 public virtual void OnCreateThread(DebugThread newThread)
 {
 }
Beispiel #17
0
 void DoSingleStep(DebugThread th)
 {
     if (SourceBoundStepping)
         StepToNextSrcLine(th);
     else
         StepToNextInstruction(th);
 }
Beispiel #18
0
 public virtual void OnThreadExit(DebugThread thread, uint exitCode)
 {
 }
Beispiel #19
0
 public virtual void OnStepComplete(DebugThread thread)
 {
 }
Beispiel #20
0
 public virtual void OnException(DebugThread thread, DebugException exception)
 {
 }
Beispiel #21
0
 public virtual void OnBreakComplete(DebugThread thread)
 {
 }
Beispiel #22
0
        /// <summary>
        /// See <see cref="StepIn"/>.
        /// If there's a call as next instruction, it'll be skipped.
        /// </summary>
        public void StepOver(DebugThread th)
        {
            var code = APIIntermediate.ReadArray<byte>(th.OwnerProcess.Handle, th.CurrentInstruction, DisAsm86.MaximumInstructionLength);

            int instructionLength = 0;
            var instrType = DisAsm86.GetInstructionType(code, false, out instructionLength);

            /*
             * If there's a call, set a breakpoint right after the call to skip the called subroutine
             */
            if (instrType == InstructionType.Call)
            {
                var bpAddr = IntPtr.Add(th.CurrentInstruction, instructionLength);

                var tempBreakPoint = Breakpoints.ByAddress(bpAddr);
                bool keepBpAfterStepComplete = false;
                if (keepBpAfterStepComplete = tempBreakPoint == null)
                    tempBreakPoint = Breakpoints.CreateBreakpoint(bpAddr);

                th.ContinueDebugging();
                Debuggee.WaitForDebugEvent();

                if (!keepBpAfterStepComplete)
                    Breakpoints.Remove(tempBreakPoint);
            }
            else
                StepIn(th);
        }
Beispiel #23
0
 public virtual void OnThreadExit(DebugThread thread, uint exitCode)
 {
 }
Beispiel #24
0
 public virtual void OnCreateThread(DebugThread newThread)
 {
 }
Beispiel #25
0
        void SkipAndRestoreLastBreakpoint(bool resetStepFlag = true)
        {
            if (lastUnhandledBreakpoint == null)
                throw new Exception("lastUnhandledBreakpoint must not be null");
            if (!lastUnhandledBreakpoint.temporarilyDisabled)
                throw new Exception("breakpoint must be marked as temporarily disabled!");
            if (lastUnhandledBreakpointThread == null)
                throw new Exception("lastUnhandledBreakpointThread must not be null");
            if (postBreakpointResetStepCompleted)
                throw new Exception("why is postBreakpointResetStepCompleted set to true?");

            // Immediately after the breakpoint was hit, the bp code was taken out of the code
            // and the instruction pointer was decreased, so the original instruction will be executed then afterwards.

            // 1) Enable single-stepping (SS flag)
            // 2) Step one forward (execute original instruction)
            // 3) Re-enable breakpoint
            // 4) (optionally) disable single-stepping again

            // 1)
            lastUnhandledBreakpointThread.Context.TrapFlagSet = true;

            // 2)
            lastUnhandledBreakpointThread.ContinueDebugging();

            postBreakpointResetStepCompleted = false;
            Debuggee.WaitForDebugEvent();

            if (!postBreakpointResetStepCompleted)
                return;

            // 3)
            lastUnhandledBreakpoint.Enable();

            // 4)
            if(resetStepFlag)
                lastUnhandledBreakpointThread.Context.TrapFlagSet = false;

            lastUnhandledBreakpointThread = null;
            lastUnhandledBreakpoint = null;
            postBreakpointResetStepCompleted = false;
        }
Beispiel #26
0
 public virtual void OnDebugOutput(DebugThread thread, string outputString)
 {
 }
Beispiel #27
0
        /// <summary>
        /// Steps until there's an associated code location for the currently executed instruction 
        /// or if there aren't any debug information.
        /// </summary>
        void StepToNextSrcLine(DebugThread th)
        {
            string file = null;
            ushort line = 0;
            var mainMod = Debuggee.MainProcess.MainModule;
            var modMetaInfo = mainMod.ModuleMetaInfo;

            do{
                StepToNextInstruction(th);
            }
            while(Debuggee.IsAlive &&
                mainMod.ContainsSymbolData &&
                !modMetaInfo.TryDetermineCodeLocation((uint)Debuggee.CurrentThread.CurrentInstruction.ToInt32(), out file, out line));
        }
Beispiel #28
0
 public virtual void OnException(DebugThread thread, DebugException exception)
 {
 }
Beispiel #29
0
 public virtual void OnBreakpoint(DebugThread thread, Breakpoint breakpoint)
 {
 }
Beispiel #30
0
 public virtual void OnStepComplete(DebugThread thread)
 {
 }
Beispiel #31
0
        void HighlightCurrentInstruction(DebugThread th)
        {
            if (currentFrameMarker != null)
                currentFrameMarker.Delete();

            ushort line = 0;
            string file = null;

            if (th.OwnerProcess.MainModule.ContainsSymbolData &&
                th.OwnerProcess.MainModule.ModuleMetaInfo.TryDetermineCodeLocation((uint)th.CurrentInstruction.ToInt32(), out file, out line))
            {
                LoadSourceFileIntoEditor(file);

                currentFrameMarker = new CurrentFrameMarker(MarkerStrategy, editor.Document, line);
                MarkerStrategy.Add(currentFrameMarker);
                currentFrameMarker.Redraw();
            }
        }
Beispiel #32
0
 public virtual void OnBreakComplete(DebugThread thread)
 {
 }
Beispiel #33
0
 public override void OnCreateThread(DebugThread newThread)
 {
     base.OnCreateThread(newThread);
 }
Beispiel #34
0
        void HandleDebugEvent(DebugEventData de)
        {
            var p = ProcessById(de.dwProcessId);
            var th = CurrentThread = p.ThreadById(de.dwThreadId);

            switch (de.dwDebugEventCode)
            {
                case DebugEventCode.EXCEPTION_DEBUG_EVENT:
                    HandleException(th, de.Exception);
                    break;

                case DebugEventCode.CREATE_PROCESS_DEBUG_EVENT:
                    var cpi = de.CreateProcessInfo;

                    if (MainProcess != null && de.dwProcessId == MainProcess.Id)
                    {
                        API.CloseHandle(cpi.hProcess);
                        API.CloseHandle(cpi.hThread);
                        API.CloseHandle(cpi.hFile);

                        foreach(var l in DDebugger.EventListeners)
                            l.OnCreateProcess(MainProcess);
                        break;
                    }

                    // After a new process was created (also occurs after initial WaitForDebugEvent()!!),
                    p = new DebugProcess(this,cpi, de.dwProcessId, de.dwThreadId);

                    API.CloseHandle(cpi.hFile);

                    // enlist it
                    processes.Add(p);

                    // and call the listeners
                    foreach (var l in DDebugger.EventListeners)
                        l.OnCreateProcess(p);
                    break;

                case DebugEventCode.CREATE_THREAD_DEBUG_EVENT:
                    p = ProcessById(de.dwProcessId);

                    // Create new thread wrapper
                    th = CurrentThread = new DebugThread(p,
                        de.CreateThread.hThread,
                        de.dwThreadId,
                        de.CreateThread.lpStartAddress,
                        de.CreateThread.lpThreadLocalBase);
                    // Register it to main process
                    p.RegThread(th);

                    // Call listeners
                    foreach (var l in DDebugger.EventListeners)
                        l.OnCreateThread(th);
                    break;

                case DebugEventCode.EXIT_PROCESS_DEBUG_EVENT:

                    foreach (var l in DDebugger.EventListeners)
                        l.OnProcessExit(p, de.ExitProcess.dwExitCode);

                    processes.Remove(p);
                    p.Dispose();
                    break;

                case DebugEventCode.EXIT_THREAD_DEBUG_EVENT:

                    foreach (var l in DDebugger.EventListeners)
                        l.OnThreadExit(th, de.ExitThread.dwExitCode);

                    p.RemThread(th);
                    th.Dispose();
                    break;

                case DebugEventCode.LOAD_DLL_DEBUG_EVENT:
                    var loadParam = de.LoadDll;

                    var modName = APIIntermediate.GetModulePath(p.Handle, loadParam.lpBaseOfDll, loadParam.hFile);
                    API.CloseHandle(loadParam.hFile);

                    var mod = new DebugProcessModule(loadParam.lpBaseOfDll, modName, ExecutableMetaInfo.ExtractFrom(modName));
                    p.RegModule(mod);

                    foreach (var l in DDebugger.EventListeners)
                        l.OnModuleLoaded(p, mod);
                    break;

                case DebugEventCode.UNLOAD_DLL_DEBUG_EVENT:
                    mod = p.ModuleByBase(de.UnloadDll.lpBaseOfDll);

                    foreach (var l in DDebugger.EventListeners)
                            l.OnModuleUnloaded(p, mod);

                    p.RemModule(mod);
                    break;

                case DebugEventCode.OUTPUT_DEBUG_STRING_EVENT:
                    var message = APIIntermediate.ReadString(p.Handle,
                        de.DebugString.lpDebugStringData,
                        de.DebugString.fUnicode == 0 ? Encoding.ASCII : Encoding.Unicode,
                        (int)de.DebugString.nDebugStringLength);

                    foreach (var l in DDebugger.EventListeners)
                        l.OnDebugOutput(th, message);
                    break;
            }
        }
Beispiel #35
0
 public override void OnException(DebugThread thread, DebugException exception)
 {
     form.HighlightCurrentInstruction(thread);
 }
Beispiel #36
0
        void HandleException(DebugThread th, EXCEPTION_DEBUG_INFO e)
        {
            th.Context.Update();

            var code = e.ExceptionRecord.Code;
            // The instruction
            var targetSiteAddress = e.ExceptionRecord.ExceptionAddress;
            if (code == ExceptionCode.Breakpoint)
            {
                if (CodeStepping.lastUnhandledBreakpoint != null)
                    CodeStepping.postBreakpointResetStepCompleted = true;

                var bp = Breakpoints.ByAddress(targetSiteAddress);
                if (bp == null)
                    return;
                //APIIntermediate.GetCallStack_x86(th.OwnerProcess.Handle, (uint)th.StartAddress.ToInt32(), th.Context["ebp"], th.Context["eip"]);
                /*
                var sf = new STACKFRAME64();
                sf.AddrPC.Offset = th.Context["eip"];
                sf.AddrFrame.Offset = th.Context["ebp"];
                sf.AddrStack.Offset = th.Context["esp"];
                sf.AddrReturn.Mode = sf.AddrStack.Mode = sf.AddrPC.Mode = sf.AddrFrame.Mode = ADDRESS_MODE.AddrModeFlat;

                if(!API.StackWalk64(MachineType.i386, th.OwnerProcess.Handle, th.Handle, ref sf, ref th.Context.lastReadCtxt))
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                */
                th.CurrentInstruction = targetSiteAddress;
                bp.Disable();
                bp.temporarilyDisabled = true;
                CodeStepping.lastUnhandledBreakpoint = bp;
                CodeStepping.lastUnhandledBreakpointThread = th;

                bp.WasHit();

                foreach (var l in DDebugger.EventListeners)
                    l.OnBreakpoint(th, bp);
            }
            else if (code == ExceptionCode.SingleStep)
            {
                if (CodeStepping.lastUnhandledBreakpoint != null)
                    CodeStepping.postBreakpointResetStepCompleted = true;
                else
                    foreach (var l in DDebugger.EventListeners)
                        l.OnStepComplete(th);
            }
            else
            {
                var ex = new DebugException(e.ExceptionRecord, e.dwFirstChance != 0);

                foreach (var l in DDebugger.EventListeners)
                    l.OnException(th, ex);

                LastException = ex;
            }
        }
Beispiel #37
0
 public override void OnThreadExit(DebugThread thread, uint exitCode)
 {
     base.OnThreadExit(thread, exitCode);
 }
Beispiel #38
0
 internal void RegThread(DebugThread th)
 {
     threads.Add(th);
 }
Beispiel #39
0
        void HandleException(DebugThread th, EXCEPTION_DEBUG_INFO e)
        {
            th.Context.Update();

            var code = e.ExceptionRecord.Code;
            // The instruction
            var targetSiteAddress = e.ExceptionRecord.ExceptionAddress;

            if (code == ExceptionCode.Breakpoint)
            {
                if (CodeStepping.lastUnhandledBreakpoint != null)
                {
                    CodeStepping.postBreakpointResetStepCompleted = true;
                }

                var bp = Breakpoints.ByAddress(targetSiteAddress);
                if (bp == null)
                {
                    return;
                }
                //APIIntermediate.GetCallStack_x86(th.OwnerProcess.Handle, (uint)th.StartAddress.ToInt32(), th.Context["ebp"], th.Context["eip"]);

                /*
                 * var sf = new STACKFRAME64();
                 * sf.AddrPC.Offset = th.Context["eip"];
                 * sf.AddrFrame.Offset = th.Context["ebp"];
                 * sf.AddrStack.Offset = th.Context["esp"];
                 * sf.AddrReturn.Mode = sf.AddrStack.Mode = sf.AddrPC.Mode = sf.AddrFrame.Mode = ADDRESS_MODE.AddrModeFlat;
                 *
                 * if(!API.StackWalk64(MachineType.i386, th.OwnerProcess.Handle, th.Handle, ref sf, ref th.Context.lastReadCtxt))
                 *      throw new Win32Exception(Marshal.GetLastWin32Error());
                 */
                th.CurrentInstruction = targetSiteAddress;
                bp.Disable();
                bp.temporarilyDisabled = true;
                CodeStepping.lastUnhandledBreakpoint       = bp;
                CodeStepping.lastUnhandledBreakpointThread = th;

                bp.WasHit();

                foreach (var l in DDebugger.EventListeners)
                {
                    l.OnBreakpoint(th, bp);
                }
            }
            else if (code == ExceptionCode.SingleStep)
            {
                if (CodeStepping.lastUnhandledBreakpoint != null)
                {
                    CodeStepping.postBreakpointResetStepCompleted = true;
                }
                else
                {
                    foreach (var l in DDebugger.EventListeners)
                    {
                        l.OnStepComplete(th);
                    }
                }
            }
            else
            {
                var ex = new DebugException(e.ExceptionRecord, e.dwFirstChance != 0);

                foreach (var l in DDebugger.EventListeners)
                {
                    l.OnException(th, ex);
                }

                LastException = ex;
            }
        }
Beispiel #40
0
 internal bool RemThread(DebugThread th)
 {
     return threads.Remove(th);
 }
Beispiel #41
0
 public DebugThreadContext(DebugThread thread)
 {
     this.Thread = thread;
 }
Beispiel #42
0
 internal void RegThread(DebugThread th)
 {
     threads.Add(th);
 }