作为嵌入式软件工程师,大家应该都知道axf文件是 ARM 架构下特有的一种可执行文件格式,主要用于程序的调试和目标设备的执行。MDK默认就会在output目录中生成axf文件,当用户点击仿真或下载按钮后,MDK会烧写并加载axf文件。
今天的故事是有客户发现芯片有问题,能提供复现问题的板子,但是不方便提供代码,只能提供axf文件来验证问题。由于axf文件中不仅包含编译后的机器码(可执行指令)和数据,还嵌入了丰富的调试信息,如源代码行号、变量名、函数符号、地址映射关系等。这些信息对调试工具(如 ARM DS-5、Keil MDK 等)至关重要,能实现源码级调试(单步执行、断点、变量监视等)。
1.进入调试环境
拿到客户的板子,MCU是我们一款比较老的产品:LPC15xx,个人觉得这个产品最大好处是ROM支持USB升级固件,用户通过它来升级代码非常方便。
首先,我们随便找一个MDK的工程,我这里用的是AN11555里的periph_uart_rb,直接编译成功(不成功也没问题)
然后把客户给的axf文件改名为periph_uart_rb.axf,这个名字取决于下面这个配置:
接下来把periph_uart_rb.axf复制到keil_output\iflash_nxp_lpcxpresso_1549目录,只要有这个文件就可以了,所以前面说不需要成功编译哦:
然后我们就可以在MDK中点仿真了,下图左边是寄存器窗口,比较关键的是PC, LR, SP这几个寄存器,中间是汇编指令,其中包含一些函数调用信息,右侧是外设接口窗口,我们可以从中看到复位源,时钟配置等信息。
2. 开始调试
进入调试环境后,通常情况下,我们就可以点击单步跟过,单步跟入,全速运行等功能,同时也可以在汇编窗口的左侧打断点:
如果我们想跳过某些函数,比如上图的SystemCoreClockUpdate,我们可以直接修改PC指针,让它指向后面的地址,这样就可以跨过时钟初始化函数,直接去配置GPIO了:
对于有问题的情况,直接点全速运行,很可能就会进入Hardfault:
这个时候,通常情况下,我们可以从SP中看到一些函数地址,往往包含了代码飞走之前的信息,通过检查SP,在某些情况下,我们就可以找到关键的位置,比如0x11D4F,这些一看就是Flash地址,应该都是函数调用,通过它我们就能一步步找到哪里出现的错误:
我们可以在汇编窗口右键,选择Show Disassembly at Address, 然后在弹出窗口输入0x11D4F:
跳转到对应的代码后,向上看就可以看到是哪个函数调用的了:
有的时候,我们发现可能和某个变量有关,可以在Memory窗口设置内存断点:
甚至还可以加入一些条件判断,比如读/写该地址时触发中断,或者第几次触发读/写后再中断,Command里可以设置更复杂的条件,需要的小伙伴可以看MDK的手册
3.修改AXF文件
有些情况下,我们想修改程序的运行逻辑,如果是Hex文件,我们可以直接修改对应的机器码,但是AXF文件就没有那么容易了,下面请出今天的主角:IDA pro,这个工具是反编译大神,支持各种不同的平台,包括X86, ARM等等,都可以完美支持。用IDA pro打开axf文件后,会提示用户选择该文件对应的汇编指令集,它已经猜到是ARM了,这里我就点OK了:
导入文件后,我们看到它已经找到main的入口了:
现在我想要函数跳过时钟初始化函数,每次从GPIOInit开始运行。我们可以先问问豆包,都有哪些NOP指令可以替换:
看来0000就可以了,在IDA pro中,将光标停在需要修改的指令上,然后选择Edit->Patch program->Change byte:
在弹出菜单输入00 00 00 00替换原先的指令:
可以看到指令已经被替换了:
这个时候其实没有真正写入到axf文件,我们需要把补丁打上,弹出对话框直接点OK即可:
补丁打好后,我们就可以在MDK中验证修改是否生效了:
如果我想让代码停在某一行,相当于执行while(1),有办法实现么?看看豆包的回答:
重开IDA pro,把刚才的0x0000替换为0xE7FE试试,注意是小端模式:
在板子上实际跑一下看看,代码果然跑不下去了,通过这个方式我们可以替换一些代码,来控制程序停留在某些位置。
只要Flash的空间足够大,我们可以在空闲的Flash上写一些函数来给有bug的程序打补丁。
更复杂的功能留给大家去研究吧。
------------END------------
嵌入式软件编程——事件标志组
假如回到没有互联网和AI的年代,你还会编程吗?
基于RA单片机移植CoreMark跑分源码