现在的位置: 首页 > 综合 > 正文

写个自己的调试器

2013年10月07日 ⁄ 综合 ⁄ 共 5877字 ⁄ 字号 评论关闭
对于写了一段时间的程序员来说,了解一些debugger的实质无疑对于技术的提高是有很大帮助的。而debugging自身也是一门非常细节化,比较复杂的技术。好的Debug工具如SoftICE,也是技术稍深一些的程序员必备的技术之一。这篇随笔并不会去讨论Debug技术的实质,而只是利用Platform SDK和最新的DbgHelp.dll提供的API作为引擎写一个自己的debugger,也即是写一个实用的debuggerHost端。呵呵,本人不擅于写文章,所以下面就指一个例子来说,即如何给一个进程设下断点并捕获。其实看过IA32的人都知道,CPUDebug提供了强力的支持,比如设单步跟踪的标志;再比如BIOS代码的调试,对于ROMBIOS代码是无法进行插CCint 3)指令进行断点设置的,这时CPU提供了地址中断,执行至指定地址时,CPU自行中断。在这里并不会提及这些内容,当然也许有时间,或大家感兴趣,我可以写点或讨论一二。下面单就如何给进行设断点来说说,即然是随笔,所以可能会有错别字,欢迎大家来找别字 ^_^

       调试目标程序时,你得有相应的权限。用下面这个函数得到它:

//--------------------------------------------------------------------------

// EnableDebugAccessCtl

// Description: Enable or disable debug access control

// Return value: 0 means succeed

DWORD EnableDebugAccessCtl(BOOL bEnable)

{

    HANDLE hTokenHandle = NULL;

    if( !OpenProcessToken(GetCurrentProcess(),

                            TOKEN_ADJUST_PRIVILEGES,

                            &hTokenHandle ) )

    {

        return GetLastError();

    }

    // Lookup the privilege value

    TOKEN_PRIVILEGES tp;

    tp.PrivilegeCount = 1;

    if( !LookupPrivilegeValue( NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid ) )

    {

        CloseHandle(hTokenHandle);

        return -1;

    }

    // Enable/disable the privilege

    tp.Privileges[0].Attributes = bEnable ? SE_PRIVILEGE_ENABLED : 0;

    if( !AdjustTokenPrivileges( hTokenHandle, FALSE, &tp, sizeof(tp), NULL, NULL ) )

    {

        CloseHandle(hTokenHandle);

        return -1;

    }

 

    CloseHandle(hTokenHandle);

    return 0;

}

       我们可以attach一个正在运行的进行或重新启动一个进程进行调试。DebugActiveProcess可以实现第一种情况;第二种情况可以用CreateProcess,下面是代码,注意创建标志。

// Create Process

    STARTUPINFO si;

    PROCESS_INFORMATION pi;

    ZeroMemory( &si, sizeof(si) );

    si.cb = sizeof(si);

    ZeroMemory( &pi, sizeof(pi) );

    if( !CreateProcess(NULL, csCmdParms.GetBuffer(),

                        NULL, NULL, FALSE,

                           CREATE_NEW_CONSOLE | DEBUG_ONLY_THIS_PROCESS,

                        NULL, NULL,

                        &si, &pi ) )

    {

        return GetLastError();     

    }

 

这下初调试的程序就跑起来了,接下来一般来用一个循环来获取目标进行的Debug事件。主循环看起来应该是这样的,嘿,MSDN给了你一个模板,那就用吧:

DEBUG_EVENT DebugEv;                   // debugging event information 
DWORD dwContinueStatus = DBG_CONTINUE; // exception continuation 

for(;;) 

