ARM汇编指令学习总结

ARM指令

影响CPSR的三种终极情形:

"目标寄存器":暂存运算结果的寄存器,离指令最近的寄存器

1.指令后面加s,并且目标寄存器是pc,不仅仅实现一个跳转

还要实现一个状态的恢复(cpsr=spsr)

movs pc, lr 此指令做两件事:

1)pc=lr,结果实现一个返回跳转

2)cpsr=spsr,结果实现一个状态的恢复

 

2.指令后面加s,但是目标寄存器不是pc, 根据运算结果影响cpsr的nzcv

subs r0, r1, #1 @r0=r1-1,根据运算结果r0里的值影响cpsr的nzcv位

 

3.比较测试指令后面不加s,运算结果照样影响cpsr的nzcv位!

cmp r0, #1 @本质做:运算结果=r0-1,结果影响cpsr的nzcv位

 

1.数据传输指令

1)MOV

Mov和mvn指令操作的立即数的范围是0x00~0xFF

MOV(MOVE)指令可完成从另一个寄存器、被移位的寄存器或将一个立即数加载到目的寄器

例如:

mov r0, #0x1F @合法

mov r0, #0x1FF @不合法

mov R0,R1 @把R1的值传到R0

Mov R3,#3 @把常数3传给R3,MOV中用#表示常数,这个值不能超过

MOVS PC,LR @把LR的值给PC实现跳转返回,由于目标寄存器是PC且指令后面加S,实现状态恢复(cpsr=spsr)

2)MVN

MVN( MOVE Negative)按位取反后再传值,比MOV多了一步取反

mvn r0, #0x0 @合法,结果:r0=0xFFFFFFFF

mvn R0, #0 @把0取反(即-1)传给R0

mvn R1,R2  @把R2的值按位取反传给R1

mvn R3,R4,LSL,#2 @把R4的值左移两位后按位取反后给R3

 

2.算数运算指令

1)ADD/ADC

add加法指令

add R0,R1,R2@R0=R1+R2

add R0,R1,#3 @R0=R1+3

add r0, r0, #1 @r0=r0+1

 

adc带进位加法指令,即除了加两个数以外,还要把CPSR的C值也要带进来

adc r0, r0, #1 @r0=r0+1+cpsr的c位

通常用于大数(超过32Bit整数)相加,这时单用ADD不能处理,必须折成两步,其中一步用ADC.

以下是做64Bit的加法

ADDS R0,R1,R2@R0=R1+R2,ADDS中S表示把进位结果写入CPSR

ADC R5,R3,R4 @R5=R3+R4+C

2)SUB/SBC

sub减法指令

SUB R0,R1,R2@R0=R1-R2

SUB R0,R1,#3 @R0=R1-3

sub r0, r0, #1 @r0=r0-1

 

sbc带进位减法指令,即除了加两个数以外,还要把CPSR的C值也要带进来,类似adc

以下是做64Bit的减法

SUBS R0,R1,R2@R0=R1-R2,SUBS中S表示把进位结果写入CPSR

SBC R5,R3,R4 @R5=R3-R4-cpsr的c位

3)MUL:MUL 乘法指令

MUL R0,R1,R2@R0=R1*R2

MUL R0,R1,#3 @R0=R1*3

 

3.位运算指令

1)ANDAND位与指令

AND R0,R1,R2@ R0=R1 & R2

AND R0,R1,#0xFF @R0=R1 & 0xFF

AND R0,R1,R2,LSL,#1 @R2左移一位,再跟R1按位与,把结果给R0,结果影响cpsr的nzcv

2)ORR:ORR位或指令

ORR R0,R1,R2@ R0=R1 | R2

ORR R0,R1,#0xFF @R0=R1 | 0xFF

3)EOR:EOR位异或指令

EOR     R0, R0, #3@ 反转R0中的位0和1

EOR     R1,R1,#0x0F@将R1的低4位取反

EOR     R2,R1,R0@R2=R1^R0

EORS    R0,R5,#0x01 @R5和0x01进行逻辑异或,结果保存到R0,结果影响cpsr的nzcv

4)BIC:BIC先取反后位与 (&= ~),清位操作

BIC   R0,R0,#0xF   @ 等同于先把0xF取反然后跟R0按位与,把结果放到R0中去

