《程序员的自我修养》第三章学习笔记

1, 编译器编译源代码生成的文件叫做目标文件。

从结构上说,是编译后的可执行文件,只不过还没有经过链接

3.1 目标文件的格式

1,可执行文件的格式: Windows下的PE  和   Linux下的ELF

2,从广义上说,目标文件与可执行文件的格式几乎是一样的,所以广义上可以将目标文件与可执行文件看成是一种类型的文件。

3,可执行文件,动态链接库,静态链接库都按照可执行文件格式存储(Windows下是 PE-COFF格式,Linux下是ELF格式)。

4,Linux下命令: $: file   ***   显示出对应文件的类型

目标文件是什么样的

1,目标文件包含的内容:编译后的机器指令代码、数据,还有链接时要的一些信息(比如符号表、调试信息、字符串等)。

1,  一般目标文件把上述信息按不同的属性,以节(section)的形式存储。有时也叫段(segment)

2,  代码段常见的名字有 “.code”  “.text”,编译后的机器指令就放在代码段,

数据段:一般名字都是 “.data”,已初始化全局变量和局部静态变量数据放这

3, ELF文件的开头是一个”文件头”,它描述了整个文件的文件属性(是否可读可写可执行,是静态链接还是动态链接及入口地址)、目标硬件、目标操作系统等信息。

4,  文件头还有一个段表(section table)。描述文件各个段的数组(各个段在文件中的偏移和属性)。

5,  关于bss段:未初始化的全局变量和静态局部变量一般放在一个”.bss”段的地方。

它只是为未初始化的全局变量和静态局部变量预留位置而已,并没有内容,在文件中不占空间。

6,  总体来说:程序源代码被编译后主要分成两种段:程序指令(代码段),程序数据(数据段,bss段)。

3.3挖掘simplesection.o

1,objdump 查看各种目标文件的结构和内容, objdump –h main.o  -h 表示把ELF文件的各个段的基本信息打出来

   readelf 专门针对ELF文件格式的解析器

   size 可以用来查看ELF文件的代码段、数据段和bss段的长度   size main.o

3.3.1代码段

1,objdump 的参数 –s 将所有的段的内容以十六进制的方式打印出来

                    -d 将所有包含指令的段反汇编 比如 objdump  –s  –d  main.o

                    -h 把关键的段显示了出来,忽略了辅助性段

3.3.2数据段和只读数据段

1, “.data” :保存的是已初始化全局变量和局部静态变量

2,,”rodata”:存放的是只读数据,一般是程序中的只读变量(如用const修饰的)和字符串常量。

3.3.3 bss段

1,存放的是未初始化的全局变量和静态局部变量。不占磁盘空间

3.3.4 其他段

还可以自定义段GCC提供的扩展机制

3.4 ELF文件结构描述

3.4.1 文件头

1,查看 readelf –h main.o

2,ELF文件中定义了:ELF魔数、文件机器字节长度、数据存储方式、版本、运行平台、ABI版本、

ELF重定位类型、硬件平台、硬件平台版本、入口地址、程序头入口和长度、段表的位置和长度及段的数量等。

3,ELF魔数:文件头最开始的4个字节是所有ELF都必须相同的标识码。又称为魔数。

魔数用来确认文件类型,操作系统在加载可执行文件的时候会确认魔数是否正确,若不正确则拒绝加载。

4,ELF文件类型:分为 可重定位、可执行、共享目标文件、核心转储文件

3.4.2 段表(除了文件头以外最重要的结构)

1,描述了ELF各个段的信息(比如每个段的段名、段的长度、在文件中的偏移,读写权限及其他属性)。

2,编译器、链接器和装载器都是靠段表来定位和访问各个段的属性的。

3,readelf –S main.o 显示真正的段表结构

4,上述结果是一个以 “ELF32_Shdr” 结构体为元素的数组。

3.4.3 重定位表

1,一个叫”rel.text”的段,类型是”SHT_REL”,就是重定位表

2,链接器在处理目标文件时,需要对目标文件中的某些部位进行重定位(即代码段和数据段中那些对绝对地址的引用的位置)。

3,这些重定位信息都记录在ELF文件的重定位表里。对于每个需要重定位的代码段或数据段,都会有一个相应的重定位表。

3.4.3 字符串表

1,把ELF文件中用到的字符串(段名、变量名等)集中起来存放到一个表中。然后使用字符串在表中的偏移来引用字符串。这个表就是字符串表。

2,一般字符串表在ELF文件中也以段的形式保存。常见的段名有”.strtab”, ”.shstrtab”

3,”.strtab”:字符串表:保存普通的字符串

4,”.shstrtab”:段表字符串表:保存段表中用到的字符串,最常见的就是段名。

3.5 链接的接口——符号

1,链接过程的本质就是要把多个不同的目标文件之间相互粘滞到一起。

2,为了使不同目标文件之间能够相互粘合,这写目标文件必须要有固定的规则才行。

3,在链接中,目标文件之间相互拼合实际上是目标文件之间对地址的引用,即对函数和变量的地址的引用。

4,在链接中,我们将函数和变量统称为符号(symbol),函数名或变量名就是符号名(symbol name)。

5,每一个目标文件都会有一个相应的符号表(symbol table),记录着目标文件中所用到的所有的符号。

