汇编Switch(十一)

1.先看个最简单的switch(小于4)

分析:

解释:这个主要是拿到一个静态变量调用printf打印

打印完执行:

对于上面也就是拉伸栈空间(这个前面说过,不多说),w0-1判断是否相等,-2是否相等,-3是否相等,如果相等就直接执行printf,然后取出值结束。其实这个小于3个分支,也就是和if else一样


2.多分支switch执行default(大于或等于4)

分析:

tip:判断是否b.hi大于,是根于subs改变了标记寄存器(状态寄存器)cpsr来判断是否大于 (link:标记寄存器 )

问题:mark_b处看printf是什么?

  1. 0x1029068b0低12位清0     >0x102906000
  2. 0x102906000低12位值+1    >0x102907000
  3. 0x102907000 +0xf2c           >0x102907f2c

然后在lldb执行:

问题:mark_a处为什么减4?

先判断是不是default


3.多分支switch(大于或等于4)

分析:

问题:

#mark-1.1 取值多少?

根据adrp运行得知x8=0x104c438c0,然后通过lldb取出内存分布

lldb: memory read 0x104c438c0

0x104c438c0: 79 22 32 34 00 42 33 32 40 30 3a 38 40 22 55 49 ……..

(从右到左没4个字节(2bit位=1字节)如0x34322279)

#mark-2 ldrsw x10, [x8, x9, lsl #2] 什么意思?

x8+ (以x8作为基地址,x9左移2位),由上面分析可知x9=1,

  1. x9(1)左移2位是 4
  2. x8+4也就是上面的79 22 32 34 00 42 33 32 40 30 3a 38 40 22 55 49….向右偏移4个字节,也就是从00…开始
  3. x10=00 42 33 32,也就是0x32334200
  4. register read x10 可以验证第3步地址对不对

 register read x10  读取 x10 = 0xffffffffffffffa8 也就是个负数 (0xff-0xa8+1) =0x58 (十进制-88) (全是ffffff是-1)

 #mark-3 0x104c42838 <+60>: add x8, x10, x8  ?

通过上面2步知道:

x8= x10是个负数, x8地址-x10地址

如果case 202: ?  

解:w0直接减200来判断 (一样来查表)

如果case是乱序的呢?switch分支有空缺 比如 case 1:    case 5: case 9: case 11:   

它一样是制作一张表,拿空间换时间 把中间跳过的数补充default地址 可看#mark-1.1来查看x8地址 如下图


如果是乱序呢?

解析:

这个完全就失去switch的优势,就是if else


Switch

1、假设switch语句的分支比较少的时候 小于4个相当于if
2、各个分支常量的差值较大的时候,编译器会在效率还是内存进行取舍(这个时候编译器还是会编译成类似于if,else的结构)
3、在分支比较多的时候:在编译的时候会生成一个表(跳转表每个地址四个字节)通过地址直接跳转  (最好是连续的case)

汇编循环(十)

do{} while();

汇编后

while(){};

for(){}

分析:

其中for和while非常相似,哪个执行效率高? 主要看汇编执行的二进制,如果二进制一样效率一样的。在反汇编的时候当做for和while都可以…

cmp(Compare)比较指令

CMP 把一个寄存器的内容和另一个寄存器的内容或立即数进行比较。但不存储结果,只是正确的更改标志。
一般CMP做完判断后会进行跳转,后面通常会跟上B指令!

  • BL 标号:跳转到标号处执行
  • B.GT 标号:比较结果是大于(greater than),执行标号,否则不跳转
  • B.GE 标号:比较结果是大于等于(greater than or equal to),执行标号,否则不跳转
  • B.EQ 标号:比较结果是等于,执行标号,否则不跳转
  • B.HI 标号:比较结果是无符号大于,执行标号,否则不跳转

汇编If(九)

cmp(Compare)比较指令

