南大计算机系统基础——链接

395 阅读6分钟

在学CSAPP第七章链接,感觉CMU的老师讲的太快了跳过了很多细节,实在难以消化,因而去看了南大袁春风老师关于链接的讲解,感觉袁老师讲的十分清楚透彻。不过由于这一块的内容虽算不上很杂但是很多,因此在此记录一下笔记。

@[toc]

1.可执行文件生成概述

在这里插入图片描述

①预处理

在这里插入图片描述 预处理将#define定义的宏直接粘贴到文本中,处理条件预编译指令,把#include 头文件中合适的文本粘贴进来。 在这里插入图片描述

②编译

在这里插入图片描述

③汇编

在这里插入图片描述

④链接

在这里插入图片描述 在这里插入图片描述 本次内容都以上面的两个c代码作为例子。 在main.c中定义了全局变量buf,全局函数main,在swap.c中定义了全局变量bufp0和local变量(这里不是一般说的那种局部变量,这里是以static变量为局部变量)bufp1,temp是分配在栈中的,链接不涉及。 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述


⑤Linux操作

在这里插入图片描述

2.目标文件格式概述

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

3.ELF可重定位目标文件

在这里插入图片描述

①可重定位目标文件格式

在这里插入图片描述 在这里插入图片描述

②ELF Header(ELF 头)

在这里插入图片描述 readelf -h main.o可以查看ELF Header. 在这里插入图片描述 可以看到,ELF Header中有系统的基本信息,程序的入口Entry,程序头表(段头表)Program Header,节头表Section header(根据此可以导向节头表,节头表有更多的信息)的地址。

③Section Header Table(节头表)

在这里插入图片描述 readelf -S main.o查看节头表 在这里插入图片描述 这个感觉看起来好乱,可能是系统转为中文的原因,还是看PPT的把。 在这里插入图片描述 在这里插入图片描述 Section Header Table 节头表实际上就是各个section的索引,以及记录各个节的长度、对齐等重要信息。

man readelf

在这里插入图片描述

4.ELF可执行目标文件

①可执行目标文件格式

在这里插入图片描述

②ELF Header(ELF 头)

readelf -h p可以查看ELF Header. 在这里插入图片描述 可以看到上面写着类型为共享目标文件(因为之前没有静态链接,默认为动态链接),想要得到的可执行文件文件的话,在链接时使用静态链接: gcc -static -O2 -g -o p main.o swap.o 后面都以这个静态链接的p来讲解。 在这里插入图片描述

③Program Header Table(程序头表)

readelf -l p. 在这里插入图片描述

在这里插入图片描述 在这里插入图片描述 程序头表记录着可执行文件载入到内存时分配的虚拟地址空间。可执行文件中的地址仍然是从0开始的,这个地址与虚拟地址的映射关系放在程序头表中,一遍将来载入内存时运行的时候可以根据用虚拟内存来运行。

④存储器映像

可执行文件载入到内存的映射方式: 在这里插入图片描述

5.符号及符号表

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 符号解析其实就是对每个符号统一确定一个地址,并且引用该符号时能够绑定到那个地址。 在这里插入图片描述 这里的全局变量名指的就是global,与之并列的分类是local,即带static的函数名或者变量名 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

6.静态链接和符号解析

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 ar rcs 库名.a module1.o module2.o ...打包成静态库。 在这里插入图片描述

静态库.a文件中是有一个目录的,应该是记录了所有的模块名称和定义的函数或变量名称吧,然后是根据U中需要解析的那个函数名去找对应的.o模块。我感觉一开始U不是空的,而是有一个main,这样才比较符合逻辑。因为我试了一下,gcc -static -o p1 swap.o会报错(.text+0x24): undefined reference to main,说没有main的定义。这样后面的就说的通了。(后面的所有分析都基于这个假设)

