我真的搞明白调试了吗

性能分析与调试杂谈

Posted by moment on August 18, 2026

性能分析

采样分析

采样式性能分析”(sampling profiler),俗称 poor man’s profiler——不修改被测程序,靠反复“暂停看一眼调用栈”来统计热点。它的原理和工作流程如下:

核心原理

程序在某一时刻“卡在哪”是随机的。如果对运行中的程序随机采样几百上千次调用栈,那么某个函数出现在栈里的频率,就近似等于它的执行时间占比。比如函数 A 出现在 30% 的采样里,就说明它大约消耗 30% 的 CPU 时间。这就是统计采样的思想,不需要在代码里插桩计时。

具体做法

用 shell 循环反复执行那条 GDB 批处理命令,每次执行完成一轮“采样”:

target remote :3333 连接 OpenOCD——连接的瞬间 MCU 被 halt(暂停); thread apply all bt 打印出此刻所有线程的调用栈快照; -batch 模式下命令跑完自动退出,断开连接,MCU 恢复运行; shell 脚本循环几百上千次,把每次的回溯输出追加到日志里。 采完后再离线解析日志:统计每个函数名出现的次数、看它出现在栈的哪一层,就能得到热点排行,甚至用 addr2line 解地址后合成火焰图。

其优缺点很明显,MemFault如是说道:

不幸的是,使用这种方法无法获得更精确的结果。使用 GDB 和 OpenOCD 会带来巨大的开销,这意味着我们只能每 50 毫秒获取一个数据点。鉴于我们的 MPU 每秒能执行约 2 亿次时钟周期,这意味着我们实际上只能每 1000 万次时钟周期获取一个样本。这样的采样频率显然远远不够,无法进行有效的分析。因此,我们必须寻找更好的方法。

它确实不影响性能,但是也不够精确。因此很容易想到其他办法:

ITM采样

Instrumented Trace Macrocell (ITM)是cortex-M内核的一种可选功能,它能够将固件或硬件直接产生的跟踪信息进行格式化处理,然后通过专用总线将这些信息输出出来。你也可以通过它来实现日志功能,例如https://blog.japaric.io/itm/ 中描述的那样。它的实现非常简单:

void putc(char c) {
  while (ITM_STIM32(0) == 0);
  ITM_STIM8(0) = c;
}

仅仅是这样就可以做到打印调试信息了,而且能够通过Trace或者SWO口输出到调试器。 关于这个的详细信息,https://interrupt.memfault.com/blog/profiling-firmware-on-cortex-m 里面讲的非常好了,我不想再做多余的搬运。所以不如来看看他其中涉及的一些调试相关寄存器:

DWT Control Register

CYCCNT register

现在一想,调试用的相关寄存器和逻辑真是繁多,为什么没有一个割舍这些东西,单独出的精简版MCU呢?算了,先不碎碎念,我们看看更现代的一些调试工具:

SystemView

perf

OpenOcd

GDB

参考链接(如果正文中没提到的话)

https://openocd.org/doc/html/Architecture-and-Core-Commands.html#index-tpiu-config