编译器编译源代码后生成的文件叫做目标文件
目标文件从结构上讲,它是已经编译后的可执行文件格式,只是没有经过链接的过程。

3.1目标文件的格式

现在PC平台流行的是可执行文件格式,主要是win下的PE和Linux的ELF,它们都是COFF格式的变种。

不光是可执行文件按照可执行文件格式存储。动态链接库和静态链接库文件都是按照可执行文件格式存储。

ELF文件类型 说明 实例
可重定位文件 包含代码和数据,可以被用来链接成可执行文件或共享目标文件,静态链接库也是这种 Linux的.0。win的.obj
可执行文件 包含可以直接执行的程序,ELF可执行文件,一般没有扩展名 win的.exe文件
共享目标文件 包含代码和数据,可以在两种情况下使用:1) 链接器可使用这种文件和其他可重定位文件和共享目标文件链接,产生新的目标文件 Linux的.so,win的DLL
核心转储文件 当进程意外终止,系统可以将进程的地址空间的内容以及终止时的其他信息转储到核心转储文件 Linux下的core dump

3.2目标文件是什么样的

目标文件中包含机器指令代码,数据,还包括里链接时所需要的一些信息:符号表、调试信息、字符串等。目标文件将这些信息按节的形式存储,也叫做段。

程序源代码编译后的机器指令放在代码段里(.code .text),全局变量和局部静态变量数据经常放在数据段(.data),未初始化的全局变量和局部静态变量一般放在叫(bss)段里,.bss中只是为未初始化的全局变量和局部静态变量预留位置,它没有内容,所以在文件中也不占据空间。
文件头描述了整个文件的属性,文件头还包括一个段表,段表就是一个描述文件中各个段的数组。
程序源代码被编译以后主要分成两种段:程序指令和程序数据。代码段属于程序指令,数据段和bss属于程序数据。

3.3挖掘SimpleSection.o

真正了不起的程序员对自己的程序的每一个字节都了如执掌

/*XimpleSection.c*/
int printf(const char* format,...)int global_init_var=84;
int global_uninit_var;void func1(int i)
{
printf("%d\n",i);
}
int main(void)
{
static int static_var=85;
static int static_var2;
int a=1;
int b;func1(static_var + static_var2 +a+b);return a;
}

执行 $ gcc -c SimpleSection.c
得到一个1104字节的SimpleSection.o目标文件,使用binutils查看SimpleSection.o内部结构:

上面除了最基本的代码段、数据段、BSS段以外,还有只读数据段、注释信息段、堆栈提示段。
每个段的第二行中“CONTENTS”,”ALLOC”表示段的各种属性,BSS段没有CONTENTS,表示它在ELF中没有内容。

3.3.1 代码段

3.3.2 数据段和只读数据段

.data段保存的是那些已经初始化了的全局静态变量和局部静态变量。两个变量共四字节,一共就是data段大小8字节。
字符串常量”%d/n”,被放到了”.rodata”段,”.rodata”段四个字节刚好是字符串常量的ASCII字节序。
“.rodata”段存放的是只读数据,是程序里面的只读变量和字符串常量。

3.3.3  BBS段

.bss段存放的是未初始化的全局变量和局部静态变量,上述代码global_uninit_var和static_var2 就被存放在.bss段中。但有些编译器不会把未初始化的全局变量存放在目标文件.bss段中,只是预留一个未定义的全局变量。但编译单元内部可见的静态变量(给global_uninit_var加上static修饰)则是存放在bss段的。

变量存放位置
static int x1=0;//f3放在BBS段中,被认为未初始化的
static int x2=1;

3.3.4 自定义段

GCC提供一个扩展机制,可以指定变量所处的段:

_attribute_((section("FOO"))) int global=42;
_attribute_((section("FOO"))) int FOO()

全局变量或函数之前加上”attribute((section(“name”)))”属性就可以把相应的变量或函数放到以”name”作为段名的段中。

3.4 ELF文件结构描述

3.4.1 文件头

可以使用readelf命令来详细查看ELF文件。

