首页 > 编程语言 >2.程序员的自我修养 - 静态链接与程序装载

2.程序员的自我修养 - 静态链接与程序装载

时间:2023-04-10 16:04:22浏览次数:43  
标签:可执行文件 文件 符号 ELF 程序员 地址 修养 链接


目录

 

第二章 静态链接

2.1程序编译的过程

第三章 目标文件的格式

3.1目标文件的格式

3.2目标文件具体内容

3.3ELF文件结构

3.4重定位表

3.5字符串表

3.6链接的接口

3.7弱符号与强符号

第四章 静态链接

4.1空间与地址分配

4.2符号解析与重定位

4.3COMMON块

4.4C++相关问题

4.5链接控制脚本

第五章 Windows PE/COFF

第六章 可执行文件的装载与进程

6.1进程虚拟地址空间

6.2装载方式

6.3从操作系统角度看可执行文件的装载

6.4进程虚存空间分布

6.5Liunx内核装载ELF过程


第二章 静态链接

2.1程序编译的过程

gcc 编译hello World程序的4个步骤:预处理(prepressing)、编译(compilation)、汇编(assembly)和链接(linking)

2.程序员的自我修养 - 静态链接与程序装载_可执行文件

预编译:源代码文件和相关头文件被预编译器cpp预编译成.i文件,主要处理源代码文件中以“#”开始的预编译指令,如#include、#define 等,规则如下:

2.程序员的自我修养 - 静态链接与程序装载_可执行文件_02

编译:对预处理完成的文件进行一系列词法(扫描器将源代码字符分割成一系列的记号)、语法(语法分析器对记号分析产生语法树)、语义分析(在语法树中添加类型并更新符号表中符号类型,静态语法分析包括声明、类型匹配和类型转换,如浮点赋值给指针被判定非法)、源代码优化 (通过优化生成中间语言语法树,如2+6在语法树中被优化为8)、目标代码生成与优化(将中间代码转换为目标机器代码,并通过优化器优化目标代码如选择合适寻址方式、使用移位代替乘法、删除多余指令等)并进行优化产生相应汇编代码文件。

汇编:将汇编代码转换成及其可以执行的指令,每个汇编语句几乎都对应一条机器指令。

链接:将一大堆汇编产生的文件链接(模块拼接)起来得到最终的可执行文件,即将各模块间相互引用的部分处理好,使各模块正确衔接,包括地址和空间分配、符号决议(符号/地址绑定)和重定位等。

模块拼接:一个程序被划分为多个模块后(如一个项目含多个.c 文件),模块间如何组合成一个单一的程序是需解决的问题,实际上属于模块间通信问题。其中属于静态链接的通信方式包括模块间函数调用和变量访问,这两种方式都需知道目标地址,均可归结为模块间符号的引用。

静态链接过程举例:如全局变量var 在目标文件A中,我们在目标文件B 中需要访问var,则编译目标文件B时由于不知道变量var 的目标地址将其暂时设置为0,等链接器将目标文件A和B 链接起来的时候再将var 的地址进行修正,地址修正也叫重定位,被修改的地方叫重定位入口

2.程序员的自我修养 - 静态链接与程序装载_重定位_03

词法、语法、语义、分析举例:如分析 array[index] = (index + 4) * (2+6);

  1. 词法分析

2.程序员的自我修养 - 静态链接与程序装载_可执行文件_04

  1. 语法分析

2.程序员的自我修养 - 静态链接与程序装载_可执行文件_05

  1. 语义分析

2.程序员的自我修养 - 静态链接与程序装载_目标文件_06

  1. 源代码优化

2.程序员的自我修养 - 静态链接与程序装载_可执行文件_07

第三章 目标文件的格式

3.1目标文件的格式

目标文件:编译器编译源代码后生成的文件(Windows的.obj和Linux的.o),从结构上它已经是可执行文件格式,只是没有经过链接,其中有些符号和地址没有调整。

可执行文件格式:Windows下的PE和Linux 下的ELF都是COFF格式的变种。此外,动态链接库(windows 的 .dll 和 Linux 的 .so)及静态链接库(windows的.lib和Linux的.a)文件都是按照可执行文件的格式存储。ELF格式文件分类如下:

2.程序员的自我修养 - 静态链接与程序装载_可执行文件_08

Linux 文件格式查看:file 文件名

3.2目标文件具体内容

