调试篇——断点与单步

写在前面

  此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我

你如果是从中间插过来看的,请仔细阅读 羽夏看Win系统内核——简述 ,方便学习本教程。

  看此教程之前,问几个问题,基础知识储备好了吗?保护模式篇学会了吗?练习做完了吗?没有的话就不要继续了。


🔒 华丽的分割线 🔒


概述

  在软件调试的过程中,最常用的就是断点和单步了。有过调试经验的人知道,断点有软件断点、硬件断点、内存断点;单步有单步步入和单步步过。下面我们来介绍它们的实现原理。

软件断点

  软件断点是实现的本质就是在下断点的地方插入int3,也被称之为CC断点。下面我们来做个实验:
  我们打开一个调试器调试一个进程,然后在某条汇编下断点,于此同时通过CE来查看当前指令的字节变化情况,如下是操作示例:

  也就是说,调试器帮我们屏蔽掉了0xCC的显示,本质上还是int3,就凭这些我们就可以写一个接管软件断点的简单的调试器了:

#include "stdafx.h"
#include <windows.h>
#include <stdlib.h>

BOOL SysInt3 = TRUE;

void WaitUserInput();
BOOL BreakPointHandler(EXCEPTION_DEBUG_INFO* info);
const UCHAR Int3 = 0xCC;
UCHAR bInt3;
PROCESS_INFORMATION pi ;

#define DBGBREAKPOINT

int main(int argc, char* argv[])
{
    char filename[]= "C:\\WINDOWS\\NOTEPAD.EXE";
    STARTUPINFO si ={sizeof(STARTUPINFO)};
    DEBUG_EVENT dbgEvent;
    BOOL isContinue = TRUE;
    DWORD buffer;
        
    BOOL ret =CreateProcess(NULL,filename,NULL,NULL,FALSE,DEBUG_PROCESS|DEBUG_ONLY_THIS_PROCESS,NULL,NULL,&si,&pi);

    if (ret)
    {
        while (isContinue)
        {
            ret = WaitForDebugEvent(&dbgEvent,INFINITE);
            if (!ret)
            {
                printf("WaitForDebugEvent 出错:%d",GetLastError());
                break;
            }

            EXCEPTION_DEBUG_INFO info = dbgEvent.u.Exception;

            switch (dbgEvent.dwDebugEventCode)
            {
            case EXCEPTION_DEBUG_EVENT:
                puts("EXCEPTION_DEBUG_EVENT");
                switch (info.ExceptionRecord.ExceptionCode)
                {
                case EXCEPTION_BREAKPOINT:
                    isContinue = BreakPointHandler(&info);
                    break;
                }
                break;
            case CREATE_THREAD_DEBUG_EVENT:
                puts("CREATE_THREAD_DEBUG_EVENT");
                break;
            case CREATE_PROCESS_DEBUG_EVENT:
                puts("CREATE_PROCESS_DEBUG_EVENT"); 

#ifdef DBGBREAKPOINT

                puts("请输入要下断点的位置:");
                scanf("%x",&buffer);

                if (!ReadProcessMemory(pi.hProcess,(void*)buffer,&bInt3,1,NULL))
                {
                    puts("软件断点设置失败!");
                    isContinue = FALSE;
                    TerminateProcess(pi.hProcess,0);
                }

                if (!WriteProcessMemory(pi.hProcess,(void*)buffer,(void*)&Int3,1,NULL))
                {
                    puts("软件断点设置失败!");
                    isContinue = FALSE;
                    TerminateProcess(pi.hProcess,0);
                }

#endif

                break;
            case EXIT_THREAD_DEBUG_EVENT:
                puts("EXIT_THREAD_DEBUG_EVENT");
                break;
            case EXIT_PROCESS_DEBUG_EVENT:
                puts("EXIT_PROCESS_DEBUG_EVENT");
                break;
            case LOAD_DLL_DEBUG_EVENT:
                puts("LOAD_DLL_DEBUG_EVENT");
                break;     
            case UNLOAD_DLL_DEBUG_EVENT:
                puts("UNLOAD_DLL_DEBUG_EVENT");
                break;
            case OUTPUT_DEBUG_STRING_EVENT:
                puts("OUTPUT_DEBUG_STRING_EVENT");
                break;
            default:
                break;
            }

            isContinue = ContinueDebugEvent(dbgEvent.dwProcessId,dbgEvent.dwThreadId,DBG_CONTINUE);   
        }
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);
    }
    else
    {
        printf("创建进程失败:%d\n",GetLastError());
    }
    system("pause");
    return 0;
}

