Linux ELF与动态链接

一 . ELF文件格式

Linux的可执行文件一般是elf格式的,在这个可执行文件的头部包含了很多重要的信息:如文件格式,加载地址,符号表等。当链接器链接生成可执行文件时,会将程序的加载地址写入可执行文件头。

在程序运行时,动态加载器将可执行文件载入文件头指定的加载地址处,并加载该地址,开始从该地址处运行。由此可见,可执行文件的起始地址是在编译时就决定的:

读取elf文件头

x@QiTianM650-A245:~$ readelf -h dnsmasq.elf 
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           ARM
  Version:                           0x1
  Entry point address:               0xa5d8
  Start of program headers:          52 (bytes into file)
  Start of section headers:          347256 (bytes into file)
  Flags:                             0x5000002, Version5 EABI, <unknown>
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         7
  Size of section headers:           40 (bytes)
  Number of section headers:         36
  Section header string table index: 33

 上述命令可以获得ELF文件的调用入口是0xa5d8, Program headers偏移地址是52, section headers的偏移地址是347256

 

读取program header(segments)

x@QiTianM650-A245:~$ readelf -l dnsmasq.elf 

Elf file type is EXEC (Executable file)
Entry point 0xa5d8
There are 7 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  EXIDX          0x0155fc 0x0001d5fc 0x0001d5fc 0x00010 0x00010 R   0x4
  PHDR           0x000034 0x00008034 0x00008034 0x000e0 0x000e0 R E 0x4
  INTERP         0x000114 0x00008114 0x00008114 0x00014 0x00014 R   0x1
      [Requesting program interpreter: /lib/ld-uClibc.so.0]
  LOAD           0x000000 0x00008000 0x00008000 0x15610 0x15610 R E 0x8000
  LOAD           0x015610 0x00025610 0x00025610 0x0040c 0x00528 RW  0x8000
  DYNAMIC        0x01561c 0x0002561c 0x0002561c 0x00108 0x00108 RW  0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4

 Section to Segment mapping:
  Segment Sections...
   00     .ARM.exidx 
   01     
   02     .interp 
   03     .interp .hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .ARM.exidx .eh_frame 
   04     .init_array .fini_array .jcr .dynamic .got .data .bss 
   05     .dynamic 
   06     

 

program header用于描述segment的特性(多个section被组合成segment,Section to Segment mapping字段描述了其对应关系):

Offset表示该segment相对ELF文件开头的偏移量;

FileSiz表示该segment在ELF文件中的大小,MemSiz表示该segment加载到内存后所占用的大小。FileSizMemSiz的大小只有在少数情况下不相同;

VirtAddr表示segment的虚拟地址,PhysAddr表示它的物理地址。在现代常见的体系架构中,很少直接使用物理地址,所以这里VirtAddr的值与PhysAddr相同;

Flg表示segment的标志;

Align表示segment的对齐方式(也就是PhysAddrOffsetAlign取模的余数)。它的可能值为2的倍数,0和1表示不采用对齐方式。Align的对齐方式不仅针对虚拟地址(VirtAddr),在ELF文件中的偏移量(Offset)也要采用与虚拟地址相同的对齐方式。PT_LOAD类型的segment需要针对所在操作系统的页对齐。

以上出现的section作用