CMP 把一个寄存器的内容和另一个寄存器的内容或立即数进行比较。但不存储结果,只是正确的更改标志。
一般CMP做完判断后会进行跳转,后面通常会跟上B指令!

  • BL 标号:跳转到标号处执行
  • B.GT 标号:比较结果是大于(greater than),执行标号,否则不跳转
  • B.GE 标号:比较结果是大于等于(greater than or equal to),执行标号,否则不跳转
  • B.EQ 标号:比较结果是等于,执行标号,否则不跳转
  • B.HI 标号:比较结果是无符号大于,执行标号,否则不跳转

B.LE: 小于等于,写成(>){}(汇编中是反的 如果是LE直接写成>就行)

分析:

优化后:

汇编还原高级代码(八)

内存分区域:

  • 代码区  特点:可读可写可执行
  • 栈区域  参数和局部变量
  • 堆区域 动态申请 可读可写
  • 全局:可读可写 (地址传递 p *(int *) 0x13fff)
  • 常量区:只读

首先下载一个ida分析工具:

链接:https://pan.baidu.com/s/1TYppMVvuAJROZrhDw_a-Ig 密码:561e

在ipa(.app)中的exec可执行文件直接拖进去,分析汇编代码

以下面例子简单分析:

从上面可以看出在main中调用了funTemp()函数,然后重点分析funTemp()函数怎么实现的

代码没优化的基本实现

如果验证还原的对不对呢? 可以把下面代码放在原来的地方,然后编译下吧.app包把执行文件拖到ida中 对比下和上面的是否相同就行(二进制一样,运行结果一样)。还原代码并不是目的,是为了分析…

汇编状态寄存器(七)

  • CPU有一种特殊的寄存器(对于不同的处理器,个数和结构都可能不同).这种寄存器在ARM中,被称为状态寄存器就是CPSR(current program status register)寄存器 。标记寄存器(状态寄存器)
    CPSR和其他寄存器不一样,其他寄存器是用来存放数据的,都是整个寄存器具有一个含义.而CPSR寄存器是按位起作用的,也就是说,它的每一位都有专门的含义,记录特定的信息.

cpsr 16进制共有8位(0xffffffff) 每2位一个字节,也就是  4*8=32位 注:CPSR寄存器是32位的

  • CPSR的低8位(包括I、F、T和M[4:0])称为控制位,程序无法修改,除非CPU运行于特权模式下,程序才能修改控制位!
  • N、Z、C、V均为条件码标志位。它们的内容可被算术或逻辑运算的结果所改变,并且可以决定某条指令是否被执行!意义重大!

 

N(Negative)标志

CPSR的第31位是 N,符号标志位。它记录相关指令执行后,其结果是否为负.如果为负 N = 1,如果是非负数 N = 0 (把最高位的16进制转二进制).

注意,在ARM64的指令集中,有的指令的执行时影响状态寄存器的,比如add\sub\or等,他们大都是运算指令(进行逻辑或算数运算)

Z(Zero)标志

CPSR的第30位是Z,0标志位。它记录相关指令执行后,其结果是否为0.如果结果为0.那么Z = 1.如果结果不为0,那么Z = 0.

对于Z的值,我们可以这样来看,Z标记相关指令的计算结果是否为0,如果为0,则N要记录下是0这样的肯定信息.在计算机中1表示逻辑真,表示肯定.所以当结果为0的时候Z = 1,表示结果是0.如果结果不为0,则Z要记录下不是0这样的否定信息.在计算机中0表示逻辑假,表示否定,所以当结果不为0的时候Z = 0,表示结果不为0

C(Carry)标志

CPSR的第29位是C,进位标志位。一般情况下,进行无符号数的运算。
加法运算:当运算结果产生了进位时(无符号数溢出),C=1,否则C=0。
减法运算(包括CMP):当运算时产生了借位时(无符号数溢出),C=0,否则C=1。

对于位数为N的无符号数来说,其对应的二进制信息的最高位,即第N – 1位,就是它的最高有效位,而假想存在的第N位,就是相对于最高有效位的更高位。

进位

