《iOS底层原理文章汇总》
1.cmp(Compare)比较指令
CMP 把一个寄存器的内容和另一个寄存器的内容或立即数进行比较。但不存储结果,只是正确的更改标志。
一般CMP做完判断后会进行跳转,后面通常会跟上B指令!
- BL 标号:跳转到标号处执行
- B.LT 标号:比较结果是小于,执行标号,否则不跳转
- B.LE 标号:比较结果是小于等于,执行标号,否则不跳转
- B.GT 标号:比较结果是大于(greater than),执行标号,否则不跳转
- B.GE 标号:比较结果是大于等于(greater than or equal to),执行标号,否则不跳转
- B.EQ 标号:比较结果是等于,执行标号,否则不跳转
- B.HI 标号:比较结果是无符号大于,执行标号,否则不跳转
- B.NE 标号:比较结果不等于
- B.HI 标号:无符号大于
2.内存分区
1.代码区:存放代码,可读,可执行,MachO文件只有一部分是代码,还有一部分是数据,还有一部分是MachO文件描述信息
2.栈区:参数,局部变量,临时数据
3.堆区:动态申请,可读,可写
4.全局变量:可读可写
5.常量区:只读
I.全局变量和常量
68 61 68 61
h a h a 前面四个字节表示haha
0x104c93f9f: 68 61 68 61 00 01 00 00 00 1c 00 00 00 02 00 00 haha............
0x104c93faf: 00 24 00 00 00 00 00 00 00 24 00 00 00 02 00 00 .......
以上两个十六个字节分别对应字符,x0获取到的内存地址属于字符串常量区,
0x104c9218c <+20>: adrp x0, 1
0x104c92190 <+24>: add x0, x0, #0xf9f ; =0xf9f
上面两句代码是如何得到字符串常量区的内存地址0x0000000104c93f9f并存放到x0寄存器中的呢?adrp表示以页来寻址(address page),将1的值左移12位,十六进制左移0x1000,相当于二进制左移12位,将当前pc寄存器的地址加上1左移12位的值,且将原来的地址最后3位(低12位)清零
0x104c9218c --- > 0x104c9218c
0x1000 0x1000
0x104c93000得到尾数是000,则地址值是16的倍数,也是4的倍数,iOS中一页数据的大小PAGESIZE是16K,也就是会偏移0x4000,Mac中一页数据PAGESIZE的大小是4K,0x104c93000是一页数据的开头,加上偏移地址f9f得到存放字符串常量“haha”的内存地址0x104c93f9f
页号是在编译器编译时候确定的,以PC寄存器作为参照相当于以当前代码段的地址作为参照
为什么不能直接加这个地址,需要偏移呢?
我们不能得到一个确定的内存地址,但是我们能得到一个参照
程序在内存中的地址通常会通过ASLR,即实际地址+ASLR = 偏移地址,ASLR -> 0x0000000102e18000,
实际地址 + ASLR -> 0x0000000102e18000 = 偏移地址 0x0000000102e1e18c
偏移值作为参照会有两种参照方式:从文件的起始位置偏移或从当前代码的地址偏移,常量区的内存地址的计算是从当前代码的地址偏移
同理,取全局变量g的内存地址 0x104f16194 ---> 0x104f19648
通过汇编拿到地址-aspr 得到MachO文件中的地址
0x f9f
0x104c93f9f
0xf9f的值是编译器给的
若是0x1002bf888 <+20>: adrp x0,2
01002c1000
最后结果是将“haha”字符串出放入x0寄存器中传入printf函数
通过内存地址无法判断是字符串常量还是全局变量
3.通过汇编代码还原高级语言
1.编译当前工程
2.在product目录下.app中显示包内容,将app同名的MachO文件拖入hopper disassembler v4中
直接adrp的地址已经算好,是没有aslr的地址0x100007000+0xf9f
在MachOView烂苹果中查找0x100007f9f的地址,得到字符串常量haha的值,字符串常量存在String区
同理查找全局变量g的值0x100009000 + 0x648
在MachOView烂苹果中查找0x100009648的地址,得到全局变量g的值12,字符串常量存在String区,全局变量存在于section64,data部分
精简代码
得到原来高级代码
3.代码的执行流程并不关心,只关心执行的结果和原始的一样
4.if
两者相减,若小于0,跳入loc_1000061c0
若大于0,直接往下走
还原高级语言代码
5.循环(loop)
do{}while();循环
while(){};循环
for循环 和 while循环一模一样
5.Switch
1、假设switch语句的分支比较少的时候(例如3,少于4的时候没有意义)没有必要使用此结构,相当于if。
2、各个分支常量的差值较大的时候,编译器会在效率还是内存进行取舍,这个时候编译器还是会编译成类似于if,else的结构。
3、在分支比较多的时候:在编译的时候会生成一个表(跳转表每个地址四个字节)。
switch三个case情况
switch四个case情况:建立一张数据表8字节、8字节、8字节,将每一个条件要做的事情放到不同的连续地址8字节,得到index到数据表中去查找地址,找到后直接执行地址中保存的代码,通过一次运算,直接查表,找到要执行的地址,不需要多次if else判断
switch判断条件比较多,一直if else会浪费时间比较多
switch若判断条件无规则呢?没有查表,进行多次比较
条件有规律的情况下,如何建表,查表的呢?且看下文分析。