段概念的引入
关于段的脚本语法编写可以参考:
关于重定位可以参考我之前的一篇随笔:
ld,连接器
简单通俗一点来说,就是把可执行代码放在内存的另一个地方去。
比如,我的bin文件烧写在nor flash上,在前2k存放代码段,紧接着存放.data段,此时我想把烧写在nor上的代码中.data段的数据赋值到到sdram0x30000000的位置进行操作,这个过程就叫做重定位,重定位之后,我们读写.data段的数据就在sdram中进行。
1、运行地址<--->链接地址:他们两个是等价的,只是两种不同的说法。
2、加载地址<--->存储地址:他们两个是等价的,也是两种不同的说法。
运行地址:也叫链接地址,是程序定位的绝对地址,即在编译连接时确定的地址。如果程序中有位置相关指令,程序在运行时,程序必须在运行地址上
加载地址:程序放置的位置。
运行地址和加载地址的值有时相等,有时却不相等,所以这给初学者带来很大的困扰。这也涉及到位置有关和无关码。
说了那么多概念文字类的东西,肯定很空洞没有真实体会感,so,
Talk is cheap. Show me the code
现在,我们开始编写连接脚本,对于熟悉理解段和运行或者加载地址很有帮助。
在上一个实验中,我们把数据段连接在0x800的地址处。而现在,我们要实现重定位,简单地在Makefile中指定连接地址已经不能满足要求了,需要编写连接脚本。在之前的nor启动代码,我们对变量执行++操作不起作用,所以,现在我们采取把nor flash上的data段重定位到sdram上,这样就可实现在nor启动的时候,全局变量执行写操作也可以得到正确的响应。
连接脚本如下:
SECTIONS { .text 0: {*(.text)} .rodata : {*(.rodata)} .data 0x30000000 :AT(0x800) { data_load_add = LOADADDR(.data); data_start = . ; *(.data) data_end = . ; } .bss :{*(.bss) *(COMMEN)} }
注意一点,=号左右空出一个空格可以让你正确执行上面的脚本,第一次实验的时候,我没有再=号后面加上一个空格,导致连接器无法识别我的段内符号。
由于连接脚本的符号支持各种字符,你不使用空格分离,ld不知道如何解释,所以,请你在书写脚本的时候注意空格分割你的符号。
再者,细心的你可能会问,那脚本中什么时候末尾加分号,什么时候又不加呢?
译:
您可以创建全局符号,并使用任何C赋值运算符将值(地址)分配给全局符号:
symbol = expression ;
symbol &= expression ;
symbol += expression ;
symbol -= expression ;
symbol *= expression ;
symbol /= expression ;
在ld
表达式中,有两点区别于其他运算符。
可能会出现赋值语句:
- 作为
ld
脚本自身的命令; - 作为
SECTIONS
命令内的独立陈述; - 作为
SECTIONS
命令中部分定义内容的一部分
通用符号(common symbol)的输入section:
在许多目标文件格式中,通用符号并没有占用一个section。连接器认为:输入文件的所有通用符号在名为COMMON的section内。
例子,
.bss { *(.bss) *(COMMON) }
这个例子中将所有输入文件的所有通用符号放入输出.bss section内。可以看到COMMOM section的使用方法跟其他section的使用方法是一样的。
有些目标文件格式把通用符号分成几类。例如,在MIPS elf目标文件格式中,把通用符号分成standard common symbols(标准通用符号)和small common symbols(微通用符号,不知道这么译对不对?),此时连接器认为所有standard common symbols在COMMON section内,而small common symbols在.scommon section内。
在一些以前的连接脚本内可以看见[COMMON],相当于*(COMMON),不建议继续使用这种陈旧的方式。
通配符在连接脚本中是支持的:
In any place where you may use a specific file or section name, you may also use a wildcard pattern. The linker handles wildcards much as the Unix shell does. A `*' character matches any number of characters. A `?' character matches any single character. The sequence `[chars]' will match a single instance of any of the chars; the `-' character may be used to specify a range of characters, as in `[a-z]' to match any lower case letter. A `\' character may be used to quote the following character.
在启动文件中需要增加sdram初始化和重定位代码:
bl sdram_init /* 重定位data段 */ ldr r1, =data_load_add /* 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
上面重定位汇编,是把加载地址也就是存储地址的代码拷贝到运行地址也是连接地址(也是重定位地址)sdram上。
main函数:
#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; void delay(volatile int d) { while (d--); } int main(void) { uart0_init(); while (1) { putchar(g_Char); g_Char++; /*没有重定位时, nor启动, 此代码无效 */ delay(1000000); } return 0; }
Makefile:
.PHONY:clean objcts :=start.o main.o init.o uart.o lds:=sdram.lds sdram.bin:$(objcts) arm-linux-ld -T $(lds) $^ -o sdram.elf arm-linux-objcopy -O binary -S sdram.elf $@ arm-linux-objdump -S -D sdram.elf > sdram.dis %.o:%.c arm-linux-gcc -o $@ -c -g $< %.o:%.S arm-linux-gcc -o $@ -c -g $< clean: -rm *.bin *.o *.elf *.dis
注意上面红色部分,已经变成连接脚本了而不是单纯的简单指定。
这个程序,烧写在nand和nor上都可以正常运行了。
当然,这需要连接脚本的基础,所以还是建议先看看那篇gnu ld的文章。
欢迎加入作者的小圈子
扫描下方左边二维码加入QQ交流群,扫描下方右边二维码关注个人微信公众号并获取更多隐藏干货,QQ交流群:816747642 微信公众号:Crystal软件学堂
作者:Crystal软件学堂 bilibili视频教程地址:https://space.bilibili.com/5782182 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在转载文章页面给出原文连接。 如果你觉得文章对你有所帮助,烦请点个推荐,你的支持是我更文的动力。 文中若有错误,请您务必指出,感谢给予我建议并让我提高的你。 |