我们知道,当两个数据相加的时候,有可能产生从最高有效位想更高位的进位。比如两个32位数据:0xaaaaaaaa + 0xaaaaaaaa,将产生进位。由于这个进位值在32位中无法保存,我们就只是简单的说这个进位值丢失了。其实CPU在运算的时候,并不丢弃这个进位制,而是记录在一个特殊的寄存器的某一位上。ARM下就用C位来记录这个进位值。比如,下面的指令

借位

当两个数据做减法的时候,有可能向更高位借位。再比如,两个32位数据:0x00000000 – 0x000000ff,将产生借位,借位后,相当于计算0x100000000 – 0x000000ff。得到0xffffff01 这个值。由于借了一位,所以C位 用来标记借位。C = 0.比如下面指令:

V(Overflow)溢出标志

CPSR的第28位是V,溢出标志位。在进行有符号数运算的时候,如果超过了机器所能标识的范围,称为溢出。

  • 正数 + 正数 为负数 溢出
  • 负数 + 负数 为正数 溢出
  • 正数 + 负数 不可能溢出

 

总结:

其中add和adds 加上s会影响标记寄存器

Cycript

1.Cycript安装

官网下载: http://www.cycript.org/

把下载的cycript_0.9.594*.zip 解压放到/opt/ 目录下,然后执行下命令 (cycript_0.9.594换成下载的版本名)

但是可能会出现这个问题

dyld: Library not loaded: /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/libruby.2.0.0.dylib
Referenced from: /opt/cycript_0.9.594/./Cycript.lib/cycript-apl
Reason: image not found
[1] 14883 abort ./cycript

解决:这错误是mac上的ruby版本过高导致,查看下mac上的ruby版本

在2.3基础上复杂一份叫2.0就行,命令如下:

使用和不使用sudo或切root问题还是出来了

mkdir: /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/: Operation not permitted

以下这些路径不能直接修改的

输入命令 csrutil status 打印的是 System Integrity Protection status: enabled.

这个是在 OS X El Capitan 中有一个跟安全相关的模式叫 SIP(System Integrity Protection ),它禁止让软件以 root 身份来在 Mac 上运行,在升级到 OS X 10.11 中或许你就会看到部分应用程序被禁用了,这些或许是你通过终端或者第三方软件源安装。对于大多数用户来说,这种安全设置很方便,但是也有些开发者或者高级 Mac 用户不需要这样的设置。https://developer.apple.com/videos/play/wwdc2015/706/

这是由于Mac的rootless机制,可以进入恢复模式关闭rootless机制:

解决:

1、重启mac,按command+R进入恢复模式

2、选择终端,在左上角,输入指令:

3、重启后让机器正常启动,可以在终端查看rootless状态:

正常情况下rootless已经关闭。

4、要想重新开启rootless机制,参照步骤1,输入指令:

这些弄完后,再从文章开头从新开始吧~


bash_profile配置

在每次打开终端只有指定/opt/cycript_0.9.594/才能找到cycript,这样非常的不方便。

终端直接执行

在.bash_profile最下面添加这个

如果你使用的是zsh, 在每次打开zsh会调用~/.zshrc, 在这里最下加入

这样不管是bash还是zsh都能在终端直接输入cycript

汇编函数参数和变量(六)

函数的参数和返回值

ARM64下,函数的参数是存放在X0到X7(W0到W7)这8个寄存器里面的.如果超过8个参数,就会入栈.
函数的返回值通常是放在X0 寄存器里面的.如何证明,看下面代码:

把上面汇编add x0,x0,x1 改成x1 是不是结果也对?答案是不对的,x0就是接受的它的返回值结果

返回值在x30主要是记住return(在arm中x29 和x30都会进栈)

函数的局部变量

函数的局部变量放在栈里面!

*str w10 [sp] : 把w10放sp的区域中

*x30:保护函数返回

*叶子函数:函数中不调用别的函数 ,它不用开辟栈空间   如:


函数嵌套

解析:

x0(w0)是用来保存函数返回值,不是return那个(return是x30(w30))

1.看函数functionA,首页把参数a,b入栈(也就是w0,w1入栈)#mark1,#mark2

