欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 文化 > 计算机系统基础{四}

计算机系统基础{四}

2025/2/22 2:13:31 来源:https://blog.csdn.net/qq_75246137/article/details/140146070  浏览:    关键词:计算机系统基础{四}

1.目标文件格式

程序的链接概述

链接的作用

模块化

  • 一个程序可以分成很多源程序文件
  • 可构建公共函数库,如数学库,标准I/O库

效率高

  • 一个程序可以分成很多的源程序文件,然后重新链接
  • 可执行文件和运行时的内存中只需包含所调用函数的代码,而不需要包含整个共享库

符号定义

符号定义:全局静态变量名、函数名、全局变量

链接

链接本质:合并相同的“节“

step1:符号解析

编译器将定义的符号存放在一个符号表

链接器将每个符号的引用都与一个确定的符号定义建立关联

step2:重定位

将多个代码段与数据段分别合并为一个单独的代码段和数据段

计算每个定义的符号在虚拟地址空间中的绝对地址

将可执行文件中符号引用处的地址修改为重定位后的地址信息

ELF目标文件、重定位目标文件格式、可执行目标文件格式

可重定位目标文件 (.o) :其代码和数据可和其他可重定位文件合并为可执行文件;每个.o 文件由对应的.c文件生成;每个.o文件代码和数据地址都从0开始

可执行目标文件 (默认为a.out) 包含的代码和数据可以被直接复制到内存并被执行;代码和数据地址为虚拟地址空间中的地址;

共享的目标文件 (.so) 特殊的可重定位目标文件,能在装入或运行时被装入到内存并自动被链接,称为共享库文件

  • .text:代码节
  • .rodata:只读数据节
  • .data:已初始全局数据节
  • .bss:未初始全局数据节

可重定位目标文件

静态链接库文件由若干个可重定位目标文件组成

  • ELF头:ELF魔数(用来确定文件的类型和格式)、版本、小端/大端,目标文件的类型,节头表的起始位置和长度等
  • rodata:只读数据,如 printf 格式串,switch-case的跳转表
  • bss节:未初始化全局变量,仅是占位符,不占据任何实际磁盘空间

让我们来看一个具体例子

  • Size of this header:ELF头长度52字节
  • Entry point address:因为是可重定位的文件所以地址从零开始
  • Start of program headers:程序头表(可选)
  • Start of section headers:节头表离文件起始处的偏移516字节
  • Size of section headers:每个表项40字节
  • Number of section headers:表项数15个

节头表

 

通过上述表我们可以看到.bss是没有分配空间的

可执行目标文件

  • 定义的所有变量和函数已有确定地址(虚拟地址空间中的地址)
  • 符号引用处已被重定位,以指向所引用的定义符号
  • 指令地址和指令给出的操作数地址都是虚拟地址

  •  多一个程序头表,也称段头表
  • 多一个.init节,用于定义_init函数,该函数用来进行可执行目标文件开始执行时的初始化工作
  • 少两个.rel节(无需重定位)

程序头表

描述可执行文件中的节与虚拟空间中的存储段之间的映射关系

表项中包含可装入段(即Type=LOAD)

第一可装入段:包括ELF头、程序头表、.init、.text和.rodata节,是只读代码段

第二可装入段:.data节,是可读写数据段

2.符号解析与重定位

符号和符号表、符号解析

符号

每个可重定位目标模块都有一个符号表:Global symbols(模块内部定义的全局符号) 指不带static的全局变量;External symbols(外部定义的全局符号),由其他模块定义并被模块m引用的全局符号,Local symbols(本模块的局部符号),带static的函数和全局变量

符号表(symtab)

函数名在text节中;变量名在data节或bss节中

让我们来看一个符号表最后三个条目的例子

Name为buf的全局变量,大小是8B,类型是变量,是第3节.data偏移为0的符号

swap是main.o中未定义全局(在其他模块定义)符号,类型和大小未知

符号解析 

将每个模块中引用的符号与某个目标模块中的定义符号建立关联

符号定义的实质:被分配了存储空间,所有定义符号的值就是其目标所在的首地址

全局符号的强弱

  • 函数名和已初始化的全局变量名是强符号
  • 未初始化的全局变量名是弱符号

多重定义符号的处理规则

  • Rule 1: 强符号不能多次定义 强符号只能被定义一次,否则链接错误
  • Rule 2: 若一个符号被定义为一次强符号和多次弱符号,则按强定义为准,对弱符号的引用被解析为其强定义符号
  • Rule 3: 若有多个弱符号定义,则任选其中一个,使用命令 gcc –fno-common链接时,会告诉链接器在遇到多个弱定义的全局符号时输出一条警告信息。