EXIDX:  包含.ARM.exidx , 用于异常栈展开。(`.ARM.exidx` is the section containing information for unwinding the stack. If your C program has functions that print out a stack backtrace, the functions will likely depend on this section being present.相关的编译选项 `-funwind-tables);

PHDR (Program Header Table) :是描述程序头的,不含section;

INTERP: 类型包含有interp段,Interp”是“ Interpreter”(解释器)的缩写), 描述动态链接器。 ".interp"的内容很简单,里面保存的就是一个字符串,这个字符串就是可执行的所需要的动态链接器的路径(/lib/ld-uClibc.so.0)

DYNAMIC: dynamic link table,该段中保存了动态链接器所需要的基本信息,是一个结构数组,可以看做动态链接下 ELF 文件的“文件头”。存储了动态链接会用到的各个表的位置等信息。

LOAD:可加载内容,主要包括程序目标代码和常量信息

GNU_STACK 使用的系统栈

 

读取elf sections

x@QiTianM650-A245:~$ readelf -S dnsmasq.elf 
There are 36 section headers, starting at offset 0x54c78:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        00008114 000114 000014 00   A  0   0  1
  [ 2] .hash             HASH            00008128 000128 000358 04   A  3   0  4
  [ 3] .dynsym           DYNSYM          00008480 000480 000730 10   A  4   1  4
  [ 4] .dynstr           STRTAB          00008bb0 000bb0 000415 00   A  0   0  1
  [ 5] .gnu.version      VERSYM          00008fc6 000fc6 0000e6 02   A  3   0  2
  [ 6] .gnu.version_r    VERNEED         000090ac 0010ac 000020 00   A  4   1  4
  [ 7] .rel.dyn          REL             000090cc 0010cc 000028 08   A  3   0  4
  [ 8] .rel.plt          REL             000090f4 0010f4 0002f8 08   A  3  10  4
  [ 9] .init             PROGBITS        000093ec 0013ec 000010 00  AX  0   0  4
  [10] .plt              PROGBITS        000093fc 0013fc 000488 04  AX  0   0  4
  [11] .text             PROGBITS        00009884 001884 00fc44 00  AX  0   0  4
  [12] .fini             PROGBITS        000194c8 0114c8 000010 00  AX  0   0  4
  [13] .rodata           PROGBITS        000194d8 0114d8 004124 00   A  0   0  4
  [14] .ARM.exidx        ARM_EXIDX       0001d5fc 0155fc 000010 00  AL 11   0  4
  [15] .eh_frame         PROGBITS        0001d60c 01560c 000004 00   A  0   0  4
  [16] .init_array       INIT_ARRAY      00025610 015610 000004 00  WA  0   0  4
  [17] .fini_array       FINI_ARRAY      00025614 015614 000004 00  WA  0   0  4
  [18] .jcr              PROGBITS        00025618 015618 000004 00  WA  0   0  4
  [19] .dynamic          DYNAMIC         0002561c 01561c 000108 08  WA  4   0  4
  [20] .got              PROGBITS        00025724 015724 000188 04  WA  0   0  4
  [21] .data             PROGBITS        000258ac 0158ac 000170 00  WA  0   0  4
  [22] .bss              NOBITS          00025a20 015a1c 000118 00  WA  0   0  8
  [23] .comment          PROGBITS        00000000 015a1c 00004d 01  MS  0   0  1
  [24] .ARM.attributes   ARM_ATTRIBUTES  00000000 015a69 00002b 00      0   0  1
  [25] .debug_aranges    PROGBITS        00000000 015a98 000328 00      0   0  8
  [26] .debug_info       PROGBITS        00000000 015dc0 01cf30 00      0   0  1
  [27] .debug_abbrev     PROGBITS        00000000 032cf0 002f37 00      0   0  1
  [28] .debug_line       PROGBITS        00000000 035c27 00604f 00      0   0  1
  [29] .debug_frame      PROGBITS        00000000 03bc78 0014fc 00      0   0  4
  [30] .debug_str        PROGBITS        00000000 03d174 004373 01  MS  0   0  1
  [31] .debug_loc        PROGBITS        00000000 0414e7 01189d 00      0   0  1
  [32] .debug_ranges     PROGBITS        00000000 052d84 001da8 00      0   0  1
  [33] .shstrtab         STRTAB          00000000 054b2c 00014b 00      0   0  1
  [34] .symtab           SYMTAB          00000000 055218 002e10 10     35 442  4
  [35] .strtab           STRTAB          00000000 058028 001558 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),
  y (purecode), p (processor specific)

 

二.动态链接过程

对于动态库来说,不像静态库在链接可执行文件时代码段和数据段直接拷贝到可执行文件中,动态库是在运行时加载动态库代码,因此无法在编译和链接阶段获取代码段的符号地址(代码段的符号包括引用的全局数据,调用的函数等)。

INIERP指定了程序的Dynamic Linker动态链接器(/lib/ld-uClibc.so.0)。程序启动时也会启动Dynamic Linker,这个Dynamic Linker其实做了三件事情:

1.加载程序所依赖的库

加载依赖的库,这一步比较简单。我们可以通过readelf -d 查看程序依赖的动态库:

x@QiTianM650-A245:~$ readelf -d dnsmasq.elf 

Dynamic section at offset 0x1561c contains 28 entries:
  Tag        Type                         Name/Value
 0x00000001 (NEEDED)                     Shared library: [libnvram.so]
 0x00000001 (NEEDED)                     Shared library: [libpthread.so.0]
 0x00000001 (NEEDED)                     Shared library: [libsqlite.so]
 0x00000001 (NEEDED)                     Shared library: [libgcc_s.so.1]
 0x00000001 (NEEDED)                     Shared library: [libc.so.0]
 0x0000000c (INIT)                       0x93ec
 0x0000000d (FINI)                       0x194c8
 0x00000019 (INIT_ARRAY)                 0x25610
 0x0000001b (INIT_ARRAYSZ)               4 (bytes)
 0x0000001a (FINI_ARRAY)                 0x25614
 0x0000001c (FINI_ARRAYSZ)               4 (bytes)
 0x00000004 (HASH)                       0x8128
 0x00000005 (STRTAB)                     0x8bb0
 0x00000006 (SYMTAB)                     0x8480
 0x0000000a (STRSZ)                      1045 (bytes)
 0x0000000b (SYMENT)                     16 (bytes)
 0x00000015 (DEBUG)                      0x0
 0x00000003 (PLTGOT)                     0x25724
 0x00000002 (PLTRELSZ)                   760 (bytes)
 0x00000014 (PLTREL)                     REL
 0x00000017 (JMPREL)                     0x90f4
 0x00000011 (REL)                        0x90cc
 0x00000012 (RELSZ)                      40 (bytes)
 0x00000013 (RELENT)                     8 (bytes)
 0x6ffffffe (VERNEED)                    0x90ac
 0x6fffffff (VERNEEDNUM)                 1
 0x6ffffff0 (VERSYM)                     0x8fc6
 0x00000000 (NULL)                       0x0

这里记录了程序所依赖的其他动态库,只需要递归的查找这个区的数据就可以获取所有依赖库的列表,然后挨个加载即可。加载过程中Dynamic Linker会动态分配一段进程地址空间,并将动态库加载到该地址空间。

 

2.重新分配依赖库的内存地址

在程序编译阶段,当遇到使用动态库中的变量和函数时,是不知道该变量和函数的地址的,Linker就把动态库函数然后放到PLT(Procedule Linkage Table)里面。调用PLT函数会跳转到GOT(Global Offset Table)表。PLT表在编译过程就已形成并不会被修改,GOT表编译后是空的,当动态库加载时Dynamic Linker会修改GOT表,每次程序使用动态库里的函数和变量的时候,会通过PLT->GOT的方式找到实际最终符号地址。

 

3.初始化应用程序
Linux 中有特殊函数 _init 和 _fini, 主要是分别用来初始化动态库和关闭的时候做一些必要的处理。初始化应用程序会先调用_init函数完成动态库的初始化。(详细内容可以参阅《linux动态库的初始化和清理(init()函数和fini()函数)》)

 

 

 三.应用示例

以应用dnsmasq为例(arm32+linux平台)梳理下动态库调用的过程。

1. 应用程序编译时,编译器不知道调用的动态库的函数地址,所以Linker会把所有动态函数的入口放置在一起形成一个PLT表(Procedule Linkage Table),这个表在程序运行过程中是不需要更改。如下图所示,memcpy的入口函数对应了在memcpy@plt的符号。

 我们也可以用readelf --dyn-syms命令获取动态函数的入口地址:

x@QiTianM650-A245:~$ readelf --dyn-syms dnsmasq.elf 

Symbol table '.dynsym' contains 115 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00009410     0 FUNC    GLOBAL DEFAULT  UND opendir
     2: 0000941c     0 FUNC    GLOBAL DEFAULT  UND ctime
     3: 00009428     0 FUNC    GLOBAL DEFAULT  UND memcmp
     4: 00025a20     4 OBJECT  GLOBAL DEFAULT   22 stderr
     5: 00009434     0 FUNC    GLOBAL DEFAULT  UND listen
     6: 00009440     0 FUNC    GLOBAL DEFAULT  UND ioctl
     7: 0000944c     0 FUNC    GLOBAL DEFAULT  UND sendmsg
     8: 00009458     0 FUNC    GLOBAL DEFAULT  UND abort
     9: 00009464     0 FUNC    GLOBAL DEFAULT  UND realloc
    10: 00009470     0 FUNC    GLOBAL DEFAULT  UND connect
    11: 0000947c     0 FUNC    GLOBAL DEFAULT  UND __uClibc_main
    12: 00009488     0 FUNC    GLOBAL DEFAULT  UND write
    13: 00009494     0 FUNC    GLOBAL DEFAULT  UND _exit
    14: 00025b38     0 NOTYPE  GLOBAL DEFAULT   22 _bss_end__
    15: 000094a0     0 FUNC    GLOBAL DEFAULT  UND srandom
    16: 000094ac     0 FUNC    GLOBAL DEFAULT  UND sscanf
    17: 000094b8     0 FUNC    GLOBAL DEFAULT  UND gettimeofday
    18: 000094c4     0 FUNC    GLOBAL DEFAULT  UND pipe
    19: 000094d0     0 FUNC    GLOBAL DEFAULT  UND access
    20: 000194c8     0 FUNC    GLOBAL DEFAULT   12 _fini
    21: 00000000     0 NOTYPE  WEAK   DEFAULT  UND __deregister_frame_info
    22: 000094e8     0 FUNC    GLOBAL DEFAULT  UND recvmsg
    23: 000094f4     0 FUNC    GLOBAL DEFAULT  UND getpid
    24: 00009500     0 FUNC    GLOBAL DEFAULT  UND strcmp
    25: 0000950c     0 FUNC    GLOBAL DEFAULT  UND readdir
    26: 00009518     0 FUNC    GLOBAL DEFAULT  UND time
    27: 00009524     0 FUNC    GLOBAL DEFAULT  UND memset
    28: 00009530     0 FUNC    GLOBAL DEFAULT  UND recvfrom
    29: 0000953c     0 FUNC    GLOBAL DEFAULT  UND malloc
    30: 00009548     0 FUNC    GLOBAL DEFAULT  UND random
    31: 00009554     0 FUNC    GLOBAL DEFAULT  UND memmove
    32: 00000000     0 NOTYPE  WEAK   DEFAULT  UND _Jv_RegisterClasses
    33: 00009560     0 FUNC    GLOBAL DEFAULT  UND strcat
    34: 0000956c     0 FUNC    GLOBAL DEFAULT  UND fchown
    35: 00025a28    16 OBJECT  GLOBAL DEFAULT   22 in6addr_any
    36: 00009578     0 FUNC    GLOBAL DEFAULT  UND strcpy
    37: 00009584     0 FUNC    GLOBAL DEFAULT  UND strrchr
    38: 00009590     0 FUNC    GLOBAL DEFAULT  UND closedir
    39: 0000959c     0 FUNC    GLOBAL DEFAULT  UND fclose
    40: 00000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTab
    41: 000093ec     0 FUNC    GLOBAL DEFAULT    9 _init
    42: 000095a8     0 FUNC    GLOBAL DEFAULT  UND inet_addr
    43: 000095b4     0 FUNC    GLOBAL DEFAULT  UND open
    44: 000095c0     0 FUNC    GLOBAL DEFAULT  UND __cmsg_nxthdr
    45: 000095cc     0 FUNC    GLOBAL DEFAULT  UND vfprintf
    46: 000095d8     0 FUNC    GLOBAL DEFAULT  UND kill
    47: 00025a38     4 OBJECT  GLOBAL DEFAULT   22 optarg
    48: 000095e4     0 FUNC    GLOBAL DEFAULT  UND rename
    49: 000095f0     0 FUNC    GLOBAL DEFAULT  UND getsockname
    50: 00025a3c     4 OBJECT  GLOBAL DEFAULT   22 __ctype_b
    51: 000095fc     0 FUNC    GLOBAL DEFAULT  UND strchr
    52: 00009608     0 FUNC    GLOBAL DEFAULT  UND inet_pton
    53: 00000000     0 FUNC    GLOBAL DEFAULT  UND __aeabi_unwind_cpp_pr0@GCC_3.5 (2)
    54: 00009614     0 FUNC    GLOBAL DEFAULT  UND bind
    55: 00009620     0 FUNC    GLOBAL DEFAULT  UND ftell
    56: 0000962c     0 FUNC    GLOBAL DEFAULT  UND printf
    57: 00009638     0 FUNC    GLOBAL DEFAULT  UND waitpid
    58: 0000a5d8    80 FUNC    GLOBAL DEFAULT   11 _start
    59: 00009644     0 FUNC    GLOBAL DEFAULT  UND alarm
    60: 00009650     0 FUNC    GLOBAL DEFAULT  UND strncpy
    61: 0000965c     0 FUNC    GLOBAL DEFAULT  UND sigemptyset
    62: 00000000     0 NOTYPE  WEAK   DEFAULT  UND __register_frame_info
    63: 00009674     0 FUNC    GLOBAL DEFAULT  UND exit
    64: 00009680     0 FUNC    GLOBAL DEFAULT  UND raise
    65: 0000968c     0 FUNC    GLOBAL DEFAULT  UND fprintf
    66: 00009698     0 FUNC    GLOBAL DEFAULT  UND sqlite3_open
    67: 00025a1c     0 NOTYPE  GLOBAL DEFAULT   22 __bss_start
    68: 000096a4     0 FUNC    GLOBAL DEFAULT  UND fgets
    69: 00025b38     0 NOTYPE  GLOBAL DEFAULT   22 __end__
    70: 000096b0     0 FUNC    GLOBAL DEFAULT  UND sqlite3_errmsg
    71: 000096bc     0 FUNC    GLOBAL DEFAULT  UND socket
    72: 000096c8     0 FUNC    GLOBAL DEFAULT  UND close
    73: 00000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable
    74: 000096d4     0 FUNC    GLOBAL DEFAULT  UND sysconf
    75: 000096e0     0 FUNC    GLOBAL DEFAULT  UND read
    76: 000096ec     0 FUNC    GLOBAL DEFAULT  UND gethostname
    77: 000096f8     0 FUNC    GLOBAL DEFAULT  UND fputc
    78: 00009704     0 FUNC    GLOBAL DEFAULT  UND openlog
    79: 00025a1c     0 NOTYPE  GLOBAL DEFAULT   22 __bss_start__
    80: 00009710     0 FUNC    GLOBAL DEFAULT  UND __errno_location
    81: 0000971c     0 FUNC    GLOBAL DEFAULT  UND setsockopt
    82: 00009728     0 FUNC    GLOBAL DEFAULT  UND fcntl
    83: 00009734     0 FUNC    GLOBAL DEFAULT  UND getpeername
    84: 00009740     0 FUNC    GLOBAL DEFAULT  UND vsnprintf
    85: 0000974c     0 FUNC    GLOBAL DEFAULT  UND nanosleep
    86: 00009758     0 FUNC    GLOBAL DEFAULT  UND times
    87: 00009764     0 FUNC    GLOBAL DEFAULT  UND sendto
    88: 00009770     0 FUNC    GLOBAL DEFAULT  UND free
    89: 0000977c     0 FUNC    GLOBAL DEFAULT  UND atoi
    90: 00009788     0 FUNC    GLOBAL DEFAULT  UND stat
    91: 00009794     0 FUNC    GLOBAL DEFAULT  UND accept
    92: 000097a0     0 FUNC    GLOBAL DEFAULT  UND vsyslog
    93: 00025a1c     0 NOTYPE  GLOBAL DEFAULT   21 _edata
    94: 000097ac     0 FUNC    GLOBAL DEFAULT  UND strerror
    95: 000097b8     0 FUNC    GLOBAL DEFAULT  UND fopen
    96: 00025b38     0 NOTYPE  GLOBAL DEFAULT   22 __bss_end__
    97: 000097c4     0 FUNC    GLOBAL DEFAULT  UND select
    98: 000097d0     0 FUNC    GLOBAL DEFAULT  UND strtok
    99: 000097dc     0 FUNC    GLOBAL DEFAULT  UND sqlite3_close
   100: 000097e8     0 FUNC    GLOBAL DEFAULT  UND inet_ntop
   101: 000097f4     0 FUNC    GLOBAL DEFAULT  UND sprintf
   102: 00009800     0 FUNC    GLOBAL DEFAULT  UND sigaction
   103: 00025b38     0 NOTYPE  GLOBAL DEFAULT   22 _end
   104: 00025a40     4 OBJECT  GLOBAL DEFAULT   22 opterr
   105: 0000980c     0 FUNC    GLOBAL DEFAULT  UND memcpy
   106: 00009818     0 FUNC    GLOBAL DEFAULT  UND strlen
   107: 00009824     0 FUNC    GLOBAL DEFAULT  UND shutdown
   108: 00009830     0 FUNC    GLOBAL DEFAULT  UND strtol
   109: 0000983c     0 FUNC    GLOBAL DEFAULT  UND getopt_long
   110: 00009848     0 FUNC    GLOBAL DEFAULT  UND sqlite3_exec
   111: 00009854     0 FUNC    GLOBAL DEFAULT  UND fseek
   112: 00009860     0 FUNC    GLOBAL DEFAULT  UND strcasecmp
   113: 0000986c     0 FUNC    GLOBAL DEFAULT  UND cfg_get_item
   114: 00009878     0 FUNC    GLOBAL DEFAULT  UND difftime

 

 2. 应用程序运行过程中,如果调用了memcpy等动态库函数,最终跳转到0x93FC的_init函数后段([0x25814+0x70]->0x93FC)。注意,0x93FC的值位于_GLOBAL_OFFSET_TABLE(GOT表),该表可以在运行过程中动态修改。下图中大部分表项都是0x93FC是因为C库函数都有一个共同入口即_init后段。

 

从_init入口继续往下执行,最终会跳转到0x2572C指向的指令地址0xB6F037F0。该表项也位于_GLOBAL_OFFSET_TABLE,由于0xB6F037F0已经位于库文件的虚拟地址空间,很明显该表项是动态库在加载过程中修改的。

 

 3.跳转到库文件的地址空间(0xB6F037F0)后,库文件代码会根据传入的不同的R12值进行计算得出不同的调用入口,而后跳转到0xB6F00918完成库函数分流,最终完成库函数执行。

 

 

总结:

    (1)GOT:在动态库的数据段增加GOT(Global Offset Table),该表的每一项是符号到地址的绝对映射。由于代码段到数据段的偏移是固定的,因此可以在编译时确定代码段中的某个符号到GOT特定项之间的偏移。这样,代码段中的符号偏移就可以在编译时确定了,在加载时也无需修改代码段的内容,只需要填写位于数据段的GOT的所有项的符号的绝对地址就完成了。因为数据段本来就是进程间不共享,每个进程独立的一份,因此GOT的设计完全解决了以上两个问题,从而达到两个目的:1,代码段可以在多进程间共享;2,代码段是只读的。

    (2)PLT:PLT是 Program Linkage Table 的缩写,即程序链接表,PLT的出现是为了延时定位的目的。一个动态库中的函数往往要远多于全局变量,并且被调用的函数往往少于定义的函数。GOT中包含了该动态库中的所有的全局变量的映射,并且在链接器加载时解析所有的全局变量的地址。如果用同样的方式去处理函数调用符号,则开销会非常大。因此在代码段设计了一个PLT表,每一项其实是个代码段,用于执行如下逻辑:首次访问时,解析参数和向GOT填写函数地址,后续访问直接访问GOT中的函数地址。如此达到了延时定位的目的。

    因此,一个PIC的动态库中,对全局变量使用GOT来映射,对函数调用使用PLT+GOT来映射,从而达到共享库代码段复用,代码段安全访问的目的。

 

 四.-fPIC的作用


PIC = position independent code

-fPIC Generate position-independent code (PIC) suitable for use in a shared library

共享库有一个很重要的特征,就是可以被多个可执行文件共享,以达到节省磁盘和内存空间的目标:
共享意味着不仅磁盘上只有一份拷贝,加载到内存以后也只有一份拷贝,那么代码部分在运行时也不能被修改,否则就得有多个拷贝存在;
同时意味着,需要能够灵活映射在不同的虚拟地址空间,以便适应不同程序,避免地址冲突;

这里使用crc.c示例文件,尝试使用不同的选项来探讨生成的指令有何区别。

源代码:

#include <stdint.h>
#include <stdio.h>

unsigned char  testc[128]=
{0x62,0x6f,0x6f,0x74,0x72,0x6f,0x6d,0x2e,0x62,0x69,0x6e,0x00,0x35,0x31,0x35,0x32,
 0x20,0x31,0x34,0x32,0x35,0x30,0x33,0x32,0x35,0x32,0x36,0x35,0x20,0x31,0x30,0x30,
 0x37,0x37,0x35,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};

static const uint16_t crc16_tab[] = {
    0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
    0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
    0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
    0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
    0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
    0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
    0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
    0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
    0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
    0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
    0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
    0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
    0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
    0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
    0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
    0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
    0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
    0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
    0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
    0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
    0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
    0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
    0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
    0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
    0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
    0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
    0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
    0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
    0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
    0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
    0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
    0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0,
};

uint16_t crc16_ccitt(uint16_t cksum, const unsigned char *buf, int len)
{
    for (int i = 0;  i < len;  i++)
        cksum = crc16_tab[((cksum>>8) ^ *buf++) & 0xff] ^ (cksum << 8);

    return cksum;
}

 uint16_t ym_crc16(const uint8_t *buf, uint16_t len) 
{
  uint16_t x;
  uint16_t crc = 0;
  while (len--) {
    x = (crc >> 8) ^ *buf++;
    x ^= x >> 4;
    crc = (crc << 8) ^ (x << 12) ^ (x << 5) ^ x;
  }
  return crc;
}

int crc_main()
{
    uint16_t crc;
    crc = crc16_ccitt(0,testc,128);
    printf("res = 0x%x\n",crc);
    crc = ym_crc16(testc,128);
    printf("res = 0x%x\n",crc);

    return 0;
}

 void __attribute__((constructor)) _init_crc(void)
{
    printf("_init_crc\n");
}

 

不使用-fPIC生成共享库:

x@QiTianM650-A245:~$ arm-none-eabi-gcc -shared -o libcrc.so crc.c 

反汇编libcrc.so, 查看crc_main函数的printf调用,可以看到传入的R0参数为固定的值0xB00

 

 使用-fPIC生成共享库:

x@QiTianM650-A245:~$ arm-none-eabi-gcc -shared -o libcrc_fpic.so -fPIC crc.c 

反汇编libcrc.so, 查看crc_main函数的printf调用,可以看到传入的R0参数通过计算得出,先是从0xb18地址读出值0x2a0,再加上PC值0xac4(预取值),得到最终的值0xD6C。

 

由此可以看到,添加fPIC编译选项后,变量的地址也会转换成相对值。即使动态库的加载地址发生变化,读写变量也不会受到影响。

因此编译动态库时一定要打开fPIC选项,才能生成与位置无关的指令代码。

 

 

 

[参考文章]

1.https://cloud.tencent.com/developer/article/1432828

2.https://blog.csdn.net/hudaliquan/article/details/50055493

3.https://blog.csdn.net/21cnbao/article/details/103004401

4.https://blog.csdn.net/NBDR_YL/article/details/98742611

posted @ 2023-03-10 18:35  DF11G  阅读(529)  评论(0编辑  收藏  举报