在这里插入图片描述 所以符号解析的一大重要目的就是得到需要的.o文件集E,以及引用到的符号,另外一大目的感觉就是如果有多处定义同个符号时,要确定到底用哪个符号 吧? 在这里插入图片描述

由于符号解析是一个个地比对.o文件或者.a库,比对完了以后该.o文件或者该.a库就扔掉了,如果U中仍有未解析的,就继续找后面没找过的库或.o模块。所以gcc -static -o myproc ./mylib.a main.o出错,因为一开始U=main,检索mylib.a,没有找到main的定义,就扔掉这个库了,然后检索main.o,有了定义了,但是又需要调用myfunc1,然后找默认的libc库,显然是找不到的。因为找完了都找不到,那么报错。

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

7.符号的重定位

在这里插入图片描述 在这里插入图片描述 引用的地方都会产生一个重定位条目,记录该引用的信息,包括在哪引用地址,引用的符号,以及引用类型(相对寻址还是绝对寻址)。有了这些信息以后,之后把多个.o文件的text,data..合并到虚拟地址空间中的text,data..中时就会很方便。 在这里插入图片描述 在这里插入图片描述 readelf -r main.o可展示重定位条目 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

8.可执行文件的加载

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 readelf -h p,可执行文件p的ELF头信息 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

9.共享库和动态链接

计算机默认都是采用动态链接。 在这里插入图片描述 在这里插入图片描述

在这里插入图片描述 gcc -shared -fPIC -o 共享库名.so module1.o module2.o ... 将module1.o .. 打包成共享库名.so 有两种动态链接:加载时和运行时。 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

10.深入理解动态链接

①何谓PLT与GOT

动态链接大概知道什么过程,但非常不清楚细节,参考聊聊Linux动态链接中的PLT和GOT(1)——何谓PLT与GOT,深入其中的细节看看。

#include <stdio.h>
 
void print_banner()
{
    printf("Welcome to World of PLT and GOT\n");
}
 
int main(void)
{
    print_banner();
 
    return 0;
}

编译:gcc -Wall -g -o test.o -c test.c -m32 链接:gcc -o test test.o -m32 反汇编 objdump -d test > testdata.txt得到:

080483d4 <print_banner>:
 80483d4:	55                   	push   %ebp
 80483d5:	89 e5                	mov    %esp,%ebp
 80483d7:	83 ec 18             	sub    $0x18,%esp
 80483da:	c7 04 24 d0 84 04 08 	movl   $0x80484d0,(%esp)
 80483e1:	e8 0a ff ff ff       	call   80482f0 <puts@plt>
 80483e6:	c9                   	leave  
 80483e7:	c3                   	ret    

080483e8 <main>:
 80483e8:	55                   	push   %ebp
 80483e9:	89 e5                	mov    %esp,%ebp
 80483eb:	83 e4 f0             	and    $0xfffffff0,%esp
 80483ee:	e8 e1 ff ff ff       	call   80483d4 <print_banner>
 80483f3:	b8 00 00 00 00       	mov    $0x0,%eax
 80483f8:	c9                   	leave  
 80483f9:	c3                   	ret    
 80483fa:	90                   	nop
 80483fb:	90                   	nop
 80483fc:	90                   	nop
 80483fd:	90                   	nop
 80483fe:	90                   	nop
 80483ff:	90                   	nop
080482f0 <puts@plt>:
 80482f0:	ff 25 00 a0 04 08    	jmp    *0x804a000
 80482f6:	68 00 00 00 00       	push   $0x0
 80482fb:	e9 e0 ff ff ff       	jmp   

在这里插入图片描述 x/1x *0x804a000是找到0x804a000地址的地址,即: 在这里插入图片描述 可以看到,printf是跳转到0x00000068中去执行,且看名字是< puts@plt+6 >,所以printf的指令地址放在PLT中。 去访问0x00000068看看吧 在这里插入图片描述 显示不可访问... 在这里插入图片描述