概述
实验程序:
$ cat SimpleSection.c
int printf(const char* format,...);
int globa_init_var = 84;
int global_uninit_val;
void func1(int i){
printf("%dn",i);
}
int main(){
static int static_var = 85;
static int static_var2;
int a = 1;
int b;
func1(static_var+static_var2+a+b);
return 0;
}
-----------------------------------------------------------------------------------------------------------
elf头数据结构
/usr/include/elf.h
85 typedef struct
86 {
87 unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
88 Elf64_Half e_type; /* Object file type */ //可重定位 可执行 共享
89 Elf64_Half e_machine; /* Architecture */
90 Elf64_Word e_version; /* Object file version */
91 Elf64_Addr e_entry; /* Entry point virtual address */
92 Elf64_Off e_phoff; /* Program header table file offset */
93 Elf64_Off e_shoff; /* Section header table file offset */
94 Elf64_Word e_flags; /* Processor-specific flags */
95 Elf64_Half e_ehsize; /* ELF header size in bytes */
96 Elf64_Half e_phentsize; /* Program header table entry size */
97 Elf64_Half e_phnum; /* Program header table entry count */
98 Elf64_Half e_shentsize; /* Section header table entry size */
99 Elf64_Half e_shnum; /* Section header table entry count */
100 Elf64_Half e_shstrndx; /* Section header string table index */
101 } Elf64_Ehdr;
$ gcc -c -o SimpleSection.o SimpleSection.c
$ readelf -h SimpleSection.o
ELF 头:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: REL (可重定位文件)
Machine: Advanced Micro Devices X86-64
Version: 0x1
入口点地址: 0x0
程序头起点: 0 (bytes into file)
Start of section headers: 400 (bytes into file)
标志: 0x0
本头的大小: 64 (字节)
程序头大小: 0 (字节)
Number of program headers: 0
节头大小: 64 (字节)
节头数量: 13
字符串表索引节头: 10
--------------------------------------------------------------------------------------
上面内容比较清晰,不过多介绍。注意其中的入口点地址目前为0,因为目前是可重定位文件,等最终链接完成就会得到最终地址,另外看一下最后一项:字符串表索引节头 指的是字符串表在section table中的索引
section table如下:
$ readelf -S SimpleSection.o
共有 13 个节头,从偏移量 0x190 开始:
节头:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
0000000000000056 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 000006b8
0000000000000078 0000000000000018 11 1 8
[ 3] .data PROGBITS 0000000000000000 00000098
0000000000000008 0000000000000000 WA 0 0 4
[ 4] .bss NOBITS 0000000000000000 000000a0
0000000000000004 0000000000000000 WA 0 0 4
[ 5] .rodata PROGBITS 0000000000000000 000000a0
0000000000000004 0000000000000000 A 0 0 1
[ 6] .comment PROGBITS 0000000000000000 000000a4
0000000000000025 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 000000c9
0000000000000000 0000000000000000 0 0 1
[ 8] .eh_frame PROGBITS 0000000000000000 000000d0
0000000000000058 0000000000000000 A 0 0 8
[ 9] .rela.eh_frame RELA 0000000000000000 00000730
0000000000000030 0000000000000018 11 8 8
[10] .shstrtab STRTAB 0000000000000000 00000128 //保存段表中用到的字符串,最常见的就是段名
0000000000000061 0000000000000000 0 0 1
[11] .symtab SYMTAB 0000000000000000 000004d0
0000000000000180 0000000000000018 12 11 8
[12] .strtab STRTAB 0000000000000000 00000650 //保存普通字符串,如符号的名字
0000000000000065 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
上面其实就是一个Section table的数组,继续看一下Section header对应的数据结构:
270 /* Section header. */
286 typedef struct
287 {
288 Elf64_Word sh_name; /* Section name (string tbl index) */ //索引的是.shstrtab
289 Elf64_Word sh_type; /* Section type */
290 Elf64_Xword sh_flags; /* Section flags */
291 Elf64_Addr sh_addr; /* Section virtual addr at execution */
292 Elf64_Off sh_offset; /* Section file offset */
293 Elf64_Xword sh_size; /* Section size in bytes */
294 Elf64_Word sh_link; /* Link to another section */ //该段使用的字符串在段表中的下标
295 Elf64_Word sh_info; /* Additional section information */ //重定位表所作用的段在段表中的下标
296 Elf64_Xword sh_addralign; /* Section alignment */
297 Elf64_Xword sh_entsize; /* Entry size if section holds table */
298 } Elf64_Shdr;
以.rela.text为例,它的sh_link为11,指向的是.symtab,说明它的字符串在.symtab中,它的sh_info为1,指向的是.text,说明它是对.text段进行重定位。
我们来查看一下.symtab的内容:
$ readelf -s SimpleSection.o
Symbol table '.symtab' contains 16 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS SimpleSection.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 5
6: 0000000000000004 4 OBJECT LOCAL DEFAULT 3 static_var.1730
7: 0000000000000000 4 OBJECT LOCAL DEFAULT 4 static_var2.1731
8: 0000000000000000 0 SECTION LOCAL DEFAULT 7
9: 0000000000000000 0 SECTION LOCAL DEFAULT 8
10: 0000000000000000 0 SECTION LOCAL DEFAULT 6
11: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 globa_init_var
12: 0000000000000004 4 OBJECT GLOBAL DEFAULT COM global_uninit_val
13: 0000000000000000 33 FUNC GLOBAL DEFAULT 1 func1
14: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
15: 0000000000000021 53 FUNC GLOBAL DEFAULT 1 main
还有另一种方式查看.symtab
$ objdump -t SimpleSection.o
SimpleSection.o: 文件格式 elf64-x86-64
SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 SimpleSection.c
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .data 0000000000000000 .data
0000000000000000 l d .bss 0000000000000000 .bss
0000000000000000 l d .rodata 0000000000000000 .rodata
0000000000000004 l O .data 0000000000000004 static_var.1730
0000000000000000 l O .bss 0000000000000004 static_var2.1731
0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l d .eh_frame 0000000000000000 .eh_frame
0000000000000000 l d .comment 0000000000000000 .comment
0000000000000000 g O .data 0000000000000004 globa_init_var
0000000000000004 O *COM* 0000000000000004 global_uninit_val
0000000000000000 g F .text 0000000000000021 func1
0000000000000000 *UND* 0000000000000000 printf
0000000000000021 g F .text 0000000000000035 main
可见需要重定位的printf和func1的确是在该段之中。
这里顺便看一下symbol table的entry的数据结构:
391 typedef struct
392 {
393 Elf64_Word st_name; /* Symbol name (string tbl index) *///索引.strtab
394 unsigned char st_info; /* Symbol type and binding */
395 unsigned char st_other; /* Symbol visibility */
396 Elf64_Section st_shndx; /* Section index */ //索引 .shstrtab
397 Elf64_Addr st_value; /* Symbol value */ //有不同的含义
398 Elf64_Xword st_size; /* Symbol size */
399 } Elf64_Sym;
上面通过readelf -s SimpleSection.o查看时,name一列只显示了从.strtab中索引到的字符串,对于.shstrtab中的没有显示出来。
回头看一下,给定一个索引,查找Section header表就得到了相应的Section header,它描述了section的信息。而Section header table的起始位置又保存在elf文件首部(Elf64_Ehdr)中。因此,可以很方便的通过索引查找相应的Section信息。
接下来,我们就看一下重定位相关内容:
通过上面已经知道,根据.rela.text对应的section我们可以得到它使用的字符串所在的段,那么在.rela.text中这些字符串是以什么方式被使用的呢?重定位表中不同的项是如何区分的呢?重定位的过程又是怎样的呢?如何与.text联系的呢?
首先来看一下它的数据结构:
522 typedef struct
523 {
524 Elf64_Addr r_offset; /* Address */
525 Elf64_Xword r_info; /* Relocation type and symbol index */ //低8位表示重定位类型,高24为作为.symtab的索引
526 Elf64_Sxword r_addend; /* Addend */
527 } Elf64_Rela;
通过该数据结构结合注释,上面的疑问就可以解决了,elf文件中的.rela.text部分以Elf64_Rela数组的形式保存需要重定位的符号,其中 r_offset指明了它在相应段中的偏移,r_info则作为重定位类型和symbol索引。通过r_offset把.text和.rela.text关联起来,通过r_info把.rela.text与.symtab联系起来,这样我们就知道了一个需要重定位的符号的名字,在.text中的偏移以及重定位的类型。链接过程中根据这些信息就可以很容易进行重定位工作了。
关于重定位,我们再来看下面两个例子:
/********* a.c ****************/
extern int shared;
int main(){
int a = 100;
swap(&a,&shared);
}
/********* b.c ***************/
int shared = 1;
void swap(int *a,int *b){
*a ^= *b ^= *a ^= *b;
}
$ objdump -d a.o
a.o: 文件格式 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: c7 45 fc 64 00 00 00 movl $0x64,-0x4(%rbp)
f: 48 8d 45 fc lea -0x4(%rbp),%rax //取a的地址,保存到rax中
13: be 00 00 00 00 mov $0x0,%esi //第二个参数,也就是&shared,可见这里为0
18: 48 89 c7 mov %rax,%rdi //第一个参数,也就是&a
1b: b8 00 00 00 00 mov $0x0,%eax //返回值
20: e8 00 00 00 00 callq 25 <main+0x25>
25: c9 leaveq
26: c3 retq
关于寄存器的使用可以参考X86-64寄存器和栈帧,
可以看到,上面shared的地址暂时为0,swap的地址也没有确定,其中25是callq指令的下一条指令的偏移。现在我们来看一下链接之后的情况:
$ ld a.o b.o -e main -o ab
$ objdump -d ab
ab: 文件格式 elf64-x86-64
Disassembly of section .text:
00000000004000e8 <main>:
4000e8: 55 push %rbp
4000e9: 48 89 e5 mov %rsp,%rbp
4000ec: 48 83 ec 10 sub $0x10,%rsp
4000f0: c7 45 fc 64 00 00 00 movl $0x64,-0x4(%rbp)
4000f7: 48 8d 45 fc lea -0x4(%rbp),%rax
4000fb: be b8 01 60 00 mov $0x6001b8,%esi
400100: 48 89 c7 mov %rax,%rdi
400103: b8 00 00 00 00 mov $0x0,%eax
400108: e8 02 00 00 00 callq 40010f <swap>
40010d: c9 leaveq
40010e: c3 retq
000000000040010f <swap>:
40010f: 55 push %rbp
400110: 48 89 e5 mov %rsp,%rbp
400113: 48 89 7d f8 mov %rdi,-0x8(%rbp)
400117: 48 89 75 f0 mov %rsi,-0x10(%rbp)
40011b: 48 8b 45 f8 mov -0x8(%rbp),%rax
40011f: 8b 10 mov (%rax),%edx
400121: 48 8b 45 f0 mov -0x10(%rbp),%rax
400125: 8b 08 mov (%rax),%ecx
400127: 48 8b 45 f8 mov -0x8(%rbp),%rax
40012b: 8b 30 mov (%rax),%esi
40012d: 48 8b 45 f0 mov -0x10(%rbp),%rax
400131: 8b 00 mov (%rax),%eax
400133: 31 c6 xor %eax,%esi
400135: 48 8b 45 f8 mov -0x8(%rbp),%rax
400139: 89 30 mov %esi,(%rax)
40013b: 48 8b 45 f8 mov -0x8(%rbp),%rax
40013f: 8b 00 mov (%rax),%eax
400141: 31 c1 xor %eax,%ecx
400143: 48 8b 45 f0 mov -0x10(%rbp),%rax
400147: 89 08 mov %ecx,(%rax)
400149: 48 8b 45 f0 mov -0x10(%rbp),%rax
40014d: 8b 00 mov (%rax),%eax
40014f: 31 c2 xor %eax,%edx
400151: 48 8b 45 f8 mov -0x8(%rbp),%rax
400155: 89 10 mov %edx,(%rax)
400157: 5d pop %rbp
400158: c3 retq
可以看到shared的地址已经替换为0x6001b8,通过$ objdump -x ab来查看一下:
....省略其他......
节:
Idx Name Size VMA LMA File off Algn
0 .text 00000071 00000000004000e8 00000000004000e8 000000e8 2**0
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .eh_frame 00000058 0000000000400160 0000000000400160 00000160 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .data 00000004 00000000006001b8 00000000006001b8 000001b8 2**2
CONTENTS, ALLOC, LOAD, DATA
....省略其他......
可以看到0x6001b8正是.data段的虚拟地址,而.data中只有一个元素就是shared,因此0x6001b8就是shared的虚拟地址。
同样,swap的地址也确定了,因此重定位为40010f(=40010d+2)。
链接器是根据重定位表中的信息进行重定位操作的,来看一下
$ objdump -r a.o
a.o: 文件格式 elf64-x86-64
RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
0000000000000014 R_X86_64_32 shared
0000000000000021 R_X86_64_PC32 swap-0x0000000000000004
其中OFFSET表示其在.text中的偏移,R_X86_64_32表示是绝对定位,也就是shared的地址重定位时应该修改为绝对地址,这根我们上面分析的情况是一致的;
swap的type为R_X86_64_PC32,表示采用相对定位,上面的分析中也看到的确是采用的相对地位。(当a.o和b.o合并后,其中的符号的地址可能会改变,这时需要对相应的地址增加一个偏移)
为什么这里变量shared采用绝对定位,而函数swap采用相对定位呢?
--------------------
关于静态重定位就简单说这些,接下来看其他几个问题:
1)COMMON块
直接导致需要COMMON块的原因是编译器和连接器都允许不同类型的弱符号存在;根本原因是链接器不支持符号类型,即链接器无法判断各个符号的类型是否一致。
2)C++相关问题
3)重复代码消除和全局构造与析构
4)C++与ABI
以上几点具体细节可参考最后的书籍
========================
示例程序:
/********hello.c********/
int main(){
printf("hello world!n");
}
通过下面指令查看链接的详细过程
$ gcc -static --verbose -fno-builtin hello.c
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/4.8/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 4.8.2-19ubuntu1' --with-bugurl=file:///usr/share/doc/gcc-4.8/README.Bugs --enable-languages=c,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.8 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.8 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --disable-libmudflap --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-4.8-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.8.2 (Ubuntu 4.8.2-19ubuntu1)
COLLECT_GCC_OPTIONS='-static' '-v' '-fno-builtin' '-mtune=generic' '-march=x86-64'
/usr/lib/gcc/x86_64-linux-gnu/4.8/cc1 -quiet -v -imultiarch x86_64-linux-gnu hello.c -quiet -dumpbase hello.c -mtune=generic -march=x86-64 -auxbase hello -version -fno-builtin -fstack-protector -Wformat -Wformat-security -o /tmp/ccgYAffR.s //1.编译
GNU C (Ubuntu 4.8.2-19ubuntu1) version 4.8.2 (x86_64-linux-gnu)
compiled by GNU C version 4.8.2, GMP version 5.1.3, MPFR version 3.1.2-p3, MPC version 1.0.1
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../x86_64-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
/usr/lib/gcc/x86_64-linux-gnu/4.8/include
/usr/local/include
/usr/lib/gcc/x86_64-linux-gnu/4.8/include-fixed
/usr/include/x86_64-linux-gnu
/usr/include
End of search list.
GNU C (Ubuntu 4.8.2-19ubuntu1) version 4.8.2 (x86_64-linux-gnu)
compiled by GNU C version 4.8.2, GMP version 5.1.3, MPFR version 3.1.2-p3, MPC version 1.0.1
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: dc75e0628c9356affcec059d0c81cc01
COLLECT_GCC_OPTIONS='-static' '-v' '-fno-builtin' '-mtune=generic' '-march=x86-64'
as -v --64 -o /tmp/cc0r4M2S.o /tmp/ccgYAffR.s //2.汇编
GNU汇编版本 2.24 (x86_64-linux-gnu) 使用BFD版本 (GNU Binutils for Ubuntu) 2.24
COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/4.8/:/usr/lib/gcc/x86_64-linux-gnu/4.8/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.8/:/usr/lib/gcc/x86_64-linux-gnu/
LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/4.8/:/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-static' '-v' '-fno-builtin' '-mtune=generic' '-march=x86-64'
/usr/lib/gcc/x86_64-linux-gnu/4.8/collect2 --sysroot=/ --build-id -m elf_x86_64 --hash-style=gnu --as-needed -static -z relro /usr/lib/gcc/x86_64-linux-gnu/4.8/../../../x86_64-linux-gnu/crt1.o /usr/lib/gcc/x86_64-linux-gnu/4.8/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/4.8/crtbeginT.o -L/usr/lib/gcc/x86_64-linux-gnu/4.8 -L/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/4.8/../../.. /tmp/cc0r4M2S.o --start-group -lgcc -lgcc_eh -lc --end-group /usr/lib/gcc/x86_64-linux-gnu/4.8/crtend.o /usr/lib/gcc/x86_64-linux-gnu/4.8/../../../x86_64-linux-gnu/crtn.o //3.链接
关于链接过程控制请参考 Chap-4 Section 4.6 链接控制过程
=============
装载与动态链接
1.装载
从操作系统角度看可执行文件的装载
在有虚拟内存的情况下,创建一个进程然后加载只需要做三件事情:
a) 创建一个独立的虚拟地址空间
b) 读取可执行文件,并建立虚拟空间与可执行文件的映射关系
c) 将cpu的指令寄存器设置为可执行文件的入口地址,启动执行
其中关于a,可以参考《fork源码分析》;关于b可以参考《内存管理--page.s memory.c源码分析》;关于c可以参考《execve源码分析》。
简单提一下,a中通过fork复制父进程的页表,当发生缺页中断时,current结构体中的execute(inode节点)即为elf文件对应的inode节点,读入elf文件的header,其中记录了入口地址,设置为ip,启动执行。
关于elf文件到虚拟空间的映射需要注意,如果elf文件中每个section都单独映射到虚拟地址空间中,由于该映射过程是按页来的,可能会浪费大量空间,因此提出了程序段(segment)的概念,链接器在链接文件时会把相同属性的section尽量相邻放置,这样,在映射时就可以按照segment为单位来进行,节省了空间。比如前面出现过的ab的程序头如下:
$ readelf -l ab
Elf 文件类型为 EXEC (可执行文件)
入口点 0x4000e8
共有 3 个程序头,开始于偏移量 64
程序头:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x00000000000001b8 0x00000000000001b8 R E 200000
LOAD 0x00000000000001b8 0x00000000006001b8 0x00000000006001b8
0x0000000000000004 0x0000000000000004 RW 200000
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 10
其对应的数据结构如下:
539 /* Program segment header. */
553 typedef struct
554 {
555 Elf64_Word p_type; /* Segment type */
556 Elf64_Word p_flags; /* Segment flags */
557 Elf64_Off p_offset; /* Segment file offset */
558 Elf64_Addr p_vaddr; /* Segment virtual address */
559 Elf64_Addr p_paddr; /* Segment physical address */
560 Elf64_Xword p_filesz; /* Segment size in file */
561 Elf64_Xword p_memsz; /* Segment size in memory */
562 Elf64_Xword p_align; /* Segment alignment */
563 } Elf64_Phdr;
2.动态链接
/*********program1.c*********/
#include "Lib.h"
int main(){
foobar(1);
return 0;
}
/*********program2.c*********/
#include "Lib.h"
int main(){
foobar(2);
return 0;
}
/*********Lib.h*********/
#ifndef LIB_H
#define LIB_H
void foobar(int i);
#endif
/*********Lib.c*********/
#include<stdio.h>
void foobar(int i){
printf("printing from Lib.so %dn",i);
}
gcc -fPIC -shared -o Lib.so Lib.c
gcc -o program1 program1.c ./Lib.so
gcc -o program2 program2.c ./Lib.so
$ readelf -d program1
Dynamic section at offset 0xe18 contains 25 entries:
标记 类型 名称/值
0x0000000000000001 (NEEDED) 共享库:[./Lib.so] //NEEDED指明了该可执行文件(或共享对象)所依赖的共享对象
0x0000000000000001 (NEEDED) 共享库:[libc.so.6] //NEEDED指明了该可执行文件(或共享对象)所依赖的共享对象
0x000000000000000c (INIT) 0x400540
0x000000000000000d (FINI) 0x400724
.........
0x0000000000000003 (PLTGOT) 0x601000
.........
0x0000000000000017 (JMPREL) 0x4004f8
0x0000000000000007 (RELA) 0x4004e0 //动态链接重定位表地址
0x0000000000000008 (RELASZ) 24 (bytes)
0x0000000000000009 (RELAENT) 24 (bytes)
0x000000006ffffffe (VERNEED) 0x4004c0
0x000000006fffffff (VERNEEDNUM) 1
0x000000006ffffff0 (VERSYM) 0x4004a6
0x0000000000000000 (NULL) 0x0
其对应的数据结构如下:
650 /* Dynamic section entry. */
662 typedef struct
663 {
664 Elf64_Sxword d_tag; /* Dynamic entry type */
665 union
666 {
667 Elf64_Xword d_val; /* Integer value */
668 Elf64_Addr d_ptr; /* Address value */
669 } d_un;
670 } Elf64_Dyn;
关于其作用,比如上面的0x0000000000000007 (RELA) 0x4004e0,就是指出了动态重定位表的地址,这一点可以验证:
$ objdump -h program1
......
Idx Name Size VMA LMA File off Algn
.........
8 .rela.dyn 00000018 00000000004004e0 00000000004004e0 000004e0 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
........
接下来,重点看一下动态重定位表是如何作用于重定位过程的
首先来看一下动态链接的重定位表:.rela.dyn(用于变量)和.rela.plt(用于函数)
$ readelf -r program1
重定位节 '.rela.dyn' 位于偏移量 0x4e0 含有 1 个条目:
Offset Info Type Sym. Value Sym. Name + Addend
000000600ff8 000400000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
重定位节 '.rela.plt' 位于偏移量 0x4f8 含有 3 个条目:
Offset Info Type Sym. Value Sym. Name + Addend
000000601018 000200000007 R_X86_64_JUMP_SLO 0000000000000000 foobar + 0
000000601020 000300000007 R_X86_64_JUMP_SLO 0000000000000000 __libc_start_main + 0
000000601028 000400000007 R_X86_64_JUMP_SLO 0000000000000000 __gmon_start__ + 0
以foobar为例来看一下重定位过程:我们知道foobar的地址放在.golt.plt中,通过objdump查看.got.plt地址如下:
22 .got.plt 00000030 0000000000601000 0000000000601000 00001000 2**3
其中foobar的偏移000000601018 与0000000000601000的关系如下图所示:
那么接下来怎么重定位呢?只需要根据在Lib.so中找到的foobar的地址填入上面的000000601018中即可。
上面foobar的重定位类型为R_X86_64_JUMP_SLO,除此之外还有R_X86_64_GLOB_DAT以及R_X86_64_RELATIVE,最后一种主要用于共享对象包含绝对地址
引用的情况。
简单总结一下上面涉及到的几个对象之间的关系:elf文件首部指明了section首部的地址(Start of section headers),section 就包括了.dynamic, 根据之前的描述可知,它指明了本可执行文件(或共享对象)所依赖的共享共享对象,还指明了比如动态重定位表的地址等,动态重定位表表项指明了待重定位符号在.text中的地址,重定位类型,符号名称等,据此就可以完成重定位过程,新的地址会被填入到.golt.plt(或.got中)
========
显示运行时的链接
dlopen():打开一个动态库,并将其加载到进程的地址空间,完成初始化过程
dlsym():通过它找到所需要的符号
dlerror():判断上一次调用是否成功
dlclose():将一个加载模块卸载
更多内容或细节,请参考下面的书籍
参考
程序员的自我修养-链接、装载与库
最后
以上就是紧张白开水为你收集整理的链接 加载的全部内容,希望文章能够帮你解决链接 加载所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复