【Linux】制作U-Boot烧写镜像到SD卡的过程(中篇:LDS文件)

上一篇文章,讲述了制作U-Boot烧写镜像到SD卡的过程,其中运用make的方式来进行将.s文件编译成.bin文件,那make是什么意思?它主要实现了什么?

先讲一下,如果不采用make的方式该怎样实现这个过程。

准备工作

先准备两个.s文件,myboot.smylowlevel_init.s。为了使用讲解一下链接过程,本文故意将gpio_outled2_on两个过程写在两个文件中。

myboot.s

b reset			//8种异常的处理,都是跳转到reset
b reset
b reset
b reset
b reset
b reset
b reset

reset:
	bl gpio_out
	bl led2_on
	mov r0, r1				//5句无用代码
	mov r1, r2
	mov r2, r3
	mov r3, r4
	mov r4, r5
1:					//标号
	b 1b					//死循环

gpio_out:
	ldr r11, =0xE0200280			//获得寄存器地址
	ldr r12, =0x00001111			//配置成输出状态
	str r12, [r11]					//将r12寄存器的值放回r11

	ldr r11, =0xE0200284
	ldr r12, =0xF
	str r12, [r11]
	mov pc, lr

mylowlevel_init.s

.globl led2_on
led1_on:
	ldr r11, =0xE0200284
	ldr r12, [r11]
	bic r12, r12, #(1<<1)			//将r12的第二位清零,回写到r12
	str r12, [r11]
	mov pc, lr

下面就先将.s文件汇编成.o文件,利用arm-linux-gcc命令。

arm-linux-gcc -c mystart.s
arm-linux-gcc -c mylowlevel_init.s

那么两个.o文件怎么链接成一个可执行文件呢?

.o链接成可执行文件

LDS文件

当对一个.c文件进行处理,经过预编译编译汇编后,最终会生成.o文件。最后一步,将所有的.o文件进行链接,从而生成最终的可执行文件。

那么如何进行链接呢?

汇编后的.o文件会由好多段(section)组成,其中最基本的段就是:bss段data段text段。这些段的含义如下:

  • bss段:bss段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域,属于静态内存分配
  • data段:数据段(data segment)通常是指用来存放程序中已初始化的全局变量的一块内存区域,属于静态内存分配
  • text段:代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读(某些架构也允许代码段为可写,即允许修改程序)。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。

在链接的过程中,当然不会喜欢将这些段直接拼接,而是希望它们按种类进行分别拼接,也就是说:

【Linux】制作U-Boot烧写镜像到SD卡的过程(中篇:LDS文件)_第1张图片

对于标准C程序而言,链接是固定的。它是ld调用一个缺省的链接脚本来完成的。因此对于一般的应用开发者,几乎感觉不到ld以及链接脚本的存在。

但是如果在一些特殊情况下,主要是底层非操作系统程序。里面很多代码,特别是汇编代码。必须要链接到指定的位置。而且这个时候的程序入口不一定也不是main了。这种情况在bootloader、Linux内核以及裸机程序下比较普遍,这时就要手工编写lds文件了。

总而言之,创建.o可执行文件的最后一步就是链接。它是由ld或者是用gcc间接调用ld来完成的。它主要任务和把外部库和应用程序目标代码的各个段放到正确位置。

那么,本文就先创建一个简单的LDS文件(myboot.lds)来进行链接:

SECTIONS
{
	. = 0xD0020010						//链接完的可执行文件开始运行的地址(即定位器位置)
	.text : {
		mystart.o				//mystart.o的.text最前面
		* (.text)				//剩余文件的.text都放在后面,先后顺序不管
	}
	.data : {
		* (.data)			//所有文件的.data放在一起
	}
	.bss_start = .;				//.bss_start在当前位置
	.bss : {					//可能在其他的文件中用到
		* (.bss)
	}
	.bss_end = .;				//.bss_end在当前位置
}

想要了解更多的内容,可以参考博文:Linux下的lds链接脚本详解。

链接

链接部分使用arm-linux-ld指令:

arm-linux-ld -T myboot.lds -o myboot mystart.o mylowlevel_init.o

查看文件

查看一下mystart.o文件:

arm-linux-objdump -S mystart.o

运行结果如图所示:

【Linux】制作U-Boot烧写镜像到SD卡的过程(中篇:LDS文件)_第2张图片

可以看到,由于未链接所以地址从0开始。前面8句都是跳转到地址20,地址20就是reset语句。但是,可以看到所有的汇编代码相同,但是机器指令却是不同的。

根据机器指令,如何计算出汇编代码呢?

根据ARM的手册,可以查看这些机器指令的含义,这里就不展开了。ea000006就表示向后跳转6条指令;而pc指针等于当前地址+8,即指向后两条指令处。总共是向后跳转8条指令,也就是reset处。这样就可以理解了。

另外,还看到led2_on的汇编代码,由于还未进行链接不知道跳转到哪里去,所以显示的地址为0。

除此之外,还看到gpio_out的代码有所变化,r11变成了fp,r12变成了ip,这其实就是ARM内核将r11、r12寄存器增加了新的功能,但是在一般情形下,也可以当做普通寄存器,不需要管。

还有,由于ARM的每条指令为4个字节,但是立即数0xE0200280直接就已经是4个字节了。因此,ARM就将这一条指令变成了两条指令,一条指令将0xE0200280放在一个新的地址上,另一条指令通过相对指针偏转指令来进行读取

40:     e59fb014    ldr    fp,    [pc, #20]
5c:		e0200280    .word 		0xe0200280

顺便也查看一下mylowlevel_init.o文件

arm-linux-objdump -S mylowlevel_init.o

运行结果如图所示:

【Linux】制作U-Boot烧写镜像到SD卡的过程(中篇:LDS文件)_第3张图片

就不再进行分析了。

最后看一下u-boot可执行文件:

arm-linux-objdump -S myboot

运行结果如图所示:

【Linux】制作U-Boot烧写镜像到SD卡的过程(中篇:LDS文件)_第4张图片

可以看到,起始地址和led2_on的地址都没有问题。

后续工作

得到了可执行文件,但是由于myboot的文件较大,需要生成纯二进制.bin文件。使用objcopy命令:

arm-linux-objcopy -O binary myboot myboot.bin

然后,添加HeaderInfo信息:

./mkv210 u-boot.bin u-boot.16k

最后烧写到SD卡中:

sudo dd iflag=dsync oflag=dsync if=u-boot.16k of=/dev/sdb seek=1

总结起来:

arm-linux-gcc -c mystart.s
arm-linux-gcc -c mylowlevel_init.s			//.s文件生成.o文件

arm-linux-ld -T myboot.lds -o myboot mystart.o mylowlevel_init.o	//.o文件生成可执行文件

arm-linux-objcopy -O binary myboot myboot.bin			//只保留二进制文件

./mkv210 u-boot.bin u-boot.16k					//添加HeaderInfo

sudo dd iflag=dsync oflag=dsync if=u-boot.16k of=/dev/sdb seek=1		//烧写到SD卡

可以看到,如果讲需求改成点亮LED3的话,只需要修改mylowlevel_init.s即可。但是这些命令都还要再次输入一遍,这是很麻烦的事情!

有没有什么简单的办法呢?这就是make的功能了,请看下一篇博文。

你可能感兴趣的:(《操作系统》Linux系统移植)