与静态库的链接

静态库

将所有相关的目标模块(.o)打包为一个单独的库文件(.a),称为静态库文件 ,也称存档文件(archive) 增强了链接器功能,使其能通过查找一个或多个库文件中的符号来解析符号,在构建可执行文件时只需指定库文件名,链接器会自动到库中寻找那些应用程序用到的目标模块,并且只把用到的模块从库中拷贝出来 在gcc命令行中无需明显指定C标准库libc.a(默认库)

链接器中符号解析的全过程

  • E 将被合并以组成可执行文件的所有目标文件集合
  • U 当前所有未解析的引用符号的集合
  • D 当前所有定义符号的集合

让我们还是来看一个例子

上述是创建了一个myproc1.c然后让它和mypro2.c打包生成一个mylib的库

开始EDU为空,首先扫描到main.c,把它放入E,同时myfunc1放入U,main加入D。接着扫描mylib,将U的符号与目标模块(myproc1.c、mypro2.c)匹配,找到myfunc1。于是将myproc1.c放入E,将myfunc1放入D,同时将printf放入U。接着扫描默认库文件(libc.a),找到printf.o中的printf,于是将printf.o放入E,将printf从U移到D

重定位信息、重定位过程

  • 合并相同的节

将集合E的所有目标模块中相同的节合并成新节:例如,所有.text节合并作为可执行文件中的.text节

  • 对定义符号进行重定位(确定地址)

确定新节中所有定义符号在虚拟地址空间中的地址:例如,为函数确定首地址,进而确定每条指令的地址,为变量确定首地址 完成这一步后,每条指令和每个全局变量都可确定地址

  • 对引用符号进行重定位(确定地址)

修改.text节和.data节中对每个符号的引用(地址)    需要用到在.rel_data和.rel_text节中保存的重定位信息

重定位信息

  • 汇编器遇到引用时,生成一个重定位条目
  • 数据引用的重定位条目在.rel_data节中
  • 指令中引用的重定位条目在.rel_text节中

两种最基本的重定位类型 R_386_32: 绝对地址;R_386_PC32: PC相对地址

由该图可知,swap在重定义条目rel_text节中:

r_offset=0x7, r_sym=10, r_type=R_386_PC32

其中 r_sym=10是指在符号表中第十位符号swap

R_386_32的重定位方式

假设合并后buf的存储地址ADDR(buf)=0x8049620,buf定义在.data节中偏移为0处,占8B

bufp0紧接在buf后,故地址为0x8049620+8= 0x8049628

因是R_386_32方式,故bufp0内容为buf的绝对地址0x8049620,即“20 96 04 08”

R_386_PC32的重定位方式

已知swap函数紧跟在main后,main函数对应的机器代码从0x8048380开始。

则swap函数开始地址=0x8048380+0x12(main函数大小)=0x8048392,为了4字节对齐,swap函数开始地址是0x8048394

转移目标地址=PC+偏移地址,也就是偏移地址(相对PC地址)=转移目标地址-PC。这里的转移目标地址就是0x8048394,PC=0x8048380+0x7(call指令的偏移量)+4=0x804838b,所以偏移地址=0x8048394-0x804838b=0x9

3.动态链接

可以动态地在装入时或运行时被加载并链接

动态链接可以按以下两种方式进行:

  • 在第一次加载并运行时进行 
  • 在已经开始运行后进行

位置无关代码(PIC)

  • 保证共享库代码的位置可以是不确定的
  • 即使共享库代码的长度发生变化,也不会影响调用它的程序

可通过动态链接器接口提供的函数在运行时进行动态链接,如dlopen, dlsym, dlerror, dlclose等,其头文件为dlfcn.h

#include <stdio.h>
#include <dlfcn.h>
int main() 
{void *handle;void (*myfunc1)();char *error; /* 动态装入包含函数myfunc1()的共享库文件 */handle = dlopen("./mylib.so", RTLD_LAZY);if (!handle) {fprintf(stderr, "%s\n", dlerror());exit(1);}/* 获得一个指向函数myfunc1()的指针myfunc1*/myfunc1 = dlsym(handle, "myfunc1");if ((error = dlerror()) != NULL) {fprintf(stderr, "%s\n", error);exit(1);}/* 现在可以像调用其他函数一样调用函数myfunc1() */myfunc1();/* 关闭(卸载)共享库文件 */if (dlclose(handle) < 0) {fprintf(stderr, "%s\n", dlerror());exit(1);}return 0;
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词