x64dbg 异常断点日志条件命令 SetExceptionBreakpointLogCondition 完全指南
【免费下载链接】x64dbgAn open-source user mode debugger for Windows. Optimized for reverse engineering and malware analysis.项目地址: https://gitcode.com/gh_mirrors/x6/x64dbg
导读
SetExceptionBreakpointLogCondition是 x64dbg 中用于为异常断点(exception breakpoint)设置"日志条件"(logging condition)的命令。当调试器命中断点后,是否把预设的日志文本输出到日志窗口,由该条件表达式决定——它独立于断点自身的"中断条件",允许你在断点不中断程序执行的情况下,仅凭条件过滤日志输出。阅读本文后,你将掌握该命令的完整语法、与中断条件/日志文本的分工关系、异常断点的三种寻址方式,以及其底层实现原理与 GUI 联动行为。
一、命令是什么:日志条件 vs 中断条件
在 x64dbg 的条件断点体系中,一个断点可以同时拥有多组相互独立的属性,每组属性各司其职:
| 属性 | 对应命令 | 作用 |
|---|---|---|
| 断点名称 | SetExceptionBreakpointName | 给断点起别名,便于引用 |
| 中断条件(break condition) | SetExceptionBreakpointCondition | 控制是否中断(暂停目标进程) |
| 日志文本(log text) | SetExceptionBreakpointLog | 定义命中时要输出到日志窗口的文本模板 |
| 日志条件(log condition) | SetExceptionBreakpointLogCondition | 控制日志文本是否真的被输出 |
SetExceptionBreakpointLogCondition官方定义如下(原文见 docs/commands/conditional-breakpoint-control/SetExceptionBreakpointLogCondition.md):
Sets the logging condition of an exception breakpoint. When log condition is not specified, log text always be logged regardless of the break condition, otherwise it will be logged when the logging condition is satisfied.
即:
- 未设置日志条件时:只要命中该异常断点,日志文本无条件输出,与中断条件是否满足无关;
- 设置了日志条件时:只有当日志条件表达式求值为真时,日志文本才会被输出。
这正是 x64dbg "免中断追踪"(log-only breakpoint)玩法的核心:断点条件可以永远为假(不暂停程序),日志条件负责筛选关键事件,从而在几乎不干扰目标程序运行节奏的前提下完成行为监控。
与之对应的普通软件断点版本是SetBreakpointLogCondition(见 docs/commands/conditional-breakpoint-control/SetBreakpointLogCondition.md),两者的语义与参数结构完全一致,仅断点类型不同。
二、命令语法与参数详解
SetExceptionBreakpointLogCondition arg1 [, arg2]arg1 —— 异常断点的定位标识
arg1指定要修改的异常断点,支持三种写法(对应源码BpGetAny的解析逻辑,见下文第三节):
- 断点名称:此前通过
SetExceptionBreakpointName设置的别名; - 异常名称:如
EXCEPTION_ACCESS_VIOLATION、EXCEPTION_BREAKPOINT、EXCEPTION_ILLEGAL_INSTRUCTION等; - 异常代码:如
0xC0000005、0x80000003、0xC000001D等十六进制数值。
注意:异常断点的
arg1与软件/硬件/内存断点不同,它定位的是异常类型而非内存地址,因此不需要也不接受模块名+偏移形式的地址表达式。
arg2 —— 日志条件(可选)
arg2是日志条件表达式,遵循 x64dbg 的表达式语法(详见 docs/introduction/Expressions.md)。省略该参数时,等价于"无条件输出日志"(即上文所说的默认行为)。常用的条件形式包括:
// 寄存器比较 SetExceptionBreakpointLogCondition EXCEPTION_ACCESS_VIOLATION, eax==0xC0000005 // 内存引用 SetExceptionBreakpointLogCondition 0xC0000005, [esp+4]==0 // 函数参数 / 变量 / 标志位 SetExceptionBreakpointLogCondition EXCEPTION_ILLEGAL_INSTRUCTION, arg1==0 && arg2返回值
该命令不设置任何结果变量($result等保持不变),这是它与GetBreakpointHitCount系列命令的显著区别。要判断命令是否执行成功,只能观察命令行是否报错(如No such breakpoint "...")。
三、底层实现:从命令注册到断点属性写入
1. 命令注册
在 src/dbg/x64dbg.cpp 中,异常断点四个属性命令集中注册:
dbgcmdnew("SetExceptionBreakpointName", cbDebugSetBPXExceptionName, true); dbgcmdnew("SetExceptionBreakpointCondition", cbDebugSetBPXExceptionCondition, true); dbgcmdnew("SetExceptionBreakpointLog", cbDebugSetBPXExceptionLog, true); dbgcmdnew("SetExceptionBreakpointLogCondition", cbDebugSetBPXExceptionLogCondition, true);第三个参数为true,表示这是一个调试态命令(debug command),只能在调试会话中执行。
2. 命令处理函数
在 src/dbg/commands/cmd-conditional-breakpoint-control.cpp 中:
bool cbDebugSetBPXExceptionLogCondition(int argc, char* argv[]) { return cbDebugSetBPXLogConditionCommon(BPEXCEPTION, argc, argv); }所有断点类型的日志条件命令都收敛到同一个通用助手cbDebugSetBPXLogConditionCommon(同文件第 47-50 行),它又调用更底层的cbDebugSetBPXTextCommon(第 9-30 行)。这段公共逻辑清晰地展示了命令的执行流程:
static bool cbDebugSetBPXTextCommon(BP_TYPE Type, int argc, char* argv[], const String & description, const std::function<bool(duint, BP_TYPE, const char*)> & setFunction) { BREAKPOINT bp; if(IsArgumentsLessThan(argc, 2)) return false; const char* value = ""; if(argc > 2) value = argv[2]; if(!BpGetAny(Type, argv[1], &bp)) { dprintf(QT_TRANSLATE_NOOP("DBG", "No such breakpoint \"%s\"\n"), argv[1]); return false; } if(!setFunction(bp.addr, Type, value)) { dprintf(QT_TRANSLATE_NOOP("DBG", "Can't set %s on breakpoint \"%s\"\n"), description.c_str(), argv[1]); return false; } DebugUpdateBreakpointsViewAsync(); return true; }从源码可以确认几个关键事实:
arg2缺失时,value被置为空字符串"",即清空日志条件,恢复"无条件输出"的默认行为;- 断点不存在时输出
No such breakpoint并返回失败; - 设置成功后调用
DebugUpdateBreakpointsViewAsync(),异步刷新 GUI 的"断点"视图(Breakpoints View),因此你在断点窗口中能实时看到日志条件列的变化。
3. 异常断点的定位:BpGetAny 的三级解析
arg1的三种写法由 src/dbg/breakpoint.cpp 的BpGetAny实现:
bool BpGetAny(BP_TYPE Type, const char* Name, BREAKPOINT* Bp) { if(BpGet(0, Type, Name, Bp)) // 1. 按断点名称查找 return true; if(Type != BPDLL) { duint addr; if(valfromstring(Name, &addr)) // 2. 按数值/异常代码查找 if(BpGet(addr, Type, 0, Bp)) return true; if(Type == BPEXCEPTION) { addr = 0; if(ExceptionNameToCode(Name, reinterpret_cast<unsigned int*>(&addr))) // 3. 按异常名称查找 if(BpGet(addr, BPEXCEPTION, 0, Bp)) return true; } } ... }解析顺序为:断点名称 → 直接数值(异常代码)→ 异常名称。其中异常名称的解析走ExceptionNameToCode(见 src/dbg/exception.cpp),它先在ExceptionNames(常见 Win32 异常名数据库)中查找,若失败再尝试NtStatusNames(NTSTATUS 状态码数据库),所以像STATUS_ACCESS_VIOLATION这类 NT 状态名同样可用。
4. 属性存储
命中断点后,BpSetLogCondition把表达式字符串写入BREAKPOINT结构的logCondition字段(src/dbg/breakpoint.cpp、字段定义见 src/dbg/breakpoint.h)。这一字段同时参与断点数据库的序列化(JSON 导出/导入,见 src/dbg/breakpoint.cpp 与第 980-981 行),意味着日志条件会随数据库持久化保存,下次加载同一调试会话时自动恢复。
四、实战:用日志条件实现免中断异常监控
结合 SetExceptionBreakpointLog(日志文本)与 SetExceptionBreakpointCondition(中断条件),可以搭出一套典型的"只记录不暂停"监控方案。
场景一:仅记录某一模块内发生的访问违例
SetExceptionBPX EXCEPTION_ACCESS_VIOLATION // 先创建异常断点 SetExceptionBreakpointCondition EXCEPTION_ACCESS_VIOLATION, 0 // 中断条件恒假,永不暂停 SetExceptionBreakpointLog EXCEPTION_ACCESS_VIOLATION, "AV at {p:eip} address={p:[esp+4]}" SetExceptionBreakpointLogCondition EXCEPTION_ACCESS_VIOLATION, [esp+4]>=0x00400000 && [esp+4]<0x01000000效果:程序照常运行,只有访问违例的目标地址落在0x00400000 ~ 0x01000000(主模块镜像区间)时,日志窗口才输出一条记录。{p:...}为格式化占位符语法,完整说明见 docs/introduction/Formatting.md。
场景二:按命中次数采样
SetExceptionBreakpointLogCondition EXCEPTION_ILLEGAL_INSTRUCTION, hitcount%100==0命中计数(hitcount)达到 100 的整数倍时才记录,适合高频异常下的日志降噪。
场景三:参数值过滤
SetExceptionBreakpointLogCondition EXCEPTION_BREAKPOINT, arg1==0x1234仅当首个函数参数等于0x1234时输出日志,可在不打断调用链的前提下跟踪特定调用上下文。
场景四:清空日志条件恢复默认
SetExceptionBreakpointLogCondition EXCEPTION_ACCESS_VIOLATION省略arg2即清空条件,之后日志文本将无条件输出(只要该异常断点命中)。
五、GUI 与脚本联动
1. 断点视图
设置成功后,断点窗口(Breakpoints View)的 Log Condition 列会同步显示刚写入的表达式(由DebugUpdateBreakpointsViewAsync()触发刷新);该列空白即表示"无条件输出"。
2. 数据库恢复路径
x64dbg 在加载断点数据库、重建各类型断点属性时,会逐条回放设置命令。在 src/gui/Src/Utils/Breakpoints.cpp 中可以看到异常断点日志条件的恢复逻辑:
exec(QString("SetExceptionBreakpointLogCondition %1, \"%2\"").arg(addrText).arg(DbgCmdEscape(bp.logCondition)));这也解释了为什么该命令必须以字符串形式完整保留表达式——它不仅是运行时属性,也是数据库持久化的载体(同一文件第 210 行在判断断点是否"干净"时同样检查logCondition字段)。
3. 脚本与自动化
命令本身是纯命令行的,可在脚本(Script)中与SetExceptionBreakpointLog搭配使用。由于它不设置结果变量,脚本中如需确认成败,可先执行命令再配合bplist检查断点属性(见 docs/commands/breakpoint-control/bplist.md)。
六、注意事项与易错点
- 必须先在断点:本命令只能修改已存在的异常断点,目标不存在时报
No such breakpoint;先通过SetExceptionBPX(见 docs/commands/breakpoint-control/SetExceptionBPX.md)创建断点。 - 区分中断条件与日志条件:两者语义不同——中断条件决定是否暂停,日志条件决定是否记录。想"只记不停",需把中断条件设为恒假(如
0)同时设置日志条件。 - 表达式求值环境:
arg2在断点命中时求值,可用的寄存器/内存/变量与条件断点一致(参考 docs/introduction/ConditionalBreakpoint.md);表达式写错会导致条件求值失败,表现为日志不按预期输出。 - 调试态命令:只能在调试会话中使用,未附加/未启动目标程序时命令不可用。
- arg1 命名冲突:当断点名称恰好是十六进制数值或异常名时,
BpGetAny按"名称 → 数值 → 异常名"顺序解析,可通过改名避免歧义。
七、总结
SetExceptionBreakpointLogCondition是 x64dbg 条件断点体系中"日志通道"的总开关:它以独立的表达式控制日志输出,与中断条件解耦,让异常断点可以退化为纯监控探针。其实现贯穿 命令注册、命令处理、断点定位 与 属性存储 四层代码,并通过 GUI 断点视图与数据库持久化与用户交互闭环。掌握它与SetExceptionBreakpointLog、SetExceptionBreakpointCondition的组合用法,即可在恶意软件行为分析、协议逆向等高噪声场景中构建高效、低干扰的自动化日志体系。
【免费下载链接】x64dbgAn open-source user mode debugger for Windows. Optimized for reverse engineering and malware analysis.项目地址: https://gitcode.com/gh_mirrors/x6/x64dbg
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考