当前位置:首页 > 公众号精选 > 程序喵大人
[导读]对于静态链接先提出两个问题: Q: 每个目标文件都有好多个段,目标文件在被链接成可执行文件时,输入目标文件中的各个段如何被合并到输出文件? A: 合并相似的段,将所有的.text段合并到输出文件的.text段,将所有的.data段合并到输出文件的.data段。 Q: 链接


对于静态链接先提出两个问题:


Q:

每个目标文件都有好多个段,目标文件在被链接成可执行文件时,输入目标文件中的各个段如何被合并到输出文件?


A:

合并相似的段,将所有的.text段合并到输出文件的.text段,将所有的.data段合并到输出文件的.data段。




Q:

链接器如何为他们分配在输出文件中的空间和地址?


A:

这里涉及到程序链接的两个步骤:


  1. 空间与地址分配:扫描所有的输入目标文件,获得它们每个段的长度属性和位置,收集输入目标文件中的符号表中的所有符号定义和符号引用,统一放到一个全局符号表中,合并所有的段,计算出输出文件中各个段合并后的长度和位置,并建立映射关系。


  2. 符号解析与重定位:使用第一步收集到的所有信息,读取输入文件中段的数据及重定位信息,进行符号解析和重定位,调整代码中的地址,将每个段中需要重定位的指令和数据进行“修补”,使他们都指向正确的位置。




Tips:



外部符号指的是目标文件需要引用的符号,但是定义在其它目标文件中,链接前外部符号地址都是000000之类,链接后的可执行文件就可以看见这些外部符号都是有地址的。链接就是把相似的段放在一起,先找到段的偏移地址,再找出符号在段中的偏移,这样可以确定符号在整个可执行程序中的地址。


对于那些需要重定位的符号,都会放在重定位表里,也叫重定位段,即.rel.data、.rel.text等,如果.text段有被重定位的地方,就有.rel.text段,如果.data段有被重定位的地方,就有.rel.data段。



可以使用objdump查看目标文件的重定位表。


源代码:

int main() { printf("程序喵\n"); return 0;}gcc -c test


objdump -r test.o
test.o: file format elf64-x86-64
RELOCATION RECORDS FOR [.text]:OFFSET TYPE VALUE0000000000000007 R_X86_64_PC32 .rodata-0x0000000000000004000000000000000c R_X86_64_PLT32 puts-0x0000000000000004

RELOCATION RECORDS FOR [.eh_frame]:OFFSET TYPE VALUE0000000000000020 R_X86_64_PC32 .text

使用nm也可以查看需要重定位的符号:

nm -u test.o U _GLOBAL_OFFSET_TABLE_ U puts

对于UND类型,这种未定义的符号都是因为该目标文件中有关于他们的重定位项,在链接器扫描完所有的输入目标文件后,所有这种未定义的符号都应该能在全局符号表中找到,否则报符号未定义错误。

注意:我们代码里明明用的是printf,为什么它却引用了puts的符号呢,因为编译器默认情况下会把只用一个字符串参数的printf替换成puts, 可以节省格式解析的时间,使用-fno-builtin会关闭这个内置函数优化选项,如下:

~/test$ gcc -c -fno-builtin testlink.cc -o test.o~/test$ nm test.o U _GLOBAL_OFFSET_TABLE_0000000000000000 T main U printf


Tips:



现在的程序和库通常来讲都很大,一个目标文件可能包含成百上千个函数或变量,当需要用到某个目标文件的任意一个函数或变量时,就需要把它整个目标文件都链接进来,也就是说那些没有用到的函数也会被链接进去,这会导致链接输出文件变的很大,造成空间浪费。



有一个编译选项叫函数级别链接,可以使得某个函数或变量单独保存在一个段里面,都链接器需要用到某个函数时,就将它合并到输出文件中,对于没用到的函数则将他们抛弃,减少空间浪费,但这会减慢编译和链接过程,GCC编译器的编译选项是:
-ffunction-sections-fdata-sections

可能很多人都会以为程序都是由main函数开始执行和结束的,但其实不是,在main函数调用之前,为了保证程序可以顺利进行,要先初始化进程执行环境,如堆分配初始化、线程子系统等,C++的全局对象构造函数也是这一时期被执行的,全局析构函数是main之后执行的。

Linux一般程序的入口是__start函数,程序有两个相关的段:

init段:进程的初始化代码,一个程序开始运行时,在main函数调用之前,会先运行.init段中的代码。
fini段:进程终止代码,当main函数正常退出后,glibc会安排执行该段代码。

如何指定程序入口

在ld链接过程中使用-e参数可以指定程序入口,由于一段简短的printf函数其实都依赖了好多个链接库,我们也不太方便使用链接脚本将目标文件与所有这些依赖库进行链接,所以使用下面这段内嵌汇编的程序来打印一段字符串,这段程序不依赖任何链接库就可以打印出字符串内容,读者如果不懂其中的含义也不用担心,只需要了解下面介绍的链接知识就好。