BIC   R0,R0,#%1011  @等同于先把1011取反然后跟R0按位与,把结果给R0,该指令清除 R0 中的位 0 1  3,其余的位保持;   %表示是二进制,0x表示十六进制

BICEQS PC,R0,#0X7F @因为指令带EQ,所以首先判断当前CPSR的Z位,为1执行,为0不执行,然后将0x7f按位取反后跟R0按位与,把结果给PC,因为指令后面加S,且目标寄存器是PC,所以实现状态恢复CPSR=SPSR

 

4.比较测试指令(这些指令后面不加s,运算结果照样影响cpsr的nzcv位)

1)CMP:比较指令

CMP比较两个操作数大小(减法),并把结果存入CPSR,供下一句语句使用

CMP R0,R1   @比较R0,R1大小

CMP R1,#10   @假设R1的值是10,同10做减法运算=10,影响Z位,Z位置1,MOVEQ先判断Z位,条件成立,执行 
             MOV,把R1的值给R0,因为指令后面没有加S,所以不影响标志位,Z位还是1,BLEQ判断Z位为1, 
             执行BL,跳转到test,在这之前将下一条指令保存到LR里

MOVEQ R0,R1

BLEQ test     @如果R1不等于10,MOVEQ和BLEQ不执行

……

test:

…….

2)CMN:反值比较指令

CMN比较取负的值(加法),并把结果存入CPSR,供下一句使用

CMN R0,#1 @把R0与-1进行比较

CMN R1,#5  @假设R1=-5,将-5和5做加法运算,结果为0,Z位置1,BEQ先判断Z位,Z位为1,执行B,实现跳转,执行SKIP

BEQ SKIP

…….

SKIP:

……

3)TST:位测试指令

TST 测试某一位是否为1(位与),并把结果写入CPSR,供下一句使用

与AND的区别是,TST不加S就影响CPSR

TST R1,#0xffe @等同于if(R1 & 0xffe),结果影响NZC位(TST没有溢出,不影响V位)

TST R1,#%1 @测试最低位是否为1,%表示二进制

TST R0,#0x1  @R0跟0x1做位与,用来判断R0的第0位是不是0,如果是0则影响Z位,Z=1,从而影响下面语句的实现,

ADDEQ R1,R2,R3

4)TEQ:相等测试指令

TEQ 对两个操作数进行位异或操作,并把结果写入CPSR,供下一句使用

TEQ R0,R1@比较R0和R1是否相等

 

5.加载存储指令(LDR,STR是用于寄存器和外部存储器交换数据指令(注意与MOV的区别,后面只在寄存器或常数交换))

                        任何数据的运算都必须进过CPU核的处理

“加载”:将外设的数据读取到CPU核内部的寄存器中,对应的指令为ldr

“存储”:将CPU核内部寄存器中的数据写入到外设中,对应的指令为str

切记:CPU核运算的数据来自外设,经过CPU核运算最终还要回归到外设中,否则运算毫无意义。

1)LDR(ldr(4字节)/ldrb(1字节)/ldrh(2字节)/ldrsb(有符号1字节)/ldrsh(有符号2字节))

LDR(load)用于把一个32Bit的WORD(四字节)数据从外部存储空间读取到CPU核内部寄存器中

LDR R0,[R1] @以R1寄存器的中的数据当做地址,取出4字节数据放到ARM核R0这个寄存器中 (R0=*R1)

