u-boot重定位过程代码分析
本文是在ubuntu下使用qemu+gdb调试u-boot下进行。
u-boot 启动过程
程序的入口参数有五种:
- 命令行
-e
指定 - 链接器脚本
ENTRY(symbol)
指定 - 如果定义了
start
, 那么start
标号是程序入口 .text
段的第一个字节- 地址
0
这里是u-boot.lds
链接器脚本指定了ENTRY(_start)
,所以程序入口地址就是_start
。
_start
入口之后,跳到reset
,对协处理器进行初始化后,跳转到_main
,arm64
跳转到crt0_64.S
里面的_main
。
接下来是进行一些内存预留,然后跳转到board_init_f
进行第一步的板级初始,执行init_sequence_f
数组里面的函数;u-boot-2016
版本的结构与u-boot-2010
的这个初始化流程进行了优化。
其中比较重要的几个就是在RAM
顶部预留空间的部分函数,比如reserve_uboot
为后续搬移u-boo
t预留空间,reserve_board
为bd_t
结构体预留空间,reserve_global_data
为gd_t
预留空间。
setup_reloc
拷贝gd_t
到预留的RAM
顶部空间之后,开始跳转到relocate_code
,开始代码重定位处理。
到relocate_code
之前的流程大概如下:
-> _start [arch/arm/lib/vectors.S]
-> reset [arch/arm/cpu/armv7/start.S]
...
-> _main [arch/arm/lib/crt0.S]
-> board_init_f_alloc_reserve ->> [_main] [common/init/board_init.c]
-> board_init_f_init_reserve ->> [_main]
-> board_init_f [common/board_f.c]
-> initcall_run_list [lib/initcall.c]
-> setup_mon_len ->> [initcall_run_list] [common/board_f.c]
-> initf_malloc ->> [initcall_run_list] [common/dlmalloc.c]
...
-> init_baud_rate ->> [initcall_run_list] [common/board_f.c]
-> serial_init ->> [initcall_run_list] [drivers/serial/serial.c]
-> console_init_f ->> [initcall_run_list] [common/console.c]
...
-> reserve_round_4k ->> [initcall_run_list]
-> reserve_mmu ->> [initcall_run_list]
-> reserve_trace ->> [initcall_run_list]
-> reserve_uboot ->> [initcall_run_list]
-> reserve_malloc ->> [initcall_run_list]
-> reserve_board ->> [initcall_run_list]
-> setup_machine ->> [initcall_run_list]
-> reserve_global_data ->> [initcall_run_list]
-> reserve_fdt ->> [initcall_run_list]
-> reserve_arch ->> [initcall_run_list]
-> reserve_stacks ->> [initcall_run_list]
-> setup_dram_config ->> [initcall_run_list]
-> show_dram_config ->> [initcall_run_list]
-> display_new_sp ->> [initcall_run_list]
-> reloc_fdt ->> [initcall_run_list]
-> setup_reloc ->> [initcall_run_list] ->> [_main]
-> relocate_code [arch/arm/lib/relocate.S] ->> [_main]
u-boot 对函数及全局变量的寻址过程
参考这个博客:
[https://blog.csdn.net/skyflying2012/article/details/37660265]
在u-boot代码[common/main.c]里面修改一下, 只要保证代码被这个平台编译, 且不被优化掉就可以了。如果代码或者变量没有被引用, 可能会被编译器优化掉, 取决于优化等级。
static int xxx_var = 100;
void xxx_func_1(void)
{
xxx_var +=1;
}
void xxx_func_2(void)
{
xxx_var = 200;
}
typedef void (*xxx_func_t)(void);
static xxx_func_t xxx_func_var = xxx_func_1;
void xxx_rel_dyn(void)
{
xxx_var = 55;
(*xxx_func_var)();
xxx_func_var = xxx_func_2;
(*xxx_func_var)();
}
编译完后的代码的段情况如下:
There are 30 section headers, starting at offset 0x190e24:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 60800000 008000 02d978 00 AX 0 0 32
[ 2] .rodata PROGBITS 6082d978 035978 00bfd1 00 A 0 0 8
[ 3] .hash HASH 6083994c 04194c 00002c 04 A 13 0 4
[ 4] .data PROGBITS 60839978 041978 001b38 00 WA 0 0 8
[ 5] .got.plt PROGBITS 6083b4b0 0434b0 00000c 04 WA 0 0 4
[ 6] .u_boot_list PROGBITS 6083b4bc 0434bc 000774 00 WA 0 0 4
[ 7] .efi_runtime PROGBITS 6083bc30 043c30 000100 00 WAX 0 0 8
[ 8] .efi_runtime_rel REL 6083bd30 043d30 000090 08 A 13 0 4
[ 9] .rel.dyn REL 6083bdc0 043dc0 007240 08 A 13 0 4
[10] .bss_start PROGBITS 6083bdc0 04b11d 000000 00 W 0 0 1
[11] .bss NOBITS 6083bdc0 000000 037764 00 WA 0 0 64
[12] .bss_end PROGBITS 60873524 04b11d 000000 00 W 0 0 1
[13] .dynsym DYNSYM 60843000 04b000 000060 10 A 14 3 4
[14] .dynstr STRTAB 60843060 04b060 00002a 00 A 0 0 1
[15] .dynamic DYNAMIC 6084308c 04b08c 000080 08 WA 14 0 4
[16] .interp PROGBITS 6084310c 04b10c 000011 00 A 0 0 1
[17] .ARM.attributes ARM_ATTRIBUTES 00000000 04b11d 000029 00 0 0 1
[18] .comment PROGBITS 00000000 04b146 00002b 01 MS 0 0 1
[19] .debug_line PROGBITS 00000000 04b171 0195d6 00 0 0 1
[20] .debug_info PROGBITS 00000000 064747 0a0a7a 00 0 0 1
[21] .debug_abbrev PROGBITS 00000000 1051c1 01bbe0 00 0 0 1
[22] .debug_aranges PROGBITS 00000000 120da8 0037d8 00 0 0 8
[23] .debug_frame PROGBITS 00000000 124580 008fe4 00 0 0 4
[24] .debug_str PROGBITS 00000000 12d564 0109ac 01 MS 0 0 1
[25] .debug_loc PROGBITS 00000000 13df10 04aa07 00 0 0 1
[26] .debug_ranges PROGBITS 00000000 188918 0083e0 00 0 0 8
[27] .shstrtab STRTAB 00000000 190cf8 00012b 00 0 0 1
[28] .symtab SYMTAB 00000000 1912d4 010000 10 29 3251 4
[29] .strtab STRTAB 00000000 1a12d4 0065f8 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
反汇编后的三个函数的代码如下:
60809708 <xxx_func_1>:
60809708: e59f300c ldr r3, [pc, #12] ; 6080971c <xxx_func_1+0x14>
@ r3 = 6083ad50, 6083ad50 = <xxx_var>, [6083ad50] = xxx_var
6080970c: e5932000 ldr r2, [r3] @ r2 = [r3], r2 = xxx_var, 取变量的值
60809710: e2822001 add r2, r2, #1 @ +1 操作
60809714: e5832000 str r2, [r3] @ 存回 xxx_var
60809718: e12fff1e bx lr @ 返回
@ 后面这个是 xxx_func_1 函数寻址用的
6080971c: 6083ad50 addvs sl, r3, r0, asr sp @ 后面这段汇编指令应该是反汇编有问题, 应该是类似
@ 这样的: 608010b4: 6083997c .word 0x6083997c
@ 指定某一个立即数的
60809720 <xxx_func_2>:
60809720: e59f3008 ldr r3, [pc, #8] ; 60809730 <xxx_func_2+0x10>
@ r3 = 6083ad50, 6083ad50 = <xxx_var>, [6083ad50] = xxx_var
60809724: e3a020c8 mov r2, #200 ; 0xc8
60809728: e5832000 str r2, [r3] @ xxx_var = #200
6080972c: e12fff1e bx lr @ 返回
@ 后面这个是 xxx_func_2 函数寻址用的
60809730: 6083ad50 addvs sl, r3, r0, asr sp @ 后面这段汇编指令应该是反汇编有问题, 应该是类似
@ 这样的: 608010c8: 6083997c .word 0x6083997c
@ 指定某一个立即数的
60809734 <xxx_rel_dyn>:
@ 入栈
60809734: e92d4038 push {r3, r4, r5, lr}
@ 准备工作
60809738: e3a03037 mov r3, #55 ; 0x37 @ r3 存入临时变量, r3 = #55
6080973c: e59f4020 ldr r4, [pc, #32] ; 60809764 <xxx_rel_dyn+0x30>
@ r4 = 6083ad50, 6083ad50 = <xxx_var>, [6083ad50] = xxx_var
60809740: e59f5020 ldr r5, [pc, #32] ; 60809768 <xxx_rel_dyn+0x34>
@ r5 = 6083ad4c, 6083ad4c = <xxx_func_var>, [6083ad4c] = xxx_func_var
@ 此时 [6083ad4c] = 60809708 = <xxx_func_1>
@ C代码 : xxx_var = 55;
60809744: e5843000 str r3, [r4] @ [r4] = xxx_var , r3 = #55, [r4] = r3 => xxx_var = #55
@ 将 r3 的值存到 xxx_var 变量
@ C代码 : (*xxx_func_var)(); (第一个)
60809748: e5953000 ldr r3, [r5] @ [r5] = xxx_func_var, r3 = [r5] => r3 = xxx_func_var => r3 = <xxx_func_1>
6080974c: e12fff33 blx r3 @ 跳转 r3 所在的地址, 此时是 <xxx_func_1>
@ C代码 : xxx_func_var = xxx_func_2;
60809750: e59f3014 ldr r3, [pc, #20] ; 6080976c <xxx_rel_dyn+0x38>
@ r3 = 60809720, 60809720 = <xxx_func_2>
60809754: e5853000 str r3, [r5] @ [r5] = xxx_func_var, r3 = <xxx_func_2>, [r5] = r3 => xxx_func_var = <xxx_func_2>
@ 这里应该是函数 xxx_func_2() 被内联了?
60809758: e3a030c8 mov r3, #200 ; 0xc8
6080975c: e5843000 str r3, [r4] @ r3 = #200, [r4] = xxx_var, [r4] = r3 => xxx_var = #200
@ 出栈
60809760: e8bd8038 pop {r3, r4, r5, pc}
@ 后面这几个是 xxx_rel_dyn 函数寻址用的
60809764: 6083ad50 addvs sl, r3, r0, asr sp @ 后面这段汇编指令应该是反汇编有问题, 应该是类似
@ 这样的: 60809764: 6083ad50 .word 0x6083ad50
@ 指定某一个立即数的
60809768: 6083ad4c addvs sl, r3, ip, asr #26
6080976c: 60809720 addvs r9, r0, r0, lsr #14
在.data
段中可以发现: (6083ad4c
地址的值就是60809708
, 6083ad50
的值就是00000064
)
Disassembly of section .data:
...
6083ad4c <xxx_func_var>:
6083ad4c: 60809708 addvs r9, r0, r8, lsl #14
6083ad50 <xxx_var>:
6083ad50: 00000064 andeq r0, r0, r4, rrx
addvs
和umullvs
后面的vs
代表操作条件,Overflow
时会对置位寄存器的V=1
。但后面这几行的应该是代表代码段或数据段的位置, 而不是汇编指令。
经过上面的代码分析, 可以看到:
- 对函数的寻址是通过 bl 或者 b 指令实现的, 是相对地址的跳转
- 对全局变量的寻址是通过函数代码段后的地址实现的
全局变量经过重定向后的地址会改变, 导致后面寻址有问题, 所以这里要对这些全局变量的地址进行重新修改。
查看拷贝后__image_copy_start
到__image_copy_end
的内容
变量xxx_func_var
和xxx_var
: 重定向前地址分别为0x6083ad4c
和0x6083ad50
, 查看gd->reloc_off
或者当前relocate_code
的r4
, 可以得知重定向的偏移为: 0x1f77c000
, 这两个地址的连续的, 直接查看0x6083ad4c
。
反汇编代码:
6083ad4c <xxx_func_var>:
6083ad4c: 60809708 addvs r9, r0, r8, lsl #14
6083ad50 <xxx_var>:
6083ad50: 00000064 andeq r0, r0, r4, rrx
重定向后的内存地址的值:
(gdb) x/2x 0x7ffb6d4c
0x7ffb6d4c: 0x60809708 0x00000064
反汇编的相关代码段地址为0x60809708~0x6080976c
, 查看0x7ff85708
: (当前都是机器码了)
(gdb) x/26x 0x7ff85708
0x7ff85708: 0xe59f300c 0xe5932000 0xe2822001 0xe5832000
0x7ff85718: 0xe12fff1e 0x6083ad50 0xe59f3008 0xe3a020c8
0x7ff85728: 0xe5832000 0xe12fff1e 0x6083ad50 0xe92d4038
0x7ff85738: 0xe3a03037 0xe59f4020 0xe59f5020 0xe5843000
0x7ff85748: 0xe5953000 0xe12fff33 0xe59f3014 0xe5853000
0x7ff85758: 0xe3a030c8 0xe5843000 0xe8bd8038 0x6083ad50
0x7ff85768: 0x6083ad4c 0x60809720
对比反汇编代码:
60809734 <xxx_rel_dyn>:
60809734: e92d4038 push {r3, r4, r5, lr}
60809738: e3a03037 mov r3, #55 ; 0x37
6080973c: e59f4020 ldr r4, [pc, #32] ; 60809764 <xxx_rel_dyn+0x30>
60809740: e59f5020 ldr r5, [pc, #32] ; 60809768 <xxx_rel_dyn+0x34>
60809744: e5843000 str r3, [r4]
60809748: e5953000 ldr r3, [r5]
6080974c: e12fff33 blx r3
60809750: e59f3014 ldr r3, [pc, #20] ; 6080976c <xxx_rel_dyn+0x38>
60809754: e5853000 str r3, [r5]
60809758: e3a030c8 mov r3, #200 ; 0xc8
6080975c: e5843000 str r3, [r4]
60809760: e8bd8038 pop {r3, r4, r5, pc}
60809764: 6083ad50 addvs sl, r3, r0, asr sp
60809768: 6083ad4c addvs sl, r3, ip, asr #26
6080976c: 60809720 addvs r9, r0, r0, lsr #14
可以看到里面的寻址标签还是0x6083ad50
, 0x6083ad4c
, 0x60809720
。另外两个函数同样。
在.rel.dyn
段中, 可以找到下面的内容: 其中的60809764 60809768 6080976c
这几个值分别是 xxx_rel_dyn 函数用来寻址变量的值。
...
6083d380: 60809764 addvs r9, r0, r4, ror #14
6083d384: 00000017 andeq r0, r0, r7, lsl r0
6083d388: 60809768 addvs r9, r0, r8, ror #14
6083d38c: 00000017 andeq r0, r0, r7, lsl r0
6083d390: 6080976c addvs r9, r0, ip, ror #14
6083d394: 00000017 andeq r0, r0, r7, lsl r0
...
在relocate_code
关于.rel.dyn
段重定向的打上断点:
(gdb) b 98 if $r0=0x60809764
Breakpoint 3 at 0x608006e0: file arch/arm/lib/relocate.S, line 98.
(gdb) c
Continuing.
Breakpoint 3, fixloop () at arch/arm/lib/relocate.S:98
(gdb) i r r0 r1
r0 0x60809764 1619040100
r1 0x17 23
(gdb) i r r0 r4
r0 0x60809764 1619040100
r4 0x1f77c000 527941632
(gdb) i r r0
r0 0x7ff85764 2146981732
r1 0x17 23
(gdb) i r r0 r1
r0 0x7ff85764 2146981732
r1 0x6083ad50 1619242320
(gdb) i r r1
r1 0x7ffb6d50 2147183952
(gdb) x /x 0x7ff85764
0x7ff85764: 0x6083ad50
(gdb) x /x 0x7ff85764
0x7ff85764: 0x7ffb6d50
在代码上补上上面的调试信息:
relocate_code
重定向
u-boot relocate_code 第一阶段
第一阶段比较简单,就是将Flash或者RAM中的u-boot重新拷贝到RAM顶端的过程,直接看汇编代码(relocate_code
到copy_loop
)。
u-boot relocate_code 第二阶段
第一阶段的就是纯粹的一个拷贝过程,基于前面的变量及函数的寻址过程,接下来查看relocate_code的第二阶段:对全局变量的寻址进行修正(剩余部分代码)。
汇编注释
/*
* void relocate_code(addr_moni)
*
* This function relocates the monitor code.
*
* NOTE:
* To prevent the code below from containing references with an R_ARM_ABS32
* relocation record type, we never refer to linker-defined symbols directly.
* Instead, we declare literals which contain their relative location with
* respect to relocate_code, and at run time, add relocate_code back to them.
*/
ENTRY(relocate_code)
ldr r1, =__image_copy_start /* r1 <- SRC &__image_copy_start */
@ __image_copy_start = 0x60800000, 可以用 `nm` 工具看到
subs r4, r0, r1 /* r4 <- relocation offset */
@ r0 = 0x7ff7c000, r1 = 0x60800000, r4 = 0x1f77c000
beq relocate_done /* skip relocation */
ldr r2, =__image_copy_end /* r2 <- SRC &__image_copy_end */
@ __image_copy_end = 0x6083bdc0, 可以用 `nm` 工具看到
@ 从上面的准备可以看到, r0 指向的是目标地址, r1 执行源地址,
@ r0 = 0x7ff7c000, r1 = 0x60800000, r2 = 0x6083bdc0, r4 = 0x1f77c000
@ 接下来就是拷贝过程了, 将 r1 指向的地址的内容 load 到 r10-r11 寄存器,
@ 然后再将 r10-r11 的内容 stor 到 r0 指向的地址, r0 和 r1 的地址都递增,
@ 在 r1 小于 r2 时一直执行 copy_loop 循环.
copy_loop:
ldmia r1!, {r10-r11} /* copy from source address [r1] */
stmia r0!, {r10-r11} /* copy to target address [r0] */
cmp r1, r2 /* until source end address [r2] */
blo copy_loop
/*
* fix .rel.dyn relocations
*/
@ 对 .rel.dyn 段进行重定向
@ r2 = 0x6083bdc0, r3 = 0x60843000 可以用 `nm` 工具看到 __rel_dyn_start 和 __rel_dyn_end
ldr r2, =__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */
ldr r3, =__rel_dyn_end /* r3 <- SRC &__rel_dyn_end */
cmp r2, r3
beq relocate_done
@
fixloop:
ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */
@ r0 = 0x60809764, r1 = 0x17, r2= 0x6083bdc8
and r1, r1, #0xff @ 取低8位
cmp r1, #23 /* relative fixup? */
@ 与 23 比较, 如果不等则表明已经重定向过了
bne fixnext
/* relative fix: increase location by offset */
add r0, r0, r4 @ r4 = 0x1f77c000, r0 = r0 + r4 = 0x7ff85764
ldr r1, [r0] @ [0x7ff85764] = 0x6083ad50(与搬移前的反汇编一致), r1 = 0x6083ad50
add r1, r1, r4 @ r4 = 0x1f77c000, r1 = r1 + r4 = 0x7ffb6d50, [0x7ffb6d50] = 0x00000064
str r1, [r0] @ r0 = 0x7ff85764, r1 = 0x7ffb6d50, [r0] = r1 => [0x7ff85764] = 0x7ffb6d50 => 指向新的地址
fixnext:
cmp r2, r3
blo fixloop
relocate_done:
#ifdef __XSCALE__
/*
* On xscale, icache must be invalidated and write buffers drained,
* even with cache disabled - 4.2.7 of xscale core developer's manual
*/
mcr p15, 0, r0, c7, c7, 0 /* invalidate icache */
mcr p15, 0, r0, c7, c10, 4 /* drain write buffer */
#endif
/* ARMv4- don't know bx lr but the assembler fails to see that */
#ifdef __ARM_ARCH_4__
mov pc, lr
#else
bx lr
#endif
ENDPROC(relocate_code)
过程大概是这样的图:
重定向前:
---------------------------- 0x80000000
|
| Other
|
----------------------------
| xxx_var(100)
---------------------------- 0x6083ad50
| xxx_func_var(60809708)
---------------------------- 0x6083ad4c
|
| Other
|
----------------------------
| 60809720
---------------------------- 0x6080976C
| 6083ad4c
---------------------------- 0x60809768
| 6083ad50
---------------------------- 0x60809764
|
| Other
|
---------------------------- 0x60809730
| xxx_func_2
---------------------------- 0x60809720
| xxx_func_1
---------------------------- 0x60809708
|