u-boot的自拷贝(重定位)

u-boot的加载地址

早期的通用启动器(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出现挂死的问题(仅保留了相关的信息):

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的自拷贝功能

不考虑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地址

在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)的展开:

u-boot的自拷贝(重定位)_第1张图片

据该指令的介绍,可知是将当前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位置无关的配置选项

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上几乎不能实现。

你可能感兴趣的:(杂谈,linux,u-boot)