{  

// Wait for a debugging event to occur. The second parameter indicates 

// that the function does not return until a debugging event occurs.  

    WaitForDebugEvent(&DebugEv, INFINITE);  

// Process the debugging event code.  

    switch (DebugEv.dwDebugEventCode) 

{ 

        case EXCEPTION_DEBUG_EVENT: 

        // Process the exception code. When handling 

        // exceptions, remember to set the continuation 

        // status parameter (dwContinueStatus). This value 

        // is used by the ContinueDebugEvent function.  

            switch (DebugEv.u.Exception.ExceptionRecord.ExceptionCode) 

            { 

                case EXCEPTION_ACCESS_VIOLATION: 

                // First chance: Pass this on to the system. 

                // Last chance: Display an appropriate error. 

                    break; 

                case EXCEPTION_BREAKPOINT: 

                // First chance: Display the current 

                // instruction and register values. 

                    break; 

                case EXCEPTION_DATATYPE_MISALIGNMENT: 

                // First chance: Pass this on to the system. 

                // Last chance: Display an appropriate error. 

                    break; 

                case EXCEPTION_SINGLE_STEP: 

                // First chance: Update the display of the 

                // current instruction and register values. 

                    break; 

                case DBG_CONTROL_C: 

                // First chance: Pass this on to the system. 

                // Last chance: Display an appropriate error. 

                    break; 

                default;

                // Handle other exceptions. 

                    break;

            }  

        case CREATE_THREAD_DEBUG_EVENT: 

        // As needed, examine or change the thread's registers 

        // with the GetThreadContext and SetThreadContext functions; 

        // and suspend and resume thread execution with the 

        // SuspendThread and ResumeThread functions. 

            break;

        case CREATE_PROCESS_DEBUG_EVENT: 

        // As needed, examine or change the registers of the 

        // process's initial thread with the GetThreadContext and 

        // SetThreadContext functions; read from and write to the 

        // process's virtual memory with the ReadProcessMemory and 

        // WriteProcessMemory functions; and suspend and resume 

        // thread execution with the SuspendThread and ResumeThread 

        // functions. Be sure to close the handle to the process image 

        // file with CloseHandle.

            break; 

        case EXIT_THREAD_DEBUG_EVENT: 

        // Display the thread's exit code. 

            break; 

        case EXIT_PROCESS_DEBUG_EVENT: 

        // Display the process's exit code. 

            break; 

        case LOAD_DLL_DEBUG_EVENT: 

        // Read the debugging information included in the newly 

        // loaded DLL. Be sure to close the handle to the loaded DLL 

        // with CloseHandle.

            break; 

        case UNLOAD_DLL_DEBUG_EVENT: 

        // Display a message that the DLL has been unloaded. 

            break; 

        case OUTPUT_DEBUG_STRING_EVENT: 

        // Display the output debugging string. 

            break; 

    } 

 

// Resume executing the thread that reported the debugging event.  

ContinueDebugEvent(DebugEv.dwProcessId, DebugEv.dwThreadId, dwContinueStatus); 

 }

可以从CASE语句中看到可以获取什么样的事件。注意CREATE_PROCESS_DEBUG_EVENT事件,我们设置断点是在这里,也就是进行被创建的时候。

说到这里就要讲讲断点是怎么设置的了。通常我们设置的断点称为软件断点。通俗点说这个断点其实并不是在原代码中写的,它的基本工作是:调试器跟据用户的设置对代码进行了修改,也即是在指地址处更换一个字节的值为CC,当程序运行到用户指定地址时,CC被处理器装入执行,因为CC对应用int 3指令,这是处理器的一个断点异常,于是处理暂停当前程序,并发出相应异常,通过异常处理程序和debug驱动程序,调试器Host端得到这个事件,此时用户就可以对被debugging的程序进行一些访问了,比如寄存器,局部变量等各种信息,可以对目标程序的指定代码块进行反汇编或进行源码级的查看。

注意:这里指出的是当程序被断点所中断时,调试器获得此断点事件后第一件是要将有被改写为CC的内存单元重新改会为原来程序的代码,以保证程序的完整性。另一点提示,被捕获的第一个断点异常应该被丢掉,呵呵,那不关你的事,它是规定的一个步骤,当然,你是可以用它做点什么事的。

当然继续调试的时候,目标程序会根据EIP来执行下一条指令,而这时EIP实际指向的是CC后的位置,因此必须令EIP指针值减1,这样程序就对用户实现了透明,用户是不知道调试器作了什么手脚的,当然别忘,程序继续跑时,其实的断点还要改成CC的。

写目标进程可以用WriteProcessMemory进行,写完后别忘了用FlushInstructionCache指令flush一下目标进行的指令的cache。如果页为只读时,可以使用VirtualProtectEx改其为读写属性然后再写。

修改目标线程EIP的值可以用SetThreadContext当然EIP的值请先用GetThreadContext来获得。啊,这样就可以了。好热,写了40多分钟......要吃冰棒去了。

抱歉!评论已关闭.