读书-程序员的自我修养-链接、封装与库(9: 第四章:静态链接(2)符号解析,重定位,COMMON块

1. 符号解析与重定位

在完成空间和地址的分配步骤之后,链接器就进行符号解析与重定位。

1.1 重定位

先看a.o里面是怎么使用这两个外部符号的(iShared swap)。
也就是说a.c编译成指令时,它如何访问 iShared 变量的?
如何调用 swap 函数的?

objdump -d a.o //-d 参数看代码段反汇编结果

root@ubuntu-admin-a1:/home/4Chapter# objdump -d a.o
a.o:     file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <main>:
0:	55                   	push   %rbp
1:	48 89 e5             	mov    %rsp,%rbp
4:	48 83 ec 10          	sub    $0x10,%rsp
8:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax
f:	00 00 
11:	48 89 45 f8          	mov    %rax,-0x8(%rbp)
15:	31 c0                	xor    %eax,%eax
17:	c7 45 f4 01 00 00 00 	movl   $0x1,-0xc(%rbp)
1e:	48 8d 45 f4          	lea    -0xc(%rbp),%rax
22:	be 00 00 00 00       	mov    $0x0,%esi		//main 
27:	48 89 c7             	mov    %rax,%rdi
2a:	b8 00 00 00 00       	mov    $0x0,%eax		//iShared 
2f:	e8 00 00 00 00       	callq  34 <main+0x34>  //swap 
34:	b8 00 00 00 00       	mov    $0x0,%eax
39:	48 8b 55 f8          	mov    -0x8(%rbp),%rdx
3d:	64 48 33 14 25 28 00 	xor    %fs:0x28,%rdx
44:	00 00 
46:	74 05                	je     4d <main+0x4d>
48:	e8 00 00 00 00       	callq  4d <main+0x4d>
4d:	c9                   	leaveq 
4e:	c3                   	retq   
root@ubuntu-admin-a1:/home/4Chapter#

可以看到 main 的起始地址是 0x0000。
22: be 00 00 00 00 mov $0x0,%esi //main
因为未进行空间分配,都是0。

2a:	b8 00 00 00 00       	mov    $0x0,%eax		//iShared 
2f:	e8 00 00 00 00       	callq  34 <main+0x34>  //swap 

objdump -d ab
我们再看目标文件ab的反汇编结果,其中main函数的两个重定位入口都已经被修正。

root@ubuntu-admin-a1:/home/4Chapter# objdump -d ab
ab:     file format elf64-x86-64
Disassembly of section .init:
……
0000000000400546 <main>:
400546:	55                   	push   %rbp
400547:	48 89 e5             	mov    %rsp,%rbp
40054a:	48 83 ec 10          	sub    $0x10,%rsp
40054e:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax
400555:	00 00 
400557:	48 89 45 f8          	mov    %rax,-0x8(%rbp)
40055b:	31 c0                	xor    %eax,%eax
40055d:	c7 45 f4 01 00 00 00 	movl   $0x1,-0xc(%rbp)
400564:	48 8d 45 f4          	lea    -0xc(%rbp),%rax
400568:	be 38 10 60 00       	mov    $0x601038,%esi  //iShared
40056d:	48 89 c7             	mov    %rax,%rdi
400570:	b8 00 00 00 00       	mov    $0x0,%eax
400575:	e8 1b 00 00 00       	callq  400595 <swap>  //swap
40057a:	b8 00 00 00 00       	mov    $0x0,%eax
40057f:	48 8b 55 f8          	mov    -0x8(%rbp),%rdx
400583:	64 48 33 14 25 28 00 	xor    %fs:0x28,%rdx
40058a:	00 00 
40058c:	74 05                	je     400593 <main+0x4d>
40058e:	e8 8d fe ff ff       	callq  400420 <__stack_chk_fail@plt>
400593:	c9                   	leaveq 
400594:	c3                   	retq   
……
root@ubuntu-admin-a1:/home/4Chapter# 

修正以后 iShared 和 swap 的地址分别为: 0x38106000 0x1b000000(大端)
这里swap 的地址是个偏移量。
swap 的真实地址是: 0x0840057a + 1b = 0x08400595 正好等于swap的地址

1.2 重定位表

1.2.1 什么是重定位表?

链接器怎么知道哪些指令要被调整/重定位呢?怎么调整?
这就是重定位表的功能了。
重定位表专门保存这些与重定位相关的信息。
重定位表是ELF文件中的一个段。

1.2.2 查看重定位表 objdump -r a.o

root@ubuntu-admin-a1:/home/4Chapter# objdump -r a.o
a.o:     file format elf64-x86-64
RELOCATION RECORDS FOR [.text]:
OFFSET           TYPE              VALUE 
0000000000000023 R_X86_64_32       iShared
0000000000000030 R_X86_64_PC32     swap-0x0000000000000004
0000000000000049 R_X86_64_PC32     __stack_chk_fail-0x0000000000000004
……
root@ubuntu-admin-a1:/home/4Chapter#

可以看到 a.o 里面有两个重定位入口。

1.3 符号解析

1.3.1 常见链接失败例子

root@ubuntu-admin-a1:/home/4Chapter# ld a.o
ld: warning: cannot find entry symbol _start; defaulting to 00000000004000b0
a.o: In function `main':
a.c:(.text+0x23): undefined reference to `iShared'
a.c:(.text+0x30): undefined reference to `swap'
a.c:(.text+0x49): undefined reference to `__stack_chk_fail'
root@ubuntu-admin-a1:/home/4Chapter# 

由于缺少符号的定义所以导致链接失败。一般是缺少某个库导致的。

1.3.2 readelf -s a.o

root@ubuntu-admin-a1:/home/4Chapter# readelf -s a.o

Symbol table '.symtab' contains 12 entries:
Num:    Value          Size Type    Bind   Vis      Ndx Name
	0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
	1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS a.c
	2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 
	3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 
	4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 
	5: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
	6: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 
	7: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 
	8: 0000000000000000    79 FUNC    GLOBAL DEFAULT    1 main
	9: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND iShared
	10: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND swap
	11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND __stack_chk_fail
root@ubuntu-admin-a1:/home/4Chapter#

UND 表示 undefined 未定义类型。
即 iShared swap 是外部符号,它们存在于全局符号表中。

1.4 指令修正方式

近址寻址和远址寻址
绝对寻址和相对寻址
寻址长度: 8 16 32 64位
绝对近址32位寻址
相对近址32位寻址

2 COMMON块

2.1 问题的出现

目前链接器不支持符号的类型,即链接器只知道符号的名字,不知道符号类型。
导致一个问题:
当我们定义多个符号定义类型不一致时,链接器怎么处理?
如: 多个弱符号怎么处理?
未初始化的全局变量是典型的弱符号

2.2 什么是 COMMON 块?

当不同的目标文件需要的COMMON 块空间大小不一致时,以最大的那块为准。

2.2.1 例子说明

当我们不同文件中定义两个同名的不同类型的未初始化的全局变量
a.c
int g_uinit_var;

b.c
double g_uinit_var;

即两个文件链接后输出文件中的 g_uinit_var 所占空间大小为8字节。

2.3 代码例子说明

simple.c

#include<stdio.h>

int g_init_var = 100;
int g_uinit_var;

void func1(int i)
{
	printf("%d\n",i);
}

int main()
{
	static int iStaticVar = 101;
	static int iStaticVar2;
	
	int a = 1;
	int b;
	func1(iStaticVar + iStaticVar2 + a + b);
	return 0;
}

查看 g_uinit_var

root@ubuntu-admin-a1:/home# readelf -s simple.o
Symbol table ‘.symtab’ contains 16 entries:
Num: Value Size Type Bind Vis Ndx Name
……
11: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 g_init_var
12: 0000000000000004 4 OBJECT GLOBAL DEFAULT COM g_uinit_var
13: 0000000000000000 34 FUNC GLOBAL DEFAULT 1 func1
14: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
15: 0000000000000022 53 FUNC GLOBAL DEFAULT 1 main
root@ubuntu-admin-a1:/home#

可以看到 未初始化的全局变量 g_uinit_var 的标志位 COM 即 COMMON块

来源于: 早期的Fortran。

2.4 为什么会有 COMMON 块?

编译器和链接器允许不同类型的弱符号存在。
本质原因是链接器不支持符号类型,即链接器无法判断各个符号的类型是否一致。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值