Cutter Debug 菜单深度解析:调试、仿真与单步执行的完整工作流
【免费下载链接】cutterFree and Open Source Reverse Engineering Platform powered by rizin项目地址: https://gitcode.com/gh_mirrors/cu/cutter
Cutter 的 Debug(调试)菜单是动态分析的核心入口,它把 rizin 底层的调试能力封装为一组 GUI 操作:启动调试会话、启动 ESIL 仿真、附加到进程、连接远程调试器,以及单步进入/单步跳过/单步跳出、继续执行、继续到调用、继续到系统调用等执行控制动作。本文基于用户文档 debug-menu.rst 完整继承其全部条目与快捷键,并结合 DebugActions.cpp 与 Cutter.cpp 的源码实现,说明每个菜单项背后的真实调用链、快捷键定义位置和实际行为差异(例如调试模式与仿真模式走不同的 rizin API)。
1. 调试菜单的整体结构:四个入口按钮与一个"继续"下拉
在 DebugActions.cpp 中,调试工具栏由两类QToolButton组织:
- Start 按钮(MenuButtonPopup 弹出菜单),包含四个动作:
actionStart(Start debug)、actionStartEmul(Start emulation)、actionAttach(Attach to process)、actionStartRemote(Connect to a remote debugger)。直接点击按钮默认触发"Start debug"。 - Continue 按钮(continueUntilButton),下拉菜单包含
actionContinueUntilMain(Continue until main)、actionContinueUntilCall(Continue until call)、actionContinueUntilSyscall(Continue until syscall),默认项为 Continue until main。
此外工具栏还平铺了 Continue、Stop、Step over、Step into、Step out、反向单步、反向继续和 trace 按钮(见 DebugActions.cpp)。所有调试动作默认隐藏,仅在调试会话建立后通过setAllActionsVisible(true)显示(DebugActions.cpp)。
所有快捷键由快捷键管理器统一定义,见 DefaultShortcuts.cpp:
| 动作 | 快捷键(源码定义) | 文档快捷键 |
|---|---|---|
| Debug.start(Start debug) | F9 | F9 |
| Debug.continue(Continue) | F5 | F5 |
| Debug.continueBack | Ctrl+F5 | —(文档未提及,属于反向调试扩展) |
| Debug.step(Step into) | F7 | F7 |
| Debug.stepOver(Step over) | F8 | F8 |
| Debug.stepOut(Step out) | Ctrl+F8 | Ctrl+F8 |
| Debug.stepBack | Ctrl+F7 | —(依赖 trace 会话后可见) |
Continue until call / Continue until syscall 在源码中没有绑定全局快捷键,只通过 Continue 下拉菜单触发。
2. Start Debugging:启动本地调试会话
功能描述(继承自原文档):Start the debugging session of the current loaded binary(为当前加载的二进制启动调试会话)。
操作路径:Debug -> Start debug;快捷键:F9。
源码实现位于 DebugActions::startDebug(),实际流程比"点一下开始"更细致:
- 可执行权限检查:读取
file.path配置,若非调试状态且文件没有可执行权限,弹出 "File 'xxx' does not have executable permissions." 提示并中止(DebugActions.cpp)。 - 显示 beta 警告:首次使用调试功能会弹出 "Debug is currently in beta." 提示(
showDebugWarning(),DebugActions.cpp)。 - 弹出 NativeDebugDialog 配置对话框:预填
dbg.args(调试参数)与dbg.profile(调试配置文件)两项 rizin 设置。该对话框支持三种配置方式(见 NativeDebugDialog.h 中的DebugConfigMethod枚举):CommandLine:设置dbg.args,同时清空dbg.profile(profile 优先于 args,故移除);RzRunProfile:将选中的.rz/.rrz配置文件路径写入dbg.profile;RzRunDirectives:通过Core()->setProfileDirectives()直接下发指令。
- UI 状态切换:隐藏 Attach/Emul/Remote 入口,Start 按钮变为 "Restart program" 并换成 spin 图标;调用
setButtonVisibleIfMainExists()检查sym.main或mainflag,若二进制没有 main 函数则隐藏 "Continue until main" 并将下拉默认项改为 "Continue until call"(DebugActions.cpp)。 - 真正启动:调用 CutterCore::startDebug(),它记录调试前的 PC 位置,然后在异步任务中执行
rz_core_file_reopen_debug(core, "")—— 即以调试模式重新打开当前文件。任务完成后会刷新寄存器窗口、关闭asm.flags(避免寄存器名干扰反汇编显示)、发出toggleDebugView()信号切换调试视图。
3. Start Emulation:ESIL 仿真(不真正执行二进制)
功能描述(继承自原文档):Start an emulation session on the current loaded binary. Cutter supports emulation of different file formats. Unlike debugging, emulation isn't really executing the binary, but only emulating the instructions. This is very strong feature for analysis of self-contained functions or programs, to analyze cryptographic algorithms or to deobfuscate data. Emulation isn't limited by the running platform, so Linux files such as ELF can be emulated on Windows platforms, and DLL can be emulated on Linux.
操作路径:Debug -> Start emulation。
底层实现在 CutterCore::startEmulation(),它在异步任务中依次调用三个 ESIL 初始化函数:
rz_core_analysis_esil_reinit(core); // 重新初始化 ESIL 状态 rz_core_analysis_esil_init_mem(core, nullptr, UT64_MAX, UT32_MAX); // 初始化仿真内存 rz_core_analysis_esil_init_regs(core); // 初始化寄存器仿真建立后会设置io.cache = true,使自修改代码等二进制变化可以在视图中观察到;会话结束时(stopDebug 的仿真分支,Cutter.cpp)会释放仿真内存、调用rz_core_analysis_esil_deinit并清除寄存器 flags。
与调试的运行时差异:后续每个执行动作都会根据currentlyEmulating标志分叉——例如 Step into 在仿真模式下调用rz_core_esil_step()+rz_core_reg_update_flags(),而调试模式下调用rz_core_debug_step_one(core, 1)(对比 stepDebug() 的两个分支)。仿真模式下 "Continue until main" 也被隐藏,Continue 下拉的默认项被改为 "Continue until syscall"(DebugActions.cpp),这与原文档中"仿真不受运行平台限制"的定位一致:ESIL 只是按指令语义推演寄存器/内存状态,适合分析加解密例程与去混淆。
4. Attach to Process:附加到正在运行的进程
功能描述(继承自原文档):Attach Cutter's debugger to a running process, instead of spawning a new process.
操作路径:Debug -> Attach to process。
实现链条:attachProcessDialog() 弹出 AttachProcDialog 供用户选择进程,取到 PID 后调用 attachProcess();Stop 按钮随即改为 "Detach from process"(detach 图标),随后调用 CutterCore::attachDebug(pid)。该函数构造dbg://<pid>URI,在异步任务中设置cfg.debug = true,然后:
- 若当前没有打开任何文件:
rz_core_file_open_load(core, uri, 0, RZ_PERM_R, false)直接以该进程为目标打开; - 若已有文件:
rz_core_file_reopen_remote_debug(core, uri, 0)以调试模式重开当前二进制并附加。
任务完成后调用syncAndSeekProgramCounter()将视图定位到目标进程的 PC,并记录currentlyAttachedToPID。
5. Connect to a Remote Debugger:连接远程调试器(GDB / WinKd)
功能描述(继承自原文档):Connect Cutter to a remote debugger such as GDB or WinDbg by providing IP and Port of the remote debugger.
操作路径:Debug -> Connect to a remote debugger。
RemoteDebugDialog 提供远程连接表单,源码中定义了两个后端(RemoteDebugDialog.cpp):
| 后端 | URI 前缀 | 表单要求 |
|---|---|---|
| GDB | gdb:// | 校验 IP 合法性(QHostAddress)与端口范围 1–65535,最终 URI 形如gdb://ip:port |
| WinKd - Pipe | winkd:// | 校验本地管道/路径存在(QFileInfo::exists),URI 形如winkd://path |
对话框还维护最近连接列表:成功连接后 URI 会被写入 QSettings 的recentIpList(DebugActions.cpp),下次打开对话框可直接点击回填表单(fillFormData()按前缀解析出 IP 与端口)。
确认连接后调用 CutterCore::attachRemote(uri):设置cfg.debug = true并执行rz_core_file_reopen_remote_debug(core, uri, 0)。任务完成后会遍历rz_id_storage_list(core->io->files)核对是否真的建立了该 URI 的 IO 描述符;连接失败则发出attachedRemote(false)并弹窗 "Error connecting.",成功后将 Stop 按钮语义改为 Detach(attachRemoteDebugger())。
6. Step Into / Step Over / Step Out:三种单步模式
以下三项继承自原文档的完整描述:
- Step Into:Execute a single assembler instruction, stepping into functions and loops(执行一条汇编指令,并进入函数和循环内部)。路径:Debug -> Step;快捷键
F7。 - Step Over:Execute a single assembler instruction, stepping over functions and procedures. The functions will not be skipped and will be executed by Cutter. The execution will pause when reaching the instruction after the function call(执行一条指令并跨过函数调用;函数会被完整执行,PC 停在调用返回后的第一条指令)。路径:Debug -> Step over;快捷键
F8。 - Step Out:Execute the code and suspends execution when the current function returns(持续执行,直到当前函数返回时暂停)。路径:Debug -> Step out;快捷键
Ctrl+F8。
三者分别映射到 Cutter.cpp 中的独立实现,且都有调试/仿真两条路径:
| 动作 | 调试模式 API | 仿真(ESIL)模式 API |
|---|---|---|
| Step into | rz_core_debug_step_one(core, 1) | rz_core_esil_step(core, UT64_MAX, ...)+rz_core_reg_update_flags |
| Step over | rz_core_debug_step_over(core, 1)+rz_core_dbg_follow_seek_register | rz_core_analysis_esil_step_over(core) |
| Step out | rz_core_debug_step_until_frame(core) | —(源码中 stepOut 仅实现调试分支,见 stepOutDebug()) |
三个动作均以asyncTask形式在debugTask线程执行,避免阻塞 UI;任务结束后统一调用syncAndSeekProgramCounter()同步视图到新的 PC 并刷新寄存器窗口。每个动作入口都先检查currentlyDebugging,非调试状态下点击无效。
7. Continue 及"继续到"系列
Continue(继承自原文档):Continue the execution of the running program. The execution will stop when reached a breakpoint, when manually suspended by the user, or when the running program quits.(继续执行程序,遇到断点、被用户手动挂起或程序退出时停止。)路径:Debug -> Continue;快捷键F5。
一个值得注意的细节:Continue 按钮是继续/挂起二合一的。DebugActions.cpp 中的槽函数根据isDebugTaskInProgress()分流:有调试任务正在运行时调用Core()->suspendDebug()(break 当前任务),否则调用Core()->continueDebug();同时图标在media-skip-forward(continue)与media-suspend(suspend)之间切换(debugTaskStateChanged 槽)。
调试模式下continueDebug()调用rz_debug_continue(core->dbg);仿真模式下则执行一步 ESIL(rz_core_esil_step),这与原文档"仿真只是推演指令而非执行"的说法在源码层面得到印证(Cutter.cpp)。
Continue Until Call(继承自原文档):Continue the execution of the program until a function call is reached.(继续执行直到到达函数调用。)路径:Debug -> Continue until call。底层:调试模式调用rz_core_debug_step_one(core, 0),仿真模式调用rz_core_analysis_continue_until_call(core)(continueUntilCall())。
Continue Until Syscall(继承自原文档):Continue the execution of the program until a Syscall is reached.(继续执行直到到达系统调用。)路径:Debug -> Continue until syscall。调试分支实现较为典型(continueUntilSyscall()):
rz_cons_break_push(..., core->dbg); // 注册 break 回调 rz_reg_arena_swap(core->dbg->reg, true); // 切换寄存器 arena rz_debug_continue_syscalls(core->dbg, nullptr, 0); // 继续直到任意系统调用 rz_cons_break_pop(); rz_core_dbg_follow_seek_register(core);仿真分支则调用rz_core_analysis_continue_until_syscall(core)。
原文档之外,源码中还提供了 Continue until main:它查找sym.main(回退到main)flag 的偏移,然后调用continueUntilDebug(offset)(DebugActions.cpp、Cutter.cpp)。若二进制分析后没有 main flag,该入口会被自动隐藏——这是"从源码结构看"文档未覆盖的一个实用行为。
8. 会话管理:Stop 按钮、进程退出提示与反向调试
原文档聚焦正向调试流程,但理解完整生命周期需要补充 Stop 与 trace 两处的源码事实:
- Stop debug / Stop emulation / Detach:同一个
actionStop按钮按会话类型切换文案。CutterCore::stopDebug() 先挂起运行中的任务,然后按类型清理:仿真分支释放 ESIL 内存与 trace、清除寄存器 flags;调试分支调用rz_core_debug_process_close(core)结束调试进程并复位currentlyAttachedToPID = -1。 - 进程退出提示:DebugActions.cpp 连接了
debugProcessFinished信号,调试进程退出时会弹窗 "Debugged process exited (pid)"。 - trace 与反向调试:
actionTrace切换startTraceSession()/stopTraceSession()(调试分支对应rz_debug_session_new+rz_debug_add_checkpoint),开始 trace 后 Step back(Ctrl+F7)与 Continue back(Ctrl+F5)按钮才可见;仿真分支则基于rz_core_analysis_esil_trace_start/stop(Cutter.cpp)。反向动作的调用(如rz_core_esil_step_back)同样区分仿真与调试路径。
9. 小结:从菜单项到 rizin API 的完整映射
| Debug 菜单项 | 快捷键 | 核心 rizin API(调试模式) | 源码位置 |
|---|---|---|---|
| Start debug | F9 | rz_core_file_reopen_debug | Cutter.cpp#L2083 |
| Start emulation | — | rz_core_analysis_esil_reinit/init_mem/init_regs | Cutter.cpp#L2127 |
| Attach to process | — | dbg://pid+rz_core_file_open_load/reopen_remote_debug | Cutter.cpp#L2238 |
| Connect to remote debugger | — | gdb:///winkd://+rz_core_file_reopen_remote_debug | Cutter.cpp#L2175 |
| Step into | F7 | rz_core_debug_step_one(core, 1) | Cutter.cpp#L2534 |
| Step over | F8 | rz_core_debug_step_over(core, 1) | Cutter.cpp#L2572 |
| Step out | Ctrl+F8 | rz_core_debug_step_until_frame | Cutter.cpp#L2608 |
| Continue | F5 | rz_debug_continue(运行中则 break 挂起) | Cutter.cpp#L2342 |
| Continue until call | — | rz_core_debug_step_one(core, 0) | Cutter.cpp#L2454 |
| Continue until syscall | — | rz_debug_continue_syscalls | Cutter.cpp#L2491 |
需要说明的适用前提:源码中调试功能自标注为 beta(首次启动会弹出提示,DebugActions.cpp);仿真模式下部分动作(如 Step out)在源码中只有调试分支实现;Continue until main 依赖二进制存在sym.main/mainflag。实际行为请以当前仓库 Cutter.cpp 与 DebugActions.cpp 的实现为准。
【免费下载链接】cutterFree and Open Source Reverse Engineering Platform powered by rizin项目地址: https://gitcode.com/gh_mirrors/cu/cutter
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考