在学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看看吧
显示不可访问...