13_代码重定位
文章目录
1、段的概念
程序从nor启动,直接从nor flash中运行;
程序从nand启动,硬件先把前4K内容复制到SRAM中,在前4K程序中完成代码的重定位,即复制代码到SDRAM。
程序含有:
2、链接脚本
Nor启动:
Nand启动:
重定位:
2.1、链接脚本编写
SECTIONS {
.text 0 : { *(.text) } /* 代码段 */
.rodata : { *(.rodata) } /* 只读数据段 */
.data 0x30000000 : AT(0x800) /* 数据段 */
{
data_load_addr = LOADADDR(.data);
data_start = . ;
*(.data)
data_end = . ;
}
.bss : { *(.bss) *(.COMMON) } /* bss段,存放无初值、初值为0、注释代码 */
}
连接脚本的格式:
2.2、重定位过程
在start,S中:
/* 在main函数之前初始化sdram */
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 /* bne:Branch if Not Equal 如果 r2 != r3 则跳转到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
/* 跳转到main函数执行程序 */
bl main
上述使用ldrb strb 一个字节效率低,使用ldr str 四字节效率高。
/* 在main函数之前初始化sdram */
bl sdram_init
/* 重定位data段 */
ldr r1, =data_load_addr /* data段在bin文件中的地址(加载地址) */
ldr r2, =data_start /* data段重定位后的地址(程序运行的地址) */
ldr r3, =data_end /* data段结束地址 */
cpy:
ldr r4, [r1]
str r4, [r2]
add r1, r1, #4
add r2, r2, #4
cmp r2, r3
ble cpy /* ble:Branch if Less than or Equal如果:r2 <= r3 */
/* 清除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 /* 如果:r1 <= r2 */
/* 跳转到main函数执行程序 */
bl main
修改了str ldr之后程序在运行清除bss段时可能就把data段也清除了(因为bss段的地址没有4字节对齐)。所以程序在清楚过程中自动4字节对齐之后就把data段业清除了(恰巧data段放在bss段旁边)。
加上四字节对齐即可解决问题:
连接脚本文件sdram.lds中:
. = ALIGN(4); /* 加上此句话,向4字节对齐 */
.bss :
{
bss_start = . ;
*(.bss) *(.COMMON)
bss_end = . ;
}
2.3、程序分析
代码中的bl跳转分析:
程序使用bl main不会在sdram运行,还是在flash运行;使用ldr pc, main绝对跳转之后程序可在sdram运行
3、C语言写重定位代码
3.1、汇编写
/* 在main函数之前初始化sdram */
bl sdram_init
/* 重定位text,rodata,data段整个程序 */
mov r1, #0
ldr r2, =_start /*第一条指令运行时的地址(程序运行的地址) */
ldr r3, =__bss_start /* 结束地址:bss段的起始地址 */
cpy:
ldr r4, [r1]
str r4, [r2]
add r1, r1, #4
add r2, r2, #4
cmp r2, r3
ble cpy /* 如果:r2 <= r3 */
/* 清除bss段 */
ldr r1, =__bss_start
ldr r2, =_end
mov r3, #0
clean:
str r3, [r1]
add r1, r1, #4
cmp r1, r2
ble clean /* 如果:r1 <= r2 */
3.2、C语言写
/* 在main函数之前初始化sdram */
bl sdram_init
/* 重定位text,rodata,data段整个程序 */
bl copy2sdram
/* 清除bss段 */
bl clean_bss
在init.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;
unsigned int i = 0;
while(dest < end)
{
*dest++ = *src++;
}
}
void clean_bss(void)
{
/* 要从lds文件中获得 __bss_start,_end */
extern int __bss_start,_end;
volatile unsigned int *start = (volatile unsigned int *)&__bss_start;
volatile unsigned int *end = (volatile unsigned int *)&_end;
while(start <= end)
{
*start++ = 0;
}
}
3.3、C语言、汇编代码分析
上述汇编程序程序中使用ldr r1等指令把参数传给函数,也可不是用这些命令直接使用lds中的参数。
如: extern int __bss_start,_end;
extern int __code_start,__bss_start;等
使用方法:
C函数怎么使用lds文件中的变量abc?
a. 在C函数中声明改变量为extern类型, 比如:
extern int abc;
b. 使用时, 要取址, 比如:
int *p = &abc; // p的值即为lds文件中abc的值
相关论坛内容:
3.4、C代码如何使用链接脚本中定义的变量
http://www.100ask.org/bbs/forum.php?mod=viewthread&tid=16231&highlight=%C1%B4%BD%D3%BD%C5%B1%BE
即下列内容:
在链接脚本中,经常有这样的代码:
SECTIONS
{
.....
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
.got : { *(.got) }
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) }
_end = .;
}
其中__bss_start, _end 表示BSS段的起始、终止地址。
我们想对这段空间清零时,
1.在汇编代码中,可以直接引用__bss_start, _end,比如:
ldr r0, =__bss_start
ldr r1, =_end
2. 在C代码中,我们不能直接引用它们,要这样做:
void clean_bss(void)
{
extern int __bss_start, _end;
int *p = &__bss_start;
for (; p < &_end; p++)
*p = 0;
}
问:__bss_start, _end不是表示某个值吗?在C代码中为什么要使用取址符号 & ?
答:
一.
在C代码中,这样的语句:
int foo = 1000;
会导致2件事情发生:
- 在代码中,留出4字节的空间,保存数值1000
2.在C语言的symbole talbe,即符号表中,有一个名为foo的项,它里面存有那4字节空间的地址。
我们执行 foo = 1时,会先去符号表中找到foo对应的地址,然后把数值1填到那个地址对应的内存;
我们执行 int *a = &foo时,会直接把符号表中foo的地址,写给a。
二.
在链接脚本中,假设
__bss_start = 1000
__bss_start并不是一个变量,它只是一个值,并不需要在内存中留出一段空间来保存它;
在C语言中,符号表中会有一个名为__bss_start的项,这个项目中的值(地址值)是1000;
注意,这个1000并没有实际存在的内存。
三.
所以:在C语言中,要去使用链接脚本中定义的值时,应该这样做:
extern int __bss_start;
int val = &__bss_start;
使用取址符号&去得到它在符号表中的值。
注意,这个值只是链接脚本中定义的值,并不表示某个变量的地址。