- 逆向工程
- 调试器
- 开发工具
- 应用安全
【免费下载链接】x64dbg
An open-source user mode debugger for Windows. Optimized for reverse engineering and malware analysis.
异常断点是 Windows 用户态逆向分析中最常用的调试手段之一,而singleshoot(一次性)标志则赋予异常断点"命中一次即自动消失"的临时断点语义。本文以 x64dbg 的SetExceptionBreakpointSingleshoot命令为切入点,完整讲解该命令的语法、参数语义、典型应用场景,并结合仓库源码(命令处理实现、断点内核、异常名称映射)剖析其底层执行链路。读完本文,你将掌握如何为异常断点开启/关闭一次性命中行为,并能理解该标志从设置、命中到自动删除的完整生命周期。
命令是什么
SetExceptionBreakpointSingleshoot是 x64dbg 条件断点控制(conditional breakpoint control)命令族中的一员,用于设置某个异常断点的 singleshoot 标志。当该标志被启用时,异常断点在第一次命中后会被自动删除,从而只拦截一次异常,之后调试器恢复到原来的异常处理策略,不再对该异常码产生中断。
与之配套的SetExceptionBPX命令(见 SetExceptionBPX.md)负责创建异常断点;而SetExceptionBreakpointSingleshoot则负责在创建之后调整其一次性语义。二者结合,即可实现"捕获下一个 X 异常,然后放行"的经典临时观察场景。
命令在调试器中注册于 src/dbg/x64dbg.cpp#L255:
dbgcmdnew("SetExceptionBreakpointSingleshoot", cbDebugSetBPXExceptionSingleshoot, true); //set breakpoint singleshoot第三个参数true表示该命令可中断执行(即允许在调试会话运行状态下从命令栏或脚本中执行)。
语法与参数详解
SetExceptionBreakpointSingleshoot arg1 [, arg2]arg1:断点标识
arg1用于定位要修改的异常断点,支持三种形式:
| 形式 | 说明 | 示例 |
|---|---|---|
| 断点名称 | 通过SetExceptionBreakpointName自定义的名称 | "MyBp" |
| 异常名称 | 内置异常数据库中登记的助记名(大小写需匹配) | ACCESS_VIOLATION、INT3 |
| 异常代码 | 数值形式的异常码,支持表达式 | 0xC0000005、C0000005 |
从实现上看,查找逻辑由BpGetAny完成(src/dbg/breakpoint.cpp#L314-L338),其查找顺序为:
- 先按名称在断点全局表中查找(
BpGet(0, Type, Name, Bp)); - 若失败且类型不是 DLL 断点,则将
arg1当作数值表达式解析(valfromstring),按地址/代码查找; - 若类型为
BPEXCEPTION且数值解析也未命中,再调用ExceptionNameToCode将异常助记名转换为异常码后查找。
其中ExceptionNameToCode(src/dbg/exception.cpp#L107-L112)依次查询两张映射表:ExceptionNames(如ACCESS_VIOLATION、INT3等)与NtStatusNames(NTSTATUS 状态码名称)。这意味着部分 NTSTATUS 名称同样可以作为arg1传入。
arg2:singleshoot 标志
arg2为可选的布尔标志,语义如下:
| 取值 | 行为 |
|---|---|
| 省略 | 默认启用singleshoot(源码默认singleshoot = true) |
0 | 禁用 singleshoot,断点命中后保留,可多次命中 |
| 非 0 值 | 启用 singleshoot,断点第一次命中后被自动删除 |
arg2通过表达式求值函数valfromstring解析(支持1、0以及任何非零表达式),解析结果非零即视为启用。
返回与结果变量
该命令不设置任何结果变量(如$result),成功时返回true,失败时返回false并在日志窗口输出错误信息,典型错误包括:
No such breakpoint "<arg1>":未找到对应的异常断点;Can't set singleshoot on breakpoint "<arg1>":内部设置失败(例如断点表锁定异常或底层引擎调用失败)。
实战:实现"只捕获一次异常"
下面以一个典型场景演示完整流程:程序运行时频繁产生访问违例(0xC0000005),你只想在下一次访问违例时中断,观察现场后放行后续所有同类异常。
// 1. 创建 0xC0000005 的异常断点(默认捕获 first-chance) SetExceptionBPX ACCESS_VIOLATION // 2. 将其设为 singleshoot:命中一次后自动删除 SetExceptionBreakpointSingleshoot ACCESS_VIOLATION // 或省略 arg2(默认启用): SetExceptionBreakpointSingleshoot ACCESS_VIOLATION, 1 // 或按异常码定位: SetExceptionBreakpointSingleshoot 0xC0000005命中行为验证:断点触发时,日志会输出类似Exception Breakpoint ACCESS_VIOLATION (C0000005) at 0x7FF6...!的信息(该日志由 src/dbg/debugger.cpp#L828-L834 的printExceptionBpInfo生成);随后调试器调用BpDelete将该断点从全局表中移除。之后再次发生 0xC0000005 时,异常将交由默认异常处理策略(通常是"首次异常不中断、交由程序处理"),不再中断。
如需取消一次性语义、恢复持久拦截:
SetExceptionBreakpointSingleshoot ACCESS_VIOLATION, 0底层实现原理
命令分发:类型通用化设计
SetExceptionBreakpointSingleshoot是五类断点 singleshoot 命令之一,全部注册于 src/dbg/x64dbg.cpp:
// L203: 软件断点 dbgcmdnew("SetBreakpointSingleshoot", cbDebugSetBPXSingleshoot, true); // L216: 硬件断点 dbgcmdnew("SetHardwareBreakpointSingleshoot", cbDebugSetBPXHardwareSingleshoot, true); // L229: 内存断点 dbgcmdnew("SetMemoryBreakpointSingleshoot", cbDebugSetBPXMemorySingleshoot, true); // L242: DLL 断点 dbgcmdnew("SetLibrarianBreakpointSingleshoot", cbDebugSetBPXDLLSingleshoot, true); // L255: 异常断点 dbgcmdnew("SetExceptionBreakpointSingleshoot", cbDebugSetBPXExceptionSingleshoot, true);异常断点的回调只是一个薄封装(src/dbg/commands/cmd-conditional-breakpoint-control.cpp#L466-L469):
bool cbDebugSetBPXExceptionSingleshoot(int argc, char* argv[]) { return cbDebugSetBPXSingleshootCommon(BPEXCEPTION, argc, argv); }真正的工作在通用函数cbDebugSetBPXSingleshootCommon中完成(src/dbg/commands/cmd-conditional-breakpoint-control.cpp#L94-L119),其流程为:
- 参数不足 2 个(缺
arg1)时直接失败; - 解析
arg2:缺省置true,否则valfromstring求值,非零为真; - 调用
BpGetAny定位断点,失败打印No such breakpoint; - 调用
BpSetSingleshoot写入标志,失败打印错误信息; - 调用
DebugUpdateBreakpointsViewAsync()异步刷新 GUI 断点视图。
内核写入:BpSetSingleshoot
断点实体定义在 src/dbg/breakpoint.h#L26-L47,其中:
bool singleshoot = false; // whether the breakpoint should be deleted on first hitBpSetSingleshoot(src/dbg/breakpoint.cpp#L559-L597)在持有LockBreakpoints互斥锁的前提下完成写入:
- 对所有类型:直接更新
bpInfo->singleshoot字段; - 对软件断点(
BPNORMAL):同步更新 TitanEngine 侧的titantype,通过位运算设置/清除UE_SINGLESHOOT标志,若断点处于启用状态则先DeleteBPX再SetBPX重新下发,确保底层引擎与内部状态一致; - 对内存断点(
BPMEMORY):同样先移除再以!singleshoot参数重设(SetMemoryBPXEx),因为 TitanEngine 的内存断点 API 直接把"是否一次性"作为参数传递; - 异常断点(
BPEXCEPTION)本身不依赖硬件/软件断点指令,因此落入default分支,仅更新内部标志即可。
BP_TYPE枚举定义于 src/dbg/breakpoint.h#L17-L24,异常断点对应BPEXCEPTION = 4。值得注意的是,异常断点的addr字段直接存储异常码本身(见 src/dbg/breakpoint.cpp#L163-L166),无需换算模块基址,这也是它能按名称/代码灵活定位的原因。
命中即删:singleshoot 的生效点
singleshoot 的真正语义在断点命中回调中兑现。handleBreakCondition(src/dbg/debugger.cpp#L878-L888)开头即检查该标志:
static void handleBreakCondition(const BREAKPOINT & bp, const void* ExceptionAddress, duint CIP, bool forceSilent) { if(bp.singleshoot) { BpDelete(bp.addr, bp.type); if(bp.type == BPHARDWARE) // Remove this singleshoot hardware breakpoint { if(TITANDRXVALID(bp.titantype) && !DeleteHardwareBreakPoint(TITANGETDRX(bp.titantype))) dprintf(QT_TRANSLATE_NOOP("DBG", "Delete hardware breakpoint failed: %p (DeleteHardwareBreakPoint)\n"), bp.addr); } } ... }即:断点一命中,先将其从内部全局断点表中删除(BpDelete,见 src/dbg/breakpoint.cpp#L363-L373,异常断点直接按地址即异常码删除),硬件断点还需额外清理 DR 寄存器状态;删除之后再决定是否打印命中日志、是否执行条件/命令等后续动作。因此"删除"发生在断点回调执行的最前端,确保一次命中后该断点立即失效。
持久化:singleshoot 随数据库保存
x64dbg 会将断点状态保存到用户数据库中。BpCacheSave在序列化断点时写入titantype(软件断点场景下其中含UE_SINGLESHOOT位);加载时由BpCacheLoad(src/dbg/breakpoint.cpp#L947-L1015)恢复type、addr(异常断点即异常码)、titantype等字段。因此开启 singleshoot 的异常断点在dbsave后重新加载,一次性语义仍会保留。
GUI 中的对应操作
在断点视图(Breakpoints 视图)中右键编辑某个异常断点时,GUI 层会通过向调试器发送命令字符串来落地修改。见 src/gui/Src/Utils/Breakpoints.cpp#L348-L360:
case bp_exception: ... exec(QString("SetExceptionBreakpointSilent %1, %2").arg(addrText).arg(bp.silent)); exec(QString("SetExceptionBreakpointSingleshoot %1, %2").arg(addrText).arg(bp.singleshoot)); break;其中addrText为断点地址(即异常码)文本。同理,src/gui/Src/Gui/BreakpointsView.cpp#L849 在生成"设置一次性"命令时也使用SetExceptionBreakpointSingleshoot前缀。这意味着命令行与 GUI 编辑走的是同一条命令通道,行为完全一致。
命令家族与协同使用
SetExceptionBreakpointSingleshoot属于"条件断点控制"命令族,同目录(docs/commands/conditional-breakpoint-control/)下还有针对异常断点的:
SetExceptionBreakpointCondition:命中时求值条件,条件为真才停下;SetExceptionBreakpointLog/SetExceptionBreakpointLogCondition:命中时输出日志(可带条件);SetExceptionBreakpointCommand/SetExceptionBreakpointCommandCondition:命中时执行脚本命令;SetExceptionBreakpointFastResume:命中后不经过 GUI/插件直接恢复运行;SetExceptionBreakpointSilent:命中时不打印默认提示。
singleshoot 与上述属性正交,可自由组合。例如"下一次命中时记录日志并立即恢复、且只发生一次":
SetExceptionBPX 0xC0000005 SetExceptionBreakpointSingleshoot 0xC0000005 SetExceptionBreakpointFastResume 0xC0000005 SetExceptionBreakpointLog 0xC0000005, "AV at {cip}"对其它断点类型,x64dbg 提供了语法一致的同族命令(SetBreakpointSingleshoot.md、SetHardwareBreakpointSingleshoot、SetMemoryBreakpointSingleshoot、SetLibrarianBreakpointSingleshoot),差异仅在arg1的定位方式(地址、模块名等)与底层落地手段(TitanEngine 软件断点位标志、DR 寄存器、内存页属性、模块加载通知)。
注意事项与最佳实践
- 省略
arg2即默认启用:与直觉相反,只传arg1时命令默认把 singleshoot 置为true(源码auto singleshoot = true;)。若只想确认断点存在而不想改动标志,请显式传入0。 arg1的优先级:查找时先匹配断点名称,再按数值解析,最后按异常名称转换。若你给断点起的名字恰好是十六进制数或异常名,会优先命中名称匹配,请留意命名冲突。- 异常名称区分大小写:
ExceptionNameToCode使用字符串精确比较(见 src/dbg/exception.cpp#L94-L105),传入access_violation这类小写形式无法匹配。 - 异常断点与"一次性"天然契合:异常断点不消耗调试寄存器、不修改目标指令字节,临时创建/删除开销极小,配合 singleshoot 非常适合在大量同类异常中精准抓取一次现场。
- 调试器未运行时的行为:断点相关命令(包括查找与设置)依赖调试会话,
BpGet在DbgIsDebugging()为假时直接返回false(src/dbg/breakpoint.cpp#L209-L212),因此该命令应在调试会话激活后使用。
小结
SetExceptionBreakpointSingleshoot通过"命中一次即自动删除"的语义,为异常断点提供了精准的一次性观察能力。其实现横跨命令解析层(cbDebugSetBPXSingleshootCommon)、断点内核层(BpSetSingleshoot、BREAKPOINT::singleshoot)与命中回调层(handleBreakCondition中的BpDelete),并在 GUI 与数据库持久化中保持一致。理解这条调用链,不仅能熟练使用该命令,也能举一反三地掌握整个断点属性控制命令族的架构模式。
- 逆向工程
- 调试器
- 开发工具
- 应用安全
【免费下载链接】x64dbg
An open-source user mode debugger for Windows. Optimized for reverse engineering and malware analysis.
相关推荐
x64dbg 条件断点实战:SetLibrarianBreakpointCommandCondition 命令详解与底层实现
x64dbg 条件断点实战:SetLibrarianBreakpointCommandCondition 命令详解与底层实现 导读 SetLibrarianBr
逆向工程调试器开发工具应用安全x64dbg 内存断点命名实战:bpmname 命令用法与底层实现解析
x64dbg 内存断点命名实战:bpmname 命令用法与底层实现解析 x64dbg 是一款面向 Windows 逆向工程与恶意软件分析的开源用户态调试器。在调
逆向工程调试器开发工具应用安全x64dbg 异常断点命令 SetExceptionBPX 详解:语法、机会类型与底层捕获机制
x64dbg 异常断点命令 SetExceptionBPX 详解:语法、机会类型与底层捕获机制 异常断点是逆向工程与恶意软件分析中最常用的调试手段之一。本文围绕
逆向工程调试器开发工具应用安全
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考