目标文件中的内容:编译后的机器指令代码、数据、链接所需的信息(符号表、调试信息、字符串等),一般目标文件将这些信息按不同属性以“节”(有时候称段)存储。

编译后数据存放:机器指令存放在代码段,以.code和.text 命名;已初始化的全局变量和局部静态变量数据放在数据段,以.data命名;未初始化的全局变量和局部静态变量存放在.bss段,它没内容所以在文件中不占据存储空间。其它一些数据存放的段:

2.程序员的自我修养 - 静态链接与程序装载_重定位_09

程序段和代码段分别存储的好处:(1)数据和指令分别被映射到两个虚存区域,指令对进程只读,数据可读写,分开有利于程序指令被恶意改写;(2)指令区和数据区的分离有利于提高程序的局部性(缓存命中率);(3)系统中运行多个程序副本时,他们的指令都一样,内存只需保存一份改程序指令的部分,通过共享指令节省大量内存。

3.3ELF文件结构

ELF文件结构:ELF重要的文件结构如下

2.程序员的自我修养 - 静态链接与程序装载_可执行文件_10

ELF文件头(ELF Header)包含描述整改文件的基本属性,如ELF文件版本、目标机器型号、程序入口地址等。

段表(Section Header Table)该表描述了ELF文件包含的所有段的信息,如每个段的段名、段长度、段在文件中的偏移、读写权限和其它属性。

3.4重定位表

重定位表:链接器在处理目标文件时,需对目标文件中的某些部位进行重定位,即代码段和数据段中那些对绝对地址的引用(如printf 函数调用)的位置,这些位置信息均记录在ELF文件的重定位表内。

3.5字符串表

字符串表:ELF文件中用到了很多字符串,如段名、变量名等,因字符串长度不定,所以固定结构表示较困难,常用的做法是把字符串集中起来存放在一个表中,然后使用字符串在表中的偏移来引用字符串。

3.6链接的接口

链接符号:链接中将函数和变量名统称为符号,函数名或变量名就是符号名。

符号表:用于符号管理,每个目标文件都有一个相应的符号表,记录了目标文件所用的全部符号,每个符号表对应一个值叫做符号值,对变量和函数来说符号之就是它们的地址。符号分类如下:

2.程序员的自我修养 - 静态链接与程序装载_重定位_11

符号修饰与函数签名:为了避免命名冲突,编译器通过通过对符号进行修饰和函数签名的方式解决该问题,如int func(int)函数签名被VS编译器修饰后的名称为 ?func@@YAHH@Z。

extern ”C”C++为了与C兼容在符号管理上C++有一个用来申明或定义一个C的符号的extern ”C” 关键字,C++编译器会将其当作C代码处理,此时C++命名修饰机制不起作用。其用法如下:

extern ”C”{

       int func(int);

       int varl

}

3.7弱符号与强符号

强符号和弱符号判定:对C/C++而言,编译器默认函数和初始化了的全局变量为强符号,未初始化的全局变量为弱符号。GCC的__attribute__((weak))可将任意强符号转为弱符号。如下

extern int ext;

int weak;

int strong=1;

__attribute__((weak)) weak2=2;

int main()

{

       return 0;

}

weak 和 weak2为弱符号,strong 为强符号,ext 不是强符号也不是弱符号。

 

强弱符号的选择规则:(1)不允许强符号多次定义,若多次定义报重复定义错误;(2)一个符号在某目标文件为强符号,在其它文件为弱符号则选择强符号;(3)一个符号在所有目标文件中都是弱符号,选占用空间最大的一个。

弱引用和强引用:对外部文件的符号引用在目标文件被最终链接成可执行文件时,若没找到该符号的定义(只有申明没有定义),链接器会报符号未定义错误,这被称为强引用;与之相对的弱引用若该符号未定义则连接器对该引用不报错,一般对于未定义弱引用,链接器默认其为0或是一个特殊值。GCC可通过static __attribute__((weakref(“别名”))) 这个扩展关键字来声明对一个外部函数的引用为弱引用,通过弱引用可以有效判断当前程序是链接到了单线程的库还是多线程的库。

第四章 静态链接

4.1空间与地址分配

链接器如何将多个输入目标文件各段合并到输出文件:有按序号叠加(直接将各目标文件依次合并,会造成大量内部碎片)、相似段合并(现在使用的方法,将相同性质的段合并到一起,如.text合并到输出文件的.text段)。

 

