《程序员的自我修养》(一)——编译与静态连接

简介

温故而知新

  • 计算机科学领域的任何问题均可以经过增长一个间接地中间层来解决。
  • 在UNIX中,硬件设备的访问形式跟访问普通的文件形式同样;在Windows系统中,图形硬件被抽象成了GDI,声音和多媒体设备被抽象成了DirectX对象,磁盘被抽象成了普通文件系统,等等。
  • 如何将计算机上有限的物理内存分配给多个程序使用。整个想法是这样的,咱们把程序给出的地址看做是一种虚拟地址,而后经过某些映射的方法,将这个虚拟地址转换成实际的物理地址。
  • 进程的映射方式:算法

    • 分段,基本思路是把一段与程序须要的内存大小的虚拟空间映射到某个地址空间。
    • 分页,基本方法是把地址空间人为地等分红固定大小的页,每一页的大小又硬件决定,或硬件支持多种大小的页,由操做系统选择决定页的大小。目前几乎全部的PC上的操做系统都使用4KB大小的页。几乎全部的硬件都是采用一个叫MMU的部件来进行页映射。
    • 在页映射模式下,CPU发出的Virtual Address,即咱们的程序看到的是虚拟地址。通过MMU转换之后就变成了Physical Address。通常MMU都集成在CPU内部了,不会以独立的部件存在。

静态连接

编译和连接

  • 预编译(预处理):预编译过程主要处理那些源代码文件中的以“#”开始的预编译指令。C文件预编译后造成.i文件,C++文件编译后扩展名是.ii。
  • 编译:编译过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后生产相应的汇编代码文件,编译后生成.s文件。
  • 汇编:汇编器是将汇编代码转变成机器能够执行的指令,通过汇编后生成.o文件。
  • 连接:连接的主要内容就是把各个模块之间相互引用的部分都处理好,使得各个模块之间可以正确地衔接。连接过程主要包括了地址和空间分配、符号决议和重定位等步骤。最终生成可执行文件。

  • 编译过程通常能够分为6步:扫描、语法分析、语义分析、源代码优化、代码生成和目标代码优化。数组

    • 词法分析:首先源代码程序被输入到扫描器,扫描器的任务很简单,它只是简单地进行词法分析,运用一种相似于有限状态机的算法能够很轻松地将源代码的字符序列分割成一系列的记号。
    • 语法分析:接下来语法分析器将对由扫描器产生的记号进行语法分析,从而产生语法树(以表达式为节点的树)。
    • 语义分析:编译器所能分析的语义是静态语义,所谓静态语义是指在编译期能够肯定的语义,静态语义一般包括声明和类型的匹配,类型的转换。
    • 中间语言生成:现代的编译器有着不少层次的优化,每每在源代码级别会有一个优化过程。源代码优化器每每将整个语法树转换成中间代码,它是语法树的顺序表示,其实它已经很是接近目标代码了。
    • 目标代码生成与优化:代码生成器将中间代码转换成目标机器代码,这个过程十分依赖于目标机器,由于不一样的机器有着不一样的字长、寄存器、整数数据类型和浮点数数据类型等。最后目标代码优化器对上述的目标代码进行优化,好比选择合适的寻址方式、使用位移来代替乘法运算、删除多余的指令等。

目标文件里有什么

  • 如今PC平台流行的可执行文件格式主要是Windows下的PE(Portable Executable)和Linux的ELF(Executable Linkable Format),它们都是COFF(Common file format)格式的变种。
  • 目标文件中包括机器指令代码、数据、符号表、调试信息、字符串等。通常目标文件将这些信息按不一样属性,以“段”的形式存储。
  • 程序源代码编译后的机器指令常常被放在代码段里,代码段常见的名字有“.code”或“.text”;全局变量和局部静态变量数据常常放在数据段,数据段的通常名字都叫“.data”。
  • .bss段只是为未初始化的全局变量和局部静态变量预留位置而已,它并无内容,因此它在文件中也不占据空间

  • ELF目标文件的文件头描述了整个文件的基本属性,包括文件是否可执行、是静态连接仍是动态连接及入口地址、目标硬件、目标操做系统、程序入口地址等。紧接着就是ELF文件各个段,后面是与段有关的重要结构——段表,段表实际上是一个描述文件中各个段的数组。段表描述了文件中各个段的段名、段的长度、在文件中的偏移位置、读写权限及段的其余属性等。

