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

一、链接脚本的解析

注意:

① 链接脚本中首先放所有程序的代码段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("\n\rg_char = ");
		printHex(g_char);
		puts("\n\r");

		
		puts("\n\rg_char3 = ");
		printHex(g_char3);
		puts("\n\r");

发现输出依旧不对:


显然这两个全局变量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(小于等于则跳转)指令。




猜你喜欢

转载自blog.csdn.net/u011663005/article/details/81036615