arm汇编指令——分析问题的利器

文章目录

    • arm汇编指令为什么重要
      • 1. 主导问题
      • 2. arm汇编指令分类介绍
        • 通用寄存器
        • 状态寄存器
        • 数据转移指令
        • 寻址格式
        • 运算指令
        • 比较指令
        • 跳转指令
      • 3. 反汇编接口
      • 4. 反汇编分析举例
        • test1
        • test2
        • test3
        • test4
        • test5
        • test6
        • test7

arm汇编指令为什么重要

 分析问题中会涉及到看反汇编代码,通过反汇编code推断代码逻辑,从而更快更准确定位问题点,所以学会看反汇编代码是必要的。
 下面将我工作中用到的一些常用汇编知识分享给大家,欢迎交流补充!

1. 主导问题

  • 什么是寄存器?
    • CPU的运算速度是非常快的,为了性能CPU在内部开辟一小块临时存储区域,并在进行运算时先将数据从内存复制到这一小块临时存储区域中,运算时就在这一小快临时存储区域内进行。我们称这一小块临时存储区域为寄存器。
  • 寄存器与栈的关系?
    • 寄存器是全局容器,所有函数共用,但是栈不一样,一个函数占用独有的栈空间, 在各个函数嵌套调用时,寄存器很容易被覆盖读写,这个时候为了保持寄存器的数据不被改变,通常结合栈临时保存寄存器中的值,然后函数ret之前将数据恢复,这样就能确保上一个函数的数据不被改变,也就是实现了将寄存器当做局部变量使用。
  • 栈对齐
    • ARM64里面 对栈的操作是16字节对齐的, 也就是一次开辟栈空间至少是16字节, 或者是16的倍数, 如果不是这个值会报错
  • 什么是叶子函数
    • 函数体中没有调用其他函数的函数称之为叶子函数,又称为末尾函数。
    • 这种函数在编写汇编代码时可以省略使用栈空间, 栈空间是为了临时保护数据不被下一个函数污染, 叶子函数不存在这种风险,所以不需要进行保护处理,直接使用寄存器即可
  • 栈地址
    • 栈顶:低地址
    • 栈底:高地址
    • 栈内存开辟从高地址往低地址开辟
    • 读取内存是从低地址往高地址读取

2. arm汇编指令分类介绍

 注意:汇编指令对大小写不敏感

通用寄存器

  • r0 ~ r15
  • 有特殊用途的寄存器:
    • r14:别名lr(Link Register),链接寄存器,保存函数返回地址
    • r15:别名pc, 程序计数器,值为当前指令地址+4(顺序执行的下一条指令)
  • 与编译器有特殊约定的寄存器:
    • r13:别名sp(stack pointer),栈顶指针寄存器,保存栈顶地址(r9~r13都有约定,但还是sp最常用到)
    • fp(frame pointer): 栈帧指针寄存器,指向当前函数栈帧的栈底
    • 栈帧的概念,即每个函数所使用的栈空间是一个栈帧,所有的栈帧就组成了这个进程完整的栈。而fp就是栈基址寄存器,指向当前函数栈帧的栈底,sp则指向当前函数栈帧的栈顶。通过sp和fp所指出的栈帧可以恢复出母函数的栈帧,以此类推就可以backtrace出所有函数的调用顺序。
  • 其他与函数调用约定相关的寄存器:
    • r0~r3:函数调用传入参数的前4个32位数据
    • r0:函数返回值

状态寄存器

  • cpsr(current program status registers)寄存器,CPSR和其他寄存器不一样,其他寄存器是用来存放数据的,都是整个寄存器具有一个含义。而CPSR寄存器是按位起作用的,也就是说,它的每一位都有专门的含义,记录特定的信息。
  • 要想在算数运算是影响标记寄存器的值,必须在指令后面加上s
  • CPSR寄存器是32位的
  • N、Z、C、V均为条件码标志位。它们的内容可被算术或逻辑运算的结果所改变,并且可以决定某条指令是否被执行!意义重大!
  • CPSR的第31位是 N(Negative),符号标志位。它记录相关指令执行后,其结果是否为负.如果为负 N = 1,如果是非负数 N = 0.
  • CPSR的第30位是Z(Zero),0标志位。它记录相关指令执行后,其结果是否为0.如果结果为0.那么Z = 1.如果结果不为0,那么Z = 0.
  • CPSR的第29位是C(Carry),进位标志位。一般情况下,进行无符号数的运算。 加法运算:当运算结果产生了进位时(无符号数溢出),C=1,否则C=0。 减法运算(包括CMP):当运算时产生了借位时(无符号数溢出),C=0,否则C=1。
  • CPSR的第28位是V(Overflow),溢出标志位。在进行有符号数运算的时候,如果超过了机器所能标识的范围,称为溢出。

