(32位)arm 汇编学习(2)

时间:2021-06-11 01:02:40

条件码

在指令中,有4位用来表示条件,也就是有16种条件。如果当前条件满足,则继续执行,否则这条指令将被忽略。

结果标志主要是被数据操作指令设置和清除,这些指令只有在你强调其应该操作标志位的时候才会影响标志位,比如MOV不会影响标志位,但是MOVS(mov with set)就会设置标志位。

接下来我们就要看一些指令条件,是通过在指令后面加上2个字母的后缀来表明的。

AL(ALways)

这个指令即表示总是,也就是指令将总是会被执行,如果没有表明条件,这个条件将会是默认的。

NV(NeVer)

所有的ARM条件指令都有相对应的相反的条件指令,AL就对应NV,NV就表示永不执行,可以用来占位或者消耗掉很小的一段时间。

EQ(EQual)

这个条件在Z标志位为1的时候被认为是true(关于标志位可参见第一篇)。Z标志位主要是由比较指令和一些可能会使得目标为0的指令来产生。

NE(Not Equal)

和EQ正好相反。

VS(oVerflow Set)

这个条件在V(溢出标志)为1的时候被认为是true,加法减法和比较指令会影响这个标志位。

VC (oVerflow Clear)

和VS相反

MI (MInus)

这个条件在N(负数标志)为1的时候被认为是true。N标志在最后一个数据操作使得数据为负的时候被设置。

PL(PLus)

和MI正好相反

CS (Carry Set)

从本指令开始的4个指令常被用来比较2个无符号数

CS指令的意思是当C(进位标志)是1的时候认为为true。C标志被算术操作,比如加法、减法、比较、移位等影响。
用作比较的时候,可以被翻译为大于等于,所以也可以用HS(Higher or Same)来代替这个标志。

CC(Carry Clear)

为CS的相反情况,在比较的时候,也可以用来表示小于,所以也可以写作LO(LOwer than)

HI(HIgher)

这个标志位在C标志为1且Z标志为0的时候被认为是true,在比较或者减法之后,这个组合可以被解释为左边的操作数大于右边的数(无符号数)。 (根据前几条命令,我们可以知道C表示大于等于,Z表示等于,大于等于且不等于,则为大于。)

LS(Lower or Same)

和上一条条件相反,C为0Z为1时为true,即左边的数小于等于右边的数。

