引言
最近工作比较忙,没怎么去研究汇编的内容,这么多天,感觉有点生疏,有的时候累死累活很晚才回来,还要在学习别的东西,在写一些总结,力不从心,就算是强迫自己去写去学习,我认为效率也是低下的。这种情况,既影响了第二天的工作,学习的时候也不会太过专注,只为一味的应付了事,学习是一个枯燥、兴趣使向、有目的性的
一些概念上的内容,其实光看光听、光说不练,基本上也就相当于拍脑袋做事情,正儿八经的研究应该是:构思->过程->验证 往往复复每次都会有新的体会
switch 注意事项
1、假设switch语句的分支比较少的时候(例如3,少于4的时候没有意义)没有必要使用此结构,相当于if。
2、各个分支常量的差值较大的时候,编译器会在效率还是内存进行取舍,这个时候编译器还是会编译成类似于if,else的结构。
3、在分支比较多的时候:在编译的时候会生成一个表(跳转表每个地址四个字节)。
switch高级代码 <4选项
void funcC(int a ){
switch (a) {
case 0:
printf("yuanying"); //元婴
break;
case 1:
printf("fenshen"); //分身
break;
case 2:
printf("dujie"); //渡劫
break;
default:
printf("qiongsi"); //穷死
break;
}
}
int main(int argc, char * argv[]) {
funcC(2);
return 0;
}
汇编代码
汇编Switch`funcC:
0x10475a82c <+0>: sub sp, sp, #0x30 ; =0x30
0x10475a830 <+4>: stp x29, x30, [sp, #0x20]
0x10475a834 <+8>: add x29, sp, #0x20 ; =0x20
0x10475a838 <+12>: stur w0, [x29, #-0x4]
-> 0x10475a83c <+16>: ldur w0, [x29, #-0x4]
0x10475a840 <+20>: mov x8, x0
0x10475a844 <+24>: stur w8, [x29, #-0x8]
0x10475a848 <+28>: cbz w0, 0x10475a878 ; <+76> at main.m:17
0x10475a84c <+32>: b 0x10475a850 ; <+36> at main.m
0x10475a850 <+36>: ldur w8, [x29, #-0x8]
0x10475a854 <+40>: subs w9, w8, #0x1 ; =0x1
0x10475a858 <+44>: stur w9, [x29, #-0xc]
0x10475a85c <+48>: b.eq 0x10475a88c ; <+96> at main.m:20
0x10475a860 <+52>: b 0x10475a864 ; <+56> at main.m
0x10475a864 <+56>: ldur w8, [x29, #-0x8]
0x10475a868 <+60>: subs w9, w8, #0x2 ; =0x2
0x10475a86c <+64>: str w9, [sp, #0x10]
0x10475a870 <+68>: b.eq 0x10475a8a0 ; <+116> at main.m:23
0x10475a874 <+72>: b 0x10475a8b4 ; <+136> at main.m:26
0x10475a878 <+76>: adrp x0, 1
0x10475a87c <+80>: add x0, x0, #0xf10 ; =0xf10
0x10475a880 <+84>: bl 0x10475abe0 ; symbol stub for: printf
0x10475a884 <+88>: str w0, [sp, #0xc]
0x10475a888 <+92>: b 0x10475a8c4 ; <+152> at main.m:29
0x10475a88c <+96>: adrp x0, 1
0x10475a890 <+100>: add x0, x0, #0xf19 ; =0xf19
0x10475a894 <+104>: bl 0x10475abe0 ; symbol stub for: printf
0x10475a898 <+108>: str w0, [sp, #0x8]
0x10475a89c <+112>: b 0x10475a8c4 ; <+152> at main.m:29
0x10475a8a0 <+116>: adrp x0, 1
0x10475a8a4 <+120>: add x0, x0, #0xf21 ; =0xf21
0x10475a8a8 <+124>: bl 0x10475abe0 ; symbol stub for: printf
0x10475a8ac <+128>: str w0, [sp, #0x4]
0x10475a8b0 <+132>: b 0x10475a8c4 ; <+152> at main.m:29
0x10475a8b4 <+136>: adrp x0, 1
0x10475a8b8 <+140>: add x0, x0, #0xf27 ; =0xf27
0x10475a8bc <+144>: bl 0x10475abe0 ; symbol stub for: printf
0x10475a8c0 <+148>: str w0, [sp]
0x10475a8c4 <+152>: ldp x29, x30, [sp, #0x20]
0x10475a8c8 <+156>: add sp, sp, #0x30 ; =0x30
0x10475a8cc <+160>: ret
代码分析
- 0x10475a82c 拉伸栈空间
- 0x10475a830 保存fp(栈底)、LR(回main函数,funcC函数调用完成以后执行的代码)
- 0x10475a834 x29 = sp + #0x20
- 0x10475a838 w0 存入到 【x29 - 0x4】
- 0x10475a83c 将【x29 - 0x4】的值读入到w0
- 0x10475a840 x8 = x0
- 0x10475a844 w8存入进【x29 - 0x4】
- 0x10475a848 判断w0是否为0 如果为0 直接跳转执行0x10475a878中的方法,不跳转直接向下执行 因为结果是2
- 0x10475a84c 跳转0x10475a850的方法 0x10475a848 中没有跳转
- 0x10475a850 w8 = 【x29 - 0x8】
- 0x10475a854 w9 = w8 - #0x1 修改标记位
- 0x10475a858【x29- 0x8】 = w9
- 0x10475a85c 上面的subs是否为0 ,就是等于的意思,等于 执行标记地址的方法的方法0x10475a88c
- 如果等于执行0x10475a88c adrp x0, 1
- 0x10475a890 add x0, x0, #0xf19
- 0x10475a894 bl 0x10475abe0 执行printf函数打印
- 0x10475a898 保存w0 [sp, #0x8] = w0
- 0x10475a89c b 0x10475a8c4 跳转(准备返回跳出该函数)
- 0x10475a860 向下执行0x10475a864标记的方法
- 0x10475a864 w8 = [x29, #-0x8]
- 0x10475a868 w9 = w8 - #0x2 修改标记位
- 0x10475a86c 【sp + 0x10】 = w9
- 0x10475a870 w9 = w8 - #0x2 是否为0 为0 执行标记地址中的方法 0x10475a8a0 不为0直接往下走
- 0x10475a874 上面的结果没有跳转,执行标记地址(0x10475a8b4)中的方法
- 下面的内容
- 0x10475a8b4 <+136>: adrp x0, 1
- 0x10475a8b8 <+140>: add x0, x0, #0xf27 ; =0xf27
- 0x10475a8bc <+144>: bl 0x10475abe0 ; symbol stub for: printf
以上是找到一个常量、并打印
以下是0x10475a870 执行的标记地址中的方法 - 0x10475a8a0 <+116>: adrp x0, 1
- 0x10475a8a4 <+120>: add x0, x0, #0xf21 ; =0xf21
- 0x10475a8a8 <+124>: bl 0x10475abe0
以上是找到常量或局部变量 - 0x10475a8ac [sp, #0x4] = w0
- 0x10475a8b0 执行标记地址中的方法0x10475a8c4
- 0x10475a878 <+76>: adrp x0, 1
- 0x10475a87c <+80>: add x0, x0, #0xf10 ; =0xf10
以上找到常量或者局部变量 - 0x10475a880 printf打印
- 0x10475a884 [sp, #0xc] = w0
- 0x10475a888 跳转到0x10475a8c4 直接返回之前函数准备
- 0x10475a8c0 <+148>: str w0, [sp] 保存 [sp] = w0
- 0x10475a8c4 <+152>: ldp x29, x30, [sp, #0x20] 恢复 fp lr
- 0x10475a8c8 <+156>: add sp, sp, #0x30 ; =0x30 恢复栈空间
- 0x10475a8cc <+160>: ret 返回
Switch超过4个选项汇编高级代码还原
高级代码
- (void)TheSelector:(NSInteger )Index{
switch (Index) {
case 0:
{
}
break;
case 1:
{
}
break;
case 2:
{
}
break;
case 3:
{
}
break;
case 4:
{
}
break;
case 5:
{
}
break;
default:
break;
}
}
高级代码还原`-[ViewController TheSelector:]:
0x10427e69c <+0>: sub sp, sp, #0x30 ; =0x30
0x10427e6a0 <+4>: str x0, [sp, #0x28]
0x10427e6a4 <+8>: str x1, [sp, #0x20]
0x10427e6a8 <+12>: str x2, [sp, #0x18]
-> 0x10427e6ac <+16>: ldr x0, [sp, #0x18]
0x10427e6b0 <+20>: mov x1, x0
0x10427e6b4 <+24>: subs x0, x0, #0x5 ; =0x5
0x10427e6b8 <+28>: str x1, [sp, #0x10]
0x10427e6bc <+32>: str x0, [sp, #0x8]
0x10427e6c0 <+36>: b.hi 0x10427e6f4 ; <+88> at ViewController.m:51
0x10427e6c4 <+40>: adrp x8, 0
0x10427e6c8 <+44>: add x8, x8, #0x700 ; =0x700
0x10427e6cc <+48>: ldr x9, [sp, #0x10]
0x10427e6d0 <+52>: ldrsw x10, [x8, x9, lsl #2]
0x10427e6d4 <+56>: add x8, x10, x8
0x10427e6d8 <+60>: br x8
0x10427e6dc <+64>: b 0x10427e6f8 ; <+92> at ViewController.m:54
0x10427e6e0 <+68>: b 0x10427e6f8 ; <+92> at ViewController.m:54
0x10427e6e4 <+72>: b 0x10427e6f8 ; <+92> at ViewController.m:54
0x10427e6e8 <+76>: b 0x10427e6f8 ; <+92> at ViewController.m:54
0x10427e6ec <+80>: b 0x10427e6f8 ; <+92> at ViewController.m:54
0x10427e6f0 <+84>: b 0x10427e6f8 ; <+92> at ViewController.m:54
0x10427e6f4 <+88>: b 0x10427e6f8 ; <+92> at ViewController.m:54
0x10427e6f8 <+92>: add sp, sp, #0x30 ; =0x30
0x10427e6fc <+96>: ret
分开分析
- 0x10427e69c <+0>: sub sp, sp, #0x30 ; =0x30
拉伸栈空间 - 0x10427e6a0 <+4>: str x0, [sp, #0x28]
0x10427e6a4 <+8>: str x1, [sp, #0x20]
0x10427e6a8 <+12>: str x2, [sp, #0x18]
0x10427e6ac <+16>: ldr x0, [sp, #0x18]这三句的代码,将x0、x1、x2存入到栈中,然后在把x2存入栈中的值,读出来存到x0,这个过程的含义,大概可以这么理解,因为在OC当中调用方法,是调用一个方法_objc_msgSend,它的三个参数分别是Self、_TheSelector、以及传过来的值,所以最后要读出来x2的值在存入到x0
- 0x10427e6b0 <+20>: mov x1, x0
将将x0的值赋值给x1
- 0x10427e6b4 <+24>: subs x0, x0, #0x5 ; =0x5
x0 = x0 - #0x5 得出的结果将会修改标记位
-
0x10427e6b8 <+28>: str x1, [sp, #0x10]
0x10427e6bc <+32>: str x0, [sp, #0x8]x1 、x0存入到栈中
-
0x10427e6c0 <+36>: b.hi 0x10427e6f4
如果标记位也就是ubs x0, x0, #0x5 x0>#0x5 直接跳转到标记中的地址,当然根据调试我们发现并没有往下走,而且根据我们写的高级代码来说 3肯定没有5大
-
0x10427e6c4 <+40>: adrp x8, 0
0x10427e6c8 <+44>: add x8, x8, #0x700 ; =0x700
ldr x9, [sp, #0x10]获取一个全局变量、或者一个常量
x8得到的结果是-36 x8 中的地址是 猛的一看就是负数
0x10427e6cc <+48>: ldr x9, [sp, #0x10]
0x10427e6b8 <+28>: str x1, [sp, #0x10]
0x10427e6cc <+48>: ldr x9, [sp, #0x10] 因为x1是参数3,x9 = 3
0x10427e6d0 <+52>: ldrsw x10, [x8, x9, lsl #2]
0x10427e6d4 <+56>: add x8, x10, x8
x10 = [x8, x9, lsl #2]内存地址中的值
[x8, x9, lsl #2] 以x8为基准值 + (x9的值左移2位 )
以x8地址为基准值 + (3的值左移2位 )
以x8地址为基准值 + (3的值左移2位 )0011 变成 1100 8+4 = 12
以x8地址为基准值 + 12
x10 = 0xffffffe8
直接跳转到指定的case中
结论
- 其实核心在于
0x10427e6c4 <+40>: adrp x8, 0
0x10427e6c8 <+44>: add x8, x8, #0x700
中拿到的x8,0x100d4e700
**switch中的选项>=4个的话,中间间隔不是很长的话,会直接连续的在栈中创建一个表,每4个字节代表一个数值,比如:case 1、2、3
内存创建的表抽象的对应这些地址:xx FF FF FF = 0 、xx FF FF FF = 1 、xx FF FF FF = 2 、xx FF FF FF = 3,当然最后你需要计算出真正的偏移的位置 **