数据段重定位
/*
*硬件平台:韦东山嵌入式Linxu开发板(S3C2440.v3)
*软件平台:运行于VMware Workstation 12 Player下的UbuntuLTS16.04_x64 系统
*参考资料:Using LD, the GNU linker:http://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_mono/ld.htm,
EM63A165TS(SDRAM)datasheet,S3C2440datasheet,开发版原理图
*/
一、基础知识点
-
程序由以下五个段组成
{
.text(代码段),
.data(数据段),
.rodata(只读数据段),
.bss(.bss,未初始化或初始化为0的全局变量),
.COMMON(注释段),
} -
汇编指令解析
2.1 ldr:把数据从内存加载到寄存器
{
LDR指令的格式为:
LDR{条件} 目的寄存器,<存储器地址>
LDR指令用于从存储器中将一个32位的字数据传送到目的寄存器中。
}
2.2 str:把数据从寄存器保存到内存
{
STR指令的格式为:
STR{条件} 源寄存器,<存储器地址>
STR指令用于从源寄存器中将一个32位的字数据传送到存储器中。
}
2.3 bne:是“不相等或不为0跳转指令”
{
cmp同bne搭配理解
如:cmp r0,r1
bne clean_bss//如果r0!=r1,就执行bne,跳转到clean_bss函数处执行,否则向下执行。
} -
C函数怎么使用lds文件中的变量abc?
3.1 在C函数中声明改变量为extern类型, 比如:
extern int abc;
3.2. 使用时, 要加取址符&, 比如:
int *p = &abc; // p的值即为lds文件中abc的值
二、什么是重定位?
重定位就是把代码段搬移到自身想要的地址。本来程序是运行在运行地址处的,你可以通过重定位搬移到链接地址处。
三、为什么要重定位?
1、被动原因:Flash本身的内存大小不足,没有办法保证代码运行时的完整性。CPU发出的地址可以直接到达SDRAM,SRAM,NOR但是无法直接到达NAND
因此我们的程序可以直接放在NOR,SDRAM直接运行,假设我们把程序烧录到NAND中,CPU无法直接从NAND取地址运行。
2、主动原因:自身代码设计要求。
四、怎么重定位?
!在东山嵌入式Linxu开发板(S3C2440.v3)进行学习!
第一步:
判断bin文件所要烧写到NOR FLASH还是NAND FLASH
第二步:
实验一:烧写到NOR FLASH
目的:需要把data段重定位到SDRAM
原因:NOR FLASH中的数据只可读不可写,修改其中的数据是无效的
*************************************************************
实验二:烧写到NAND FLASH,
目的:需要把data段重定位到SDRAM
原因:NAND FLASH中的内存大小为4k,若程序的bin文件>4K时,前4K的代码需要把整个程序读出,放到SDRAM
第三步:
进行代码的编写工作
四、怎么在代码中实现数据段重定位?
Makefile文件:
添加脚本文件
arm-linux-ld -T sdram.lds start.o led.o uart.o sdram_init.o main.o -o sdram.elf
sdram.lds脚本文件:
根据《Using LD, the GNU linker》中的
SECTIONS {
...
secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
{ contents } >region :phdr =fill
...
}
编译出:
SECTIONS {
.text 0 : { *(.text) } //所有文件的代码段放到起始地址为0的代码段
.rodata : { *(.rodata) } //所有文件的只读数据段放到紧接着代码段的只读数据段
.data 0x30000000 : AT(0x800) //把data的加载地址和data的长度放到起始代码为0x800的数据段中
{
data_load_addr = LOADADDR(.data); //data_load_addr = 加载地址
. = ALIGN(4); //起到保证4字节对齐的作用
data_start = . ; //data_start = 当前地址即当前地址
*(.data) //添加数据段
data_end = . ; //data_start = 当前地址即添加数据段后地址的地址
}
. = ALIGN(4); //起到保证4字节对齐的作用
bss_start = .;
.bss :{*(.bss),*(.COMMON)}
bss_end = .;
}
!0x30000000与0x800需要看反汇编代码与NOR FLASH ,SRAM,NAND FLASH的内存大小确定!
start.S汇编文件数据段重定位和清除bss,COMMON段功能实现:
bl sdram_init
/* 重定位data段 */
ldr r1, =data_load_addr //把data_loda_addr的值传送到r1寄存器中,data段在bin文件中的地址, 加载地址
ldr r2, =data_start //把data_start的值传送到r2寄存器中,data段在重定位地址, 运行时的地址
ldr r3, =data_end //把data_end的值传送到r3寄存器中
cpy:
ldr r4, [r1] //从r1存储器中传送数据到r4寄存器中,r4 = [r1]
str r4, [r2] //把r4寄存器中的数据写入到r2存储器中,r4 ->[r2]
add r1, r1,#4
add r2, r2,#4
cmp r2, r3 //比较r2-r3 =0?,!=则继续执行cpy程序
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
五、怎么在代码中实现全部重定位?
Makefile文件:
添加脚本文件
arm-linux-ld -T sdram.lds start.o led.o uart.o sdram_init.o main.o -o sdram.elf
sdram.lds脚本文件:
SECTIONS {
.text 0 : { *(.text) } //所有文件的代码段放到起始地址为0的代码段
.rodata : { *(.rodata) } //所有文件的只读数据段放到紧接着代码段的只读数据段
.data 0x30000000 : AT(0x800) //把data的加载地址和data的长度放到起始代码为0x800的数据段中
{
data_load_addr = LOADADDR(.data); //data_load_addr = 加载地址
. = ALIGN(4); //起到保证4字节对齐的作用
data_start = . ; //data_start = 当前地址即当前地址
*(.data) //添加数据段
data_end = . ; //data_start = 当前地址即添加数据段后地址的地址
}
. = ALIGN(4); //起到保证4字节对齐的作用
bss_start = .;
.bss :{*(.bss),*(.COMMON)}
bss_end = .;
}
!0x30000000与0x800需要看反汇编代码与NOR FLASH ,SRAM,NAND FLASH的内存大小确定!
start.S汇编文件代码重定位和清除bss,COMMON段功能实现:
bl sdram_init
/* 重定位data段 */
ldr r1, =data_load_addr //把data_loda_addr的值传送到r1寄存器中,data段在bin文件中的地址, 加载地址
ldr r2, =data_start //把data_start的值传送到r2寄存器中,data段在重定位地址, 运行时的地址
ldr r3, =data_end //把data_end的值传送到r3寄存器中
cpy:
ldr r4, [r1] //从r1存储器中传送数据到r4寄存器中,r4 = [r1]
str r4, [r2] //把r4寄存器中的数据写入到r2存储器中,r4 ->[r2]
add r1, r1,#4
add r2, r2,#4
cmp r2, r3 //比较r2-r3 =0?,!=则继续执行cpy程序
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
提问:既然在汇编文件中可以使用链接脚本的变量,那么在C文件中可以吗,如果可以的话在C文件中实现代码重定位,清除数据段功能。
回答:是可以的,3.1小点有介绍。
下面进行代码的编译:
start.S汇编文件代码重定位和清除bss,COMMON段功能实现:
bl sdram_init
bl copy_to_sdram
bl clean_bss
在sdram.c文件中实现上述函数功能:
/*功能:复制整个text,rodata,data段到SDRAM中*/
void copy_to_sdram(void)
{
extern int start,__bss_start; //建立外部变量,方便获取lds文件中的量
volatile unsigned int *text =(volatile unsigned int *) &start; //text指向程序开头地址
volatile unsigned int *end = (volatile unsigned int *)&__bss_start; //end指向bss段开头的地址
volatile unsigned int *src = (volatile unsigned int *)0; //src指向0地址.即FLASH的开头地址
while(text < end)
{
*text++ = *src++;
}
}
/*功能:清楚全部bss段*/
void clean_bss(void)
{
extern int __bss_start,_end; //建立外部变量,方便获取lds文件中的量
volatile unsigned int *_start = (volatile unsigned int *)&__bss_start;
volatile unsigned int *end = (volatile unsigned int *)&_end;
while(_start <= end)
{
*(_start)++ =0;
}
}
int sdram_test(void)
{
int i;
volatile unsigned char *p = (volatile unsigned char *)0x30000000; //p指针指向sdram的地址
//写数据到sdram,从0x30000000到0x30001000都写入0x22
for(i=0;i<=1000;i++)
{
p[i] = 0x22;
}
//从sdram读数据
for(i=0;i<=1000;i++)
{
if(p[i] != 0x22)
return -1;
}
return 0;
}