C/C++符号重定位实例教程及图示

2023-03-1514:42:50编程语言入门到精通Comments1,321 views字数 14888阅读模式

1.编译、链接相关的文件

测试代码如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/31316.html

/*main.c*/
int add(int a_, int b_);
extern int global_extern_int;
int global_int = 3;

int main()
{
    static int a = 19;
    global_int = 5;
    int rtv = 0;
    rtv = add(global_int, global_extern_int);
    return rtv;
}
/*add.c*/
int global_extern_int = 2;

int add(int a_, int b_)
{
    return a_+ b_;
}

编译、链接(32位),文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/31316.html

gcc -g -fno-pie  -no-pie -m32 -c add.c
gcc -g -fno-pie  -no-pie -m32 -c main.c
gcc -g -fno-pie -no-pie -m32 -o main main.o add.o

生成的应用程序main的大小为16K。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/31316.html

lyf@lyf:~/test$ ls -hl
total 32K
-rw-r--r-- 1 lyf lyf   74 Dec  2 10:22 add.c
-rw-r--r-- 1 lyf lyf 2.0K Dec  2 10:42 add.o
-rwxr-xr-x 1 lyf lyf  16K Dec  2 13:54 main
-rw-r--r-- 1 lyf lyf  212 Dec  2 11:33 main.c
-rw-r--r-- 1 lyf lyf 2.3K Dec  2 11:34 main.o

大部分的现代操作系统都使用ASCII标准来表示文本字符,使用单字节的整数值来表示每个字符,如下所示,程序的源文件为ASCII文件,这种只由ASCII字符构成的文件称为文本文件,除了文本文件,其他的都是二进制文件文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/31316.html

lyf@lyf:~/test$ file main.c
main.c: C source, ASCII text
lyf@lyf:~/test$ hexdump -C ./main.c
00000000  69 6e 74 20 61 64 64 28  69 6e 74 20 61 5f 2c 20  |int add(int a_, |
00000010  69 6e 74 20 62 5f 29 3b  0a 65 78 74 65 72 6e 20  |int b_);.extern |
00000020  69 6e 74 20 67 6c 6f 62  61 6c 5f 65 78 74 65 72  |int global_exter|
00000030  6e 5f 69 6e 74 3b 0a 69  6e 74 20 67 6c 6f 62 61  |n_int;.int globa|
00000040  6c 5f 69 6e 74 20 3d 20  33 3b 0a 0a 69 6e 74 20  |l_int = 3;..int |
00000050  6d 61 69 6e 28 29 0a 7b  0a 20 20 20 20 73 74 61  |main().{.    sta|
00000060  74 69 63 20 69 6e 74 20  61 20 3d 20 31 39 3b 0a  |tic int a = 19;.|
00000070  20 20 20 20 67 6c 6f 62  61 6c 5f 69 6e 74 20 3d  |    global_int =|
00000080  20 35 3b 0a 20 20 20 20  69 6e 74 20 72 74 76 20  | 5;.    int rtv |
00000090  3d 20 30 3b 0a 20 20 20  20 72 74 76 20 3d 20 61  |= 0;.    rtv = a|
000000a0  64 64 28 67 6c 6f 62 61  6c 5f 69 6e 74 2c 20 67  |dd(global_int, g|
000000b0  6c 6f 62 61 6c 5f 65 78  74 65 72 6e 5f 69 6e 74  |lobal_extern_int|
000000c0  29 3b 0a 20 20 20 20 72  65 74 75 72 6e 20 72 74  |);.    return rt|
000000d0  76 3b 0a 7d                                       |v;.}|
000000d4
lyf@lyf:~/test$ file main.o
main.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), with debug_info, not stripped
lyf@lyf:~/test$ file add.o
add.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), with debug_info, not stripped
lyf@lyf:~/test$ file main
main: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=5b8fb49e162dbf05605114331a1fdfb524aefbbf, for GNU/Linux 3.2.0, with debug_info, not stripped
  • .o的文件格式为ELF relocatable,可重定位的;
  • 应用程序main的文件格式为ELF executable,可执行的;
  • 编译生成的.o文件以及链接后最终生成的main应用程序,从本质上来讲依然是文件(Linux上遵循ELF这种文件格式);
  • 应用程序main默认是动态链接的,大小为16K;

默认是动态链接的,那么链接了什么动态库?发现是libc文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/31316.html