代码如下:


const char* str = "hello";
void print() { asm("movl $13,%%edx \n\t" "movl str,%%ecx \n\t" "movl $0,%%ebx \n\t" "movl $4,%%eax \n\t" "int $0x80 \n\t" : :"r"(str):"edx", "ecx", "ebx");}

void exit() { asm("movl $42,%ebx \n\t" "movl $1,%eax \n\t" "int $0x80 \n\t");}
void nomain() { print(); exit();}

使用如下命令生成目标文件:

gcc -c -fno-builtin test.cc


看下输出的test.o的符号:

~/test$ nm -a test.o0000000000000000 b .bss0000000000000000 n .comment0000000000000000 d .data0000000000000000 d .data.rel.local0000000000000000 r .eh_frame0000000000000000 n .note.GNU-stack0000000000000000 r .rodata0000000000000000 t .text0000000000000026 T _Z4exitv0000000000000000 T _Z5printv0000000000000039 T _Z6nomainv0000000000000000 D str0000000000000000 a test.cc

这里由于我的源文件是.cc结尾,所以是以c++方式编译的,所以符号变成了上面的形式,如果变成了test.c,符号如下:

~/test$ gcc -c -fno-builtin test.c -o test.o~/test$ nm -a test.o0000000000000000 b .bss0000000000000000 n .comment0000000000000000 d .data0000000000000000 d .data.rel.local0000000000000000 r .eh_frame0000000000000000 n .note.GNU-stack0000000000000000 r .rodata0000000000000000 t .text0000000000000026 T exit0000000000000039 T nomain0000000000000000 T print0000000000000000 D str0000000000000000 a test.c

再使用-e指定入口函数符号:

~/test$ ld -static -e nomain -o test test.o~/test$ ./testhello

如何使用自定义链接脚本实现自定义段的功能
在ld链接过程中使用-T参数可以指定链接脚本,通过ld -verbose可以查看默认的链接脚本,原文太长,这里简单截取了一部分:

$ ld -verboseGNU ld (GNU Binutils for Ubuntu) 2.30 Supported emulations: elf_x86_64 elf32_x86_64 elf_i386 elf_iamcu i386linux elf_l1om elf_k1om i386pep i386peusing internal linker script:==================================================/* Script for -z combreloc: combine and sort reloc sections *//* Copyright (C) 2014-2018 Free Software Foundation, Inc. Copying and distribution of this script, with or without modification, are permitted in any medium without royalty provided the copyright notice and this notice are preserved. */OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64", "elf64-x86-64")OUTPUT_ARCH(i386:x86-64)ENTRY(_start)SEARCH_DIR("=/usr/local/lib/x86_64-linux-gnu"); SEARCH_DIR("=/lib/x86_64-linux-gnu"); SEARCH_DIR("=/usr/lib/x86_64-linux-gnu"); SEARCH_DIR("=/usr/lib/x86_64-linux-gnu64"); SEARCH_DIR("=/usr/local/lib64"); SEARCH_DIR("=/lib64"); SEARCH_DIR("=/usr/lib64"); SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib"); SEARCH_DIR("=/usr/x86_64-linux-gnu/lib64"); SEARCH_DIR("=/usr/x86_64-linux-gnu/lib");SECTIONS{ /* Read-only sections, merged into text segment: */ PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;
.init : { KEEP (*(SORT_NONE(.init))) } .plt : { *(.plt) *(.iplt) } .plt.got : { *(.plt.got) } .plt.sec : { *(.plt.sec) } .text : { *(.text.unlikely .text.*_unlikely .text.unlikely.*) *(.text.exit .text.exit.*) *(.text.startup .text.startup.*) *(.text.hot .text.hot.*) *(.text .stub .text.* .gnu.linkonce.t.*) /* .gnu.warning sections are handled specially by elf32.em. */ *(.gnu.warning) } .fini : { KEEP (*(SORT_NONE(.fini))) } .rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) } /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }}

这里自定义一个简单的链接脚本test.lds

ENTRY(nomain)
SECTIONS{ . = 0x8048000 + SIZEOF_HEADERS; tinytext : { *(.text) *(.data) *(.rodata) } /DISCARD/ : { *(.comment) }}

再使用-T指定链接脚本:

~/test$ ld -static -T test.lds -e nomain -o test test.o~/test$ ./testhello

上面的tinytext一行是指将.text段、.data段、.rodata段的内容都合并到tinytext段中,使用readelf查看段的信息。

~/test$ readelf -S test~/test$ There are 6 section headers, starting at offset 0x482a0:
Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .eh_frame PROGBITS 00000000080480b0 000480b0 0000000000000078 0000000000000000 A 0 0 8 [ 2] tinytext PROGBITS 0000000008048128 00048128 0000000000000066 0000000000000000 WAX 0 0 8 [ 3] .shstrtab STRTAB 0000000000000000 0004826e 000000000000002e 0000000000000000 0 0 1 [ 4] .symtab SYMTAB 0000000000000000 00048190 00000000000000c0 0000000000000018 5 4 8 [ 5] .strtab STRTAB 0000000000000000 00048250 000000000000001e 0000000000000000 0 0 1Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), l (large) I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific)


