【ARM裸机】 - 重定位
2017-12-30 22:01
288 查看
1、 重定位的目的
对于2440上电后,分两种情况,从nor启动,直接从nor作为0地址,开始在nor中运行。从nand启动,拷贝前4K到片内SRAM中。当代码大于4K的时候,我们需要重定位代码到更大的SDRAM中去运行。从nor运行时,由于nor只可读而不可以修改nor中的内容,就会导致一些全局变量,在代码中无法修改,此时我们可以将全局变量重定位(指定链接地址)到SDRAM中,这样我就可以nor中运行,然后对全局变量进行修改。总结来说:重定位就是代码的存储地址和运行地址不一致,我们需要把代码拷贝到运行地址处。可以重定位所有代码,也可以重定位数据段等。由于代码存在flash中,一般可在链接脚本中加入AT()来指定加载地址,以防止编译出的代码过大。
链接脚本中,指定程序运行地址位于SDRAM,上电后从片内0地址开始运行,所以重定位前是位置无关码。重定位后,跳转到SDRAM中运行。
2、 程序组成
代码段(text):运行指令数据段(data):全局变量
只读数据段(rodata):const全局变量
Bss段:初值为0的全局变量
Common段:注释
3、使用链接脚本
all: arm-linux-gcc -c -o led.o led.c arm-linux-gcc -c -o uart.o uart.c arm-linux-gcc -c -o init.o init.c arm-linux-gcc -c -o main.o main.c arm-linux-gcc -c -o start.o start.S arm-linux-ld -Ttext 0 -Tdata 0x30000000 start.o led.o uart.o init.o main.o -o sdram.elf arm-linux-objcopy -O binary -S sdram.elf sdram.bin arm-linux-objdump -D sdram.elf > sdram.dis clean: rm *.bin *.o *.elf *.dis
当代码段与数据段之间差距较大时,我们编译出的可执行文件会很大,这样会导致无法下载运行,如上。由于我们从nor启动,又想修改全局变量,所以我们要对数据段进行重定位。从0地址放代码段,然后0x800放数据段,当程序运行时,将0x800的数据段复制到0x30000000地址去,这样就可以在nor启动时,实现修改全局变量。
此时链接脚本这样写:
SECTIONS { .text 0 : { *(.text) } .rodata : { *(.rodata) } .data 0x30000000 : AT(0x800) { *(.data) } .bss : { *(.bss) *(.COMMON) } }
代码中重定位数据段内容到0x30000000:
bl sdram_init /* 重定位data段 */ mov r1, #0x800 ldr r0, [r1] mov r1, #0x30000000 str r0, [r1] bl main
代码中包含可能变化的数字,可以当数据段较大的时候,灵活性很差,此时我们可以完善链接脚本:
SECTIONS { .text 0 : { *(.text) } .rodata : { *(.rodata) } .data 0x30000000 : AT(0x800) { data_load_addr = LOADADDR(.data); data_start = . ; *(.data) data_end = . ; } .bss : { *(.bss) *(.COMMON) } }并优化代码:
bl sdram_init /* 重定位data段 */ ldr r1, =data_load_addr /* data段在bin文件中的地址, 加载地址 */ ldr r2, =data_start /* data段在重定位地址, 运行时的地址 */ ldr r3, =data_end /* data段结束地址 */ cpy: ldrb r4, [r1] strb r4, [r2] add r1, r1, #1 add r2, r2, #1 cmp r2, r3 bne cpy bl main
另一种方法是,我们让程序的链接地址是0x30000000,上电后从0地址运行,此时把所有代码复制到0x30000000开始的地方,然后到SDRAM中运行。
3、 bss段
存储了初值为0的变量,bss段不保存在bin中,但是我们要链接bss的地址,并清除bss段,否在初值会不定。因此链接脚本如下:SECTIONS { .text 0 : { *(.text) } .rodata : { *(.rodata) } .data 0x30000000 : AT(0x800) { data_load_addr = LOADADDR(.data); data_start = . ; *(.data) data_end = . ; } bss_start = .; .bss : { *(.bss) *(.COMMON) } bss_end = .; }
Bss会跟着data段的地址往后排,因此bss地址为0x3XXXXXXX。然后在代码中清除bss段:
bl sdram_init /* 重定位data段 */ ldr r1, =data_load_addr /* data段在bin文件中的地址, 加载地址 */ ldr r2, =data_start /* data段在重定位地址, 运行时的地址 */ ldr r3, =data_end /* data段结束地址 */ cpy: ldrb r4, [r1] strb r4, [r2] add r1, r1, #1 add r2, r2, #1 cmp r2, r3 bne cpy /* 清除BSS段 */ ldr r1, =bss_start ldr r2, =bss_end mov r3, #0 clean: strb r3, [r1] add r1, r1, #1 cmp r1, r2 bne clean bl main
上面是按字节拷贝和读取,nor是16bit,sdram是32bit,可以改为按4字节操作:
cpy: ldr r4, [r1] str r4, [r2] add r1, r1, #4 add r2, r2, #4 cmp r2, r3 ble cpy /* 清除BSS段 */ ldr r1, =bss_start ldr r2, =bss_end mov r3, #0 clean: str r3, [r1] add r1, r1, #4 cmp r1, r2 ble clean bl main同时完善链接脚本,使4字节对齐:
SECTIONS { .text 0 : { *(.text) } .rodata : { *(.rodata) } .data 0x30000000 : AT(0x800) { data_load_addr = LOADADDR(.data); . = ALIGN(4); data_start = . ; *(.data) data_end = . ; } . = ALIGN(4); bss_start = .; .bss : { *(.bss) *(.COMMON) } bss_end = .; }
4、 链接脚本解析
http://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_mono/ld.html5、 位置无关码-重定位所有代码
(1)标准的链接脚本SECTIONS { . = 0x30000000; __code_start = .; . = ALIGN(4); .text : { *(.text) } . = ALIGN(4); .rodata : { *(.rodata) } . = ALIGN(4); .data : { *(.data) } . = ALIGN(4); __bss_start = .; .bss : { *(.bss) *(.COMMON) } _end = .; }
(2)位置无关码
调转的时候使用的是偏移地址,相对跳转 如B/BL,不使用全局变量、静态变量、不使用有初始值的数组。
重定位前使用位置无关码,不使用绝对地址。看反汇编。
一切就绪之后:ldr pc, =main /* 绝对跳转, 跳到SDRAM */
bl sdram_init //bl sdram_init2 /* 用到有初始值的数组, 不是位置无关码 */ /* 重定位text, rodata, data段整个程序 */ mov r0, #0 ldr r1, =_start /* 第1条指令运行时的地址 */ ldr r2, =__bss_start /* bss段的起始地址 */ sub r2, r2, r1 bl copy2sdram /* src, dest, len */ /* 清除BSS段 */ ldr r0, =__bss_start ldr r1, =_end bl clean_bss /* start, end */ //bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */ ldr pc, =main /* 绝对跳转, 跳到SDRAM */ halt: b halt
重定位及清楚bss的c代码
void copy2sdram(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; } } void clean_bss(volatile unsigned int *start, volatile unsigned int *end) /* start, end */ { while (start <= end) { *start++ = 0; } }ldr pc, =main 之前的都是位置无关码。通过ldr pc, =main跳转到SDRAM中继续执行。
(3)在C中直接获得链接脚本中的地址信息
C函数怎么使用lds文件中的变量abc?
a. 在C函数中声明改变量为extern类型, 比如:
extern int abc;
b. 使用时, 要取址, 比如:
int *p = &abc; // p的值即为lds文件中abc的值
c. 汇编可直接使用
void copy2sdram(void) { /* 要从lds文件中获得 __code_start, __bss_start * 然后从0地址把数据复制到__code_start */ extern int __code_start, __bss_start; volatile unsigned int *dest = (volatile unsigned int *)&__code_start; volatile unsigned int *end = (volatile unsigned int *)&__bss_start; volatile unsigned int *src = (volatile unsigned int *)0; while (dest < end) { *dest++ = *src++; } } void clean_bss(void) { /* 要从lds文件中获得 __bss_start, _end */ extern int _end, __bss_start; volatile unsigned int *start = (volatile unsigned int *)&__bss_start; volatile unsigned int *end = (volatile unsigned int *)&_end; while (start <= end) { *start++ = 0; } }
相关文章推荐
- 六.ARM裸机学习之重定位和链接脚本
- ARM裸机-4.SDRAM和重定位relocate
- Eclipse开发调试ARM裸机程序
- ARM嵌入式linux系统学习之裸机(一)
- 【ARM】s3c2440裸机之RTC数字时钟
- ARM裸机实战课程介绍
- S5pv210裸机实验——SDRAM重定位
- ARM remap与重定位摘抄
- 学习arm裸机程序
- ARM remap与重定位摘抄
- ARM裸机开发笔记1(指令简介)
- ARM裸机开发笔记6(ARM伪指令)
- ARM裸机程序--GPIO实验 LED(2)
- ARM裸机程序-点亮LED
- 【ARM裸机】 - 中断与异常
- 1.11.ARM裸机第十一部分-NandFlash和iNand
- tiny4412 裸机程序 七、重定位代码到DRAM
- ARM裸机程序研究 - 编译和链接
- 纯Linux下的 ARM裸机调试环境搭建(GDB + JLink)
- ARM裸机入门简介