2.sumA(a,b); 会把上面栈中的2个参数读到寄存器 #mark2下面2行

3.int pr1 对应#mark3 w0入栈

4.pr2和pr1也一样,寄存器和内存交互

5.add sp 栈平衡

总结:

函数调用会开辟一段空间(栈空间)。函数局部变量 桉树 寄存器的保护。

bl跳转将下一个指令放入lr(x30)寄存器,ret返回lr

x0-x7(个数有关系,数据结构有关系),多余的入栈

函数嵌套调用 A(开辟栈空间) > B(开辟栈空间)   > A(开辟栈空间)    A<->A (内存溢出) 每次调用都会开辟空间

汇编栈bl和ret(五)

bl和ret指令

bl标号

  • 将下一条指令的地址放入lr(x30)寄存器
  • 转到标号处执行指令

ret

  • 默认使用lr(x30)寄存器的值,通过底层指令提示CPU此处作为下条指令地址!

ARM64平台的特色指令,它面向硬件做了优化处理的

x30寄存器

x30寄存器存放的是函数的返回地址.当ret指令执行时刻,会寻找x30寄存器保存的地址值!

注意:在函数嵌套调用的时候.需要讲x30入栈!

下面这个例子是死循环

bl(x30)会覆盖bl main中的一个指令地址

具体如何优化?(参考系统 a(){B{} return} b(){return}):

在bl前,把x30放到栈里面,在ret前把x30从栈中写到寄存器.  x30:保存ret值地址

优化代码:

解释:

汇编栈(四)

栈:是一种具有特殊的访问方式的存储空间(后进先出, Last In Out Firt,LIFO)

1.SP和FP寄存器(push pop没了)

  • sp寄存器在任意时刻会保存我们栈顶的地址.
  • 现在是通过sp便宜来栈放数据
  • sp寄存器指向哪里,哪里就是栈

fp寄存器也称为x29寄存器属于通用寄存器,但是在某些时刻我们利用它保存栈底的地址!()

注意:上面sp上下拉的时候必须是16字节或16倍数。

注意:ARM64开始,取消32位的 LDM,STM,PUSH,POP指令! 取而代之的是ldr\ldp str\stp
ARM64里面 对栈的操作必须是16字节对齐的(也就是sp必须是16的倍数)!!

2.关于内存读写指令

注意:读/写 数据是都是往高地址读/写

str(store register)指令

将数据从寄存器中读出来,存到内存中.

ldr(load register)指令

将数据从内存中读出来,存到寄存器中

此ldr 和 str 的变种ldp 和 stp 还可以操作2个寄存器.

例子:

使用32个字节空间作为这段程序的栈空间,然后利用栈将x0和x1的值进行交换.

上面是把寄存器放到栈中(control+鼠标左键进入汇编代码,把x0,x1赋值 0xffffffff,register write x0 0xffffffff: 往寄存器x0赋值)