数据转移指令

  • LDR(load register) 字数据加载指令

    • LDR{条件} 目的寄存器,<存储器地址>
    • LDR指令用于从存储器中将一个32位的字数据传送到目的寄存器中。
  • STR(store register) 字数据存储指令

    • STR{条件} 源寄存器,<存储器地址>
    • STR指令用于从源寄存器中将一个32位的字数据传送到存储器中。该指令在程序设计中比较常用,且寻址方式灵活多样,使用方式可参考指令LDR。
  • 此ldr 和 str 的变种ldp(pair) 和 stp(pair) 还可以操作2个寄存器

  • ldp指令

    • load address pair 读取一对地址
    • ldp x1,x2,[x0,#0x10]
    • 把内存中x0+0x10的地址开始的值,存入到寄存器x1和x2中
  • LDR 字数据加载指令(4bytes)

  • LDRB 字节数据加载指令(1bytes)

  • LDRH 半字数据加载指令(2bytes)

  • STR 字数据存储指令(4bytes)

  • STRB 字节数据存储指令(1byte)

  • STRH 半字数据存储指令(2bytes)

寻址格式

  • signedoffset
    [x10,#0x10]//signedoffset。意思是从x10+0x10的地址取值
  • pre-index
    [sp,#-16]!//pre-index。意思是从sp-16地址取值,取值完后在把sp-16writeback回sp
  • post-index
    [sp],#16//post-index。意思是从sp地址取值,取值完后在把sp+16writeback回sp

运算指令

  • SUB指令 减法指令

    • sub ax,bx
    • sub ax,bx 就是ax中的值减bx中的值,放入ax中。
    • sub r0,#1 表示直接把1放入到r0中
    • sub r0,r0,#1 相当于 r0=r0-1放到r0
  • ADD 加法指令

    • 格式: ADD A,B //A=A+B;
    • A和B均为寄存器是允许的,一个为寄存器而另一个为存储器也是允许的, 但不允许两个都是存储器操作数。也就是说A与B不能同时是指针
  • MOV 指令将源操作数复制到目的操作数。作为数据传送(data transfer)指令,它几乎用在所有程序中。在它的基本格式中,第一个操作数是目的操作数,第二个操作数是源操作数:

    • MOV destination,source
  • movk

  • EOR 逻辑异或

    • EOR R2,R1,R0;
    • R2=R1∧R0
  • ORR

  • LSL(Logic Shift Left) 逻辑左移指令,也就是向左移位

    • #0xfffe, lsl #16

    • 0xfffe左移16位

  • rsb 指令(逆向减法指令)

    RSB{条件}{S} 目的[寄存器],操作数1,操作数2
    RSB指令称为逆向减法指令,用于把操作数2减去操作数1,并将结果存放到目的寄存器中。
    操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位的寄存器

比较指令

  • CMP指令(比较):

    CMP{条件} 操作数1, 操作数2

    CMP指令用于把一个寄存器的内容和另一个寄存器的内容或立即数进行比较,同时更新CPSR中条件标志位的值。
    该指令进行一次减法(cmp r0, r1 等价于 sub r2, r0, r1 (r2 = r0 - r1))运算,但不存储结果,只更改条件标志位

    标志位表示的是操作数1与操作数2的关系(大、小、相等),例如,当操作数1大于操作数2,则此后的有GT后缀的指令将可以执行。

跳转指令

  • cbz w8, 0x100c96694

    • cbz 指令的意思是 如果w8==0,则跳转到 0x100c96694,如果不等于0,则继续执行代码
  • b.eq b.ne

    • 当前运算结果为1,则Z=0
    • 当前运算结果为0,则Z=1
    • bne: 标志寄存器中Z标志位不等于零时, 跳转到BNE后标签处
    • beq: 标志寄存器中Z标志位等于零时, 跳转到BEQ后标签处
  • B{条件} 目标地址

    • B Label
    • 程序无条件跳转到标号 Label 处执行
    • B指令可以接上后缀,用来和cmp比较后待条件的跳转
      • EQ:equal 相等
      • NE:not equal,不相等
      • GT:great than,大于
      • GE greate equal,大于等于
      • LT:less than,小于
      • LE:less equal,.小于等于
  • BL{条件} 目标地址

    • 跳转之前,会在寄存器R14 中保存PC 的当前内容,因此,可以通过将R14 的内容重新加载到PC 中,来返回到跳转指令之后的那个指令处执行。该指令是实现子程序调用的一个基本但常用的手段。以下指令:

      BL Label ;当程序无条件跳转到标号 Label 处执行时,同时将当前的 PC 值保存到 R14 中

  • bx

    指令跳转到指定的目标地址中, 目标地址处指令即可是ARM指令也可以是Thumb指令

    bx lr
    

    返回子程序

3. 反汇编接口

  • __aeabi_idiv0 除法操作
  • raise 除0报错

4. 反汇编分析举例

test1

  • code
int main() {return 0;

}
  • 反汇编 code with -O2
00000000 <main>:
   0:	e3a00000 	mov	r0, #0    // r0 = 0;  r0为函数返回值寄存器
   4:	e12fff1e    bx	lr        // lr别名r14,保存函数返回地址寄存器
  • 反汇编 code without -O2
00000000 <main>:
   0:	e52db004 	push	{fp}		; (str fp, [sp, #-4]!)  // 压栈,保存main函数上一个func的栈帧地址fp到sp -4, sp -= 4
   4:	e28db000 	add	fp, sp, #0    // fp = sp + 0
   8:	e3a03000 	mov	r3, #0        // r3 = 0
   c:	e1a00003 	mov	r0, r3        // r0 = r3
  10:	e24bd000 	sub	sp, fp, #0    // sp = fp - 0
  14:	e49db004 	pop	{fp}		; (ldr fp, [sp], #4)     // 出栈,fp = sp + 4
  18:	e12fff1e 	bx	lr            // 跳转到函数返回地址寄存器

test2

  • code

    int main() {
        int a = 2;
        return 0;
    }
    
  • 反汇编 code not with -O2

00000000 <main>:
   0:	e52db004 	push	{fp}		; (str fp, [sp, #-4]!)   //压栈,保存main函数上一个func的栈帧地址fp到sp -4, sp -= 4
   4:	e28db000 	add	fp, sp, #0    // 栈帧指针指向栈顶地址,fp = sp
   8:	e24dd00c 	sub	sp, sp, #12   // sp -= 12
   c:	e3a03002 	mov	r3, #2        // r3 = 2
  10:	e50b3008 	str	r3, [fp, #-8]  // 保存r3到(fp - 8)寄存器,局部变量a放到栈内存中
  14:	e3a03000 	mov	r3, #0         // r3 = 0
  18:	e1a00003 	mov	r0, r3         // r0 = r3, r0 函数返回值寄存器,即return 0
  1c:	e24bd000 	sub	sp, fp, #0     // sp = fp, 栈顶指针指向栈帧指针,销毁掉局部变量a
  20:	e49db004 	pop	{fp}		; (ldr fp, [sp], #4)   // fp = [sp], sp += 4,栈帧指针指向main函数上一个func的栈帧,栈顶指针指向main函数上一个func的栈顶
  24:	e12fff1e 	bx	lr     // 跳转到目标地址lr,即r14(函数返回地址)

test3

  • code
int main(void) {
    int a = 2;
    char b = 3;
    a = b;
    return 0;
}
  • 反汇编,我们只关注与test2的差异点
00000000 <main>:
   0:	e52db004 	push	{fp}		; (str fp, [sp, #-4]!)
   4:	e28db000 	add	fp, sp, #0
   8:	e24dd00c 	sub	sp, sp, #12
   c:	e3a03002 	mov	r3, #2
  10:	e50b3008 	str	r3, [fp, #-8]
  14:	e3a03003 	mov	r3, #3              // r3 = 3,对应代码中b = 3
  18:	e54b3009 	strb	r3, [fp, #-9]   // 3存入一个bytes到fp-9中
  1c:	e55b3009 	ldrb	r3, [fp, #-9]   // 加载[fp-9]的一个bytes到r3中
  20:	e50b3008 	str	r3, [fp, #-8]       // 将r3存入[fp-8]中,即a = b
  24:	e3a03000 	mov	r3, #0
  28:	e1a00003 	mov	r0, r3
  2c:	e24bd000 	sub	sp, fp, #0
  30:	e49db004 	pop	{fp}		; (ldr fp, [sp], #4)
  34:	e12fff1e 	bx	lr

test4

  • code
void test(void) {

}

int main(void) {
    int a = 2;
    char b = 3;
    a = b;
    test();
    return 0;
}
  • 反汇编
00000000 <test>:
   0:	e52db004 	push	{fp}		; (str fp, [sp, #-4]!)   // 压栈帧
   4:	e28db000 	add	fp, sp, #0
   8:	e24bd000 	sub	sp, fp, #0
   c:	e49db004 	pop	{fp}		; (ldr fp, [sp], #4)   // 弹栈帧
  10:	e12fff1e 	bx	lr

00000014 <main>:
  14:	e92d4800 	push	{fp, lr}
  18:	e28db004 	add	fp, sp, #4
  1c:	e24dd008 	sub	sp, sp, #8
  20:	e3a03002 	mov	r3, #2
  24:	e50b3008 	str	r3, [fp, #-8]
  28:	e3a03003 	mov	r3, #3
  2c:	e54b3009 	strb	r3, [fp, #-9]
  30:	e55b3009 	ldrb	r3, [fp, #-9]
  34:	e50b3008 	str	r3, [fp, #-8]
  38:	ebfffffe 	bl	0 <test>           // 跳转到test func
  3c:	e3a03000 	mov	r3, #0
  40:	e1a00003 	mov	r0, r3
  44:	e24bd004 	sub	sp, fp, #4
  48:	e8bd8800 	pop	{fp, pc}

test5

  • code
void test(int p1, int p2, int p3, int p4) {

}

int main(void) {
    int a = 2;
    char b = 3;
    a = b;
    test(a, a, a, a);
    return 0;
}
  • 反汇编,只关注与test4的差异点
00000000 <test>:
   0:	e52db004 	push	{fp}		; (str fp, [sp, #-4]!)
   4:	e28db000 	add	fp, sp, #0
   8:	e24dd014 	sub	sp, sp, #20                // 栈顶指针开辟空间 20个
   c:	e50b0008 	str	r0, [fp, #-8]              // 第一个形参拷贝第一个实参
  10:	e50b100c 	str	r1, [fp, #-12]              // 第2个形参拷贝第2个实参
  14:	e50b2010 	str	r2, [fp, #-16]              // 第3个形参拷贝第3个实参
  18:	e50b3014 	str	r3, [fp, #-20]	; 0xffffffec     // 第4个形参拷贝第4个实参
  1c:	e24bd000 	sub	sp, fp, #0
  20:	e49db004 	pop	{fp}		; (ldr fp, [sp], #4)
  24:	e12fff1e 	bx	lr

00000028 <main>:
  28:	e92d4800 	push	{fp, lr}
  2c:	e28db004 	add	fp, sp, #4
  30:	e24dd008 	sub	sp, sp, #8
  34:	e3a03002 	mov	r3, #2
  38:	e50b3008 	str	r3, [fp, #-8]
  3c:	e3a03003 	mov	r3, #3
  40:	e54b3009 	strb	r3, [fp, #-9]
  44:	e55b3009 	ldrb	r3, [fp, #-9]
  48:	e50b3008 	str	r3, [fp, #-8]
  4c:	e51b0008 	ldr	r0, [fp, #-8]    // 函数入参1,赋值为a
  50:	e51b1008 	ldr	r1, [fp, #-8]    // 函数入参2,赋值为a
  54:	e51b2008 	ldr	r2, [fp, #-8]    // 函数入参3,赋值为a
  58:	e51b3008 	ldr	r3, [fp, #-8]    // 函数入参4,赋值为a
  5c:	ebfffffe 	bl	0 <test>
  60:	e3a03000 	mov	r3, #0
  64:	e1a00003 	mov	r0, r3
  68:	e24bd004 	sub	sp, fp, #4
  6c:	e8bd8800 	pop	{fp, pc}

test6

  • code
int test(int p1, int p2, int p3, int p4) {
    int p = 0;
    p = p1 + p2 + p3 - p4;
    return p;
}

int main(void) {
    int a = 2;
    char b = 3;
    a = b;
    a = test(a, a, a, a);
    return 0;
}
  • 反汇编,只关注与test5的差异点
00000000 <test>:
   0:	e52db004 	push	{fp}		; (str fp, [sp, #-4]!)
   4:	e28db000 	add	fp, sp, #0
   8:	e24dd01c 	sub	sp, sp, #28               // 栈顶指针开辟空间 28个
   c:	e50b0010 	str	r0, [fp, #-16]
  10:	e50b1014 	str	r1, [fp, #-20]	; 0xffffffec
  14:	e50b2018 	str	r2, [fp, #-24]	; 0xffffffe8
  18:	e50b301c 	str	r3, [fp, #-28]	; 0xffffffe4
  1c:	e3a03000 	mov	r3, #0                  // p = 0
  20:	e50b3008 	str	r3, [fp, #-8]           // p ---> [fp - 8]
  24:	e51b2010 	ldr	r2, [fp, #-16]
  28:	e51b3014 	ldr	r3, [fp, #-20]	; 0xffffffec
  2c:	e0822003 	add	r2, r2, r3              // p1 += p2
  30:	e51b3018 	ldr	r3, [fp, #-24]	; 0xffffffe8
  34:	e0822003 	add	r2, r2, r3              // p1 += p3
  38:	e51b301c 	ldr	r3, [fp, #-28]	; 0xffffffe4
  3c:	e0633002 	rsb	r3, r3, r2              // p4 = p1 - p4
  40:	e50b3008 	str	r3, [fp, #-8]           // p = p4
  44:	e51b3008 	ldr	r3, [fp, #-8]
  48:	e1a00003 	mov	r0, r3                  // 函数返回值r0 = p
  4c:	e24bd000 	sub	sp, fp, #0
  50:	e49db004 	pop	{fp}		; (ldr fp, [sp], #4)
  54:	e12fff1e 	bx	lr

00000058 <main>:
  58:	e92d4800 	push	{fp, lr}
  5c:	e28db004 	add	fp, sp, #4
  60:	e24dd008 	sub	sp, sp, #8
  64:	e3a03002 	mov	r3, #2
  68:	e50b3008 	str	r3, [fp, #-8]
  6c:	e3a03003 	mov	r3, #3
  70:	e54b3009 	strb	r3, [fp, #-9]
  74:	e55b3009 	ldrb	r3, [fp, #-9]
  78:	e50b3008 	str	r3, [fp, #-8]
  7c:	e51b0008 	ldr	r0, [fp, #-8]
  80:	e51b1008 	ldr	r1, [fp, #-8]
  84:	e51b2008 	ldr	r2, [fp, #-8]
  88:	e51b3008 	ldr	r3, [fp, #-8]
  8c:	ebfffffe 	bl	0 <test>
  90:	e50b0008 	str	r0, [fp, #-8]   // a = test函数返回值r0
  94:	e3a03000 	mov	r3, #0
  98:	e1a00003 	mov	r0, r3
  9c:	e24bd004 	sub	sp, fp, #4
  a0:	e8bd8800 	pop	{fp, pc}

test7

  • code
int test(int p1, int p2, int p3, int p4) {
    int p = 0;
    p = p1 + p2 + p3 - p4;
    return p;
}

int main(void) {
    int a = 2;
    char b = 3;
    a = b;
    a = test(a, a, a, a);

    for (int i = 0; i < 4; i++) {
        a = a+ i;
    }

    if (a > 100) {
        a = 200;
    } else {
        a = 1;
    }
    return 0;
}
  • 反汇编
00000000 <test>:
   0:	e52db004 	push	{fp}		; (str fp, [sp, #-4]!)
   4:	e28db000 	add	fp, sp, #0
   8:	e24dd01c 	sub	sp, sp, #28
   c:	e50b0010 	str	r0, [fp, #-16]
  10:	e50b1014 	str	r1, [fp, #-20]	; 0xffffffec
  14:	e50b2018 	str	r2, [fp, #-24]	; 0xffffffe8
  18:	e50b301c 	str	r3, [fp, #-28]	; 0xffffffe4
  1c:	e3a03000 	mov	r3, #0
  20:	e50b3008 	str	r3, [fp, #-8]
  24:	e51b2010 	ldr	r2, [fp, #-16]
  28:	e51b3014 	ldr	r3, [fp, #-20]	; 0xffffffec
  2c:	e0822003 	add	r2, r2, r3
  30:	e51b3018 	ldr	r3, [fp, #-24]	; 0xffffffe8
  34:	e0822003 	add	r2, r2, r3
  38:	e51b301c 	ldr	r3, [fp, #-28]	; 0xffffffe4
  3c:	e0633002 	rsb	r3, r3, r2
  40:	e50b3008 	str	r3, [fp, #-8]
  44:	e51b3008 	ldr	r3, [fp, #-8]
  48:	e1a00003 	mov	r0, r3
  4c:	e24bd000 	sub	sp, fp, #0
  50:	e49db004 	pop	{fp}		; (ldr fp, [sp], #4)
  54:	e12fff1e 	bx	lr

00000058 <main>:
  58:	e92d4800 	push	{fp, lr}
  5c:	e28db004 	add	fp, sp, #4
  60:	e24dd010 	sub	sp, sp, #16    // 栈顶指针开辟空间 16个
  64:	e3a03002 	mov	r3, #2
  68:	e50b3008 	str	r3, [fp, #-8]
  6c:	e3a03003 	mov	r3, #3
  70:	e54b300d 	strb	r3, [fp, #-13]
  74:	e55b300d 	ldrb	r3, [fp, #-13]
  78:	e50b3008 	str	r3, [fp, #-8]
  7c:	e51b0008 	ldr	r0, [fp, #-8]
  80:	e51b1008 	ldr	r1, [fp, #-8]
  84:	e51b2008 	ldr	r2, [fp, #-8]
  88:	e51b3008 	ldr	r3, [fp, #-8]
  8c:	ebfffffe 	bl	0 <test>
  90:	e50b0008 	str	r0, [fp, #-8]
  94:	e3a03000 	mov	r3, #0            // i = 0
  98:	e50b300c 	str	r3, [fp, #-12]    // i ----> [fp - 12](i)
  9c:	ea000006 	b	bc <main+0x64>    // 跳转到 main的bc地址处
  a0:	e51b2008 	ldr	r2, [fp, #-8]     //-------a0 
-------- a4: e51b300c ldr r3, [fp, #-12] a8: e0823003 add r3, r2, r3 // i = a +i ac: e50b3008 str r3, [fp, #-8] // i ----> [fp - 8](a),即a = i b0: e51b300c ldr r3, [fp, #-12] b4: e2833001 add r3, r3, #1 // i += 1 b8: e50b300c str r3, [fp, #-12] bc: e51b300c ldr r3, [fp, #-12] //------bc
---------- c0: e3530003 cmp r3, #3 // i - 3,根据结果置标志寄存器 Z C c4: dafffff5 ble a0 <main+0x48> // if(i <= 3),则跳转到 main的a0地址处 c8: e51b3008 ldr r3, [fp, #-8] cc: e3530064 cmp r3, #100 ; 0x64 // a - 100 d0: da000002 ble e0 <main+0x88> // if (a <= 100),则跳转到main的e0地址处 d4: e3a030c8 mov r3, #200 ; 0xc8 // else { a = 200 } d8: e50b3008 str r3, [fp, #-8] dc: ea000001 b e8 <main+0x90> // 跳转到main的e8处,不再对a进行赋值操作 e0: e3a03001 mov r3, #1 // ------- e0
-------- e4: e50b3008 str r3, [fp, #-8] // a = 1 e8: e3a03000 mov r3, #0 // ------- e8
-------- ec: e1a00003 mov r0, r3 // 函数返回值r0 = 0 f0: e24bd004 sub sp, fp, #4 f4: e8bd8800 pop {fp, pc}

你可能感兴趣的:(经验分享,Arm,arm,arm开发)