2022年程序员的自我修养静态链接 .pdf

上传人:H****o 文档编号:39716097 上传时间:2022-09-07 格式:PDF 页数:20 大小:2.06MB
返回 下载 相关 举报
2022年程序员的自我修养静态链接 .pdf_第1页
第1页 / 共20页
2022年程序员的自我修养静态链接 .pdf_第2页
第2页 / 共20页
点击查看更多>>
资源描述

《2022年程序员的自我修养静态链接 .pdf》由会员分享,可在线阅读,更多相关《2022年程序员的自我修养静态链接 .pdf(20页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。

1、程序员的自我修养3静态链接Table of Contents 1、链接最实际的做法-相似段合并 .1 2、链接的两个步骤.3 2.1,空间与地址分配.3 2.2,符号解析与重定位。.6 2.2.1 查看反汇编代码(objdump-d),效果如下,.6 2.2.2 那链接器是怎么知道怎么调整?.7 2.2.3 符号地址修改的过程.8 3、解决一个疑问.9 4,API与库.9 5、链接过程控制.14 5.1 控制方法.14 5.2 最小程序 内嵌汇编、系统调用、自建链接脚本.15 5.3 ld 链接脚本语法简介.18 6、用到的一些命令:.20 1、链接最实际的做法-相似段合并有了目标文件之后,接

2、下来的问题就是如何将它们组合起来,形成一个可以使用的程序或者更大的模块,这就是静态链接要解决的问题,静态链接是链接的核心内容。现在有两个文件:a.c b.c 名师资料总结-精品资料欢迎下载-名师精心整理-第 1 页,共 20 页 -程序员的自我修养3静态链接编译成目标文件并查看符号表:名师资料总结-精品资料欢迎下载-名师精心整理-第 2 页,共 20 页 -程序员的自我修养3静态链接可以看出,b.c 总共定义了两个全局符号,变量shared和函数 swap,a.c中只有一个全局符号main。模块 a.c 引用了 b.c 里的两个全局变量。我们接下来要做的就是链接这两个文件,最终形成可执行文件a

3、b.我们知道可执行文件中的代码段和数据段都是由输入的目标文件中合并而来的。这样就产生了第一个问题,对于多个输入文件,链接器怎样将他们的各段合并到输出文件?最简单的方案:简单的叠加,即A 有 5 个段,B 有 5 个段,C 有 5 个段,那么可执行文件中将有15 个段。这种做法非常浪费空间,因为对于X86 硬件来说,段的装载地址和空间对齐的单位是页,也就是4K,也就是说如果段只有1 字节,也要占4K 空间,这样会造成内存空间大量的内部碎片,所以这并不是好的方案。最实际的做法:相似段合并,其中.bss 段在目标文件和可执行文件中并不占用空间,但是在装载的时候占用地址空间。所以链接器合并各段的时候也

4、要.bss 合并,并且分配虚拟空间。2、链接的两个步骤空间与地址分配。扫描所有的输入文件,获得它们的各个段的长度、属性和位置。将输入文件中的符号表中所有的符号定义和符号引用收集起来,统一放到一个全局符号表。合并相应段,计算出合并后的段的长度和位置,并建立映射关系。符号解析与定位。这是链接的核心,根据输入文件中的段信息、重定位信息,进行符号解析和重定位、调整代码中的地址。2.1,空间与地址分配执行链接操作:名师资料总结-精品资料欢迎下载-名师精心整理-第 3 页,共 20 页 -程序员的自我修养3静态链接a.o 和 b.o 的段情况如下:名师资料总结-精品资料欢迎下载-名师精心整理-第 4 页,

5、共 20 页 -程序员的自我修养3静态链接为输出文件分配地址和空间有两种含义,一种是输出的可执行文件的空间;第二个是在装载后的虚拟地址中的虚拟地址空间。对于.text 和.data 这样的实际地址中的段在文件和虚拟地址中都要分配空间,但是.bss 这样的段,分配空间的意义只限于虚拟地址空间,因为它在文件中没有内容。以后谈空间分配,就是虚拟空间分配,因为这个关系到链接器后面关于地址的计算步骤,而可执行文件本身的空间分配与链接的过程关系不大。VMA 虚拟地址LMA 加载地址正常情况下,两个值应该是一样的,但是在有些嵌入式系统中可能不同。我们只关注VMA。在链接之前,目标文件中所有段的VMA 都是

6、0,因为虚拟空间还没有被分配。链接后程序中使用的地址已经是虚拟地址,我们关心VMA 和 size 忽略文件偏移。必须使用虚拟地址,因为每次装载后在内存中的地址才是真实的物理地址,而且由于分页映射机制,物理地址在运行前不可能被确定!只能是分段机制进行地址隔离,虚拟映射,搞的好像每个程序都占用3GB 的可用内存!编程时使用的或者链接后符号地址、在文件中的偏移地址都不可用,实际地址和虚拟地址的映射由操作系统和MMU负责。名师资料总结-精品资料欢迎下载-名师精心整理-第 5 页,共 20 页 -程序员的自我修养3静态链接上图忽略了像.comment这种无关紧要的段,本例又没有.bss 段,所以只关心代

7、码段和数据段。为什么链接器要将ab 的代码段分配到ox08048094,数据段分配到ox08049108,而不是从虚拟地址的 0 地址开始分配呢?这涉及操作系统的进程虚拟地址空间分配规则,在 linux下,ELF可执行文件默认从地址0 x08048000开始分配。段的虚拟地址确定以后,链接器要开始计算各个符号的虚拟地址,因为各个符号在段内位置固定,ld 要为每个符号计算一个偏移量。这个很好计算,三个全局符号的地址被计算出来后,全局符号表被更新。2.2,符号解析与重定位。因为程序内部要使用虚拟地址,所以要对代码进行调整。2.2.1 查看反汇编代码(objdump-d),效果如下,对于 share

8、d 变量引用:Link 之前,在 a.o 中引用shared变量链接之后,查看符号表:名师资料总结-精品资料欢迎下载-名师精心整理-第 6 页,共 20 页 -程序员的自我修养3静态链接发现 shared 的虚拟地址是080490f8。与是查看 ab 的反汇编代码中对shared的引用:而 a.o 对swap的调用:(这是一条近址相对位移调用指令,调用下一条指令,这只是临时的假调用)在 ab 中对 swap 的调用:可以看出在目标文件中,对shared和 swap 的引用都是用假地址来代替,真正的地址计算工作留给了链接器,而在第一步的地址和空间分配后,已经可以确定所有的符号的虚拟地址了。那么链

9、接器可以对每个需要重定位的指令进行地址修正。2.2.2 那链接器是怎么知道怎么调整?哪些指令是要被调整的呢?这些指令的哪些部分需要调整?怎么调整?ELF文件中有个叫重定位表的结构专门来保存这些与重定位相关的信息。名师资料总结-精品资料欢迎下载-名师精心整理-第 7 页,共 20 页 -程序员的自我修养3静态链接对于可重定位文件来说,它必须包含重定位表。重定位表是一个结构体数组,每个结构体描述一个重定位入口,使用objdump r 参数查看重定位表:我们看到重定位表的内容,意思是在.text 段的 15 偏移量的地方有一个叫shared 的变量地址需要重定位,我们查看代码段的命令偏移量 15 处

10、正是 shared的假地址。Swap 同理。每一个要被重定义的地方都叫一个重定位入口。2.2.3 符号地址修改的过程应该是,查看 a.o 的符号表 发现有两项GLOBAL 类型的所在段属性是UND 即未定义,即这两项是需要重定位的,然后在链接的第一步时生成的全局符号表 中找这两个项,找不到则报错,找到了则根据 重定位表 中的偏移找到要修改的重定位点。具体的过程,因为指令的寻址方式有很多种,所以并不是直接从全局符号表中拿出真实的地址就可以用的!在重定位表中有一项TYPE:R_386_32 绝对寻址修正 S+A R_386_PC32 相对寻址修正S+A-P A 代表被修正位置的值,S 表示符号的实

11、际地址,P 代表被修正的位置。两种方式的区别在于绝对寻址修正后的地址为该符号的实际地址,相对寻址修正后的地址为被修改指令距符号的距离差。名师资料总结-精品资料欢迎下载-名师精心整理-第 8 页,共 20 页 -程序员的自我修养3静态链接3、解决一个疑问在上个文档中我们发现,本该和未初始化的静态局部变量一起放在.bss 段的未初始化全局变量,并没有出现在.bss 段,而是被标记为COMMON类型的变量,原因是它是一个弱符号,大小并不确定,可能在多个目标文件中都有定义,在链接的过程中可能被强符号取代,也可能被其它比较大的弱符号取代,所以只有在链接后才能最终确定大小,最终还是会在.BSS段分配空间。

12、所以从总体来看,未初始化的全局变量还是被放在BSS段的。4,API 与库运行库(Run-time Library)是程序运行时需要的支撑库。对于 C/C+程序来说,其运行库一定是动态链接库或者共享库,而不是静态库。比如,很多程序的运行需要 C 标准库以及其它一些库的支持,那么 C 标准库或者需要的库就是此程序的运行库。如果在程序连接的时候对所有的库进行的是静态连接,那么程序运行的时候就不需要任何运行库的支持。操作系统 API提供在这个操作系统上与任何东西互操作的能力:文件、内存、时钟、网络、图形、各种外设等等。API 通常还提供许多工具类的功能:操纵字符串、各种数据类型、时间日期等等。每个 A

13、PI 函数的实现可能由一个系统调用实现,也可以由多个系统调用实现,当然,也可以不使用系统调用,系统调用是内核留给用户的接口,以C 库 API 的方式进行封装。而一些语言库就是对操作系统API 的封装。大致层次:应用程序-语言库-C 运行库(API 提供者)-系统调用内核服务。在LINUX中 C 语言标准库是个例外,它本身就是提供API 的 C 运行库的一部分。比如printf函数在 linux下是一个 write系统调用,而在window下则是一个writeconsole系统 API。世界上最通用的操作系统 API 其实是传统Unix 的 POSIX 接口(可移植操作系统接口),标准 C 的标

14、准库其实就是这个接口的子集,所有类 Unix 操作系统所提供的操作系统 API,几乎都被称为 libc(对 C 库的传统称呼),所有操作系统所提供的自然操作系统接口都是以 C 语言执行库的方式提供的。Windows 操作系统上提供的 Windows API 与POSIX 不同,但也是 C 函数库。因为无论是windows还是 Linux 系统 API 都是 C 语言接口,而且一般以动态库(运行库)的方式提供,所以又称为C 运行库。它们通过或不通过系统调用实现保罗万象的功能。而且一般包含名师资料总结-精品资料欢迎下载-名师精心整理-第 9 页,共 20 页 -程序员的自我修养3静态链接C 标准库

15、的静态库和动态库(C 语言标准运行库),是C 标准库的超集,也就是说glibc和MSVCRT都对其进行了丰富的扩展。比如像线程操作这样的函数并不是C 标准库的一部分。glibc提供一组头文件和一组库文件,最基本、最常用的 C 标准库函数和系统函数在libc.so 库文件中,几乎所有C 程序的运行都依赖于libc.so,有些做数学计算的C 程序依赖于libm.so,以后我们还会看到多线程的C 程序依赖于libpthread.so。以后我说libc 时专指libc.so 这个库文件,而说glibc 时指的是 glibc 提供的所有库文件上图依然正确:第一层,应用程序调用API,展开了说就是,应用程

16、序用某种语言编程,利用了该语言提供的语言库,这个语言库通过API 又调用了 C 运行库,C 运行库经过系统调用申请内核服务。可以想象,用户不能直接调用内核,从而有了应用程序.。shell 程序(解释器)可以提供直接调用内核的接口,想必也是将用户命令解析成系统API,然后调用内核。glibc 的发布版本主要由两部分组成,一部分是头文件,比如stdio.h、stdlib.h等,它们往往位于/usr/include;另外一部分则是库的二进制文件部分。二进制部分主要的就是C 语言标准库,它有静态和动态两个版本。动态的标准库它位于/lib/libc.so.6;而静态标准库位于/usr/lib/libc.

17、a。事实上 glibc除了 C 标准库之外,还有几个辅助程序运行的运行库,这几个文件可以称得上是真正名师资料总结-精品资料欢迎下载-名师精心整理-第 10 页,共 20 页 -程序员的自我修养3静态链接的“运行库”。它们就是/usr/lib/crt1.o、/usr/lib/crti.o和/usr/lib/crtn.o。虽然它们都很小,但这几个文件都是程序运行的最关键的文件。名师资料总结-精品资料欢迎下载-名师精心整理-第 11 页,共 20 页 -程序员的自我修养3静态链接静态 C 标准库/usr/lib/libc.a是目标文件的集合,如printf.o/scanf.o/fread.o/fwr

18、ite.o等 1400多个目标文件,我们编写一个hello world程序,我们需要链接libc.a中的 printf.o形成可执行文件,这个解释似乎很完美。gcc c hello.c ar x licb.a ld hello.o printf.o 链接却失败了,原因是printf.o 依赖其他的目标文件:stdio.o,vfprint.o,很不幸,这两个目标文件还依赖其它的目标文件。理论上我们可以最终收集齐并链接成功。手动链接代价太大。幸好名师资料总结-精品资料欢迎下载-名师精心整理-第 12 页,共 20 页 -程序员的自我修养3静态链接ld 链接器会帮助我们处理这一切繁琐的事物:自动寻找

19、需要的符号,并从libc.a 中解压出来,追中得到可执行文件。是不是说将libc.a和 hello.o链接起来就 OK 了?实际情况恐怕还是令人失望的。当我们编译和链接一个普通的C 程序的时候,不仅要用到C 语言库 libc.a,还有一些辅助性的目标文件和库。使用gcc verbose将所有的编译链接过程打印出来:第一步:用cc1 程序(C 语言编译器)完成编译成汇编文件ccIcaseCz.s 第二步:用as 程序将.s 文件汇编成目标文件ccNmgnnQ.o 第三步:用collection2(ld的封装)将包括 libc.a 的目标文件链接起来形成可执行文件。名师资料总结-精品资料欢迎下载-

20、名师精心整理-第 13 页,共 20 页 -程序员的自我修养3静态链接最后执行可执行文件a.out 最后有一个疑问,为什么静态库里一个目标文件只包含一个函数?为什么这么组织。链接器在链接的时候是以目标文件为单位的,如果很多函数放在同一个目标文件中,如果只用到printf,很多没有用的函数都会被链接到输出文件。5、链接过程控制绝大部分情况下,我们使用链接器提供的默认规则进行链接。但是有一些特殊的程序,如:操作系统内核、BIOS 一些在没有操作系统的情况下运行的程序(bootloader/嵌入式系统程序等)内核驱动等它们往往受限于一些特殊条件,如需要指定输出文件的各段虚拟地址、段的名称、段的存放顺

21、序等,因为这些特殊的环境,特别是硬件条件的限制,往往对程序的各段地址有特殊的要求。整个链接过程还要很多需要确定的:使用哪些目标文件?使用哪些库文件?是否保留调试信息?输出文件格式?导出某些符号以供调试器、程序本身或者其它程序使用?5.1 控制方法链接器大致提供了三种方式控制链接过程:命令行参数链接指令放在目标文件中,编译器经常使用这种方法向链接器传递参数。PE目标文件的.drectve段以用来传递参数。使用链接控制脚本,也是最灵活、最强大的链接控制方法。重点介绍的方法名师资料总结-精品资料欢迎下载-名师精心整理-第 14 页,共 20 页 -程序员的自我修养3静态链接其实如果用户不指定链接脚本

22、时,使用的是默认的链接脚本。是用ld verbose 查看默认脚本。Visual c+将这种控制脚本叫做模块定义文件,扩展名为.def。默认的 ld 脚本存放在/usr/lib/ldscript下:在 Intel IA32下的普通可执行ELF 文件的链接脚本为elf_i386.x;IA32 下的共享库的链接脚本为 elf_i386.xs等。当然,为了更精确的控制链接过程,我们可以自己写一个脚本,然后指定这个脚本为控制脚本。Ld T link.script 5.2 最小程序 内嵌汇编、系统调用、自建链接脚本最终目标是在屏幕上打印出hello world 但是不用 C 库函数。如果使用库的话,就必

23、须有main 函数,我们知道程序的入口在库的_start,由库负责初始化以后调用main函数来执行程序的主体部分,为了不使用 main 这个让我们感到厌烦的函数名,我们的小程序使用nomain作为程序入口。经典的 helloworld程序会产生很多段,main 程序的指令部分会产生.text 段、字符串常量会放在只读数据段或者数据段,还有C 库的一些段。为了演示ld 链接脚本的控制过程,我们将小程序的所有段都合并到一个叫tinytext的段。使用 gcc 内嵌汇编编程printf函数使用 linux 的 Write 系统调用,exit 函数使用 linux 的EXIT 系统调用。系统调用使用O

24、X80 中断实现,其中eax 为调用号。名师资料总结-精品资料欢迎下载-名师精心整理-第 15 页,共 20 页 -程序员的自我修养3静态链接对于 print函数调用 write 系统调用 eax 值为 4,是因为 write 调用号为 4。Edx,ecx,ebx用来传参。用C 描述该系统调用的原型就是:Int write(int filedesc,char*buffer,int size)Filedesc 表示被写入的文件句柄,用ebx 寄存器传值,默认为0,即 stdout 字符串长度为12,用 edx 传参。Ecx 表示要写入的缓冲区地址即str 在 EXIT 系统调用中,ebx 表示进

25、程的退出码,为42.先使用普通的命令行来编译、链接。名师资料总结-精品资料欢迎下载-名师精心整理-第 16 页,共 20 页 -程序员的自我修养3静态链接-e 参数是表明程序的入口地址为nomain,它会把 elf 文件文件头中的入口地址修改为nomain函数的虚拟地址。太神奇了!怎么这么喜欢看到helloworld呢!如果把链接过程比作一台计算机,ld 就是 CPU,目标文件、库文件就是输入,可执行文件就是输出,链接控制脚本就是程序。脚本使用一种特殊的语言写成,即链接脚本语言,这种语言并不复杂,只有为数不多的几种操作。无论是输入文件还是输出文件,主要的数据就是文件中的各段,我们把输入文件中的

26、段成为输入段,输出 文件中的段称为输出段。控制的过程无非是输入段如何变成输出段:哪些输入段要合并成一个输出段?哪些输入段要丢弃?指定输出段的名称、装载地址、属性等。我们新建 tinyhello.lds作为链接脚本ENTRY 指定程序的入口为nomain函数后面的 SECTIONS 命令一般是链接脚本的主体,这个命令指定了输入段到输出段的变换规则。第一行是赋值语句,.代表当前的虚拟地址,因为这条语句后紧跟着tinytext段,所以虚拟地址为。它将当前虚拟地址设置为一个巧妙的值,一边装载时页映射更为方便。名师资料总结-精品资料欢迎下载-名师精心整理-第 17 页,共 20 页 -程序员的自我修养3

27、静态链接第二句是段转换规则,将输入文件中所有名为.text/.data/.rodata的段依次合并到tinytext里。最后一句顾名思义。我们使用这个脚本链接,并运行查看段表,符合预期。5.3 ld 链接脚本语法简介Ld 链接器的链接脚本语言语法继承于AT&T链接器命令语言的语法,风格有点像C,它本身并不复杂。链接脚本由一系列语句组成。语句分两种:命令语句和赋值语句。语句之间使用分号作为分隔符,原则上讲语句之间都要用分号作为分隔符,但是对于命令语句来说也可以使用换行来结束该句。赋值语句必须以;结束。表达式与运算符。脚本语言的语句中可以使用C 语言类似的表达式和运算操作符,比如:+-*、+=-=

28、*=等,甚至包括|这些位操作。注释和字符引用使用/*/作为注释。脚本文件中使用到的文件名、格式名或者段名等凡是包含分号或其它分隔符,用双引号括起来。如果包含引号,无法处理。SECTION 命令负责指定链接过程和段转换过程,也是链接最核心和最复杂的部分。一些常用的命令:名师资料总结-精品资料欢迎下载-名师精心整理-第 18 页,共 20 页 -程序员的自我修养3静态链接ENTRY(symbol)指定符号 symbol的值为入口地址。入口地址即进程执行的第一条用户空间的指令在进程地址空间的地址。它被设定在ELF 文件头中。Ld 有很多中方法可以设定入口地址,优先级顺序如下:Ld 命令行 e 参数链

29、接脚本中的ENTRY 命令如果定义了 _start 符号,使用 _start 符号值如果存在.text 段,使用.text 段的第一字节的地址使用 0 STARTUP(filename)将文件 filename作为链接过程中的第一个输入文件。SEARCH_DIR(path)将路径 path 添加到 ld 链接器的库查找路径INPUT(file,file)INPUT(file file)将指定文件作为链接过程中的输入文件INCLUDE filename将指定文件包含进本链接脚本。PROVIDE(symbol)在链接脚本中定义特殊符号,该符号可以在程序中被引用。更多的命令参见ld 使用手册。SEC

30、TIONS命令语句最基本的格式为:SECTIONS .Secname:contents Content的写法如下:名师资料总结-精品资料欢迎下载-名师精心整理-第 19 页,共 20 页 -程序员的自我修养3静态链接Filename(sections)举例子:File1.o(.data)File1.o(.data,.rodata)没有逗号也行File.o 所有段都符合*(.data)所有输入文件中名字为.data 的文件符合条件。还可以使用正则表达式中的其它规则。6、用到的一些命令:ld a.o b.o-e main-o ab 链接 a 和 b 两个目标文件,指定入口地址和输出文件名称objdump-d a.o 反汇编objdump-r a.o查看重定位表gcc-static-verbase-fno-builtin hello.c静态链接而不是默认的动态,去除优化,打印整个过程gcc verbose 查看默认的链接脚本Ld T link.script 指定脚本名师资料总结-精品资料欢迎下载-名师精心整理-第 20 页,共 20 页 -

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 技术资料 > 技术总结

本站为文档C TO C交易模式,本站只提供存储空间、用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。本站仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知淘文阁网,我们立即给予删除!客服QQ:136780468 微信:18945177775 电话:18904686070

工信部备案号:黑ICP备15003705号© 2020-2023 www.taowenge.com 淘文阁