第七章:动态链接的实现

第七章:动态链接的实现

7.1 动态链接的基本实现

动态链接器

动态链接器的主要功能:

  1. 加载共享库
  2. 符号解析
  3. 重定位

示例代码:

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
#include <stdio.h>
#include <dlfcn.h>

int main() {
// 加载共享库
void *handle = dlopen("./libshared.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "dlopen failed: %s\n", dlerror());
return 1;
}

// 获取函数地址
void (*func)() = dlsym(handle, "shared_func");
if (!func) {
fprintf(stderr, "dlsym failed: %s\n", dlerror());
dlclose(handle);
return 1;
}

// 调用函数
func();

dlclose(handle);
return 0;
}

编译和运行命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 创建共享库
cat > libshared.c << EOL
#include <stdio.h>

void shared_func() {
printf("This is a shared function\n");
}
EOL

gcc -shared -fPIC libshared.c -o libshared.so

# 编译主程序
gcc dynamic_linker.c -ldl -o dynamic_linker

# 运行
./dynamic_linker

符号解析

符号解析的过程:

  1. 收集所有符号
  2. 建立符号表
  3. 解析符号引用

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <dlfcn.h>

int main() {
// 加载共享库
void *handle = dlopen("./libshared.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "dlopen failed: %s\n", dlerror());
return 1;
}

// 解析符号
void (*func1)() = dlsym(handle, "shared_func1");
void (*func2)() = dlsym(handle, "shared_func2");

if (func1) func1();
if (func2) func2();

dlclose(handle);
return 0;
}

编译和运行命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 创建共享库
cat > libshared.c << EOL
#include <stdio.h>

void shared_func1() {
printf("Function 1\n");
}

void shared_func2() {
printf("Function 2\n");
}
EOL

gcc -shared -fPIC libshared.c -o libshared.so

# 编译主程序
gcc symbol_resolve.c -ldl -o symbol_resolve

# 运行
./symbol_resolve

7.2 动态链接的优化

延迟绑定

延迟绑定的实现:

  1. 第一次调用时解析
  2. 缓存解析结果
  3. 后续调用直接使用

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <dlfcn.h>

int main() {
// 加载共享库
void *handle = dlopen("./libshared.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "dlopen failed: %s\n", dlerror());
return 1;
}

// 第一次调用(触发延迟绑定)
void (*func)() = dlsym(handle, "shared_func");
if (func) func();

// 第二次调用(使用缓存)
func = dlsym(handle, "shared_func");
if (func) func();

dlclose(handle);
return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译
gcc lazy_binding.c -ldl -o lazy_binding

# 运行
./lazy_binding

共享库缓存

共享库缓存的实现:

  1. 缓存共享库路径
  2. 缓存符号解析结果
  3. 加速加载过程

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <dlfcn.h>

int main() {
// 加载共享库(使用缓存)
void *handle = dlopen("./libshared.so", RTLD_NOW);
if (!handle) {
fprintf(stderr, "dlopen failed: %s\n", dlerror());
return 1;
}

// 获取函数地址
void (*func)() = dlsym(handle, "shared_func");
if (func) func();

dlclose(handle);
return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译
gcc shared_cache.c -ldl -o shared_cache

# 运行
./shared_cache

7.3 动态链接的调试

调试工具

常用的调试工具:

  1. ldd
  2. nm
  3. objdump
  4. readelf

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <dlfcn.h>

int main() {
// 加载共享库
void *handle = dlopen("./libshared.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "dlopen failed: %s\n", dlerror());
return 1;
}

// 获取函数地址
void (*func)() = dlsym(handle, "shared_func");
if (func) func();

dlclose(handle);
return 0;
}

调试命令:

1
2
3
4
5
6
7
8
9
10
11
# 查看依赖
ldd debug_dl

# 查看符号表
nm libshared.so

# 查看重定位信息
objdump -R debug_dl

# 查看动态段
readelf -d debug_dl

错误处理

错误处理的方法:

  1. 检查返回值
  2. 使用dlerror
  3. 设置错误处理函数

示例代码:

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
#include <stdio.h>
#include <dlfcn.h>

void error_handler(const char *msg) {
fprintf(stderr, "Error: %s\n", msg);
}

int main() {
// 设置错误处理函数
dlerror();

// 加载共享库
void *handle = dlopen("./libshared.so", RTLD_LAZY);
if (!handle) {
error_handler(dlerror());
return 1;
}

// 获取函数地址
void (*func)() = dlsym(handle, "shared_func");
if (!func) {
error_handler(dlerror());
dlclose(handle);
return 1;
}

// 调用函数
func();

dlclose(handle);
return 0;
}

编译和运行命令:

1
2
3
4
5
# 编译
gcc error_handle.c -ldl -o error_handle

# 运行
./error_handle

实践练习

  1. 动态链接实验
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
# 创建共享库
cat > libtest.c << EOL
#include <stdio.h>

void test_func() {
printf("Test function\n");
}
EOL

gcc -shared -fPIC libtest.c -o libtest.so

# 创建主程序
cat > main.c << EOL
#include <stdio.h>
#include <dlfcn.h>

int main() {
void *handle = dlopen("./libtest.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "dlopen failed: %s\n", dlerror());
return 1;
}

void (*func)() = dlsym(handle, "test_func");
if (func) func();

dlclose(handle);
return 0;
}
EOL

# 编译和运行
gcc main.c -ldl -o main
./main
  1. 调试实验
1
2
3
4
5
6
7
8
# 查看依赖
ldd main

# 查看符号表
nm libtest.so

# 查看重定位信息
objdump -R main
  1. 错误处理实验
1
2
3
4
5
# 删除共享库
rm libtest.so

# 运行程序(应该显示错误)
./main

思考题

  1. 动态链接器的主要功能是什么?
  2. 什么是延迟绑定?它有什么作用?
  3. 如何优化动态链接的性能?
  4. 常用的动态链接调试工具有哪些?
  5. 如何处理动态链接中的错误?

参考资料

  1. 《程序员的自我修养:链接、装载与库》
  2. 动态链接器文档
  3. ELF 文件格式规范
  4. GNU Binutils 文档
  5. System V ABI