ELF文件布局
relocatable类型(ET_REL)的ELF文件(如.o目标文件)无Program Header Table
ELF文件头
例子
ELF Header:
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: DYN (Shared object file)
Machine: AArch64
Version: 0x1
Entry point address: 0x570
Start of program headers: 64 (bytes into file)
Start of section headers: 7520 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 8
Size of section headers: 64 (bytes)
Number of section headers: 31
Section header string table index: 28
数据结构
typedef struct elf64_hdr {
unsigned char e_ident[EI_NIDENT]; /* ELF "magic number" */
Elf64_Half e_type;
Elf64_Half e_machine;
Elf64_Word e_version;
Elf64_Addr e_entry; /* Entry point virtual address */
Elf64_Off e_phoff; /* Program header table file offset */
Elf64_Off e_shoff; /* Section header table file offset */
Elf64_Word e_flags;
Elf64_Half e_ehsize;
Elf64_Half e_phentsize;
Elf64_Half e_phnum;
Elf64_Half e_shentsize;
Elf64_Half e_shnum;
Elf64_Half e_shstrndx;
} Elf64_Ehdr;
- e_type: ELF文件类型
- ET_REL
- ET_EXEC
- ET_DYN
- e_phoff: Program header table在ELF文件中的偏移
- e_shoff: Section header table在ELF文件中的偏移
- e_ehsize: ELF文件头大小
- e_phentsize: Program header table项的大小
- e_phnum: Program header table项的数量
- e_shentsize: Section header table项的大小
- e_shnum: Section header table项的数量
- e_shstrndx: Section header字符串表在Section header table中的索引
ELF Program header
例子
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000 ---> 可加载的Segment
0x0000000000000718 0x0000000000000718 R E 10000
LOAD 0x0000000000000dc0 0x0000000000010dc0 0x0000000000010dc0 ---> 可加载的Segment
0x000000000000024c 0x000000000000024c RW 10000
DYNAMIC 0x0000000000000dd8 0x0000000000010dd8 0x0000000000010dd8
0x00000000000001f0 0x00000000000001f0 RW 8
NOTE 0x0000000000000200 0x0000000000000200 0x0000000000000200
0x0000000000000024 0x0000000000000024 R 4
NOTE 0x0000000000000680 0x0000000000000680 0x0000000000000680
0x0000000000000098 0x0000000000000098 R 4
GNU_EH_FRAME 0x0000000000000634 0x0000000000000634 0x0000000000000634
0x0000000000000014 0x0000000000000014 R 4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 10
GNU_RELRO 0x0000000000000dc0 0x0000000000010dc0 0x0000000000010dc0
0x0000000000000240 0x0000000000000240 R 1
Section to Segment mapping:
Segment Sections...
00 .note.gnu.build-id .hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .plt .text .rodata .eh_frame_hdr .eh_frame .note.android.ident
01 .init_array .fini_array .dynamic .got .data
02 .dynamic
03 .note.gnu.build-id
04 .note.android.ident
05 .eh_frame_hdr
06
07 .init_array .fini_array .dynamic .got
数据结构
typedef struct elf64_phdr {
Elf64_Word p_type;
Elf64_Word p_flags;
Elf64_Off p_offset; /* Segment file offset */
Elf64_Addr p_vaddr; /* Segment virtual address */
Elf64_Addr p_paddr; /* Segment physical address */
Elf64_Xword p_filesz; /* Segment size in file */
Elf64_Xword p_memsz; /* Segment size in memory */
Elf64_Xword p_align; /* Segment alignment, file & memory */
} Elf64_Phdr;
- p_type: Program header(Segment)类型
- PT_NULL
- PT_LOAD
- PT_DYNAMIC
- ......
- p_offset: Segment内容在ELF文件中的偏移
- p_vaddr: Segment的虚拟地址
- p_paddr: Segment的物理地址
ELF Section header
例子
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .note.gnu.build-i NOTE 0000000000000200 00000200
0000000000000024 0000000000000000 A 0 0 4
[ 2] .hash HASH 0000000000000228 00000228
0000000000000050 0000000000000004 A 3 0 8
[ 3] .dynsym DYNSYM 0000000000000278 00000278
0000000000000168 0000000000000018 A 4 3 8
[ 4] .dynstr STRTAB 00000000000003e0 000003e0
00000000000000a1 0000000000000000 A 0 0 1
[ 5] .gnu.version VERSYM 0000000000000482 00000482
000000000000001e 0000000000000002 A 3 0 2
[ 6] .gnu.version_r VERNEED 00000000000004a0 000004a0
0000000000000020 0000000000000000 A 4 1 8
[ 7] .rela.dyn RELA 00000000000004c0 000004c0
0000000000000018 0000000000000018 A 3 0 8
[ 8] .rela.plt RELA 00000000000004d8 000004d8
0000000000000048 0000000000000018 AI 3 18 8
[ 9] .plt PROGBITS 0000000000000520 00000520
0000000000000050 0000000000000010 AX 0 0 16
[10] .text PROGBITS 0000000000000570 00000570
0000000000000094 0000000000000000 AX 0 0 4
[11] .rodata PROGBITS 0000000000000604 00000604
000000000000002e 0000000000000001 AMS 0 0 1
[12] .eh_frame_hdr PROGBITS 0000000000000634 00000634
0000000000000014 0000000000000000 A 0 0 4
[13] .eh_frame PROGBITS 0000000000000648 00000648
0000000000000038 0000000000000000 A 0 0 8
[14] .note.android.ide NOTE 0000000000000680 00000680
0000000000000098 0000000000000000 A 0 0 4
[15] .init_array INIT_ARRAY 0000000000010dc0 00000dc0
0000000000000008 0000000000000008 WA 0 0 1
[16] .fini_array FINI_ARRAY 0000000000010dc8 00000dc8
0000000000000010 0000000000000008 WA 0 0 8
[17] .dynamic DYNAMIC 0000000000010dd8 00000dd8
00000000000001f0 0000000000000010 WA 4 0 8
[18] .got PROGBITS 0000000000010fc8 00000fc8
0000000000000038 0000000000000008 WA 0 0 8
[19] .data PROGBITS 0000000000011000 00001000
000000000000000c 0000000000000000 WA 0 0 8
[20] .comment PROGBITS 0000000000000000 0000100c
0000000000000064 0000000000000001 MS 0 0 1
[21] .debug_pubnames PROGBITS 0000000000000000 00001070
0000000000000028 0000000000000000 0 0 1
[22] .debug_info PROGBITS 0000000000000000 00001098
000000000000006b 0000000000000000 0 0 1
[23] .debug_abbrev PROGBITS 0000000000000000 00001103
0000000000000054 0000000000000000 0 0 1
[24] .debug_line PROGBITS 0000000000000000 00001157
000000000000009d 0000000000000000 0 0 1
[25] .debug_str PROGBITS 0000000000000000 000011f4
0000000000000110 0000000000000001 MS 0 0 1
[26] .debug_macinfo PROGBITS 0000000000000000 00001304
0000000000000001 0000000000000000 0 0 1
[27] .debug_pubtypes PROGBITS 0000000000000000 00001305
000000000000001a 0000000000000000 0 0 1
[28] .shstrtab STRTAB 0000000000000000 00001c1d
0000000000000143 0000000000000000 0 0 1
[29] .symtab SYMTAB 0000000000000000 00001320
0000000000000708 0000000000000018 30 63 8
[30] .strtab STRTAB 0000000000000000 00001a28
00000000000001f5 0000000000000000 0 0 1
数据结构
typedef struct elf64_shdr {
Elf64_Word sh_name; /* Section name, index in string tbl */
Elf64_Word sh_type; /* Type of section */
Elf64_Xword sh_flags; /* Miscellaneous section attributes */
Elf64_Addr sh_addr; /* Section virtual addr at execution */
Elf64_Off sh_offset; /* Section file offset */
Elf64_Xword sh_size; /* Size of section in bytes */
Elf64_Word sh_link; /* Index of another section */
Elf64_Word sh_info; /* Additional section information */
Elf64_Xword sh_addralign; /* Section alignment */
Elf64_Xword sh_entsize; /* Entry size if section holds table */
} Elf64_Shdr;
- sh_offset: Setion内容在ELF文件中的偏移
Segment与Section
Segment与Section是ELF文件提供的不同视图
ELF Dynamic Section
描述动态链接所需的信息
例子
Dynamic section at offset 0xdd8 contains 27 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libm.so] ---> 依赖的库
0x0000000000000001 (NEEDED) Shared library: [libdl.so]
0x0000000000000001 (NEEDED) Shared library: [libc.so]
0x000000000000000e (SONAME) Library soname: [libtest_dynamiclink.so]
0x0000000000000019 (INIT_ARRAY) 0x10dc0
0x000000000000001b (INIT_ARRAYSZ) 8 (bytes)
0x000000000000001a (FINI_ARRAY) 0x10dc8
0x000000000000001c (FINI_ARRAYSZ) 16 (bytes)
0x0000000000000004 (HASH) 0x228 ---> hash表的文件偏移
0x0000000000000005 (STRTAB) 0x3e0 ---> 动态字符串表的文件偏移
0x0000000000000006 (SYMTAB) 0x278 ---> 动态符号表的文件偏移
0x000000000000000a (STRSZ) 161 (bytes) ---> 动态字符串标的大小
0x000000000000000b (SYMENT) 24 (bytes)
0x0000000000000003 (PLTGOT) 0x10fc8 ---> GOT表的虚拟地址
0x0000000000000002 (PLTRELSZ) 72 (bytes) ---> 使用PLT重定位的函数表(.rela.plt表)的大小
0x0000000000000014 (PLTREL) RELA
0x0000000000000017 (JMPREL) 0x4d8 ---> 使用PLT重定位的函数表地址(.rela.plt表文件偏移)
0x0000000000000007 (RELA) 0x4c0 ---> 需要重定位的数据表的地址(.rela.dyn表文件偏移)
0x0000000000000008 (RELASZ) 24 (bytes) ---> 重定位的数据表大小
0x0000000000000009 (RELAENT) 24 (bytes) ---> 重定位的数据表项大小
0x0000000000000018 (BIND_NOW) ---> 执行前重定位,不采用延迟绑定
0x000000006ffffffb (FLAGS_1) Flags: NOW
0x000000006ffffffe (VERNEED) 0x4a0
0x000000006fffffff (VERNEEDNUM) 1
0x000000006ffffff0 (VERSYM) 0x482
0x000000006ffffff9 (RELACOUNT) 1
0x0000000000000000 (NULL) 0x0
数据结构
typedef struct {
Elf64_Sxword d_tag; /* entry tag value */
union {
Elf64_Xword d_val;
Elf64_Addr d_ptr;
} d_un;
} Elf64_Dyn;
-
d_tag: Dynamic table项的类型
enum { DT_NULL = 0, // Marks end of dynamic array. DT_NEEDED = 1, // String table offset of needed library. DT_PLTRELSZ = 2, // Size of relocation entries in PLT. DT_PLTGOT = 3, // Address associated with linkage table. DT_HASH = 4, // Address of symbolic hash table. DT_STRTAB = 5, // Address of dynamic string table. DT_SYMTAB = 6, // Address of dynamic symbol table. DT_RELA = 7, // Address of relocation table (Rela entries). DT_RELASZ = 8, // Size of Rela relocation table. DT_RELAENT = 9, // Size of a Rela relocation entry. DT_STRSZ = 10, // Total size of the string table. DT_SYMENT = 11, // Size of a symbol table entry. DT_INIT = 12, // Address of initialization function. DT_FINI = 13, // Address of termination function. DT_SONAME = 14, // String table offset of a shared objects name. DT_RPATH = 15, // String table offset of library search path. DT_SYMBOLIC = 16, // Changes symbol resolution algorithm. DT_REL = 17, // Address of relocation table (Rel entries). DT_RELSZ = 18, // Size of Rel relocation table. DT_RELENT = 19, // Size of a Rel relocation entry. DT_PLTREL = 20, // Type of relocation entry used for linking. DT_DEBUG = 21, // Reserved for debugger. DT_TEXTREL = 22, // Relocations exist for non-writable segments. DT_JMPREL = 23, // Address of relocations associated with PLT. DT_BIND_NOW = 24, // Process all relocations before execution. DT_INIT_ARRAY = 25, // Pointer to array of initialization functions. DT_FINI_ARRAY = 26, // Pointer to array of termination functions. DT_INIT_ARRAYSZ = 27, // Size of DT_INIT_ARRAY. DT_FINI_ARRAYSZ = 28, // Size of DT_FINI_ARRAY. DT_RUNPATH = 29, // String table offset of lib search path. DT_FLAGS = 30, // Flags. DT_ENCODING = 32, // Values from here to DT_LOOS follow the rules // for the interpretation of the d_un union. DT_PREINIT_ARRAY = 32, // Pointer to array of preinit functions. DT_PREINIT_ARRAYSZ = 33, // Size of the DT_PREINIT_ARRAY array. DT_LOOS = 0x60000000, // Start of environment specific tags. DT_HIOS = 0x6FFFFFFF, // End of environment specific tags. DT_LOPROC = 0x70000000, // Start of processor specific tags. DT_HIPROC = 0x7FFFFFFF, // End of processor specific tags. ...... }
- DT_NEEDED:
- DT_PLTRELSZ:
- DT_PLTGOT:
d_un: d_tag决定d_un的意义
ELF rela.dyn Section
存放需要重定位数据引用
例子
Relocation section '.rela.dyn' at offset 0x4c0 contains 1 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000010dc8 000000000403 R_AARCH64_RELATIV 570
数据结构
typedef struct elf64_rela {
Elf64_Addr r_offset; /* Location at which to apply the action */
Elf64_Xword r_info; /* index and type of relocation */
Elf64_Sxword r_addend; /* Constant addend used to compute value */
} Elf64_Rela;
- r_offset: 需重定位的存储单元的虚拟地址(EXEC和DYN文件)
- r_info: 包含需重定位的符号在符号表中的索引以及重定位类型(处理器相关)
- 索引: r_info的高32位
- 类型: r_info的低32位
- R_AARCH64_GLOB_DAT: 重定位类型,创建GOT表项存储特定符号的地址
- R_AARCH64_JUMP_SLOT: 重定位类型,通过PLT找到目标符号的地址
ELF rela.plt Section
存放需要重定位的函数引用
例子
Relocation section '.rela.plt' at offset 0x4d8 contains 3 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000010fe0 000400000402 R_AARCH64_JUMP_SL 0000000000000000 printf@LIBC + 0
000000010fe8 000500000402 R_AARCH64_JUMP_SL 0000000000000000 __cxa_finalize@LIBC + 0
000000010ff0 000e00000402 R_AARCH64_JUMP_SL 0000000000000000 __cxa_atexit@LIBC + 0
以printf项为例
r_info : 000400000402
高32位: 0x0004 => 动态符号表(.dynsym)项索引
低32位: 0x00000402 => R_AARCH64_JUMP_SLOT
ELF .dynsym Section
例子
Symbol table '.dynsym' contains 15 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000570 0 SECTION LOCAL DEFAULT 10
2: 0000000000011000 0 SECTION LOCAL DEFAULT 19
3: 000000000001100c 0 NOTYPE GLOBAL DEFAULT ABS _bss_end__
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@LIBC (2)
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __cxa_finalize@LIBC (2)
6: 0000000000011008 4 OBJECT GLOBAL DEFAULT 19 g_var
7: 000000000001100c 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
8: 0000000000011010 0 NOTYPE GLOBAL DEFAULT ABS __end__
9: 000000000001100c 0 NOTYPE GLOBAL DEFAULT ABS __bss_start__
10: 000000000001100c 0 NOTYPE GLOBAL DEFAULT ABS _edata
11: 000000000001100c 0 NOTYPE GLOBAL DEFAULT ABS __bss_end__
12: 0000000000011010 0 NOTYPE GLOBAL DEFAULT ABS _end
13: 00000000000005ac 88 FUNC GLOBAL DEFAULT 10 test_dl
14: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __cxa_atexit@LIBC (2)
数据结构
typedef struct elf64_sym {
Elf64_Word st_name;
/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
unsigned char st_info;
unsigned char st_other;
Elf64_Half st_shndx;
Elf64_Addr st_value;
/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
Elf64_Xword st_size;
} Elf64_Sym;
- st_name: 符号名在动态字符串表的索引
- st_info: 符号的类型以及Bind属性
- 类型
- STT_NOTYPE
- STT_OBJECT: 符号是数据对象(变量、数组等)
- STT_FUNC: 符号是可执行的代码(函数等)
- STT_SECTION
- STT_FILE
- ......
- Bind属性
- STB_LOCAL: 本地符号,目标文件外不可见
- STB_GLOBAL: 全局符号
- STB_WEAK: 类似与全局符号,但优先级低
- 类型
- st_other: 0(保留)
- st_shndx: 符号定义所在Section的索引(参考Section Header Table)
- st_value: 符号相关的地址或者值
- st_size: 符号size
ELF .dynstr Section
例子
示意图
动态符号表查找符号字符串过程
Symbol table '.dynsym' contains 15 entries:
Num: Value Size Type Bind Vis Ndx Name
......
13: 00000000000005ac 88 FUNC GLOBAL DEFAULT 10 test_dl
14: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __cxa_atexit@LIBC (2)
以动态符号表项13、14为例,也就是查找test_dl以及__cxa_atexit字符串为例。
- 动态符号表在ELF中的位置
[ 3] .dynsym DYNSYM 0000000000000278 00000278
0000000000000168 0000000000000018 A 4 3 8
在ELF中的位置: 0x278
动态符号表项size: 0x18 = 24字节
动态符号表大小: 0x168
下面查找动态符号表项(test_dl以及__cxa_atexit)st_name字段中的内容
-
动态符号表的二进制内容
符号test_dl项所在地址 = 0x278 + 13 * 24 = 0x278 + 0x138 = 0x3b0
符号__cxa_atexit项所在的地址 = 0x278 + 14 * 24 = 0x278 + 0x150 = 0x3c8
根据动态符号表项的数据结构可知,st_name字段位于符号表项的起始4个字节中
test_dl项st_name字段内容:2a 00 00 00 --> 0x2a (little-endian)
__cxa_atexit项st_name字段内容:10 00 00 00 --> 0x10 (little-endian)
最终test_dl在动态字符串表中的索引为42, __cxa_atexit的索引为16
ELF .plt Section
过程链表,完成从地址无关的函数调用到绝对地址的转换
Disassembly of section .plt:
0000000000000520 <printf@plt-0x20>:
520: a9bf7bf0 stp x16, x30, [sp,#-16]!
524: 90000090 adrp x16, 10000 <note_end+0xf8e8>
528: f947ee11 ldr x17, [x16,#4056]
52c: 913f6210 add x16, x16, #0xfd8
530: d61f0220 br x17
534: d503201f nop
538: d503201f nop
53c: d503201f nop
0000000000000540 <printf@plt>:
540: 90000090 adrp x16, 10000 <note_end+0xf8e8>
544: f947f211 ldr x17, [x16,#4064] ----------> X17=0x10fe0
548: 913f8210 add x16, x16, #0xfe0
54c: d61f0220 br x17
......
ELF .got Section
全局偏移表,在数据段中存储绝对地址,用于产生地址无关的代码
0000000000010fc8 <.got>:
...
10fe0: 00000520 .word 0x00000520 ---------->链接完成后,存储printf的绝对地址
10fe4: 00000000 .word 0x00000000
10fe8: 00000520 .word 0x00000520 ----------> __cxa_finalize地址
10fec: 00000000 .word 0x00000000
10ff0: 00000520 .word 0x00000520 ----------> __cxa_atexit地址
10ff4: 00000000 .word 0x00000000
10ff8: 00010dd8 .word 0x00010dd8
10ffc: 00000000 .word 0x00000000
理解函数调用过程(使用PLT+GOT)
这里以一个简单的NDK Demo为例。
void test_dl(int param)
{
printf("Test dynamiclink...\n");
printf("param %d\n", param);
printf("Test func end\n");
}
通过Android Studio断点调试功能,分析test_dl()
调用printf()
的过程。
test_dl()
的汇编代码如下:
(lldb) di -f
libtest_dynamiclink.so`test_dl:
0x7f9af5e5ac <+0>: sub sp, sp, #0x20 ; =0x20
0x7f9af5e5b0 <+4>: stp x29, x30, [sp, #0x10]
0x7f9af5e5b4 <+8>: add x29, sp, #0x10 ; =0x10
0x7f9af5e5b8 <+12>: stur w0, [x29, #-0x4]
0x7f9af5e5bc <+16>: adrp x0, 0
0x7f9af5e5c0 <+20>: add x0, x0, #0x604 ; =0x604
-> 0x7f9af5e5c4 <+24>: bl 0x7f9af5e540 ; symbol stub for: printf
......
调用printf()
是通过bl指令跳转到地址0x7f9af5e540,该地址实际是PLT表的printf项地址
使用LLDB查看PLT表项的汇编代码
[0x0000007f9af5e520-0x0000007f9af5e570) r-x 0x00000520 0x00000050 0x00000006 libtest_dynamiclink.so..plt ---> PLT表
(lldb) di -s 0x7f9af5e540
libtest_dynamiclink.so`printf:
0x7f9af5e540 <+0>: adrp x16, 16
0x7f9af5e544 <+4>: ldr x17, [x16, #0xfe0]
0x7f9af5e548 <+8>: add x16, x16, #0xfe0 ; =0xfe0
0x7f9af5e54c <+12>: br x17
具体指令的含义:
adrp x16, 16 => x16 = PC(低12位清零) + 16 << 12 = 0x7f9af5e000 + 0X10000 = 0x7f9af6e000
ldr x17, [x16, #0xfe0] => x17 = 地址[0x7f9af6e000 + #0xfe0 = 0x7f9af6efe0]中的值
使用LLDB读取地址0x7f9af6efe0中的值
[0x0000007f9af6efc8-0x0000007f9af6f000) rw- 0x00000fc8 0x00000038 0x00000003 libtest_dynamiclink.so..got ---> GOT表
(lldb) x -s4 -fx -c2 0x7f9af6efe0
0x7f9af6efe0: 0xac1d5868 0x0000007f ---------> ((四字节)little-endian)
(lldb) x/8xb 0x7f9af6efe0
0x7f9af6efe0: 0x68 0x58 0x1d 0xac 0x7f 0x00 0x00 0x00 ---------> ((字节)little-endian)
以0x7f9af6efe0为起始地址的8个字节内容是0x7fac1d5868, 这就是printf的函数地址。最终指令br x17(0x7fac1d5868)
开始执行printf()
。
使用LLDB反汇编printf()
函数进行验证,printf()
函数地址的确是0x7fac1d5868
(lldb) di -n printf
libc.so`printf:
0x7fac1d5868 <+0>: stp x20, x19, [sp, #-0x20]!
0x7fac1d586c <+4>: stp x29, x30, [sp, #0x10]
0x7fac1d5870 <+8>: add x29, sp, #0x10 ; =0x10
0x7fac1d5874 <+12>: sub sp, sp, #0x120 ; =0x120
0x7fac1d5878 <+16>: adrp x19, 115
0x7fac1d587c <+20>: orr w8, wzr, #0xffffff80
小结
Android函数重定位并未采用延迟绑定,所以外部函数调用相对简单。