在线时间4067 小时
UID3441752
注册时间2017-11-21
NXP金币759430
TA的每日心情 | 开心 2024-3-26 15:16 |
---|
签到天数: 266 天 [LV.8]以坛为家I
管理员
- 积分
- 32003
- 最后登录
- 2024-4-9
|
那个讨厌的HardFault,看你往哪里跑! 说到debug,相信大家伙最不想看到的就是程序偷偷的停掉,然后跳进一个叫做HardFault_Handler的函数中。虽说我们意志坚强、不惧难题,无论程序虐我千百遍,我待程序如爱恋;但是还是需要有效的方法来找到问题,解决问题。
为了顺利定位问题的所在,常见的方法是要向工程中插入额外的代码和变量,这样一来,不仅可能增加代码尺寸,还可能会破坏程序的执行逻辑。
而且,每次对于代码的修改都需要对工程进行重新编译,效率较低。
如果说,遇到上述情况的时候,我们能够找来合适的帮手,一款强大的IDE来辅助调试,这缕东风还能送我们一程。当然,倘若我们手里只有一些命令行工具,比如GDB呢,,,,可能只能一声长叹了。
不过,好在小编最近发现了一个叫做GNU gdb脚本的好东西(.gdbinit),迫不及待要来和大家分享。本篇小编将以调试HardFault为例进行说明,话不多说,开整。
什么是.gdbinit?
当GDB启动时,他会查找一个名为.gdbinit的文件;如果该文件存在,则GDB会执行该文件中的所有命令。
通常,该文件用于简单的配置命令,例如读取/设置寄存器的值,控制GDB行为(设置/查看断点等)。其编写语法遵循如下格式:
define<command> // 定义的命令名
<code> // 对应的代码
end
document<command>// 命令帮助文档
<helptext> // 帮助文档内容
end
此定义的上半部分(在 define ... end 动词所界定的范围内)构成了在调用该命令时所执行的代码。
此定义的下半部分(在 document ... end 所界定的范围内)由 GDB 命令解释器使用,用于在键入help <command> 时显示与<command> 命令关联的文本。
举个简单的例子:
define cls
shell clear
end
document cls
Clears the screen with a simple command.
end
这里我们定义了一个叫做cls的指令,document是对cls指令的解释,执行help cls后将显示我们所添加的说明信息:
此时,如果我们直接键入cls,当前控制台会被清空。
征战HardFault 下面,让我们开始HardFault之旅,搞点复杂的。
先上一段.gdbinit里的脚本代码:
- define armex
- printf "EXEC_RETURN (LR):\n",
- info registers $lr
- if (($lr & 0x4) == 0x4)
- printf "Uses PSP 0x%x return.\n", $psp
- set $armex_base = $psp
- else
- printf "Uses MSP 0x%x return.\n", $msp
- set $armex_base = $msp
- end
-
- printf "xPSR 0x%x\n", *($armex_base+28)
- printf "ReturnAddress 0x%x\n", *($armex_base+24)
- printf "LR (R14) 0x%x\n", *($armex_base+20)
- printf "R12 0x%x\n", *($armex_base+16)
- printf "R3 0x%x\n", *($armex_base+12)
- printf "R2 0x%x\n", *($armex_base+8)
- printf "R1 0x%x\n", *($armex_base+4)
- printf "R0 0x%x\n", *($armex_base)
- printf "Return instruction:\n"
- x/i *($armex_base+24)
- printf "LR instruction:\n"
- x/i *($armex_base+20)
- end
-
- document armex
- ARMv7 Exception entry behavior.
- xPSR, ReturnAddress, LR (R14), R12, R3, R2, R1, and R0
- end
复制代码 在这里,我们定义了一个叫做armex的指令,正如document中所述,这条指令会在进入arm exception时候被调用,并且打印出r0-r3, r12, r14, pc以及xPSR,而这也正是我们所需要参考的信息,比较重要的是r14与PC,他们能够告诉我们,是哪条指令触发的异常。这样,根据这个地址值,我们就可以找到对应的那条指令。
那么,同样的功能,如果要在我们的工程中添加C代码,需要以下这些语句:
- void HardFault_HandlerC(unsigned long *stack_top){
- volatile unsigned long stacked_r0 ;
- volatile unsigned long stacked_r1 ;
- volatile unsigned long stacked_r2 ;
- volatile unsigned long stacked_r3 ;
- volatile unsigned long stacked_r12 ;
- volatile unsigned long stacked_lr ;
- volatile unsigned long stacked_pc ;
- volatile unsigned long stacked_psr ;
- stacked_r0 = ((unsigned long)hardfault_args[0]) ;
- stacked_r1 = ((unsigned long)hardfault_args[1]) ;
- stacked_r2 = ((unsigned long)hardfault_args[2]) ;
- stacked_r3 = ((unsigned long)hardfault_args[3]) ;
- stacked_r12 = ((unsigned long)hardfault_args[4]) ;
- stacked_lr = ((unsigned long)hardfault_args[5]) ;
- stacked_pc = ((unsigned long)hardfault_args[6]) ;
- stacked_psr = ((unsigned long)hardfault_args[7]) ;
-
- printf(“0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x,”,
- stacked_r0, stacked_r1, stacked_r2, stacked_r3,
- stacked_r12, stacked_lr, stacked_pc, stacked_psr);
- }
复制代码 而这,仅仅是最精简的代码实现,多余的代码和变量的引入,导致代码尺寸以及系统栈空间使用量的增加,同时,破坏了原有代码的逻辑。
相较于利用.gdbinit的方式,不够清爽。还有一个问题,相关寄存器的打印需要借助于printf函数,并且需要初始化并连接板子上的串口到PC上,无疑又多了一些操作。
而利用.gdbinit中调用命令的方式,直接会打印在gdb的调试窗口,无需任何硬件资源初始化以及硬件连接。
实际上,这是GDB通过芯片的调试接口,直接从芯片中读取数据,并由GDB直接输出结果的。
实战演示
为了这次测试,小编很不情愿的造了一个有BUG的工程,在main函数中定义一个对于空指针的赋值,并通过配置MPU对0x0开始的32B地址进行写保护,这样,对于0x0处地址的访问,会触发一个MemManager Fault,从而体现到HardFault:
- MPU->RBAR = ARM_MPU_RBAR(5, 0x00000000U);
- MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_RO, 0, 0, 1, 1, 0, ARM_MPU_REGION_SIZE_32B);
- int *p = NULL;
- *p = 0x1000;
复制代码 接下来,就是编译下载,在HardFault_Handler处设置断点,运行,此处略过1万+的操作流程,直接出结果,不出意料,程序停在了我们设置的断点处,尝试键入armex,令人兴奋的事情出现了,我们想要看到的东西被显示出来了:
根据ReturnAddress,0x788,我们就可以直接定位到那行捣乱的代码处:
这里所指向的是一条str指令,正是要将r2寄存器的值,即0x1000存储到指针p所指向的位置,而目标位置由于已经被MPU保护作为一块只读的区域,导致触发了一个MemManager Fault,进而触发为HardFault。
至此,危害代码的小虫子就这样找到了,调试工作取得了又一重大进展,开启了一天的好心情了。
总结
小编今天为大家介绍了一种全新的调试思路,利用自定义GDB所提供的脚本文件来实现一些特定操作,自动触发。
这样一来,我们可以做到一种非侵入式调试,所谓非侵入调试,我们无需添加任何的代码、变量到工程中,缩减镜像尺寸。
同时,能够让我们的调试显得更加方便、灵活、可控,而且不必修改每个工程。最重要的是,我们可以更大限度发挥GDB的潜力,让他为我们所用。
小编在这里仅仅是抛砖引玉,更多的功能要靠大家集思广益了。
|
|