Trace32嵌入式调试入门:从连接目标板到自动化脚本实战
1. 从一次连不上目标板说起trace32到底是个什么东西第一次接触trace32的人十有八九会卡在同一个地方——软件装好了界面也打开了但就是连不上板子。我当年也是这么过来的对着一个黑乎乎的界面折腾了一下午最后发现是配置文件里的一行地址写错了。所以这篇内容我不打算从什么是trace32这种教科书式的定义讲起而是从一个实际使用者的角度把入门阶段真正会遇到的问题一个个拆开说。trace32是德国劳特巴赫Lauterbach公司出品的嵌入式调试工具全称是TRACE32 Debugger。它在嵌入式开发圈子里算是重型武器级别的存在——功能极其强大支持几乎所有主流CPU架构从ARM、MIPS、RISC-V到PowerPC、TriCore基本上你能叫得出名字的嵌入式处理器它都能调。但与此同时它的学习曲线也确实陡峭很多人第一次打开界面的时候是完全懵的没有菜单栏没有工具栏只有一堆命令行窗口。这就引出了trace32最核心的一个设计理念一切操作皆命令。你在图形界面上点的每一个按钮背后都对应着一条命令。理解了这一点后面的学习就会顺畅很多。比如你想设置一个断点图形界面里是点一下断点图标但实际执行的命令是Break.Set。你想查看内存对应的命令是Data.Dump。这种设计的好处是一旦你熟悉了命令就可以写脚本自动化效率会成倍提升。那trace32到底解决什么问题简单说它解决的是**程序在目标板上跑飞了你怎么知道它飞到哪里去了**这个问题。普通的printf调试在嵌入式场景下有很多局限——串口资源可能不够用打印本身会影响时序有些bug只在特定时序下出现一打印就消失了。trace32通过硬件调试接口比如JTAG、SWD、cJTAG等直接访问CPU的调试单元可以在不修改程序的前提下实时查看寄存器、内存、变量设置硬件断点甚至做指令级和数据级的trace。这是软件调试器做不到的事情。适合谁来学如果你在做嵌入式底层开发——驱动、BSP、RTOS移植、裸机程序——那trace32基本是绕不开的工具。如果你只是做上层应用开发那可能用不上这么重型的工具。另外做芯片验证、FPGA原型验证的工程师也会用到trace32因为很多芯片在流片之前就是用trace32在FPGA上做调试的。2. 装好之后第一件事搞懂trace32的目录结构和配置文件很多人装完trace32之后看到安装目录里一大堆文件夹就晕了。我刚开始也是这样完全不知道哪个文件夹是干什么的。后来用久了才发现其实核心的就那么几个搞清楚了之后后面配置起来会快很多。2.1 安装目录里哪些文件夹真正需要关注trace32的安装目录通常长这样以Windows版本为例C:\T32\ ├── bin\windows64\ # 主程序在这里 ├── demo\ # 各种Demo脚本和示例 ├── pdf\ # 文档非常重要 ├── config\ # 配置文件模板 └── ...bin\windows64\下面就是主程序t32marm64.exeARM架构64位版本或者t32mriscv.exeRISC-V版本之类的可执行文件。注意trace32针对不同的CPU架构有不同的可执行文件你用什么架构的芯片就启动对应的程序。这一点和很多通用调试器不一样不是启动一个程序然后选架构而是直接启动对应架构的版本。demo\文件夹我强烈建议新手花时间翻一翻。里面有针对各种CPU架构的示例脚本包括怎么初始化、怎么加载程序、怎么设置断点。很多时候你遇到问题去demo里找一个类似的例子改一改就能用。pdf\文件夹里是完整的用户手册和命令参考。trace32的命令有上千条没有人能全部记住所以查手册是常态。我建议把pdf\里的debugger_user.pdf和debugger_reference.pdf这两个文件放在手边遇到不认识的命令就查。2.2 配置文件trace32连接目标板的钥匙trace32连接目标板靠的是配置文件通常是一个.cmm脚本文件。这个文件里包含了所有必要的初始化命令选择调试接口、设置时钟频率、配置内存映射、初始化CPU核心等等。一个最简化的ARM配置文件大概长这样; 这是注释分号开头 SYStem.CPU CortexA53 ; 指定CPU类型 SYStem.JtagClock 10MHz ; 设置JTAG时钟频率 SYStem.Up ; 连接目标板 Data.LOAD.Elf program.elf ; 加载程序看起来很简单对吧但实际使用中最容易出问题的就是这几行。SYStem.CPU指定的型号必须和你的芯片完全匹配差一个字都不行。SYStem.JtagClock设置的频率如果太高线缆质量又不好就会连接不稳定如果太低加载大程序的时候会慢得让你怀疑人生。提示JTAG时钟频率的设置有一个经验法则——先从低频开始比如1MHz确认能稳定连接之后再逐步提高。如果提高到某个频率后连接变得不稳定就退回上一档。不要一上来就设很高那样只会浪费更多时间在排查连接问题上。SYStem.Up这条命令执行的时候trace32会通过调试接口向CPU发送一系列初始化序列把CPU的调试单元激活。如果这一步失败了后面的一切都无从谈起。失败的原因通常有几个接线不对、目标板没供电、CPU型号选错了、调试接口被禁用了有些芯片出厂默认关闭JTAG需要通过OTP或者efuse来使能。2.3 为什么你的trace32连不上常见连接问题排查连接失败是新手遇到最多的问题没有之一。我把常见的失败原因和排查方法整理成了一个表你可以对照着检查现象可能原因排查方法提示SYStem.Up failed接线错误检查TCK、TMS、TDI、TDO、GND是否接对提示Debug port not available调试接口被禁用检查芯片的efuse/OTP设置连接时断时续JTAG时钟太快降低JtagClock频率能连接但读不到内存内存映射配置错误检查MMU/Cache配置提示CPU not responding目标板未供电测量目标板供电电压这个表里的每一行我都实际遇到过。特别是调试接口被禁用这一条有些芯片为了安全考虑出厂时JTAG是关闭的需要先通过其他方式比如串口发送解锁命令才能打开。这种情况在量产芯片上很常见但在开发板上一般不会遇到。还有一个容易被忽略的点GND必须接。很多人觉得JTAG只需要接TCK、TMS、TDI、TDO四根线就够了GND不接或者只接一根。实际上如果GND接触不良信号回流路径不完整连接就会非常不稳定。我建议至少接两根GND线而且线越短越好。3. 界面看着懵先把这几个核心窗口用起来trace32的界面和常见的IDE完全不同没有菜单栏没有工具栏打开之后就是几个空白窗口。很多人第一次看到这个界面的时候第一反应是我是不是装错了。其实这就是trace32的设计风格——极简一切靠命令。3.1 命令窗口你和trace32对话的地方命令窗口是trace32最核心的窗口你所有的操作都可以通过在这里输入命令来完成。比如SYStem.Up— 连接目标板Data.LOAD.Elf program.elf— 加载ELF文件Break.Set main— 在main函数处设置断点Go— 全速运行Step— 单步执行这些命令看起来简单但组合起来就能完成非常复杂的调试任务。而且trace32支持命令历史按上下箭头翻、Tab补全、命令缩写比如SYStem.Up可以缩写成SYSU用熟了之后效率很高。我个人的习惯是把常用的命令写成脚本文件然后在命令窗口里用DO命令执行。比如我写了一个load.cmm里面包含了连接、加载、设置断点的所有命令每次调试的时候只需要输入DO load.cmm就行了省去了重复输入的时间。3.2 寄存器窗口和内存窗口查看CPU内部状态寄存器窗口显示当前CPU所有寄存器的值包括通用寄存器、状态寄存器、控制寄存器等等。这个窗口在调试底层问题的时候特别有用——比如程序跑飞了你可以看看PC指针指向哪里SP指针是否合法状态寄存器里的标志位是什么。内存窗口可以查看任意地址的内存内容支持多种显示格式十六进制、十进制、ASCII、反汇编等。我经常用内存窗口来检查DMA缓冲区的数据是否正确或者查看栈的使用情况。这两个窗口都可以通过命令打开Register.view /All ; 打开所有寄存器窗口 Data.Dump 0x80000000 ; 查看0x80000000地址的内存3.3 断点窗口管理你的断点断点窗口列出了当前设置的所有断点包括断点类型硬件断点/软件断点、地址、命中次数等信息。trace32支持多种断点类型软件断点通过替换指令为断点指令实现数量不限但只能设置在RAM中硬件断点通过CPU的调试单元实现数量有限通常4-8个但可以设置在Flash中数据断点当某个地址被读写时触发用于排查内存被意外修改的问题数据断点在排查某个变量莫名其妙被改了这类问题时特别有用。你只需要在变量地址上设置一个写断点程序一写这个地址就会停下来然后看调用栈就知道是谁改的。注意硬件断点的数量是CPU决定的不是trace32决定的。ARM Cortex-A系列通常有6个硬件断点和4个观察点Cortex-M系列通常有4个硬件断点和2个观察点。如果你设置的硬件断点超过了这个数量trace32会提示你无法设置。4. 从加载程序到跑起来一次完整的调试流程前面说了那么多准备工作现在终于到了实际调试的环节。我以一个典型的ARM Linux驱动调试为例把整个流程串一遍。4.1 加载程序ELF文件里有什么trace32加载程序通常用Data.LOAD.Elf命令后面跟ELF文件路径。ELF文件里包含了代码段、数据段、符号表、调试信息等内容。trace32加载的时候会把这些信息都读进来这样你才能在源码级别调试。Data.LOAD.Elf vmlinux /NoClear ; 加载内核镜像不清除已有断点/NoClear这个选项的意思是加载新程序的时候不清除已设置的断点。如果你在调试过程中需要重新加载程序比如修改了代码重新编译这个选项可以省去重新设置断点的麻烦。加载完成之后你可以用List命令查看源码用Break.List查看断点列表确认一切就绪。4.2 设置断点的几种姿势设置断点最常用的命令是Break.SetBreak.Set main ; 在main函数入口设断点 Break.Set 0x80001234 ; 在指定地址设断点 Break.Set main /Hard ; 设置硬件断点 Break.Set var_name /Write ; 在变量写入时触发 Break.Set 0x80002000 /ReadWrite ; 在地址读写时触发这里有一个新手经常踩的坑在Flash中设置软件断点。软件断点的原理是把目标地址的指令替换成断点指令但Flash通常是不能直接写的所以软件断点会失败。这时候必须用硬件断点。trace32在设置软件断点失败的时候会给出提示但如果你没注意看提示就会以为断点设上了结果程序跑过去根本没停。另一个坑是断点设置在优化过的代码上。编译器优化之后有些代码行可能被合并或者删除了你在源码上设的断点可能对应不到任何指令。这时候trace32会提示breakpoint not set你需要换一个位置设断点或者关闭编译器优化重新编译。4.3 单步执行和查看变量程序停在断点之后你可以用以下命令控制执行Step— 单步执行进入函数Step.Over— 单步执行跳过函数Go— 全速运行Go.Up— 运行到当前函数返回查看变量用Var.View命令Var.View %Hex var_name ; 以十六进制显示变量 Var.View %String str_var ; 以字符串显示变量 Var.View *ptr ; 查看指针指向的内容trace32的变量查看功能非常强大支持结构体展开、数组查看、类型转换等。你甚至可以在变量窗口里直接修改变量的值这在调试的时候非常方便——比如你想测试某个条件分支可以直接把条件变量改成想要的值不用重新编译。4.4 一个实际案例排查空指针访问我遇到过一个问题驱动在加载的时候偶尔会崩溃但崩溃的位置每次都不一样。这种问题用printf很难查因为崩溃是随机的而且崩溃之后系统已经挂了打印信息可能还没出来。用trace32的排查思路是这样的首先在可能出问题的代码区域设置数据断点监控关键指针变量的读写。然后全速运行等程序崩溃的时候trace32会自动停下来因为CPU进入了异常状态。这时候查看调用栈Frame.View命令就能看到崩溃时的函数调用链。如果调用栈也被破坏了比如栈溢出那就需要查看更底层的信息PC指针、LR寄存器、SP寄存器。通过这些信息结合反汇编List命令可以大致定位到崩溃的位置。最后发现是一个结构体指针在某个错误分支下没有被初始化导致后续访问时踩到了非法地址。这种问题如果没有trace32光靠printf可能要花好几天才能定位。5. 那些没人告诉你但迟早会踩的坑trace32的文档很全但有些经验是文档里不会写的只有实际用过的人才知道。我把这些年踩过的坑整理一下希望能帮你少走弯路。5.1 脚本里的路径问题trace32的脚本里写文件路径的时候必须用正斜杠/或者双反斜杠\\不能用单反斜杠\。因为反斜杠在trace32的命令语法里是转义字符。比如; 正确 Data.LOAD.Elf C:/project/build/program.elf ; 错误反斜杠会被当成转义字符 Data.LOAD.Elf C:\project\build\program.elf这个坑我踩过不止一次每次都是加载文件失败查半天才发现是路径写法的问题。5.2 多核调试的注意事项现在的SoC大多是多核的trace32支持多核调试但配置起来比单核复杂。每个核心需要单独配置而且核心之间的启动顺序有讲究。通常的做法是先连接主核通过主核来释放从核的复位然后再连接从核。多核调试的时候断点的行为也需要特别注意。默认情况下一个核心停下来的时候其他核心可能还在跑。如果你需要所有核心同时停下来比如调试核间通信的问题需要设置SYStem.Mode为SMP模式。5.3 实时跟踪Trace功能的使用条件trace32的Trace功能可以记录CPU执行的每一条指令这对于排查偶发性bug非常有用。但Trace功能需要CPU支持而且通常需要额外的Trace引脚比如ETM、ETB、ETM等。如果你的硬件没有引出这些引脚那就用不了Trace功能。另外Trace功能会产生大量数据需要足够的存储空间。有些芯片内置了Trace Buffer比如ETB容量有限只能记录最近几千条指令。如果要记录更长时间的Trace就需要外接Trace Port Analyzer比如劳特巴赫自家的PowerTrace模块。5.4 断点数量超限的处理前面提到过硬件断点的数量是CPU决定的。当你设置的硬件断点超过CPU支持的数量时trace32会报错。这时候你有几个选择删除一些不常用的断点把能在RAM中设置的断点改成软件断点使用Break.Set的/Onchip选项把断点设置在CPU的片上调试单元里如果支持的话我个人的习惯是调试的时候只保留最关键的几个硬件断点其他的都用软件断点代替。软件断点虽然只能设在RAM里但数量不限而且对于大多数调试场景来说够用了。6. 把重复劳动交给脚本trace32的自动化调试trace32最强大的功能之一就是脚本自动化。你可以把一系列调试操作写成脚本一键执行。这在做回归测试或者批量调试的时候特别有用。6.1 PRACTICE脚本基础trace32的脚本语言叫PRACTICE语法类似C和Shell的混合体。一个简单的脚本例子; 连接目标板并加载程序 SYStem.CPU CortexA53 SYStem.JtagClock 10MHz SYStem.Up Data.LOAD.Elf program.elf /NoClear ; 设置断点 Break.Set main Break.Set func_a Break.Set func_b ; 运行 Go WAIT 5s ; 等待5秒 Break ; 暂停这个脚本可以保存为.cmm文件然后用DO命令执行。PRACTICE支持变量、条件判断、循环、函数等高级特性你可以写出非常复杂的自动化测试脚本。6.2 用脚本做自动化回归测试假设你有一个测试用例列表每个用例需要加载不同的程序、设置不同的断点、检查不同的变量值。你可以写一个脚本来自动完成这些操作; 定义测试用例 LOCAL test_num test_num1 WHILE test_num10 ( PRINT Running test case test_num Data.LOAD.Elf test_test_num.elf /NoClear Break.Set test_func Go WAIT 10s IF (Register(PC)0x80001000) PRINT Test case test_num PASSED ELSE PRINT Test case test_num FAILED ) test_numtest_num1 )这个脚本会自动加载10个测试程序运行然后根据PC指针的值判断测试是否通过。这种自动化测试在驱动开发中非常实用可以大大节省回归测试的时间。6.3 脚本调试的技巧写脚本的时候难免会有bugtrace32提供了一些调试脚本的手段PRINT命令可以输出调试信息开头的变量可以在命令窗口里查看和修改STEP命令可以单步执行脚本ERROR命令可以主动抛出错误我建议写脚本的时候多写PRINT语句把关键变量的值打印出来。这样脚本出问题的时候一眼就能看出是哪一步不对。7. 遇到问题怎么查trace32的文档和社区资源trace32的文档非常全但问题是太多了新手往往不知道从哪查起。我分享一下我的查文档经验。7.1 命令参考手册的正确打开方式debugger_reference.pdf是命令参考手册里面列出了所有命令的语法和参数说明。查这个手册的时候我建议用PDF的搜索功能直接搜命令名而不是从头翻。比如你想查Break.Set的用法直接搜Break.Set就能跳到对应的页面。手册里每个命令都有详细的参数说明和示例但示例通常比较简短。如果你想要更完整的例子可以去demo\文件夹里找。7.2 常见错误信息的含义trace32的错误信息有时候比较晦涩我整理了一些常见的错误信息和对应的解决方法错误信息含义解决方法SYStem.Up failed连接目标板失败检查接线、供电、CPU型号Breakpoint not set断点设置失败检查地址是否有效、是否在Flash中No symbol found找不到符号检查ELF文件是否包含调试信息Access timeout访问超时检查内存映射配置、降低JTAG频率Core is running核心正在运行先暂停核心再执行该命令这些错误信息我基本都遇到过每次都是查手册加搜索才解决的。现在有了这些经验排查起来就快多了。7.3 什么时候该放弃自己排查有些问题自己排查可能花几个小时都搞不定这时候该找人帮忙就找人帮忙。trace32的代理商通常有技术支持遇到连接问题、配置问题可以找他们。另外劳特巴赫的官方论坛也有不少有价值的讨论虽然主要是英文的但搜索一下往往能找到类似问题的解决方案。我个人的经验是如果一个问题排查超过两个小时还没有头绪就应该考虑换一种思路或者找人帮忙了。继续死磕只会浪费时间而且容易钻牛角尖。8. 从入门到不放弃一些个人体会trace32这个工具说实话入门阶段确实不太友好。界面不直观命令多配置复杂随便一个环节出问题都会卡住。但我用了这么多年下来觉得它最大的价值在于确定性——当你的程序跑飞了当你的系统莫名其妙挂了trace32能给你一个确定的答案PC在哪里栈是什么状态内存里有什么。这种确定性是printf给不了的。我见过很多人装了trace32之后因为连不上板子就放弃了继续用printf调试。这其实挺可惜的。连接问题虽然烦人但一旦解决了后面就是一马平川。而且连接问题通常就那么几种原因排查过一次之后下次再遇到就能很快解决。另外trace32的命令虽然多但常用的就那么几十条。你不需要把所有命令都记住只需要把最常用的那些用熟遇到问题再查手册。我到现在也记不住所有命令但这并不影响我使用trace32。最后说一个我自己的习惯每次调试一个新的芯片或者新的板子我都会把配置过程记录下来包括CPU型号、JTAG频率、内存映射、特殊配置等等。这样下次再调同样的板子直接翻记录就行了不用重新摸索。这个习惯帮我省了很多时间也推荐给你。