早期的通用启动器(Universal Bootloader, u-boot)没有图形界面配置的功能,为某个设备修改配置的操作比较繁琐。较新版本的u-boot提供了图形化配置界面,可以在以下配置界面下修改u-boot的加载基地址(对应的配置项为CONFIG_SYS_TEXT_BASE
):
-> Boot options
-> Boot images
-> Text Base
该地址指定了u-boot在内存中存放的起始地址,通常由SoC厂商的内置BOOTROM
把u-boot镜像从启动介质读取,加载到该地址,并跳转到此处运行。某些芯片可能因未初始化DDR,不能直接将u-boot镜像加载到该地址处;一种可行的解决方案是使用u-boot的二级启动器(Secondary Program Loader,SPL)来加载常规的u-boot。SPL的特点是体积小,可用于初始化DDR等SoC外围设备。
笔者希望通过u-boot的TFTP网络命令下载u-boot,避免频繁将u-boot烧写至设备,从而加速对u-boot的调试过程。而u-boot要求在内存中的加载地址是固定的,正在运行的u-boot不能将其使用的加载地址(CONFIG_SYS_TEXT_BASE
)作为TFTP命令的目标地址。一种解决方法是修改调试u-boot的加载地址,不过该方法的缺陷是,它与正常使用的加载地址不同,可能会对调试结果产生影响。另一种解决方法是u-boot在运行过程中进行自考贝,其优点是其加载地址可以任意(需要满足一定的拷贝条件)。
在实现u-boot的自拷贝功能之前,先尝试一下加载地址不匹配的启动操作。在不修改加载地址的情况下,将u-boot加载到其他的地址,发现u-boot出现挂死的问题(仅保留了相关的信息):
U-Boot 2021.07-rc5-g79cf70d9 (Jul 04 2021 - 17:29:50 +0800)
DRAM: 948 MiB
MMC: mmc@7e202000: 0, sdhci@7e300000: 1
Loading Environment from FAT... OK
Hit any key to stop autoboot: 0
U-Boot> tftp 0x8000000 u-boot-old.bin
TFTP from server 10.10.8.100; our IP address is 10.10.8.151
Filename 'u-boot-old.bin'.
Load address: 0x8000000
Loading: ################################################## 528.8 KiB
3.1 MiB/s
done
Bytes transferred = 541512 (84348 hex)
U-Boot> go 0x8000000
## Starting application at 0x08000000 ...
U-Boot 2021.07-rc5-g79cf70d9 (Jul 04 2021 - 17:29:50 +0800)
DRAM: 948 MiB
u-boot的加载地址(CONFIG_SYS_TEXT_BASE
)默认为0x80000,但被加载到0x8000000之后,也能运行;但挂在DRAM: 948M
之后便再没有新的串口信息了。
不考虑u-boot的实际加载地址与设定的加载地址不满足拷贝条件的情况(即二者无重叠),可以增加以下简单的自拷贝汇编代码:
diff --git a/arch/arm/cpu/armv8/start.S b/arch/arm/cpu/armv8/start.S
index 66244915..1f34b535 100644
--- a/arch/arm/cpu/armv8/start.S
+++ b/arch/arm/cpu/armv8/start.S
@@ -52,9 +52,28 @@ _bss_start_ofs:
_bss_end_ofs:
.quad __bss_end - _start
+addr_save_boot_params:
+ .quad save_boot_params
+
reset:
+ /* copy u-boot if there is an offset */
+ adr x1, _start /* 获取u-boot的实际加载地址 */
+ ldr x2, _TEXT_BASE /* 获取u-boot的设定加载地址,CONFIG_SYS_TEXT_BASE */
+ subs x3, x1, x2
+ beq 0f
+ mov x4, xzr /* 拷贝起始长度清零 */
+ ldr x5, _end_ofs /* 获取u-boot的镜像长度,单位为字节 */
+1:
+ cmp x4, x5
+ bcs 0f
+ ldr x6, [x1], #0x8
+ str x6, [x2], #0x8
+ add x4, x4, #0x8
+ b 1b
+0:
/* Allow the board to save important registers */
- b save_boot_params
+ ldr x1, addr_save_boot_params
+ br x1
.globl save_boot_params_ret
save_boot_params_ret:
上面的修改中,值得注意的有两处;第一处是实际加载地址的获取的汇编指令:
adr x1, _start
该指令是一个伪指令,实际上是针对PC指针的偏移(加减)操作。后面会做具体的分析。第二处是对函数save_boot_params
的修改:
ldr x1, addr_save_boot_params
br x1
这样修改是为了跳转到拷贝后的u-boot代码段中继续执行。在64位ARM架构中,跳转指令b.cond
是相对当前的PC指针的,如果沿用之前的跳转方式,则仍会在不正确的加载地址中运行u-boot代码。修改后的u-boot可以正常运行(但可能因MMU被之前运行的u-boot初始化了,运行了一段后自动重置了):
U-Boot 2021.07-rc5-g79cf70d9 (Jul 04 2021 - 17:29:50 +0800)
DRAM: 948 MiB
Loading Environment from FAT... OK
U-Boot> tftp 0x8000000 u-boot.bin
lan78xx_eth Waiting for PHY auto negotiation to complete.... done
Using lan78xx_eth device
TFTP from server 10.10.8.100; our IP address is 10.10.8.151
Filename 'u-boot.bin'.
Load address: 0x8000000
Loading: ################################################## 530.9 KiB
3.1 MiB/s
done
Bytes transferred = 543664 (84bb0 hex)
U-Boot> go 0x8000000
## Starting application at 0x08000000 ...
U-Boot 2021.07-rc5-g573873af-dirty (Jul 25 2021 - 20:58:33 +0800)
DRAM: 948 MiB
MMC: mmc@7e202000: 0, sdhci@7e300000: 1
Loading Environment from FAT... OK
Emergency page table not setup.
resetting ...
尽管目前带有自拷贝功能的u-boot检测页表异常而重置,但相比之前的u-boot未发生挂死的异常;因两个u-boot镜像不同,可以肯定自拷贝功能是可行、可用的。此外,原先的u-boot已经使能了指令缓存和数据缓存(icache和dcache),可能会对新加载的u-boot产生一些影响,限于篇幅,不再深入探究。
另附:使用该博客文章中的goraw
命令可以正常在0x8000000
地址处加载运行u-boot镜像:
U-Boot 2021.07-g4955deac (Aug 29 2021 - 20:46:36 +0800)
U-Boot> tftp 0x8000000 u-boot.bin
TFTP from server 10.10.8.100; our IP address is 10.10.8.151
Filename 'u-boot.bin'.
Load address: 0x8000000
Loading: ################################################## 532.5 KiB
2.7 MiB/s
done
Bytes transferred = 545248 (851e0 hex)
U-Boot> goraw 0x8000000
## Starting application at 0x08000000 ...
U-Boot 2021.07-g95c97869-dirty (Aug 28 2021 - 18:53:45 +0800)
Hit any key to stop autoboot: 0
U-Boot>
在64位ARM架构中,PC指针寄存器不能直接访问,但提供了伪指令adr
间接地获取相对PC的某个偏移的地址,这正是上面修改的代码,获取u-boot的加载地址的方式:
adr x1, _start
对u-boot进行反汇编,可以得到:
0000000000080000 <__image_copy_start>:
80000: 0c 00 00 14 1f 20 03 d5 ..... ..
0000000000080008 <_TEXT_BASE>:
80008: 00080000 .word 0x00080000
8000c: 00000000 .word 0x00000000
0000000000080010 <_end_ofs>:
80010: 00084bb0 .word 0x00084bb0
80014: 00000000 .word 0x00000000
.......
0000000000080030 :
80030: 10fffe81 adr x1, 80000 <__image_copy_start>
80034: 58fffea2 ldr x2, 80008 <_TEXT_BASE>
80038: eb020023 subs x3, x1, x2
8003c: 54000120 b.eq 80060 // b.none
80040: aa1f03e4 mov x4, xzr
80044: 58fffe65 ldr x5, 80010 <_end_ofs>
80048: eb05009f cmp x4, x5
8004c: 540000a2 b.cs 80060 // b.hs, b.nlast
80050: f8408426 ldr x6, [x1], #8
80054: f8008446 str x6, [x2], #8
80058: 91002084 add x4, x4, #0x8
8005c: 17fffffb b 80048
80060: 58fffe41 ldr x1, 80028
80064: d61f0020 br x1
上面的反汇编结果“指出”,获得的实际的u-boot加载地址为0x80000,这显然不是对的:上面通过TFTP将其下载到了0x8000000地址处。这个“错误”实际上是由aarch64-linux-gnu-objdump
反汇编器自动计算推导,它按照设定的加载地址计算的,自然与实际不符:
80030: 10fffe81 adr x1, 80000 <__image_copy_start>
80034: 58fffea2 ldr x2, 80008 <_TEXT_BASE>
# 查看二进制机器码:
-----------------------------------------------
Value [0x10fffe81] (0x10fffe81, 285212289):
28 24 20 16 12 8 4 0
0001 0000 1111 1111 1111 1110 1000 0001
31 27 23 19 15 11 7 3
查找armv8 Architecture Reference Manual文档中关于adr
指令的说明,结合上面对该指令(0x10fffe81
)的展开:
据该指令的介绍,可知是将当前PC指针的值加上一个有符号的偏移imm
,作为结果写入到x1
寄存器中(第0比特到第4比特为1)。将29:30位,以及5:23位的数据拼接,可得到偏移量为:
1111 1111 1111 1110 100 00 ->
SignExtend(0x1fffd0, 64) = -48 = -0x30
而当前的u-boot的PC针为0x8000030
,二者相加可以得到通过TFTP命令加载的地址:0x8000000
;实际上,-0x30
即为u-boot起始地址到当前地址的偏移量。该指令中没有对PC寄存器的指涉,其0到4位是指定写入寄存器的序号(即x1
寄存器),因此可以得出结论:在64位ARM架构中,PC指针是不能直接访问的;其值可通过伪汇编指令adr
间接读取。
U-boot提供了CONFIG_POSITION_INDEPENDENT
配置选项,从字面涵义上看是指“位置无关”;文本配置界面的描述为Generate position-independent pre-relocation code
。删除以上添加了拷重定位代码,并使能该选项,发现u-boot并不能“位置无关地运行”(Position-Independent Execute):
U-Boot> tftp 0x8000000 u-boot.bin
TFTP from server 10.10.8.100; our IP address is 10.10.8.151
Filename 'u-boot.bin'.
Load address: 0x8000000
Loading: ################################################## 530.9 KiB
3 MiB/s
done
Bytes transferred = 543656 (84ba8 hex)
U-Boot> goraw 0x8000000
## Starting application at 0x08000000 ...
U-Boot 2021.07-rc5-g573873af (Aug 30 2021 - 22:32:11 +0800)
DRAM: 948 MiB
"Synchronous Abort" handler, esr 0x96000005
elr: 00000000000cca28 lr : 00000000000ccac4 (reloc)
elr: 000000003b3aca28 lr : 000000003b3acac4
x0 : 000000004334daf0 x1 : 0000000000000000
x2 : 0000000000000004 x3 : 000000003af5c050
x4 : 000000003af5c020 x5 : 0000000000000000
x6 : 0000000000000013 x7 : 000000003af5c0f0
x8 : 000000004334daf0 x9 : 0000000000000008
x10: 0000000000000000 x11: 000000003af5bc9c
x12: 0000000000000002 x13: 000000000000024c
x14: 000000003af5bcdc x15: 000000004334daf0
x16: 0000000000004110 x17: 7d7e7500194c1020
x18: 000000003af5bdd0 x19: 0000000000000000
x20: 0000000000000000 x21: 000000003af5c050
x22: 0000000000000000 x23: 0000000008000000
x24: 0000000000000000 x25: 0000000000000000
x26: 0000000000000000 x27: 0000000000000000
x28: 000000003af616d0 x29: 000000003af5bc70
Code: 12800100 d65f03c0 12800120 d65f03c0 (b9400803)
Resetting CPU ...
resetting ...
该配置选项的功能后面再探讨,但可知不能替代我们增加的重定位汇编代码;实际上,位置无关的可执行镜像,在u-boot上几乎不能实现。