理解位置无关性和位置相关性

理解位置无关性和位置相关性

  • 本文的目的是通过案例分析程序链接过程中位置无关性和位置相关性。

1.代码如下:
1>.symbol.c

// symbol.c
int my_var = 42;
int my_func(int a, int b) 
{
	return a + b;
}

编译为动态链接库:

gcc -g -m32 -shared -fPIC symbol.c -o libsymbol.so

2>.main.c

// main.c
int var = 10;
extern int my_var;
extern int my_func(int, int);
int main() {
	int a, b;
    a = var;
    b = my_var;
    return my_func(a, b);
}

使用libsymbol.so编译出两个版本, 分别是位置相关的main和位置无关的main_pie:

位置相关
gcc -g -m32 -L. -lsymbol main.c libsymbol.so -o main
位置无关
gcc -g -m32 -L. -lsymbol -pie -fpie main.c libsymbol.so -o main_pie

2.分析位置相关性
  根据链接器约定,32位程序会加载到地址0x08048000,所以以这个地址为基础, 对变量进行绝对地址寻址。 以main为例:

albert/symbol$ readelf -S ./main | grep “.data”
[24] .data PROGBITS 0804a018 001018 00000c 00 WA 0 0 4

关于加载地址0x08048000:
albert/symbol$ readelf -l main
Elf file type is EXEC (Executable file)
Entry point 0x8048470
There are 9 program headers, starting at offset 52
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align

LOAD 0x000000 0x08048000 0x08048000 0x0070c 0x0070c R E 0x1000
LOAD 0x000f00 0x08049f00 0x08049f00 0x00124 0x0012c RW 0x1000

如上所示:
  在可执行文件中 .data section的偏移量为0x1018,加载到虚拟内存中的地址是0x8048000+0x1018=0x804a18, 和显示的结果一样。查看main函数的汇编代码:

albert/symbol$ objdump  -d ./main | grep "<main>" -A 15
0804856d <main>:
 804856d:	55                   	push   %ebp
 804856e:	89 e5                	mov    %esp,%ebp
 8048570:	83 e4 f0             	and    $0xfffffff0,%esp
 8048573:	83 ec 20             	sub    $0x20,%esp
 8048576:	a1 20 a0 04 08       	mov    0x804a020,%eax
 804857b:	89 44 24 18          	mov    %eax,0x18(%esp)
 804857f:	a1 24 a0 04 08       	mov    0x804a024,%eax
 8048584:	89 44 24 1c          	mov    %eax,0x1c(%esp)
 8048588:	8b 44 24 1c          	mov    0x1c(%esp),%eax
 804858c:	89 44 24 04          	mov    %eax,0x4(%esp)
 8048590:	8b 44 24 18          	mov    0x18(%esp),%eax
 8048594:	89 04 24             	mov    %eax,(%esp)
 8048597:	e8 a4 fe ff ff       	call   8048440 <my_func@plt>
 804859c:	c9                   	leave  
 804859d:	c3                   	ret  

注意该行:
8048576: a1 20 a0 04 08 mov 0x804a020,%eax
该行的作用是获取变量,使用的是绝对地址0x804a020(在.data范围内)。
通过gdb查看该地址正是var变量的地址, 且初始值为0xa:

$ gdb ./main
(gdb) x/xw 0x804a020
0x804a020 : 0x0000000a

3.分析位置无关性
  如2所述,由于一个进程只有一个主函数,所以变量按绝对地址寻址, 对于可执行文件来说不是什么问题;但是对于动态链接库而言, 如果每个.so文件都要求加载到某个绝对地址,那简直是噩梦,因为无法保证不和其他的.so加载地址发生冲突。 所以就产生了位置无关性代码。
  查看main_pie:

albert/symbol$ readelf -S ./main_pie | grep “.data”
[24] .data PROGBITS 0000201c 00101c 00000c 00 WA 0 0 4

如上所示:
  偏移量固定, 但Addr部分不是绝对地址。即程序可以加载到虚拟内存的任意位置。继续看看main的汇编:

albert/symbol$ objdump -d main_pie | grep "<main>" -A 20
0000067b <main>:
 67b:	55                   	push   %ebp
 67c:	89 e5                	mov    %esp,%ebp
 67e:	53                   	push   %ebx
 67f:	83 e4 f0             	and    $0xfffffff0,%esp
 682:	83 ec 20             	sub    $0x20,%esp
 685:	e8 c6 fe ff ff       	call   550 <__x86.get_pc_thunk.bx>
 68a:	81 c3 76 19 00 00    	add    $0x1976,%ebx
 690:	8b 83 24 00 00 00    	mov    0x24(%ebx),%eax
 696:	89 44 24 18          	mov    %eax,0x18(%esp)
 69a:	8b 83 f0 ff ff ff    	mov    -0x10(%ebx),%eax
 6a0:	8b 00                	mov    (%eax),%eax
 6a2:	89 44 24 1c          	mov    %eax,0x1c(%esp)
 6a6:	8b 44 24 1c          	mov    0x1c(%esp),%eax
 6aa:	89 44 24 04          	mov    %eax,0x4(%esp)
 6ae:	8b 44 24 18          	mov    0x18(%esp),%eax
 6b2:	89 04 24             	mov    %eax,(%esp)
 6b5:	e8 16 fe ff ff       	call   4d0 <my_func@plt>
 6ba:	8b 5d fc             	mov    -0x4(%ebp),%ebx
 6bd:	c9                   	leave  
 6be:	c3                   	ret   

  注意:685~690处,和之前的区别是这次通过ebx寄存器来对变量进行寻址,对于__x86.get_pc_thunk.bx函数:
objdump -d main_pi | grep “__x86.get_pc_thunk.bx” -A 2
00000550 <__x86.get_pc_thunk.bx>:
550: 8b 1c 24 mov (%esp),%ebx
553: c3 ret

  该函数作用就是把esp(即返回地址)的值保存在ebx中,然后寻址用。经过685和68a两条指令后, ebx的值等于相对当前PC指针的固定位移。
只看静态代码的话,可知:ebx=0x68a+0x1976=0x2000,这个地址位于:

albert/symbol$ readelf -S ./main_pie | grep 2000 -C 1
[22] .got PROGBITS 00001fe4 000fe4 00001c 04 WA 0 0 4
[23] .got.plt PROGBITS 00002000 001000 00001c 04 WA 0 0 4
[24] .data PROGBITS 0000201c 00101c 00000c 00 WA 0 0 4

  它是.got.plt的起始地址。 现在先看汇编的690处,通过ebx+0x24=0x2024获取了变量的值,这个地址已经进入到了.data之中:

gdb ./main_pie
(gdb) x/xw 0x2000+0x24
0x2024 : 0x0000000a

  所以, 位置无关代码实际上就是通过运行时PC指针的值来找到代码所引用的其他符号的位置, 不管二进制文件被加载到哪个位置, 都可以正确执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值