6,每个定义的符号有一个对应的值叫做符号值(symbol value)。对于变量或函数来说,符号值就是它们的地址。

8,  符号的分类: (1)和(2)比较需要关注

(1)       定义在本目标文件的全局符号,可被其他目标文件引用。

(2)       在本目标文件中引用的全局符号,在别的目标文件中定义。一般叫做外部符号(external symbol)。

(3)       段名,这种符号往往由编译器产生,它的值就是该段的起始地址。

(4)       局部符号,这类符号只在编译单元内部可见,

(5)       行号信息,即目标文件指令与源代码中代码行的对应关系,是可选的。

3.5.1 ELF符号表结构

1,ELF文件中的符号表往往是文件中的一个段,y一般叫做 “.symtab”。是一个Elf32_Sym的数组,数组中每个元素对应一个符号。

2,结构体定义如下

typedef struct{
    Elf32_Word st_name;
    Elf32_Addr st_value;
    Elf32_Word st_size;
    unsigned char st_info; 
    unsigned char st_other;
    Elf32_Half st_shndx;
}Elf32_Sym;

3.5.2 特殊符号

1,ld链接器产生可执行文件时,会给我们定义很多符号(没有在自己的程序中定义),但是可以直接声明并且引用它,我们称之为特殊符号。

3.5.3 符号修饰与函数签名

1,c++增加了名称空间(namespace)的方法来解决多模块之间的符号冲突问题。

2,c++符号修饰

(1)       函数签名:包含了一个函数的信息(函数名、参数类型、所在的类和名称空间和其他信息)。函数签名用于识别不同的函数。

3.5.4 extern”C”

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

2,可以让c++的名称修饰机制不作用。

3.5.5 弱符号与强符号

1,多个目标文件含有相同名字的全局符号的定义,在链接时将会出现符号重复定义的错误。

2,编译器默认函数和初始化了的全局变量为强符号,未初始化的全局变量为弱符号。

3,强弱符号都是针对定义来说的,不是针对符号的引用。 假如 extern int ext; ext不是强符号也不是弱符号,因为它是一个外部变量的引用。

4,链接器按下面规则处理与选择被多次定义的全局符号。

(1)       不允许强符号被多次定义,否则报错。

(2)       若一个符号在某个目标文件中是强符号,在其他文件中都是弱符号,那么选择强符号。

(3)       若一个符号早所有的目标文件中都是若符号,那么选择其中占用空间最大的那一个。

5,弱引用与强引用:

强引用:假如链接时没找到该符号的定义,链接器就会报符号未定义错误。

弱引用:假如链接时没找到该符号的定义,链接器不会报错,默认其为0或是一个特殊值。

6,GCC中可以通过 “__attribute__((weakref))”这个扩展关键字来声明一个外部函数为弱引用  p93

用法: __attribute__((weakref)) void foo();  这样就说明foo为弱符号。

7,弱符号和弱引用对库来说非常有用,比如库中定义的弱符号可以被用户定义的强符号所覆盖。

8,补充: Linux中支持多线程时需要在编译时加上 –lpthread 选项, 比如 gcc test.c -lpthread

3.6 调试信息

1,在gcc编译时加上 –g 参数就会在产生的目标文件里面加上调试信息。 目标文件会多些 debug段。

2,假设有个目标文件 test, Linux下可以用 ”strip” 命令来去除调试信息, 用法: $ strip test

3.7 本章小结

参考《程序员的自我修养》

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java工会

反射在微信公众平台开发中的应用

1464
来自专栏武培轩的专栏

京东面经汇总

一、Java Java的优势 平台无关性、垃圾回收 Java有哪些特性,举个多态的例子。 封装、继承、多态 abstract interface区别 含有abs...

5886
来自专栏塔奇克马敲代码

在使用Qt5.8完成程序动态语言切换时遇到的问题

2434
来自专栏磨磨谈

CephFS: No space left on device

这是一篇旧闻,由于用cephfs的人还不是很多,所以比较少发关于cephfs相关的东西,最近看到有人开始使用,并且遇到了问题,这个就是其中一个比较容易出的问题,...

2655
来自专栏python3

python if else 流程判断

Python条件语句是通过一条或多条语句的执行结果(True或者False)来决定执行的代码块。 可以通过下图来简单了解条件语句的执行过程:

1922
来自专栏刺客博客

(转载)Linux之sed匹配第N个进行替换

2.3K4
来自专栏javathings

volatile 解决了什么问题?

volatile 关键字是最常问到的问题,关于这个关键字的作用解释,网上的文章已经多如牛毛了。

3153
来自专栏Google Dart

Dart 服务端开发 shelf_bind 包

shelf_bind倾向于约定优于配置,因此您可以编写必要的最小代码,但仍然可以根据需要覆盖默认值。

972
来自专栏C/C++基础

Linux命令(36)——awk命令

AWK是一个优良的文本处理工具,Linux及Unix环境中现有的功能最强大的数据处理引擎之一。数据可以来自标准输入(stdin)、一个或多个文件,或其它命令的输...

1702
来自专栏C/C++基础

Linux命令(46)——read命令

read命令是Shell内建命令,用于从标准输入或-u选项指定的文件描述符中读取单行,并将读取的单行根据IFS变量分割成多个字段,并将分割后的字段分别赋值给指定...

1312

扫码关注云+社区

领取腾讯云代金券