“第013课 代码重定位”的版本间的差异
Baiwen root(讨论 | 贡献) |
Baiwen root(讨论 | 贡献) |
||
第229行: | 第229行: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | =第003节_链接脚本的解析 = | ||
+ | |||
+ | 链接脚本的语法 | ||
+ | <syntaxhighlight lang="c" > | ||
+ | SECTIONS { | ||
+ | ... | ||
+ | secname start BLOCK(align) (NOLOAD) : AT ( ldadr ) | ||
+ | { contents } >region :phdr =fill | ||
+ | ... | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | 解释: | ||
+ | secname :段名 | ||
+ | start :起始地址:运行时的地址(runtime addr);重定位地址(relocate addr) | ||
+ | AT ( ldadr ) :可有可无(load addr:加载地址) 不写时LoadAddr = runtime addr | ||
+ | { contents } 的内容: | ||
+ | start.o //内容为start.o文件 | ||
+ | *(.text)所有的代码段文件 | ||
+ | start.o *(.text)文件 | ||
+ | |||
+ | |||
+ | elf文件格式 | ||
+ | 1 链接得到elf文件,含有地址信息(load addr) | ||
+ | 2 使用加载器 | ||
+ | 2.1 对于裸板是JTAG调试工具 | ||
+ | 2.2 对于APP,加载器也是APP 把elf文件解析读入内存的加载地址 | ||
+ | 3 运行程序 | ||
+ | 4 如果loadaddr != runtimeaddr程序本身要重定位 | ||
+ | 核心程序运行时应该位于 runtimeaddr(reloate addr)或者链接地址 | ||
+ | |||
+ | bin文件 | ||
+ | 1 elf生成bin文件 | ||
+ | 2 硬件机制启动 | ||
+ | 3 如果bin文件所在位置 不等于runtimeaddr ,程序本身实现重定位 | ||
+ | |||
+ | |||
+ | bin文件/elf文件都不保存bss段 这些都是初始值为0 或者没有初始化的全局变量 | ||
+ | |||
+ | 程序运行时把bss段对应的空间清零 | ||
+ | |||
+ | 做个实验,把全局变量g_A以16进制打印出来 | ||
+ | <syntaxhighlight lang="c" > | ||
+ | /* 0xABCDEF12 */ | ||
+ | void printHex(unsigned int val) | ||
+ | { | ||
+ | int i; | ||
+ | unsigned char arr[8]; | ||
+ | |||
+ | /* 先取出每一位的值 */ | ||
+ | for (i = 0; i < 8; i++) | ||
+ | { | ||
+ | arr[i] = val & 0xf; | ||
+ | val >>= 4; /* arr[0] = 2, arr[1] = 1, arr[2] = 0xF */ | ||
+ | } | ||
+ | |||
+ | /* 打印 */ | ||
+ | puts("0x"); | ||
+ | for (i = 7; i >=0; i--) | ||
+ | { | ||
+ | if (arr[i] >= 0 && arr[i] <= 9) | ||
+ | putchar(arr[i] + '0'); | ||
+ | else if(arr[i] >= 0xA && arr[i] <= 0xF) | ||
+ | putchar(arr[i] - 0xA + 'A'); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | </syntaxhighlight> | ||
+ | |||
+ | |||
+ | //打印初始值为0的变量 | ||
+ | |||
+ | <syntaxhighlight lang="c" > | ||
+ | int g_A = 0; | ||
+ | int g_B; | ||
+ | |||
+ | int main(void) | ||
+ | { | ||
+ | uart0_init(); | ||
+ | |||
+ | puts("\n\rg_A = "); | ||
+ | printHex(g_A); | ||
+ | puts("\n\r"); | ||
+ | |||
+ | </syntaxhighlight> | ||
+ | |||
+ | 上述代码,没有清理bss段 g_A等于莫名奇妙的值 并不等于0 所以需要清理bss段 | ||
+ | |||
+ | |||
+ | |||
+ | 修改lds链接文件 | ||
+ | |||
+ | <syntaxhighlight lang="c" > | ||
+ | SECTIONS { | ||
+ | .text 0 : { *(.text) } | ||
+ | .rodata : { *(.rodata) } | ||
+ | .data 0x30000000 : AT(0x800) | ||
+ | { | ||
+ | data_load_addr = LOADADDR(.data); | ||
+ | data_start = . ; | ||
+ | *(.data) | ||
+ | data_end = . ; | ||
+ | } | ||
+ | |||
+ | bss_start = .; //bss开始地址是当前位置 | ||
+ | .bss : { *(.bss) *(.COMMON) } | ||
+ | bss_end = .; //bss结束地址也是当前位置 | ||
+ | } | ||
+ | |||
+ | </syntaxhighlight> | ||
+ | |||
+ | 修改start.s,清除bss段 | ||
+ | <syntaxhighlight lang="c" > | ||
+ | |||
+ | |||
+ | /* 清除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 | ||
+ | |||
+ | halt: | ||
+ | b halt | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | 现在的代码全局变量就是为0,通过几行代码,就可以少几十个甚至上千个全局变量的存储空间。 | ||
2018年1月17日 (三) 17:13的版本
第001节_段的概念_重定位的引入
S3C2440的CPU可以直接给SDRAM发送命令、给Nor Flash发送命令、给4K的片上SDRAM发送命令,但是不能直接给Nand Flsh发送命令
假如把程序烧写到Nand Flsh上,即向Nand Flsh烧入** bin** 文件,CPU是无法从Nand Flsh中取代码执行的。
为什还可以使用NAND启动? 1. 上电后,Nand启动硬件会自动把Nand Flsh前4K复制到SRAM; 2. CPU从0地址运行SRAM;
如果我的程序大于4K怎么办? 前4K的代码需要把整个程序读出来放到SDRAM(即代码重定位)。
如果从Nor Flash启动,会出现什么问题? 将拨动开关拨到Nor Flash启动时,此时CPU认为的** 0地址 **在Nor Flash上面,片内内存SRAM的基地址就变成了0x40000000(Nand启动时片内内存SRAM的基地址基地址是0),由于Nor Flash特性:**可以像内存一样读,但不能像内存直接写**,因此需要把全局变量和静态变量重定位 放到SDRAM里。
例如执行如下几条汇编指令
MOV R0, #0 LDR R1, [R0] @读有效 STR R1, [R0] @写无效
当程序中含有需要写的全局变量或静态变量时,假如是在Nand Flash可以正常操作,如果是在Nor Flash,修改无效。因此我们需要把全局变量和静态变量重定位 放到SDRAM
#include "s3c2440_soc.h"
#include "uart.h"
#include "init.h"
char g_Char = 'A'; //定义一个全局变量
const char g_Char2 = 'B'; //定义固定的全局变量
int g_A = 0;
int g_B;
int main(void)
{
uart0_init();
while (1)
{
putchar(g_Char); /*让g_Char输出*/
g_Char++; /* nor启动时, 此代码无效 */
delay(1000000);
}
return 0;
}
编译运行查看是否有效果
查看sdram.dis文件 发现data数据段放在了0x00008474这个地址导致 程序太大
在makefile中加入这么一句话
arm-linux-ld -Ttext 0 ** -Tdata 0x800 ** start.o led.o uart.o init.o main.o -o sdram.elf
16进制的800就是十进制的2048 这时我们的bin文件就变为2049
烧写程序
烧写在NORFlash 烧写在NANDFlash观察这两种的效果
设置成NANDFlash启动没有问题 显示ABCDE... 设置成NORFlash启动显示AAA... 对于NOR启动时g_Char++; /* nor启动时, 此代码无效 */
Disassembly of section .data:
00000800 <__data_start>:
800: Address 0x800 is out of bounds. //数据段
Disassembly of section .rodata:
//放在只读数据段内
00000474 <g_Char2>: //const char g_Char2 = 'B';
474: Address 0x474 is out of bounds.
Disassembly of section .bss: //bss段
00000804 <g_A>: //int g_A = 0;
804: 00000000 andeq r0, r0, r0
00000808 <g_B>: //int g_B;
808: 00000000 andeq r0, r0, r0
Disassembly of section .comment:
一个程序里面有
- .text 代码段
- .data 数据段
- rodata 只读数据段(const全局变量)
- bss段 (初始值为0,无初始值的全局变量)
- commen 注释
其中bss段和commen 注释不保存在bin文件中。
第002节_链接脚本的引入与简单测试
前面程序运行,发现从Nand Flash启动和从Nor Flash启动表现是不一样的。 设置成Nand Flash启动没有问题 显示ABCDE... 设置成NOor Flash启动则显示AAA... 这是什么原因呢? 假如现在是Nor启动: <img src="./lesson/lesson2/lesson2_001.jpg"> Nor Flash就被认为是0地址,g_Char被放在0x800后面。CPU上电后从0地址开始执行,它能读取Nor Flash上的代码,打印出A,当进行g_Char++的时候,写操作操作无效,下次读取的数据仍然是A。
假如现在是Nor启动: <img src="./lesson/lesson2/lesson2_002.jpg"> 上电后,Nand Flash前4K代码就被自动的复制到SRAM里面,SRAM是CPU认为的0地址。CPU上电后从0地址开始执行,它读取SRAM上的代码,并g_Char++修改变量,下次读取的数据就依次增加了。
为了解决Nor Flash里面的变量不能写的问题,我们把变量所在的数据段放在SDRAM里面,看行不行。
修改Makefile 指定数据段为0x30000000 `-Tdata 0x30000000`:
arm-linux-ld -Ttext 0 -Tdata 0x30000000 start.o led.o uart.o init.o main.o -o sdram.elf
这样的话编译出来的bin文件 从0地址 到 0x30000000地址 文件大小有800多MB,代码段和数据段直接有间隔,称之为黑洞。 <img src="./lesson/lesson2/lesson2_003.jpg">
解决黑洞有两个办法:
- 第一个方法
- 把数据段的g_Char和代码段靠在一起;
- 烧写在Nor Flash上面;
- 运行时把g_char(全局变量)复制到SDRAM,即0x3000000位置(重定位);
- 第二个方法
- 让文件直接从0x30000000开始,全局变量在0x3......;
- 烧写Nor Flash上 0地址处;
- 运行会把整个代码段数据段(整个程序)从0地址复制到SDRAM的0x30000000(重定位);
这两个方法的区别是前者只重定位了数据段,后者重定位了数据段和代码段。
参考文档 Using LD, the GNU linker http://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_mono/ld.html
第一种办法如何实现 修改Makefile的代码段地址,使用链接脚本sdram.lds指定。
#arm-linux-ld -Ttext 0 -Tdata 0x30000000 start.o led.o uart.o init.o main.o -o sdram.elf
arm-linux-ld -T sdram.lds start.o led.o uart.o init.o main.o -o sdram.elf
链接脚本的语法:
SECTIONS {
...
secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
{ contents } >region :phdr =fill
...
}
我们需要依次排列 代码段、只读数据段、数据段、.bss段、.common。
其中数据段放在0x800,但运行时在0x3000000:
SECTIONS {
.text 0 : { *(.text) }//所有文件的.text
.rodata : { *(.rodata) } //只读数据段
.data 0x30000000 : AT(0x800) { *(.data) } //放在0x800,但运行时在0x3000000
.bss : { *(.bss) *(.COMMON) }//所有文件的bss段,所有文件的.COMMON段
}
重新编译后烧写bin文件,发现启动后显示乱码。原因是我们从0x30000000处获取g_Char,但在这之前,并没有在0x30000000处准备好数据。因此需要重定位数据段,将0x800的数据移动到0x30000000处,在start.S加入:
bl sdram_init /* 重定位data段 */ mov r1, #0x800 ldr r0, [r1] mov r1, #0x30000000 str r0, [r1] bl main
上面的这种方法,只能复制0x800处的一位数据,不太通用,下面写一个更加通用的复制方法:
链接脚本修改如下:
SECTIONS {
.text 0 : { *(.text) }
.rodata : { *(.rodata) }
.data 0x30000000 : AT(0x800)
{
data_load_addr = LOADADDR(.data);
data_start = . ;//等于当前位置
*(.data) //等于数据段的大小
data_end = . ;//等于当前位置
}
.bss : { *(.bss) *(.COMMON) }
}
修改start.S
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] //从r1读到r4
strb r4, [r2] //r4存放到r2
add r1, r1, #1 //r1+1
add r2, r2, #1 //r2+1
cmp r2, r3 //r2 r3比较
bne cpy //如果不等则继续拷贝
bl main
第003节_链接脚本的解析
链接脚本的语法
SECTIONS {
...
secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
{ contents } >region :phdr =fill
...
}
解释:
secname :段名 start :起始地址:运行时的地址(runtime addr);重定位地址(relocate addr) AT ( ldadr ) :可有可无(load addr:加载地址) 不写时LoadAddr = runtime addr { contents } 的内容: start.o //内容为start.o文件 *(.text)所有的代码段文件 start.o *(.text)文件
elf文件格式
1 链接得到elf文件,含有地址信息(load addr)
2 使用加载器
2.1 对于裸板是JTAG调试工具
2.2 对于APP,加载器也是APP 把elf文件解析读入内存的加载地址
3 运行程序
4 如果loadaddr != runtimeaddr程序本身要重定位
核心程序运行时应该位于 runtimeaddr(reloate addr)或者链接地址
bin文件 1 elf生成bin文件 2 硬件机制启动 3 如果bin文件所在位置 不等于runtimeaddr ,程序本身实现重定位
bin文件/elf文件都不保存bss段 这些都是初始值为0 或者没有初始化的全局变量
程序运行时把bss段对应的空间清零
做个实验,把全局变量g_A以16进制打印出来
/* 0xABCDEF12 */
void printHex(unsigned int val)
{
int i;
unsigned char arr[8];
/* 先取出每一位的值 */
for (i = 0; i < 8; i++)
{
arr[i] = val & 0xf;
val >>= 4; /* arr[0] = 2, arr[1] = 1, arr[2] = 0xF */
}
/* 打印 */
puts("0x");
for (i = 7; i >=0; i--)
{
if (arr[i] >= 0 && arr[i] <= 9)
putchar(arr[i] + '0');
else if(arr[i] >= 0xA && arr[i] <= 0xF)
putchar(arr[i] - 0xA + 'A');
}
}
//打印初始值为0的变量
int g_A = 0;
int g_B;
int main(void)
{
uart0_init();
puts("\n\rg_A = ");
printHex(g_A);
puts("\n\r");
上述代码,没有清理bss段 g_A等于莫名奇妙的值 并不等于0 所以需要清理bss段
修改lds链接文件
SECTIONS {
.text 0 : { *(.text) }
.rodata : { *(.rodata) }
.data 0x30000000 : AT(0x800)
{
data_load_addr = LOADADDR(.data);
data_start = . ;
*(.data)
data_end = . ;
}
bss_start = .; //bss开始地址是当前位置
.bss : { *(.bss) *(.COMMON) }
bss_end = .; //bss结束地址也是当前位置
}
修改start.s,清除bss段
/* 清除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
halt:
b halt
现在的代码全局变量就是为0,通过几行代码,就可以少几十个甚至上千个全局变量的存储空间。