iOS ADRP汇编指令与虚拟内存

1,780 阅读3分钟

作者不知何许人也,亦不详其姓字。因喜魏晋,号竹林七闲。好编程,不求甚解;每有会意,便欣然忘食。常著文章自娱,颇示己志。奈水平有限,如有纰漏欢迎指教。

ADRP指令与虚拟内存

代码源码片段

图片1

问题

  • 不同行数下的UITextView如何索引?
  • ADRP指令有什么作用?

viewDidLoad中UITextView对应的汇编代码

图片1

通过image list 命令查看,当前App的ASLR为0x0000000104be4000

图片1

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对应的是:

图片1

原来0x00351518在Mach-O中对应的__Objc_classrefs中第一个Class地址,那么0x00351518存放的值是什么呢?继续追查Mach-O:

图片1

通过Mach-O的Binding信息,我们发现, 在UIKit库加载时, 会往0x00351518处写入UITextView Class的真实地址,与上述运行时调试时信息一致(UITextView是系统符号 ,所以在dyld加载过程中通过binding的方式写入)。

injected中UITextView符号如何索引呢(不同方法不同代码行,PC指令寄存器会变化)?

同样是调用UITextView符号, 先看下injected对应的UITextView符号获取的汇编代码 图片1

当前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所对应的指令:图片1

同理,在看下,Mach-OView中,Inject方法中UITextView对应的汇编: 图片1

惊人的发现,Mach-O(单arm64架构,多架构未必符合此结论, 段迁移也不知道是否会有影响),因为没有ASLR, 所以在任意函数, 其符号不变(如UITextView)在任意指令下其符号偏移都是常量, 直接是符号虚拟内存所对应的页数。如,在Mach-O中,UITextView的adrp指令, 常量部分都是0x10351000, 也就是UITextView加载时虚拟内存所在的页数。

那么如何在Mach-OView中直接确认UITextView符号的虚拟内存所在的起始页数以及偏移常量呢?

因为UITextView是系统动态库符号,所以在Mach-O的Segment:__DATA, Section:__classref中,如下图所示:

图片1 

在Mach-O结构中,一旦编译链接成功,UITextView(任意特定符号,本文系统动态库符号是个一般例子)所对应虚拟内存的页数以及偏移就是固定的。

关于Mach-O,欢迎前往58同城iOS团队开源项目,里面有大量Mach-O相关的功能与代码,如有所得,欢迎star:github.com/wuba/WBBlad…