lyf@lyf:~/test$ readelf -d ./main

Dynamic section at offset 0x2f14 contains 24 entries:
  Tag        Type                         Name/Value
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]
 ......

执行应用程序,文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/31316.html

lyf@lyf:~/test$ ./main
lyf@lyf:~/test$ echo $?
7
  • 在shell中执行./main,shell也是一个应用程序,当执行./main时,会在当前目录下寻找名为main的可执行程序,然后解析命令行参数,本例中没有,除此之外,还会将shell中的一些环境变量引入到main程序的执行过程中;接着读取main文件(二进制文件),将ELF可执行文件加载到内存中(虚拟内存,一个程序运行时对应一个进程,现代操作系统进程所操作的内存地址为虚拟地址),如果是链接了动态库,还会将动态库也加载到内存中(对于PIC,Position Independent Code,只加载一份,后续介绍);
  • 执行完成后,返回值为7,符合程序的预期;

改main为静态链接,文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/31316.html

lyf@lyf:~/test$ gcc -g -fno-pie -no-pie -m32 -static -o main main.o add.o

静态链接后,main的大小为731K,大了很多。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/31316.html

lyf@lyf:~/test$ ls -hl
total 748K
-rw-r--r-- 1 lyf lyf   74 Dec  2 10:22 add.c
-rw-r--r-- 1 lyf lyf 2.0K Dec  2 10:42 add.o
-rwxr-xr-x 1 lyf lyf 731K Dec  2 13:51 main
-rw-r--r-- 1 lyf lyf  212 Dec  2 11:33 main.c
-rw-r--r-- 1 lyf lyf 2.3K Dec  2 11:34 main.o

2.内存中的程序

如下图所示,文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/31316.html

  • 通过编译,生成的文件为ELF Relocatable object(.o)文件;
  • 通过重定位、链接等,生成ELF Executable文件;
  • 链接器对编译生成.o文件采用相似段合并的方法最终生成executable(其中很重要的一步就是后面要讲的符号重定位)。
C/C++符号重定位实例教程及图示

如前面所介绍的,编译链接后的硬盘中的main应用程序是ELF格式的文件,通过shell执行main后,会加载到内存中。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/31316.html

  • 进程的内存空间中的一部分空间是使用ELF中的部分段来填充的,例如对于main的ELF中的.init,.text,.rodata等段会放在只读区,而.data,.bss等段会放在可读可写的区域。代码段及只读数据等肯定要设计成是只读的,否则程序指令能被随意修改会产生安全问题。数据区显而易见是需要可读写的。
  • 对于.bss段,看过别人有个精妙的解释,可以把它叫做Better Save Space段,主要是为了减少elf文件不必要的文件大小的增加,例如代码中定义了一个全局4096字节长度的char数组,均初始化为0,这个变量就没必要放在数据段,然后占用4096字节都填充为0。我们只需要知道这个数据结构的组成这些信息即可,当后续加载到内存中时,可以再为其分配空间。
C/C++符号重定位实例教程及图示

3.什么是符号(Symbol)

符号对应的就是地址。变量的符号是变量的地址,函数的符号是函数的地址。链接过程的本质就是把多个不同的目标(.o)文件之间相互“粘”在一起,是解决目标文件之间对函数和变量地址的引用。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/31316.html

关于ELF文件格式的细节,这里不做介绍,网上很多资料。应用程序main通过编译先有add.o和main.o,然后再链接生成main。因此接下来先分析.o中相关的符号(Symbol),然后再分析应用程序main中对相关符号是如何处理的。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/31316.html

对于add.o,其中定义了一个全局变量global_extern_intadd函数,文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/31316.html

lyf@lyf:~/test$ readelf -s add.o

Symbol table '.symtab' contains 10 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 00000000     0 FILE    LOCAL  DEFAULT  ABS add.c
     2: 00000000     0 SECTION LOCAL  DEFAULT    1 .text
     3: 00000000     0 SECTION LOCAL  DEFAULT    4 .debug_info
     4: 00000000     0 SECTION LOCAL  DEFAULT    6 .debug_abbrev
     5: 00000000     0 SECTION LOCAL  DEFAULT    9 .debug_line
     6: 00000000     0 SECTION LOCAL  DEFAULT   11 .debug_str
     7: 00000000     0 SECTION LOCAL  DEFAULT   12 .debug_line_str
     8: 00000000     4 OBJECT  GLOBAL DEFAULT    2 global_extern_int
     9: 00000000    13 FUNC    GLOBAL DEFAULT    1 add