BOOL BreakPointHandler(EXCEPTION_DEBUG_INFO* info)
{
    if (SysInt3)
    {
        SysInt3 = FALSE;
        return TRUE;
    }
        
    EXCEPTION_RECORD record = info->ExceptionRecord;
    WriteProcessMemory(pi.hProcess,record.ExceptionAddress,&bInt3,1,NULL);
    printf(">> BreakPointHandler - Addr : 0x%X\n",record.ExceptionAddress);
        
    WaitUserInput();
        
    CONTEXT context;
    context.ContextFlags = CONTEXT_FULL |CONTEXT_DEBUG_REGISTERS;
    GetThreadContext(pi.hThread,&context);
    context.Eip--;
    SetThreadContext(pi.hThread,&context);
    return TRUE;
}

void WaitUserInput()
{
    bool p = true;
    char cmd = 0;
    while(p)
    {
        printf("COMMAND>> ");
        
        /*************清空缓冲区**************/
        scanf("%*[^\n]");
        scanf("%*c");
        /*************************************/

        cmd = getchar();
        switch (cmd)
        {
        case 'g':
            p = false;
            break;
        default:
            break;
        }
    }
}

  我定义DBGBREAKPOINT这个宏是为了方便之后实验只需定义宏来控制单个功能的测试。WaitUserInput函数里面清空缓冲区的代码不清楚的话可以阅读 羽夏闲谈—— C 的 scanf 的高级用法 ,如下是我的实验结果:

  当然我们只是简单的实现,一般的调试器都会保留这个断点,具体实现我就不实现了。

内存断点

  内存断点是通过修改页属性来制造异常出现接管的,既然是修改其他程序的,就需要调用下面的函数:

BOOL VirtualProtectEx(
  HANDLE hProcess,        // handle to process
  LPVOID lpAddress,       // region of committed pages
  SIZE_T dwSize,          // size of region
  DWORD flNewProtect,     // desired access protection
  PDWORD lpflOldProtect   // old protection
);

  内存断点有被分为内存访问断点和内存写入断点,它们分别对应的第四个参数为PAGE_NOACCESSPAGE_EXECUTE_READPAGE_NOACCESS属性会把对应的页属性的P位改为0,而PAGE_EXECUTE_READ对应的页属性的P位虽然是1,但R/W0。下面我们来实现一个简单的内存访问断点:

#include "stdafx.h"
#include <windows.h>
#include <stdlib.h>

BOOL SysInt3 = TRUE;

void WaitUserInput();
BOOL BreakPointHandler(EXCEPTION_DEBUG_INFO* info);
BOOL MemBreakPointHandler(EXCEPTION_DEBUG_INFO* info);
const UCHAR Int3 = 0xCC;
UCHAR bInt3;
PROCESS_INFORMATION pi ;
DWORD orignExcuteAccess;
DWORD buffer;

//#define DBGBREAKPOINT
#define MemBREAKPOINT

