Mercurial > projects > ddbg_continued
view src/debugger.d @ 5:496dfd8f7342 default tip
added:
-repeat option for "in", "ov"
-run until a line option
-run until a function option
-break on a function start
-n is an alias for ov
author | marton@basel.hu |
---|---|
date | Sun, 17 Apr 2011 11:05:31 +0200 |
parents | a5fb1bc967e6 |
children |
line wrap: on
line source
/* Ddbg - Win32 Debugger for the D programming language * Copyright (c) 2007 Jascha Wetzel * All rights reserved. See LICENSE.TXT for details. */ /************************************************************************************************** The main debugger code. Handles win32 debugging events, manages breakpoints, calls the disassembler, etc. **************************************************************************************************/ import std.file; import std.string; import std.format; import win32.winbase; import win32.winuser; import win32.winnt; import win32.dbghelp; import minidump; import container; import util; import breakpoint; import dbgprocess; import dbgthread; import disasm; import callstack; import expression.evaluationcontext; import expression.expression_apd; import expression.datahandler; import codeview.coff; import codeview.codeview; import cli.userinterface; const uint VERSION_MAJOR = 0, VERSION_MINOR = 13, VERSION_PATCH = 3; const string VERSION_STRING = "Ddbg "~itoa(VERSION_MAJOR)~"."~itoa(VERSION_MINOR) ~(VERSION_PATCH>0?"."~itoa(VERSION_PATCH):"")~" beta", WELCOME_STRING = VERSION_STRING~" - D Debugger\n"~ "Copyright (c) 2007 Jascha Wetzel\n"~ "see http://ddbg.mainia.de/doc.html for documentation\n"; class DebuggerException : Exception { this(...) { string str; void putc(dchar c) { str ~= c; } doFormat(&putc, _arguments, _argptr); super(str); } } /************************************************************************************************** **************************************************************************************************/ class Debugger { const uint STATUS_DIGITAL_MARS_D_EXCEPTION = (3 << 30) | (1 << 29) | (0 << 28) | ('D' << 16) | 1; //============================================================================================= // variables bool paused, process_loaded, abort, single_step, breakonmain =false ; size_t current_address; uint last_line; // uint repeat=0; string[][string] source_files; string[] source_search_paths; string main_image_file; COFFImage main_image; ImageSet images; Breakpoint[uint] breakpoints; List!(Breakpoint) temp_breakpoints; Breakpoint passing_breakpoint; DbgProcess process; MiniDump miniDump; CallStack current_stack; EXCEPTION_RECORD* exceptionRecord; DEBUG_EVENT debug_event; uint process_id, thread_id; UserInterface ui; uint evaluationDepth = 2; bool create_new_console; //============================================================================================= // construction /********************************************************************************************** Uses the debugger's CLI arguments for the debuggee. Example: debugger debuggee arg0 arg1 ... **********************************************************************************************/ this(string debuggee, UserInterface _ui) { assert(_ui !is null); images = new ImageSet; ui = _ui; openImage(debuggee); source_search_paths ~= ".\\"; temp_breakpoints = new List!(Breakpoint); } CallStack stack() { if ( current_stack is null ) { if ( process !is null ) current_stack = process.loadStack(process.threads[thread_id]); else if ( miniDump !is null ) { CONTEXT* ctx = miniDump.getContext; // TODO: where does that 4-byte offset come from ubyte[] data = miniDump.getMemory(miniDump.thread.Stack.Memory)[4..$]; current_stack = new CallStack(cast(size_t)miniDump.thread.Stack.StartOfMemoryRange+data.length, ctx.Esp, ctx.Ebp); current_stack.data = data; } } return current_stack; } /********************************************************************************************** **********************************************************************************************/ bool step(StepMode mode) { if ( !process_loaded ) return false; Location loc = images.findLocation(current_address); int bpi=-1; Breakpoint bp; switch ( mode ) { //------------------------------------------------------------------------------------- case StepMode.e_in: Location next_loc = images.findNextSrcLine(loc); if ( next_loc !is null ) { bp = setBreakpoint(next_loc, bpi, thread_id); if ( bp !is null ) addForeachBreakpoint(bp); } size_t disasm_end; if ( loc.codeblock !is null ) disasm_end = loc.codeblock.end+loc.getCodeBase; DisAsm.disasm( process, current_address, disasm_end, (ud_t* ud_obj) { return setBranchBreakpoints(ud_obj,StepMode.e_in); } ); break; //------------------------------------------------------------------------------------- case StepMode.e_out: uint ret_adr = getReturnAddress(loc); if ( ret_adr > 0 ) { debug DbgIO.println("setting step-out to 0x%08x", ret_adr); bp = setBreakpoint(ret_adr, bpi, thread_id); if ( bp !is null ) bp.frame_ptr = getFramePointer(loc, 1); } else { debug DbgIO.println("no scope found - falling back on disasm-step-out"); DisAsm.disasm( process, current_address, 0, (ud_t* ud_obj) { return setBranchBreakpoints(ud_obj,StepMode.e_out); } ); } break; //------------------------------------------------------------------------------------- case StepMode.e_over: Location next_loc = images.findNextSrcLine(loc); if ( next_loc !is null ) { bp = setBreakpoint(next_loc, bpi, thread_id); if ( bp !is null ) { bp.frame_ptr = getFramePointer(loc); debug DbgIO.println("FP for step over is 0x%x", bp.frame_ptr); addForeachBreakpoint(bp); } } size_t disasm_end; if ( loc.codeblock !is null ) disasm_end = loc.codeblock.end+loc.getCodeBase; DisAsm.disasm( process, current_address, disasm_end, (ud_t* ud_obj) { return setBranchBreakpoints(ud_obj,StepMode.e_over); } ); break; default: assert(0); } return true; } /********************************************************************************************** **********************************************************************************************/ void addForeachBreakpoint(Breakpoint foreach_end) { CodeView cv; CodeBlock cb = images.findCodeBlockAbs(current_address, cv); if ( cb !is null ) { CodeBlock cbs[] = cb.segment.file.blocks_by_line[cb.line]; if ( cbs.length > 1 && cb is cbs[0] ) { Location loc = images.findLocation(cbs[1].start+cv.getCodeBase); if ( loc.scope_sym !is null && find(loc.scope_sym.mangled_name, "__foreachbody") >= 0 ) { int bpi; if ( getBreakpoint(loc.address, bpi) is null ) { Breakpoint bp = new Breakpoint(loc, true, thread_id); bp.foreach_end = foreach_end; foreach_end.foreach_end = foreach_end; addBreakpoint(bp, -1); } } } } } /********************************************************************************************** **********************************************************************************************/ uint getReturnAddress(Location loc) { ProcedureSymbol psym = cast(ProcedureSymbol)loc.scope_sym; if ( psym !is null ) { uint index; ubyte[] frame = stack.firstFrame(index); if ( frame is null ) throw new DebuggerException("getReturnAddress: null frame"); uint adr = current_address-psym.cvdata.offset-loc.getCodeBase; if ( adr >= psym.cvdata.debug_start && adr < psym.cvdata.debug_end ) { frame = stack.prevFrame(index, index); if ( frame.length >= 8 ) return (cast(uint[])frame)[1]; } } return 0; } /********************************************************************************************** **********************************************************************************************/ uint getFramePointer(Location loc, uint level=0) { ProcedureSymbol psym = cast(ProcedureSymbol)loc.scope_sym; if ( psym !is null ) { uint frame_ptr; if ( level > 0 ) { uint index, l; ubyte[] frame = stack.firstFrame(index); if ( frame is null ) throw new DebuggerException("getCurrenFramePointer: null frame"); for ( l = 0; l < level && frame !is null; ++l ) frame = stack.prevFrame(index, index); if ( frame is null ) throw new DebuggerException("getCurrenFramePointer: null frame at level %d", l); frame_ptr = (cast(uint[])frame)[0]; } else frame_ptr = stack.frame_ptr; uint adr = current_address-psym.cvdata.offset-loc.getCodeBase; if ( adr >= psym.cvdata.debug_start && adr < psym.cvdata.debug_end ) return frame_ptr; } return 0; } /********************************************************************************************** **********************************************************************************************/ SymbolData evaluateExpression(string expr, uint frame_level=0, NamedSymbol symbol=null) { SyntaxTree* root; bool success; try success = parse("", expr, root, true); catch( ParserException e ) throw new EvaluationException("Parser: "~e.toString); if ( !success ) throw new EvaluationException("Parser: Invalid expression!"); string elmtype; uint scope_address; if ( frame_level == 0 ) scope_address = current_address; else { ubyte[] frame = stack.getFrame(frame_level); scope_address = (cast(uint[])frame)[1]; } SymbolData sd; auto loc = images.findLocation(current_address); if ( loc.codeview is null ) throw new EvaluationException("No debug symbols available"); root.Expr(new EvaluationContext( loc.codeview, scope_address, miniDump, process, process is null?null:process.threads[thread_id], stack, frame_level), sd ); return sd; } /********************************************************************************************** **********************************************************************************************/ SymbolValue handleData(SymbolData sd, bool decreaseED) { auto loc = images.findLocation(current_address); if ( loc.codeview is null ) throw new EvaluationException("No debug symbols available"); EvaluationContext ctx = new EvaluationContext( loc.codeview, 0, miniDump, process, process is null?null:process.threads[thread_id], stack, 0 ); ubyte[] data = sd.getData(ctx); if ( data.length <= 0 ) throw new EvaluationException("Expression evaluated to empty data"); uint ed = evaluationDepth; if ( decreaseED ) --ed; SymbolValue val = DataHandler.handle(ctx, sd.type, data, ed); if ( val is null ) throw new EvaluationException("No DataHandler for type "~sd.type); return val; } /********************************************************************************************** **********************************************************************************************/ uint getScopeAddress(uint frame_level) { uint scope_address; if ( frame_level == 0 ) scope_address = current_address; else { ubyte[] frame = stack.getFrame(frame_level); if ( frame !is null ) scope_address = (cast(uint[])frame)[1]; } return scope_address; } //============================================================================================= // process & thread handling /********************************************************************************************** **********************************************************************************************/ void selectThread(size_t threadId) { thread_id = threadId; current_stack = null; if ( process !is null ) { CONTEXT ctx; if ( !process.threads[threadId].getContext(ctx) ) throw new DebuggerException("Couldn't get thread's context"); current_address = ctx.Eip; } else if ( miniDump !is null ) { miniDump.selectThread(threadId); CONTEXT* ctx = miniDump.getContext; current_address = ctx.Eip; } else throw new DebuggerException("Invalid debugger state"); } /********************************************************************************************** Loads and parses the PE image. Returns: success **********************************************************************************************/ void openImage(string filename) { assert(filename.length); filename = strip(filename); if ( !exists(filename) ) filename ~= ".exe"; if ( !exists(filename) ) throw new DebuggerException("Couldn't open \"%s\"", filename[0..$-4]); main_image_file = filename.dup; main_image = new COFFImage; main_image.load(main_image_file); // catch ( Exception e ) { // throw new DebuggerException("Failed to load COFF image \""~main_image_file~"\": "~e.msg); // } if ( main_image.codeView is null ) DbgIO.println("WARNING: No debugging symbols found in main image, try compiling and linking with -g"); images ~= main_image; // return true; } /********************************************************************************************** Creates the child process for the debuggee. Returns: success **********************************************************************************************/ bool createDebugProcess(string command_line) { // initialiZe process startup information STARTUPINFOA* startInfo = new STARTUPINFOA; startInfo.cb = STARTUPINFO.sizeof; startInfo.dwFlags = STARTF_FORCEONFEEDBACK | STARTF_USESHOWWINDOW; startInfo.wShowWindow = SW_SHOWNORMAL; PROCESS_INFORMATION procInfo; // process info // create process uint flags = DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS; if ( create_new_console ) flags |= CREATE_NEW_CONSOLE; bool suc = cast(bool)CreateProcessA( toStringz(main_image_file), toStringz(command_line), cast(LPSECURITY_ATTRIBUTES)null, cast(LPSECURITY_ATTRIBUTES)null, false, flags, null, cast(char*)null, cast(LPSTARTUPINFOA)startInfo, cast(LPPROCESS_INFORMATION)&procInfo ); if( !suc ) throw new DebuggerException("CreateProcess failed on %s", main_image_file); assert(procInfo.dwProcessId); // open process for all access HANDLE hChildProcess = OpenProcess(PROCESS_ALL_ACCESS, false, procInfo.dwProcessId); if( hChildProcess == null ) { TerminateProcess(procInfo.hProcess, 0); throw new DebuggerException("OpenProcess failed"); } process_id = procInfo.dwProcessId; CloseHandle(procInfo.hProcess); return true; } /********************************************************************************************** **********************************************************************************************/ void resume() { single_step = false; paused = false; } //============================================================================================= // exception handling /********************************************************************************************** Debugger's main loop. Handles win32 debugging events. **********************************************************************************************/ void start(string command_line) { createDebugProcess(command_line); while( !abort ) { if( WaitForDebugEvent(&debug_event, win32.winbase.INFINITE) ) { if( debug_event.dwProcessId != process_id ) { debug DbgIO.println("WARNING: Exception for unhandled process received: PID=%d", debug_event.dwProcessId); ContinueDebugEvent(debug_event.dwProcessId, debug_event.dwThreadId, DBG_CONTINUE); continue; } current_stack = null; thread_id = debug_event.dwThreadId; exceptionRecord = null; bool exception_handled = true; switch( debug_event.dwDebugEventCode ) { case OUTPUT_DEBUG_STRING_EVENT: char[] debug_string; paused = false; debug_string.length = debug_event.DebugString.nDebugStringLength; process.readProcessMemory(cast(uint)debug_event.DebugString.lpDebugStringData, debug_string.ptr, debug_string.length); if ( debug_event.DebugString.fUnicode ) DbgIO.println("WARNING: Unicode debug strings not implemented, yet. Output might be corrupted"); if ( debug_string.length > 3 && debug_string[0..4] == "DDL:" ) { string cmdStr = debug_string[4..$]; if ( strip(cmdStr).length > 0 ) { auto r = std.regexp.RegExp("(([^\" \\t]+)|(\"[^\"]+\"))+"); string[] cmd; foreach ( m; r.search(strip(cmdStr)) ) cmd ~= r.match(0); switch ( cmd[0] ) { case "load": DbgIO.println("loadImage not implemented, yet: %s %s", cmd[1], cmd[2]); break; default: DbgIO.println("WARNING: Unknown DDL command recieved: %s", cmdStr); break; } } } else if ( debug_string.length > 5 && debug_string[0..5] == "Ddbg:" ) { ui.runCommands(debug_string[5..$]); paused = true; } else ui.debugString(debug_string); break; case EXCEPTION_DEBUG_EVENT: exceptionRecord = &debug_event.Exception.ExceptionRecord; exception_handled = handleExceptionEvent(exceptionRecord); break; case LOAD_DLL_DEBUG_EVENT: DLL dll = process.loadDLL(&debug_event.LoadDll); if ( dll.image !is null ) { images ~= dll.image; foreach ( bp; breakpoints ) { if ( bp.location.address == 0 ) { debug DbgIO.println("binding bp %s", bp.toString); if ( bp.location.bind(images, source_search_paths) ) { debug DbgIO.println("activating bp %s", bp.toString); bp.activate(process); } } } } ui.loadedDLL(dll); paused = false; break; case UNLOAD_DLL_DEBUG_EVENT: // DebugDLL* dll = process.findDLL(cast(uint)debug_event.LoadDll.lpBaseOfDll); // ui.unloadedDLL(dll); // images.remove(dll.image); paused = false; break; case CREATE_THREAD_DEBUG_EVENT: process.threads[debug_event.dwThreadId] = new DbgThread(debug_event.CreateThread.hThread, debug_event.dwThreadId); paused = false; break; case EXIT_THREAD_DEBUG_EVENT: process.threads.remove(debug_event.dwThreadId); paused = false; break; case CREATE_PROCESS_DEBUG_EVENT: process = new DbgProcess; process.processId = process_id; process.process_handle = debug_event.CreateProcessInfo.hProcess; process.mainThreadId = debug_event.dwThreadId; process.threads[debug_event.dwThreadId] = new DbgThread(debug_event.CreateProcessInfo.hThread, debug_event.dwThreadId); process_loaded = true; if ( main_image !is null && main_image.codeView !is null ) { DataSymbol mainds; if ( main_image.codeView.global_pub !is null ) mainds = main_image.codeView.global_pub.findDataSymbol("_main"); if ( mainds !is null ) { int bpi=-1; Breakpoint bp = setBreakpoint(mainds.offset+main_image.getCodeBase, bpi, 0, true); bp.deactivate_d_exception_handler = true; debug DbgIO.println("set breakpoint"); } { int bpi=-1; // stop on first line if (breakonmain) { breakonmain=false; mainds = main_image.codeView.global_pub.findDataSymbol("__Dmain"); if ( mainds !is null ) { Breakpoint bp = setBreakpoint(mainds.offset+main_image.getCodeBase, bpi, thread_id); } } } } foreach ( bp; breakpoints ) { if ( bp.location.address != 0 && !bp.activate(process) ) throw new DebuggerException("Error: couldn't write breakpoint opcode at %s:%d", bp.file, bp.line); } foreach ( bp; temp_breakpoints ) { debug DbgIO.println("tbp activated %s", bp.value); if ( bp.value.location.address != 0 && !bp.value.activate(process) ) throw new DebuggerException("Error: couldn't write breakpoint opcode at %s:%d", bp.value.file, bp.value.line); } break; case EXIT_PROCESS_DEBUG_EVENT: process_loaded = false; delete process; paused = true; single_step = false; ui.exitProcess; break; case RIP_EVENT: debug DbgIO.println("DebugEvent: "~getEventName(debug_event.dwDebugEventCode)); paused = true; break; default: throw new DebuggerException("Win32Debugger ERROR - unknown debug event: %d", debug_event.dwDebugEventCode); } if ( single_step ) { ui.singleStep(); paused = true; activateSingleStep(true); } if ( paused ) { bool cont=false; while ( !cont ) { debug cont = ui.readCommand(); else { try cont = ui.readCommand(); catch ( Exception e ) DbgIO.println("%s", e.msg); } } paused = false; } if ( !abort ) { exceptionRecord = null; ContinueDebugEvent( debug_event.dwProcessId, debug_event.dwThreadId, exception_handled?DBG_CONTINUE:DBG_EXCEPTION_NOT_HANDLED ); } } else throw new DebuggerException("ERROR: WaitForDebugEvent failed: %s", lastError); } } /********************************************************************************************** Activates single step execution by setting the trap flag in the eflags register of the currently considered thread. **********************************************************************************************/ void activateSingleStep(bool enable) { if ( !(thread_id in process.threads) ) return; if ( enable ) process.threads[thread_id].changeContext(delegate(ref CONTEXT ctx){ ctx.EFlags |= 1<<8; }); else if ( !enable ) process.threads[thread_id].changeContext(delegate(ref CONTEXT ctx){ ctx.EFlags &= ~(1<<8); }); single_step = enable; } /********************************************************************************************** **********************************************************************************************/ void getExceptionNameMessage(size_t objPtr, out char[] class_name, out char[] msg) { // get thrown object's class name ClassInfo ci = process.getClassInfo(objPtr); class_name.length = ci.name.length; process.readProcessMemory(cast(uint)ci.name.ptr, class_name.ptr, class_name.length); // get object's data uint[] objdata; objdata.length = ci.init.length; process.readProcessMemory(objPtr, objdata.ptr, objdata.length*uint.sizeof); char[] name; name ~= class_name; do { if ( name == "object.Exception" ) { msg.length = objdata[2]; process.readProcessMemory(objdata[3], msg.ptr, msg.length); break; } ubyte[] data; data.length = ClassInfo.classinfo.init.length; process.readProcessMemory(cast(uint)cast(void*)ci.base, data.ptr, data.length); ci = cast(ClassInfo)data.ptr; name.length = ci.name.length; process.readProcessMemory(cast(uint)ci.name.ptr, name.ptr, name.length); } while ( ci.base !is null ); } /********************************************************************************************** **********************************************************************************************/ bool handleExceptionEvent(EXCEPTION_RECORD* exrec) { current_address = cast(uint)exrec.ExceptionAddress; Lswitch: switch ( exrec.ExceptionCode ) { case EXCEPTION_BREAKPOINT: debug DbgIO.println("EXCEPTION_BREAKPOINT"); int bp_index; Breakpoint bp = getBreakpoint(current_address, bp_index); if ( bp !is null ) { handleBreakpoint(bp, bp_index); last_line = bp.line; } else ui.breakpoint(-1, new Breakpoint(images.findLocation(cast(uint)exrec.ExceptionAddress), false, 0, false), process.threads[thread_id]); break; case EXCEPTION_SINGLE_STEP: debug DbgIO.println("EXCEPTION_SINGLE_STEP"); // check whether this is a HWBP or real single step CONTEXT ctx; process.threads[thread_id].getContext(ctx, CONTEXT_DEBUG_REGISTERS); if ( (ctx.Dr6 & 0xf) > 0 ) { debug DbgIO.println("Hardware breakpoint"); size_t dr = void; if ( ctx.Dr6 & 1 ) dr = ctx.Dr0; else if ( ctx.Dr6 & 2 ) dr = ctx.Dr1; else if ( ctx.Dr6 & 4 ) dr = ctx.Dr2; else if ( ctx.Dr6 & 8 ) dr = ctx.Dr3; int bp_index; foreach( i, bp; breakpoints ) { if( !bp.hardware ) continue; if ( dr == bp.address ) { handleBreakpoint(bp, i); break Lswitch; } } ui.breakpoint(-1, new Breakpoint(images.findLocation(dr), false, 0, false), process.threads[thread_id]); } else { debug DbgIO.println("Single Step exception"); // pause if interactive single step is active paused = single_step; if ( passing_breakpoint !is null ) { debug DbgIO.println("passing breakpoint"); activateSingleStep(false); paused = false; if ( process !is null && !passing_breakpoint.activate(process) ) throw new DebuggerException("ERROR: Failed to write breakpoint opcode at %s:%d", passing_breakpoint.file, passing_breakpoint.line); passing_breakpoint = null; } } break; // ctrl+c case DBG_CONTROL_C: ui.userInterrupt(); paused = true; break; // D exceptions case STATUS_DIGITAL_MARS_D_EXCEPTION: // normal non-post-mortem mode if ( process !is null && !debug_event.Exception.dwFirstChance ) { char[] class_name, msg; getExceptionNameMessage(exrec.ExceptionInformation[0], class_name, msg); ui.exception(thread_id, class_name, msg, exrec.ExceptionInformation[0]); paused = true; return false; } // minidump mode else if ( miniDump !is null ) { string className, message; className = (cast(char*)exrec.ExceptionInformation[1])[0..exrec.ExceptionInformation[0]]; message = (cast(char*)exrec.ExceptionInformation[3])[0..exrec.ExceptionInformation[2]]; ui.exception(thread_id, className, message, 0); paused = true; return false; } paused = false; return false; default: ui.win32exception(thread_id, exrec); paused = true; single_step = false; return false; } return true; } /********************************************************************************************** **********************************************************************************************/ void handleBreakpoint(Breakpoint bp, uint bp_index) { CONTEXT ctx; debug DbgIO.println("handling breakpoint"); if ( !process.threads[thread_id].getContext(ctx) ) throw new DebuggerException("Error: Couldn't GetThreadContext to reset instruction pointer: %s", lastError); if ( !bp.hardware ) { if ( process !is null && !bp.deactivate(process) ) throw new DebuggerException("ERROR: Failed to write opcode for breakpoint at %s:%d", bp.file, bp.line); // adjust for "int 3" instruction byte --ctx.Eip; if ( !process.threads[thread_id].setContext(ctx) ) throw new DebuggerException("Error: Couldn't SetThreadContext to reset instruction pointer: %s", lastError); } // check if we have a constrained thread if ( bp.threadId > 0 && thread_id != bp.threadId ) { debug DbgIO.println("skipping bp in incorrect thread %d", thread_id); if ( !bp.hardware ) { passing_breakpoint = bp; activateSingleStep(true); } } // check if breakpoint requires a certain frame else if ( bp.frame_ptr > 0 && bp.frame_ptr != ctx.Ebp ) { debug DbgIO.println("skipping bp in incorrect frame, Ebp=0x%x required=0x%x", ctx.Ebp, bp.frame_ptr); if ( !bp.hardware ) { passing_breakpoint = bp; activateSingleStep(true); } } // restore non-temporary breakpoints else if ( !bp.temporary ) { ui.breakpoint(bp_index, bp, process.threads[thread_id]); paused = true; if ( !bp.hardware ) { passing_breakpoint = bp; activateSingleStep(true); } } // propagate breakpoints to jmp/call/ret destination else if ( bp.propagate ) { debug DbgIO.println("propagate"); foreach ( tbp; temp_breakpoints ) { if ( tbp.value is bp ) { temp_breakpoints.remove(tbp); break; } } DisAsm.disasm(process, current_address, 0, (ud_t* ud_obj){return setPropagatedBreakpoint(ud_obj,bp);}); } // temporary breakpoints else { if ( bp.deactivate_d_exception_handler ) { debug DbgIO.println("deactivateDExceptionHandler"); deactivateDExceptionHandler(); } else { clearTemporaryBreakpoints(); paused = ui.breakpoint(-1, bp, process.threads[thread_id]); } } } /********************************************************************************************** **********************************************************************************************/ void deactivateDExceptionHandler() { if ( main_image is null || main_image.codeView is null ) return; // deactivate default D exception handling bool found=false; foreach ( ds; main_image.codeView.global_pub.data_symbols ) { if ( ds.name_notype == "_no_catch_exceptions" ) { uint address = ds.offset+main_image.getSectionBase(ds.cvdata.segment); bool nce = true; process.writeProcessMemory(address, &nce, bool.sizeof); found = true; break; } else if ( ds.name_notype == "_cr_trapExceptions" || ds.name_notype == "_rt_trapExceptions" ) { uint address = ds.offset+main_image.getSectionBase(ds.cvdata.segment); bool nce = false; process.writeProcessMemory(address, &nce, bool.sizeof); found = true; break; } } if ( !found ) { debug DbgIO.println("Neither Phobos nor Tango exception handler switch not found"); } } //============================================================================================= // source&symbol handling /********************************************************************************************** Searches the cache for the given source file and loads it if nessecary. Tries to load the source file from all source_search_paths. Returns: Array of lines of the source file. **********************************************************************************************/ string[] getSourceFile(string filename) { if ( filename is null || filename.length <= 0 ) return null; if ( !(filename in source_files) ) { string full = getFullSourcePath(filename); if ( full is null || !exists(full) ) return null; auto lines = splitlines(cast(string)read(full)); source_files[filename] = lines; } return source_files[filename]; } /********************************************************************************************** Tries to find the source file in all source_search_paths. Returns: Full path of the file **********************************************************************************************/ string getFullSourcePath(string filename) { string full = getFullPath(filename); if ( exists(full) ) return full; foreach ( string sp; source_search_paths ) { debug DbgIO.println("searching for source file %s",sp~filename); full = getFullPath(sp~filename); if ( exists(full) ) return full; } return filename; } //============================================================================================= // breakpoint handling /********************************************************************************************** **********************************************************************************************/ bool setPropagatedBreakpoint(ud* ud_obj, Breakpoint prev_bp) { if ( ud_obj is null ) return true; int bpi=-1; Breakpoint bp; if ( DisAsm.isConditionalJump(ud_obj.mnemonic) || ud_obj.mnemonic == ud_mnemonic_code.UD_Ijmp || ud_obj.mnemonic == ud_mnemonic_code.UD_Icall ) { bool is_imm=false; uint jmp_dest = DisAsm.calcJumpDestination(process, process.threads[thread_id], ud_obj, is_imm); assert(!is_imm); debug DbgIO.println("propagating jmp/call bp to 0x%x", jmp_dest); Location loc = images.findLocation(jmp_dest); if ( loc.file !is null ) bp = setBreakpoint(jmp_dest, bpi, thread_id); else { debug DbgIO.println("not source info available - checking for unconditional jmp at destination"); DisAsm.disasm(process, jmp_dest, 0, (ud_t* ud_obj){return propagateUncondJmp(ud_obj, prev_bp);}); } if ( bp !is null ) bp.frame_ptr = prev_bp.frame_ptr; } else if ( ud_obj.mnemonic == ud_mnemonic_code.UD_Iret || ud_obj.mnemonic == ud_mnemonic_code.UD_Iretf ) { uint index; size_t ret_adr = (cast(uint[])stack.data)[0]; Location loc = images.findLocation(ret_adr); if ( loc.file !is null ) bp = setBreakpoint(ret_adr, bpi, thread_id); else debug DbgIO.println("not setting BP on unknown ret address 0x%x", ret_adr); if ( bp !is null ) bp.frame_ptr = prev_bp.frame_ptr; } else { debug DbgIO.println("unknown instruction for propagating breakpoint"); assert(0); } return false; } /********************************************************************************************** Propagate breakpoint if unconditional jump is found. Used for virtual functions with redirectors. **********************************************************************************************/ bool propagateUncondJmp(ud* ud_obj, Breakpoint prev_bp) { if ( ud_obj is null ) return true; int bpi=-1; Breakpoint bp; if ( DisAsm.isConditionalJump(ud_obj.mnemonic) || ud_obj.mnemonic == ud_mnemonic_code.UD_Icall ) { debug DbgIO.println("aborting propagateUncondJmp: condJmp or call"); return false; } else if ( ud_obj.mnemonic == ud_mnemonic_code.UD_Ijmp ) { bool is_imm=false; uint jmp_dest = DisAsm.calcJumpDestination(process, process.threads[thread_id], ud_obj, is_imm); debug DbgIO.println("propagating bp to 0x%x", jmp_dest); Location loc = images.findLocation(jmp_dest); if ( loc.file !is null ) bp = setBreakpoint(jmp_dest, bpi, thread_id); else { debug DbgIO.println("not source info available - checking for unconditional jmp at destination"); } if ( bp !is null ) bp.frame_ptr = prev_bp.frame_ptr; return false; } else if ( ud_obj.mnemonic == ud_mnemonic_code.UD_Iret || ud_obj.mnemonic == ud_mnemonic_code.UD_Iretf ) { debug DbgIO.println("aborting propagateUncondJmp: ret"); return false; } else return true; } /********************************************************************************************** If the opcode is an immediate jump or a call, sets a temporary breakpoint at it's destination. If it's a memory call, sets a call-propagating breakpoint at the call instruction. **********************************************************************************************/ bool setBranchBreakpoints(ud_t* ud_obj, StepMode mode) { // first call if ( ud_obj is null ) return true; int bpi=-1; Breakpoint bp; if ( DisAsm.isConditionalJump(ud_obj.mnemonic) || ud_obj.mnemonic == ud_mnemonic_code.UD_Ijmp ) { if ( mode!=StepMode.e_out ) { bool is_imm=true; uint jmp_dest = DisAsm.calcJumpDestination(process, process.threads[thread_id], ud_obj, is_imm); if ( is_imm ) bp = setBreakpoint(jmp_dest, bpi, thread_id, false); else { // set propagating breakpoint bp = setBreakpoint(cast(size_t)ud_obj.insn_offset, bpi, thread_id, true); if ( bp !is null ) bp.propagate = true; } } } else if ( ud_obj.mnemonic == ud_mnemonic_code.UD_Icall ) { if ( mode==StepMode.e_in ) { bool is_imm=true; uint jmp_dest = DisAsm.calcJumpDestination(process, process.threads[thread_id], ud_obj, is_imm); if ( is_imm ) { Location loc = images.findLocation(jmp_dest); if ( loc.file !is null ) bp = setBreakpoint(jmp_dest, bpi, thread_id, false); else debug DbgIO.println("not setting BP on unknown call destination 0x%x", jmp_dest); } else { // set propagating breakpoint bp = setBreakpoint(cast(size_t)ud_obj.insn_offset, bpi, thread_id, true); if ( bp !is null ) bp.propagate = true; } } } else if ( ud_obj.mnemonic == ud_mnemonic_code.UD_Iret || ud_obj.mnemonic == ud_mnemonic_code.UD_Iretf ) { if ( mode!=StepMode.e_out ) { Location loc = images.findLocation(current_address); if ( loc.scope_sym !is null && find(loc.scope_sym.mangled_name, "__foreachbody") >= 0 ) { ProcedureSymbol ps = cast(ProcedureSymbol)loc.scope_sym; if ( ps !is null ) { setBreakpoint(ps.cvdata.offset+loc.getCodeBase, bpi, thread_id); uint ret_adr = getReturnAddress(images.findLocation(getReturnAddress(loc))); if ( ret_adr > 0 ) setBreakpoint(ret_adr, bpi, thread_id); } } if ( mode != StepMode.e_over || loc.scope_sym is null || find(loc.scope_sym.mangled_name, "__foreachbody") < 0 ) { uint ret_adr = getReturnAddress(loc); if ( ret_adr > 0 ) { debug DbgIO.println("setBranchBPs: using return address from frame"); loc = images.findLocation(ret_adr); if ( loc.file !is null ) bp = setBreakpoint(ret_adr, bpi, thread_id, true); else debug DbgIO.println("not setting BP on unknown ret address 0x%x", ret_adr); } else { bp = setBreakpoint(cast(size_t)ud_obj.insn_offset, bpi, thread_id, true); if ( bp !is null ) bp.propagate = true; } } } else { bp = setBreakpoint(cast(size_t)ud_obj.insn_offset, bpi, thread_id, true); if ( bp !is null ) bp.propagate = true; } } return true; } /********************************************************************************************** Removes breakpoint from list and deactivates it. Returns: success **********************************************************************************************/ bool removeBreakpoint(uint bp_index) { if ( bp_index in breakpoints ) { Breakpoint bp = breakpoints[bp_index]; if ( process !is null && !bp.deactivate(process) ) throw new DebuggerException("ERROR: Failed to write breakpoint opcode at %s:%d", bp.file, bp.line); breakpoints.remove(bp_index); if ( passing_breakpoint is bp ) { passing_breakpoint = null; activateSingleStep(false); } delete bp; return true; } return false; } /********************************************************************************************** **********************************************************************************************/ void clearTemporaryBreakpoints() { debug DbgIO.println("clearing temporary breakpoints"); if ( process !is null ) { foreach ( bp; temp_breakpoints ) { if ( bp.value.foreach_end !is null && bp.value.foreach_end.address != current_address ) { if ( !bp.value.hardware ) passing_breakpoint = bp.value; activateSingleStep(true); continue; } if ( !bp.value.deactivate(process) ) throw new DebuggerException("ERROR: Failed to restore opcode for breakpoint at %s:%d", bp.value.file, bp.value.line); temp_breakpoints.remove(bp); } } } /********************************************************************************************** Creates a breakpoint at the given location and adds it with the given index. Overwrites the breakpoint at the same index if existant. Adds a temporary breakpoint instead if index < 0. Reaturns: Existing breakpoint at the location or new breakpoint. **********************************************************************************************/ Breakpoint setBreakpoint(Location loc, inout int new_index, uint threadId, bool hardware=false) { if ( loc is null ) return null; // lookup existing breakpoint Breakpoint bp; if ( new_index >= 0 ) { int bp_index; bp = getBreakpoint(loc, bp_index); if( bp !is null ) { new_index = bp_index; return bp; } } // create a breakpoint bp = new Breakpoint(loc, new_index<0, threadId, hardware); addBreakpoint(bp, new_index); return bp; } /********************************************************************************************** Creates a breakpoint at the given address and adds it with the given index. Overwrites the breakpoint at the same index if existant. Adds a temporary breakpoint instead if index < 0. Reaturns: Existing breakpoint at the address or new breakpoint. **********************************************************************************************/ Breakpoint setBreakpoint(size_t address, inout int new_index, uint threadId, bool set_on_current_line=false, bool hardware=false) { Location loc = images.findLocation(address); Breakpoint bp; int bp_index; bp = getBreakpoint(address, bp_index); if( bp !is null ) { debug DbgIO.println("instead 0x%08x using existing breakpoint at 0x%08x", address, bp.address); new_index = bp_index; return bp; } if ( !set_on_current_line && loc.codeblock !is null && current_address == loc.codeblock.start+loc.getCodeBase ) { debug DbgIO.println("not setting bp at current 0x%08x", address); return null; } debug DbgIO.println("setting breakpoint 0x%08x", address); bp = new Breakpoint(loc, new_index<0, threadId, hardware); addBreakpoint(bp, new_index); return bp; } /********************************************************************************************** Adds the given breakpoint with at the given index. Adds it as temporary breakpoint if new_index < 0. Activates the breakpoint. **********************************************************************************************/ void addBreakpoint(Breakpoint bp, int new_index) { // add to breakpoint list debug DbgIO.println("adding breakpoint 0x%08x", bp.address); if ( bp.temporary ) temp_breakpoints ~= bp; else breakpoints[new_index] = bp; // set breakpoing as active in debugger if ( process !is null && !bp.activate(process) ) throw new DebuggerException("ERROR: Failed to write breakpoint opcode at %s:%d", bp.file, bp.line); } /********************************************************************************************** Searches the breakpoints for one that is set at the given source location. Returns: Index of the breakpoint in the breakpoints array. **********************************************************************************************/ Breakpoint getBreakpoint(Location loc, out int bp_index) { foreach( uint i, Breakpoint bp; breakpoints ) { if( !bp.hardware && bp.file == loc.file && bp.line == loc.line || bp.hardware && bp.address == loc.address ) { bp_index = i; return bp; } } foreach( bp; temp_breakpoints ) { if( !bp.value.hardware && bp.value.file == loc.file && bp.value.line == loc.line || bp.value.hardware && bp.value.address == loc.address ) { bp_index = -1; return bp.value; } } return getBreakpoint(loc.address, bp_index); } /********************************************************************************************** Searches for a breakpoint at the given address. Returns: Index of breakpoint in breakpoints array. **********************************************************************************************/ Breakpoint getBreakpoint(uint address, out int bp_index) { foreach ( uint i, Breakpoint bp; breakpoints ) { if ( bp.address == address ) { bp_index = i; return bp; } } foreach ( bp; temp_breakpoints ) { if ( bp.value.address == address ) { bp_index = -1; return bp.value; } } return null; } //============================================================================================= // Minidumps void writeMiniDump(string filename) { char[] class_name, msg; if ( exceptionRecord !is null && exceptionRecord.ExceptionCode != EXCEPTION_BREAKPOINT && exceptionRecord.ExceptionCode != EXCEPTION_SINGLE_STEP && exceptionRecord.ExceptionCode != DBG_CONTROL_C ) { getExceptionNameMessage(exceptionRecord.ExceptionInformation[0], class_name, msg); } MiniDump.writeMiniDump(filename, process, thread_id, exceptionRecord, class_name, msg); } void readMiniDump(string filename) { try { miniDump = new MiniDump(filename); selectThread(miniDump.threads[miniDump.selectedThread].ThreadId); if ( miniDump.exceptionRecord !is null ) handleExceptionEvent(miniDump.exceptionRecord); else thread_id = miniDump.threadInfo.currentThreadId; //miniDump.threadInfo.mainThreadId } catch ( Exception e ) DbgIO.writeln("Error: "~e.msg); //return miniDump !is null; } }