GE(Greater than or Equal

从本指令开始的4条指令常被用来比较有符号数

本指令在N为0,V为0或者N为1V为1时为true。(大于等于)

LT(Less Than)

为上一条指令的相反,N为1V为0或者N为0V为1。小于

GT(Greater Than)

和GE类似,不过Z必须为0。 表示大于

LE(Less than or Equal)

和LT类似,不过在Z为1的时候始终为true。 表示小于等于

第一类-数据操作

这一类包含16个指令,指令语法较为相似,我们以ADD为例,然后其他指令说一下不同的部分。

汇编典型格式

以add为例:

 ADD{cond}{S} <dest>, <lhs>, <rhs>

花括号里的是可选的。

  • cond:条件码,如果忽略则为AL
  • S:如果有S,则这条指令影响标志位。
  • dest:目的地,即存储结果的寄存器号,比如R0,R1等等。
  • lhs:永远是一个寄存器号,左边的操作数
  • rhs:右操作数比较复杂,再解释完移位操作再详细解释,其语法为#<value>立即数,或者<reg>{,<shift type><reg>}或者<reg>{,<shift type> #<shift count>}

立即数

立即数用井号开始,后跟一个数字,比如#1表示立即数1。
用&开头可以表示16进制,比如#&1000表示#65536

其内部立即数表示方式为12位。

那么如何表示超过12位的数呢? 于是arm对于立即数,将其12位分为了两个部分,高4位为pos,即位置,低8位为value,即值。值是8位的数字,表示256种组合,而位置用4位,来表示值在32位数字中位于哪个位置。pos从0到f,每一个表示值在32位中向右循环移位2位

但是,作为用户,我们并不关心他是如何表示的,不过需要注意的是,如果这个数字不能被转换为这样的12进制表示,将会出现一个错误

移位操作

LSL #n(Logical Shift Left immediate)

逻辑左移n位,右边添0,左边超出的进入C标志位。

ASL #n(Arithmetic Shift Left immediate)

算术左移n位,和LSL一样

LSR #n(Logical Shift Right immediate)

逻辑右移n位,最右位进入C标志位,左边添0。

ASR #n(Arithmetic Shift Right immediate)

算术右移n位,最右边进入C标志位,左边添最左一位。

ROR #n(ROtate Right immediate)

循环右移n位,最右进入C标志位且左边添加最右一位。

RXX(Rotate right one bit with extend)

循环右移1位,C标志位进入最左边,最右边进入C标志位。

LSL rn(Logical Shift Left by a register)

逻辑左移寄存器指定的位数,以下几条指令与此类似,为之前指令的寄存器指定数字版本.不过需要注意的是只有寄存器最低一个字节会被计算。

ASL rn

LSR rn

ASR rn

ROR rn

再谈<rhs>

现在有了移位操作,再让我们来看看rhs的几种语法:

#<value>

立即数,就没有什么好说的了

<reg>{,<shift type>#<shift count>}

这里的几个元素分别是寄存器,以及其移位方式,比如ASL LSR等等的移位方式,和移位的位数的0到32以内的立即数。
也就是说,这个操作数是通过寄存器的值经过移位之后决定的

<reg>{,<shift type> <reg>}

这里除了<reg>其他和上一条一样,而这个元素即寄存器,也就是在移位当中解释的用于指明移动多少位的寄存器。

一个例子

ADD R0, R0, R0, LSL #1

这个即为R0 = R0 + R0逻辑左移一位,因为逻辑左移一位即×2,所以这个指令即为
R0 = R0 + R0 * 2 = R0 * 3

标志位影响

数据操作指令是会影响标志位的,主要遵循以下几个规律:

  • 一般情况,没有S的指令不影响标志位
  • N标志位:结果为负,即31位为1时为1,否则为0
  • Z标志位:结果为0时为1,否则为0
  • C标志位:如果RHS包含移位操作,则可能在这里被影响,否则不被影响
  • V标志位:不被影响

指令介绍

接下来介绍这一组的指令

AND 逻辑与

比较简单,就是逻辑与

BIC 清除特定位

即与非操作

TST 检验位

进行与操作,不过不存储,只影响标志位,加不加S,都影响标志位

ORR 逻辑或

EOR 逻辑异或

TEQ 检验相等性

进行逻辑异或,但是不存储结果,只影响标志位,且加不加S都影响标志位。

MOV 移动值

类似于赋值操作,将rhs放入lhs

MVN 移动逆反值

类似于赋值操作,不过要先进行逻辑非操作,即每一位取反再移动。

ADD 加法

ADC 带进位加法

除了加lhs和rhs还要加上C标志位

SBC 带借位减法

dest = lhs - rhs - NOT carry
NOT即逻辑非,carry为C标志位

RSB 反转减法

倒过来减,dest = rhs - lhs

RSC 带借位反转减法

同上不过多减一个C标志位取反

CMP 比较

做减法,但是结果不存储,影响标志位

CMN 比较负值

倒过来减,然后影响标志位,不存储结果

另:在这一组操作中使用R15

如果lhs为R15 则0与1位无法被看到
如果rhs为R15 则所有位都可以被看到

第一组A

有一些指令和第一组很像,但是不属于第一组,就是乘法指令,被归类为第一组A。
这个组有两条指令

MUL {cond}{S}<dest>,<lhs>,<rhs>
MLA {cond}{s}<dest>,<lhs>,<rhs>,<add>

注意:

  • 这里没有移位操作。
  • 所有的参数都为寄存器。
  • MUL将lhs乘上rhs然后将结果放入dest。
  • MLA类似,不过加上了寄存器add。
  • rhs和lhs不能为同一寄存器
  • R15不能作为dest

第二类-读取和存储

这一组只有两条基本的指令,LDR从一个地址读取值到寄存器,STR存储寄存器值。LDR和STR只在数据传输方向上不一样,所以我们以STR为核心介绍,LDR相应遵循规则即可。

STR 存储一个字(word)或者一个字节

取值模式

在指明地址的时候,有两种取值模式可以用,分别为前索引和后索引。

  • 前索引:STR {cond}<srce>,[<base>{,<offset>}]
  • 后索引:STR {cond}<srce>,[<base>],<offset>

前索引

srce是要存储的寄存器,base是包含需要的内存地址的基地址,offset是可选的,表示在存储前需要在地址上加上的偏移量,也就是说,存储位置为:
srce数据存入base + offset为地址的内存

偏移量格式

偏移量可以通过两种方式表示:

  • 0到4095之间的立即数:如STR R0,[R1,#20] ; STR R0,[R1,#-200]
  • 经过移位的寄存器:如STR R0,[R1,R2,LSL#2]会将R0的值存入R1+R2×4的地址的内存。

写回

只要内存地址被计算好之后,就更新存储基地址的寄存器的值会很方便,这样就可以在下一条指令中再使用。通过在指令后加上一个感叹号!,可以强制使得基寄存器从base+offset计算中被更新。
比如:

 STR R0,[R1,#-16]!

这条指令会将R0存入R1-16的位置,然后自动进行:

SUB R1, [R1,#16]

而这个额外的指令不需要额外的时间。
也就是说,在地址后面添加了感叹号,会使得保存基地址的寄存器在计算了基地址加offset之后,将这个基地址加offset的值直接存入保存基地址的寄存器,而这个过程是 “免费”的,不需要多余的时间。

字节和字操作

通过在STR指令后添加B,可以进行对字节的操作,否则一定是只能针对字也就是4个字节对齐来进行操作。

后索引取址

这是第二种取址模式,再来回顾一下语法

STR{cond} <srce>,[<base>],<offset>

这里和前索引的主要区别在于offset一直存在而不是可选的,且写回操作,虽然没有感叹号,可是都会被写回,也就是会自动写回,而不需要专门指明感叹号。

LDR 加载一个字或者一个字节

这里的取值模式等和STR是一样的,不过需要注意的是:

矫正

会有一个特殊的矫正操作,比如地址在addr,那么addr & 3FFFFFFC的地址处的值会被加载,也就是说低两位会被置0,之后再加载。然后,寄存器循环右移(addr mod 4)×8位。
举例说明:
假设&1000处的值为&76543210,加载如下左侧的地址,会得到右边的值

地址 R0
&1000 &76543210
&1001 &10765432
&1002 &32107654
&1003 &54321076

由于以字,即4字节对齐操作的要求,我们得到的都应该是&1000这样的值,所以需要一个循环右移操作来矫正。

相对于PC的取址

LDR 有一种特殊的格式:

LDR <dest>,<expression>
 expression产生一个地址,在这种情况下,指令会用到R15作为基寄存器,然后计算立即数偏移,偏移范围在-4095到+4095

第三类 多重读取和存储

LDM和STM用来读取和存储多个值。

关于栈

基本东西就不赘述了,网上有一大堆,不过注意这里的栈和数据结构的栈不完全等同,这里主要是指存储函数调用内容的栈。
注意:

  • 满/空栈:满栈full stack,即SP指向最后一个push进来的值,空栈empty stack,即SP指向下一个应该被放进来的空位置(下一个可用位置),这里主要是和数据结构的栈一样。
  • 上升/下降栈:上升栈,即SP在push之后增加,pull之后减小。下降栈,相反,push之后减小,pull之后增加。

    一般来说,我们使用的栈为满下降栈。

STM 存储多个寄存器

主要用于将多个值进栈,语法:

 STM<type> <base>{!},<registers>

我们忽略了条件码,但是一般来说是应该有条件码的。

  • type:两个字母,Full/Empty, Ascending/Descending,分别表示满F/空E,上升A/下降D。第一个字母F/E,第二个字母A/D。
  • base:栈指针,和LDR/STR一样,感叹号可以用来导致写回。
  • registers:我们想要push的寄存器列表,由大括号括起来的寄存器,可以每一个都写出来,也可以用Rx-Ry的形式。

    举例:

STMED R13!,{R1,R2,R5}

这条指令将会把R1,R2和R5用R13作为SP存入,因为指明了需要写回,R13最终会被减少3×4。因为是空栈,所以压栈结束之后,最后一个字的地址为R13+4。(R13指向最后一个可用位置)

再提一句关于FD(满下降栈)等等

主要是F/E和A/D的一个关系,在处理的时候,A/D会导致加减offset不同,是相反的,这个比较好理解,然而F/E的一个主要区别除了SP指向的位置不一样以外,在执行指令的时候,顺序也会不同,因为F栈指向最后一个已经用了的元素,push的时候会先调整offset到下一个可用位置,然后再处理,E栈则是先处理再调整offset。

存储方向

我们以FD栈为例,对于列表中的寄存器,存储的顺序将是低编号寄存器位于低地址,其他根据这个情况作相应变换。

存储SP和PC

在需要写回且用作SP的寄存器同时也需要push的时候,先写回值还是先push呢?也就是说push的是哪一个值呢? 如果sp是列表中编号最低的,则先push,否则写回的值是存在栈中那一个,我们一般用R13作为SP,所以一般都是新值。

LDM 读取多个寄存器

语法LDM<type> <base>{!},<registers>{^}
这里注意一下一个 ^ 符号,这个表示当R15也需要读取的时候,是否影响整个R15的值,换言之,有这个符号存在,将会影响标志位,否则标志位不变。

LDM SP和PC

当SP也被读取的时候,SP将永远时这个指令执行之后的值。
如果R15也需要读取,由^来决定是否需要改变状态,即标志位。

可选标识符

E/F A/D 可以被更换为 I/D B/A
对应关系:

  • STMFD:STMDB
  • LDMFD:LDMIA
  • STMFA:STMIB
  • LDMFA:LDMDA
  • STMED:STMDA
  • LDMED:LDMIB
  • STMEA:STMIA
  • LDMEA:LDMDB

第四类 跳转

语法:

B{cond} <expression>
  • cond:条件码
  • expression:地址

偏移量和管道

(这一节对于逆向来讲似乎没有明显作用,掠过)

带链接跳转

即BL指令,用来处理函数,R14用来存返回地址,如果还需要使用到R14,则存在栈上。

第五类 软中断

主要用于和操作系统交互
语法:

SWI{cond} <expression>
  • cond:条件码
  • expression:根据操作系统不同而不同