int main(int argc, char* argv[])
{
    char filename[]= "C:\\WINDOWS\\NOTEPAD.EXE";
    STARTUPINFO si ={sizeof(STARTUPINFO)};
    DEBUG_EVENT dbgEvent;
    BOOL isContinue = TRUE;

    BOOL ret =CreateProcess(NULL,filename,NULL,NULL,FALSE,DEBUG_PROCESS|DEBUG_ONLY_THIS_PROCESS,NULL,NULL,&si,&pi);

    if (ret)
    {
        while (isContinue)
        {
            ret = WaitForDebugEvent(&dbgEvent,INFINITE);
            if (!ret)
            {
                printf("WaitForDebugEvent 出错:%d",GetLastError());
                break;
            }

            EXCEPTION_DEBUG_INFO info = dbgEvent.u.Exception;

            switch (dbgEvent.dwDebugEventCode)
            {
            case EXCEPTION_DEBUG_EVENT:
                puts("EXCEPTION_DEBUG_EVENT");
                switch (info.ExceptionRecord.ExceptionCode)
                {
                case EXCEPTION_BREAKPOINT:
                    ret = BreakPointHandler(&info);
                    break;
                case EXCEPTION_ACCESS_VIOLATION:
                    ret = MemBreakPointHandler(&info);
                    break;
                }
                if (!ret)
                {
                    printf("出错:%d\n",GetLastError());
                    isContinue = FALSE;
                    TerminateProcess(pi.hProcess,0);
                }
                isContinue = ret;
                break;
            case CREATE_THREAD_DEBUG_EVENT:
                puts("CREATE_THREAD_DEBUG_EVENT");
                break;
            case CREATE_PROCESS_DEBUG_EVENT:
                puts("CREATE_PROCESS_DEBUG_EVENT");  

#ifdef DBGBREAKPOINT

                puts("请输入要下断点的位置:");
                scanf("%x",&buffer);

                if (!ReadProcessMemory(pi.hProcess,(void*)buffer,&bInt3,1,NULL))
                {
                    puts("软件断点设置失败!");
                    isContinue = FALSE;
                    TerminateProcess(pi.hProcess,0);
                }

                if (!WriteProcessMemory(pi.hProcess,(void*)buffer,(void*)&Int3,1,NULL))
                {
                    puts("软件断点设置失败!");
                    isContinue = FALSE;
                    TerminateProcess(pi.hProcess,0);
                }

#endif
    
#ifdef MemBREAKPOINT
                buffer = (DWORD)dbgEvent.u.CreateProcessInfo.lpStartAddress;   
                if (!VirtualProtectEx(pi.hProcess,(void*)buffer,1,PAGE_NOACCESS,&orignExcuteAccess))
                {
                    puts("内存执行断点设置失败!");
                    isContinue = FALSE;
                    TerminateProcess(pi.hProcess,0);
                }
                printf("内存访问断点的位置:0x%X\n",buffer);
#endif
                break;
            case EXIT_THREAD_DEBUG_EVENT:
                puts("EXIT_THREAD_DEBUG_EVENT");
                break;
            case EXIT_PROCESS_DEBUG_EVENT:
                puts("EXIT_PROCESS_DEBUG_EVENT");    
                break;
            case LOAD_DLL_DEBUG_EVENT:
                puts("LOAD_DLL_DEBUG_EVENT");
                break;     
            case UNLOAD_DLL_DEBUG_EVENT:
                puts("UNLOAD_DLL_DEBUG_EVENT");
                break;
            case OUTPUT_DEBUG_STRING_EVENT:
                puts("OUTPUT_DEBUG_STRING_EVENT");
                break;
            default:
                break;
            }

            if (!isContinue)
            {
                break;
            }

            isContinue = ContinueDebugEvent(dbgEvent.dwProcessId,dbgEvent.dwThreadId,ret?DBG_CONTINUE:DBG_EXCEPTION_NOT_HANDLED);   
        }
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);
    }
    else
    {
        printf("创建进程失败:%d\n",GetLastError());
    }
    system("pause");
    return 0;
}

BOOL BreakPointHandler(EXCEPTION_DEBUG_INFO* info)
{
    if (SysInt3)
    {
        SysInt3 = FALSE;
        return TRUE;
    }
        
    EXCEPTION_RECORD record = info->ExceptionRecord;
    WriteProcessMemory(pi.hProcess,record.ExceptionAddress,&bInt3,1,NULL);
    printf(">> BreakPointHandler - Addr : 0x%X\n",record.ExceptionAddress);
        
    WaitUserInput();
        
    CONTEXT context;
    context.ContextFlags = CONTEXT_FULL |CONTEXT_DEBUG_REGISTERS;
    GetThreadContext(pi.hThread,&context);
    context.Eip--;
    SetThreadContext(pi.hThread,&context);
    return TRUE;
}

