Crash triage(崩溃分类诊断)
用途
从一份 core dump 和产生它的精确二进制出发,回答三个问题:死在哪里(用户代码的第一帧)、为什么(崩溃类型及置信度)、该修什么。每条结论都要绑定证据。程序全程不执行,REA 和 gdb 只读取文件。
核心限制
- 不要运行崩溃程序,即使是为了复现或确认崩溃,即使被要求“复现它”。此类请求不构成对某条具体命令的批准:二进制可能触碰生产数据、网络或所在机器。正确做法是从 core 完成诊断,然后给出你打算使用的确切命令、输入和环境,等待用户批准该命令。Hermes 在执行终端命令前不会提示(一次性运行会批准所有操作),因此这条规则是唯一保障。无人值守运行时绝不执行,只报告能复现崩溃的命令。
- REA 的工具位于 Hermes 的工具搜索之后,按 reverse-engineer-anything 技能的说明操作:用 tool_search 查找(例如 rea recorded crash、rea binary layout),用 tool_describe 加载 schema,用 tool_call 调用。工具缺失或提供方不可用时,遵循该技能的连接与恢复指南。
步骤
1. 收集输入
- core 文件:core_pattern 为 core 时是 ./core;systemd 主机上用 coredumpctl list 再 coredumpctl dump <pid> -o core;否则用崩溃报告器或 CI 产物给出的路径。
- 产生崩溃的精确二进制:重新构建的二进制会给出错误的函数名。若安装了 elfutils,eu-unstrip -n --core CORE 会列出 core 中记录的各模块 build ID,可与 readelf -n BINARY 对比。
- 可选:程序日志(崩溃时在做什么)、源码检出、gdb。
inspect_recorded_crash 和 inspect_binary_layout 需要调用方在 REA_PWNTOOLS_PYTHON 中提供的 pwntools Python(pwntools 4.15.0、pyelftools 0.33、Unicorn 2.1.2)。REA 不会安装它,未经询问也不应自行安装。Hermes 只向 MCP 服务器传递少量环境变量,因此用户需在 Hermes profile 的 .env(~/.hermes/.env)中设置,而不能只在 shell 中设置。若 REA 报 provider_unavailable,通常就是这个原因:要说明这一点,并改用辅助脚本和 gdb 继续。
2. 读取崩溃记录
调用 inspect_recorded_crash,参数 {"path": "<core 绝对路径>"}。注意:
- 信号、其 si_code 和故障地址(只有 SIGSEGV 的 code 1 和 2 能确定故障地址);
- 收到信号的线程的程序计数器(rip/pc)、栈指针和其他寄存器(多线程 core 每线程一条记录);
- Evidence ID,用于在报告中引用。
3. 把程序计数器转换为函数
REA 返回 core 的 NT_FILE note 原始字节。用本技能的辅助脚本解码(仅标准库,只读文件,不运行任何东西):
python3 <this skill's directory>/scripts/core_mappings.py CORE --binary BINARY --addr <pc>
它会打印映射的文件、二进制的加载偏移,以及每个地址对应的模块和链接地址(在二进制内)或 module_offset(在库内)。然后调用 inspect_binary_layout,传入二进制路径(大二进制用 "detail": "summary",再分页查看函数),找到满足 value <= linked < value + size 的函数。以 function+offset 形式报告,并标注为 computed(由记录的 pc 加映射计算),而非 recorded。
若 pc 位于某个库中(libc 的 strlen、memcpy、free 等),该库通常不是责任方:调用方传了坏指针或重复释放了内存。需要解释的是用户二进制中的第一帧(见步骤 4)。
4. 获取回溯(可选)
安装了 gdb 时,以 core-file 模式读取 core。此方式不加载 init 文件、自动加载脚本或 debuginfod,也不运行程序:
gdb -nx -batch -iex 'set auto-load off' -iex 'set debuginfod enabled off' \
-ex 'bt' -ex 'info registers' -ex 'x/8i $pc' -- BINARY CORE
另一种方式是,当用户为 REA 配置了 pwndbg 时,调用 inspect_recorded_crash 并设 "include_debugger_context": true。两者都没有时,只有出错帧:应在报告中说明,而不要猜测调用者。有调试信息(-g)时,addr2line -f -e BINARY <linked> 给出源文件和行号。
5. 分类崩溃
| 证据 | 崩溃类型 | 置信度 |
|---|---|---|
| SIGSEGV,故障地址低于 0x1000 | 空指针解引用 | 高 |
| SIGSEGV,故障地址为小偏移(低于约 64 KiB) | 通过 NULL 结构体指针读字段 | 中 |
| SIGSEGV,故障地址恰在栈指针之下,或出现深层重复帧 | 栈溢出(失控递归、超大局部缓冲区) | 出现重复帧时为高 |
| 只读或代码映射上的 SIGSEGV code 2 | 写只读内存(字符串字面量、const 数据) | 高 |
| SIGSEGV 且 pc 等于故障地址 | 通过坏函数指针跳转(已释放对象、损坏 vtable) | 中 |
| SIGSEGV code 128(SI_KERNEL),无故障地址 | 通用保护错误:非规范垃圾指针(释放后使用、未初始化) | 中 |
| SIGABRT | abort():断言失败、未捕获的 C++ 异常(std::terminate)或 glibc 堆检查(“double free or corruption”、“malloc(): ...” ) | 取决于帧和日志消息 |
| SIGFPE | 整数除零(或 INT_MIN / -1) | 高 |
| SIGILL | trap 指令(ud2、__builtin_trap、编译器插入的 UB trap)或损坏的 pc | 中 |
| SIGBUS | 访问被截断的内存映射文件末尾之外,或未对齐访问 | 中 |
对 NULL 和垃圾指针崩溃,要找出存放坏值的寄存器:反汇编出错指令(gdb 的 x/8i $pc,或先 open_binary 再对步骤 3 的函数做 analyze_function;注意大二进制上第一次 Ghidra 支持的查询可能要数分钟)。内存操作数(mov (%rax),%eax 中 rax = 0)指明寄存器;再追踪该寄存器从哪里被加载。伪代码是反编译器输出,不是原始源码:要用汇编佐证。
6. 找根因
崩溃点是坏值被使用的地方。修复应在它产生的地方:指针为何为 NULL(从未分配、查找失败、未检查错误)、谁释放了内存、递归为何不停止。
- 有源码检出时,阅读出错函数及其调用者,用 git log -L 或 git blame 查看相关行最近的变化。
- 堆破坏导致的 abort 指向的是发现损坏的分配器,而非造成损坏的代码。建议用 -fsanitize=address,undefined -g 重新构建并重跑失败用例,这能指出元凶。是否运行由用户决定:先询问。
- 用日志把崩溃与某个输入关联起来:崩溃前最后处理的请求、文件或记录。
7. 撰写报告
使用以下结构,GitHub Markdown 格式,便于粘贴到 issue:
# Crash: <crash type> in `<function+offset>`
## Summary
<program> crashed with <signal>: <one sentence on what happened and where>.
## Crash details
| Field | Value |
| --- | --- |
| Signal | SIGSEGV (code 1) |
| Fault address | `0x0` |
| Program counter | `0x...` |
| Location | `<function+offset> (<module>+0x...)`, `<file:line>` if known |
| Confidence | high / medium / low |
| Signature | <crash type> + top 3 function names in the user's code |
## Backtrace
## Faulting instruction and registers
## Likely cause
## Suggested next steps
## Evidence and limitations
- Core and binary SHA-256, REA Evidence IDs, gdb used or not
- Which facts were recorded (signal, registers), computed (function+offset)
or inferred (crash type, cause)
- The program was not executed
签名只使用崩溃类型和函数名,绝不使用地址或路径,这样多个 core 中的同一缺陷能归为一组。有多个 core 时,逐个诊断,按签名分组并按数量排序。
注意事项
- core 是进程内存的副本,可能含有密码、令牌和用户数据。要在其所在位置分析,绝不粘贴原始内存或上传 core。
- 报告包含路径、寄存器值和日志行。在用户公开发布前应提醒这一点。
- 复现崩溃意味着运行程序。见开头规则:提出确切命令并等待该命令获批;无人值守运行时只报告。用 sanitizer 重新构建并重跑测试同理。