ELF文件有32位版本和64位版本,文件头结构也有两个版本,叫做”Elf32_Ehdr”和”Elf64_Ehdr”.文件头内容是一样的,不过有些成员大小不一样。为了对每个成员大小做出明确规定以便于在不同的编译环境下都拥有相同的字段长度,”elf.h”使用typedef定义了一套自己的变量体系:

ELF文件头中各个成员含义与readelf输出结果的对照表:

ELF魔数

几乎所有可执行文件格式的最开始几个字节都是魔数,比如a.out开始两个字节为0X01,0X07。这种魔数用来确定文件类型,操作系统加载可执行文件的时候会确定魔数是否正确。

文件类型

e_type成员表示ELF文件类型。系统通过这个常量来判断ELF的真正文件类型,而不是通过扩展名。

机器类型

ELF文件格式被设计成可以在多个平台下使用,但不表示同一个ELF文件可以在不同平台下使用,是不同平台下的ELF文件遵循同一套ELF标准。e_machine成员就表示该ELF文件的平台属性。

3.4.2 段表

段表就是保存ELF中各种段的基本属性的结构。它描述了各个段的信息。ELF文件的段结构就是由段表决定的,编译器,链接器和转载器都是依靠段表来定位和访问各个段的属性的。ELF文件中的位置由ELF文件头的”e_shoff”成员决定。
使用readlf工具查看段表结构:

段表结构是一个以”Elf32_Shdr”结构体为元素的数组。数组元素的个数等于段的个数,每个结构体对应一个段。”Elf32_Shdr”又被描述为段描述符。ELF段表的第一个元素是无效的段描述符,类型为NULL。

段表的位置:

段的类型

段的名字只是在链接和编译过程中有意义,它不能真正表示段的类型。

段的标志位

段的标志位表示该段在进程虚拟地址空间的属性,相关常量以SHF_开头

段的链接信息

如果段的类型与链接相关,那么sh_link和sh_info这两个成员所包含的意义如下图:(其他类型的段,这两个成员没有意义)

3.4.3 重定位表

“.rel.text”的段,类型是”SHT_REL”它是一个重定位表。链接器处理目标文件的时候必须对目标文件中某些部位进行重定位,这些重定位信息就存在重定位表中。”.rel.text”就是针对”.text”段的重定位表。

3.4.4 字符串表

因为字符串的长度往往是不定的,所以用固定的结构来表示它比较困难。常见的做法是把字符串集中起来存放一个表,然后使用字符串在表中的偏移来引用字符串。

3.5 链接的接口——符号

链接过程的本质就是要把多个不同的目标文件之间相互”粘”到一起,为了使不同目标文件之间能够相互粘合,这些目标文件必须有固定的规则才行。目标文件之间的相互拼合实际上时对地址的引用。链接中,我们将函数和变量统称为符号,函数名或变量名就是符号名
每一个目标文件都有一个对应的符号表,这个表里面记录了目标文件所用到的所有符号,每个定义的符号有一个值,这个值叫做符号值
将符号表中所有符号进行分类,它们可能是下面类型中的一种:

  • 定义在本目标恩健的全局符号,可以被其他目标文件引用。
  • 在本目标文件中引用的全局符号,没有定义在本目标文件内,一般叫做外部符号
  • 段名。这种符号往往由编译器产生,它的值就是该段的起始地址。
  • 局部符号,这类符号只能在编译单元内部可见
  • 行号信息,即目标文件指令与源代码中代码行的对应关系。

链接过程只关系全局符号的相互”粘合”。
使用nm查看符号结果如下:

3.5.1 ELF符号表结构

ELF文件中的符号表往往是文件中的一个段,段名一般叫”.symtab”,每个Elf32_Sym结构对应一个符号。

符号类型和绑定信息

该成员低4位表示符号的类型,高28位表示符号绑定信息。

符号所在段

如果符号定义在本目标文件中,那么这个成员表示符号所在的段在段表中的下标,如果符号不是定义在本目标文件中或者对于有些特殊符号,st_shndx的值有些特殊。

