• 1、前言
  • 2、uboot段相关变量
  • 3、relocate_code函数
  • 4、relocate_vectors函数
  • 5、小结

1、前言

在前面的文章《Uboot启动流程分析(四)》,链接如下:

原文链接

已经对board_init_f() 函数作出了简单的分析,该函数对一些早期的外设进行了初始化,例如调试串口,并填充了gd_t结构体中的成员变量,最主要的是对整个DRAM的内存进行了分配,以便uboot的重定位,接下来,先回顾一下_main函数的大概流程,如下:

_main|board_init_f_alloc_reserve-->reserve gd and early malloc area|board_init_f_init_reserve-->initialize global data|board_init_f-->initialize ddr,timer...,and fill gd_t|relocate_code-->relocate uboot code|relocate_vectors-->relocate vectors|board_init_r-->calling board_init_r

在_main函数中,调用完了board_init_f()函数后,将DRAM的内存分配好,填充gd_t结构体成员变量,接下来,就是调用relocate_code()函数重定位uboot代码,调用relocate_vectors()函数重定位中断向量表,本篇文章将简单分析uboot的大概重定位过程。

2、uboot段相关变量

在分析relocate_code函数之前,先来总结一下相关的uboot段相关变量,这些段的地址在uboot代码重定位的时候需要用到,将uboot源码进行编译后,会在源码根目录生成u-boot.lds链接文件和u-boot.map内存映射文件,通过这两个文件,可以寻找到uboot段的地址,一些重要的段地址如下表格所示:

在上面寻找到的变量值中,除了_start和__image_copy_start值不会该变,当修改了uboot的源码或改变了编译条件,其他的变量都可能会发生改变,因此分析时,一定要以实际编译时进行uboot分析。

3、relocate_code函数

在调用relocate_code函数之前的分析,可以参考《文章Uboot启动流程分析(二)》,链接如下所示:

原文链接

也就是_main函数执行的第二部分,relocate_code函数会传入一个参数,该参数为gd->relocaddr,也就是uboot重定位的目的地址。

接下来,对relocate_code函数进行分析,该函数用于重定位uboot,函数的定义在下面的汇编文件中:

uboot/arch/arm/lib/relocate.S

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 */ //r1保存源image开始地址//r0 = gd->relocaddr,r4 = r0 - r1 = 0x8ff3b000 - 0x87800000 = 0x873b000subs    r4, r0, r1 /* r4 <- relocation offset */ //r4 = gd->reloc_off,保存偏移地址beq    relocate_done    /* skip relocation */ //如果r0和r1相等,则不需要uboot重定位ldr    r2, =__image_copy_end  /* r2 <- SRC &__image_copy_end */  //r2保存源image结束地址copy_loop:ldmia    r1!, {r10-r11}  /* copy from source address [r1] */ //拷贝uboot代码到r10和r11stmia    r0!, {r10-r11}  /* copy to  target address [r0] */ //将uboot代码写到目的地址cmp    r1, r2     /* until source end address [r2] */ //判断uboot是否拷贝完成blo    copy_loop  //循环拷贝/** fix .rel.dyn relocations //修复.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}        /* (r0,r1) <- (SRC location,fixup) */and    r1, r1, #0xffcmp    r1, #23            /* relative fixup? */bne    fixnext/* relative fix: increase location by offset */add    r0, r0, r4ldr    r1, [r0]add    r1, r1, r4str    r1, [r0]
fixnext:cmp    r2, r3blo    fixlooprelocate_done:ENDPROC(relocate_code)

函数传入的参数为r0 = 0x8ff3b000,uboot重定位的目的地址,函数进来后,将__image_copy_start的数值赋值给r1,也就是uboot复制开始地址,从表格可以知道r1 = 0x87800000,接下来进行一个减法操作,此时r4将保存着uboot的偏移量,也就是r4 = gd->reloc_off。

接下来会进行一个判断,判断uboot重定位的目的地址是否和uboot源地址是否相等,如果相等的话,表示不需要重定位,跳到relocate_done处,继续运行,如果不相等的话,则是需要进行重定位。

将__image_copy_end的值赋值给r2,也就是uboot复制结束的地址,从表格中可以知道,此时r2 = 0x87868960,接下来,开始进行uboot代码的拷贝,进入到copy_loop循环,从r1,也就是__image_copy_start开始,读取uboot代码到r10和r11寄存器,一次就拷贝两个32位的数据,r1自动更新,保存下一个需要拷贝的地址,然后将r10和r11里面的数据,写到r0保存的地址,也就是uboot重定位的目的地址,r0自动更新。

