ARM技术论坛
直播中

张伟

7年用户 1595经验值
私信 关注
[经验]

ARM裸机重定位的目的是什么?如何实现

1、 重定位的目的
       对于2440上电后,分两种情况,从nor启动,直接从nor作为0地址,开始在nor中运行。从nand启动,拷贝前4K到片内SRAM中。当代码大于4K的时候,我们需要重定位代码到更大的SDRAM中去运行。从nor运行时,由于nor只可读而不可以修改nor中的内容,就会导致一些全局变量,在代码中无法修改,此时我们可以将全局变量重定位(指定链接地址)到SDRAM中,这样我就可以nor中运行,然后对全局变量进行修改。
       总结来说:重定位就是代码的存储地址和运行地址不一致,我们需要把代码拷贝到运行地址处。可以重定位所有代码,也可以重定位数据段等。由于代码存在flash中,一般可在链接脚本中加入AT()来指定加载地址,以防止编译出的代码过大。
       链接脚本中,指定程序运行地址位于SDRAM,上电后从片内0地址开始运行,所以重定位前是位置无关码。重定位后,跳转到SDRAM中运行。
1.jpg
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、 链接脚本解析
5、 位置无关码-重定位所有代码
(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;
}
}

原作者:【星星之火】

更多回帖

发帖
×
20
完善资料,
赚取积分