S32K3 工具篇9:如何在无源码情况下灵活调试elf文件
- 一,文档简介
- 二, 功能实现
- 2.1 代码工具准备
- 2.2 elf修改功能实现:Fun2功能跳过
- 2.2.1 PC越过Fun2
- 2.2.2 Fun2替换为nop
- 2.3 elf修改功能实现:Fun4替换Fun2入口
- 2.3.1 linkfile修改
- 2.3.2 Fun4函数体
- 2.3.3分离Fun4函数体到独立文件
- 2.3.4合并Fun4函数体到原始elf对应的srec
- 2.3.5修改main中原始Fun2调用为Fun4调用
- 2.3 劳德巴赫同步加载原始elf符号表
- 三,知识点
一,文档简介
平时在支持客户的时候,常会遇到客户因为公司政策的原因,无法提供问题工程源码,最多提供问题工程的elf文件,这里就涉及到,如何利用好elf来达到灵活的调试目的。Elf文件是一种二进制文件格式,包含程序的代码、数据、符号表,段表等信息。
调试elf的时候,无法看到源码,但是能看到函数的名称对应汇编地址的情况,这个时候调试还是相对能知道位置。但是,问题是无法像修改源码一样随意修改生成新的文件。那么对于elf文件,如果想特定做一些修改,来达到具体功能测试的目的是否可行?
本文将会在一个现有elf上,对特定位置予以跳过,抹掉功能,替换代码,拼接函数等给出测试方法,实现elf某些位置代码的跳过,或者任何时候都不执行,另起一段区域拼接其他功能性函数,再修改原有调用位置强行插入测试函数代码。
本文测试平台为S32K344, RTD500。
二, 功能实现
图1是一个原本功能的elf文件,功能是启动之后,调用三个函数:Fun1, Fun2, Fun3.
- Fun1 : 闪烁红灯
- Fun2 : 闪烁绿灯
- Fun3 : 闪烁蓝灯
本文需要实现的功能主要分为如下几点:
(1)Fun2功能跳过
这里功能跳过分为两种情况:
• debug的时候从Fun2开始的地方,修改PC跳到Fun3
• 上电都不运行Fun2,直接抹掉main里面Fun2值为nop
在这里插入图片描述
(2)添加Fun4,修改Main中Fun2调用为跳转Fun4
这里涉及到插入Fun4代码到空flash地址,修改Fun2的跳转代码数据位跳转Fun4.
2.1 代码工具准备
硬件平台:S32K344-EVB
软件:RTD500, S32DS3.5, JFLASH, PE Multilink(EVB自带),lauterbach
JFLASH下载连接:https://www.segger.com/downloads/jlink/
新建一个简单的点灯工程,可以基于RTD原有的Siul2_Dio_Ip_Example_S32K344工程,添加三个led灯引脚,并且构造3个分别红灯闪烁,绿灯闪烁,蓝灯闪烁的函数,main中顺序调用3个函数。测试能够工作的情况下,生成elf备用。
2.2 elf修改功能实现:Fun2功能跳过
这里给出具体实现图2具体方法:PC越过Fun2以及Fun2替换为nop
2.2.1 PC越过Fun2
在Main函数里面,调用Fun2的汇编位置打断点,直接修改PC为Fun3+1的值,然后运行,即可跳过Fun2. 图4中可以看到已经运行到Fun2,但是还没有进入到Fun2的函数体,直接将原本要调用Func2函数体的指针入口改成0X4027B4+1,也就是Fun3的函数入口。可以看到,修改PC之后回车,单步,即可进入到Fun3的函数体。如果在Fun2的函数体里面开始地方修改PC跳转到Fun3,会从main功能整体上运行两边Fun3.
2.2.2 Fun2替换为nop
上面直接使用debug跳转PC的情况越过Fun2,虽然测试上是可以跳过Fun2,但是要注意在下载代码的时候,实际上是会运行一下代码再进入到debug。如果有些测试就希望从POR开始,就不曾运行Fun2,那么就需要把原始的elf的Fun2调用的位置代码直接抹去,常用的方法可以替换为人畜无害的nop指令。nop指令的汇编十六制值为:00 BF,如下图:
有了目标修改值,下面就是找到elf main中Fun2的调用地址,将对应的4个字节替换为00BF00BF。
从原始的elf文件可以看到,调用Fun2的数据位置为绝对地址0X0040280E开始的4个字节,使用Segger JLINK驱动中的JFLASH工具打开原始elf文件,修改0X0040280E开始的4个字节数据为00BF00BF,修改后另存为srec文件,然后在临时工程中调用srec文件去运行修改后的代码。
下图是修改过程
修改后debug的结果如下:
可以看到,原本0X40280e区域的跳转到Fun2的汇编已经变成了nop指令。
这个时候,全速运行将会直接忽略fun2,顺序运行下去。不论debug,还是上电之后,从整体运行时序上彻底抹去了Fun2的调用。
当然,由于手动修改后的elf通过JFLASH另存的时候,不能直接存为elf文件,所以选择存为srec文件,再次debug的时候,就会丢掉了符号表了,需要前期elf的时候,大概记住几个需要用的函数的绝对地址。
2.3 elf修改功能实现:Fun4替换Fun2入口
上面是在Fun2的位置直接跳过或者插入nop,那么是否可以在原始的Fun2插入调用另外的函数体用于测试,达到移花接木的效果?是可以的。这里也分为两种:一种是破坏原始Fun2函数体位置,直接替换函数体代码内容,当然这点受到原始Fun2函数体大小的限制。另一种,保留原始Fun2以备后用,可以在flash其他空白的地址,另起一个函数Fun4,再将main中调用Fun2的代码改为调用Fun4即可实现Fun1->Fun4->Fun3运行的无缝对接。
本文主要使用在空白的特定绝对地址新建一个函数,当然要注意原始的elf map情况,保证空白的区域足够使用,这种新建函数最好是自成一体,不依赖于其他的函数的独立体,以免引起调用上的偏差,如果一定要调用其他函数体,那么需要在构建这个新函数的时候,把其他依赖的函数在样本工程中,地址设定为一致。
这里,我们新建一个S32DS工程,在linkfile里面划分一块flash区域,用于存放新建的Fun4,Fun4功能是实现红绿灯的交替闪烁。
2.3.1 linkfile修改
MEMORY
{
int_pflash : ORIGIN = 0x00400000, LENGTH = 0x00010000 /* 4096KB - 176KB (sBAF + HSE)*/
int_pflash_user : ORIGIN = 0x00410000, LENGTH = 0x003C4000
int_dflash : ORIGIN = 0x10000000, LENGTH = 0x00020000 /* 128KB */
int_itcm : ORIGIN = 0x00000000, LENGTH = 0x00010000 /* 64KB */
int_dtcm : ORIGIN = 0x20000000, LENGTH = 0x0001F000 /* 124KB */
int_stack_dtcm : ORIGIN = 0x2001F000, LENGTH = 0x00001000 /* 4KB */
int_sram : ORIGIN = 0x20400000, LENGTH = 0x0002FF00 /* 184KB, needs to include int_sram_fls_rsv */
int_sram_fls_rsv : ORIGIN = 0x2042FF00, LENGTH = 0x00000100
int_sram_no_cacheable : ORIGIN = 0x20430000, LENGTH = 0x0000FF00 /* 64KB, needs to include int_sram_results */
int_sram_results : ORIGIN = 0x2043FF00, LENGTH = 0x00000100
int_sram_shareable : ORIGIN = 0x20440000, LENGTH = 0x00004000 /* 16KB */
ram_rsvd2 : ORIGIN = 0x20444000, LENGTH = 0 /* End of SRAM */
}
SECTIONS
{
.FUNC4 :
{
*(.func4)
} > int_pflash_user
…
}
2.3.2 Fun4函数体
函数体代码构建如下,纯逻辑,不依赖任何外在其他函数,变量。
__attribute__((section (".func4"))) void Func4(void)
{
uint8 count1 = 0U;
static volatile uint32 DelayTimer = 0;
volatile uint8 *red_addr_byte = (volatile uint8 *)0x4029131e;
volatile uint8 *green_addr_byte = (volatile uint8 *)0x4029131d;
volatile uint8 *blue_addr_byte = (volatile uint8 *)0x4029131c;
//RED: GPIO29, 0x4029131e
//green: GPIO30, 0x4029131d
//blue: GPIO31, 0x4029131c
while (count1++ < 6)
{
*red_addr_byte = 1;
*green_addr_byte = 0;
while(DelayTimer < 4800000)
{
DelayTimer++;
}
DelayTimer = 0;
*red_addr_byte = 0;
*green_addr_byte = 1;
while(DelayTimer < 4800000)
{
DelayTimer++;
}
DelayTimer = 0;
/*
Siul2_Dio_Ip_WritePin(LED_RED_PORT, LED_RED_PIN, 1U);
Siul2_Dio_Ip_WritePin(LED_GREEN_PORT, LED_GREEN_PIN, 0U);
while(DelayTimer < 4800000)
{
DelayTimer++;
}
DelayTimer = 0;
Siul2_Dio_Ip_WritePin(LED_RED_PORT, LED_RED_PIN, 0U);
Siul2_Dio_Ip_WritePin(LED_GREEN_PORT, LED_GREEN_PIN, 1U);
while(DelayTimer < 4800000)
{
DelayTimer++;
}
DelayTimer = 0;
*/
}
*red_addr_byte = 0;
*green_addr_byte = 0;
}
这里知道,这个Fun4地址是从flash 0x00410000开始的,在带有上面函数的工程编译之后,生成elf。
2.3.3分离Fun4函数体到独立文件
使用JFLASH打开带有新建Fun4的elf文件,删去0x00410000以上的所有代码,方法:
JFLASH->Edit->Delete range:
删除之后得到一个只有Fun4代码的文件,另存为ElfdebugSource_S32K344_RTD500_delete.srec文件。
2.3.4合并Fun4函数体到原始elf对应的srec
使用JFLASH打开原始elf对应的srec,以及刚才分离出来的Fun4 srec文件。选择合并两个文件,会自动把不同地址的Fun4拼接到原始srec文件中去,另存文件。
2.3.5修改main中原始Fun2调用为Fun4调用
直接上图,就是将原本的:
0040280e: ff f7 ab ff bl 0x402768 < Func2 >
修改为:
0040280e: 0d f0 f7 fb bl 0x410000
其中,0x410000就是Fun4的绝对地址。
将已经添加了Fun4的srec文件修改0040280e开始的值为0d f0 f7 fb ,再另存为新的srec,并且debug,可以发现main的运行顺序已经变成了:Fun1->Fun4->Fun3.
跳过了Fun2的同时还运行了新接入的Fun4.
上图是在S32DS+PE Multilink的环境下运行的,这个时候因为运行的srec文件,所以已经不带有elf的符号表信息了,但是功能都是成功的。
2.3 劳德巴赫同步加载原始elf符号表
由于原始elf经过一系列的嫁接修改保存为了srec,丢失了符号表。那么如果还想查看未修改区域的符号表,可以借助于劳德巴赫工具,在attach了代码之后,可以通过如下在trace32中命令加载原始的elf文件:
Data.LOAD.Elf C:\S32DS35_RTD500\elfdebug\elftest\Debug_FLASH\Elfdebug_S32K344_RTD500.elf /nocode
可以看到,原始带有Fun2符号表的地方,只是因为被修改了,所以缺失了符号表,但是其他的调用头符号表还是存在的。这点也是便于代码的运行读取。
三,知识点
这里分享关于BL addr跳转的对应16进制的数据运算情况。前面是直接使用S32DS生成的
0040280e: 0d f0 f7 fb bl 0x410000
可以知道,对于bl 0x410000指令对应的值是0d f0 f7 fb.
那么这个:0d f0 f7 fb值是怎么算出来的呢?
这点需要参考ARM的架构文档:DDI0403E_d_armv7m_arm.pdf
对于BL跳转thumb2指令对应的情况:
对于BL,是一个长跳转,实际上是由两条跳转指令组成的。Thumb
指令都是2个字节,BL是两条跳转指令组成了4个字节。
0-11位表示11位地址,具体含义如下:
第11位为0,代表偏移高位
第11位为1,代表偏移低位
计算公式如下:
offset = (目标地址- 源地址 -4) & 0x007fffff
high = offset >> 12(十进制)
low = ( offset & 0x00000fff )>>1
machineCode = ((0xF800 | low) << 16) | (0xF000 | high)
下面来算算我们这里用到的:
bl 0x410000
offset = (目标地址- 源地址 -4) & 0x007fffff
= (0x410000-0x40280e-4)& 0x007fffff =D7EE
high = offset >> 12(十进制) = D
low = ( offset & 0x00000fff )>>1 = 3F7
machineCode = ((0xF800 | low) << 16) | (0xF000 | high)
=((0xF800 | 3F7) << 16) | (0xF000 | D)
=0XFBF7F00D
也就对应了从低到高的:0D 00 7F FB
这也是如下二进制调制指令的来源:
0040280e: 0d f0 f7 fb bl 0x410000
代码链接:
https://community.nxp.com/t5/S32K-Knowledge-Base/S32K3-tool-part-How-to-flexibly-debug-elf-files-without-source/ta-p/2108317