BOOL MemBreakPointHandler(EXCEPTION_DEBUG_INFO* info)
{
    DWORD ExceptionADDR = info->ExceptionRecord.ExceptionInformation[1];
    DWORD ExceptionAccess = info->ExceptionRecord.ExceptionInformation[0];
        
    if (ExceptionADDR>>12 == buffer>>12)    //这里的判断是不对的,但为了做示例足够了
    {
        printf("内存执行断点:%x %x\n",ExceptionADDR,   ExceptionAccess);
        WaitUserInput();
        if (!VirtualProtectEx(pi.hProcess,(void*)buffer,1,  orignExcuteAccess,&orignExcuteAccess))
        {
            printf("内存断点修复失败:%d\n",GetLastError());
        }
        return TRUE;
    }
    return FALSE;
}

void WaitUserInput()
{
    bool p = true;
    char cmd = 0;
    while(p)
    {
        
        /*************清空缓冲区**************/
        scanf("%*[^\n]");
        scanf("%*c");
        /*************************************/

        printf("COMMAND>> ");

        cmd = getchar();
        switch (cmd)
        {
        case 'g':
            p = false;
            break;
        default:
            break;
        }
    }
}

  可以看出,该代码是基于软件断点实验基础上写的。在WaitUserInput有一个小Bug,就是没有输入的话至少需要随意输入字符来继续清空缓冲区操作,不过对于示例来说无伤大雅,如下是测试效果图:

  注意,我是简单是实现内存断点,为什么这么说呢?是因为这个函数一旦影响就是一个物理页,所以你得做判断。如果对数据下访问断点,如果多于1个字节,你还得考虑在多个物理页的情况,还得实现内存断点的记录功能。这些都是一个基本能用的调试器所具备的功能。

硬件断点

概述

  硬件断点和上面的不同,它是基于硬件的,不依赖调试程序,有自己的优势,如果通过CRC校验是不会被检测到的。如下是与硬件断点相关的寄存器结构:

  Dr0 ~ Dr3用于设置硬件断点,Dr4Dr5被保留了。由于只有4个断点寄存器,所以最多只能设置4个硬件调试断点。Dr7是最重要的寄存器,它比较复杂,我们来看看它的结构:

L0/G0 ~ L3/G3

  控制Dr0 ~ Dr3是否有效,局部还是全局。每次异常后,Lx都被清零,Gx不清零。

LEN0 ~ LEN3

  表示硬件断点的长度。如果是0表示1个字节;是1表示2个字节;是3表示4个字节。

R/W0 ~ R/W3

  指示断点类型。如果是0表示执行断点;是1表示写入断点;是3表示访问断点。

处理

  硬件调试断点产生的异常是STATUS_SINGLE_STEP,即单步异常。触发异常后,B0 ~ B3对应的位会被置1,以此可以区分单步步入产生的单步异常,后续会详细讲解。

实验

  如下是实验代码:

#include "stdafx.h"
#include <windows.h>
#include <stdlib.h>

BOOL SysInt3 = TRUE;

void WaitUserInput();
BOOL BreakPointHandler(EXCEPTION_DEBUG_INFO* info);
BOOL MemBreakPointHandler(EXCEPTION_DEBUG_INFO* info);
BOOL SingleStepHandler(EXCEPTION_DEBUG_INFO* info);
const UCHAR Int3 = 0xCC;
UCHAR bInt3;
PROCESS_INFORMATION pi ;
DWORD orignExcuteAccess;
DWORD buffer;

//#define DBGBREAKPOINT
//#define MemBREAKPOINT
#define HardwareBREAKPOINT

