首页 > 代码库 > 重定位和链接脚本
重定位和链接脚本
大部分指令是“位置有关编码”
- 位置无关编码:汇编源文件编码成二进制可执行程序时,编码方式与位置无关。
在我们写程序时,必须给编译器链接器指定地址。将来的程序被执行时必须放在当时编译链接时给定的地址才能运行。
- 位置有关编码:汇编源码编码成二进制可执行程序后和内存地址是有关的。
但是也有一种特别的指令他可以跟指定的链接地址没有关系,这些代码不管放在哪里都可以正常运行。
分析:
之前的裸机程序中,makefile中用-Ttext 0x0来指定链接地址是0x0;这意味着我们认为将来这个程序会被放在0x0这个地址去运行。但是实际上我们在DNW中,把程序下载到0xd0020010. 在s5pv210中,由于在内部做了映射,所以0xd002_0010和0x0000_0010是一样的。那么为什么放在0x0而不是0x0000_0010依旧可以呢?原因就这个是位置无关编码。
- 链接地址:链接时指定的地址(指定方式:makefile中用T-text,或者链接脚本)
- 运行地址:程序实际运行的地址(指定方式:由实际运行时被加载到内存那个位置说了算)
在linux中的应用程序:gcc hello.c -o hello
- 这时候默认的链接地址就是0x0,所以链接在0地址,因为应用程序运行在操作系统的一个进程中,这个进程独享了4G的内存空间,所以应用程序可以链接到0地址,因为每个进程都是从0地址开始的。
- 210中的裸机程序运行地址由我们下载时确定,下载时下载到0xd0020010,所以就从这里开始运行。(这个下载地址是iROM中的BL0加载BL1时实现指定好的地址,这个是由CPU设计时候决定的)。所以理论上我们编译链接时应该指定到0xd0020010,但是实际上我们之前的裸机程序都是使用位置无关码PIC,所以链接地址可以是0
再分析s5pv210的启动过程
三星推荐的启动方式:
bootloader必须大于16KB小于96KB,假定为80KB。启动过程如下:开机上电后BL0运行,BL0加载外部启动设备中的bootloader的前16KB(BL1)到SRAM中运行,BL1运行后会加载BL2到(80-16=64KB)到SRAM中运行,BL2运行时会初始化DDR并且将OS搬到DDR中执行,启动完成。
uboot实际使用方式:
uboot大小随意,假定为200KB,启动过程:先开机上电后BL0运行,BL0会加载外部启动设备中的uboot的前16KB到SRAM运行,BL1运行时会初始化DDR,然后将整个uboot搬运到DDR中,然后用一句长跳转指令从SRAM直接跳转到DDR中继续执行uboot直到uboot完全启动。uboot启动后在命令行中启动OS
为什么需要重定位?
- 原因:链接地址和运行地址有时候必须不相同,而且不能全部用位置无关码,这种就只能重定位
- 扩展:分散加载:把uboot分为2部分(BL1和整个uboot)两个部分分别制定不同的链接地址。启动时将两部分加载到不同的地址(BL1加载到SRAM,整个uboot加载到DDR),这时候不用重定位也能启动
- 评价:分散加载其实相当于手工重定位,重定位是用代码来重定位,分散加载是手工操作重定位
从源码到可执行程序的步骤:
- 预编译:预编译器执行。譬如C中的宏定义就是由预编译处理器处理,注释等也是由预编译器处理的
- 编译:编译器来执行。把源码.c .S编译成机器码
- 链接:连接器来执行。把.o文件中的各个程序按照一定的规则(链接脚本指定)累积在一起
strip:strip是把可执行程序中的符号给拿掉,以节省空间(debug版本和release版本)
objcopy:由可执行程序生成可烧录的镜像bin文件
程序段的概念:代码段、数据段、bss段(ZI段)、自定义段
段是程序的一部分,我们把整个程序的所有东西分成了一个个段,给每个段起一个名字,然后再链接时可以用和这个名字来指示这些段
段名分为2种:一种是编译器连接器内部定好的,一种是程序员自己指定的,自定义的段名
先天性段名:
- 代码段:(.text),又叫文本段,代码段其实就是函数编译后生成的东西
- 数据段:(.data),数据段就是C语言中显示初始化为非0的全局变量
- bss段:(.bss),又叫ZI(zero initial)段,就是零初始化段,对应C语言中初始化为0的全局变量
后天性段名:
段名由程序员自己定义,段的属性和特征也由程序员自己定义。
分析一些问题,跟这里结合,然后试图明白一些本质
- C语言中全局变量如果未显示初始化,值为0。本质上是因为C语言中我们将这类全局变量放在了bss段,从而保证了为0
- C运行时环境如何保证显示初始化为非0的变量在main函数之前就被赋值了,就是因为把这类变量放在了.data段了,而.data段会在main函数之前被处理。
链接脚本就是一个规则文件,他是程序员用来指挥链接器用来工作的。链接器会参考链接脚本并且使用其中规定的规则来处理.o文件中的那些段,将其链接成可执行程序。
链接脚本的关键内容有两个部分:段名+地址(作为链接地址的内存地址)
链接脚本的理解:
SECTIONS{}这个是整体链接脚本
. 点号在链接脚本中代表当前位置
= 等号代表赋值
SECTIONS { . = 0xd0024000 .text : { start.o *(.text) } .data : { *(.data) } bss_start = .; .bss : { *(.bss) } bss_end = .; }
重定位和链接脚本