正常情况下,我们使用链接器的默认规则对目标文件进行链接是没问题的。但是在一些特殊的场景,比如操作系统内核,BIOS,嵌入式软件,BootLoader,内核驱动程序下,因为一些受限制的条件,我们需要显示的指明程序的各个段的起始地址,段的名称,段存放的顺序等等。
而且连接过程中有很多内容需要我们显示的确定,比如该使用哪些目标文件,使用哪些库文件,是否在最终的可执行文件中保留调试信息,输出哪种文件格式(可执行程序还是动态链接库程序)等等。
因此这些场景下我们需要自己控制链接的过程。控制链接过程的方法有两种:使用ld的命令行选项和使用链接脚本。
1.ld命令
ld是GNU提供的链接器,它有许许多多的命令行选项来完成不同的功能配置。例如前文中我们把a.o和b.o链接成可执行文件ab时,命令如下:
ld a.o b.o -e main -o ab
这里的-e和-o都是命令行选项,作用如下:
- '-o output' 使用output作为链接后输出的可执行程序的名称
- '-e entry' 使用符号entry作为程序的起始地址
除此之外,ld还有很多其他的命令行选项,诸如:
- '-s' 去除输出文件的所有符号信息
- '-S' 去除输出文件的所有调试符号信息
- '-N' 将.text段和.data段设置为可读,同时取消.data段的页对齐,并取消共享库的链接称
- '--no-omagic' 将.text段设置为只读,强制.data段的页对齐
- '-T ScriptFile' 使用ScriptFile作为链接器脚本,该脚本会替代连接器的缺省脚本
ld作为内置在几乎所有类unix操作系统发行版中的链接器,他的功能十分强大,完整的命令行选项和使用方法可以参考GNU手册:ld(1): GNU linker - Linux man page
2.链接脚本
除了命令行之外,更灵活的方法是我们自定义链接脚本来替代默认的链接缺省脚本来进行链接处理。例如链接脚本文件LdScript.lds如下所示:
我们使用刚刚介绍的方法,指定使用该链接脚本作为链接的缺省脚本,命令如下,最终生成了可执行文件ab_Manu。
ld a.o b.o -T LdScript.lds -o ab_Manu
我们简单介绍一下LdScript.lds中的每行语句的意思:OUTPUT_FORMAT("elf64-x86-64")表示输出文件的格式为x86平台,64位的Elf64文件格式;OUTPUT_ARCH(i386:x86-64)表示输出文件对应的机器体系结构是x86-64;ENTRY(main)表示使用main函数作为输出程序的程序起始地址;SECTIONS{...}表示定义输出文件的段。
我们观察SECTIONS{...}内部定义的段,. = SEGMENT_START("text-segment", 0x400000);表示当前的地址为.text段的起始地址,若该段不存在,则当前地址为0x400000;.text : { *(.text) }表示输出文件的.text段由所有输入文件的.text段合并组成;因为当前地址为0x400000,而.text段紧跟在当前地址后面排列,因此可以知道.text段的起始地址为0x400000。同理,我们可知.data段的起始地址为0x600000,.bss段紧跟在.data段后排列; /DISCARD/ : { *(.comment) }表示丢弃所有输入文件的.comment段。
所以使用readelf查看链接生成的可执行程序ab_Manu,可以发现.text段和.data段的起始地址确实为0x400000和0x600000。
这里只对ld脚本做了一个简单地介绍,关于ld脚本的详细语法,可以查看ld语法手册Scripts (LD)