int main(int argc, char* argv[])
{
    char filename[]= "C:\\WINDOWS\\NOTEPAD.EXE";
    STARTUPINFO si ={sizeof(STARTUPINFO)};
    DEBUG_EVENT dbgEvent;
    BOOL isContinue = TRUE;
        
    BOOL ret =CreateProcess(NULL,filename,NULL,NULL,FALSE,DEBUG_PROCESS|DEBUG_ONLY_THIS_PROCESS,NULL,NULL,&si,&pi);

    if (ret)
    {
        while (isContinue)
        {
            ret = WaitForDebugEvent(&dbgEvent,INFINITE);
            if (!ret)
            {
                printf("WaitForDebugEvent 出错:%d",GetLastError());
                break;
            }
   
            EXCEPTION_DEBUG_INFO info = dbgEvent.u.Exception;

            switch (dbgEvent.dwDebugEventCode)
            {
            case EXCEPTION_DEBUG_EVENT:
                puts("EXCEPTION_DEBUG_EVENT");
                switch (info.ExceptionRecord.ExceptionCode)
                {
                case EXCEPTION_BREAKPOINT:
                    ret = BreakPointHandler(&info);
                    break;
                case EXCEPTION_ACCESS_VIOLATION:
                    ret = MemBreakPointHandler(&info);
                    break;
                case EXCEPTION_SINGLE_STEP:
                    ret = SingleStepHandler(&info);
                    break;
                default:
                    ret = FALSE;
                    break;
                }
                if (!ret)
                {
                    printf("出错:%d\n",GetLastError());
                    isContinue = FALSE;
                    TerminateProcess(pi.hProcess,0);
                }
                isContinue = ret;
                break;
            case CREATE_THREAD_DEBUG_EVENT:
                puts("CREATE_THREAD_DEBUG_EVENT");
                break;
            case CREATE_PROCESS_DEBUG_EVENT:
                puts("CREATE_PROCESS_DEBUG_EVENT");  
    
#ifdef DBGBREAKPOINT
    
                puts("请输入要下断点的位置:");
                scanf("%x",&buffer);

                if (!ReadProcessMemory(pi.hProcess,(void*)buffer,&bInt3,1,NULL))
                {
                    puts("软件断点设置失败!");
                    isContinue = FALSE;
                    TerminateProcess(pi.hProcess,0);
                }

                if (!WriteProcessMemory(pi.hProcess,(void*)buffer,(void*)&Int3,1,NULL))
                    
                    puts("软件断点设置失败!");
                    isContinue = FALSE;
                    TerminateProcess(pi.hProcess,0);
                }
    
#endif
    
#ifdef MemBREAKPOINT
                buffer = (DWORD)dbgEvent.u.CreateProcessInfo.lpStartAddress;   
                if (!VirtualProtectEx(pi.hProcess,(void*)buffer,1,PAGE_NOACCESS,&orignExcuteAccess))
                {
                    puts("内存执行断点设置失败!");
                    isContinue = FALSE;
                    TerminateProcess(pi.hProcess,0);
                }
                printf("内存访问断点的位置:0x%X\n",buffer);
#endif
    
#ifdef HardwareBREAKPOINT
                SuspendThread(pi.hThread);
                puts("请输入要下断点的位置:");
                scanf("%x",&buffer);
                CONTEXT context;
                context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
                GetThreadContext(pi.hThread,&context);
                context.Dr0 = buffer;
                context.Dr7 |= 1;
                if (!SetThreadContext(pi.hThread,&context))
                {
                    puts("硬件断点设置失败!");
                }
                ResumeThread(pi.hThread);
#endif

                break;
            case EXIT_THREAD_DEBUG_EVENT:
                puts("EXIT_THREAD_DEBUG_EVENT");
                break;
            case EXIT_PROCESS_DEBUG_EVENT:
                puts("EXIT_PROCESS_DEBUG_EVENT");    
                break;
            case LOAD_DLL_DEBUG_EVENT:
                puts("LOAD_DLL_DEBUG_EVENT");
                break;     
            case UNLOAD_DLL_DEBUG_EVENT:
                puts("UNLOAD_DLL_DEBUG_EVENT");
                break;
            case OUTPUT_DEBUG_STRING_EVENT:
                puts("OUTPUT_DEBUG_STRING_EVENT");
                break;
            default:
                break;
            }

            if (!isContinue)
            {
                break;
            }

            isContinue = ContinueDebugEvent(dbgEvent.dwProcessId,dbgEvent.dwThreadId,ret?DBG_CONTINUE:DBG_EXCEPTION_NOT_HANDLED);   
        }
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);
    }
    else
    {
        printf("创建进程失败:%d\n",GetLastError());
    }
    system("pause");
    return 0;
}