2.程序员的自我修养 - 静态链接与程序装载_可执行文件_12

相似段合并的两步链接法:(1)空间地址分配:扫描所有输入目标文件,获得各段长度、属性和位置,并将符号表中所有符号定义和引用收集并放到全局符号表,最终计算输出文件各段合并后的长度与位置并建立映射关系;(2)符号解析与重定位:在上一步基础上读取输入文件中段的数据、重定位信息并进行符号解析与重定位、调整代码中的地址等。

符号地址确定:空间地址分配后输入文件各段在链接后的虚拟地址已经确定(如.text段起始地址),因各符号(如main)在断内相对位置固定,链接器通过起始地址加偏移量的方式可直接计算各符号虚拟地址

4.2符号解析与重定位

重定位表:保存与重定位有关的信息(如需要调整的指令),用于描述如何修改相应的段里的内容,对于每个要被重定位的ELF段都有一个对应的重定位表,而一个重定位表往往就是ELF文件的一个段(如.text在需重定位的地方有.rel.text的段,保存代码段的重定位表),所以它也叫重定位段。

符号解析:重定位过程伴随着符号解析过程,每个目标文件都可能定义一些符号,也可能引用到定义在其他目标文件的符号,这个时候连接器需要去查找所有输入目标文件的符号表组成的全局符号表,找到相应的符号后进行重定位。

指令修正方式:绝对寻址修正(修正后的地址为该符号的实际地址)和相对寻址修正(修正后的地址为符号距离被修正位置的地址差)。

4.3COMMON块

问题:弱符号机制允许一个符号的定义存在于多个文件中,若他们的类型不同链接器需要处理三种情况:(1)两个或两个以上强符号类型不一致,编译器直接报从定义错误;(2)有一个强符号,其它都是弱符号,出现类型不一致;(3)两个或两个以上弱符号类型不一致。

COMMON块机制:当全为弱符号时,当不同的目标文件需要的common块空间(临时分配空间)大小不一致时,以最大的那块为准;当有一个为强符号时,最终输出结果中的符号所占空间与强符号相同(如果有弱符号分配空间大于强符号空间编译器报警告)。

4.4C++相关问题

重复代码问题:C++编译器很多时候会产生重复的代码(如模板、外部内联函数、虚函数表等),如同一模板在多个编译单元同时实例化成相同的类型时,必然会生成重复代码,若直接将重复代码保留则(1)空间浪费;(2)地址容易出错;(3)指令运行效率低。

解决重复代码问题:现在主流做法是将每个模板的实例代码都单独存放在一个段里,每个段只包含一个模板实例,当不同编译单元实例化模板后可以将相同的模板实例段进行合并;外部内联函数和虚函数表也是类似进行处理。这样带来的问题是同名的段可能拥有不同的内容(编译器优化或版本差异),这时链接器可能任意选择其中一个副本作为链接的输入,同时显示警告。

初始化指令与结束指令的执行:.init 段保存初始化代码,在main函数被调用之前执行(如全局变量);.fini段保存进程终止代码,main函数正常退出后执行(如虚构函数)。

4.5链接控制脚本

链接器控制链接过程的3种方法:(1)使用命令行给链接器指定参数;(2)将链接指令存放在目标文件内,编译器经常使用该方式箱链接器传递指令;(3)使用链接控制脚本。

第五章 Windows PE/COFF

WindowsPE文件较COFF文件变化的2个地方:(1)文件最开始的部分不是COFF文件头,而是DOS MZ可执行文件格式的文件头和桩代码;(2)原来的COFF文件头中的“IMAGE_FILE_HEADER”部分扩展成了PE文件文件头结构”IMAGE_NT_HEADERS”。

PE数据目录:一些装载所需要的数据结构(导入表、导出表、资源、重定位表等)的数据位置和长度都保存在数据目录结构中。

第六章 可执行文件的装载与进程

6.1进程虚拟地址空间

进程和程序的区别:程序是静态的概念,是一些预先编译好的指令和数据集合的一个文件;进程是动态概念,他是程序运行时的一个过程

虚拟地址空间:大小由计算机硬件平台决定,具体是由CPU位数决定,硬件决定了地址空间的最大理论上限,即硬件的寻址空间大小,如32为硬件平台虚拟地址空间地址为0 ~ 232-1,即4GB。

