静态链接——目标文件(二)


相关文章:
静态链接——编译和链接(一)
静态链接——目标文件(二)
静态链接——静态链接(三)
装载与动态链接——装载与进程(一)
装载与动态链接——动态链接(二)

本文主要是《程序员的自我修养》一书的内容摘要和梳理,如有需要并且没有被本文涵盖的内容,建议读者自行观看原书。

在前文中介绍了源代码输入编译器以后,编译器主要做了哪些工作,最后引出链接这个话题,在详细介绍静态链接相关概念之前,需要花费一章的功夫来介绍目标文件这一话题,介绍目标文件里具体都有什么内容,为下面及本书之后的内容做一个铺垫。

目标文件

目标文件从格式上说就是源代码经过编译后但是还没有经过链接的可执行文件,其中有很多符号或有些地址还没有被调整。本章主要是再说目标文件里到底存放了什么,编译以后是如何存储的等比较本质的内容。

目标文件格式

现在比较流行的可执行文件格式主要是 Windows 下的 PE(Portable Excutable)和 Linux 的ELF(Executable Linkable Format),它们都是 COFF(common file format)格式的变种。这里目标文件就是,动态链接库(windows 下的 .dll 和 Linux 下的 .so),静态链接库(windows 下的 .lib 和 Linux 下的 .a),它们都是按照可执行文件的格式存储的。

  • 源代码编译后但是未进行链接的那些中间文件即可重定位文件(Relocatable File,windows 下的 .obj 和 Linux 下的 .o):这类文件包含代码和数据,可以被用来连接成可执行文件或共享目标文件,静态链接库有可以归为这一类。
  • 可执行文件(Excutable File):包含可以直接执行程序,一般都没有扩展名。
  • 共享目标文件(Shared Object File):包含了代码和数据,链接器可以使用这种文件跟其他的可重定位文件和共享文件链接,产生新的目标文件,或者就是动态链接器可以将几种共享目标文件与可执行文件结合。
  • 核心转储文件(Core Dump File):当进程意外中止,系统将该进程中的地址空间的内容及终止时的一些其他信息转储到核心转储文件。

Linux 下可以通过 file 命令来查看文件相应的文件格式。

目标文件是什么样的

在这里插入图片描述
以书中一图概括,目标文件除了包含源码编译后的机器指令代码、数据之后,还会包含一个文件头(File Header),它描述了整个文件的文件属性,包括文件是否可执行、静态链接还是动态链接,入口地址、目标操作等信息,还有段表(Section Table),来描述文件中各个分段的数组,从段表里面可以得到每个段的所有信息。
目标文件都是以 (Segment)或者(Section)的形式存储,总体来说,程序源代码编译以后主要被分成两种段:程序指令和程序数据

将程序的指令和程序数据分开存储好处多多,主要是:

  • 数据和指令分别映射到两个虚拟内存,一般数据对于进行是可读写的,指令一般就只是可读的,所以这两个区域设置不同的读写权限,可以防止程序的指令被有意无意地改写。
  • 另一方面 CPU 有着强大的缓存(Cache)体系,分开存储可以增加CPU的缓存命中率。
  • 程序可能会运行多个副本,它们指令是一样的,但是数据是不一样的,它们是进程私有的。共享指令可以节省大量空间。

目标文件的细节深入

书中主要是以一段示例代码编译成目标文件,来对上面一节的内容做更详尽的表达。

代码段 .text

在这里插入图片描述
.text 段包含是16进制内容,通过 objdump 工具,可以将图中的 16 进制反汇编成本文内容,可以看到其内容正是函数的汇编指令。

数据段 .data 和只读数据段 .rodata

.data 段保存的是初始化了的全局静态变量和局部静态变量。例如代码里2个变量,每个4个字节,所以在目标文件里这个 ,data 段的大小就是 8 字节。

.rodata 段保存的一般都是程序里面的只读变量(如 const 变量)和字符串常量。read only 只读属性,可以保存程序的安全性

.bss 段

原则上讲,.bss 段存放的是未初始化的全局变量和局部静态变量,未初始化的都是0,.bss 段不占磁盘空间。

其他段

除了上面常用的段以外,还包含其他例如 .comment 保存编译器版本信息,.dynamic 动态链接信息,.debug 调试信息 等

自定义段

GCC 还提供了扩展机制,使程序员可以指定变量所处的段,在全局变量或函数之前加上“attribute((section(“name”)))”,就可以把变量或函数放到 “name” 段名的段中。

ELF 文件结构描述

这部分内容比较细节,主要是从数据结构的角度介绍目标文件中相应 section 的具体数据结构及对应含义,有兴趣的读者可以查看原书内容。

符号

目标文件之间相互拼合实际上就是目标文件之间对地址的引用,即对函数和变量的地址的引用。这里对函数和变量统称为符号(Symbol)。链接的正确性是要基于正确的符号管理,每一个目标文件都有一个符号表(Symbol Table),其结构定义了符号名,符号对应的地址,符号大小等信息。

符号名:在编译阶段,编译器会改变我们的函数名,c语言一般是直接加下划线,而c++为了实现函数重载、继承等一系列复杂的特性,会有专门的符号修饰。 c++ 为了和c语言兼容,在符号管理上可以使用 extern "C" 关键字来使编译器把相关代码当作 C 语言来看待。

关于符号名的冲突又会引出另一个概念,弱符号强符号。当多个目标文件里包含相同名字全局符号的定义,链接时会报出符号重定义的错误,这种符号可以定义为强符号强/弱符号只针对定义来说

对于 c/c++ 语言,编译器默认函数和初始化的全局变量是强符号,未初始化的全局变量为弱符号。我们还可以通过:__attribute__((weak)) 定义任何一个强符号为弱符号

extern int ext;  // 是外部引用,不是定义,非强符号,也非弱符号
int weak;  // 弱符号
int strong = 1; //强符号
__attribute__((weak)) weak2 = 2; // 弱符号

针对强弱符号的概念,连接器对于重复定义的问题可以制定规则处理:

  • 不允许强符号被多次定义
  • 如果一个符号在某个目标文件是强符号,其他目标文件是弱符号,选强符号
  • 如果都是弱符号,选占用空间最大的。

与之相对应的概念还有,强引用弱引用。遇到链接符号未定义错误的一般称为强符号,弱引用连接器找不到不报错。

弱引用,弱符号对库来说十分有用,库中定义的弱符号可以被用户定义的强符号覆盖,把程序的扩展功能块定义为弱引用,功能块与程序链接在一起时,功能块可以正常使用,当去掉某功能块时,程序也能正常链接,只是缺少了相应功能,更方便程序的裁剪和组合

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值