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

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

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

 

3.1 目标文件的格式

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

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

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

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

 

目标文件是什么样的

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

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

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

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

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

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

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

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

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

 

3.3挖掘simplesection.o

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

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

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

 

3.3.1代码段

1objdump 的参数 –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

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

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

 

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

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

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

 

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

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

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

3readelf –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符号表结构

1ELF文件中的符号表往往是文件中的一个段,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 特殊符号

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

 

3.5.3 符号修饰与函数签名

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

 

2c++符号修饰

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

 

3.5.4 extern”C”

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

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

 

3.5.5 弱符号与强符号

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

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

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

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

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

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

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

 

5,弱引用与强引用:

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

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

6GCC中可以通过 “__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 本章小结

 

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

上一篇:关于delete 和 new


下一篇:记录我学github的路程(二)