从上述输出我们可以看出来,全局变量global_extern_intadd函数都是符号(Symbol),且都是全局的(GLOBAL)。其中全局变量global_extern_int的Ndx为2(.data),add函数的Ndx为1(.text),对应代码段和数据段。函数的TYPE为FUNC,变量的TYPE为OBJECT。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/31316.html

下面是add.o中包含的所有的段(Section)的信息,符号表中的Ndx对应段表中的Nr,表示该符号属于哪个段(Section),文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/31316.html

lyf@lyf:~/test$ readelf -S add.o
There are 20 section headers, starting at offset 0x4a4:

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 00000d 00  AX  0   0  1
  [ 2] .data             PROGBITS        00000000 000044 000004 00  WA  0   0  4
  [ 3] .bss              NOBITS          00000000 000048 000000 00  WA  0   0  1
  [ 4] .debug_info       PROGBITS        00000000 000048 00006f 00      0   0  1
  [ 5] .rel.debug_info   REL             00000000 000374 000048 08   I 17   4  4
  [ 6] .debug_abbrev     PROGBITS        00000000 0000b7 000060 00      0   0  1
  [ 7] .debug_aranges    PROGBITS        00000000 000117 000020 00      0   0  1
  [ 8] .rel.debug_a[...] REL             00000000 0003bc 000010 08   I 17   7  4
  [ 9] .debug_line       PROGBITS        00000000 000137 00004b 00      0   0  1
  [10] .rel.debug_line   REL             00000000 0003cc 000020 08   I 17   9  4
  [11] .debug_str        PROGBITS        00000000 000182 00009c 01  MS  0   0  1
  [12] .debug_line_str   PROGBITS        00000000 00021e 000030 01  MS  0   0  1
  [13] .comment          PROGBITS        00000000 00024e 00002c 01  MS  0   0  1
  [14] .note.GNU-stack   PROGBITS        00000000 00027a 000000 00      0   0  1
  [15] .eh_frame         PROGBITS        00000000 00027c 000038 00   A  0   0  4
  [16] .rel.eh_frame     REL             00000000 0003ec 000008 08   I 17  15  4
  [17] .symtab           SYMTAB          00000000 0002b4 0000a0 10     18   8  4
  [18] .strtab           STRTAB          00000000 000354 00001d 00      0   0  1
  [19] .shstrtab         STRTAB          00000000 0003f4 0000af 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  D (mbind), p (processor specific)

对于main.o而言,引用了add.o中的函数和全局变量,同时自己定义了全局变量global_int及局部变量rtv,那么main.o的符号表,文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/31316.html

lyf@lyf:~/test$ readelf -s main.o

Symbol table '.symtab' contains 13 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 00000000     0 FILE    LOCAL  DEFAULT  ABS main.c
     2: 00000000     0 SECTION LOCAL  DEFAULT    1 .text
     3: 00000004     4 OBJECT  LOCAL  DEFAULT    3 a.0
     4: 00000000     0 SECTION LOCAL  DEFAULT    5 .debug_info
     5: 00000000     0 SECTION LOCAL  DEFAULT    7 .debug_abbrev
     6: 00000000     0 SECTION LOCAL  DEFAULT   10 .debug_line
     7: 00000000     0 SECTION LOCAL  DEFAULT   12 .debug_str
     8: 00000000     0 SECTION LOCAL  DEFAULT   13 .debug_line_str
     9: 00000000     4 OBJECT  GLOBAL DEFAULT    3 global_int
    10: 00000000    72 FUNC    GLOBAL DEFAULT    1 main
    11: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND global_extern_int
    12: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND add

如上所示,文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/31316.html

  • 局部变量(rtv)不是符号,并不在符号表中;
  • 静态变量,全局变量,函数都是符号,静态变量的符号的作用域是本地LOCAL;
  • global_extern_int和add引用了add.o中的符号,因此其类型为NOTYPE,因为在链接前还不知道具体的类型。

4.符号的重定位

main.o中对符号的引用是如何进行的?也就是如何获取到所需要的符号的真实地址呢?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/31316.html