LDR R0,[R1, #8] @将(R1的值 + 8)当成地址,把这地址里的四字节数据装入R0存储器中(R0=*(R1+8))

LDR R0,[R1,#0x8]! @先将R1的值加+8当成地址,把这个地址里的数据装入到R0里面,并将R1的值更新,即R1=R1+8

LDR R0, [R1], #8 @将R1的值当成地址,再把这个地址里的数据读入到R0,并将R1 + 8的值存入R1

2)STR((4字节)/strb(1字节)/strh(2字节))

STR R0,[R1] @将ARM核R0中的数据写入到R1存储的数据为地址的存储空间中

STR R0, [R1, #12]   @将R0里面的值放到R1的值+12为地址的存储空间中
STR R0,[R1,#12]!  @将R0里面的值放到R1的值+12为地址的存储空间中,然后更新R1,即R1=R1+12

STR R0,[R1],#12 @将R0里面的值放到R1的值作为地址的存储空间中,然后更新R1,即R1=R1+12

参考代码:

MOV R0,#0x48000000 @假设成立,R0=0x48000000,而0x48000000对应的是内存中的某个地址

LDR R1,[R0]         @以R0寄存器中的数据为地址,取出4字节数据放到ARM核R1这个寄存器中,也就是从内存 
                    的0x48000000地址取出4字节数据放到ARM核R1寄存器中

ADD R1,R1,#1        @将从内存读取的数据在CPU核内部完成一个数据运算(加法运算)

STR R1,[R0]         @将ARM核R1寄存器中的数据写入到以R0寄存器中存储的数据为地址的存储空间中,也就 
                    是将ARM核R1中的数据存储到内存的0x48000000这个地址存储空间上

6.栈操作指令

Linux系统采用的是满减栈(先调整SP后压入数据,sp指针向内存地址减小的方向变化)

ARMV7之前的ARM核采用老版本的满减栈栈操作指令:

 

STMFD:压栈指令,就是将ARM核中的数据保存到栈中

STMFD SP! {R0-R4,LR} @将ARM核中R0,R1,R2,R3,R4和LR寄存器中的数据保存到栈中,更新SP

LDMFD:出栈指令,就是将栈中的数据恢复到ARM核寄存器中

LDMFD SP! {RO-R4,PC} @从栈中将之前保存的数据恢复到ARM核中的R0,R1,R2,R3,R4和PC寄存器中,更新SP

 

ARMV7之后采用新的ARM满减栈操作指令:

 

PUSH:压栈指令,就是将ARM核中的数据保存到栈中

PUSH SP! {R0-R4,LR} @将ARM核中R0,R1,R2,R3,R4和LR寄存器中的数据保存到栈中,更新SP

POP:出栈指令,就是将栈中的数据恢复到ARM核寄存器中

POP SP! {RO-R4,PC} @从栈中将之前保存的数据恢复到ARM核中的R0,R1,R2,R3,R4和PC,更新SP

入栈从右往左开始

出栈先取数后调整SP,先进后出

 

7.跳转操作指令

ARM指令之分支跳转指令:b/bl

b:不带返回的跳转(一去不复返)

bl:带返回的跳转

切记:当CPU执行bl指令时,CPU核硬件上自动将下一条指令的地址保存

到lr寄存器中,将来返回只需调用:mov pc, lr

参考代码

地址   指令
0x0   bl xxx @CPU核执行时,lr=0x4
0x4   sub 
0x8   and 
... ...  @注意:这些指令执行完毕以后,继续向下执行
xxx:
add ...
sub ...         
mov pc, lr      @pc=lr=0x04,让CPU核跑到0x04去运行,返回到上一个sub运行

 

8.移位操作指令

LSL:左移,低位补0

LSR:右移,高位补0

ASR:右移,高位补符号位

ROR:循环右移

RRX:右移,高位补C(CPSR的C位)

9.ARM伪指令

ARM伪指令不属于ARM指令集中的指令

定义这些指令可以使ARM汇编程序设计变得更方便

汇编器会自动把一条或多条ARM指令替换ARM伪指令

1)伪指令值ADR

ADR伪指令用于加载地址

注意:相对PC,向前最多加载1020(255X4)字节,,向后最多加载1020(255X4)字节

ARD伪指令的真实指令是ADD

例如:

ADR R0,Delay @将Delay标签的地址放到R0寄存器中
….           @这些代码的数量不能超过255条指令
Delay:      @Delay标签地址等于MOV R0,#10这条指令的地址
      MOV R0,#10

反汇编查看ARD的真实指令

.text

.code 32

.global _start

_start:

    adr r0, Delay
    
    mov r1, #1

    mov r2, #2

    mov r3, #3

Delay:

    mov r4, #4

    mov r5, #5

.end

 

得到:

Disassembly of section .text: //adr.o代码段的内容如下

00000000 <_start>: //入口函数_start标签的地址为0x00000000

0: e28f0008 add r0, pc, #8

4: e3a01001 mov r1, #1

8: e3a02002 mov r2, #2

c: e3a03003 mov r3, #3



00000010 <Delay>: //Delay标签函数对应的地址为0x00000010

10: e3a04004 mov r4, #4

14: e3a05005 mov r5, #5



Disassembly of section .ARM.attributes: //ARM程序其余属性段相关内容

... //汇编器arm...as额外添加的段

 

只需分析代码段的内容即可,说明如下:

1.第一列表示指令对应的地址,一条指令4字节

2.第二列表示指令对应的机器码,地址中存储的就是机器码

机器码最终给CPU用

3.第三列就是机器码的注释说明[就是指令],给程序员看

 

结论:

1.adr最终真实的指令是add指令

2.当add执行的时候,也就是adr执行,执行的结果是add r0,pc,#12

@pc=0x08,r0=0x80+8=0x10

2)伪指令之LDR

LDR作为伪指令的使用形式有三种:

 

 

形式1        

 mov r0, #0x48000000 @不合法

 ldr r0, =0x48000000 @合法,结果:r0=0x48000000,ldr作为伪指令对立即数的范围无要求

形式2        

ldr r0,=testdata @此ldr作为伪指令,将testdata标签保存的地址给r0,也就是将分配的字节的内存空间的首 
                 地址给r0
ldr r1,[r0]      @此ldr作为真实指令,以r0保存的值为地址取出4字节数据给r1
testdata:        @类似C程序 的指针变量,保存分配的4字节内存空间的首地址

                            .int 0x12345678  @分配4字节的内存空间,并且初始化为0x1235687

形式3l

dr r0,testdata    @此ldr作为伪指令,将testdata标签保存的地址给r0,也就是将分配的字节的内存空间的 
                  首地址给r0
…….
testdata:         @类似C程序 的指针变量,保存分配的4字节内存空间的首地址
.int 0x12345678   @分配4字节的内存空间,并且初始化为0x1235687      

            

经典代码:        

ldr pc,jump_table   @将jmp_table的标签内容给pc,结果是pc=main,让cpu核跑到main函数中运行
jmp_table:
         .int main  @分配四字节内存空间,保存main函数的地址

3)NOP伪指令

