本节摘要:调试器的断点、单步、寄存器观察背后是"替换指令加异常"与"调试段约定"两套机制;性能分析则靠采样与硬件计数器把时间归因到指令级。本节走通一次跨语言单步的完整会话,解释 release 版调试信息缺失的三种表现,并给出从性能现象倒推热点指令的实操路径。
全书到这一节,"单步"两个字出现了不下二十次——是时候把工具本身讲透了。这节分两半:前半讲调试器怎么工作、怎么用它把 C 与汇编的边界看穿;后半讲性能分析怎么把"程序慢"归因到具体指令。两半共用一个方法论:观察必须能落到寄存器与指令级,落不下来的观察只是猜测。
断点的机制一句话说穿:调试器把目标地址的四字节指令备份后替换成一条非法指令,处理器执行到那里触发异常,调试器接住异常、恢复原指令、把你送到交互界面——所以断点本质是"自导自演的异常",第 6 章的异常机制在这里被工具征用。单步则是另一条硬件通路:设置单步标志让处理器每执行一条就异常一次。理解机制有三处实际收益:断点打在会被优化合并的指令地址上会"跳断点"(那条指令根本没被生成);修改了代码段的断点在自校验程序里会暴露;远程调试时断点替换发生在目标机内存,看 dump 前要记得断点指令还驻留着。
会话从编译选项开始:-g 生成调试信息(把机器地址与源码行、变量名关联起来),-O0 或至少 -Og 防止优化把代码搬得面目全非。一次典型的跨语言单步——C 里调用第 5 章的手写汇编函数:
(gdb) b main (gdb) run (gdb) step // 跨过 bl sum 时用 step 会跟进汇编函数 (gdb) x/6i $pc // 现在看到的是手写汇编 0x400780 <sum>: stp x29, x30, [sp,-32]! 0x400784 <sum+4>: mov x29, sp ... (gdb) info registers x0 x1 // 参数交接点:x0 数组地址 x1 个数,AAPCS 现场 (gdb) finish // 让函数跑完返回,停在调用方 Run till exit from #0 sum () Value returned is $1 = 15 // 返回值在 x0,gdb 顺手告诉你值
解读几个容易忽略的细节:step 与 next 的区别在汇编层面比 C 层面更重要——next 会把 bl 整条当一步跨过,step 才钻进去;finish 在汇编函数上同样有效,它内部靠临时断点打在返回地址;想在某个精确地址下断点用 b *0x400784(星号表示按地址而非符号)。release 版的三种病也值得预演:没 -g 时符号全无,只能按地址断点,bt 全是十六进制;开优化后断点错位与变量"optimized out"是常态;strip 过的二进制连函数名都没有。对策按需选择:调试版找逻辑错、带 -g -O2 的"混合版"找性能问题、release 保持原样上线。

工具分两代。采样式:每几毫秒打断一次程序,统计"恰好停在哪个函数"的频率——开销小、粒度粗,适合先圈定嫌疑范围。计数器式:读硬件性能计数单元的寄存器,精确统计周期数、指令数、缓存未命中次数——一次能算出"每周期指令数"这类体检指标。两个指标给出初步诊断:指令数大而每周期指令数低,八成在等内存(访存瓶颈);指令数本身大,是算法或代码质量问题。热点锁定后用调试器或反汇编把热点函数的循环体展开,对照第 8 章的优化清单逐条检查。这条路的全流程建议亲手跑一遍:给一段故意写坏的代码(比如按列遍历大数组的转置),采样定位热点函数、看反汇编确认是访存密集、改按行遍历后复测——数字上两倍上下的差距会让"局部性"三个字从此长在你脑子里。
断点监控"执行到哪",观察点监控"谁改了它"——watch 命令是数据类疑难杂症的首选工具。某全局标志被神秘清零的悬案,三行命令破案:
(gdb) watch global_flag Hardware watchpoint 1: global_flag (gdb) c Hardware watchpoint 1: global_flag Old value = 1 New value = 0 0x0000000000400818 in worker_thread () at worker.c:42
注意输出里的 Hardware 字样——调试器把它交给处理器的硬件观察单元(比较地址的专用电路)执行,程序全速运行,只在目标写入瞬间停下;没有硬件资源的软件观察点则退化成单步轮询,慢得无法日用。ARM 的调试架构自带数个观察点槽位,被占满时 GDB 会报错,info watchpoints 随时查账。
把 7.2 正文的单步、断点、观察点拼成一次完整的远程会话,作为本节的操作总结:模拟器以调试服务模式启动、GDB 连上、b main 起跑、在汇编函数内 si 逐条走、info registers 对账参数、finish 验证返回值、对可疑变量下 watch、复现后 bt 定案——一套流程覆盖九成"看得见的 bug"。看不见的那一成(性能与竞态)交给计数器与采样,7.2 正文已经铺好。
问:为什么断点有时会"弹"到相邻行? 答:优化器把多条源语句合并进同一条指令,或把指令重排到源码顺序之外——断点落在的不是"行"而是"地址"。看到断点弹跳就说明调试信息与优化的对应已经模糊,此时信反汇编不信源码行号。
-g 给符号,低优化给结构,release 三种病各有对策。观察台配齐了。最后一节解决跨语言协作:C 与汇编怎么互调、接口报错怎么破。