程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

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-30I*B1
2024-01-28*兴20
2024-02-01QYing20
2024-02-11*督6
2024-02-18一*x1
2024-02-20c*l18.88
2024-01-01*I5
2024-04-08*程150
2024-04-18*超20
2024-04-26.*V30
2024-05-08D*W5
2024-05-29*辉20
2024-05-30*雄10
2024-06-08*:10
2024-06-23小狮子666
2024-06-28*s6.66
2024-06-29*炼1
2024-06-30*!1
2024-07-08*方20
2024-07-18A*16.66
2024-07-31*北12
2024-08-13*基1
2024-08-23n*s2
2024-09-02*源50
2024-09-04*J2
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-26B*h10
2024-09-3010
2024-10-02M*i1
2024-10-14*朋10
2024-10-22*海10
2024-10-23*南10
2024-10-26*节6.66
2024-10-27*o5
2024-10-28W*F6.66
2024-10-29R*n6.66
2024-11-02*球6
2024-11-021*鑫6.66
2024-11-25*沙5
2024-11-29C*n2.88
posted @   大奥特曼打小怪兽  阅读(1311)  评论(1编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
如果有任何技术小问题,欢迎大家交流沟通,共同进步

公告 & 打赏

>>

欢迎打赏支持我 ^_^

最新公告

程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)。

了解更多

点击右上角即可分享
微信分享提示