韦东山嵌入式Linux学习笔记之——代码重定位003_链接脚本的解析与改进

2019-07-13 08:55发布

一、链接脚本的解析注意:① 链接脚本中首先放所有程序的代码段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(小于等于则跳转)指令。