接下来,比较r1和r2是否相等,也就是判断uboot代码是否拷贝完成,如果没完成的话,跳转到copy_loop处,继续循环,直到uboot拷贝完成。

函数relocate_code()的第二部分是修复.rel.dyn的定位问题,.rel.dyn段存放了.text段中需要重定位地址的集合,uboot重定位就是uboot将自身拷贝到DRAM的另外一个地址继续运行(DRAM的高地址),一个可执行的bin文件,它的链接地址和运行地址一定要相等,也就是链接到哪个地址,运行之前就需要拷贝到哪个地址中进行运行,在前面的重定位后,运行地址和链接地址就不一样了,这样在进行寻址就会出问题,对.rel.dyn的定位问题进行修复,就是为了解决该问题。

下面,使用一个简单的例子进行分析:

先在新适配的板级文件中添加测试代码,文件如下:

uboot/board/freescale/mx6ul_comp6ul_nand/mx6ul_comp6ul_nand.c

添加的内容如下:

static int rel_a = 0;void rel_test(void)
{rel_a = 100;printf("rel_test\n");
}/* 在board_init()函数中调用rel_test()函数 */
int board_init(void)
{......rel_test();return 0;
}

接下来对uboot源码重新编译,并将u-boot文件进行反汇编分析,使用命令如下:

$ cd uboot
$ make
$ arm-linux-gnueabihf-objdump -D -m arm u-boot > u-boot.dis

然后,打开u-boot.dis文件,并在文件中,找到rel_a变量、rel_test函数和board_init函数相关的汇编代码,如下所示:

