# 重定位 ## 启动过程 前面已知,imx6会内部bootRom上电后会从启动设备(SD卡、EMMC等)读取镜像到DDR,并运行,过程如下: ![](media/image-20210810085245106.png) 例如,如果用户的应用程序链接地址为0x80100000 ,makefile会根据链接地址打包出三个镜像文件: ```bash $(TARGET).bin : $(OBJS) $(LD) -Timx6ul.lds -o $(TARGET).elf $^ $(OBJCOPY) -O binary -S $(TARGET).elf $@ $(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis ./tools/mkimage -n ./tools/imximage.cfg.cfgtmp -T imximage -e 0x80100000 -d $(TARGET).bin $(TARGET).imx dd if=/dev/zero of=1k.bin bs=1024 count=1 cat 1k.bin $(TARGET).imx > $(TARGET).img ``` 1. `.bin`:原始的用户程序; 2. `.imx`:开头就是IVT; 通过**100ask_imx6ull_flashing_tool**工具(需设置板子USB启动),可以将`.imx`直接下载到DDR运行或者下载到EMMC、TF卡上。 **注意**:通过USB下载方式,可以烧写程序到EMMC、TF卡上,但是并非“直接烧写”。它的过程如下: 1. 通过USB下载u-boot到内存; 2. 通过USB下载用户程序到内存; 3. 通过USB发送命令运行u-boot; 4. 用u-boot烧写把内存中的用户程序烧写到EMMC、TF卡上。 3. `.img`:开头是1024字节的0值数据,后面才紧跟IVT。 `.img`文件已经在前面加上`Initial Load Region`部分,所以是一个完整的镜像文件,我们可以直接用**win32diskimager**把`.img`文件烧写到SD卡上 ## 段概念 程序的段包括 - 代码段(.text):存放代码指令 - 只读数据段(.rodata):存放有初始值并且const修饰的全局类变量(全局变量或static修饰的局部变量) - 数据段(.data):存放有初始值的全局类变量 - 栈:函数内部定义的局部变量属于栈空间,进入函数的时候从向栈空间申请内存给局部变量,退出时释放局部变量,归还内存空间,**后进先出**,先进入的最后出来,只在Ram区; - 堆:使用malloc 动态分配的变量属于堆空间,只在Ram区。 - 零初始化段(.bss):存放没有初始值或初始值为0的全局类变量注释段(.comment):存放注释 **注意**:bss段和注释段不保存在bin/elf文件中 ![](media/image-20210810091548247.png) ## 链接脚本 在makefile中会指定程序的链接脚本如下 ```bash $(TARGET).bin : $(OBJS) $(LD) -Timx6ul.lds -o $(TARGET).elf $^ ``` `imx6ul.lds`文件分析 ```bash SECTIONS{ . = 0x80100000; . = ALIGN(4); .text : { obj/start.o *(.text) } . = ALIGN(4); .rodata : { *(.rodata*) } . = ALIGN(4); .data : { *(.data) } . = ALIGN(4); __bss_start = .; .bss : { *(.bss) *(COMMON) } __bss_end = .; } ``` ### 清除bss段 之前提到过bin文件中并不会保存bss段的值,因为这些值都是0,保存这些值没有意义并会使得bin文件臃肿。当程序运行涉及到bss段上的数据时,CPU会从bss段对应的内存地址去读取对应的值,为了确保从这段内存地址上读取到的bss段数值为0,在程序运行前需要将这一段内存地址上的数据清零,即清除bss段。如果不清除,那么程序运行起来后,这个值是随机的。 在汇编中清除bss段示例: ``` .text .global _start _start: /* 设置栈 */ ldr sp,=0x80200000 /* 清除bss段 */ bl clean_bss /* 跳转到主函数 */ bl main halt: b halt clean_bss: ldr r1, =__bss_start //将链接脚本变量__bss_start变量保存于r1 ldr r2, =__bss_end //将链接脚本变量__bss_end变量保存于r2 mov r3, #0 clean: strb r3, [r1] //将当前地址下的数据清零 add r1, r1, #1 //将r1内存储的地址+1 cmp r1, r2 //相等:清零操作结束;否则继续执行clean函数清零bss段 bne clean mov pc, lr ``` ## 重定位引入 有些arm芯片(比如S3C2440),上电后会从Nand或者Norflash上先读取4k的代码到DDR中运行,在这4k代码中,完成把Nand或者Norflash上的所有代码拷贝到DDR中,然后跳转到指定的链接地址去运行代码。而imx6ull内部自带的bootRom完成了这部分功能,板子上电后Boot Rom会将映像文件从启动设备(TF卡、eMMC)自动拷贝到DDR3内存上。上述拷贝代码的过程就是重定位。 思考:如果makefile中指定的程序的入口地址和链接文件的地址不一致,如下所示,会出现什么情况? ```bash # 告诉bootRom程序的入口地址为0x8010 0000 ./tools/mkimage -n ./tools/imximage.cfg.cfgtmp -T imximage -e 0x80100000 -d $(TARGET).bin $(TARGET).imx ``` 注意:`imximage.cfg.cfgtmp`文件原始所在位置为uboot的`board/freescale/mx6ullevk`目录下,如下: ```bash ebf_6ull_uboot$ cd ./board/freescale/mx6ullevk/ ebf_6ull_uboot/board/freescale/mx6ullevk$ ls built-in.o imximage.cfg imximage.cfg.cfgtmp imximage_lpddr2.cfg Kconfig MAINTAINERS Makefile mx6ullevk.c mx6ullevk.o mx6ullevk.su plugin.S README ``` ```bash SECTIONS{ . = 0x80101000; # 链接地址为0x8010 1000 和上面的指定的入口地址0x8010 0000不一样 . = ALIGN(4); .text : { obj/start.o *(.text) } ``` 这要分两种情况: ![](media/image-20210810162003452.png) 1. 如果启动文件使用`bl main`相对跳转到main函数,程序还会从DDR3的0x80100000处运行,即a点 - 重定位前:程序正常跑到main函数,之后在main函数里面如果调用`.data`里面的变量实际是访问链接地址上的`.data`段所在的位置即d点位置,那么很明显,d点位置内容不确定,这个时候程序就访问不到想要的数据; - 重定位后:程序正常跑到main函数,之后在main函数里面如果调用`.data`里面的变量实际是访问链接地址上的`.data`段所在的位置即d点位置,而此时d位置的`.data`段我们已经在`bl main`前把整个bin拷贝到c点,所以能正常使用。 2. 如果启动文件使用`ldr pc, =main`绝对跳转到main函数,程序会从链接地址DDR3的0x80101000处运行,即c点。 - 重定位前:c点内容什么都没有,可想而知接下来就是死机。 - 重点位后:c点我们在`.S`启动文件中`ldr pc, =main`执行前,将整个bin文件重新copy到c点,所以程序是能正常运行的。前提是在`ldr pc, =main`前面的代码都是位置无关的代码指令,比如不能使用初始化的全局变量,因为初始化的全局变量位于链接地址d处,拷贝前d处不知道什么内容。 这里值得注意的是,上面重点位是把整个bin文件(Text+rodata+data)段整体拷贝到链接地址处,其实针对imx6ull没有这么做的必要,我们只需保证makefile告诉bootRom的入口地址和程序里面链接文件.lds的链接地址一致即可,bootRom会自动把我们的程序整体拷贝到DDR中。 费这么大劲讲解重定位,是因为有些arm芯片,只会拷贝前4k数据到DDR中,在这4k里面,需要用户自己把对应Text+rodata+data拷贝到指定的链接地址去。故而这里大费周章的将重定位。 ## 重定位`.data`段到芯片内部RAM 汇编代码实现 `.data`段存放初始化全局变量,我们知道RAM的访问速度肯定比DDR3的访问速度快,如果把`.data`段重定位到RAM区,那么访问速度会加快很多,下面就做个重定位`.data`到芯片内部RAM的试验。 参考imx6ull芯片手册得到片内RAM的地址为:0x900000 ~ 0x91FFFF。将.data段重定位后的地址设置为0x900000 ![](media/image-20210810094348984.png) ![](media/image-20210810094639413.png) 重定位后程序在芯片内存中分布如下: ![](media/image-20210810171815637.png) ### 源码分析 参考【04.relocate_data_asm】代码 #### imx6ul.lds ```bash SECTIONS{ . = 0x80100000; . = ALIGN(4); .text : { obj/start.o *(.text) } . = ALIGN(4); .rodata : { *(.rodata) } . = ALIGN(4); data_load_addr = .; .data 0x900000 : AT(data_load_addr) #重定位data段到0x900000 { data_start = . ; *(.data) data_end = . ; } . = ALIGN(4); __bss_start = .; .bss : { *(.bss) *(COMMON) } __bss_end = .; } ``` #### start.S ``` /* ~~~~~~~~~~~~~省略N行~~~~~~~~~~~~~*/ ldr sp, =0x80200000 /* .lds中设置链接地址为0x8010 0000,设置栈顶0x8020 0000 故预留1M的空间 */ /* 重定位data段 */ bl copy_data /* 清除bss段 */ bl clean_bss /* 切记:代码里面必须有bss段(即存在未初始化的全局变量),否则这里只要调用就死机*/ /* 跳转到主函数 */ ldr pc, =main /* 绝对跳转,程序在片内RAM中执行 */ halt: b halt copy_data: /* 重定位data段 */ ldr r1, =data_load_addr /* data段的加载地址 (0x8010....) */ ldr r2, =data_start /* data段重定位地址, 0x900000 */ ldr r3, =data_end /* data段结束地址(重定位后地址 0x90....) */ cpy: ldr r4, [r1] /* 从r1读到r4 */ str r4, [r2] /* r4存放到r2 */ add r1, r1, #4 /* r1+1 */ add r2, r2, #4 /* r2+1 */ cmp r2, r3 /* r2 r3比较 */ bne cpy /* 如果不等则继续拷贝 */ mov pc, lr clean_bss: /* 清除bss段 */ ldr r1, =__bss_start ldr r2, =__bss_end mov r3, #0 clean: str r3, [r1] add r1, r1, #4 cmp r1, r2 bne clean mov pc, lr ``` #### main.c ```c #include "bsp_clk.h" #include "bsp_delay.h" #include "bsp_led_rgb.h" #include "bsp_uart.h" #include "stdio.h" uint32_t a = 0; // bss段 uint32_t b = 1; // data段 uint32_t c; // bss 段 const uint32_t d=1; // rodata 段 int main(void) { clk_enable(); /* 使能所有的时钟 */ rgb_led_init(); /* 初始化led */ uart_init(); /* 初始化串口,波特率115200 */ printf("relocate data asm demo\r\n"); while(1) { printf("globle var b=%d addr=0x%x\r\n",b,&b); // 打印data段数据 a的值和 地址 LED_RGB_RED_ON(); LED_RGB_BLUE_OFF(); LED_RGB_GREE_OFF(); delay(1000); LED_RGB_BLUE_ON(); LED_RGB_RED_OFF(); LED_RGB_GREE_OFF(); delay(1000); LED_RGB_GREE_ON(); LED_RGB_BLUE_OFF(); LED_RGB_RED_OFF(); delay(1000); } return 0; } ``` #### 实验现象 如果我们屏蔽掉start.S文件的`bl copy_data`代码,即链接文件指定了data段的链接地址为0x900000,但是我们并没有拷贝data段到该地址,所以0x900000地址数据肯定是随机的,所以代码运行效果如下: ![](media/image-20210814152525007.png) 如果不屏蔽`bl copy_data`,那么程序会在跳转到main前,把data段数据拷贝到0x900000处,所以,运行结果是正常的如下: ![](media/image-20210814152607211.png) ## 重定位.data段到芯片内部RAM C代码实现 汇编传入参数 上面的清除bss段和拷贝data段都是汇编语言实现,现在我们实现C语言版本,代码参考【05.relocate_data_c】 ### 源码分析 #### start.S ``` /* ~~~~~~~~~~~~~省略N行~~~~~~~~~~~~~*/ /* 重定位data段 */ ldr r0, =data_load_addr /* data段的加载地址 (0x8010....) */ ldr r1, =data_start /* data段重定位地址, 0x900000 */ ldr r2, =data_end /* data段结束地址(重定位后地址 0x90....) */ sub r2, r2, r1 /* r2的值为data段的长度 */ bl copy_data /* 跳转到函数copy_data并将r0,r1,r2作为函数参数传入 */ /* 清除bss段 */ ldr r0, =__bss_start ldr r1, =__bss_end bl clean_bss /* 跳转到函数clean_bss并将r0, r1作为函数参数传入*/ /* 跳转到主函数 */ ldr pc, =main /* 绝对跳转,程序在片内RAM中执行 */ halt: b halt ``` #### init.c ``` /********************************************************************** * 函数名称: copy_data * 功能描述: 将[地址src]上的的数据拷贝到[地址dest] ,拷贝的数据长度为len * 输入参数: src, dest, len * 输出参数: 无 * 返 回 值: 无 ***********************************************************************/ void copy_data (volatile unsigned int *src, volatile unsigned int *dest, unsigned int len) /* src, dest, len */ { unsigned int i = 0; while (i < len) { *dest++ = *src++; i += 4; } } /********************************************************************** * 函数名称: clean_bss * 功能描述: 清除.bss段上的数据 * 输入参数: start, end * 输出参数: 无 * 返 回 值: 无 ***********************************************************************/ void clean_bss (volatile unsigned int *start, volatile unsigned int *end) /* start, end */ { while (start <= end) { *start++ = 0; } } ``` ### 实验现象 ![](media/image-20210814152607211.png) ## 重定位.data段到芯片内部RAM C代码实现改进 直接引用链接脚本变量 上面我们在清除bss段和copy数据段的时候都是在汇编中传入参数,现在我们实现直接在C语言里面调用链接文件变量。参考代码【06.relocate_data_c_fix】 ### 源码分析 #### start.S ``` .text //代码段 .align 4 //设置字节对齐 .global _start //定义全局变量 _start: //程序的开始 b reset //跳转到reset标号处 reset: mrc p15, 0, r0, c1, c0, 0 /*读取CP15系统控制寄存器 */ bic r0, r0, #(0x1 << 12) /* 清除第12位(I位)禁用 I Cache */ bic r0, r0, #(0x1 << 2) /* 清除第 2位(C位)禁用 D Cache */ bic r0, r0, #0x2 /* 清除第 1位(A位)禁止严格对齐 */ bic r0, r0, #(0x1 << 11) /* 清除第11位(Z位)分支预测 */ bic r0, r0, #0x1 /* 清除第 0位(M位)禁用 MMU */ mcr p15, 0, r0, c1, c0, 0 /* 将修改后的值写回CP15寄存器 */ ldr sp, =0x80200000 /* .lds中设置链接地址为0x8010 0000,设置栈顶0x8020 0000 故预留1M的空间 */ bl copy_data /* 跳转到C函数copy_data并将r0,r1,r2作为函数参数传入 */ bl clean_bss /* 跳转到C函数clean_bss并将r0, r1作为函数参数传入*/ /* 跳转到主函数 */ ldr pc, =main /* 绝对跳转,程序在片内RAM中执行 */ halt: b halt ``` #### init.c ```c /********************************************************************** * 函数名称: copy_data * 功能描述: 将.data段数据从DDR3内存重定位到片内RAM * 输入参数: 无 * 输出参数: 无 * 返 回 值: 无 ***********************************************************************/ void copy_data (void) { /* 从链接脚本中获得参数 data_load_addr, data_start, data_end */ extern int data_load_addr, data_start, data_end; volatile unsigned int *dest = (volatile unsigned int *)&data_start; volatile unsigned int *end = (volatile unsigned int *)&data_end; volatile unsigned int *src = (volatile unsigned int *)&data_load_addr; /* 重定位数据 */ while (dest < end) { *dest++ = *src++; } } /********************************************************************** * 函数名称: clean_bss * 功能描述: 清除.bss段 * 输入参数: start, end * 输出参数: 无 * 返 回 值: 无 ***********************************************************************/ void clean_bss(void) { /* 从lds文件中获得 __bss_start, __bss_end */ extern int __bss_end, __bss_start; volatile unsigned int *start = (volatile unsigned int *)&__bss_start; volatile unsigned int *end = (volatile unsigned int *)&__bss_end; while (start <= end) { *start++ = 0; } } ``` ## 重定位所有段到芯片内部RAM C代码实现 下面实现把所有段都重定位到内部ram中,参考代码【07.relocate_data_all】 ### 代码分析 在【06.relocate_data_c_fix】基础上我们修改链接文件和`init.c`文件 #### imx6ull.lds ```c SECTIONS{ _load_addr= 0x80100000; . = 0x900000; . = ALIGN(4); .text : { obj/start.o *(.text) } . = ALIGN(4); .rodata : { *(.rodata) } . = ALIGN(4); .data : { *(.data) } . = ALIGN(4); __bss_start = .; .bss : { *(.bss) *(COMMON) } __bss_end = .; } ``` #### init.c ```c /********************************************************************** * 函数名称: copy_data * 功能描述: 将整个程序(.text, .rodata, .data)从DDR3重定位到片内RAM * 输入参数: 无 * 输出参数: 无 * 返 回 值: ***********************************************************************/ void copy_data (void) { /* 从链接脚本中获得参数 _start, __bss_start, */ extern int _load_addr, _start, __bss_start; volatile unsigned int *dest = (volatile unsigned int *)&_start; //_start = 0x900000 volatile unsigned int *end = (volatile unsigned int *)&__bss_start; //__bss_start = 0x9xxxxx volatile unsigned int *src = (volatile unsigned int *)&_load_addr; //_load_addr = 0x80100000 /* 重定位数据 */ while (dest < end) { *dest++ = *src++; } } /********************************************************************** * 函数名称: clean_bss * 功能描述: 清除.bss段 * 输入参数: start, end * 输出参数: 无 * 返 回 值: 无 ***********************************************************************/ void clean_bss(void) { /* 从lds文件中获得 __bss_start, __bss_end */ extern int __bss_end, __bss_start; volatile unsigned int *start = (volatile unsigned int *)&__bss_start; volatile unsigned int *end = (volatile unsigned int *)&__bss_end; while (start <= end) { *start++ = 0; } } ``` ### 实验现象 ![](media/image-20210814161911074.png)