BOOL BreakPointHandler(EXCEPTION_DEBUG_INFO* info)
{
    if (SysInt3)
    {
        SysInt3 = FALSE;
        return TRUE;
    }
        
    EXCEPTION_RECORD record = info->ExceptionRecord;
    WriteProcessMemory(pi.hProcess,record.ExceptionAddress,&bInt3,1,NULL);
    printf(">> BreakPointHandler - Addr : 0x%X\n",record.ExceptionAddress);
        
    WaitUserInput();
        
    CONTEXT context;
    context.ContextFlags = CONTEXT_FULL |CONTEXT_DEBUG_REGISTERS;
    GetThreadContext(pi.hThread,&context);
    context.Eip--;
    SetThreadContext(pi.hThread,&context);
    return TRUE;
}

BOOL MemBreakPointHandler(EXCEPTION_DEBUG_INFO* info)
{
    DWORD ExceptionADDR = info->ExceptionRecord.ExceptionInformation[1];
    DWORD ExceptionAccess = info->ExceptionRecord.ExceptionInformation[0];
        
    if (ExceptionADDR>>12 == buffer>>12)    //这里的判断是不对的,但为了做示例足够了
    {
        printf("内存执行断点:%x %x\n",ExceptionADDR,ExceptionAccess);
        WaitUserInput();
        if (!VirtualProtectEx(pi.hProcess,(void*)buffer,1,orignExcuteAccess,&orignExcuteAccess))
        {
            printf("内存断点修复失败:%d\n",GetLastError());
        }
        return TRUE;
    }
    return FALSE;
}

BOOL SingleStepHandler(EXCEPTION_DEBUG_INFO* info)
{
        
    CONTEXT context;
    context.ContextFlags = CONTEXT_FULL |CONTEXT_DEBUG_REGISTERS;
    GetThreadContext(pi.hThread,&context);
        
    if (context.Dr6&0xF)
    {
        puts("硬件断点被触发!");
        context.Dr7&=~1;
    }
    else
    {
        printf("单步中……:0x%X\n",context.Eip);
    }
        
    WaitUserInput();
    SetThreadContext(pi.hThread,&context);
    return TRUE;
}

void WaitUserInput()
{
    bool p = true;
    char cmd = 0;
    while(p)
    {
        
        /*************清空缓冲区**************/
        scanf("%*[^\n]");
        scanf("%*c");
        /*************************************/

        printf("COMMAND>> ");

        cmd = getchar();
        switch (cmd)
        {
        case 'g':
            p = false;
            break;
        default:
            break;
        }
    }
}

  硬件断点的实现十分简单,就不赘述了,如下是实验效果图:

单步步入

  在调试中我们经常一条指令一条指令的进行调试,这大大方便了我们查阅结果,CPU提供了这样的基址,就是在Eflag中的TF位实现的,如下图所示:

  好我们继续在之前的代码基础上扩展单步功能:

#include "stdafx.h"
#include <windows.h>
#include <stdlib.h>

BOOL SysInt3 = TRUE;

void WaitUserInput();
BOOL BreakPointHandler(EXCEPTION_DEBUG_INFO* info);
BOOL MemBreakPointHandler(EXCEPTION_DEBUG_INFO* info);
BOOL SingleStepHandler(EXCEPTION_DEBUG_INFO* info);
const UCHAR Int3 = 0xCC;
UCHAR bInt3;
PROCESS_INFORMATION pi ;
DWORD orignExcuteAccess;
DWORD buffer;

#define DBGBREAKPOINT
//#define MemBREAKPOINT
//#define HardwareBREAKPOINT


