uboot2012(一)分析重定位
引入
关于移植,搜索关键英文词语 portting
移植简单的介绍在readme中,手册是它的使用帮助
环境配置
这里使用编译工具arm-linux-gcc-4.3.2.tar
,具体安装参考更换gcc工具链.md
编译体验
make smdk2410_config
make
入口查找
我们可以从顶层Makefile开始分析,也可以直接看到编译结果,查看最后的链接过程如下,搜索arm-linux-
arm-linux-ld -pie -T u-boot.lds -Bstatic -Ttext 0x0 $UNDEF_SYM arch/arm/cpu/arm920t/start.o --start-group api/libapi.o
可以看到这里指定了链接脚本以及代码段的地址在-Ttext 0
,第一个文件是arch/arm/cpu/arm920t/start.o
代码分析
简单的流程如下:
- set the cpu to SVC32 mode
- close watchdog
- mask all IRQs by setting all bits in the INTMR
- FCLK:HCLK:PCLK = 1:2:4
- cpu_init_crit
- flush v4 I/D caches
- disable MMU stuff and caches
- lowlevel_init
- memory control configuration [set sdram]
- set sp [Set stackpointer in internal RAM]
- board_init_f
- init_sequence
- board_early_init_f
- set clock
- gpio
- ----....---
- init_sequence
- relocate_code
- copy_loop 复制代码
- fixloop 修改全局变量等数据段
- clear_bss
board_init_f
这里有一个关键的变量gd
,可以看到文件头上面有个宏DECLARE_GLOBAL_DATA_PTR
,定义如下
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
也就是定义gd
为寄存器变量r8
,可以看到编译输出有这么一句,表示编译器不使用r8
寄存器
-fno-common -ffixed-r8
pie
新版本的uboot是在初始化完执行完board_init_f
后进行重定位代码,但是代码本身的链接脚本就是在0地址的,那么它为什么需要再重定位代码?
我们可以看到链接命令输出如下
arm-linux-ld -pie -T u-boot.lds -Bstatic -Ttext 0x0 $UNDEF_SYM arch/arm/cpu/arm920t/start.o --start-group api/libapi.o
搜索下pie相关的内容,就是说创建位置无关可执行程序
$ arm-linux-ld --help | grep "pie"
-pie, --pic-executable Create a position independent executable
内存分布分析
我们从进入c函数的地方开始分析,前面的就不看了,c函数跳转首先需要这是sp
SP设置
到设置堆栈的代码为入口分析
/* Set stackpointer in internal RAM to call board_init_f */
call_board_init_f:
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
ldr r0,=0x00000000
bl board_init_f
这里设置sp=CONFIG_SYS_INIT_SP_ADDR
,我们可以查看反汇编得到实际的值是0x30000f80
00000098 <call_board_init_f>:
98: e59fd3d8 ldr sp, [pc, #984] ; 478 <fiq+0x58>
9c: e3cdd007 bic sp, sp, #7 ; 0x7
a0: e3a00000 mov r0, #0 ; 0x0
a4: eb0007f1 bl 2070 <board_init_f>
478: 30000f80 .word 0x30000f80
接下来仔细看下代码,这里有个宏DEFINE
,目的是将后面的参数1作为一个宏传递给汇编文件,值是第二个参数,这里的值就是global_data
也就是gd_t
向上16
对齐
#define PHYS_SDRAM_1 0x30000000 /* SDRAM Bank #1 */
#define PHYS_SDRAM_1_SIZE 0x04000000 /* 64 MB */
#define CONFIG_SYS_SDRAM_BASE PHYS_SDRAM_1
#define CONFIG_SYS_INIT_SP_ADDR (CONFIG_SYS_SDRAM_BASE + 0x1000 - \
GENERATED_GBL_DATA_SIZE)
lib\asm-offsets.c
DEFINE(GENERATED_GBL_DATA_SIZE,
(sizeof(struct global_data) + 15) & ~15);
typedef struct global_data {
bd_t *bd;
unsigned long flags;
unsigned long baudrate;
unsigned long have_console; /* serial_init() was called */
unsigned long env_addr; /* Address of Environment struct */
unsigned long env_valid; /* Checksum of Environment valid? */
unsigned long fb_base; /* base address of frame buffer */
#ifdef CONFIG_ARM
/* "static data" needed by most of timer.c on ARM platforms */
unsigned long timer_rate_hz;
unsigned long tbl;
unsigned long tbu;
unsigned long long timer_reset_value;
unsigned long lastinc;
#endif
unsigned long relocaddr; /* Start address of U-Boot in RAM */
phys_size_t ram_size; /* RAM size */
unsigned long mon_len; /* monitor len */
unsigned long irq_sp; /* irq stack pointer */
unsigned long start_addr_sp; /* start_addr_stackpointer */
unsigned long reloc_off;
#if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF))
unsigned long tlb_addr;
#endif
const void *fdt_blob; /* Our device tree, NULL if none */
void **jt; /* jump table */
char env_buf[32]; /* buffer for getenv() before reloc. */
} gd_t;
刚开始的时候,没有考虑到CONFIG_ARM
,计算出来的值与实际对不上,后来仔细搜索发现是定义了的
# grep -nR "CONFIG_ARM" ./
./arch/arm/config.mk:34:PLATFORM_CPPFLAGS += -DCONFIG_ARM -D__ARM__
./include/autoconf.mk:126:CONFIG_ARM=y
这里理论计算的就是22*4+32=120===+15&-15=128
最终sp=0x30000000+0x1000-128=0x30000F80
与汇编结果是一致的
接着我们仔细看下这个来自内核的宏DEFINE
,它是嵌入汇编,实际上他是编译不了的,因为没有->
指令,实际上,这个asm-offsets.c文件根本不是用来运行的,只是在编译的时候,用它生成一个asm-offsets.s文件,然后Kbuild会处理这个asm-offsets.s文件,生成asm-offsets.h文件。这个头文件最终被汇编文件引用,其中定义的变量最终得到应用。
如果要在自己的内核模块中使用这些变量,引用这个头文件即可#include <asm/asm-offsets.h>
. 摘自内核黑科技之DEFINE宏
include\linux\kbuild.h
#define DEFINE(sym, val) \
asm volatile("\n->" #sym " %0 " #val : : "i" (val))
后来在编译结果中搜索CONFIG_SYS_INIT_SP_ADDR
./include/generated/generic-asm-offsets.h:10:#define GENERATED_GBL_DATA_SIZE (128) /* (sizeof(struct global_data) + 15) & ~15 */
其实这个是我看了说明之后才去搜索的,这个DEFINE
就是根据lib/asm-offsets.c
生成asm-offsets.h
$ cat asm-offsets.h
#ifndef DO_DEPS_ONLY
#include <generated/generic-asm-offsets.h>
/* #include <generated/asm-offsets.h> */
#endif
最终的宏也就在generated/generic-asm-offsets.h
中了
#define GENERATED_GBL_DATA_SIZE (128) /* (sizeof(struct global_data) + 15) & ~15 */
#define GENERATED_BD_INFO_SIZE (48) /* (sizeof(struct bd_info) + 15) & ~15 */
接下来就是初始化以及内存分布设置
board_init_f
简单的代码解释如下
call_board_init_f:
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
bic sp, sp, #7 /* 8-byte alignment for ABI compliance 清除低3位 */
ldr r0,=0x00000000
bl board_init_f
board_init_f
/*这里指的就是栈顶 调用之前是 ldr sp, =(CONFIG_SYS_INIT_SP_ADDR) bic sp, sp, #7 */
gd = (gd_t *) ((CONFIG_SYS_INIT_SP_ADDR) & ~0x07);
gd->mon_len = _bss_end_ofs; /**bss_end-start 就是整个程序的大小/
/*一些初始化操作*/
*init_sequence()
addr = CONFIG_SYS_SDRAM_BASE + gd->ram_size; //这个ram_size在dram_init 初始化为64M,CONFIG_SYS_SDRAM_BASE 也就是sdram基地址
//addr 就是ram最高地址 0x04000000+0x3000,0000
/* reserve TLB table */ //保留4kb给tlb后向下64kb对齐,也就是消除0xffff 0x3400,0000-0x4000=0x33ffc,0000
addr -= (4096 * 4); // 对齐后就是 0x33ff,0000
/* round down to next 64 kB limit */
addr &= ~(0x10000 - 1);
gd->tlb_addr = addr;
/* //代码段,gd->mon_len=_bss_end_ofs
* reserve memory for U-Boot code, data & bss
* round down to next 4 kB limit
*/
addr -= gd->mon_len;
addr &= ~(4096 - 1);
/*
* reserve memory for malloc() arena
*/
addr_sp = addr - TOTAL_MALLOC_LEN;
/*
* (permanently) allocate a Board Info struct
* and a permanent copy of the "global" data
*/
addr_sp -= sizeof (bd_t);
bd = (bd_t *) addr_sp;
gd->bd = bd;
addr_sp -= sizeof (gd_t);
id = (gd_t *) addr_sp;
/* leave 3 words for abort-stack */
addr_sp -= 12;
/* 8-byte alignment for ABI compliance */
addr_sp &= ~0x07;
gd->relocaddr = addr;
gd->start_addr_sp = addr_sp;
gd->reloc_off = addr - _TEXT_BASE;
memcpy(id, (void *)gd, sizeof(gd_t));
/*这里最后进行重定位代码 */
relocate_code(addr_sp, id, addr);
relocate_code:
mov r4, r0 /* save addr_sp */
mov r5, r1 /* save addr of gd */
mov r6, r2 /* save addr of destination */
int dram_init(void)
{
/* dram_init must store complete ramsize in gd->ram_size */
gd->ram_size = PHYS_SDRAM_1_SIZE; //64M
return 0;
}
最后的分配内存如下
重定位
uboot的链接地址是0,那么它刚开始在nor上运行是能够读取指令运行的,但是他的变量怎么办呢?
比如我有一个变量在0x100
,需要修改,nor上的数据并不能像内存一样修改?那么怎么解决呢?
这里我们假设想要在sdram上运行,那么我们搬运到0x3200,0000
上去,那么我们不仅需要搬运代码,还要修改代码也就是说将变量0x100
变为0x3200,0100
那么我们怎么知道旧变量的地址0x100
,这里就是在链接的时候加入pie
选项,会有新的段生成,可以看下lds
文件
.rel.dyn : {
__rel_dyn_start = .;
*(.rel*)
__rel_dyn_end = .;
}
.dynsym : {
__dynsym_start = .;
*(.dynsym)
}
代码段重定位实现
代码重定位是在board_init_f
中调用的
// addr_sp 最后的sp
// id gd结构的位置
// addr 重定位的位置
relocate_code(addr_sp, id, addr)
我们来计算下实际的代码段加bss段的大小
.globl _bss_start_ofs
_bss_start_ofs:
.word __bss_start - _start
.globl _bss_end_ofs
_bss_end_ofs:
.word __bss_end__ - _start
.globl _end_ofs
_end_ofs:
.word _end - _start
// 查看具体的反汇编
00000040 <_TEXT_BASE>:
40: 00000000 .word 0x00000000
00000044 <_bss_start_ofs>:
44: 0006b568 .word 0x0006b568
00000048 <_bss_end_ofs>:
48: 000ae4e0 .word 0x000ae4e0
0000004c <_end_ofs>:
4c: 000736d8 .word 0x000736d8
可以看到整个的大小是_bss_end_ofs=0x000ae4e0
,我们计算下
gd->relocaddr = addr=0x33ff,0000-0x000ae4e0=33F41B20
4k对齐 &~(4096-1)=fff
=33F4,1000
具体的汇编如下
.globl relocate_code
relocate_code:
mov r4, r0 /* save addr_sp */
mov r5, r1 /* save addr of gd */
mov r6, r2 /* save addr of destination */
/*这里设置新的sp*/
/* Set up the stack */
stack_setup:
mov sp, r4
adr r0, _start
cmp r0, r6
beq clear_bss /* skip relocation */
mov r1, r6 /* r1 <- scratch for copy_loop */
ldr r3, _bss_start_ofs
add r2, r0, r3 /* r2 <- source end address */
/*支持nor的复制,不支持nand的*/
copy_loop:
ldmia r0!, {r9-r10} /* copy from source address [r0] */
stmia r1!, {r9-r10} /* copy to target address [r1] */
cmp r0, r2 /* until source end address [r2] */
blo copy_loop
变量地址修改
程序的链接地址是0,访问全局变量、静态变量、调用函数时是使"基于0地址编译得到的地址
现在把程序复制到了SDRAM,需要修改代码,把"基于0地址编译得到的地址"改为新地址
程序里有些地址在链接时不能确定,要到运行前才能确定:fixabs
我们先来看下全局变量是怎么在汇编中使用的?查看文档全局变量反汇编与重定位.md,在文档中已经知道要怎么做了, 接下去看这个汇编的实现即可,uboot总结来说有两种情况,我只了解第一种情况
从.rel.dyn 段中依次获得要修改的变量地址
如果下一个值Y是0x17
*(adr+offset)=*(adr+offset)+offset
如果下一个值Y的低8位是0x02,这里加的是绝对地址,和自身存储的地址值无关
*(adr+offset)=*[Y>>4+段dynsym_r10+4]+offset
-
正常的我们全局变量,指针的处理
-
某个lable存的值是个地址,他是一个绝对的偏移,与当前的位置无关,这个老师说是动态链接的时候是需要这样的,没有了解动态链接,暂时不去深究.
从代码的意思来说,就是该地址是固定的,它是一个确定的位置
*[Y>>4+段dynsym_r10+4]
这个值就是表格里面死的值
比如我们代码偏移了offset=20
,有两个地址单元【4】=14,【5】=15
- 假设都为0x17标记,则
【24】=14+20=34,【25】=15+20=35
【5】
为0x02
标记,则【24】=14+20=34,【25】=固定的值+20
在代码上体现是如下
/*
1. 计算偏移地址
2. 获得特殊段的 _dynsym_start_ofs _rel_dyn_start_ofs 位置
*/
/*
* fix .rel.dyn relocations
*/
ldr r0, _TEXT_BASE /* r0 <- Text base */
sub r9, r6, r0 /* r9 <- relocation offset */
ldr r10, _dynsym_start_ofs /* r10 <- sym table ofs */
add r10, r10, r0 /* r10 <- sym table in FLASH */
ldr r2, _rel_dyn_start_ofs /* r2 <- rel dyn start ofs */
add r2, r2, r0 /* r2 <- rel dyn start in FLASH */
ldr r3, _rel_dyn_end_ofs /* r3 <- rel dyn end ofs */
add r3, r3, r0 /* r3 <- rel dyn end in FLASH */
/*
从.rel.dyn 段中依次获得要修改的变量地址
如果下一个值Y是0x17
*(adr+offset)=*(adr+offset)+offset
如果下一个值Y的低8位是0x02,这里加的是绝对地址,和自身存储的地址值无关
*(adr+offset)=*[Y>>4+段dynsym_r10+4]+offset
*/
fixloop:
ldr r0, [r2] /* r0 <- location to fix up, IN FLASH! */
add r0, r0, r9 /* r0 <- location to fix up in RAM */
ldr r1, [r2, #4]
and r7, r1, #0xff
cmp r7, #23 /* relative fixup? */
beq fixrel
cmp r7, #2 /* absolute fixup? */
beq fixabs
/* ignore unknown type of fixup */
b fixnext
fixabs:
/* absolute fix: set location to (offset) symbol value */
mov r1, r1, LSR #4 /* r1 <- symbol index in .dynsym */
add r1, r10, r1 /* r1 <- address of symbol in table */
ldr r1, [r1, #4] /* r1 <- symbol value */
add r1, r1, r9 /* r1 <- relocated sym addr */
b fixnext
fixrel:
/* relative fix: increase location by offset */
ldr r1, [r0]
add r1, r1, r9
fixnext:
str r1, [r0]
add r2, r2, #8 /* each rel.dyn entry is 8 bytes */
cmp r2, r3
blo fixloop
进一步每行代码分析如下
调用之前的值
mov r4, r0 /* save addr_sp */
mov r5, r1 /* save addr of gd */
mov r6, r2 /* save addr of destination */
r0 链接地址这里是0
r6 目标地址
r9 这里就是flash与sdram的偏移地址了
/*
* fix .rel.dyn relocations
*/
ldr r0, _TEXT_BASE /* r0 <- Text base */
sub r9, r6, r0 /* r9 <- relocation offset */
-----------------------------------------
r0 链接地址这里是0
r6 目标地址
r9 这里就是flash与sdram的偏移地址了
--------------------------------------------
ldr r10, _dynsym_start_ofs /* r10 <- sym table ofs */
add r10, r10, r0 /* r10 <- sym table in FLASH */
-----------------------------------------------
_dynsym_start_ofs:
.word __dynsym_start - _start
r10 就是 __dynsym_start 在flash 的实际地址
-----------------------------------------------
ldr r2, _rel_dyn_start_ofs /* r2 <- rel dyn start ofs */
add r2, r2, r0 /* r2 <- rel dyn start in FLASH */
-----------------------------------------------
_rel_dyn_start_ofs:
.word __rel_dyn_start - _start
r2 就是 __rel_dyn_start 在flash 的实际地址
-----------------------------------------------
ldr r3, _rel_dyn_end_ofs /* r3 <- rel dyn end ofs */
add r3, r3, r0 /* r3 <- rel dyn end in FLASH */
-----------------------------------------------
_rel_dyn_end_ofs:
.word __rel_dyn_end - _start
r3 就是 __rel_dyn_end 在flash 的实际地址
-----------------------------------------------
-----------------------------------------------
总结来说就是先确定了具体在flash上的地址
.rel.dyn :
{
__rel_dyn_start = .; ---------r2
*(.rel*)
__rel_dyn_end = .; ---------r3
}
.dynsym :
{
__dynsym_start = .; ----------r10
*(.dynsym)
}
0006b568 <__rel_dyn_start>: //接下去用A标志这里 r2
6b568: 00000020 .word 0x00000020
6b56c: 00000017 .word 0x00000017
6b570: 00000024 .word 0x00000024
6b574: 00000017 .word 0x00000017
6b578: 00000028 .word 0x00000028
6b57c: 00000017 .word 0x00000017
..................................................end=r3
00073608 <__dynsym_start>: //接下去用B标志这里 r10
...
73624: 00010003 .word 0x00010003
73628: 00000000 .word 0x00000000
7362c: 00068de4 .word 0x00068de4
73630: 00000000 .word 0x00000000
73634: 00050003 .word 0x00050003
73638: 00000049 .word 0x00000049
7363c: 0006b568 .word 0x0006b568
-----------------------------------------------
这里的 r2 可以先理解为是nor上的地址 也就是加载地址
fixloop:
// r2 表示当前从 A 取址的地址
ldr r0, [r2] /* r0 <- location to fix up, IN FLASH! */
// 从A表示的地址 取出具体的值
add r0, r0, r9 /* r0 <- location to fix up in RAM */
// 将其值加上offset
ldr r1, [r2, #4]
and r7, r1, #0xff
cmp r7, #23 /* relative fixup? */
//取出下一个A的值,如果是0x17 跳转到 fixrel
// 如果是0x02 跳转到 fixabs
//--- r0 是修正过的地址值
//--- r1 是__rel_dyn_start下一个的值
beq fixrel
cmp r7, #2 /* absolute fixup? */
beq fixabs
/* ignore unknown type of fixup */
b fixnext
fixabs:
//到这里的时候 从A取出来的值已经加上偏移了
/* absolute fix: set location to (offset) symbol value */
mov r1, r1, LSR #4 /* r1 <- symbol index in .dynsym */
// r1 右移4位 这里应该是后四位无效用来表示 #2
// 猜测 r1 这里表示的是 B的序号 也就是第几个了
add r1, r10, r1 /* r1 <- address of symbol in table */
ldr r1, [r1, #4] /* r1 <- symbol value */
add r1, r1, r9 /* r1 <- relocated sym addr */
b fixnext
fixrel:
//到这里的时候 从A取出来的值r0已经加上偏移了
/* relative fix: increase location by offset */
ldr r1, [r0]
// 从重定位后的ram中取出值.老师的笔记这里写错了哦 #########################
add r1, r1, r9
// 将这个值加上offset r1
fixnext:
str r1, [r0]
add r2, r2, #8 /* each rel.dyn entry is 8 bytes */
cmp r2, r3
blo fixloop