我是靠谱客的博主 紧张白开水,最近开发中收集的这篇文章主要介绍链接 加载,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

实验程序:

$ 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的偏移0000006010180000000000601000的关系如下图所示:

那么接下来怎么重定位呢?只需要根据在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():将一个加载模块卸载

更多内容或细节,请参考下面的书籍


参考

程序员的自我修养-链接、装载与库











最后

以上就是紧张白开水为你收集整理的链接 加载的全部内容,希望文章能够帮你解决链接 加载所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(42)

评论列表共有 0 条评论

立即
投稿
返回
顶部