NOP伪指令在汇编时将会被替代成ARM中的空操作,比如可能是“MOV R0,R0”指令等。NOP可用于延时操作

 

10.ARM伪操作

伪操作(.开头)不参与程序的运行,起到一个标识的作用,共121个

——常量定义伪操作

——符号声明伪操作

——数据定义伪操作

——汇编控制伪操作

——信息控制伪操作

——其他伪操作

例如:

.equ PI, 3.14 @类似:#define PI (3.14)

.byte 250 分配1字节内存空间并且初始化为250

.space 4096 分配4096字节的内存空间

.skip 4096 分配4096字节的内存空间

.ascii "hello,world\0" 分配内存空间并且初始化为"hello,world"

.asciz "hello,world" 分配内存空间并且初始化为"hello,world"

问:

.byte/.short/.hword/.int/.word/.long/.space/

.skip/.ascii/.asciz/.string/他们都是用来分配内存

汇编程序如何获取到他们分配内存空间的首地址呢?

只有将来获取首地址汇编程序就可以随意访问了

答:只需给这些伪操作添加一个标签即可

切记:标签的地址就是分配的内存空间的首地址

str1:

    .asciz "hello"

str2:

    .asciz "world"

mem_sapce:

    .space 4096

案例:利用汇编实现两个字符串的比较

回顾:C实现字符串比较

int my_strcmp(const char *str1, const char *str2)

{

    while(*str1) {

    if(*str1 != *str2)

    return *str1 - *str2;

    str1++;

    str2++;

     }

    return *str1 - *str2;

}

汇编实现字符串比较函数的流程:

.text

.arm @等价于.code 32

.global _start

_start:

    ldr r0, =str1 @r0指向“hello”

    ldr r1, =str2 @r1指向“hfflo”

loop:             @字符串都是存于内存,将来运算必须在CPU核内部

    ldrb r2, [r0], #1 @取出一个字符

    ldrb r3, [r1], #1 @取出一个字符

    cmp r2, #0

    beq loop_end

    cmp r2, r3

    beq loop

loop_end:

    sub r0, r2, r3

b .





str1:@str1指向"hello",字符串都是存在于内存中

    .asciz "hello"

str2:@str2指向"hfllo",字符串都是存于内存中

    .asciz "hfllo"

.end

最后观察r0值:

=0:表示相等

>0:表示str1>str2

<0:表示str1<str2

猜你喜欢

转载自blog.csdn.net/isco22/article/details/87967173