Mercurial > projects > ddbg_continued
diff src/debugger.d @ 1:4a9dcbd9e54f
-files of 0.13 beta
-fixes so that it now compiles with the current dmd version
author | marton@basel.hu |
---|---|
date | Tue, 05 Apr 2011 20:44:01 +0200 |
parents | |
children | 47dd986a534e |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/debugger.d Tue Apr 05 20:44:01 2011 +0200 @@ -0,0 +1,1378 @@ +/* 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 = 12, + VERSION_PATCH = 0; +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; + + size_t current_address; + uint last_line; + + 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 = 1; + 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; + } + } + + 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; + 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 ) + { + 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; + } + 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 + 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; + } +}