函数

  • 编译器还会将一些辅助性的信息,诸如符号、重定位信息等也按照段的方式存放在目标文件中。

静态连接

  • 如今的连接器空间分配的策略都是采用一种叫两步连接的方法。布局

    • 第一步,空间与地址分配。扫描全部的输入目标文件,得到它们的各个段的长度、属性和位置,而且将输入目标中的符号表中全部的符号定义和符号引用收集起来,统一放到一个全局符号表。
    • 第二步,符号解析与重定位。使用上面第一步中收集到的全部信息,读取输入文件中段的数据、重定位信息,而且进行符号解析与重定位、调整代码中的地址等。这一步是连接过程的核心,特别是重定位过程。
  • 对于可重定位的ELF文件来讲,它必须包含有重定位表,用来描述如何修改相应的段里的内容。对于每一个要重定位的ELF段都有一个对应的重定位表,而一个重定位表每每就是ELF文件中的一个段,因此其实重定位表也能够叫重定位段。
  • 连接器的COMMON块机制解决同一个符号定义在多个文件中的问题(目前的连接器自己并不支持符号的类型,即变量类型对于连接器来讲是透明的,它只知道一个符号的名字,并不知道类型是否一致)。这个问题解决的规则是,若是是弱符号(未初始化的全局变量)则在最终连接后的输出文件中,该符号所指的变量大小以输入文件中最大的那个为准。若是其中有一个符号为强符号,那么最终输出结果中的符号所占空间与强符号相同。
  • 若是要使两个编译器编译出来的目标文件可以互相连接,那么这两个目标文件必须知足下面这些条件:采用一样的目标文件格式、拥有一样的符号修饰标准、变量的内存分布方式相同、函数的调用方式相同,等等。其中咱们把符号修饰标准、变量内存布局、函数调用方式等这些跟可执行代码二进制兼容性相关的内容称为ABI(Application Binary Interface)。
  • 一个静态库能够简单地当作一组目标文件的集合,即不少目标文件通过压缩打包后造成的一个文件。
  • VISUAL C++容许使用脚原本控制整个连接过程,这种控制脚本叫作模块定义文件,它们的扩展名通常为.def。

Windows PE/COFF

  • 在Windows平台,VISUAL C++编译器产生的目标文件使用COFF格式,而可执行文件为PE格式。微软对64位Windows平台上的PE文件结构稍微作了一些修改,这个新的文件格式叫作PE32+。新的PE32+并无添加任何结构,最大的变化就是把那些原来32位的字段变成64位。
  • COFF文件是由文件头及后面的若干个段组成,再加上文件末尾的符号表、调试信息的内容,就构成了COFF文件的基本结构。COFF文件的文件头部包括了两部分,一个是描述文件整体结构和属性的映像头,另一个是描述该文件中包含的段属性的段表。
  • “.drectve 段”其实是“Directive”的缩写,它的内容是编译器传递给连接器的指令,即编译器但愿告诉连接器应该怎样连接这个目标文件。
  • COFF文件中全部以“.debug”开始的段都包含着调试信息。好比“.debug$S”表示包含的是符号相关的调试信息段;“.debug$P”表示包含预编译头文件相关的调试信息段;“.debug$T”表示包含类型相关的调试信息段。
  • PE文件是基于COFF的扩展,它比COFF文件多了几个结构。最主要的变化有两个:第一个是文件最开始的部分不是COFF文件头,而是DOS MS可执行文件格式的文件头和桩代码;第二个变化是原来的COFF文件头中的“IMAGE_FILE_HEADER”部分扩展成了PE文件头结构“IMAGE_NT_HEADERS”,这个结构包括了原来的“Image Header”及新增的PE扩展头部结构。

相关文章
相关标签/搜索