PAE从硬件层面看32位的地址线只能访问最多4GB的物理内存,通过修改页映射方式可以访问到更多的物理内存,将该地址扩展方式叫PAE。当访问这些大于常规的内存空间时,通过窗口映射将额外的内存映射到进程地址空间中,这种访问内存的操作叫AWE。

6.2装载方式

解决内存不足的办法:利用程序运行时候的局部性原理,将程序最常用的部分驻留内存,而将不常用的数据放在磁盘里面,覆盖装入和页映射是典型的动态装载方法,均利用了局部性原理。

覆盖装入(被淘汰)编写程序时手工将程序分成若干块(将模块按照它们间的调用依赖关系组织成树状结构),然后编写一个小的辅助代码(内存覆盖器,一般常驻内存)来管理这些块何时应该驻留内存而何时应该被替换掉。嵌入式内存受限环境下该技术仍发挥作用。

覆盖器需满足的条件:(1)从任何一个模块到树根的模块都叫调用路径,整个调用路径的模块都必须在内存中;(2)禁止跨树间调用。

页映射:将内存和所有磁盘中的数据和指令以页为单位划分成若干页,以后所有装载操作都以页为单位,常见的有4096字节、2MB、4MB等,选择页的算法有FIFO、LUR(最少使用)等。

6.3从操作系统角度看可执行文件的装载

映像文件:由于可执行文件在装载时实际上被映射到虚拟空间,所以可以执行文件很多时候又被称作映像文件。

进程的建立:很多时候一个程序被执行的同时都伴随着一个新进程的创建,一个进程最关键的特征是它拥有独立的虚拟地址空间。创建进程,装载执行文件并执行的过程包含:(1)创建虚拟地址空间;(2)读取可执行文件头,并建立虚拟空间与可执行文件的映射关系;(3)将CPU指令寄存器设置成可执行文件入口,启动运行。

创建虚拟地址空间:创建页映射函数将虚拟空间各个页映射至相应的物理空间,即页映射所需的数据结构。

读取可执行文件头,并建立虚拟空间与可执行文件的映射关系:建立虚拟空间与可执行文件的映射关系,当程序发生缺页错误时,操作系统从物理内存分配一个物理页,然后将该“缺页”从磁盘中读取到内存中,再设置缺页的虚拟页和物理页的映射关系。这种映射关系只是保存在操作系统内部的一个数据结构,Linux将进程虚拟空间中的一个段叫做虚拟内存区域(VMA),当程序发生段错误时它可以通过查找这样的一个数据结构来定位错误页在可执行文件中的位置。

CPU指令寄存器设置成可执行文件入口,启动运行:操作系统通过设置CPU的指令寄存器将控制权交给进程,由此进程开始执行。

页错误:CPU执行到空页面产生页错误,将控制权交给操作系统处理该错误,操作系统通过查询(2)中的数据结构找到空页面所在的VMA并计算相应页面在可执行文件的偏移,然后在物理内存中分配物理页面,建立虚拟页与物理页的映射关系,然后将控制权还回进程,进程从之前错误处继续执行。

2.程序员的自我修养 - 静态链接与程序装载_可执行文件_13

6.4进程虚存空间分布

段的权限分类:(1)以代码段为代表的权限为可读可执行权限;(2)以数据段和BSS段为代表的权限为可读可写的段;(3)以只读数据段为代表的权限为只读的段。

同权限段合并:段的数量增多会造成空间浪费,增大页内碎片,对于同权限的段将其合并到一起当作一个段进行映射,ELF将一个Segment包含一个或多个属性类似的Section,如将.text段和.init段合并在一起看作一个Segment,装载时可将其看作一个整体一起映射,即映射后进程虚存空间只有一个对应的VMA,从而节省内存空间并减少页内碎片。

2.程序员的自我修养 - 静态链接与程序装载_重定位_14

链接视图和执行视图:从Section角度看ELF文件就是链接视图,从Segment角度看ELF文件就是执行视图。

堆和栈:大多情况下,一个进程的栈和堆分别都有一个对应的VMA,

进程虚拟地址空间:操作系统通过给进程空间划分处一个个VMA来管理进程的虚拟空间,将相同权限属性、相同映像文件映射成一个VMA。

