ELF函数重定位问题

一、背景

ld将.o连接为.so或者可执行程序,以及可执行程序使用.so时,都会遇到函数重定位的问题,本文对该问题进行分析。

二、静态连接

代码示例:

x.c:

#include <stdio.h>

void foo()
{
    printf("foo\n");
}
main.c:

extern void foo(void);

int main(void)
{
    foo();
    return 0;
}
Makefile:

all: main

main: main.o x.o
	$(CC) -m32 -o $@ $^

main.o: main.c
	$(CC) -m32 -c -o $@ $<

x.o: x.c
	$(CC) -m32 -c -o $@ $<

clean:
	rm -f main main.o x.o
调用make进行编译,得到x86 32bit版本的.o和可执行程序

objdump -d main.o得到:

00000000 <main>:
   0:	55                   	push   %ebp
   1:	89 e5                	mov    %esp,%ebp
   3:	83 e4 f0             	and    $0xfffffff0,%esp
   6:	e8 fc ff ff ff       	call   7 <main+0x7>
   b:	b8 00 00 00 00       	mov    $0x0,%eax
  10:	c9                   	leave
  11:	c3                   	ret

0xfffffffc是-4的补码, e8是相对跳转指令,e8 fc ff ff ff所跳转的位置,不和任何函数对应,是个假的位置。

这里应当填入的是foo函数的相对地址,但是我们在编译main.o时,foo是外部函数,无法得知foo的地址,所以使用了0xfffffffc这个假地址做代替,等连接时确定foo函数的地址后,再替换这个假地址。


objdump -d main得到:

08048404 <main>:
 8048404:	55                   	push   %ebp
 8048405:	89 e5                	mov    %esp,%ebp
 8048407:	83 e4 f0             	and    $0xfffffff0,%esp
 804840a:	e8 09 00 00 00       	call   8048418 <foo>
 804840f:	b8 00 00 00 00       	mov    $0x0,%eax
 8048414:	c9                   	leave
 8048415:	c3                   	ret
 8048416:	66 90                	xchg   %ax,%ax

08048418 <foo>:
 8048418:       55                      push   %ebp
 8048419:       89 e5                   mov    %esp,%ebp
 804841b:       83 ec 18                sub    $0x18,%esp
 804841e:       c7 04 24 00 85 04 08    movl   $0x8048500,(%esp)
 8048425:       e8 16 ff ff ff          call   8048340 <puts@plt>
 804842a:       c9                      leave
 804842b:       c3                      ret
 804842c:       8d 74 26 00             lea    0x0(%esi,%eiz,1),%esi

可以看到0xfffffffc这个假地址,已经被替换为0x00000009了。e8相对地址调用0x00000009,会call到0x804840f+0x00000009=0x8048418这个位置,也就是foo的地址。


那么,main.o连接为main时,到底发生了什么?

2.1 .rel.text .rel.data段

.o中有两个段:.rel.text .rel.data,用于连接时,分别处理函数和数据的重定位的问题。

这里只介绍函数的处理,数据的类似,不再赘述。


.rel.text对应的数据结构为:

typedef struct elf32_rel {
  Elf32_Addr	r_offset;
  Elf32_Word	r_info;
} Elf32_Rel;

r_offset,重定位入口的偏移,对于.o来说,是需要修正的位置的第一个字节相对于段起始的偏移;对于.so和可执行程序来说,是需要修正的位置的第一个字节的虚拟地址。

r_info,重定位入口的类型和符号,前三个字节是该入口的符号在符号表中的下标;后一个字节,表示重定位的类型,比如R_386_32、R_386_PC32。


readelf -r main.o得到:

Relocation section '.rel.text' at offset 0x3a0 contains 1 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00000007  00000902 R_386_PC32        00000000   foo

r_offset为0x00000007,我们处理的是.o,表示需要重定位的位置是0x00000007,和之前objdump -d main.o中得到的
6:	e8 fc ff ff ff

一致,需要将0xfffffffc替换为foo的相对地址


r_info为0x00000902,0x000009表示该入口符号,在符号表中的下标为0x000009,readelf -s main.o | grep 9:

     9: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND foo

可以看到这个入口处理的是foo这个函数

0x02表示重定位的类型为R_386_PC32


2.2 指令修正

ld在将main.o,x.o连接为main时,可以获得foo的实际地址为0x08048418,然后根据.rel.text中的重定位信息,进行指令修正。


ld在处理到.rel.text中的foo时,根据r_info中的0x000009可以得知,需要处理foo这个符号,foo的实际地址为0x08048418。

根据r_info中的0x02可以得知,处理方式为R_386_PC32,R_386_PC32表示相对寻址修正S+A-P。其中

A = 保存在被修正位置的值。

被修正位置为0x00000007,这个位置的值是0xfffffffc,所以A为0xfffffffc,即A为-4。


P = 被修正的位置,(相对于段开始的位置或者虚拟地址),可以通过r_offset计算得到。

r_offset为0x00000007,当连接为可执行程序时,应该用被修正位置的虚拟地址,也就是0x0804840b(objdump -d main看到被修正位置的虚拟地址为0x0804840a + 1),所以P为0x0804840b。


S = 符号的实际地址,通过r_info中前三个字节计算得到。

r_info前三个字节为0x000009,在readelf -s main.o可以查到是foo这个符号,其实际地址为0x08048418,S为0x08048418。


S+A-P = 0x08048418 + (-4) - 0x0804840b = 0x00000009,这个就是修正后的值,用它来覆盖0x0804840b这个位置,得到

804840a
  • 7
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值