首先,因为main.o要使用外部的符号(add.o中的),因此在main.o中有一个重定位段(.rel.text),告诉链接器该如何去做符号重定位。下面重定位段中的Offset指的是相对于.text段起始位置的偏移量文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/31316.html

lyf@lyf:~/test$ readelf -r ./main.o

Relocation section '.rel.text' at offset 0x484 contains 4 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00000013  00000901 R_386_32          00000000   global_int
00000024  00000b01 R_386_32          00000000   global_extern_int
00000029  00000901 R_386_32          00000000   global_int
00000033  00000c02 R_386_PC32        00000000   add
......
  • 对于global_int,需要在.text段中0x13和0x29的位置去做重定位,重定位的方式是R_386_32;
  • 对于global_extern_int,需要在.text段中0x24的位置去做重定位,重定位的方式是R_386_32;
  • 对于add函数,需要在.text段中0x33的位置去做重定位,重定位的方式是R_386_PC32;
NameValueFieldCalculation
R_386_NONE0NoneNone
R_386_322dwordS + A
R_386_PC321dwordS + A – P
R_386_GOT323dwordG + A
R_386_PLT324dwordL + A – P
R_386_COPY5NoneValue is copied directly from shared object
R_386_GLOB_DAT6dwordS
R_386_JMP_SLOT7dwordS
R_386_RELATIVE8dwordB + A
R_386_GOTOFF9dwordS + A – GOT
R_386_GOTPC10dwordGOT + A – P
R_386_32PLT11dwordL + A
R_386_1620wordS + A
R_386_PC1621wordS + A – P
R_386_822byteS + A
R_386_PC823byteS + A – P
R_386_SIZE3238dwordz + A

其中,R_386_32和R_386_PC32的重定位计算方式中,文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/31316.html

  • A为保存在修正位置的值;
  • S为符号的实际地址,这个地址在链接前是没有的,只有通过链接,将所有的.o的相似段进行合并,生成可执行文件,此时所有符号的地址都已经确定好了(此时所有符号,指令的地址都是真实的,也就是对应运行时进程的虚拟地址)。但是这个时候合并的很多.o的.text代码段中的部分符号地址依然不是最终的确定地址,需要去用上面的公式去做重定位。是个反过来的过程,先把所有符号的地址都定下来后,然后再去修改.text代码段中部分的符号地址,然后才生成可以正确执行的可执行程序。
  • P为被修正的位置,后面通过例子解释会很容易理解。

下面是main.o中对应代码段的反汇编,文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/31316.html

lyf@lyf:~/test$ objdump -drS -Mintel ./main.o

./main.o:     file format elf32-i386


Disassembly of section .text:

00000000 <main>:
int add(int a_, int b_);
extern int global_extern_int;
int global_int = 3;