int main(int argc, char* argv[])
{
    char filename[]= "C:\\WINDOWS\\NOTEPAD.EXE";
    STARTUPINFO si ={sizeof(STARTUPINFO)};
    DEBUG_EVENT dbgEvent;
    BOOL isContinue = TRUE;
    
    BOOL ret =CreateProcess(NULL,filename,NULL,NULL,FALSE,DEBUG_PROCESS|DEBUG_ONLY_THIS_PROCESS,NULL,NULL,&si,&pi);
    
    if (ret)
    {
        while (isContinue)
        {
            ret = WaitForDebugEvent(&dbgEvent,INFINITE);
            if (!ret)
            {
                printf("WaitForDebugEvent 出错:%d",GetLastError());
                break;
            }
            
            EXCEPTION_DEBUG_INFO info = dbgEvent.u.Exception;

            switch (dbgEvent.dwDebugEventCode)
            {
            case EXCEPTION_DEBUG_EVENT:
                puts("EXCEPTION_DEBUG_EVENT");
                switch (info.ExceptionRecord.ExceptionCode)
                {
                case EXCEPTION_BREAKPOINT:
                    ret = BreakPointHandler(&info);
                    break;
                case EXCEPTION_ACCESS_VIOLATION:
                    ret = MemBreakPointHandler(&info);
                    break;
                case EXCEPTION_SINGLE_STEP:
                    ret = SingleStepHandler(&info);
                    break;
                default:
                    ret = FALSE;
                    break;
                }
                if (!ret)
                {
                    printf("出错:%d\n",GetLastError());
                    isContinue = FALSE;
                    TerminateProcess(pi.hProcess,0);
                }
                isContinue = ret;
                break;
            case CREATE_THREAD_DEBUG_EVENT:
                puts("CREATE_THREAD_DEBUG_EVENT");
                break;
            case CREATE_PROCESS_DEBUG_EVENT:
                puts("CREATE_PROCESS_DEBUG_EVENT");        
                
#ifdef DBGBREAKPOINT
                
                buffer = (DWORD)dbgEvent.u.CreateProcessInfo.lpStartAddress;
                
                if (!ReadProcessMemory(pi.hProcess,(void*)buffer,&bInt3,1,NULL))
                {
                    puts("软件断点设置失败!");
                    isContinue = FALSE;
                    TerminateProcess(pi.hProcess,0);
                }
                
                if (!WriteProcessMemory(pi.hProcess,(void*)buffer,(void*)&Int3,1,NULL))
                {
                    puts("软件断点设置失败!");
                    isContinue = FALSE;
                    TerminateProcess(pi.hProcess,0);
                }
                
#endif
                
#ifdef MemBREAKPOINT
                buffer = (DWORD)dbgEvent.u.CreateProcessInfo.lpStartAddress;            
                if (!VirtualProtectEx(pi.hProcess,(void*)buffer,1,PAGE_NOACCESS,&orignExcuteAccess))
                {
                    puts("内存执行断点设置失败!");
                    isContinue = FALSE;
                    TerminateProcess(pi.hProcess,0);
                }
                printf("内存访问断点的位置:0x%X\n",buffer);
#endif
                
#ifdef HardwareBREAKPOINT
                SuspendThread(pi.hThread);
                puts("请输入要下断点的位置:");
                scanf("%x",&buffer);
                CONTEXT context;
                context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
                GetThreadContext(pi.hThread,&context);
                context.Dr0 = buffer;
                context.Dr7 |= 1;
                if (!SetThreadContext(pi.hThread,&context))
                {
                    puts("硬件断点设置失败!");
                }
                ResumeThread(pi.hThread);
#endif

                break;
            case EXIT_THREAD_DEBUG_EVENT:
                puts("EXIT_THREAD_DEBUG_EVENT");
                break;
            case EXIT_PROCESS_DEBUG_EVENT:
                puts("EXIT_PROCESS_DEBUG_EVENT");                
                break;
            case LOAD_DLL_DEBUG_EVENT:
                puts("LOAD_DLL_DEBUG_EVENT");
                break;     
            case UNLOAD_DLL_DEBUG_EVENT:
                puts("UNLOAD_DLL_DEBUG_EVENT");
                break;
            case OUTPUT_DEBUG_STRING_EVENT:
                puts("OUTPUT_DEBUG_STRING_EVENT");
                break;
            default:
                break;
            }
            
            if (!isContinue)
            {
                break;
            }

            isContinue = ContinueDebugEvent(dbgEvent.dwProcessId,dbgEvent.dwThreadId,ret?DBG_CONTINUE:DBG_EXCEPTION_NOT_HANDLED);            
        }
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);
    }
    else
    {
        printf("创建进程失败:%d\n",GetLastError());
    }
    system("pause");
    return 0;
}

