第四章:静态链接

第四章:静态链接

4.1 空间与地址分配

空间分配

静态链接时,链接器需要为每个目标文件分配空间。主要包括:

  1. 代码段空间
  2. 数据段空间
  3. BSS 段空间

示例代码:

1
2
3
4
5
6
7
8
#include <stdio.h>

int a = 10;
int b = 20;

void func_a() {
printf("a = %d, b = %d\n", a, b);
}
1
2
3
4
5
6
7
8
#include <stdio.h>

int c = 30;
int d = 40;

void func_b() {
printf("c = %d, d = %d\n", c, d);
}
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

extern void func_a();
extern void func_b();

int main() {
func_a();
func_b();
return 0;
}

编译和链接命令:

1
2
3
4
5
6
7
8
9
10
# 编译
gcc -c static_a.c -o static_a.o
gcc -c static_b.c -o static_b.o
gcc -c static_main.c -o static_main.o

# 链接
gcc static_a.o static_b.o static_main.o -o static_program

# 查看空间分配
objdump -h static_program

地址分配

链接器需要为每个符号分配地址。主要包括:

  1. 代码段地址
  2. 数据段地址
  3. BSS 段地址

使用以下命令查看地址分配:

1
2
3
4
5
# 查看符号地址
nm static_program

# 查看详细的段信息
readelf -S static_program

符号解析

链接器需要解析所有符号的引用。主要包括:

  1. 全局符号解析
  2. 局部符号解析
  3. 弱符号解析

示例代码:

1
2
3
4
5
6
7
8
#include <stdio.h>

int global_var = 10;
static int static_var = 20;

void func_a() {
printf("global_var = %d, static_var = %d\n", global_var, static_var);
}
1
2
3
4
5
6
7
8
#include <stdio.h>

extern int global_var;
static int static_var = 30;

void func_b() {
printf("global_var = %d, static_var = %d\n", global_var, static_var);
}
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

extern void func_a();
extern void func_b();

int main() {
func_a();
func_b();
return 0;
}

编译和链接命令:

1
2
3
4
5
6
7
8
9
10
# 编译
gcc -c symbol_resolve_a.c -o symbol_resolve_a.o
gcc -c symbol_resolve_b.c -o symbol_resolve_b.o
gcc -c symbol_resolve_main.c -o symbol_resolve_main.o

# 链接
gcc symbol_resolve_a.o symbol_resolve_b.o symbol_resolve_main.o -o symbol_resolve_program

# 查看符号解析结果
nm symbol_resolve_program

4.2 符号解析与重定位

符号解析

符号解析的过程:

  1. 收集所有目标文件的符号
  2. 建立符号表
  3. 解析符号引用

示例代码:

1
2
3
4
5
6
#include <stdio.h>

int a = 10;
void func_a() {
printf("a = %d\n", a);
}
1
2
3
4
5
6
#include <stdio.h>

extern int a;
void func_b() {
printf("a = %d\n", a);
}
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

extern void func_a();
extern void func_b();

int main() {
func_a();
func_b();
return 0;
}

编译和链接命令:

1
2
3
4
5
6
7
8
9
10
# 编译
gcc -c resolve_a.c -o resolve_a.o
gcc -c resolve_b.c -o resolve_b.o
gcc -c resolve_main.c -o resolve_main.o

# 链接
gcc resolve_a.o resolve_b.o resolve_main.o -o resolve_program

# 查看符号解析结果
nm resolve_program

重定位

重定位的过程:

  1. 计算符号的最终地址
  2. 修改符号引用
  3. 更新重定位表

示例代码:

1
2
3
4
5
6
#include <stdio.h>

int a = 10;
void func_a() {
printf("a = %d\n", a);
}
1
2
3
4
5
6
#include <stdio.h>

extern int a;
void func_b() {
printf("a = %d\n", a);
}
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

extern void func_a();
extern void func_b();

int main() {
func_a();
func_b();
return 0;
}

编译和链接命令:

1
2
3
4
5
6
7
8
9
10
11
12
# 编译
gcc -c relocate_a.c -o relocate_a.o
gcc -c relocate_b.c -o relocate_b.o
gcc -c relocate_main.c -o relocate_main.o

# 链接
gcc relocate_a.o relocate_b.o relocate_main.o -o relocate_program

# 查看重定位信息
objdump -r relocate_a.o
objdump -r relocate_b.o
objdump -r relocate_main.o

重定位表

重定位表记录了需要重定位的位置。主要包括:

  1. 重定位类型
  2. 重定位符号
  3. 重定位地址

使用以下命令查看重定位表:

1
2
3
4
5
# 查看重定位表
readelf -r relocate_program

# 查看详细的段信息
readelf -S relocate_program

实践练习

  1. 空间分配实验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 创建示例文件
cat > space_test.c << EOL
#include <stdio.h>

int global_var = 10;
static int static_var = 20;

void func() {
printf("global_var = %d, static_var = %d\n", global_var, static_var);
}

int main() {
func();
return 0;
}
EOL

# 编译
gcc -c space_test.c -o space_test.o

# 查看空间分配
objdump -h space_test.o
  1. 符号解析实验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 创建示例文件
cat > resolve_test.c << EOL
#include <stdio.h>

extern int global_var;
void func() {
printf("global_var = %d\n", global_var);
}

int main() {
func();
return 0;
}
EOL

# 编译
gcc -c resolve_test.c -o resolve_test.o

# 查看符号表
nm resolve_test.o
  1. 重定位实验
1
2
3
4
5
# 查看重定位信息
objdump -r resolve_test.o

# 查看详细的段信息
readelf -S resolve_test.o

思考题

  1. 静态链接时,链接器如何分配空间?
  2. 符号解析的过程是什么?
  3. 重定位的作用是什么?
  4. 重定位表的作用是什么?
  5. 如何处理符号冲突?

参考资料

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