深入编译,链接和运行

来源:互联网 发布:etm软件会员 编辑:程序博客网 时间:2024/06/05 17:11

一.编译和链接

1.预处理

  命令:gcc -E hello.c -o hello.i

  主要处理.c文件中以“#”开头的预编译指令

2.编译

  命令:gcc -S hello.i -o hello.s

[1]词法分析

[2]语法分析

[3]语义分析

  编译器只能分析静态语义(编译期确定的语义)

  静态语义有声明,类型转换,类型匹配

[4]优化后生成相应的汇编代码文件

  中间语言生成,目标代码生成与优化。

3.汇编

  命令:gcc -c hello.s -o hello.o

  汇编器是将汇编代码转化成机器可以执行的指令。

4.链接

重定位:绝对地址引用的位置“打补丁”,使其指向正确的地址

符号:函数或变量的起始地址

[1]地址和空间分配

[2]符号决议

[3]重定位


二.目标文件

基础知识:

1】可执行文件格式有windows下的PE和linux下的ELF,都是COFF格式的变种。

2】静态链接库(windows下的.lib,linux下的.a)动态链接库(windows下的.dll,linux下的.so)都按可执行文件格式存储。


为何将可执行文件的代码段和数据段分开存放?

1】代码段只读,数据段可读可写,有利于分别保护

2】现代cpu的缓存被设计为指令缓存和数据缓存分离,分开存放可提高cpu的缓存命中率。

3】运行多个进程时,有各自的数据段,共享代码段,节省内存


程序示例:



查看目标文件的结构和内容:



目标文件 段的基本分布


1.代码段(.text)

  存放机器指令

2.数据段(.data)

  存放已经初始化的静态变量,全局变量

3.只读数据段(.rodata)

  存放只读数据,一般为只读变量(const修饰的变量)和常量字符串

4.数据段(.bss)

  存放未初始化或初始化为0的静态变量,全局变量

  因为数据全为0,.data段存储数据0是没有必要的,因此在目标文件中.bss是预留的,没有内容,不占内存空间,运行时的确占内存空间

  注:未初始化的全局变量在.comment段,故.bss的大小为0x14=20字节,并非24字节。






ELF文件结构描述

1.文件头




2.段表:描述每个段的基本信息

编译器,链接器,装载器都是通过段表来访问和定位段的属性的


ELF32_Shdr段描述符结构:每一个ELF32_Shdr结构体对应一个段



mian.o的段表及所有段的位置和长度

注:以2^2=4字节对齐,故有一小部分空余



3.重定位表

.rel.text是针对.text的重定位表。在.text段有绝对地址的引用,那就是printf函数。.data段包含几个常量,没有绝对地址的引用。

4.字符串表

字符串表(.strtab):保存普通的字符串,比如符号名。

段表字符串表(.shstrtab)保存段表中的字符串,比如段名。


链接的接口----符号

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

比如目标文件B用到了目标文件A中的foo函数,则称目标文件A定义了foo函数,目标文件B引用了目标文件A中的foo函数。(同样适用于变量)

2】在链接中,将函数和变量统称为符号,函数名和变量命为符号名。

3】每一个目标文件有一个相对应的符号表。

符号表记录了目标文件的所有符号,每一个符号对应一个符号值。对函数和变量来说,符号值就是它们的地址。


符号的类型

1】全局符号               @@@@@@链接过程只关心全局符号的相互粘合。

    1)定义在本目标文件的,可以被其他目标文件引用。eg:main,gdata1,gdata2,gdata3 

    2)外部符号:没有定义在本目标文件,在本目标文件中引用。eg:printf 

2】局部符号

     只在编译单元内部可见,对于链接过程没有作用。eg:d.e.f,gdata4,gdata5,gdata6


5.符号表(.symtab)



符号修饰与函数签名

1】为了避免库文件中的函数和全局变量名与目标文件中的名字起冲突,函数经编译后要在符号名前加"_"。eg:foo----->_foo   (C语言)

2】名称空间:解决多模块的符号冲突问题    (c++)

3】c++符号修饰

   函数签名:用于识别不同的函数。包含了函数的所有信息,包括函数名,参数列表,它所在的名称空间和类。

   c++编译器在编译时会将函数(函数签名)和变量的名字进行修饰,形成符号名。


extern"c"         符号的引用

c++编译器会将 extern"c" 大括号内部的代码当作C语言代码处理。

C语言不支持 extern "c" 语法,为兼容C语言和c++定义两套头文件,c++的宏"_cplusplus",c++编译器在c++编译程序时默认调用该宏。


弱符号与强符号-----》针对符号的定义,并非符号的引用。

1】 强符号:函数和初始化了的全局变量。弱符号:未初始化的全局变量。(在.COMMON块)

