资料来源正点原子嵌入式Linux
目录
链接脚本lds
Uboot启动流程
程序调用图
board_init_f:
relocate_code
relocate_vectors
board_init_r
run_main_loop
cmd_process
u-boot.lds中给出了U-boot的总结架构。
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
*(.__image_copy_start) //0X87800000
*(.vectors) //0X87800000
arch/arm/cpu/armv7/start.o (.text*) //将start.s编译出来的代码放在这里
*(.text*) //text段结束
}
. = ALIGN(4);
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
. = ALIGN(4);
.data : {
*(.data*)
}
. = ALIGN(4);
. = .;
. = ALIGN(4);
.u_boot_list : {
KEEP(*(SORT(.u_boot_list*)));
}
. = ALIGN(4);
.image_copy_end :
{
*(.__image_copy_end)
}
.rel_dyn_start :
{
*(.__rel_dyn_start)
}
.rel.dyn : {
*(.rel*)
}
.rel_dyn_end :
{
*(.__rel_dyn_end)
}
.end :
{
*(.__end)
}
_image_binary_end = .;
. = ALIGN(4096);
.mmutable : {
*(.mmutable)
}
.bss_start __rel_dyn_start (OVERLAY) : {
KEEP(*(.__bss_start));
__bss_base = .;
}
.bss __bss_base (OVERLAY) : {
*(.bss*)
. = ALIGN(4);
__bss_limit = .;
}
.bss_end __bss_limit (OVERLAY) : {
KEEP(*(.__bss_end));
}
.dynsym _image_binary_end : { *(.dynsym) }
.dynbss : { *(.dynbss) }
.dynstr : { *(.dynstr*) }
.dynamic : { *(.dynamic*) }
.plt : { *(.plt*) }
.interp : { *(.interp*) }
.gnu.hash : { *(.gnu.hash) }
.gnu : { *(.gnu*) }
.ARM.exidx : { *(.ARM.exidx*) }
.gnu.linkonce.armexidx : { *(.gnu.linkonce.armexidx.*) }
}
可以看出,启动从_start开始。
接下来介绍main调用的board_init_f、 relocate_code、relocate_vectors 和 board_init_r四个主要函数
board_init_f主要对DDR进行初始化,并且在DDR中分配好了重定位代码各个部分的地址区域。
DDR地址从0x8000000到0XA0000000,共计0x20000000,512MB。
Uboot要将Uboot代码重定位到DDR区域最后的部分,留出前面的位置(开始存放在0x87800000),内存分配好后的分布图:
uboot 重定位后的偏移为 0X18747000,重定位后的新地址为0X9FF4700,新的 gd 首地址为 0X9EF44EB8,最终的 sp 为 0X9EF44E90。
relocate_code 函数是用于代码拷贝的。
拷贝方式:
ldr r1, =__image_copy_start /* r1 <- SRC &__image_copy_start */
subs r4, r0, r1 /* r4=r0-r1=0X9FF47000-0X87800000=0X18747000,因此 r4 保存偏移量r4 <- relocation offset */
beq relocate_done /* skip relocation */
ldr r2, =__image_copy_end /*r2=0x878668f4 r2 <- SRC &__image_copy_end */
/*将uboot代码段拷贝到目标地址,从__image_copy_start到__image_copy_end */
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
再拷贝代码时候,有些代码调用其他地址的函数,变量,那么这些变量函数的地址变化了,调用不是会出错吗?所以,为了解决这一问题,引出了rel.dyn 段。
首先来看一个简单函数:
static int rel_a = 0;
void rel_test(void)
{
rel_a = 100;
printf("rel_test\r\n");
}
该函数是在u-boot的board_init()初始化中引用。查看反汇编:
1 87804184 :
2 87804184: e59f300c ldr r3, [pc, #12] ; 87804198
3 87804188: e3a02064 mov r2, #100 ; 0x64
4 8780418c: e59f0008 ldr r0, [pc, #8] ; 8780419c
5 87804190: e5832000 str r2, [r3]
6 87804194: ea00d668 b 87839b3c
7 87804198: 8785da50 ; instruction: 0x8785da50
8 8780419c: 878426a2 strhi r2, [r4, r2, lsr #13]
9
10 878041a0 :
11 878041a0: e92d4010 push {r4, lr}
12 878041a4: ebfffff6 bl 87804184
13
14 ......
15
16 8785da50 :
17 8785da50: 00000000 andeq r0, r0, r0
可以看到board_init在调用rel_test时利用了bl跳转指令(12行),bl是相对跳转指令,也就是说bl跳转到后面的地址时是根据偏移来跳转的。现在bl指令所在是地址878041a4,跳转到87804184,bl是根据两个地址差值进行跳转。如果878041a4变化了,那后面的跳转地址也会跟着变化。
再来看变量引用,rel_test引用变量rel_a
r3=pc+8+12=[0x87804198](ARM中pc的值是当前位置+8)=8785da50,8785da50正是rel_a的地址。
r2=100
----
将100存入地址8785da50中变量rel_a中。
可以看出,函数 rel_test 对变量 rel_a 的访问没有直接进行,而是使用了一个第三方偏移地址 0X87804198,专业术语叫做 Label。
UBOOT重定位以后,偏移地址0x18747000,那么上述代码的地址变为:
1 9FF4B184 :
2 9FF4B184: e59f300c ldr r3, [pc, #12] ; 87804198
3 9FF4B188: e3a02064 mov r2, #100 ; 0x64
4 9FF4B18c: e59f0008 ldr r0, [pc, #8] ; 8780419c
5 9FF4B190: e5832000 str r2, [r3]
6 9FF4B194: ea00d668 b 87839b3c
7 9FF4B198: 8785da50 ; instruction: 0x8785da50
8 9FF4B19c: 878426a2 strhi r2, [r4, r2, lsr #13]
9
10 9FF4B1A0 :
11 9FF4B1A4: e92d4010 push {r4, lr}
12 9FF4B1A8: ebfffff6 bl 87804184
13
14 ......
15
16 9FFA4A50 :
17 9FFA4A54: 00000000 andeq r0, r0, r0
Label的地址变为0X9FF4B184+8+12=0X9FF4B198,但是这时候只是地址变化了,Label中的值,也就是rel_a的地址并没有变化。Label中的值应该是9FFA4A50才对,也就是原来的Label值8785da50+偏移地址18747000.
可以看出, uboot 对于重定位后链接地址和运行地址不一致的解决方法就是采用位置无关码(bl,label),在使用 ld 进行链接的时候使用选项“ -pie”生成位置无关的可执行文件。-pie会生成rel.dyn段。
对应的反汇编:
1 Disassembly of section .rel.dyn:
2
3 8785da44 <__rel_dyn_end-0x8ba0>:
4 8785da44: 87800020 strhi r0, [r0, r0, lsr #32]
5 8785da48: 00000017 andeq r0, r0, r7, lsl r0
6 ......
7 8785dfb4: 87804198 ; instruction: 0x87804198
8 8785dfb8: 00000017 andeq r0, r0, r7, lsl r0
先来看一下.rel.dyn 段的格式,类似第 7 行和第 8 行这样的是一组,也就是两个 4 字节数据为一组。高 4 字节是 Label 地址标识 0X17,低 4 字节就是 Label 的地址,首先判断 Label 地址标识是否正确,也就是判断高 4 字节是否为 0X17,如果是的话高 4 字节就是 Label 值。第 7 行值为 0X87804198,第 8 行为 0X00000017,说明第 7 行的 0X87804198 是个 Label,这个正是示例代码 中存放变量 rel_a 地址的那个 Label地址。前面给出的解决方案是Label的值加上偏移地址就可以了,所以在重定位rel.dyn段时,代码如下:
ldr r2, =__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */
ldr r3, =__rel_dyn_end /* r3 <- SRC &__rel_dyn_end */
fixloop:
ldmia r2!, {r0-r1} /*每次读取4字节数据存放到r0(Label地址),r1(Label标志) */
and r1, r1, #0xff //取低8位
cmp r1, #23 /* 判断r1是不是0x17 */
bne fixnext //如果不等,则不是label。
/* 如果相等,则是label,需要加上偏移*/
add r0, r0, r4 //r0=r0+r4偏移量=重定位后的Label地址0X87804198+0X18747000=0X9FF4B198
ldr r1, [r0] //读取重定位后labal中的labal值,也就是还没有定位的rel_a的地址0X8785DA50
add r1, r1, r4//label值加上偏移量, rel_a 重 定 位 后 的0X8785DA50+0X18747000=0X9FFA4A50
str r1, [r0]//重新写入r0=0X9FF4B198
fixnext:
cmp r2, r3 //比较r2,r3看rel.dyn是否重定位完成
blo fixloop //不相等,跳回去继续执行
重定位中断向量表
imx6ull的中断向量表重定位只需要向VBAR寄存器中,VBAR是c15协寄存器中目标设置为CRn=c12, opc1=0, CRm=c0,opc2=0。
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
mcr p15, 0, r0, c12, c0, 0 /* 将重定位的地址写到VBAR寄存器*/
board_init_f 并没有初始化所有的外设,还需要做一些后续工作,这些后续工作就是由函数 board_init_r 来完成的
boot 启动以后会进入 3 秒倒计时,如果在 3 秒倒计时结束之前按下按下回车键,那么就会进入 uboot 的命令模式,如果倒计时结束以后都没有按下回车键,那么就会自动启动 Linux 内核 , 这 个 功 能 就 是 由 run_main_loop 函 数 来 完 成 的 。
run_main_loop
-> main_loop
-> bootdelay_process 获取bootdelay的值,然后保存到stored_bootdelay
全局变量里面,获取bootcmd环境变量值,并且将其
返回
-> autoboot_command 参数是bootcmd的值。
-> abortboot 参数为boot delay,此函数会处理倒计时
-> abortboot_normal 参数为boot delay,此函数会处理倒计时
-> cli_loop uboot命令模式处理函数。
-> parse_file_outer
-> parse_stream_outer
-> parse_stream 解析输入的字符,得到命令
-> run_list 运行命令
-> run_list_real
-> run_pipe_real
-> cmd_process 处理命令,也就是执行命令
cmd_process用来处理命令。
Uboot使用U_BOOT_CMD来定义一个命令。CONFIG_CMD_XXX来使能uboot中的某个命令。 U_BOOT_CMD最终是定义了一个cmd_tbl_t类型的变量,所有的命令最终都是存放在.u_boot_list段里面。cmd_tbl_t的cmd成员变量就是具体的命令执行函数,命令执行函数都是do_xxx。
cmd_process
->find_cmd 从.u_boot_list段里面查找命令,当找到对应的命令以后以返回值的
形式给出,为cmd_tbl_t类型
->cmd_call
->cmdtp->cmd 直接引用cmd成员变量