一、链接脚本的解析注意:① 链接脚本中首先放所有程序的代码段text,那么这些程序的代码段按照什么样的顺序排列呢?在Makefile中有这些程序的排序
② 这里的data数据段设置了加载地址0x800,表明在生成的bin文件中,data段在0x800的位置,而前面没有指定加载地址的text代码段和rodata只读数据段存放在bin文件中的0地址开始的位置。③ data数据段的重定位功能由前面的text代码段实现。④ bss段的runtime addr运行地址紧接着data段。⑤ bin文件、ELF文件中都不存放bss段。
核心观点:程序在运行时应该位于它的runtime addr(或relocate addr),这两个地址又叫做链接地址。ELF文件:在计算机科学中,是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件。ELF文件中包含程序的加载地址等一些程序可以直接运行的信息。对于裸板的bin文件:
下面写程序来一一验证:实现将全局变量以十六进制的形式打印出来。
/* 0xABCDEF12 */
void printHex(unsigned char 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');
elif (arr[i] >= 0xA && arr[i] <= 0xF)
putchar(arr[i] - 0xA +'A');
}
}
首先在主程序中打印初值为0的全局变量g_A
但是运行的结果并不是0:
原因是bss段所对应的内存还没有清0。我们需要事先将bss段所对应的地址空间清0,那么就要首先知道这段空间的地址是多少。这时就需要修改链接脚本:
bss_start = .;
.bss : { *(.bss) *(.COMMON)}
bss_end = .;
接下来在start.S中添加清除BSS段的代码:
/* 清除BSS段 */
ldr r1, = bss_start
ldr r2, = bss_end
mov r3, #0
clean:
str r3, [r1]
add r1, r1, #1
cmp r1, r2
bne clean
这次编译烧写后的结果如下:
正确输出了0。
二、链接脚本的改进1. 拷贝(从flash--->SDRAM)代码的改进2. 链接脚本的改进
以读写SDRAM为例:① 当cpu想去SDRAM中读取某一字节的数据时(ldrb),cpu将命令发给内存控制器,然后内存控制器从32位的SDRAM中一次读出4字节数据,之后cpu拿出感兴趣的一个字节。② 当cpu想写一个字节到SDRAM时(strb),同理它会将命令发给内存控制器,这时内存控制器会将32位指令发给SDRAM,同时内存控制器还会发出
数据屏蔽信号DQM,比如当前cpu只想写一个字节,这时内存控制器就会发出三条DQM屏蔽掉其他三个字节,最终完成写一个字节。
改进:使用ldr和str命令,这两个命令以4字节为单位进行操作。效率会有很大的提升。 ldr 命令从nor flash中读取数据
str 命令写SDRAM
cpy:
ldr r4, [r1] //从r1地址处拷贝一个字节到r4
str r4, [r2]
add r1, r1, #4
add r2, r2, #4
cmp r2, r3
bne cpy
/* 清除BSS段 */
ldr r1, = bss_start
ldr r2, = bss_end
mov r3, #0
clean:
str r3, [r1]
add r1, r1, #4
cmp r1, r2
bne clean
编译烧写后发现串口只输出了
,main函数后续while(1)循环中的内容都没有输出。现在试着在while(1)循环中让g_char和g_char3也按照g_A那样以十六进制整数的方式输出:
puts("
g_char = ");
printHex(g_char);
puts("
");
puts("
g_char3 = ");
printHex(g_char3);
puts("
");
发现输出依旧不对:
显然这两个全局变量g_char和g_char3被破坏了。有可能是清除BSS段的时候意外将其他全局变量也清0了。先屏蔽start.S中BSS段清零的代码重新编译烧写,发现恢复正常。
查看反汇编代码:
然而这里的0x30000000正好是两个全局变量g_char和g_char3存放的地址。
这时候只能去修改链接脚本,让BSS段向4取整。只需要在链接脚本中加入一条:
. = ALIGN(4); //当前地址向4取整
编译烧写后:
※后续:昨天在自己修改代码编译烧写至开发板后,发现串口中什么也不显示。对照韦老师的代码后发现错误出在了start.S文件中代码重定位的cpy段和清除BSS的clean段。笔者起初在将ldrb和strb指令改为ldr和str后,并没有改动后面的bne(不相等则跳转)跳转指令,这是导致程序出错的原因。分析后发现,之前的代码不论复制还是清除都是按照1字节的长度进行操作的,但是将指令改为ldr和str后,是按照一次4字节进行操作的,如果还使用bne跳转指令就会导致在复制和清除的过程中跨过bne指令判断的条件,程序会最终卡在cpy这里。解决办法就是将bne指令改为ble(小于等于则跳转)指令。