int main()
{
   0:   8d 4c 24 04             lea    ecx,[esp+0x4]
   4:   83 e4 f0                and    esp,0xfffffff0
   7:   ff 71 fc                push   DWORD PTR [ecx-0x4]
   a:   55                      push   ebp
   b:   89 e5                   mov    ebp,esp
   d:   51                      push   ecx
   e:   83 ec 14                sub    esp,0x14
    static int a = 19;
    global_int = 5;
  11:   c7 05 00 00 00 00 05    mov    DWORD PTR ds:0x0,0x5
  18:   00 00 00
                        13: R_386_32    global_int
    int rtv = 0;
  1b:   c7 45 f4 00 00 00 00    mov    DWORD PTR [ebp-0xc],0x0
    rtv = add(global_int, global_extern_int);
  22:   8b 15 00 00 00 00       mov    edx,DWORD PTR ds:0x0
                        24: R_386_32    global_extern_int
  28:   a1 00 00 00 00          mov    eax,ds:0x0
                        29: R_386_32    global_int
  2d:   83 ec 08                sub    esp,0x8
  30:   52                      push   edx
  31:   50                      push   eax
  32:   e8 fc ff ff ff          call   33 <main+0x33>
                        33: R_386_PC32  add
  37:   83 c4 10                add    esp,0x10
  3a:   89 45 f4                mov    DWORD PTR [ebp-0xc],eax
    return rtv;
  3d:   8b 45 f4                mov    eax,DWORD PTR [ebp-0xc]
  40:   8b 4d fc                mov    ecx,DWORD PTR [ebp-0x4]
  43:   c9                      leave
  44:   8d 61 fc                lea    esp,[ecx-0x4]
  47:   c3                      ret
  • 结合main.o的重定向表.rel.text,在.text偏移0x13的位置上需要去做个R_386_32类型的重定向。也就是c7 05 00 00 00 00 05,对应的代码为global_int=5,0x13对应的是4个字节的global_int对应的地址,但是此时是00 00 00 00,所以此时对应的A为0;
  • 在.text偏移0x33的位置上需要去做个R_386_PC32类型的重定向,也就是e8 fc ff ff ff,其中0xe8是操作码,查找INTEL IA-32指令手册,这条指令是一个近址相对位移调用指令,在e8后面跟着的4个字节是被调用函数相对于调用指令下一条指令的偏移量。小端,fc ff ff ff对应ff ff ff fc,对应-4,调用指令下一条指令的地址为0x37,0x37-4=0x33,被调用函数的地址是0x33,又回到了原地?此时A的值为-4;

接下来,看看粘和后的main应用程序中对应符号的地址,对应公式中对应的S,文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/31316.html

lyf@lyf:~/test$ readelf -s ./main

Symbol table '.dynsym' contains 4 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 00000000     0 FUNC    GLOBAL DEFAULT  UND _[...]@GLIBC_2.34 (2)
     2: 00000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     3: 0804a004     4 OBJECT  GLOBAL DEFAULT   15 _IO_stdin_used

Symbol table '.symtab' contains 40 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 00000000     0 FILE    LOCAL  DEFAULT  ABS crt1.o
     2: 080481cc    32 OBJECT  LOCAL  DEFAULT    3 __abi_tag
     3: 00000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c
     4: 080490b0     0 FUNC    LOCAL  DEFAULT   13 deregister_tm_clones
     5: 080490f0     0 FUNC    LOCAL  DEFAULT   13 register_tm_clones
     6: 08049130     0 FUNC    LOCAL  DEFAULT   13 __do_global_dtors_aux
     7: 0804c024     1 OBJECT  LOCAL  DEFAULT   24 completed.0
     8: 0804bf10     0 OBJECT  LOCAL  DEFAULT   19 __do_global_dtor[...]
     9: 08049160     0 FUNC    LOCAL  DEFAULT   13 frame_dummy
    10: 0804bf0c     0 OBJECT  LOCAL  DEFAULT   18 __frame_dummy_in[...]
    11: 00000000     0 FILE    LOCAL  DEFAULT  ABS main.c
    12: 0804c01c     4 OBJECT  LOCAL  DEFAULT   23 a.0
    13: 00000000     0 FILE    LOCAL  DEFAULT  ABS add.c
    14: 00000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c
    15: 0804a0ec     0 OBJECT  LOCAL  DEFAULT   17 __FRAME_END__
    16: 00000000     0 FILE    LOCAL  DEFAULT  ABS
    17: 0804bf14     0 OBJECT  LOCAL  DEFAULT   20 _DYNAMIC
    18: 0804a008     0 NOTYPE  LOCAL  DEFAULT   16 __GNU_EH_FRAME_HDR
    19: 0804c000     0 OBJECT  LOCAL  DEFAULT   22 _GLOBAL_OFFSET_TABLE_
    20: 0804c020     4 OBJECT  GLOBAL DEFAULT   23 global_extern_int
    21: 00000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_mai[...]
    22: 080490a0     4 FUNC    GLOBAL HIDDEN    13 __x86.get_pc_thunk.bx
    23: 0804c010     0 NOTYPE  WEAK   DEFAULT   23 data_start
    24: 080491ae    13 FUNC    GLOBAL DEFAULT   13 add
    25: 0804c018     4 OBJECT  GLOBAL DEFAULT   23 global_int
    26: 0804c024     0 NOTYPE  GLOBAL DEFAULT   23 _edata
    27: 080491bc     0 FUNC    GLOBAL HIDDEN    14 _fini
    28: 0804c010     0 NOTYPE  GLOBAL DEFAULT   23 __data_start
    29: 00000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
    30: 0804c014     0 OBJECT  GLOBAL HIDDEN    23 __dso_handle
    31: 0804a004     4 OBJECT  GLOBAL DEFAULT   15 _IO_stdin_used
    32: 0804c028     0 NOTYPE  GLOBAL DEFAULT   24 _end
    33: 08049090     5 FUNC    GLOBAL HIDDEN    13 _dl_relocate_sta[...]
    34: 08049050    49 FUNC    GLOBAL DEFAULT   13 _start
    35: 0804a000     4 OBJECT  GLOBAL DEFAULT   15 _fp_hw
    36: 0804c024     0 NOTYPE  GLOBAL DEFAULT   24 __bss_start
    37: 08049166    72 FUNC    GLOBAL DEFAULT   13 main
    38: 0804c024     0 OBJECT  GLOBAL HIDDEN    23 __TMC_END__
    39: 08049000     0 FUNC    GLOBAL HIDDEN    11 _init
  • main的地址为0x08049166,add函数的地址为0x080491ae,global_extern_int的地址为0x0804c020,global_int的地址为0x0804c018;
  • main.o的在.text偏移0x13的位置上的R_386_32类型的重定向,global_int对应的真实地址为0x0804c018,S=0x0804c018,A为0,则main.o中.text的0x13的位置上的重定位的结果为S+A=0x0804c018;
  • main.o在.text偏移0x33的位置上的R_386_PC32类型的重定向,add的实际地址为0x080491ae,则S=0x080491ae,A为-4,P为修正处的位置即main+0x33,即0x08049166+0x33=0x8049199,则S+A-P=0x080491ae+(-4)-0x8049199=0x11,则重定位后的结果为0x11
  • 在main中还有一些其他的函数符号,例如,__libc_start_main@GLIBC_2.34 , _init ,_fini,可以把他们理解成图一中的System code,其实虽然main函数是C/C++的入口函数,但是它也是被其他的system code去调用,下面从main的反汇编代码中也能看出来。

下面来确认下,最终重定位的结果是否正确,查看应用程序main的反汇编代码,文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/31316.html

lyf@lyf:~/test$ objdump -d -Mintel ./main

./main:     file format elf32-i386


Disassembly of section .init:

08049000 <_init>:
 8049000:       f3 0f 1e fb             endbr32
 8049004:       53                      push   ebx
 8049005:       83 ec 08                sub    esp,0x8
 8049008:       e8 93 00 00 00          call   80490a0 <__x86.get_pc_thunk.bx>
 804900d:       81 c3 f3 2f 00 00       add    ebx,0x2ff3
 8049013:       8b 83 fc ff ff ff       mov    eax,DWORD PTR [ebx-0x4]
 8049019:       85 c0                   test   eax,eax
 804901b:       74 02                   je     804901f <_init+0x1f>
 804901d:       ff d0                   call   eax
 804901f:       83 c4 08                add    esp,0x8
 8049022:       5b                      pop    ebx
 8049023:       c3                      ret

Disassembly of section .plt:

08049030 <__libc_start_main@plt-0x10>:
 8049030:       ff 35 04 c0 04 08       push   DWORD PTR ds:0x804c004
 8049036:       ff 25 08 c0 04 08       jmp    DWORD PTR ds:0x804c008
 804903c:       00 00                   add    BYTE PTR [eax],al
        ...

08049040 <__libc_start_main@plt>:
 8049040:       ff 25 0c c0 04 08       jmp    DWORD PTR ds:0x804c00c
 8049046:       68 00 00 00 00          push   0x0
 804904b:       e9 e0 ff ff ff          jmp    8049030 <_init+0x30>

Disassembly of section .text:

08049050 <_start>:
 8049050:       f3 0f 1e fb             endbr32
 8049054:       31 ed                   xor    ebp,ebp
 8049056:       5e                      pop    esi
 8049057:       89 e1                   mov    ecx,esp
 8049059:       83 e4 f0                and    esp,0xfffffff0
 804905c:       50                      push   eax
 804905d:       54                      push   esp
 804905e:       52                      push   edx
 804905f:       e8 19 00 00 00          call   804907d <_start+0x2d>
 8049064:       81 c3 9c 2f 00 00       add    ebx,0x2f9c
 804906a:       6a 00                   push   0x0
 804906c:       6a 00                   push   0x0
 804906e:       51                      push   ecx
 804906f:       56                      push   esi
 8049070:       c7 c0 66 91 04 08       mov    eax,0x8049166
 8049076:       50                      push   eax
 8049077:       e8 c4 ff ff ff          call   8049040 <__libc_start_main@plt>
 804907c:       f4                      hlt
 804907d:       8b 1c 24                mov    ebx,DWORD PTR [esp]
 8049080:       c3                      ret
 8049081:       66 90                   xchg   ax,ax
 8049083:       66 90                   xchg   ax,ax
 8049085:       66 90                   xchg   ax,ax
 8049087:       66 90                   xchg   ax,ax
 8049089:       66 90                   xchg   ax,ax
 804908b:       66 90                   xchg   ax,ax
 804908d:       66 90                   xchg   ax,ax
 804908f:       90                      nop



08049166 <main>:
 8049166:       8d 4c 24 04             lea    ecx,[esp+0x4]
 804916a:       83 e4 f0                and    esp,0xfffffff0
 804916d:       ff 71 fc                push   DWORD PTR [ecx-0x4]
 8049170:       55                      push   ebp
 8049171:       89 e5                   mov    ebp,esp
 8049173:       51                      push   ecx
 8049174:       83 ec 14                sub    esp,0x14
 8049177:       c7 05 18 c0 04 08 05    mov    DWORD PTR ds:0x804c018,0x5
 804917e:       00 00 00
 8049181:       c7 45 f4 00 00 00 00    mov    DWORD PTR [ebp-0xc],0x0
 8049188:       8b 15 20 c0 04 08       mov    edx,DWORD PTR ds:0x804c020
 804918e:       a1 18 c0 04 08          mov    eax,ds:0x804c018
 8049193:       83 ec 08                sub    esp,0x8
 8049196:       52                      push   edx
 8049197:       50                      push   eax
 8049198:       e8 11 00 00 00          call   80491ae <add>
 804919d:       83 c4 10                add    esp,0x10
 80491a0:       89 45 f4                mov    DWORD PTR [ebp-0xc],eax
 80491a3:       8b 45 f4                mov    eax,DWORD PTR [ebp-0xc]
 80491a6:       8b 4d fc                mov    ecx,DWORD PTR [ebp-0x4]
 80491a9:       c9                      leave
 80491aa:       8d 61 fc                lea    esp,[ecx-0x4]
 80491ad:       c3                      ret

080491ae <add>:
 80491ae:       55                      push   ebp
 80491af:       89 e5                   mov    ebp,esp
 80491b1:       8b 55 08                mov    edx,DWORD PTR [ebp+0x8]
 80491b4:       8b 45 0c                mov    eax,DWORD PTR [ebp+0xc]
 80491b7:       01 d0                   add    eax,edx
 80491b9:       5d                      pop    ebp
 80491ba:       c3                      ret

Disassembly of section .fini:

080491bc <_fini>:
 80491bc:       f3 0f 1e fb             endbr32
 80491c0:       53                      push   ebx
 80491c1:       83 ec 08                sub    esp,0x8
 80491c4:       e8 d7 fe ff ff          call   80490a0 <__x86.get_pc_thunk.bx>
 80491c9:       81 c3 37 2e 00 00       add    ebx,0x2e37
 80491cf:       83 c4 08                add    esp,0x8
 80491d2:       5b                      pop    ebx
 80491d3:       c3                      ret
  • 8049177行,修正后的地址为0x804c018,和上述计算结果相同;
  • 8049198行,修正后的结果为e8 11 00 00 00,小端,即0x11,和上述计算结果相同。

结合下图和main的反汇编代码,其中__libc_start_main的函数签名如下所示,main函数是被其调用的,文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/31316.html

int __libc_start_main(int *(main) (int, char * *, char * *), int argc, char * * ubp_av, void (*init) (void), void (*fini) (void), void (*rtld_fini) (void), void (* stack_end));
C/C++符号重定位实例教程及图示
  • _start是真正的函数入口,会去调用__libc_start_main
  • __libc_start_main会去调用__init,main__fini等函数;
  • 8049070: c7 c0 66 91 04 08 mov eax,0x8049166将main函数的指针(0x8049166)放入寄存器,入栈,8049077: e8 c4 ff ff ff call 8049040 <__libc_start_main@plt>调用__libc_start_main,接着就开始执行用户层代码。
  • 关于__libc_start_main@plt后面在介绍动态库和PIC(地址无关代码)时会介绍(本文main默认动态链接了libc库),这里可以理解成调用__libc_start_main
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/31316.html
  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/ymba/31316.html

Comment

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定