分类: LINUX
2009-06-28 12:56:19
3.5 数据段重定位
在数据段中的重定位是指对指针类型的静态变量、全局变量进行初始化。它与代码段中的重定位比较起来至少有以下明显不同:
一、在用户程序获得控制权(即main函数开始执行)之前就要全部完成;
二、不经过GOT表间接寻址,这是因为此时%ebx中还没有正确的GOT表首地址;(# main()还没有开始执行,估计得执行到第一个被调用函数时,才计算本模块GOT表地址。而且不同的函数可能属于不同的.so文件,不同的.so文件的GOT表位置也不相同,所以%ebx值也是随调用的函数而变化的。参考3.4节(a))
三、直接修改数据段,而代码段重定位时不能修改代码段。(# 数据段可以直接修改,而浮动代码段在动态重定位时不能再做地址修改,应该在连接时候完成)。
a) 如果引用的是静态变量、函数、字符串常量,编译器会在目标文件(*.o)中设上R_386_32重定位标记,并计算被引用变量、函数相对于所在段(section)首地址的偏移量。连接器把它改成R_386_RELATIVE重定位标记,计算它相对于动态连接库(*.so)首地址(通常首地址为零)的偏移量。加载器会把运行模块(*.so)在内存中的首地址与该偏移量相加,得到的结果用来初始化修改指针变量。
代码片断如下:
.section .rodata
.LC0: .string "aaaa1234\n"
.data
p: .long .LC0 # .LC0 是引用的一个串常量 "Ok\n"
.o: R_386_32 w/section
.so: R_386_RELATIVE
b) 如果引用的是全局变量、函数,编译器同样设上R_386_32重定位标记,并且记录引用的符号名字。连接器不必动作。最后加载器查找被引用符号,得到的结果用来初始化指针变量。对于全局函数,查找的结果仍然是函数在PLT表中的代码片断,而不是实际入口。这与前面引用全局函数的讨论相同。
代码片断如下:
.data
p: .long printf #printf 是引用的一个全局函数
.o: R_386_32 w/symbol
.so: R_386_32 w/symbol
(# 全局变量 有可能在其他模块中;静态变量一定在本模块中)
(# 定义是指确定变量的名字类型并分配空间;初始化是指 赋予变量一个初始值。自动变量/局部变量:直接化为.text代码段里的堆栈地址;静态变量和全局变量:已初始化的存放在.data,未初始化的存放在.bss;字符串常量:存放在 .rodata )
(# 指针类型的变量存放的是一个地址,看来重定位都是对不确定的地址 重新定位。比如static char *p3 = ”abcde”,对于.so文件,”abcde”实际存放在.rodata,而指针型静态变量*p3存放在.data区。
*p3变量本身有个地址,在.so文件中,*p3变量的位置实际上是一个偏移量,加载进内存时要重定位出*p3具体的内存地址,计算方法是以GOT表为基准来计算*p3在文件内的偏移,详见 3.4 b)。
3.5节所讲的是对*p3里的内容进行重定位。*p3指针里存放的内容是一个地址,该地址是”abcde”在.rodata里的位置,也是一个偏移量. *.so文件内,”abcde”相对于文件开头的偏移量是固定的。*.so文件被加载进内存后,加载器把*.so在内存的起始地址+”abcde”在文件的偏移量,得到”abcde”在内存中的实际地址,也就完成了地址重定位;
)
(# 实际例子:
[elf@redhat elf]# vi bbb.c
int a111 = 15; # 全局变量
char *g111; # 全局指针变量 未初始化。
char *g222 = "456"; # 全局指针变量 已经初始化 放在.data
static char *p111; # 静态全局指针变量 未初始化 放在.bss
static char *p222 = "aaaaaaaaaa"; # 静态全局指针变量 已经初始化 放在.data
static char *p333; # 静态全局指针变量 未初始化 放在.bss
static char *p444 = "bbbb"; # 静态全局指针变量 已经初始化 放在.data
int foo2()
{
p333 = "cc";
char *p555 = "dd";
static char *p666= "123"; # 静态局部指针变量 已经初始化 放在.data
p111 = (char *)malloc(10);
strcpy(p111, "13579");
}
[elf@redhat elf]# gcc -fPIC -c bbb.c
[elf@redhat elf]# gcc -shared -o bbb.so ./bbb.o
[elf@redhat elf]# objdump -s bbb.o
Contents of section .data:
0000
Contents of section .rodata:
0000 34353600 61616161 61616161 61610062 456.aaaaaaaaaa.b
0010 62626200 31323300 63630064 64003133 bbb.123.cc.dd.13
0020 35373900 579.
Contents of section .data.rel.local:
0000 00000000 04000000
[elf@redhat elf]# readelf -a ./bbb.o
ELF Header:
Magic:
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: REL (Relocatable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x0
Start of program headers: 0 (bytes into file)
Start of section headers: 348 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 40 (bytes)
Number of section headers: 13
Section header string table index: 10
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00000000 000034 000058 00 AX 0 0 4
[ 2] .rel.text REL 00000000 000510 000048 08 11 1 4
[ 3] .data PROGBITS 00000000
[ 4] .bss NOBITS 00000000 000090 000008 00 WA 0 0 4
[ 5] .rodata PROGBITS 00000000 000090 000024
[ 6] .data.rel.local PROGBITS 00000000 0000b4 000010 00 WA 0 0 4
[ 7] .rel.data.rel.loc REL 00000000 000558 000020 08 11 6 4
[ 8] .note.GNU-stack NOTE 00000000
[ 9] .comment PROGBITS 00000000
[10] .shstrtab STRTAB 00000000
[11] .symtab SYMTAB 00000000 000364 000150 10 12 e 4
[12] .strtab STRTAB 00000000 0004b4
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
There are no program headers in this file.
Relocation section '.rel.text' at offset 0x510 contains 9 entries:
Offset Info Type Sym.Value Sym. Name
00000015 00000509 R_386_GOTOFF 00000000 .rodata
0000001b 00000409 R_386_GOTOFF 00000000 .bss
00000021 00000509 R_386_GOTOFF 00000000 .rodata
0000002e 00001204 R_386_PLT32 00000000 malloc
00000037 00000409 R_386_GOTOFF 00000000 .bss
00000040 00000509 R_386_GOTOFF 00000000 .rodata
00000047 00000409 R_386_GOTOFF 00000000 .bss
Relocation section '.rel.data.rel.local' at offset 0x558 contains 4 entries:
Offset Info Type Sym.Value Sym. Name
00000000 00000501 R_386_32 00000000 .rodata
00000004 00000501 R_386_32 00000000 .rodata
00000008 00000501 R_386_32 00000000 .rodata
There are no unwind sections in this file.
Symbol table '.symtab' contains 21 entries:
Num: Value Size Type Bind
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FILE LOCAL DEFAULT ABS bbb.c
2: 00000000 0 SECTION LOCAL DEFAULT 1
3: 00000000 0 SECTION LOCAL DEFAULT 3
4: 00000000 0 SECTION LOCAL DEFAULT 4
5: 00000000 0 SECTION LOCAL DEFAULT 5
6: 00000000 0 SECTION LOCAL DEFAULT 6
7: 00000004 4 OBJECT LOCAL DEFAULT 6 p222
8: 00000008 4 OBJECT LOCAL DEFAULT 6 p444
9:
10: 00000004 4 OBJECT LOCAL DEFAULT 4 p333
11: 00000000 4 OBJECT LOCAL DEFAULT 4 p111
12: 00000000 0 SECTION LOCAL DEFAULT 8
13: 00000000 0 SECTION LOCAL DEFAULT 9
14: 00000000 4 OBJECT GLOBAL DEFAULT
15: 00000000 4 OBJECT GLOBAL DEFAULT
16: 00000000 88 FUNC GLOBAL DEFAULT 1 foo2
17: 00000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
18: 00000000 0 NOTYPE GLOBAL DEFAULT UND malloc
19: 00000000 0 NOTYPE GLOBAL DEFAULT UND strcpy
20: 00000004 4 OBJECT GLOBAL DEFAULT COM g111
No version information found in this file.
[elf@redhat elf]#
1.
objdump –s ./bbb.o 可看到全部字符串常量存放在.rodata。
2.
char *g222 = "456"; # 全局指针变量 已经初始化 放在.data
static char *p222 = "aaaaaaaaaa"; # 静态全局指针变量 已经初始化 放在.data
static char *p444 = "bbbb"; # 静态全局指针变量 已经初始化 放在.data
static char *p666= "123"; # 静态局部指针变量 已经初始化 放在.data
上面代码里,4个全局/静态指针变量是初始化的,将来会被ld放在.so文件的.data里。编译器在.o里把非指针类型的全局变量int a111=15 直接放进 .data, 其他已初始化的全局/静态指针类型变量放进半成品:.data.rel.local(将来供连接器ld进一步加工后放进.data)。
.rel.data.rel.local里有4个重定位项目:记录4个变量:*g222/*p222/*p444/*p666 在.rodata里对应的字符串相对于.rodata节头部的偏移量。
3.
.rel.data.rel.local的重定位是先于.rel.text重定位的。.rel.data.rel.local表示让ld对.data.rel.local里的内容进行重定位。注意地址排序方式:0xd8840408 其实是:0x080484d8
[elf@redhat elf]# objdump –s ./bbb.so
Contents of section .rodata:
07cf 34353600 61616161 61616161 61610062 456.aaaaaaaaaa.b
07df 62626200 31323300 63630064 64003133 bbb.123.cc.dd.13
07ef 35373900 579.
Contents of section .eh_frame:
Contents of section .data:
1808 d3070000 de070000 e3070000 ............
Contents of section .dynamic:
连接器ld 在.so里把4个变量:*g222/*p222/*p444/*p666放进.data,取值=4个变量在.rodata里每个字符串相对于bbb.so文件头部的偏移量。加载器把bbb.so加载进内存,再次计算4个变量对应的字符串在内存中的地址。
)
3.6 总结
下表给出了前面讨论得到的全部结果:(看起来,函数符号的引用和调用的处理是有些区别Di)
.o .so
------------------------------------------------------------
|装载GOT表首地址 R_386_GOTPC NULL
代码段|-----------------------------------------------------
重定位|引用变量函数地址 静态 R_386_GOTOFF NULL
| 全局 R_386_GOT32 R_386_GLOB_DAT
|-----------------------------------------------------
|直接调用函数 静态 R_386_PLT32 NULL
| 全局 R_386_PLT32 R_386_JMP_SLOT
------|-----------------------------------------------------
数据段|引用变量函数地址 静态 R_386_32 w/sec R_386_RELATIVE
重定位| 全局 R_386_32 w/sym R_386_32 w/sym
------------------------------------------------------------
4 结束语
Windows使用PE文件格式,Linux使用ELF文件格式,这是两种动态连接库不同的根源。本文从ELF规范出发,深入讨论了Linux动态连接库的具体实现,目的在于进一步推广Linux的研究与应用。
5 附录:Linux汇编程序语法
x86体系结构上的Linux汇编器兼容于AT&T System V/386汇编器的语法,与常见的Intel语法颇有不同,如下表:
AT&T Intel
常数 前缀$:pushl $4 push 4
寄存器 前缀%:%ebx ebx
跳转指令(绝对地址) 前缀*:jmp *fun
跳转指令(相对偏移) 无标记:jmp fun
目的、源操作数的顺序 源在前:movl $4,%eax 目的在前:mov eax,4
操作数尺寸 后缀b、w、l:movl 修饰符byte ptr等等
变址寻址 [base+disp] disp(base)
参考文献
[1] Executable and Linking Format Spec v1.2, TIS Committee, 1995
[2] GNU Project (gcc, libc, binutils), Free Software Foundation, Inc., 1999
[3] Solaris 2.5 Linker and Libraries Guide, Sun Microsystems Inc., 1999
ftp://192.18.99.138/802-1955/802-1955.pdf
[4] SVR4 ABI x86 Supplement, The Santa Cruz Operation, Inc., 1999
http://www.sco.com/developer/devspecs/abx86-4.pdf
[5] ELF: From The Programmer's Perspective, H J Lu, 1995
[6] Using ld: The GNU linker, S Chamberlain, Cygnus Support, 1994
补充:
1. objdump 用法
Objdump –dx 反汇编文件里.text section
Objdump –D 反汇编文件里所有section
Objdump –s 察看所有节的内容
Readelf –a 察看文件头里的连接信息
2. 查看elf文件里某个偏移处的内容:
$ readelf -a test2
$ hexdump -s 0x
3. 察看内存里某个偏移处的内容
$ gdb -q test2
(gdb) disass 0x8049518
(gdb) x/20x 0x8049518 # 按16进制查看
(gdb) x/20i 0x8049518 # 反汇编查看
4.创建静态库:
$ more haha.c
# include
void haha(char* argv)
{
printf(“haha:--- %s\n”,argv);
}
$ more huhu.c
# include
void huhu(char* argv)
{
printf(“huhu:--- %s\n”,argv);
}
$ gcc –c haha.c huhu.c
$ ar -crv libmy.a haha.o huhu.o
应用静态库:
$ more mylib.h
void haha(char *);
void huhu(char *);
$ more m.c
# include “mylib.h”
int main()
{
haha(“Hello World”);
exit(0);
}
$ gcc –c m.c
$ gcc –o test m.o haha.o
$ rm –rf test
$ gcc –o test m.c libmy.a
$ gcc –static –o test m.c mylib.a # 生成不需要共享函数库的程序
很多方面,linux共享库都很象Windows的动态链接库,为了更好理解我们可以把.so库想象成.DLL文件,而.sa库想象成.LIB文件。
5.创建动态库:
实际例子1:
$ more foo1.c
int foo1()
{
printf("[+] foo1 address:%p\n", foo1);
}
$ more test2.c
# include
int main(int argc, char *argv[])
{
foo1();
return 0;
}
$ gcc -fPIC -c foo1.c
$ gcc -shared -o foo1.so foo1.o
$ gcc -c test2.c
$ gcc -o test2 test2.o foo1.so
$ export LD_LIBRARY_PATH=.
$ ldd ./test2
$ more libprint.c
/* file libprint.c */
#include "stdio.h"
void printstring(char* str)
{
printf("String:---------------> %s\n", str);
}
$ gcc -c libprint.c
$ readelf -a libprint.o > 11
$ gcc -fPIC -c libprint.c
$ readelf -a libprint.o > 22
$ diff 11 22
…
…
…
< Symbol table '.symtab' contains 11 entries:
---
> Symbol table '.symtab' contains 12 entries:
62,
< 9: 00000000 27 FUNC GLOBAL DEFAULT 1 printstring
< 10: 00000000 0 NOTYPE GLOBAL DEFAULT UND printf
---
> 9: 00000000 45 FUNC GLOBAL DEFAULT 1 printstring
> 10: 00000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
> 11: 00000000 0 NOTYPE GLOBAL DEFAULT UND printf
结论:普通目标文件.o 和 位置无关目标文件.o 的elf头相比少了个_GLOBAL_OFFSET_TABLE_符号。