Mini2440之uboot移植之源码分析u-boot重定位(三)
目录
所谓的relocation,就是重定位,u-boot运行后会将自身代码拷贝到SDRAM的另一个位置继续运行。
但基于以前的理解,一个完整可运行的bin文件,link时指定的链接地址,load时的加载地址,运行时的运行地址,这3个地址应该是一致的。
relocation后运行地址不同于加载地址,特别是链接地址,ARM的寻址会不会出现问题?
u-boot启动后会计算出一个靠近SDRAM顶端的地址,也就是gd->relocaddr,将自身代码拷贝到该地址,继续运行。
但是这样会有一个问题,relocation后u-boot的运行地址跟其链接地址不一致,compiler会在link时确定了其中全局变量的绝对地址,链接地址 加载地址 运行地址应该一致,这样看来,arm在寻址这些变量时找到的应该是relocation之前的地址,这样relocation就没有意义了!
因此我们在进行u-boot重定位需要包含代码段、rodata、data以及全局变量、函数的重定位。
一、u-boot重定位
1.1 重定位原理
我们C代码编译成汇编后,我们需要思考以下几个问题:
- 我们的函数跳转转为汇编代码后是怎样的;
- 全局变量又是如何寻址的;
实际上函数用b/bl相对跳转,因此代码进行重定位之后函数跳转是没有影响的。但是全局变量使用的是绝对地址,是有影响的。
这里我们以LCD项目为例,来介绍全局变量的寻址原理。
我们定义了const常量为例:
const u8 sunflower_320x240[] = { ... }
LCD项目的链接地址为0x30000000,代码反汇编后,查看只读数据段:
Disassembly of section .rodata: 30005634 <sunflower_320x240-0x18>: 30005634: aeb0d2ce cdpge 2, 11, cr13, cr0, cr14, {6} 30005638: f5c1e3c4 .word 0xf5c1e3c4 3000563c: 4920e0d1 stmdbmi r0!, {r0, r4, r6, r7, sp, lr, pc} 30005640: 564f4c20 .word 0x564f4c20 30005644: 4f592045 svcmi 0x00592045 30005648: 00002055 .word 0x00002055 3000564c <sunflower_320x240>: 3000564c: 73229322 .word 0x73229322 30005650: 73229322 .word 0x73229322 30005654: 93229322 .word 0x93229322 30005658: 93229322 .word 0x93229322 3000565c: 93227322 .word 0x93227322 30005660: 93229322 .word 0x93229322 ...
我们在lcd_test函数使用到了sunflower_320x240全局变量:
void lcd_test() { 300017f0: e92d4800 push {fp, lr} 300017f4: e28db004 add fp, sp, #4 ; 0x4 300017f8: e24dd010 sub sp, sp, #16 ; 0x10 /* 文件必须采用GBK编码 */ lcd_draw_bmp(0,0,LCD_WIDTH,LCD_HEIGHT, sunflower_320x240); 300017fc: e59f3050 ldr r3, [pc, #80] ; 30001854 <lcd_test+0x64> 30001800: e58d3000 str r3, [sp] 30001804: e3a00000 mov r0, #0 ; 0x0 30001808: e3a01000 mov r1, #0 ; 0x0 3000180c: e3a02d05 mov r2, #320 ; 0x140 30001810: e3a030f0 mov r3, #240 ; 0xf0 30001814: ebfffe7c bl 3000120c <lcd_draw_bmp> char *data = "我爱你刘燕 I LOVE YOU "; 30001818: e59f3038 ldr r3, [pc, #56] ; 30001858 <lcd_test+0x68> 3000181c: e50b3008 str r3, [fp, #-8] lcd_draw_char_lib(100, 50, 0xFF00FF, data, strlen(data)); 30001820: e51b0008 ldr r0, [fp, #-8] 30001824: eb0003c8 bl 3000274c <strlen> 30001828: e1a03000 mov r3, r0 3000182c: e58d3000 str r3, [sp] 30001830: e3a00064 mov r0, #100 ; 0x64 30001834: e3a01032 mov r1, #50 ; 0x32 30001838: e3a028ff mov r2, #16711680 ; 0xff0000 3000183c: e28220ff add r2, r2, #255 ; 0xff 30001840: e51b3008 ldr r3, [fp, #-8] 30001844: ebffff83 bl 30001658 <lcd_draw_char_lib> } 30001848: e24bd004 sub sp, fp, #4 ; 0x4 3000184c: e8bd4800 pop {fp, lr} 30001850: e12fff1e bx lr 30001854: 3000564c .word 0x3000564c 30001858: 30005634 .word 0x30005634
我们可以发现:
- 在函数的后面有label,PC+offset找到label;
- label存放全局变量地址;
- 函数后面的label作为text段的一部分;
可以看到[pc,#80]地址处保存sunflower_320x240全局变量的地址0x3000564c。
30001854: 3000564c .word 0x3000564c
我们LCD项目链接地址为0x30000000,我们将代码加载0x30000000地址处,并跳转到0x30000000处运行。
我们在LCD项目初始化代码中将.text、.rodata.、.data段代码从0x30000000重新定位到A,重定位偏移为relocaddr=A-0x30000000,那么地址0x30001854(位于.text段)重定位后为:
30001854+relocaddr: 3000564c .word 0x3000564c => 我们需要将这个地址值+重定位偏移
此时我们会发现地址30001854+relocaddr处保存的sunflower_320x240全局变量的地址仍然是0x3000564c,很显然这个地址不是sunflower_320x240全局变量重定位之后的地址。那么如何解决这个问题呢?
这个问题可以通过重定位.rel.dyn段解决。具体实现思路如下:
在代码编译的时候指定-pie选项,将会生成.rel.dyn段,.rel.dyn段将会保存label的地址,我们在重定位的过程中从.rel.dyn段中找到label的地址:
30006660: 30001854 .word 0x30001854
获取lable地址中的值0x30001854,然后加上relocaddr得到地址30001854+relocaddr,这个地址存放的应该是重定位后sunflower_320x240全局变量的地址。我们取出这个地址的值0x3000564c然后加上偏移地址relocaddr,再写回地址30001854+relocaddr,这样我们就可以实现全局变量的重定位了。
30001854+relocaddr: 3000564c+relocaddr .word 0x3000564c+relocaddr
下面是别人绘制的一张简图,比较生动,我就直接贴过来了:
1.2 u-boot重定位
我们已经分析到了_main(arch/arm/lib/crt0.S)中board_init_f函数的执行,接着继续分析_main。
#if ! defined(CONFIG_SPL_BUILD) /* * Set up intermediate environment (new sp and gd) and call * relocate_code(addr_moni). Trick here is that we'll return * 'here' but relocated. */ ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */ #if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */ mov r3, sp bic r3, r3, #7 mov sp, r3 #else bic sp, sp, #7 /* 8-byte alignment for ABI compliance */ #endif ldr r9, [r9, #GD_BD] /* r9 = gd->bd */ sub r9, r9, #GD_SIZE /* new GD is below bd */ adr lr, here ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */ add lr, lr, r0 #if defined(CONFIG_CPU_V7M) orr lr, #1 /* As required by Thumb-only */ #endif ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */ b relocate_code here: /* * now relocate vectors */ bl relocate_vectors /* Set up final (full) environment */ bl c_runtime_cpu_setup /* we still call old routine here */ #endif
其中GD_START_ADDR_SP 定义如下:
#define GENERATED_GBL_DATA_SIZE 176 /* (sizeof(struct global_data) + 15) & ~15 @ */ #define GENERATED_BD_INFO_SIZE 80 /* (sizeof(struct bd_info) + 15) & ~15 @ */ #define GD_SIZE 168 /* sizeof(struct global_data) @ */ #define GD_BD 0 /* offsetof(struct global_data, bd) @ */ #define GD_RELOCADDR 44 /* offsetof(struct global_data, relocaddr) @ */ #define GD_RELOC_OFF 64 /* offsetof(struct global_data, reloc_off) @ */ #define GD_START_ADDR_SP 60 /* offsetof(struct global_data, start_addr_sp) @ */
以上代码主要做了以下事情:
- 设置栈指针为gd->start_addr_sp,并且8字节对齐;
- 设置r9=gd->bd,在内存图上可以看出,新的gd结构在bd结构的下面紧挨着,所以减去gd的大小就是新的gd起始地址,r9变成了重定位后的新地址的gd结构了;
- 将here标号的地址值(adr取的是相对当前PC的偏移地址)读取到LR中,将重定位偏移值gd->reloc_off加载到R0寄存器中;
- 链接寄存器LR加上偏移值R0后,LR的地址就变成重定位后的地址了;
- 将重定位地址gd->relocaddr加载到R0中,作为参数传给relocate_code;
- 执行重定位,从relocate_code回来后,就直接运行在重定位后的u-boot中了,here标号已经是重定位后那个here标号了;
上面代码主要是用来将u-boot从CONFIG_SYS_TEXT_BASE重定位到gd->relocaddr位置。
具体如图所示:这里为了方便绘制,将内存地址拆分成了两部分,右侧地址存放的是u-boot的运行地址。
当Mini2440从NOR启动时,0x00000000就是板上2MB NOR FLASH实际的起始地址,NOR FLASH中的程序就从这里开始运行。
需要注意的是:u-boot程序默认是从NOR启动的,此时2MB的NOR FLASH足够放下u-boot的所有代码。
二、relocate_code(arch/arm/lib/relocate.S)
2.1.text、.rodata、.data、.rel.dyn重定位(arch/arm/lib/relocate.S)
/* * 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 */ subs r4, r0, r1 /* r4 <- relocation offset */ beq relocate_done /* skip relocation */ ldr r2, =__image_copy_end /* r2 <- SRC &__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
根据u-boot.lds我们绘制各个段在内存中的分布图:
以上代码主要重定位__image_copy_start、__image_copy_end的数据到新的地址:
- r0设置为u-boot重定位后的位置gd->reloacaddr;
- r1设置为__image_copy_start,为u-boot链接起始地址(链接地址需要和运行地址一致,此时链接地址就是目前u-boot运行的起始地址);
- r4设置为u-boot重定位偏移地址gd->reloc_off;
- 如果r4=0,表示r0=r1,不再进行重定位;
- r2设置为__image_copy_end;
- 以r1为起始地址(也就是目前u-boot的起始地址),加载[r1],[r1+4]字到r10,r11;
- 以r0为起始地址(也就是重定位后的的新地址),加载r10,r11的值到[r0],[r0+4]中;
- 比较是否读取到结束地址__image_copy_end,一直循环,直到拷贝结束;
除了代码段外,这里我们重定位了只读数据段和初始化数据段:
亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。
日期 | 姓名 | 金额 |
---|---|---|
2023-09-06 | *源 | 19 |
2023-09-11 | *朝科 | 88 |
2023-09-21 | *号 | 5 |
2023-09-16 | *真 | 60 |
2023-10-26 | *通 | 9.9 |
2023-11-04 | *慎 | 0.66 |
2023-11-24 | *恩 | 0.01 |
2023-12-30 | I*B | 1 |
2024-01-28 | *兴 | 20 |
2024-02-01 | QYing | 20 |
2024-02-11 | *督 | 6 |
2024-02-18 | 一*x | 1 |
2024-02-20 | c*l | 18.88 |
2024-01-01 | *I | 5 |
2024-04-08 | *程 | 150 |
2024-04-18 | *超 | 20 |
2024-04-26 | .*V | 30 |
2024-05-08 | D*W | 5 |
2024-05-29 | *辉 | 20 |
2024-05-30 | *雄 | 10 |
2024-06-08 | *: | 10 |
2024-06-23 | 小狮子 | 666 |
2024-06-28 | *s | 6.66 |
2024-06-29 | *炼 | 1 |
2024-06-30 | *! | 1 |
2024-07-08 | *方 | 20 |
2024-07-18 | A*1 | 6.66 |
2024-07-31 | *北 | 12 |
2024-08-13 | *基 | 1 |
2024-08-23 | n*s | 2 |
2024-09-02 | *源 | 50 |
2024-09-04 | *J | 2 |
2024-09-06 | *强 | 8.8 |
2024-09-09 | *波 | 1 |
2024-09-10 | *口 | 1 |
2024-09-10 | *波 | 1 |
2024-09-12 | *波 | 10 |
2024-09-18 | *明 | 1.68 |
2024-09-26 | B*h | 10 |
2024-09-30 | 岁 | 10 |
2024-10-02 | M*i | 1 |
2024-10-14 | *朋 | 10 |
2024-10-22 | *海 | 10 |
2024-10-23 | *南 | 10 |
2024-10-26 | *节 | 6.66 |
2024-10-27 | *o | 5 |
2024-10-28 | W*F | 6.66 |
2024-10-29 | R*n | 6.66 |
2024-11-02 | *球 | 6 |
2024-11-021 | *鑫 | 6.66 |
2024-11-25 | *沙 | 5 |
2024-11-29 | C*n | 2.88 |

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了