2】链接器按如下规则处理不同目标文件中重复定义的符号:

    1)同名强符号,编译错误。

    2)同名强,弱符号,选择强符号。

    3)同名弱符号,选择占用内存大的。

3】强引用和弱引

强引用:若没有找到符号的定义,链接器会报符号未定义的错误。

弱引用:若符号有定义链接器将该符号的引用决议。若没有定义,链接器不会报错。主要用于库的链接过程。


为何将未初始化的全局变量放在.comment段,不放在.bss段?????

答:未初始化的全局变量放在.comment段只针对编译后的目标文件。在链接时,两个目标文件链接为一个可执行文件,若两个目标文件出现了同名的

弱符号,则选择内存占用大的,实际上未初始化的全局变量在链接后是放在.bss段的。而在编译时,并不确定在别的源文件中是否有同名的弱符号,不

可确定其大小,因此将未初始化的全局变量暂时存放在.comment段。



三.静态链接

空间与地址分配

1】相似段合并:相同性质的段进行合并,obj文件以2^2=4字节对齐,合并后以页面(4k)对齐。

    .bss段不占目标文件和可执行文件的空间,装载时为其分配空间,其只有虚拟地址空间。

2】调整段偏移和段长度,合并符号表。

程序示例:

             

a.c b.c编译为目标文件a.o b.o

a.o b.o 链接为ab可执行文件


查看链接前后地址分配情况:




符号解析与重定位

1】重定位



2】重定位表


3】符号解析

链接时符号未定义的原因:1)链接时缺少了某个库。2)输入目标文件路径不正确。3)符号的声明与定义不一样。


链接器扫描完所有输入目标文件后,目标文件中未定义的符号应该能够在全局符号表中找到,否则链接器报符号未定义错误。

(所有obj符号表中对符号引用的地方要找到符号定义的地方)


四.可执行文件的装载与进程

可执行文件只有装载到内存才能被CPU执行。

1.进程虚拟地址空间

  1】进程和程序的区别:

   程序:静态的概念,预先编译好的数据和指令的集合的文件。

   进程:动态的概念,运行中的程序。

  2】虚拟地址空间的大小与CPU的位数有关。

   32位CPU大小为2^32=4G

  硬件决定了地址空间的最大理论上限,即硬件的寻址空间大小。

2.装载的方式

  1】静态装入:将程序运行时需要的指令和数据全部加载到内存中执行。

  2】动态装入:程序所需要的内存大于物理内存。

  思想:程序需要哪个模块,就把哪个模块装入内存,如果不需要,就将其存放在磁盘。

      1)覆盖装入

      2)页映射  页面置换算法:FIFO  LRU

3.从操作系统的角度看可执行文件的加载

1】进程的建立

  1)创建独立的虚拟地址空间。

    页映射函数:创建虚拟地址空间到物理空间的映射关系。

  2)读取可执行文件头,建立可执行文件与虚拟地址空间的映射。

    程序执行发生页错误时,操作系统在物理内存中分配一个物理页,将缺页从磁盘读取到物理内存,建立虚拟页到物理页的映射关系。同时操作系统要知道缺页位于可执行文件的哪个位置,于是建立可执行文件与虚拟地址空间的映射关系。

    可执行文件又叫映像文件。

    Linux中将虚拟地址空间的一个段叫做虚拟内存区域(VMA)。

  3)将CPU的指令寄存器设置为可执行文件的入口地址,启动运行。

2】页错误

4.进程虚存空间分布

1】ELF文件链接视图和执行视图

  段数量增多时,为减少空间浪费,可执行文件到虚拟地址空间的映射时,对于相同权限的段,合并到一起当作一个段进行映射。映射到同一个VMA。

  合并后的一个段叫做"segment",其中包含一个或多个属性类似的"section"。

  从链接的角度,可执行文件按“section”存储,可执行文件为链接视图。从装载的角度,可执行文件按“segment”划分,可执行文件为执行视图。

  目标文件链接成可执行文件时,链接器尽量将相同权限属性的段分配在同一空间。可执行文件映射时,是以“segment”来映射的。

  正如描述“section”属性的结构叫做段表,而描述“segment”属性的结构叫做程序头(program header)。描述了ELF文件该如何被操作系统映射到进程的虚拟空间。




ELF可执行文件与进程虚拟空间的映射关系


2】堆和栈

通过查看/proc来查看进程虚拟空间分布:


进程虚拟地址空间的概念:操作系统通过将进程划分成一个个VMA来管理进程的虚拟空间,基本原则是将相同属性的,有相同映像文件的映射成一个VMA。

3】堆的最大申请数量

4】段地址对齐

各个段接壤的部分共享一个物理页面,然后将该物理页面分别映射两次。

4】进程栈初始化

5】Linux内核装载ELF过程简介

详情请参见《程序员的自我修养-链接,装载与库》