符号值

每个符号都有一个对应的值。如果这个符号是一个函数或者变量的定义,那么符号值就是这个函数或变量的地址。

3.5.2 特殊符号

当我们使用id作为链接器来链接生产可执行文件时,它会为我们定义很多特殊的符号,这些符号并没有在你的程序中定义,但时你i可以直接声明并且引用它,我们称为特殊符号。

3.5.3 符号修饰与函数签名

编译器编译源代码产生目标文件时,符号名与相应的变量和函数的名字一样的,将会产生冲突。为了防止冲突,C语言源代码文件中的所有全局变量和函数经过编译以后,相对应的符号名前加上下划线”_”,后来增加了名称空间的方法来解决多模块的符号冲突问题。

C++符号修饰

C++符号修饰为了更好的区分两个函数,看下面代码:

int func(int);
float func(float);class C
{
int func(int);
class C2{
int func(int);
};
};namespace N{
int func(int);
class C{
int func(int);
};
}

函数签名:函数签名包含一个函数的信息,包括函数名,参数类型,所在类,名称空间和其他信息。函数签名用于识别不同的函数。
在编译器及链接器处理符号时,它们使用某种名称修饰的方法,使得每个函数签名对应一个修饰后名称
上面6个函数签名在GCC编译器下,相对应的修饰后名称如下图:

GCC的基本C++名称修饰方法如下:所有的符号都以”_Z”开头,对于嵌套的名字,后面紧跟”N”,然后时各个名称空间和类的名字,每个名字前名字字符串长度,再以”E”结尾。参数列表紧跟在”E”后面,对于int类型来说,就是字母”i”。
名称修饰机制也被用来防止静态变量的名字冲突。
不同的编译器厂商名称的修饰方法可能不同。

3.5.4 extern "C"

C++为了与C兼容,在符号管理上,C++有一个用来声明或定义一个C符号的”extern C”关键字。
用法:

extern "C"{
int func(int);
int var;
}

C++编译器将在extern “C”的大括号内部的代码当作C代码处理。
单独声明某个函数或者变量为C语言的符号,使用如下格式:

extern "C" int func(int);
extern "C" int var;

3.5.5 弱符号与强符号

多个目标文件中含有相同名字全局符号的定义,那么这些目标文件链接的时候将会出现符号重复定义的错误,这中符号称为强符号,有些符号可以定义为弱符号。对于C/C++,编译器默认函数和初始化了的全局变量为强符号,未初始化的全局变量未弱符号。

针对强弱符号的概念,连接器就会按如下规则处理与选择被多次定义的全局符号:

  • 规则1:不允许强符号被多次定义
  • 规则2:如果一个符号在某个目标文件中是强符号,在其他文件中都是弱符号,那么选择强符号
  • 规则3:如果一个符号在所有目标文件中都是弱符号,那么选择其中占用空间最大的一个

3.6 调试信息

目标文件里面还有可能保存调试信息。

编译器提前将源代码与目标代码之间的关系保存到目标文件中。

转载于:https://www.cnblogs.com/Tan-sir/p/7337569.html