工具小贴士

关于静态链接库:

ar rcs libxxx.a xx1.o xx2.o 打包静态链接库ar -t libc.a 查看静态链接库里都有什么目标文件ar -x libc.a 会解压所有的目标文件到当前目录gcc --verbose 可以查看整个编译链接步骤

关于objdump:

objdump -i 查看本机目标架构objdump -f 显示文件头信息objdump -d 反汇编程序objdump -t 显示符号表入口,每个目标文件都有什么符号objdump -r 显示文件的重定位入口,重定位表objdump -x 显示所有可用的头信息,等于-a -f -h -r -tobjdump -H 帮助

关于分析ELF文件格式:

readelf -h 列出文件头readelf -S 列出每个段readelf -r 列出重定位表readelf -d 列出动态段

关于查看目标文件符号信息:

nm -a 显示所有的符号nm -D 显示动态符号nm -u 仅显示没有定义的外部符号nm -defined-only 仅显示定义的符号

关于符号的说明:

如果符号类型是小写的,表明符号是局部符号,大写表示符号是全局符号。


A:该符号的值是绝对的,在以后的链接过程中,不允许进行改变。这样的符号值,常常出现在中断向量表中,例如用符号来表示各个中断向量函数在中断向量表中的位置。
B:该符号的值出现在.bss段中,未初始化的全局和静态变量。
C:该符号的值在COMMON段中,里面的都是弱符号。
D:该符号位于数据段中。
I:该符号对另一个符号的间接引用
N:debug符号
R:该符号位于只读数据区
T:该符号位于代码段
U:该符号在当前文件未定义,定义在别的文件中
?:该符号类型没有定义

参考资料

https://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/

《程序员的自我修养》




c++11新特性,所有知识点都在这了!

你的c++团队还在禁用异常处理吗?

内存对齐之格式修订版

c++11新特性之智能指针

gcc a.c 究竟经历了什么?

谈谈程序链接及分段那些事

免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

9月2日消息,不造车的华为或将催生出更大的独角兽公司,随着阿维塔和赛力斯的入局,华为引望愈发显得引人瞩目。

关键字: 阿维塔 塞力斯 华为

加利福尼亚州圣克拉拉县2024年8月30日 /美通社/ -- 数字化转型技术解决方案公司Trianz今天宣布,该公司与Amazon Web Services (AWS)签订了...

关键字: AWS AN BSP 数字化

伦敦2024年8月29日 /美通社/ -- 英国汽车技术公司SODA.Auto推出其旗舰产品SODA V,这是全球首款涵盖汽车工程师从创意到认证的所有需求的工具,可用于创建软件定义汽车。 SODA V工具的开发耗时1.5...

关键字: 汽车 人工智能 智能驱动 BSP

北京2024年8月28日 /美通社/ -- 越来越多用户希望企业业务能7×24不间断运行,同时企业却面临越来越多业务中断的风险,如企业系统复杂性的增加,频繁的功能更新和发布等。如何确保业务连续性,提升韧性,成...

关键字: 亚马逊 解密 控制平面 BSP

8月30日消息,据媒体报道,腾讯和网易近期正在缩减他们对日本游戏市场的投资。

关键字: 腾讯 编码器 CPU

8月28日消息,今天上午,2024中国国际大数据产业博览会开幕式在贵阳举行,华为董事、质量流程IT总裁陶景文发表了演讲。

关键字: 华为 12nm EDA 半导体

8月28日消息,在2024中国国际大数据产业博览会上,华为常务董事、华为云CEO张平安发表演讲称,数字世界的话语权最终是由生态的繁荣决定的。

关键字: 华为 12nm 手机 卫星通信

要点: 有效应对环境变化,经营业绩稳中有升 落实提质增效举措,毛利润率延续升势 战略布局成效显著,战新业务引领增长 以科技创新为引领,提升企业核心竞争力 坚持高质量发展策略,塑强核心竞争优势...

关键字: 通信 BSP 电信运营商 数字经济

北京2024年8月27日 /美通社/ -- 8月21日,由中央广播电视总台与中国电影电视技术学会联合牵头组建的NVI技术创新联盟在BIRTV2024超高清全产业链发展研讨会上宣布正式成立。 活动现场 NVI技术创新联...

关键字: VI 传输协议 音频 BSP

北京2024年8月27日 /美通社/ -- 在8月23日举办的2024年长三角生态绿色一体化发展示范区联合招商会上,软通动力信息技术(集团)股份有限公司(以下简称"软通动力")与长三角投资(上海)有限...

关键字: BSP 信息技术
关闭
关闭