开源开发工具技术(OSDT)博客

OSDT = HelloGCC + HelloLLVM

xmj@hellogcc

参考文档:SYSTEM V APPLICATION BINARY INTERFACE

重定位(relocation),可以理解为有些内容在编译和汇编的时候不能确定下来,需要在连接成可执行程序时才能够计算求得。最常见的就是跳转指令中的目标地址。

ELF文件中会有一些重定位段,比如对应于.text段的.rel.text段。重定位段中的每一项,即重定位项,描述了其对应的需要被重定位的地方,以及如何进行求值。

1、重定位项有两种结构类型,

1
2
3
4
5
6
7
8
9
10
typedef struct {
  Elf32_Addr r_offset;
  Elf32_Word r_info;
} Elf32_Rel;
 
typedef struct {
  Elf32_Addr r_offset;
  Elf32_Word r_info;
  Elf32_Sword r_addend;
} Elf32_Rela;

r_offset,需要进行重定位的地方,到其所在段开始处的字节偏移量;

r_info,有两部分组成,一部分是所对应的符号表中的索引,一部分是重定位类型;它们的组合方式如下:

1
2
3
#define ELF32_R_SYM(i) ((i)>>8)
#define ELF32_R_TYPE(i) ((unsigned char)(i))
#define ELF32_R_INFO(s,t) (((s)<<8)+(unsigned char)(t))

r_addend,计算求值时的常量加数。

MIPS的重定位项是使用了第一种方式,这种方式是将r_addend的值存放在了将要被修改的位置,也就是目标文件中需要被重定位的地方的初始内容。

2、执行如下命令,

`
$ mipsel-linux-readelf -r sysconf.o`

Relocation section ‘.rel.rodata’ at offset 0xb74 contains 132 entries:
Offset Info Type Sym.Value Sym. Name
00000000 0000020c R_MIPS_GPREL32 00000000 .text
00000004 0000020c R_MIPS_GPREL32 00000000 .text
00000008 0000020c R_MIPS_GPREL32 00000000 .text
0000000c 0000020c R_MIPS_GPREL32 00000000 .text
00000010 0000020c R_MIPS_GPREL32 00000000 .text

这里,打印出了每个重定位项的offset和info。从info中的内容,解析出了type和sym索引,进一步解析出sym.name和sym.value。可以看到,除了.text段中有需要进行重定位的地方以外,.rodata中也有一些,注意,这些offset是对应于.rodata段。

3、执行如下命令,

`
$ mipsel-linux-objdump -Dr sysconf.o`

Disassembly of section .rodata:

00000000 <.rodata>:
0: ffffc064 0xffffc064
0: R_MIPS_GPREL32 .text
4: ffffc06c 0xffffc06c
4: R_MIPS_GPREL32 .text
8: ffffc074 0xffffc074
8: R_MIPS_GPREL32 .text
c: ffffc07c 0xffffc07c
c: R_MIPS_GPREL32 .text
10: ffffc084 0xffffc084
10: R_MIPS_GPREL32 .text

这里将.rodata中需要重定位的地方和对应的重定位项结合在一起打印出来。可以看出,对于.rodata中的第一个字,其保存的addend值为0xffffc064。

4、MIPS ABI文档如下描述R_MIPS_GPREL_32:
`
Name Value Field Symbol Calculation
R_MIPS_GPREL_32 12 T-word32 local A + S + GP0 - GP`

A
Represents the addend used to compute the value of the relocatable
field.

S
Represents the value of the symbol whose index resides in the relocation entry, unless the the symbol is STB_LOCAL and is of type
STT_SECTION in which case S represents the original sh_addr minus
the final sh_addr.

GP
Represents the final gp value to be used for the relocatable, executable, or shared object file being produced.

GP0
Represents the gp value used to create the relocatable object.

这里的“A + S + GP0 – GP”便是连接器进行重定位时的求值公式。

5、执行如下命令
`
$ mipsel-linux-objdump -Dr sysconf.o`

Disassembly of section .reginfo:

00000000 <.reginfo>:
0: b200001e 0xb200001e

14: 00004000 sll t0,zero,0x0

MIPS通过reginfo保存一些寄存器信息,其结构体为:

1
2
3
4
5
typedef struct {
  Elf32_Word ri_gprmask;
  Elf32_Word ri_cprmask[4];
  Elf32_SWord ri_gp_value;
} ELF_RegInfo;

其中最后一个域是记录了GP0的值。可以看到,这里GP0的值为0×4000。

6、当链接成可执行程序时,依照同样的方法,我们可以找到

`
00400094 <.reginfo>:
400094: b20000f4 0xb20000f4

4000a8: 10008a70 b 3e2a6c <__start-0x1d6a4> `

所以,这里GP的值为0x10008a70,

`
00425c70 <__sysconf>: 425c70: 3c1c0fbe lui gp,0xfbe `

所以,S的值为0x425c70,

7、根据公式“A + S + GP0 – GP”,可以计算出.rodata中需要重定位的地方的最终内容。比如,对于.rodata中的第一个字,

`
0xffffc064 + 0x425c70 + 0x4000 - 0x10008a70 = 0xf041d264
`

8、附注

如果查看一下binutils/bfd/elf32-mips.c,就可以找到R_MIPS_GPREL32相应的HOWTO数据结构,以及处理函数。