/* rel_test寻址分析 */
8780424c <rel_test>:
8780424c:    e59f300c     ldr    r3, [pc, #12]    ; 87804260 <rel_test+0x14>
87804250:    e3a02064     mov    r2, #100    ; 0x64
87804254:    e59f0008     ldr    r0, [pc, #8]    ; 87804264 <rel_test+0x18>
87804258:    e5832000     str    r2, [r3]
8780425c:    ea00f1a9     b    87840908 <printf>
87804260:    87868994             ; <UNDEFINED> instruction: 0x87868994
87804264:    878494ce     strhi    r9, [r4, lr, asr #9]
...
...
878043e4:    e59f2038     ldr    r2, [pc, #56]    ; 87804424 <board_init+0x1bc>
878043e8:    e5923068     ldr    r3, [r2, #104]    ; 0x68
878043ec:    e3833030     orr    r3, r3, #48    ; 0x30
878043f0:    e5823068     str    r3, [r2, #104]    ; 0x68
878043f4:    ebffff94     bl    8780424c <rel_test>
...
...
87868994 <rel_a>:
87868994:    00000000     andeq    r0, r0, r0

在0x878043f4处,也就是board_init()函数中调用了rel_test()函数,在汇编代码中,可以看到是使用了bl指令,而bl指令是相对寻址的(pc + offset),为位置无关指令,因此,可以知道,uboot中的函数调用时与绝对位置无关的。

接下来,分析rel_test()函数对全局变量rel_a的调用过程,在0x8780424c处开始,先设置r3的值为pc + 12地址处的值,由于ARM流水线的原因,当前pc的值为当前的地址加8,所以pc = 0x8780424c + 8 = 0x87804254,因此r3 = pc + 12 = ‬0x87804254 + 12 = 0x87804260,从反汇编的代码中可以看到0x87804260处的值为0x87868994,所以,最终r3的值为0x87868994,而且从反汇编代码中可以看到0x87868994就是变量rel_a的地址,rel_test()函数继续执行,将100赋值到r2寄存器,然后通过str指令,将r2的值写到r3的地址处,也就是将0x87868994地址处的值赋值100,就是函数中调用的rel_a = 100;语句。

从上面的分析,可以知道rel_a = 100;的执行过程为:

  • 在函数rel_test()的末尾处有一个地址为0x87804260的内存空间,此内存空间中保存着变量rel_a的地址;
  • 函数rel_test()想要访问变量rel_a,首先需要访问0x87804260内存空间获取变量rel_a的地址,而访问0x87804260是通过偏移来访问的,与位置无关的操作;
  • 通过访问0x87804260获取变量rel_a的地址,然后对变量rel_a进行赋值操作;
  • 函数rel_test()对变量rel_a的访问并没有直接进行,而是通过一个偏移地址0x87804260,找到变量rel_a真正的地址,然后才对其操作,偏移地址的专业术语为Label。

从分析中,可以知道,该偏移地址就是uboot重定位运行是否会出错的原因,在上面可以知道,uboot重定位的偏移量为0x8ff3b000,将上面给出的反汇编代码进行重定位后,也就是加上地址偏移量0x873b000后,如下:

/* rel_test寻址分析(重定位后) */
8ff3f24c‬ <rel_test>:
8ff3f24c:    e59f300c     ldr    r3, [pc, #12]
8ff3f250:    e3a02064     mov    r2, #100    ; 0x64
8ff3f254:    e59f0008     ldr    r0, [pc, #8]
8ff3f258:    e5832000     str    r2, [r3]
8ff3f25c:    ea00f1a9     b    87840908 <printf>
8ff3f260:    87868994             ; <UNDEFINED> instruction: 0x87868994
8ff3f264:    878494ce     strhi    r9, [r4, lr, asr #9]
...
...
8ff3f3e4:    e59f2038     ldr    r2, [pc, #56]
8ff3f3e8:    e5923068     ldr    r3, [r2, #104]    ; 0x68
8ff3f3ec:    e3833030     orr    r3, r3, #48    ; 0x30
8ff3f3f0:    e5823068     str    r3, [r2, #104]    ; 0x68
8ff3f3f4:    ebffff94     bl    8780424c <rel_test>
...
...
8ffa3994 <rel_a>:
8ffa3994:    00000000     andeq    r0, r0, r0

函数rel_test()假设调用后对rel_a变量进行访问,分析和上面一样,先通过pc和偏移量找到0x8ff3f260,该地址是重定位后的地址,可此时该地址的值为0x87868994,也就是没有重定位后的rel_a变量的地址,但是从上面可以知道,uboot重定位后,rel_a变量的新地址为0x8ffa3994,因此,我们可以知道,如果uboot重定位后,要想正常访问rel_a变量,必须要将0x8ff3f260地址中的值0x87868994加上偏移量0x873b000,才能保证uboot重定位后,能正常访问到rel_a变量,将.rel.dyn段进行重定位修复,就是为了解决链接地址和运行地址不一致的问题。

在uboot中,对于重定位后链接地址与运行地址不一致的解决办法就是使用位置无关码,在uboot编译使用ld链接的时候使用参数"-pie"可生成与位置无关的可执行程序,使用该参数后,会生成一个.rel.dyn段,uboot则是靠该段去修复重定位后产生的问题的,在uboot的反汇编文件中,有.rel.dyn段代码,如下:

Disassembly of section .rel.dyn:87868988 <__rel_dyn_end-0x91a0>:
87868988:    87800020     strhi    r0, [r0, r0, lsr #32]
8786898c:    00000017     andeq    r0, r0, r7, lsl r0
87868990:    87800024     strhi    r0, [r0, r4, lsr #32]
87868994:    00000017     andeq    r0, r0, r7, lsl r0
...
...
87868f08:    87804260     strhi    r4, [r0, r0, ror #4]
87868f0c:    00000017     andeq    r0, r0, r7, lsl r0
87868f10:    87804264     strhi    r4, [r0, r4, ror #4]
87868f14:    00000017     andeq    r0, r0, r7, lsl r0
...
...

rel.dyn段的格式如下,类似下面的两行就是一组:

87868f08:    87804260     strhi    r4, [r0, r0, ror #4]
87868f0c:    00000017     andeq    r0, r0, r7, lsl r0

也就是两个4字节数据为一组,其中高4字节是Label地址的标识0x17,低4字节就是Label的地址,首先会判断Label地址标识是否正确,也就是判断高4字节是否为0x17,如果是的话,低4字节就是Label地址,在上面给出的两行代码中就是存放rel_a变量地址的Label,0x87868f0c的值为0x17,说明低4字节是Label地址,也就是0x87804260,需要将0x87804260 + offset处的值改为重定位后的变量rel_a地址。

在对.rel.dyn段以及Label的相关概念有一定的了解后,接下来,我们分析函数relocate_code()的第二部分代码,修复.rel.dyn段重定位问题,代码如下:

/** fix .rel.dyn relocations //修复.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}        /* (r0,r1) <- (SRC location,fixup) */and    r1, r1, #0xffcmp    r1, #23            /* relative fixup? */bne    fixnext/* relative fix: increase location by offset */add    r0, r0, r4ldr    r1, [r0]add    r1, r1, r4str    r1, [r0]
fixnext:cmp    r2, r3blo    fixloop

relocate_code()函数第二部分代码调用后,将__rel_dyn_start的值赋给r2,也就是r2中保存着.rel.dyn段的起始地址,然后将__rel_dyn_end的值赋给r3,也就是r3中保存着.rel.dyn段的结束地址。

接下来,进入到fixloop处执行,使用ldmia指令,从.rel.dyn段起始地址开始,每次读取两个4字节数据存放到r0和r1寄存器中,其中r0存放低4字节的数据,也就是Label的地址,r1存放高4字节的数据,也就是Label的标识,然后将r1的值与0xff相与,取r1值的低8位,并将结果保存到r1中,接下来,判断r1中的值是否等于23(0x17),如果r1不等于23的话,也就说明不是描述Label,跳到fixnext处循环执行,直到r2和r3相等,也就是遍历完.rel.dyn段。

如果r1的值等于23(0x17)的话,继续执行,r0保存着Label地址,r4保存着uboot重定位的偏移,因此,r0 + r4就得到了重定位后的Label地址,也就是上面分析的0x87804260 + 0x873b000 = 0x8ff3f260 = r0,此时r0已经保存着重定位后的Label地址,然后使用ldr指令,读取r0中保存的值到r1中,也就是Label地址所保存的rel_a变量的地址,但是此时,该rel_a变量的地址仍然是重定位之前的地址0x87868994,所以,此时r1 = 0x87868994,接下来,使用add指令,将r1中的值加上r4,也就是加上uboot重定位偏移量,所以,此时r1 = 0x87868994 + 0x873b000 = 0x8ffa3994,这不就是前面分析的uboot重定位后的rel_a变量的地址吗?接下来使用str指令,将r1中的值写入到r0保存的地址中,也就是将Label地址中的值设置为0x8ffa3994,就是重定位后rel_a变量的新的地址。

比较r2和r3的值,查看.rel.dyn段重定位修复是否完成,循环直到完成,才能执行完relocate_code()函数。

第二部分执行完成后,就解决了.rel.dyn段的重定位问题,从而解决了uboot重定位后,链接地址和运行地址不一致的问题。

4、relocate_vectors函数

执行完relocate_code函数后,接下来就是执行relocate_vectors函数,该函数用于重定位中断向量表,该函数的定义同样在汇编文件:

uboot/arch/arm/lib/relocate.S

函数的定义如下所示:

ENTRY(relocate_vectors)#ifdef CONFIG_CPU_V7M/** On ARMv7-M we only have to write the new vector address* to VTOR register.*/ldr    r0, [r9, #GD_RELOCADDR]    /* r0 = gd->relocaddr */ldr    r1, =V7M_SCB_BASEstr    r0, [r1, V7M_SCB_VTOR]
#else
#ifdef CONFIG_HAS_VBAR/** If the ARM processor has the security extensions,* use VBAR to relocate the exception vectors.*/ldr    r0, [r9, #GD_RELOCADDR]    /* r0 = gd->relocaddr */mcr     p15, 0, r0, c12, c0, 0  /* Set VBAR */
#else/** Copy the relocated exception vectors to the* correct address* CP15 c1 V bit gives us the location of the vectors:* 0x00000000 or 0xFFFF0000.*/ldr    r0, [r9, #GD_RELOCADDR]    /* r0 = gd->relocaddr */mrc    p15, 0, r2, c1, c0, 0    /* V bit (bit[13]) in CP15 c1 */ands    r2, r2, #(1 << 13)ldreq    r1, =0x00000000        /* If V=0 */ldrne    r1, =0xFFFF0000        /* If V=1 */ldmia    r0!, {r2-r8,r10}stmia    r1!, {r2-r8,r10}ldmia    r0!, {r2-r8,r10}stmia    r1!, {r2-r8,r10}
#endif
#endifbx    lrENDPROC(relocate_vectors)

在该函数中,对于i.mx6ul芯片来说CONFIG_CPU_V7M没有定义,接下来,判断是否定义了CONFIG_HAS_VBAR,表示向量表偏移,在.config文件有该配置,因此,会执行该定义相关的代码,首先是将gd->relocaddr的值赋给r0,也就是r0里面保存重定位后uboot的首地址,中断向量表也是从这个首地址开始存储的,接下来,使用mcr指令,将r0的值写到CP15的VBAR寄存器中,其实就是将新的中断向量表首地址写到VBAR寄存器中,设置中断向量表偏移。

5、小结

本篇文章主要是对Uboot启动流程中,uboot重定位的流程进行简单分析,核心是对relocate_code函数和relocate_vectors函数的分析。

IMX6ULL uboot启动分析(五)相关推荐

  1. Uboot启动分析--start.S启动分析(1)

    总目录 NXP i.MX8M secure boot流程 Uboot链接脚本分析述 Uboot启动分析–start.S启动分析(1) Uboot启动分析–start.S启动分析(2) Uboot启动分 ...

  2. u-boot启动分析02(board_init_f,board_init_r)

    文章目录 1._main函数分析 2. board_init_f() 2.1 初始化gd(global data)全局结构体变量 2.2 一些硬件相关的初始化 2.3 初始化DRAM 3.board_ ...

  3. 2022版u-boot启动分析笔记之一(start.S与lowlevel_init.S)

    u-boot-2022.01-rc4启动分析笔记之一(start.S与lowlevel_init.S U-Boot启动过程概述 从链接脚本u-boot.lds说起 start.S start.S从re ...

  4. (三) u-boot 启动分析_第一阶段

    参考内容点此跳转 本文重点在于分析 uboot 启动流程以及 uboot 自身的细节,比如栈空间的划分.如何设置 tag .如何添加一个自定义命令等.但是不涉及基本的硬件驱动的分析,比如内存初始化.时 ...

  5. AM335x Beaglebone black 蚂蚁矿机L3+控制板 u-Boot 启动分析

    AM335x 可以通过控制上电是sysboot 引脚的电平选择启动顺序,官方的beaglebone black 电路如下: 矿板的SYSBOOT[4:0] = 10011,默认启动顺序如下: 默认是从 ...

  6. 海思(Hi3531d)uboot启动分析

    最近在学习和研究海思Hi3531d芯片的uboot启动过程,看到"海思(Hi3521a)uboot详细分析"和3531有相通之处,谨以此记录分析过程,方便日后查看,具体请查看htt ...

  7. OK6410开发板Uboot学习总结----(一)Uboot启动分析

    OK6410开发板的Uboot是在1.1.6版本上进行移植的,为了便于分析,创建一个Source Insight工程,把源码下board目录里跟samsung相关的文件.cpu目录下s3c64xx文件 ...

  8. 君正Zeratul开发(2)——uboot启动分析

    前言    boot启动一般分为两个阶段,君正设备的第一阶段uboot spl 程序没有开源,用户编译的是第二阶段的boot,最后将两个阶段的boot合并到一起,写入到boot分区中去,boot分区如 ...

  9. DM365 u-boot 启动分析

    第一阶段:汇编语言启动 先看u-boot/board/davinci/dm365_ipnc/下的文件. u-boot.lds OUTPUT_FORMAT("elf32-littlearm&q ...

  10. u-boot简单学习笔记(三)——AR9331 uboot启动分析

    1.最开始系统上电后 ENTRY(_start)程序入口点是 _start  由board/ap121/u-boot.lds引导 2._start: cpu/mips/start.S 是第一个源程序文 ...

最新文章

  1. mybatis学习笔记(7)-输出映射
  2. 数字信号处理第一章 离散时间信号与系统
  3. python 菜鸟入门
  4. Python趣味编程---Python也会讲笑话
  5. jzoj6451-[2020.01.19NOIP提高组]不幸运数字【记忆化搜索,数位dp,高精度】
  6. Android IPC系列(一):AIDL使用详解
  7. VS2008水晶报表发布部署总结
  8. Python获取主机信息、开机时间和开机时长、当前登陆用户
  9. 在使用FireFox浏览器时,经常打开新标签,页面总是不断自动刷新,解决办法
  10. 课后作业1:字串加密
  11. App后台开发运维——架构设计
  12. 用acdsee制作html,ACDSee软件图像处理操作实用技巧
  13. ffmpeg中的时间单位以及时间转换函数(av_q2d av_rescale_q)
  14. c语言鸽笼原理,技巧丨弄懂抽屉原理
  15. jQuery下载及基本使用
  16. DBT乳腺切片投影及重建(MATLAB版)
  17. pdf转图片怎么清晰?
  18. vb雅西高速计算机考试,2016年高中信息技术学业水平考试VB程序复习题.doc
  19. Vue脚手架、镜像源下载及使用
  20. 大数据处理——Java

热门文章

  1. python+sklearn实现随机森林模型
  2. python爬虫淘宝评论_Python爬虫,抓取淘宝商品评论内容
  3. 人工智能学习思维导图
  4. 《图解TCPIP》知识学习(1.4):协议由谁规定
  5. 数据结构课程设计实验报告
  6. java的一些课程设计题目_Java课程设计
  7. Realtek显示芯片方案设计 RTD2270 RTD2281 RTD2513 RTD2525 RTD2556 RTD2785 RTD2795T 2796 VGA DVI HDMI DP转LVDS
  8. 华为手表广告营销案例和广告策划案例PPT模板
  9. 计算机组成原理知识体系
  10. VBA实战技巧精粹018:如何汇总数据