kr 第三阶段(三)调试器

news2024/11/20 7:04:59

调试框架

  • 特点:事件驱动,事件响应。 Win32 程序是消息驱动响应的基址,而在调试器则是事件驱动响应,有事件则处理,无事件则去做别的事。
    • 事件:整个调试框架是建立在异常的基础之上的基本单位。
    • 响应:通过事件循环体,持续的获取事件,处理事件。

在这里插入图片描述

创建调试会话

创建进程

创建进程主要是通过 CreateProcess 函数来实现的,该函数的原型如下:

BOOL CreateProcess(
  LPCTSTR lpApplicationName,                 // name of executable module
  LPTSTR lpCommandLine,                      // command line string
  LPSECURITY_ATTRIBUTES lpProcessAttributes, // SD
  LPSECURITY_ATTRIBUTES lpThreadAttributes,  // SD
  BOOL bInheritHandles,                      // handle inheritance option
  DWORD dwCreationFlags,                     // creation flags
  LPVOID lpEnvironment,                      // new environment block
  LPCTSTR lpCurrentDirectory,                // current directory name
  LPSTARTUPINFO lpStartupInfo,               // startup information
  LPPROCESS_INFORMATION lpProcessInformation // process information);

下面是 CreateProcess 函数的一些参数的说明:

  • lpApplicationName:指向可执行文件的路径或者文件名。
  • lpCommandLine:指向一个以 null 结尾的字符串,包含了命令行参数。
  • lpProcessAttributeslpThreadAttributes:用于指定进程和线程的安全性属性,默认为 NULL。
  • bInheritHandles:指示新进程是否继承父进程的句柄。
  • dwCreationFlags:指定进程的创建标志,例如是否创建一个新的控制台窗口、创建方式等。
  • lpEnvironment:指定新进程的环境变量,如果为 NULL,则继承父进程的环境变量。
  • lpCurrentDirectory:指定新进程的当前工作目录,如果为 NULL,则继承父进程的当前目录。
  • lpStartupInfo:指向一个 STARTUPINFO 结构体,用于指定新进程的一些启动参数,例如窗口大小、显示方式等。
  • lpProcessInformation:指向一个 PROCESS_INFORMATION 结构体,用于接收新进程的相关信息,例如进程句柄和线程句柄。
  • CreateProcess 函数成功执行时会返回非零值,否则返回零。

针对 dwCreationFlags 参数,有 DEBUG_ONLY_THIS_PROCESSDEBUG_PROCESS 两个标志参数用于指定创建进程时的调试模式:

  • DEBUG_PROCESS:调试器会受到目标进程及由目标进程创建的所有子进程中发生的调试事件。
  • DEBUG_ONLY_THIS_PROCESS:调试器只收到目标进程的调试事件,对子进程不响应。

调试器通常设置的都是 DEBUG_ONLY_THIS_PROCESS 标志位。

附加

如果想要通过附加调试某一进程需要使用 DebugActiveProcess 函数,该函数定义如下:

BOOL DebugActiveProcess(
  DWORD dwProcessId   // process to be debugged
);
  • dwProcessId:要调试的目标进程的进程标识符(PID)。
  • 函数调用成功时,返回值为非零值;否则,返回值为零。
  • 该函数附加进程效果与创建进程使用 DEBUG_ONLY_THIS_PROCESS 效果类似。

如果想脱离附加的进程可以使用 DebugActiveProcessStop 函数,该函数定义如下:

BOOL DebugActiveProcessStop(
  DWORD dwProcessId
);
  • dwProcessId:要调试的目标进程的进程标识符(PID)。
  • 函数调用成功时,返回值为非零值;否则,返回值为零。

然而有时脱离附加的进程会导致被附加的进程退出,这时候就需要在调用 DebugActiveProcessStop 函数之前调用 DebugSetProcessKillOnExit 来防止被调试进程退出,该函数定义如下:

BOOL DebugSetProcessKillOnExit (  BOOL KillOnExit);
  • KillOnExit:指定调试器在退出时是否终止被调试的进程。如果设置为 TRUE,调试器在退出时将终止被调试的进程;如果设置为 FALSE,调试器在退出时不会终止被调试的进程。
  • 函数调用成功时,返回值为非零值;否则,返回值为零。

循环接受调试事件

等待调试事件使用的是 WatiForDebugEvent 函数,该函数定义如下:

BOOL WaitForDebugEvent(
  LPDEBUG_EVENT lpDebugEvent,  // debug event information
  DWORD dwMilliseconds         // time-out value
);
  • lpDebugEvent:一个指向 DEBUG_EVENT 结构的指针,用于接收调试事件的信息。DEBUG_EVENT 结构包含了调试事件的类型和相关的数据,如调试进程、线程、异常等。
  • dwMilliseconds:等待调试事件的超时时间,以毫秒为单位。如果设置为 INFINITE (0xFFFFFFFF),则表示无限等待,直到有调试事件发生。如果设置为零,则表示不等待,立即返回。
  • 函数调用成功时,返回值为非零值;否则,返回值为零。

接收调试事件的 DEBUG_EVENT 结构体定义如下:

typedef struct _DEBUG_EVENT { 
  DWORD dwDebugEventCode; 
  DWORD dwProcessId; 
  DWORD dwThreadId; 
  union { 
      EXCEPTION_DEBUG_INFO Exception; 
      CREATE_THREAD_DEBUG_INFO CreateThread; 
      CREATE_PROCESS_DEBUG_INFO CreateProcessInfo; 
      EXIT_THREAD_DEBUG_INFO ExitThread; 
      EXIT_PROCESS_DEBUG_INFO ExitProcess; 
      LOAD_DLL_DEBUG_INFO LoadDll; 
      UNLOAD_DLL_DEBUG_INFO UnloadDll; 
      OUTPUT_DEBUG_STRING_INFO DebugString; 
      RIP_INFO RipInfo; 
  } u; 
} DEBUG_EVENT, *LPDEBUG_EVENT; 
  • dwDebugEventCode:表示调试事件的类型。可以是以下值之一:
    • EXCEPTION_DEBUG_EVENT:异常事件。
    • CREATE_THREAD_DEBUG_EVENT:线程创建事件。
    • CREATE_PROCESS_DEBUG_EVENT:进程创建事件。
    • EXIT_THREAD_DEBUG_EVENT:线程退出事件。
    • EXIT_PROCESS_DEBUG_EVENT:进程退出事件。
    • LOAD_DLL_DEBUG_EVENT:动态链接库加载事件。
    • UNLOAD_DLL_DEBUG_EVENT:动态链接库卸载事件。
    • OUTPUT_DEBUG_STRING_EVENT:输出调试字符串事件。
    • RIP_EVENT:RIP(调试错误)事件。
  • dwProcessId:调试事件所属的进程标识符(PID)。
  • dwThreadId:调试事件所属的线程标识符(TID)。
  • u:一个联合体,用于存储不同类型的调试事件数据。根据 dwDebugEventCode 的不同值,使用相应的字段来访问具体的调试事件数据。

处理调试事件

调试器具体代码实现。

提交处理结果

在处理调试事件时被调试进程是处于挂起的状态,因此提交处理结果是告诉被调试进程是否继续运行。提交处理结果的函数是 ContinueDebugEvent,该函数定义如下:

BOOL ContinueDebugEvent(
  DWORD dwProcessId,       // process to continue
  DWORD dwThreadId,        // thread to continue
  DWORD dwContinueStatus   // continuation status
);
  • dwProcessId:要继续运行的进程的标识符(PID)。
  • dwThreadId:要继续运行的线程的标识符(TID)。
  • dwContinueStatus:继续运行的状态。它可以是以下值之一:
    • DBG_CONTINUE:继续运行被调试进程。
    • DBG_EXCEPTION_NOT_HANDLED:异常未被处理,系统按照正常的异常处理流程派发异常。
  • 函数返回一个布尔值,表示操作是否成功。如果函数调用成功,返回值为非零值;如果函数调用失败,返回值为零。

环境搭建

所需工具都可以在附件中下载。

由于汇编代码可读性差,后面改用 C++ 实现(感觉w老师自己都蚌埠住了)。

汇编开发环境

由于这里使用的是汇编来编写调试框架,因此需要安装 RadASM(IDE) 和 MASM32(开发环境)。

  • RadASM 直接下载 RadASM-XXX-FullPackage.zip 然后解压到安装目录即可。
  • MASM32 就点下载链接然后一路默认安装即可,注意盘符最好选 C 盘,因为我这里 RadASM 是在 C 盘找 MASM32 的。

因为我们开发的是命令行式的调试器,因此创建项目时工程类型选择 Colsole App ,其他默认设置即可。

反汇编引擎

反汇编引擎这里我使用的是 Udis86 。该项目在 github 下载的源码缺少文件无法编译,因此我是从这里下载的源码进行编译。

由于该项目使用汇编调用过于麻烦,因此我先实现了一个提供汇编的接口项目 MuUdis86Dll

#include "pch.h"
#include "udis86.h"

#pragma comment(lib, "libudis86.lib")

extern "C" {
    __declspec(dllexport) DWORD GetAsm(
            BYTE *pCode,
            DWORD nCodeLen,
            DWORD nEIP,
            CHAR *szAsmBuf,
            DWORD dwAsmBufLen) {
        ud u;
        ud_init(&u);
        ud_set_mode(&u, 32);
        ud_set_pc(&u, nEIP);
        ud_set_input_buffer(&u, pCode, nCodeLen);
        ud_set_syntax(&u, UD_SYN_INTEL);
        ud_disassemble(&u);
        uint32_t nLen = ud_insn_len(&u);
        strcpy_s(szAsmBuf, dwAsmBufLen, ud_insn_asm(&u));
        return nLen;
    }
}

之后只需要在汇编项目中导入该项目及接口即可调用 GetAsm 函数实现反汇编。注意这里我没有声明 GetAsm 的调用约定因此默认采用 __cdecl 调用约定,因此在汇编中需要再改接口声明中加 C 来声明调用约定。

includelib MyUdis86Dll.lib

GetAsm proto C pCode:LPBYTE,nCodeLen:DWORD,nEip:DWORD,szAsmBuf:LPSTR,dwAsmBufLen:DWORD

软件断点

什么是软件断点

软件断点即 CC 断点或 int3 断点,OD中的快捷键F2,使用率也是最多的断点类型。以调试方式创建的进程,必定会有一个系统断点。

软件断点实现思路

  • 断得下来:需要再软件断点位置写入 int3(0xcc)
  • 走的过去:断点处的指令能够正常执行不受影响。
    • 触发 int3 异常后需要还原断点位置的指令。
    • 由于 int3 指令被执行了,因此还需要将 eip 寄存器减 1(int3 指令长度)。
  • 下次还来:下次执行到断点位置时还能断下来。
    • 由于触发 int3 异常后调试器将断点位置的指令恢复,因此在执行完断点位置的指令后需要再次在该位置写入 int3 指令。
    • 可以在触发 int3 异常时 TF 标志寄存器(EFLAGS 标志寄存器第 8 位)置位,这样在执行下一条指令的时候会触发单步异常。
    • 调试器在接收到该异常的时候可以恢复 int3 断点。

代码实现

创建断点实际上就是在断点位置写入 \xCC

    void HandleBpCmd(const std::vector<std::tstring> &args) {
        if (args.size() != 2) {
            std::tcout << _T("Invalid Command") << std::endl;
            return;
        } 
        PVOID lpBreakPoint = (PVOID) tcstoull(args[1]);
        if (m_BreakPointList.count(lpBreakPoint)) {
            return;
        }
        m_Process.ReadMemory(lpBreakPoint, &m_BreakPointList[lpBreakPoint], sizeof(m_BreakPointList[lpBreakPoint]));
        m_Process.WriteMemory(lpBreakPoint, (LPVOID) &INT3, sizeof(INT3));
    }

如果调试事件 EXCEPTION_DEBUG_EVENT 到来且异常类型为断点异常 EXCEPTION_BREAKPOINT 那么:

  • 将 TF 标志寄存器置位。
  • 修正 eip 寄存器减 1 。
  • 恢复断点处的指令。
  • m_lpNeedRecoverBreakPoint 指向断点位置,以便后续 EXCEPTION_SINGLE_STEP 异常到来时恢复断点。
  • 进入命令行交互。
            case EXCEPTION_BREAKPOINT: {
                if (m_bIsSysBreakPoint) {
                    std::cout << _T("EXCEPTION_BREAKPOINT") << std::endl;
                    m_bIsSysBreakPoint = FALSE;
                } else {
                    CONTEXT ctx{};
                    ctx.ContextFlags = CONTEXT_CONTROL;
                    m_Process.GetContext(ctx);
                    ctx.Eip--;
                    if (m_BreakPointList.count((LPVOID) ctx.Eip)) {
                        ctx.EFlags |= 0x100;
                        m_lpNeedRecoverBreakPoint = (LPVOID) ctx.Eip;
                        m_Process.WriteMemory((LPVOID) ctx.Eip, &m_BreakPointList[(LPVOID) ctx.Eip], sizeof(m_BreakPointList[(LPVOID) ctx.Eip]));
                    } 
                    m_Process.SetContext(ctx);
                }
                return PareseCommandLine();
            }

如果调试事件 EXCEPTION_DEBUG_EVENT 到来且异常类型为单步异常 EXCEPTION_SINGLE_STEPm_lpNeedRecoverBreakPoint 不为 NULL 则需要将断点回复。

            case EXCEPTION_SINGLE_STEP: {
                if (m_lpNeedRecoverBreakPoint != NULL) {
                    m_Process.WriteMemory(m_lpNeedRecoverBreakPoint, (LPVOID) &INT3, sizeof(INT3));
                    m_lpNeedRecoverBreakPoint = NULL;
                }
            }

效果展示

效果如下:

skydbg> u 0x772d78f1
772D78F1 mov dword [ebp-0x4], 0xfffffffe
772D78F8 mov ecx, [ebp-0x10]
772D78FB mov [fs:0x0], ecx
772D7902 pop ecx
772D7903 pop edi
772D7904 pop esi
772D7905 pop ebx
772D7906 leave
skydbg> bp 772D7902
skydbg> g
772D7902 pop ecx
skydbg> u
772D7902 pop ecx
772D7903 pop edi
772D7904 pop esi
772D7905 pop ebx
772D7906 leave
772D7907 ret
772D7908 int3
772D7909 int3

单步

单步命令实现思路

单步指令分为单步步入(t 命令)和单步步过(p 命令),这两条命令只有在 call 指令处会有所不同。

  • 单步步入:逢 call 则入
    • 设置 TF 标志位即可
  • 单步步过:逢 call 则过
    • 如果当前指令不是 call 则与单步步入相同
    • 否则在 call 的下一条指令设置临时断点

单步命令代码实现

在这里插入图片描述
为了能够让单步步入,单步步过,用户断点三者能够和谐相处,OnException 实现如上图所示。

注意:单步异常到来时当前指令还没有执行,而断点异常到来时当前指令(int3)已经被执行。

    DWORD OnException(DEBUG_EVENT& de) {
        switch (de.u.Exception.ExceptionRecord.ExceptionCode) {
            case EXCEPTION_BREAKPOINT: {
                if (m_bIsSysBreakPoint) {
                    std::cout << _T("EXCEPTION_BREAKPOINT") << std::endl;
                    m_bIsSysBreakPoint = FALSE;
                } else {
                    CONTEXT ctx{};
                    ctx.ContextFlags = CONTEXT_CONTROL;
                    m_Process.GetContext(ctx);
                    ctx.Eip--;
                    if (m_BreakPointList.count((LPVOID) ctx.Eip)) {
                        ctx.EFlags |= 0x100;
                        m_lpNeedRecoverBreakPoint = (LPVOID) ctx.Eip;
                        m_Process.WriteMemory((LPVOID) ctx.Eip, &m_BreakPointList[(LPVOID) ctx.Eip], sizeof(m_BreakPointList[(LPVOID) ctx.Eip]));
                    } 
                    if ((LPVOID) ctx.Eip == m_lpSignelStepBreakPoint.first) {
                        if (!m_BreakPointList.count((LPVOID) ctx.Eip)) {
                            m_Process.WriteMemory((LPVOID) ctx.Eip, &m_lpSignelStepBreakPoint.second, sizeof(m_lpSignelStepBreakPoint.second));
                        }
                        m_lpSignelStepBreakPoint = {};
                        m_nSingleStepCountP--;
                    } 
                    m_Process.SetContext(ctx);
                }
                return PareseCommandLine();
            }
            case EXCEPTION_SINGLE_STEP: {
                if (m_lpNeedRecoverBreakPoint != NULL) {
                    m_Process.WriteMemory(m_lpNeedRecoverBreakPoint, (LPVOID) &INT3, sizeof(INT3));
                    m_lpNeedRecoverBreakPoint = NULL;
                }
                if (m_nSingleStepCountT || m_nSingleStepCountP) {
                    if (m_nSingleStepCountT) { 
                        m_nSingleStepCountT--; 
                        assert(m_nSingleStepCountP == 0);
                    }
                    if (m_nSingleStepCountP) {
                        m_nSingleStepCountP--;
                        assert(m_nSingleStepCountT == 0);
                    }
                    CONTEXT ctx{};
                    ctx.ContextFlags = CONTEXT_CONTROL;
                    m_Process.GetContext(ctx);
                    if (m_BreakPointList.count((LPVOID)ctx.Eip)) {
                        ctx.EFlags |= 0x100;
                        m_lpNeedRecoverBreakPoint = (LPVOID) ctx.Eip;
                        m_Process.WriteMemory((LPVOID) ctx.Eip, &m_BreakPointList[(LPVOID) ctx.Eip], sizeof(m_BreakPointList[(LPVOID) ctx.Eip]));
                        m_Process.SetContext(ctx);
                    }
                    return PareseCommandLine();
                }
                else if (m_nSingleStepCountP) {
                    m_nSingleStepCountP--;
                    return PareseCommandLine();
                }
            }
        }
    }

针对单步步入命令只需要 TF 置位。

    void HandleTCmd(const std::vector<std::tstring> &args) {
        if (args.size() > 2) {
            std::tcout << _T("Invalid Command") << std::endl;
            return;
        }
        if (!args.empty()) {
            m_nSingleStepCountT = args.size() == 2 ? tcstoull(args[1]) : 1;
        }
        CONTEXT ctx{};
        ctx.ContextFlags = CONTEXT_CONTROL;
        m_Process.GetContext(ctx);
        ctx.EFlags |= 0x100;
        m_Process.SetContext(ctx);
    }

单步步过命令需要判断指令是否是 call 指令,如果不是 call 指令处理方式和单步步过相同,否则需要再 call 指令的下一条指令设置临时断点。

    void HandlePCmd(const std::vector<std::tstring>& args) {
        if (args.size() > 2) {
            std::tcout << _T("Invalid Command") << std::endl;
            return;
        }
        if (!args.empty()) {
            m_nSingleStepCountP = args.size() == 2 ? tcstoull(args[1]) : 1;
        }

        LPVOID lpEip = m_Process.GetPC();
        std::string Code, Asm;
        m_Process.ReadMemory(lpEip, Code, 16);
        DWORD dwCodeLen = m_Asm.GetOneAsm(Code, lpEip, Asm);
        if (Asm.starts_with("call")) {
            m_lpSignelStepBreakPoint.first = (LPVOID) ((SIZE_T) lpEip + dwCodeLen);
            m_Process.ReadMemory(m_lpSignelStepBreakPoint.first, &m_lpSignelStepBreakPoint.second, sizeof(m_lpSignelStepBreakPoint.second));
            m_Process.WriteMemory(m_lpSignelStepBreakPoint.first, (LPVOID) &INT3, sizeof(INT3));
        } else {
            CONTEXT ctx{};
            ctx.ContextFlags = CONTEXT_CONTROL;
            m_Process.GetContext(ctx);
            ctx.EFlags |= 0x100;
            m_Process.SetContext(ctx);
        }
    }

这里我实现单步支持 trace 功能,即一次可以执行多步并记录执行过的命令,因此对于单步步入和步过我都记录了这条命令的剩余执行次数。在用户命令交互函数 PareseCommandLine 中,如果单步命令还有剩余执行次数则直接调用对应命令的处理函数自动执行。

    DWORD PareseCommandLine() {
        ShowOneAsm(m_Process.GetPC());
        if (m_nSingleStepCountT) {
            assert(m_nSingleStepCountP == 0);
            HandleTCmd({});
            return DBG_CONTINUE;
        }
        if (m_nSingleStepCountP) {
            assert(m_nSingleStepCountT == 0);
            HandlePCmd({});
            return DBG_CONTINUE;
        }
        ...

效果展示

首先 trace 功能能够不受断点影响,且断点不受 trace 功能影响。

skydbg> u 0x772d78f1
772D78F1 mov dword [ebp-0x4], 0xfffffffe
772D78F8 mov ecx, [ebp-0x10]
772D78FB mov [fs:0x0], ecx
772D7902 pop ecx
772D7903 pop edi
772D7904 pop esi
772D7905 pop ebx
772D7906 leave
skydbg> bp 772D7902
skydbg> bp 772D7903
skydbg> bp 772D7904
skydbg> g
772D7902 pop ecx
skydbg> t 5
772D7903 pop edi
772D7904 pop esi
772D7905 pop ebx
772D7906 leave
772D7907 ret
skydbg> u 772D7902 6
772D7902 int3
772D7903 int3
772D7904 int3
772D7905 pop ebx
772D7906 leave
772D7907 ret

步过功能遇到 call 指令不会步入且临时断点自动去除。

skydbg> p 5
772D7905 pop ebx
772D7906 leave
772D7907 ret
772D1D3E call 0x7728b6c4
772D1D43 cmp byte [0x7734d1ae], 0x0
skydbg> u 772D1D3E 3
772D1D3E call 0x7728b6c4
772D1D43 cmp byte [0x7734d1ae], 0x0
772D1D4A jnz 0x772d1de5

利用单步异常实现反调试

主要利用了调试器先于 SEH 接管单步异常来实现反调试的特性(实际上这里的单步异常换成其他异常也是可以的)。
在这里插入图片描述

硬件断点

调试寄存器

IA-32 处理器定义了 8 个调试寄存器,分别称为 DR0~DR7 。这个 8 个寄存器结构如下图所示:
在这里插入图片描述

  • DR0~DR3:调试地址寄存器,用于保存 4 个硬件断点的地址。
  • DR4~DR5:保留未使用。
  • DR6:调试状态寄存器,用于在调试事件发生时向调试器报告详细信息。
    • B0~B3:如果其中任何一个置位,则表示是相应的 DR0~DR3 断点引发的调试陷阱。
    • BD:检测到访问调试寄存器,这一位与 DR7GD 位相联系,当 GD 位被置为 1,而且 CPU 发现了要修改调试寄存器(DR0~DR7)的指令时,CPU 会停止继续执行这条指令,把 BD 位设为 1,然后把执行权交给调试异常(#DB)处理程序。
    • BS:单步,这一位与标志寄存器的 TF 位相联系,如果该位为 1,则表示异常是由单步执行(single step)模式触发的。与导致调试异常的其他情况相比,单步情况的优先级最高,因此当此标志为 1 时,也可能有其他标志也为 1。
    • BT:任务切换,这一位与任务状态段(TSS)的 T 标志(调试陷阱标志,debug trap flag)相联系。当 CPU 在进行任务切换时,如果发现下一个任务的 TSS 的 T 标志为 1,则会设置 BT 位,并中断到调试中断处理程序。
    • DR6 寄存器的值建议在每次异常提交之前清除。
  • DR7:调试控制寄存器,用于进一步定义断点的中断条件。
    • R/W0~R/W3:读写域,分别与 DR0~DR3 这 4 个调试地址寄存器相对应,用来指定被监控地址的访问类型。
      • 00:执行断点
      • 01:写断点
      • 10:386 和 486 不支持此组合。对于以后的 CPU,可以通过把 CR4 寄存器的 DE(调试扩展)位设为 1 启用该组合,其含义为“当相应地址进行输入输出(即 I/O 读写)时中断”
      • 11:读写断点,但是从该地址读取指令除外。
    • LEN0~LEN3:长度域, 分别与 DR0~DR3 这 4 个调试地址寄存器相对应,用来指定被监控区域的长度。
      • 00:1 字节长
      • 01:2 字节长
      • 10:8 字节长(奔腾 4 或至强 CPU)或未定义(其他处理器)
      • 11:4 字节长
      • 注意:如果对应 R/Wn 为 0(即执行指令中断),那么这里的设置应该为 0 。
    • L0~L3:局部断点启用, 分别与 DR0~DR3 这 4 个调试地址寄存器相对应,用来启用或禁止对应断点的局部匹配。
      • 如果该位设为 1,当 CPU 在当前任务(线程,寄存器是线程独占的)中检测到满足所定义的断点条件时便中断,并且自动清除此位。
      • 如果该位设为 0,便禁止此断点。
    • G0~G3:全部断点启用,分别与 DR0~DR3 这 4 个调试地址寄存器相对应,用来全局启用或禁止对应的断点(实测没有用,要想真正设置区局断点需要遍历进程中的所有线程然后都设置 Ln,x64dbg 和 OllyDbg 都是这么实现的)。
      • 如果该位设为 1,当 CPU 在任何任务中检测到满足所定义的断点条件时便中断,不会自动清除此位。
      • 如果该位设为 0,便禁止此断点。
    • LEGE:启用局部或者全局(精确)断点。从 486 开始的 IA-32 处理器都忽略这两位的设置。此前这两位是用来启用或禁止数据断点匹配的。对于早期的处理器,当设置有数据断点时,需要启用本设置,这时CPU会降低执行速度,以监视和保证当有指令要访问符合断点条件的数据时产生调试异常。
    • GD:启用或禁止对调试寄存器的保护。当设为 1 时,如果 CPU 检测到将修改调试寄存器(DR0~DR7)的指令,CPU 会在执行这条指令前产生一个调试异常。

内存断点

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1059183.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

EXCEL会计记账报表财务软件企业公司做账系统凭证自动生成报表

本系统基于VBA编程设计&#xff0c;具有界面简洁美观&#xff0c;操作方便快捷&#xff0c;功能完备实用的特点&#xff0c;系统分为基本信息、凭证处理、账簿查询、会计报表、固定资产管理、系统管理、凭证数据库七大模块&#xff0c;您只需要录入记账凭证&#xff0c;就可以自…

(一) 使用 Hugo 搭建个人博客保姆级教程(下篇)

关于博客建站简介,请参考上篇 (一) 使用 Hugo 搭建个人博客保姆级教程(上篇) (一)Hugo 安装 Hugo在多个操作系统下的安装 准备工作 安装golang 安装hugo之前,先安装好golang,推荐安装最新版本。Windows 用户强烈建议使用 Scoop 安装(关于 Scoop 安装及使用可参考 Sc…

HTML5 跨屏前端框架 Amaze UI

Amaze UI采用国际最前沿的“组件式开发”以及“移动优先”的设计理念&#xff0c;基于其丰富的组件&#xff0c;开发者可通过简单拼装即可快速构建出HTML5网页应用&#xff0c;上线仅半年&#xff0c;Amaze UI就成为了国内最流行的前端框架&#xff0c;目前在Github上收获Star数…

【Java每日一题】— —第二十题:杨辉三角(直角三角形)。(2023.10.04)

&#x1f578;️Hollow&#xff0c;各位小伙伴&#xff0c;今天我们要做的是第二十题。 &#x1f3af;问题&#xff1a; 杨辉三角&#xff08;直角三角形&#xff09;。 解法1 第一步:动态初始化 第二步:为主对角线及第一列的元素赋值1 第三…

C++ 实现运算符重载

代码&#xff1a; #include <iostream> #include <cstring>using namespace std;class myString { private:char *str; //记录c风格的字符串int size; //记录字符串的实际长度 public://无参构造myString():size(10){str new char[size]; …

侯捷 C++ STL标准库和泛型编程 —— 8 适配器

8 适配器 适配器 Adapter 只是一个小变化&#xff0c;比如改个接口&#xff0c;函数名称等等其出现在三个地方&#xff1a;仿函数适配器&#xff0c;迭代器适配器&#xff0c;容器适配器可以使用继承 / 复合的两种方式实现&#xff0c;STL中都用复合 其思想就是将该记的东西记…

U盘插上就显示让格式化是坏了吗?

U盘以其体积小巧、存储容量大、读写速度快的特点&#xff0c;在各种工作和个人使用场合中得到了广泛应用&#xff0c;因此深得用户好评。然而&#xff0c;在日常使用U盘的过程中&#xff0c;经常会遇到一些问题和挑战。今天&#xff0c;我将为大家详细解释U盘出现要求格式化的现…

嵌入式Linux应用开发-驱动大全-同步与互斥③

嵌入式Linux应用开发-驱动大全-同步与互斥③ 第一章 同步与互斥③1.4 Linux锁的介绍与使用1.4.1 锁的类型1.4.1.1 自旋锁1.4.1.2 睡眠锁 1.4.2 锁的内核函数1.4.2.1 自旋锁1.4.2.2 信号量1.4.2.3 互斥量1.4.2.4 semaphore和 mutex的区别 1.4.3 何时用何种锁1.4.4 内核抢占(pree…

用于工业物联网和自动化的 Apache Kafka、KSQL 和 Apache PLC4

由于单一系统和专有协议&#xff0c;数据集成和处理是工业物联网&#xff08;IIoT&#xff0c;又名工业 4.0 或自动化工业&#xff09;中的巨大挑战。Apache Kafka、其生态系统&#xff08;Kafka Connect、KSQL&#xff09;和 Apache PLC4X 是以可扩展、可靠和灵活的方式实现端…

MATLAB算法实战应用案例精讲-【优化算法】雪融优化器(SAO)(附MATLAB代码实现)

前言 算法原理 算法步骤 ①初始化阶段: 与大多数智能算法相似,就是随机生成一批粒子: ②探索阶段 当雪或由雪转化的液态水转化为蒸汽时,由于不规则的运动,搜索代理呈现出高度分散的特征。在这项研究中,布朗运动被用来模拟这种情况。作为一个随机过程,布朗运动被广…

讲讲项目里的仪表盘编辑器(四)分页卡和布局容器组件

讲讲两个经典布局组件的实现 ① 布局容器组件 配置面板是给用户配置布局容器背景颜色等属性。这里我们不需要关注 定义文件 规定了组件类的类型、标签、图标、默认布局属性、主文件等等。 // index.js import Container from ./container.vue; class ContainerControl extends…

六、vpp 流表+负载均衡

草稿&#xff01;&#xff01;&#xff01; vpp node其实就是三个部分 1、plugin init 2、set command 3、function 实现功能&#xff0c;比如这里的流表 今天我们再用VPP实现一个流表的功能 一、流表 1.1流表----plugin init VLIB_REGISTER_NODE 注册流表节点 // 注册流…

15-自动化测试——理论知识

目录 1.什么是自动化测试&#xff1f; 2.常见的自动化测试分类 2.1.单元测试&#xff08;Java、Python&#xff09; 2.2.接口测试&#xff08;Java、Python&#xff09; 2.3.UI测试&#xff08;移动端、网站&#xff09; 3.如何实施自动化测试&#xff1f; 4.自动化测试…

测开 | Vue速查知识点

文章目录 Vue知识1. Vue 概述2. Vue 代码格式3. Vue 指令3.1 v-bind & v-model3.2 v-on3.3 v-if和v-show3.4 v-for 4. 生命周期 Vue知识 1. Vue 概述 简介&#xff1a; Vue.js&#xff08;读音 /vjuː/, 类似于 view&#xff09; 是一套构建用户界面的 渐进式框架。与其他…

[QT编程系列-44]: Windows + QT软件闪退的检测方法

目录 一、Windows程序闪退的问题定位方法 1.1 Windows程序闪退 1.2 要找到Windows程序的crash点 1.3 当Windows程序崩溃时&#xff0c;可以尝试以下方法获取出错信息&#xff1a; 二、关键工具的进一步分析 2.1 Windows事件查看器&#xff08;Event Viewer&#xff09; …

openGauss学习笔记-87 openGauss 数据库管理-内存优化表MOT管理-内存表特性-使用MOT-MOT使用简单概述

文章目录 openGauss学习笔记-87 openGauss 数据库管理-内存优化表MOT管理-内存表特性-使用MOT-MOT使用简单概述87.1 授予用户权限87.2 创建/删除MOT87.3 为MOT创建索引 openGauss学习笔记-87 openGauss 数据库管理-内存优化表MOT管理-内存表特性-使用MOT-MOT使用简单概述 使用…

【python的输入】sys.stdin与sys.argv

在老师的课堂里碰到了sys.stdin与sys.argv&#xff0c;虽然是很简单的东西&#xff0c;还是花了大半天的时间才勉强理解。在这里记录一下学习过程&#xff0c;方便以后用到复习。 一、sys.stdin 根据python3 library里的解释&#xff0c; sys.stdin可用于所有交互式的输入。 …

Vscode爆红Delete `␍`eslintprettier/prettier

一、先看报错 文件中爆红&#xff0c;提示 Delete ␍eslintprettier/prettier 二、解决方案 项目根目录下&#xff0c;.prettierrc.js 文件中&#xff1a; endOfLine: auto,三、重启VsCode 此时不在爆红&#xff0c;问题完美解决

STM32CubeMX学习笔记-USB接口使用(HID按键)

STM32CubeMX学习笔记-USB接口使用&#xff08;HID按键&#xff09; 一、USB简介1.1 USB HID简介 二、新建工程1. 打开 STM32CubeMX 软件&#xff0c;点击“新建工程”2. 选择 MCU 和封装3. 配置时钟4. 配置调试模式 三、USB3.1 参数配置3.2 引脚配置3.3 配置时钟3.4 USB Device…

数据分析与挖掘: 红楼梦人物关系(Python)词云图

一: 角色剧本 第一代&#xff1a;水字辈祖宗创下基业 贾源、贾演兄弟二人帮先帝打江山立下战功&#xff0c;贾演被封为宁国公&#xff08;大约有平定江山安宁天下之意&#xff09;&#xff0c;贾源被封荣国公&#xff08;大约有强国富民之功&#xff09;。贾源贾演二兄弟皆是一…