程序员的自我修养三目标文件里有什么相关推荐

  1. 【读书笔记】【程序员的自我修养 -- 链接、装载与库(三)】函数调用与栈(this指针、返回值传递临时对象构建栈、运行库与多线程、_main函数、系统调用与中断向量表、Win32、可变参数、大小端

    文章目录 前言 介绍 内存 内存布局 栈与调用惯例 堆与内存管理 运行库 入口函数和程序初始化 C/C++运行库 运行库与多线程 C++全局构造与析构 fread 实现 系统调用与API 系统调用介绍 ...

  2. 《程序员的自我修养》

    <程序员的自我修养>这本书偏底层,来来回回读了有三四遍了,每一次都有新的收获,不过很快又会忘记,所以写下了这本书从17年12月份至今的全书的笔记,留作以后自己复习. 第二章:编译和链接 源 ...

  3. 程序员的自我修养(2)——计算机网络(转) good

    相关文章:程序员的自我修养--操作系统篇 几乎所有的计算机程序,都会牵涉到网络通信.因此,了解计算机基础网络知识,对每一个程序员来说都是异常重要的. 本文在介绍一些基础网络知识的同时,给出了一些高质量 ...

  4. 程序员的自我修养(2)——计算机网络

    本文转载至 http://kb.cnblogs.com/page/211867/ 来源: Cricode  发布时间: 2014-07-04 12:39  阅读: 979 次  推荐: 2   原文链 ...

  5. 《程序员的自我修养》读书总结

    http://www.jianshu.com/p/47156b4259ed 最初买<程序员的自我修养>这本书,只因为在京东买书差一些钱,不够用优惠券.买回来以后的很长一段时间,我都以为这本 ...

  6. 程序员的自我修养—链接、装载与库 笔记

    程序员的自我修养-链接.装载与库 笔记 内存管理 直接使用物理内存地址 虚拟内存-分段 虚拟内存-分页 分页和分段的主要区别 段页式 代码生成过程 预处理 编译 词法分析 语法分析 语义分析 源代码优 ...

  7. 从实践理解《程序员的自我修养》(1)

    从实践理解<程序员的自我修养>(1) 前言 这篇文档主要从实践的角度充分理解<程序员的自我修养>一书中提到的细节.书中提到的各种机制.数据结构,我都将在实际系统中找到并理解它们 ...

  8. 《程序员的自我修养》导读

    大家好,我是Cone,一名毕业于双非本科的抖音全栈程序猿. 今天来和大家分享<程序员的自我修养----链接.装载与库>这本书的全书导读经验,它在去年我拿下微信.抖音.百度等大厂sp及以上o ...

  9. 【读书笔记】【程序员的自我修养 -- 链接、装载与库(二)】进程虚拟地址空间、装载与动态链接、GOT、全局符号表、共享库的组织、DLL、C++与动态链接

    文章目录 前言 介绍 可执行文件的装载与进程 进程虚拟地址空间 装载方式 操作系统对可执行文件的装载 进程虚存空间分布 ELF文件的链接视图和执行视图 堆和栈 Linux 内核装载ELF & ...

最新文章

  1. 完美的隐藏软键盘方法
  2. 正则 不区分大小写_为什么要学正则表达式 7
  3. DeepMind大招,以视觉为媒介,做无监督机器翻译,效果极好
  4. 七段液晶数字识别-处理程序
  5. 用shell脚本计算日期的小函数们
  6. suse linux mysql_SUSE Linux 下 MySQL集群配置
  7. Maven快照机制(SNAPSHOT)
  8. 团队作业1——团队展示选题
  9. WCF分布式开发常见错误(26):Authentication failed
  10. java junit Assert断言用法示例: Assert.assertEquals(期望的结果,运算的结果)
  11. 利用 Sunbird 处置你的日程表
  12. eclipse中无法移除jar包_IDEA中已配置阿里镜像,但maven无法下载jar包的问题
  13. Knowledge Test about Match
  14. S3C6410 时钟初始化
  15. Jquery调用C#后台方法
  16. Python 3.8 新功能大揭秘
  17. PAT 1012 数字分类
  18. 如何打开caj文件,以及caj文件如何转换为PDF格式
  19. 三个视频教你如何找到另一半
  20. Teamviewer Install

热门文章

  1. 安卓系统底层C语言算法之测试参数是几个long型的算法
  2. 如何设置iframe高度自适应,在跨域的情况下能做到吗?
  3. 医疗信息化、医学、医院管理、医疗器械资料下载
  4. numpy 100题
  5. python将txt文件多行合并为一行并将中间的空格去掉
  6. python实现直播服务非rtmp版本(非常简单)
  7. Mac 安装md5sum等
  8. ArcGIS Engine效率探究——要素的添加和删除、属性的读取和更新(转载)
  9. mysql复习增删改查
  10. [To be translated] Nova:libvirt image 的生命周期