一. uboot启动流程
_main 函数中会调用 relocate_code 函数。
relocate_code 函数分两个部分:
1. 拷贝 uboot 代码部分
2. 有关 " 重定位后有关函数调用或全局变量地址的问题"的解决方法。
本文继上一篇文章的学习,地址如下:
uboot启动流程-uboot代码重定位说明一_凌肖战的博客-CSDN博客
这里学习 uboot 重定位的第二部分内容:重定位后有关函数调用或全局变量地址的问题。
二. uboot代码重定位
1. 重定位后函数调用或全局变量使用问题
具体问题描述:
重定位 就是 uboot 将自身拷贝到 DRAM 的另一个地放去继续运行(DRAM 的高地址处)。
我们知道,一个可执行的 bin 文件,其链接地址和运行地址要相等,也就是链接到哪个地址, 在运行之前,就要拷贝到哪个地址去。现在我们重定位以后,运行地址就和链接地址不同了,这 样寻址的时候不会出问题吗?
当简单粗暴的将uboot从0X87800000 拷贝到其他地方以后,关于函数调用、全局变量引用就会出问题。Uboot对于这个的处理方法就是采用位置无关码,这个就需要借助于 .rel.dyn段。
2. 代码测试
为了分析这个问题,在ubuntu下通过 vscode 打开uboot代码工程。所使用的 uboot源码包目录如下:
阿尔法Linux开发板(A盘-基础资料-2022 / 阿尔法Linux开发板(A盘)-基础资料 / 例程源码 / 开发板教程对应的uboot和linux源码/uboot-imx-rel_imx_4.1.15_2.1.0_ga_alientek.tar.bz2
使用 "ctrl+p"快捷键 打开搜索栏,搜索 "mx6ull_alientek_nand.c" 文件, 在 "mx6ull_alientek_nand.c" 文件中输入如下内容,最后在 board_init 函数里面调用 rel_test 函数:
int board_early_init_f(void)
{
setup_iomux_uart();
return 0;
}
static int rel_a = 0;
void rel_test()
{
rel_a = 100;
printf("rel_test\r\n");
}
int board_init(void)
{
rel_test();
......
}
编译uboot 源码
board_init 函数会调用 rel_test,rel_test 会调用全局变量 rel_a,使用如下命令编译对 uboot 进行编译:
./imx6ull_alientek_nand.sh
编译完成以后,使用 arm-linux-gnueabihf-objdump 将 u-boot 进行反汇编,得到 u-boot.dis 这个汇编文件,命令如下:
arm-linux-gnueabihf-objdump -D -m arm u-boot > u-boot.dis
在 u-boot.dis 文件中找到 rel_a、rel_rest 和 board_init,相关内容如下所示:
87804088 <rel_test>:
87804088: e59f300c ldr r3, [pc, #12] ; 8780409c <rel_test+0x14>
8780408c: e3a02064 mov r2, #100 ; 0x64
87804090: e59f0008 ldr r0, [pc, #8] ; 878040a0 <rel_test+0x18>
87804094: e5832000 str r2, [r3]
87804098: ea00faff b 87842c9c <printf>
8780409c: 8786b180 strhi fp, [r6, r0, lsl #3]
878040a0: 8784b1c3 strhi fp, [r4, r3, asr #3]
878040a4 <board_init>:
878040a4: e92d4010 push {r4, lr}
878040a8: ebfffff6 bl 87804088 <rel_test>
......
8786b180 <rel_a>:
8786b180: 00000000 andeq r0, r0, r0
第 12 行,是 borad_init 调用 rel_test 函数,用到了 bl 指令,而 bl 指令时位置无关指令,bl 指令是相对寻址的(pc+offset),因此 uboot 中函数调用是与绝对位置无关的。
再来看一下 rel_test 函数对于全局变量 rel_a 的调用,第 2 行设置 r3 的值为 pc+12 地址处的 值,因为ARM流水线的原因,pc寄存器的值为:当前地址+8,因此pc=0X87804088+8=0X87804090,
r3=0X8780418C+12=0X8780409C。
第 7 行就是 0X8780409C 这个地址,0X8780409C 处的值为 0X8786B180。根据第 17 行可知,0X8786B180 正是变量 rel_a 的地址,最终 r3 = 0X8786B180 。
第 3 行,r2=100。
第 5 行,将 r2 内的值写到 r3 地址处,也就是设置地址 0X8786B180 的值为 100,这不就
是rel_test函数的第一行代码:rel_a = 100。
总结一下 rel_a=100 的汇编执行过程:
① 在函数 rel_test 末尾处有一个地址为0X8780409C 的内存空间,此内存空间保存着变量 rel_a 的地址。
② 函数 rel_test 要想访问变量 rel_a,首先访问末尾的 0X8780409C来获取变量 rel_a 的地 址,而访问 0X8780409C 是通过偏移来访问的,很明显是个位置无关的操作。
③ 通过 0X8780409C 获取到变量 rel_a 的地址,对变量 rel_a 进行操作。
④ 可以看出,函数 rel_test 对变量 rel_a 的访问没有直接进行,而是使用了一个第三方偏
移地址 0X87804198,专业术语叫做 Label。这个第三方偏移地址就是实现重定位后运行不会出
错的重要原因!
由之前的文章可知,uboot 重 定 位 后 偏 移 为 0X18747000(0X8FF38000 - 0X87800000的结果), 那 么 重 定 位 后 函 数 rel_test 的 首 地 址 就 是:
0X87804088+0X18747000 = 0X9FF4B088
保 存 变 量 rel_a 地 址 的 Label 为:
0X9FF4B088+8+12 = 0X9FF4B09C ( 即 0X8780409C+0X18747000)
变 量 rel_a 的 地 址 就 为 :
0X8786B180+0X18747000 = 0X9FFB2180。
重定位后, rel_test 函数要想正常访问 rel_a变量 ,就得 设置 0X8780409C (重定位后的 Label)地址出的值为 0X8786B180 (重定位后的变量 rel_a 地址)。 这样就解决了重定位后链接地址和运行地址不一致的问题。
可以看出,uboot 对于重定位后链接地址和运行地址不一致的解决方法就是采用位置无关码,在使用 ld 进行链接的时候使用选项 “-pie” 生成位置无关的可执行文件。在文件 arch/arm/config.mk 。
下一篇了解 uboot 重定位后函数调用地址问题进行的处理。