forked from mrchuanxu/RegularNotes
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsharelib.c
More file actions
141 lines (136 loc) · 7.3 KB
/
sharelib.c
File metadata and controls
141 lines (136 loc) · 7.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
/**
* 所谓不动笔墨不读书系列
* 其实STL库很必要了解
* awk,需要了解一下
* 检测进程 ps -ef|grep process
* grep是一个过滤器,等会需要好好了解一下
* shell是一个好东西,直接操作bash,写了sh就能直接运行
*
* 所谓不动笔墨不读书
* 接下来就好好读读书
*
* 解析共享库工作机理,灵活组织大型项目
*
* 共享库
* 共享库产生的渊源
* 静态库 在共享库出现之前,公用功能以静态库的形式存在,通用功能模块的多个目标文件打包在一起
* 用到它的程序只需要在链接时指定这个库文件,连接器就会从这个库中抽取出用到的功能代码拷贝到目标程序中
* 而不需要每次都对这些通用功能代码重新编译
*
* 静态库逐渐暴露了问题
* 一是磁盘和内存空间占用大。需要占用物理内存
* 二是库的版本升级非常麻烦。每个引用到它的程序都需要与新版本的库重新链接
*
* 共享库解决静态库问题
* 只在目标文件中记录一条引用信息,标记引用到的库函数,直到程序运行时才由动态连接器去定位功能代码的位置
* 每个共享库在物理内存中只有一份副本,多个应用会在各自的虚拟地址空间内映射这同一份可执行文件,因此可以节省客观的内存空间
*
* 共享库工作的关键在于运行时的动态加载和链接,因此也叫做动态链接库
* 同一个库在不同进程中的映射地址是不同的,为了解决多个库加载地址可能冲突的问题
*
* 动态库技术需要解决两个问题
* 1. 库代码需要能在任意内存地址运行
* 2. 映射之后库之间互相引用的代码能定位到正确的地址
*
* 静态共享库 模块装载地址的问题。 操作系统统一管理各种模块的加载地址
*
* 位置独立的代码 支持任意地址加载运行
* GCC生成位置独立的代码,需要使用-fPIC选项。需要以特定的方式生成获取变量和函数地址的代码
*
* ***/
#include <stdio.h>
static int static_val;
extern int extern_val;
int global_val;
extern void external();
static void internal() {
static_val = 0x11223344;// 模块内部数据访问
}
void sharefunc(int caller) {
internal(); // 模块内部函数调用
extern_val = 0x55667788; // 模块间的数据访问
global_val = 0x99AABBCC;
external(); // 模块间的函数调用
}
/**
* 编译成动态链接库
* gcc -fPIC -shared -o libshared.so shared.c
*
* 反汇编 objdump
*
* objdump -SD libshared.so > libshared.dump
*
* 模块内部的函数调用
*
* 根据生成的汇编码 e8 da ff ff ff 在intel编程手册可见,e8是香港队地址跳转的call指令的操作码,功能是调用下一条指令地址
* 算起偏移指定的偏移量的函数
* 6d1: e8 da ff ff ff callq 6b0 <internal>
* 而internal的初始地址也是从6b0开始
* 寻址方式天生就是与加载地址无关,所以不需要对动态库做特殊处理
*
* 模块内部数据访问 使用相对地址寻址
* 用当前指令地址加某个固定偏移量的方式就可以定位到目标数据
* rip指当前执行指令的下一条指令地址
*
* .bss没指令,只有数据,初始数据都是0
*
* 它也天生是与加载地址无关,也不需要对动态库做特殊处理
*
* 模块内数据的访问也是采用相对地址偏移的方式完成的,偏移位置是在编译时由编译器计算得到的
* 天生与加载地址无关,不需要对动态库做特殊处理
*
* 32位 使用本地相对地址跳转调用一个函数
*
* 模块之间的数据访问
* 不同模块加载后,相对位置不再是固定的,只能等到所有模块都加载完成后
* 才能根据实际的加载位置动态调整访问地址
*
* 考虑到共享库的数据段在多个进程中是每个进程独立维护的,同时数据段和代码段的相对地址是固定的
* 所以 可以让代码段到数据段的某个位置去获取变量的实际地址,程序运行时动态调整该数据段相应位置的值
* 使之指向正确的目标变量地址
*
* 在ELF中,存放外部变量真实地址的区域是.got(全局偏移表),模块内引用到的每个外部变量在该段中都会被预留出相应的位置
* 访问外部变量的代码会用相应位置寻址的方式找到这个位置,再从这个位置上读取变量真实的存放地址
*
* 程序启动时由动态连接器负责修改.got中的每一项,使之指向正确的外部变量虚拟地址
*
* 模块间的数据访问是生成了访问固定相对位置的代码,然后在这个位置上保存变化的地址的方式实现的
*
* 模块之间的函数调用
* 模块之间函数地址的确定方式与模块间的数据是类似的,也是用与代码段相对位置固定的一块区域保存外部函数的实际加载地址
* 运行时让动态连接器根据目标模块的实际加载地址来调整这里记录的地址值
*
* linux采用延迟绑定技术,程序启动时不会解析所有外部函数的符号地址,而是只有在第一次执行到引用这个外部函数的时候,采取执行函数实际地址的查找
* .got.plt段,全局偏移表
*
* 共享库模块间函数调用的实现,也是访问固定相对位置的代码,并在该位置保存变化在函数地址的方式
* 只是在这中间又加了一层精巧的延迟绑定逻辑
*
*
* 所以共享库通过生成固定相对位置的代码,和相对位置寻址,使得共享库加载过程能够实现与位置无关
*
* 动态链接的程序从装载进内存到可以正常工作之间经历的每个步骤
* 1. 装载由操作系统完成。操作系统按照ELF文件的程序头信息把指定的段映射到进程的虚拟地址空间内,并设置相应的访问权限
* 静态链接 加载后直接跳转到ELF文件头指定的入口地址去执行
* 动态链接,操作系统把动态连接器的映像也映射到进程的虚拟空间内,然后把控制权交给动态连接器
* 判断动静态。看.interp有没有东西
* 2. 动态连接器会负责加载依赖的其他共享库,并修正相互之间引用的函数和数据地址
* 广度优先搜索顺序,找出当前进程所有直接或间接依赖的共享库,并把它们都映射到进程的虚拟地址空间内
*
* 3.确定需要重定位的代码位置(.rela.dyn)
* 记录所有需要重定位的代码地址和重定位类型,以及定位目标的符号名
* 这些信息是在编译过程由编译器填上去的。
*
* 查找符号对应的地址(.dynsym)
* 动态连接器要确定外部符号的实际加载地址
*
* linux上的动态连接器在查找其他模块的地址时,会从可执行程序开始,用广度优先算法依次查找。
* 先可执行文件的符号表->查找可执行ELF文件直接依赖的动态库->层层递归找
*
* 提高符号查找效率(.hash)
* 为了优化符号查找的效率,ELF中引入了哈希数据段(.hash) 用于加快查找指定符号的速度
*
* 静态库太重,改为位置无关代码,通过相对地址寻址、加载外部相对固定位置的代码,与动态链接库的重定位,修正以及hash寻符号链接
* 使得共享库可以脱离位置,直接通过符号链接共享。
*
* ***/