作者不知何许人也,亦不详其姓字。因喜魏晋,号竹林七闲。好编程,不求甚解;每有会意,便欣然忘食。常著文章自娱,颇示己志。奈水平有限,如有纰漏欢迎指教。
ADRP指令与虚拟内存
代码源码片段
问题
- 不同行数下的UITextView如何索引?
- ADRP指令有什么作用?
viewDidLoad中UITextView对应的汇编代码
通过image list 命令查看,当前App的ASLR为0x0000000104be4000
ADRP指令公式:PC寄存器所在的页数 + 页数常量(如上面的846) = 总页数 * 4k(1页大小, k:1024 * 4 bytes)
17行处PC寄存器的值(PC寄存器的值即当前指令执行的地址):0x0000000104be7dcc
Mach-O对应偏移为(起点为零,相当于在Mach-O view查看汇编代码所在的位置):0x0000000104be7dcc - 0x0000000104be4000 = 0x0000000000003dcc
那么当前PC寄存器所在的虚拟内存的页数如何计算呢?
- 虚拟内存的大小? 虚拟内存是按页分布的,虚拟内存中每页的大小为4K: 4 * 1024,16进制的对应标识:0x1000, 二进制对应的标识:1000000000000
- 页数计算方式 当前指令的内存地址/4K; 因为计算机指令中没有除法, 所以通过 指令的内存地址 << 12的方式计算;也就是通过清空当前地址的低12位的
- 那么执行adrp指令时PC寄存器所在的页数是多少呢?
PC寄存器的值:0x0000000104be7dcc
PC寄存器相对内存偏:0x0000000104be7dcc - 0x0000000104be4000 = 0x0000000000003dcc;
PC寄存器所在页数计算:0x0000000000003dcc << 12 = 0x0000000000003000
所以虚拟内存对应在第3页;
- adrp x8, 846 解读
执行adrp指令时PC寄存器所在的页数(0x0000000104be7dcc) + 846页 = 3页 + 846页 = 849页,
并将这个计算结果存储到x8寄存器中;
- 那么UITextView对应的具体地址是多少?
ldr x0, [x8, #0x518]:
x8 + 0x518 地址下的值([]即取值), 并放到x0寄存器中(UITextView class地址)
【x8值】= 849页虚拟内存的起始地址:849页 = 849 * 1024 *4 (10进制) = 0x00351000(16进制),
【x8 + 0x518】 = 849页 + 0x518 =
0x00351000 + 0x518 + 0x0000000104be4000 (ASLR) = 0x0000000104f35518
【x0】 = 0x0000000104f35518 的值, 即 *0x0000000104f35518
下面, 通过memory read指令, 查看0x0000000104f35518对应的值,即[x8, #0x518],[]是取值的意思
(lldb) memory read 0x0000000104f35518
0x104f35518: 88 8d 2c d8 01 00 00 00 10 58 2a d8 01 00 00 00 ..,......X*.....
0x104f35528: 18 56 f3 04 01 00 00 00 d8 dc 28 d8 01 00 00 00 .V........(.....
读取出的值为:0x01d82c8d88(memory read 是内存是从低到高分布的,所以88在低地址位, 01对应高地址位)
反汇编0x01d82c8d88:
(lldb) dis -s 0x01d82c8d88
UIKitCore`UITextView:
0x1d82c8d88 <+0>: prfm #0x18, 0x1d8321f2c ; (void *)0xd0332e3800000000
0x1d82c8d8c <+4>: udf #0x1
0x1d82c8d90 <+8>: prfm #0x18, 0x1d8322c2c ; (void *)0xd8322c5800000001
0x1d82c8d94 <+12>: udf #0x1
0x1d82c8d98 <+16>: .long 0x801955b0 ; unknown opcode
0x1d82c8d9c <+20>: udf #0x1
0x1d82c8da0 <+24>: udf #0x0
0x1d82c8da4 <+28>: udf #0x991
由此可见,0x01d82c8d88正是UITextView所对应的符号实际的内存地址;我们通过Mach-OView查看一下:
UITextView 在 Mach-OView符号地址 = 0x0000000104f35518 - ALSR = 0x0000000104f35518 - 0x0000000104be4000(ALSR)
= 0x00351518
0x00351518在Mach-O对应的是:
原来0x00351518在Mach-O中对应的__Objc_classrefs中第一个Class地址,那么0x00351518存放的值是什么呢?继续追查Mach-O:
通过Mach-O的Binding信息,我们发现, 在UIKit库加载时, 会往0x00351518处写入UITextView Class的真实地址,与上述运行时调试时信息一致(UITextView是系统符号 ,所以在dyld加载过程中通过binding的方式写入)。
injected中UITextView符号如何索引呢(不同方法不同代码行,PC指令寄存器会变化)?
同样是调用UITextView符号, 先看下injected对应的UITextView符号获取的汇编代码
当前PC寄存器虚拟内存页数为:0x104f2d5f8-0x0000000104be4000(ALSR) =
0x00000000003495f8 << 12 = 0x349页(16进制) = 841页(10进制)
所以adrp = PC虚拟内存页数 + 8页 = 841页 + 8页 = 849页,
所以ldr x0, [x8, #0x518] 依然是 = 虚拟内存849(0x351)页,
偏移0x518处(截图中显示528, 不小心截错代码行数了, 大家见谅, 当做518看,重新截图太费劲,重要的是过程)。
综上, UITextView(符号)加载后,符号所在的虚拟内存页数不会发生变化。 所以在汇编代码中, adrp是获取UITextView(符号)的页数,会因PC寄存器所在的页数不同,adrp后面的常量会随之变化。具体公式如下:
adrp指令对应的PC寄存器所在虚拟内存的页数+常量页数 = 符号页数(符号页数是固定的)。
符号页数的确认方式
那么,Mach-O中, 没有ALSR, 上述的UITextView符号的页数如何表示呢?先看下viewDidLoad的汇编 adrp所对应的指令:
同理,在看下,Mach-OView中,Inject方法中UITextView对应的汇编:
惊人的发现,Mach-O(单arm64架构,多架构未必符合此结论, 段迁移也不知道是否会有影响),因为没有ASLR, 所以在任意函数, 其符号不变(如UITextView)在任意指令下其符号偏移都是常量, 直接是符号虚拟内存所对应的页数。如,在Mach-O中,UITextView的adrp指令, 常量部分都是0x10351000, 也就是UITextView加载时虚拟内存所在的页数。
那么如何在Mach-OView中直接确认UITextView符号的虚拟内存所在的起始页数以及偏移常量呢?
因为UITextView是系统动态库符号,所以在Mach-O的Segment:__DATA, Section:__classref中,如下图所示:
在Mach-O结构中,一旦编译链接成功,UITextView(任意特定符号,本文系统动态库符号是个一般例子)所对应虚拟内存的页数以及偏移就是固定的。
关于Mach-O,欢迎前往58同城iOS团队开源项目,里面有大量Mach-O相关的功能与代码,如有所得,欢迎star:github.com/wuba/WBBlad…。