ARM②——汇编指令&寻址方式
ARM②——汇编指令&寻址方式
一、汇编指令概述
1、汇编指令一般格式:
MOV r0 , #10 //将立即数10 存入 r0寄存器 MOV r0 , r1 //将r1寄存器的值 存入到 r0寄存器2、立即数
- 去除右边偶数个0,最后剩下的数小于等于8个2进制位,则为立即数。
- Shift amount:移位的次数
- Shift:移位方式
得出结论:一个数是否是立即数,取决于是否能移位(去除偶数个0),移位后的二进制数小于等于8位则为立即数
3、不是立即数的地址
当我们要使用一些地址的时候,但这个地址不是立即数,这时候就无法将地址存入寄存器内,那么这种情况下,就需要用到伪指令的方法来实现。
ldr r0 , = 0xffffff 这个方法在编译的时候,会把伪指令拆分成多条指令运行,从而达到目的。
二、汇编指令的使用
算数、逻辑运算指令
1、MOV 赋值(移动)
搬移指令,将立即数或某个寄存器的值存入到目标寄存器
MOV r0 , #3 MOV r1 , r2 MOV r3 , r4 , LSL#1 //搬移cpsr 和 spsr cpsr spsr mrs r0, cpsr and r0,#0x1 msr cpsr, r0 //r0 = cpsr 把cpsr的值获取出来 //cpsr = r02、AND 与运算
AND{条件}{S} <dest>, <op 1>, <op 2> AND R0, R0, #3; R0 = 保持 R0 的位 0 和 1,丢弃其余的位。3、ADD 加法
ADD R0, R1, R2 ; R0 = R1 + R2 ADD R0, R1, #256 ; R0 = R1 + 256 ADD R0, R2, R3,LSL#1 ; R0 = R2 + (R3 << 1)4、ADC 带进位的加法
ADDS R0, R4, R8 ; 加低端的字 ADCS R1, R5, R9 ; 加下一个字,带进位 ADCS R2, R6, R10 ; 加第三个字,带进位 ADCS R3, R7, R11 ; 加高端的字,带进位
特殊情况: 当做加法的时候,一个数大于32位,不能存入到寄存器内,那么这时候就可以把这个数,用两个寄存器来存储,(例如:r0存高位数值,r1存低位数值)
.global _start _start: ldr r0 , =0xffffffff mov r1 , #1 mov r2 , #1 mov r3 , #0 adds r4 , r0 , r2; 加低位的值 (如果溢出,会将进位标志自动置1) adcs r5 , r1 , r3; 带进位加法,加到高位值 (会自动取出进位标志位的状态,如果进位了,会变成r1 + r3 + 1(进位标志))5、SUB & SBC
SUB 减法运算,SBC带借位的减法
SUB R0, R1, R2 ; R0 = R1 - R2 SUB R0, R1, #256 ; R0 = R1 - 256 SUB R0, R2, R3,LSL#1 ; R0 = R2 - (R3 << 1) SBC{条件}{S} , <op 1>, <op 2>
dest = op_1 - op_2 - !carry;
6、BIC 清除指定位的值(置0)
BIC R0, R0, #%1011 ; 清除 R0 中的位 0、1、和 3。保持其余的不变。BIC 真值表 :
Op_1 Op_2 结果 0 0 0 0 1 0 1 0 1 1 1 0 译注:逻辑表达式为 Op_1 AND NOT Op_27、ORR 或运算
ORR R0, R0, #3 ; 设置 R0 中位 0 和 1OR 真值表(二者中存在 1 则结果为 1):
Op_1 Op_2 结果 0 0 0 0 1 1 1 0 1 1 1 18、EOR 异或运算
相同为0 相异为1
EOR R0, R0, #3 ; 反转 R0 中的位 0 和 1EOR 真值表(二者不同则结果为 1):
Op_1 Op_2 结果 0 0 0 0 1 1 1 0 1 1 1 09、MVN : 传送取反的值
MVN从另一个寄存器、被移位的寄存器、或一个立即值装载一个值到目的寄存器。不同之处是在传送之前位被反转了,所以把一个被取反的值传送到一个寄存器中。这是逻辑非操作而不是算术操作,这个取反的值加 1 才是它的取负的值MVN R0, #4 ; R0 = -5 MVN R0, #0 ; R0 = -1
移位运算
1、逻辑左移、逻辑右移
R0, LSR #1; 右移1位 R0, LSR R1; 右移R1里面存的数个位 R1, LSL #3; 左移3位2、循环右移
tips:普通移位运算会丢失低位或者高位的数据,但是循环移位不会,比如右移:低位移出会自动补齐到高位。R0, ROR #3; R1, ROR R1;
乘法指令
MUL 乘法
MUL{条件}{S} <dest>, <op 1>, <op 2> dest = op_1 * op_2MLA 带累加的乘法
MLA{条件}{S} <dest>, <op 1>, <op 2>, <op 3> dest = (op_1 * op_2) + op_3
比较、跳转指令
CMP 比较指令格式
CMP{条件}{P} <op 1>, <op 2> status = op_1 - op_2B 跳转指令
b flag @跳转到flag标识处 flag:BL 可以返回的跳转指令(跳转时会将当前运行指令的地址存入LR寄存器,方便后续更改PC实现返回操作)
示例:比较R0 R1cmp r0 , r1; movgt r2 , r0; movlt r2 , r1; 等价于: if(r0 > r1) { r2 = r0; } else { r2 = r1; }
举一反三:if(r0 == 2 || r1 == 1) { r2 = 1; } else { r2 = 0; } //利用汇编写出分支语句
汇编实现:.global _start _start: cmp r0 , #2 beq equal @满足eq 等于条件 跳转到equal cmp r1 , #1 beq equal @满足eq 跳转 bne notEqual @不满足跳转到Not equal equal: mov r2 , #1; notEqual: mov r2 , #0;函数示例
写一个函数 func 计算两个数的和(r0 , r1) 存到r2中,并且返回调用函数的地方
.global _start _start: BL func @BL跳转后 会将LR的值更新,LR = PC + 1 也就是第4行 B end func: ADD R2 , R0 , R1 MOV PC , LR end: nop @虽然实现了基本的函数功能,但是没有实现实参和形参的区分,不够安全,还需要结合sp 出入栈来实现函数调用前后实参的安全性
关于栈的讲解(连续空间,栈专用)
详细分为:
- 空递增栈
- 空递减栈
- 满递增栈
- 满递减栈(重点:Linux常用)
决定栈的类型:(1
4 和 58 可以互相替换使用的,常用5~8)
- STM 入栈
- LDM 出栈
示例:STMFD R13!, {R0, R1} @R13是LR寄存器,这里是满递减栈 LDMFD R13!, {R1, R0} @出栈顺序与入栈顺序相反(栈的特性)
非栈区空间(可用于数组等结构的实现)
- STR——将Rx 写入[ ]空间内
- LDR——从[ ] 空间取数值
示例讲解:LDR R0 , [R1 , #4]; @等同于 r0 = *(r1 + 4); C语言的指针偏移量操作 STR R0 , [R1 , #4]; @等同于 *(r1 + 4) = r0;
三、寻址方式
1、立即数寻址
MOV R0 , #10 @R0 = 10 , 操作数2是立即数就是立即数寻址2、寄存器寻址
MOV R1 , R2 @R1 = R2 , 操作数2为寄存器3、寄存器移位寻址
MOV R1 , R2 ,LSL #4 @R2左移4位后给到R14、寄存器间接寻址
MOV R1 , [R2] @将R2地址的数值取出来给R15、基址变址寻址
LDR R0 , [R1 , #4] @R0 = *(R1 + 4) LDR R0 , [R1 , #4]! @R1 += 4 , R0 = *R1 LDR R0 , [R1] , #4 @R0 = *R1 , R1 += 46、多寄存器寻址
LDMxx R0 , [R1-R5] @同时对R1到R5 5个寄存器操作 存储到R1-R5中(从R0地址开始) STMxx R0 , [R1-R5]7、相对寻址
B flag @跳转到flag 属于相对寻址8、堆栈寻址
STMFD SP! , {LR , R0-R12} LDMFD SP! , {R0-R12 , PC}
四、伪指令
.byte 单字节定义 .byte 0x12,’a’,23
.short 定义双字节数据 .short 0x1234,65535
.long /.word 定义4字节数据 .word 0x12345678
.quad 定义8字节 .quad 0x1234567812345678
.float 定义浮点数 .float 0f3.2
.string/.asciz/.ascii 定义字符串 .ascii “abcd\0”
number: .word 4
arr: .word 4,5,6,7,8
stack: .space 20
.text .text {subsection} 将定义符开始的代码编译到代码段
.data .data {subsection} 将定义符开始的代码编译到数据段,初始化数据段
.bss .bss {subsection} 将变量存放到.bss段,未初始化数据段
.global/ .globl :用来声明一个全局的符号
.end 文件结束 start 汇编程序的缺省入口是 start标号,用户也可以在连接脚本文件中用ENTRY标 志指明其它入口点
五、综合案例代码
.global _start @将ARR4个数(数组),分别存入R0~R3寄存器 _start: LDR R4 , =arr LDMIA R4 , {R0-R3} NOP NOP ARR: .word 3,4,5,6
汇编实现: 1~100的和.global _start _start: MOV R1 , #1 @1到100的累加和 MOV R0 , #0 getSum: ADD R0 , R0 , R1 @存储器R0存储和 , R1控制循环变量 ADD R1 , R1 , #1 CMP R1 , #101 blt getSum @小于101跳转到getSum(循环) nop
gdb调试运行
汇编实现:冒泡排序.text .global _start _start: MOV R0 , #0 ;@R0控制外层循环 MOV R10 , #4 ; LDR R2 , =ARR ; outside: CMP R0 , #4 ;@R0 == 4 跳出循环 (5-1次循环) BEQ END ;@跳转到END 结束循环 MOV R1 , #0 ;@给内层循环R0赋初值 inside: MUL R3 , R1 , R10 ;@数组偏移量 ARR[R2+R3] ADD R4 , R3 , #4 ;@ARR[R2+R3+1] CMP R1 , #3 ;@判断是否结束内层循环 BGT out_count ;@跳转到外层循环自增 LDR R5 , [R2,R3] ; LDR R6 , [R2,R4] ; CMP R5 , R6 ;@比较R5和R6(ARR[i] > ARR[i+1]) BGT swap ;@跳转到交换 B in_count ;@不用交换直接跳转到自增 swap: STR R5 , [R2,R4] ; STR R6 , [R2,R3] ; in_count: ADD R1 , R1 , #1 ;@内层循环自增1 B inside ; out_count: ADD R0 , R0 , #1 ;@外层循环变量自增1 B outside ; ARR: .word 13,5,7,9,10 ;@数组 END: LDMIA R2 , {R3-R7} ; NOP
gdb调试运行
查看6道真题和解析