在对嵌入式程序进行调试的时候,TRACE32中变量观察怎么添加,以及TRACE32中变量被优化后看不到该怎么办,这两个问题经常会被大家遇到。表面上看起来这只是变量窗口的使用问题,但是这其实和ELF符号、当前停机的位置、函数的栈帧以及编译优化的等级都有关系。也就是说,并不是只要把变量名输入到TRACE32里面就一定可以显示出数值,调试器还需要知道变量的具体位置,当前代码运行到了哪里,以及这个变量有没有被编译器给保留下来。
一、TRACE32变量观察怎么添加
在TRACE32里面要想添加变量观察,通常有三种常见的方法,分别是临时查看、加入到Watch窗口里面、以及对当前局部变量进行查看。TRACE32本身能够支持启动、停止、单步执行、对内存和寄存器进行读写,同时也支持对变量值进行跟踪等调试的操作,不过这些操作都需要依赖目标连接和调试信息的正常状态。
1、首先需要确认符号文件已经加载成功
要想进行变量观察,前提条件是TRACE32必须能够对变量名进行识别。操作人员一般需要把带有调试信息的ELF、AXF或者OUT文件加载进去,比如可以使用Data.LOAD.Elf app.elf这一条命令来把程序和符号加载好,或者在程序已经被烧录到板子里的情况下,使用Data.LOAD.Elf app.elf/NoCODE这一条命令,这样就只把符号加载进去。
2、使用Var.View命令行来临时查看变量
如果只是想临时查看某一个变量,那么可以在命令行里面输入Var.View变量名。比如想要看全局的计数变量,就可以输入Var.View g_counter;想要看结构体的话,就可以输入Var.View g_systemState;要是只想看结构体里面的成员,就可以输入Var.View g_systemState.mode。
3、使用Var.Watch命令行来长期观察变量
如果有一些变量需要经常被查看,那么就可以使用Var.Watch变量名这种方式,把它们加入到观察窗口里面。比如输入Var.Watch g_state g_errorCode,这样就可以把状态变量和错误码同时放在Watch窗口里面了。在后面断点被触发、或者进行单步执行的时候,大家就不需要每一次都去重新输入变量名了。
二、TRACE32变量优化后看不到怎么办
变量在被优化之后就看不到,这是TRACE32调试过程中很普遍的一个现象。这倒不一定是因为TRACE32没有读取到变量,而是由于编译器在进行优化之后,源码里面的变量可能就已经没有固定的地址了。因为有些变量会被放进寄存器里面,有些变量会被合并掉,还有些变量由于没有被实际用到就会被直接删掉,所以调试窗口里面自然就没办法显示出来了。
1、首先要判断是不是因为优化的原因导致变量消失
如果在变量窗口里面看到了???或者optimized out这样的提示,或者发现局部变量一会儿能够看到、一会儿又看不到,那么这时候就要优先去检查一下编译优化的等级。大家经常使用的O2、O3、或者是针对速度和体积的优化,这些都有可能导致变量的显示变得不稳定。
代码被优化之后,源码的行数、变量的生命周期和实际的汇编指令之间不一定能够完全对应得上。TRACE32里面有专门针对优化后的代码进行调试的讲解内容,这也证明了优化的代码确实会对变量以及源码级别的调试表现产生影响。
2、可以把关键的文件修改成低优化模式来进行调试
在排查问题的时候,并不建议大家为了查看某一个变量,就把整个工程的设置都修改掉。更加稳妥的做法是,只把相关的C文件、驱动模块或者发生问题函数所在的文件修改成低优化的模式,同时把调试信息保留下来。在GCC工程里面,常见的参数组合是-g-O0,如果项目里面希望保留一部分的优化,那么也可以尝试使用-g-Og。
3、不要随便使用volatile关键字来硬保变量
有一些人在看到变量被优化掉之后,就会直接给变量加上volatile关键字。这个办法有时候确实可以让变量的访问被保留下来,但是大家不能去乱用它。因为volatile更加适合用在硬件寄存器映射的变量、中断的标志、或者是多任务共享的标志这类确实会被外部环境所改变的数据上面。
更加合理的做法应该是:在调试的阶段先把关键文件的优化等级降低,只有针对那些真正具有硬件访问、中断共享或者任务共享含意的变量,才按照代码的意思去使用volatile。
三、TRACE32中变量显示异常该如何继续排查
有时候变量并不是完全看不到,而是显示出来的数值不对,或者是Watch窗口在刷新之后和预期的不一致。面对这一类问题,大家不能只盯着变量名去看,而是需要把当前的PC指针、调用栈、变量的地址、内存窗口以及目标的运行状态结合在一起来看。很多所谓的变量异常,其实是因为停机的位置不对,或者是因为当前的上下文已经不是该变量有效的上下文了。
1、需要检查当前的作用域和调用栈
局部变量对当前函数的栈帧是有很大依赖性的。
比较稳妥的做法是打开【List.auto】窗口来确认当前PC指针所在的源码行,然后再使用【Frame.view】来查看调用栈,并且用【Var.Local】来查看当前作用域里面的局部变量。如果变量的赋值语句都还没有被执行,大家就不要急着去判断是变量显示错了;而如果函数都已经返回了,再去查看这个函数里的局部变量,就很容器拿到无效的栈内容。
2、可以利用变量的地址和内存窗口进行对照
对于全局变量、静态变量、数组以及结构体,如果它们显示异常,大家可以用变量的地址来进行反查。比如可以先使用Var.View g_counter来查看变量,然后再使用Data.dump&g_counter来查看对应的内存位置。如果变量窗口和内存窗口里面的内容能够互相对应上,那就说明符号和地址基本上是没有问题的。
3、要区分开停机观察和运行观察这两种情况
对于变量的观察,最好是先在停机的状态下进行确认。大家可以执行Break命令让CPU停止运行,然后再去查看Watch窗口和变量的数值,接着单步执行一两行,去观察变量有没有按照源码的逻辑发生改变。采用这样的方式来判断是最稳妥的。
如果目标程序现在还在运行,变量一直被中断、DMA、另外一个任务或者另外一个核心所修改,那么Watch窗口显示得不稳定也就是很正常的事情了。虽然有一些芯片能够支持运行态的访问,但是在有些场景下还需要对运行时内存的访问方式进行配置。TRACE32的术语说明里面也说到了,像Var.View%E这一类的格式参数是和运行时内存访问有关系的,如果大家把场景用错了,就很容器对变量的刷新结果产生误判。
总结
对于TRACE32中变量观察怎么添加,核心的地方是要先加载好正确的符号文件,然后再使用Var.View进行临时查看,使用Var.Watch进行长期观察,并且利用Var.Local来查看当前作用域里的变量。而对于TRACE32中变量优化后看不到该怎么办,核心的办法并不是去反反复复地刷新窗口,而是应该去检查编译的优化、调试的信息、函数的内联、变量的作用域以及ELF的版本是不是一致的。