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;
    }
}