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加载到内存后所占用的大小。FileSiz和MemSiz的大小只有在少数情况下不相同;
VirtAddr表示segment的虚拟地址,PhysAddr表示它的物理地址。在现代常见的体系架构中,很少直接使用物理地址,所以这里VirtAddr的值与PhysAddr相同;
Flg表示segment的标志;
Align表示segment的对齐方式(也就是PhysAddr和Offset对Align取模的余数)。它的可能值为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