stp 如果是减 stp x0,x1,[sp,#-0x10]

add sp,sp,#0x20: 是为了栈平衡(出函数后里面局部变量等这些都不要了)


问题:

1. sp根据什么决定拉伸多少空间?

根据参数和变量size和函数调用来开辟多少空间,比如下面例子,如果没有参数f,那么他就是-> 0x100bac704 <+0>: sub sp, sp, #0x10 ; =0x10, 开辟的是0x10也就是16字节(sizeof(int)*4),如果是5个int参数sizeof(int)*5超过了栈地址对齐16字节,它就直接开辟0x20也就是32字节

如果在sumA增加一个函数调用

寄存器会把x30的值入栈,保存这个函数的值 这时也会开辟栈空间又增加了16字节(0x20+0x10)(其实arm中包含x29和x30都会进栈+0x20 看下面代码)

汇编寄存器(三)

CPU的运算速度是非常快的,为了性能CPU在内部开辟一小块临时存储区域,并在进行运算时先将数据从内存复制到这一小块临时存储区域中,运算时就在这一小快临时存储区域内进行。我们称这一小块临时存储区域为寄存器。

  • 对开发者说CPU最重要的器件是寄存器,可以通过改变寄存器的内容来实现对CPU的控制
  • 不同CPU寄存器个数、结构不相同

高速缓存

iPhoneX上搭载的ARM处理器A11它的1级缓存的容量是64KB,2级缓存的容量8M.

CPU每执行一条指令前都需要从内存中将指令读取到CPU内并执行。而寄存器的运行速度相比内存读写要快很多,为了性能,CPU还集成了一个高速缓存存储区域.当程序在运行时,先将要执行的指令代码以及数据复制到高速缓存中去(由操作系统完成).CPU直接从高速缓存依次读取指令来执行.

问题:

既然高速缓存这么牛x,为什么不把内存搞大点?其中有一个原因商业成本 等等

数据地址寄存器

数据地址寄存器通常用来做数据计算的临时存储、做累加、计数、地址保存等功能。定义这些寄存器的作用主要是用于在CPU指令中保存操作数,在CPU中当做一些常规变量来使用。

浮点和向量寄存器

因为浮点数的存储以及其运算的特殊性,CPU中专门提供浮点数寄存器来处理浮点数

  • 浮点寄存器 64位: D0 – D31 32位: S0 – S31

现在的CPU支持向量运算.(向量运算在图形处理相关的领域用得非常的多)为了支持向量计算系统了也提供了众多的向量寄存器.

  • 向量寄存器 128位:V0-V31

ARM64中

  • 64位: X0-X30, XZR(零寄存器)
  • 32位: W0-W30, WZR(零寄存器)

注意:8086汇编一些特殊寄存器段寄存器 CS,DS,SS,ES这些段的基地址属于Intel CPU架构中(Intel也不在用这些段寄存器),ARM中不存在

1.通用寄存器

ARM64拥有有31个64位的通用寄存器 x0 到 x30,这些寄存器通常用来存放一般性的数据,称为通用寄存器(有时也有特定用途)x开头是64位寄存器,w开头是32位寄存器

  • 那么w0 到 w28 这些是32位的. 因为64位CPU可以兼容32位.所以可以只使用64位寄存器的低32位.
  • 比如 w0 就是 x0的低32位!

*通常,CPU会先将内存中的数据存储到通用寄存器中,然后再对通用寄存器中的数据进行运算

2.PC寄存器(program counter)

  • 为指令指针寄存器,它指示了CPU当前要读取指令的地址
  • 在内存或者磁盘上,指令和数据没有任何区别,都是二进制信息
  • CPU在工作的时候把有的信息看做指令,有的信息看做数据,为同样的信息赋予了不同的意义
  • CPU根据什么将内存中的信息看做指令?
    • CPU将pc指向的内存单元的内容看做指令
    • 如果内存中的某段内容曾被CPU执行过,那么它所在的内存单元必然被pc指向过

其中pc是指即将执行的指令(pc就是指令 读/写等)

pc指向那里就是读那里,它读的是内存地址 (指令保存到高速缓存中)首先去高速缓存映射关系表中找,如果有就直接在高速读,如果没有把内存一堆copy到高速缓存中。

如果把pc寄存器改了下一个即将执行 可以使用 register write pc 0x21233b2

*CPU和内存交互

  • CPU首先会将红色内存空间的值放到X0寄存器中:mov X0,红色内存空间
  • 然后让X0寄存器与1相加:sub X0,1
  • 最后将值赋值给内存空间:mov 蓝色内存空间,X0

移走后是读,读内存空间本数不改变。内存空间也可以是一个。

3.bl指令(转移跳转指令)

  • CPU从何处执行指令是由pc中的内容决定的,我们可以通过改变pc的内容来控制CPU执行目标指令
  • ARM64提供了一个mov指令(传送指令),可以用来修改大部分寄存器的值(#10:就是数字),比如
    • mov x0,#10、mov x1,#20
  • 但是,mov指令不能用于设置pc的值,ARM64没有提供这样的功能
  • ARM64提供了另外的指令来修改PC的值,这些指令统称为转移指令,最简单的是bl指令

在main中执行