VMA划分:代码VMA(读、可执行,有映像文件)、数据VMA(读、写、可执行,有映像文件)、堆VMA(读、写、可执行,无映像文件,匿名可向上扩展)、栈VMA(读、写无映像文件,匿名可向下扩展)。

6.5Liunx内核装载ELF过程

Linux系统bash下输入命令执行ELF程序的过程:bash进程调用fork()系统调用创建一个新的进程,新进程调用execve()系统调用执行指定的ELF文件,原先的bash进程则继续等待刚才启动的新进程结束,然后继续等待用户输入命令。

标签:可执行文件,文件,符号,ELF,程序员,地址,修养,链接
From: https://blog.51cto.com/u_16063698/6181052

相关文章

  • 1.程序员自我修养 - 绪论1
    1.1南北桥      为了协调CPU、内存和高速的图形设备,设计了高速北桥芯片;同时为了协调磁盘、USB、键盘等低速设备,设计了南桥低速芯片。现代计算机北桥已集成到CPU内部,南桥一般指现在的芯片组,新名称为PCH(PlatformControllerHub)。北桥:适配高速设备。北桥芯片则主要是集成了......
  • DOSbox的安装及其运行和基本命令的使用(内附下载链接)
    下载工具AsmTools(内含4个文件)1个安装文件(DOSBox0.74-Win32-installer),3个调试工具(debug、LINK、MASM)链接:https://pan.baidu.com/s/12HX_hHye8upcCO9Wwm6Qtg提取码:1234 任选一个分区盘,如E盘在根目录下建立一个英文文件夹命名为“debug”,将3个调试工具(debug、LINK、MASM)放入......
  • 程序员面试金典---2
    回文排列思路:回文排列的特征之一就是如果字符串中每个字符的个数都是两个,或者只有只有一个字符个数为奇数个。只有上述两种结果。classSolution:defcanPermutePalindrome(self,s:str)->bool://将字符串转成个数字典s_dic=Counter(s)/......
  • 友情链接
    先放宝贝儿们的链接CFCStudioCFCStudio–CrazyforCodesolkatt'sBlogsolkatt的小窝Tajang临渊羡鱼,不如退而结网欢迎大家来交换友链捏,评论区留言就行或者微信我就行,友链格式示例:名称:春告鳥链接:https://www.cnblogs.com/Cl0ud头像:h......
  • 不要做一个傲慢的程序员
    万事通不会在这个职业中走得太远。昨天,另一位开发人员向我描述了一个问题。看起来应该很简单。所以,我这样告诉他。那是个大错误。问题,而不是陈述我以为我知道答案。但这个问题一直困扰着一位非常聪明的工程师。我应该更明智地考虑我的话。当您遇到新问题时,请在开始陈述之前......
  • 程序员的数学1-1 进制转换
    进制转换人们正常接触的数字为十进制格式,但是电脑读取的格式为0或1即二进制表示。  引用程序员的数学1-p3   引用程序员的数学1-p6 反之,求余求几进制的转换就取几进制的余数  引用程序员的数学1-p7C语言的实现  C语言进制转换代码二进制转换为十......
  • keil 5 stm32f4 固件库 set up文件链接
    STSW-STM32065-STM32F4DSP和标准外设库-意法半导体STMicroelectronics ......
  • 修改git的submodule链接
    修改git的submodule链接简介从github导入库到gitee,有些库会以submodule形式依赖第3方库,这时我们从gitee下载时也要对应修改。修改.gitsubmodule文件将库的路径改成gitee库的名称即可gitsubmodule命令gitsubmodule[--quiet][--cached]gitsubmodule[--quiet]add[<opt......
  • 程序员如何与ChatGPT携手作战
    黄昏将至,还是黎明到来?ChatGPT大火,使得程序员对于”35岁危机“的担忧又加一层:如何在35岁之前避免被AI淘汰?因为ChatGPT擅长语言逻辑类的工作,这不正是程序员擅长的事情么?这不是要先革了程序员的命么?哎,人生真难。实际上,ChatGPT并无意于淘汰任何人,它只是一种更为先进的工具。这......
  • 在pycharm链接的矩池云里install 包
    打开pycharm打开矩池云连接到终端会显示3.安装需要的包重点一般是condainstall****,或者是pipinstall***比如pipinstalltorchnet关于更新pippipinstall--upgradepip-ihttp://pypi.douban.com/simple/--trusted-host=pypi.douban.com/simple参考htt......