BOOL BreakPointHandler(EXCEPTION_DEBUG_INFO* info)
{
    if (SysInt3)
    {
        SysInt3 = FALSE;
        return TRUE;
    }
    
    EXCEPTION_RECORD record = info->ExceptionRecord;
    WriteProcessMemory(pi.hProcess,record.ExceptionAddress,&bInt3,1,NULL);
    printf(">> BreakPointHandler - Addr : 0x%X\n",record.ExceptionAddress);
    
    WaitUserInput();
    
    SuspendThread(pi.hThread);
    CONTEXT context;
    context.ContextFlags = CONTEXT_FULL |CONTEXT_DEBUG_REGISTERS;
    GetThreadContext(pi.hThread,&context);
    context.Eip--;
    SetThreadContext(pi.hThread,&context);
    ResumeThread(pi.hThread);
    return TRUE;
}

BOOL MemBreakPointHandler(EXCEPTION_DEBUG_INFO* info)
{
    DWORD ExceptionADDR = info->ExceptionRecord.ExceptionInformation[1];
    DWORD ExceptionAccess = info->ExceptionRecord.ExceptionInformation[0];
    
    if (ExceptionADDR>>12 == buffer>>12)    //这里的判断是不对的,但为了做示例足够了
    {
        printf("内存执行断点:%x %x\n",ExceptionADDR,ExceptionAccess);
        WaitUserInput();
        if (!VirtualProtectEx(pi.hProcess,(void*)buffer,1,orignExcuteAccess,&orignExcuteAccess))
        {
            printf("内存断点修复失败:%d\n",GetLastError());
        }
        return TRUE;
    }
    return FALSE;
}

BOOL SingleStepHandler(EXCEPTION_DEBUG_INFO* info)
{
    
    CONTEXT context;
    context.ContextFlags = CONTEXT_FULL |CONTEXT_DEBUG_REGISTERS;
    SuspendThread(pi.hThread);
    GetThreadContext(pi.hThread,&context);
    
    if (context.Dr6&0xF)
    {
        puts("硬件断点被触发!");
        context.Dr7&=~1;
    }
    else
    {
        printf("单步中:0x%X\n",context.Eip);
        context.EFlags &= ~0x100;
    }    
    SetThreadContext(pi.hThread,&context);
    ResumeThread(pi.hThread);
    WaitUserInput();
    return TRUE;
}

void WaitUserInput()
{
    bool p = true;
    char cmd = 0;
    while(p)
    {
        
        /*************清空缓冲区**************/
        scanf("%*[^\n]");
        scanf("%*c");
        /*************************************/

        printf("COMMAND>> ");

        cmd = getchar();
        switch (cmd)
        {
        case 'g':
            p = false;
            break;
        case 't':
            p=false;
            SuspendThread(pi.hThread);
            CONTEXT context;
            context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
            GetThreadContext(pi.hThread,&context);
            context.EFlags |= 0x100;
            if (!SetThreadContext(pi.hThread,&context))
            {
                puts("单步设置失败!");
            }
            ResumeThread(pi.hThread);
            break;
        default:
            break;
        }
    }
}

  单步是实现比较简单,如下是效果图:

单步步过

  单步步过和单步步入不同,单步步入会逐个走指令,到达call执行会进入,而单步步过不会进入。单步步过是可以基于硬件断点或者软件断点实现。至于实现过程我就不赘述了。

小结

  本篇文章主要介绍了调试器断点和单步的实现相关基本知识,如果要真正的实现一个调试器,还需要大量的实现。在总结与提升篇,我还会详细介绍所有断点和单步异常的内核处理流程。

下一篇

  调试篇——总结与提升

posted @ 2022-03-03 17:00  寂静的羽夏  阅读(1162)  评论(0编辑  收藏  举报