一、汇编学习:可以向上理解软件、向下感知硬件
二、符号(注释)
@ | 注释 |
# | 注释(放在行首表示注释一行) |
/* */ | 注释 |
#+数字 | 立即数 |
: | 一种标号(比如main: loop:) |
.text .end+换行 | 固定格式 |
三、ARM指令格式和立即数
ARM指令构成 | ARM 指令包含操作码和一些其他的信息,只剩下8 位存放数据 具体来说,一个 ARM 指令通常由以下部分组成:
其中,操作码、条件码、寄存器索引和位移量总共占据了大部分的指令位数,因此,一个 ARM 指令中只有少部分(通常为 8 位)是用来表示立即数的。 例如,一个 ARM 指令可能是这样的格式: Opcode Condition Register1 Register2 Offset/Displacement #0 |
一般指令格式 | 操作码 目的操作数 源操作数 mov r0, #5 |
立即数的优点 | 取指的时候,可以直接读到cpu 不需要单独去内存读取,速度快 |
立即数的缺点 | 不能是任意的32位数字,有局限,且只能放在指令右边 |
四、如何判断立即数合法
转化为二进制编码,观察第一个1和最后一个1之间的位数(包含这两个1) 1、将数据转换为 二进制编码 4 个一组 |
eg1:0xF000 000F 1111 0000 0000 0000 0000 0000 0000 1111 -- 1111 1111合法 |
eg2:0x123 ... 0000 0001 0010 0011 -- 01 0010 0011不合法 |
eg3:0x234 .... 0010 0011 0100合法 |
eg4:0x3F0 ... 0011 1111 0000合法 |
五、伪指令
伪指令不是指令
伪指令和指令的根本区别 | 编译后,是否生成机器码(伪指令不会生成) |
伪指令的意义 | 指导编译的过程(如果需要执行伪指令,必须先翻译成在指令) 比如.text .end 也是伪指令 |
伪指令和编译器相关 | 我们用到是gnu工具链,因此我们学习的是gnu 环境下的汇编伪指令 |
常用伪指令 | ldr //大范围地址的加载指令 ldr (load register) 将内存内容加载进入通用寄存器 |
eg: ldr r0, =0x12345678 num : .word 0x15300000 //int num = 0x15300000 buf1: .byte 1, 2, 3, 4 //char buf1[4] = {1,2,3,4}; |
六、指令--搬移、偏移指令
mov | 搬移 mov r2, r1 // r2 = r1 mov r0, #7 //r1 = 7 |
mvn | 取反搬移 mvn r0, r1 // r0 = ~r1 |
ldr | 向寄存器存入非立即数可以用伪指令 |
LSL | 左移位 |
LSR | 右移位 |
ASL | 算术左移位(位数不够补符号位) |
ASR | 算术右移位(位数不够补符号位) |
ROR | 循环移位 |
RRX | 带扩展的循环右移 |
偏移指令总结 | mov r0,r2, lsr #3 //r0 = r2 >> 3 mov r1 ,r0,lsl #1 //r1 = r0 << 1 add r2, r1,r0,lsr #2 // r2 = r1 + r0 >> 2 LSL '逻辑左移' (Logical shift left) LSR '逻辑右移'(Logical shift right) ASL '算术左移' (Arithmet shift left ) ASR '算术右移'(Arithmet shift right ) ROR '循环右移' RRX '带扩展的循环右移' |
重要例子 | tst r0, #0x8 @判断某个数的某几位 是否为 0 tst r0, #0x8 表示判断r0的第三位(8的二进制1000,也就是从右往左的第四位(0 1 2 3))是否为0 eq ne表示相等/不相等 |
搬移
练习:
a) r0 = 16
b) r0 = r1/16
c) r1 = r2 *2
d) r0 = -4
e) r1 = r0/2
答:
mov r0, #16
mov r1, #32
mov r2,r1, LSR #4
mov r2, #4
mov r3, r2, LSL #1
mov r0, #-4
mov r1,r0, ASR #1
偏移(移位)
.text @基本格式的头
@搬移指令
mov r0, #3 @搬移指令: r0 = 3 ; #3 立即数
ldr r0, =0xfff00000 @ldr 伪指令 r0 = 0xfff
mov r1,r0 @搬移指令 r1 = r0
@移位
mov r0, #0x4
mov r1, r0, LSL #1 @r1=r0 << 1 , 逻辑左移: 高位移除部分舍去,低位不够补0
ldr r0, =0xffffffff
mov r2, r0, LSR #1 @r2=r0 >>1 逻辑右移,低位移除部分舍去, 高位部分不够补 0
mov r3, r0, ASR #1 @r3 = r0 >> 1 算术右移,低位移除部分舍去 高位不够 补 符号位
ldr r0, =0x7000000f
mov r4,r0, ROR #1 @r4 = r0 >>1 , 循环右移, 低位移除部分补到 高位
ldr r0, =0xff0000ff
mvn r5,r0 @r5 = ~r0
.end @基本格式的尾巴 通常 该行下边 加空行
七、指令--位操作 AND ORR BIC EOR
and | 与 |
orr | 或 |
bic | 按位清零(比如说0x4 就表示第2位(从0开始数)清零) bic r0,r1,#0x4 //r0 = r1 &(~0x4 ) |
eor | 异或 |
.text @基本格式的头
@逻辑指令
mov r0, #6
mov r1, #5
and r2, r0,r1 @r2 = r1 & r0
orr r3, r0,r1 @r3 = r1 | r0
eor r4, r0,r1 @r4 = r1 ^ r0
ldr r0,=0x12345678
bic r1, r0, #0xff
bic r2,r0, #0xf0000000
tst r0, #0x8 @判断某个数的某几位 是否为 0
@ movne r11, #1
moveq r11, #1 @当上一条判断结果相等时 执行 mov
movne r12, #2 @当上一条判断结果不相等时执行mov
@使能中断和快速中断(设置cpsr的值)
mrs r0, cpsr @ r0 = cpsr
bic r1, r0, #0xc0
msr cpsr, r1 @cpsr = r1
.end @基本格式的尾巴 通常 该行下边 加空行
八、指令--比较指令、测试指令
用法注意: cmp moveq movne |
cmp | cmp r1, r0 //改变 cpsr 的 NZ moveq r2, #0 //相当于 if (r1 == r0) r2 = 0(这是两行代码) |
cmn | |
关系符号 | eq == ge >= gt > le <= lt < ne != |
tst | 实质是做 与运算, 通常用于 测试某一位或几位是0 还是1 结果 CPSR 'Z' 位来判断, Z 位位 1 表明结果为0 tst r0, #0x10 //测试 第4位是否为 0, if((r0 &(0x10)) == 0) |
teq | 实质是异或运算 测试两个 数是否相等,如果两个数相等或者异或结果 为 0 修改 cpsr 的z 位 判断 teq r0, r1 // if((r0 ^ r1 ) == 0) |
.text @基本格式的头
@比较指令
/*
int a = 5;
if(a >= 5)
a = 0
else
a = 100
*/
/*
mov r0, #5
cmp r0, #5 @比较指令 , 比较 r0 和 5
movge r0 , #0 @当cmp的结果 >= 时, r0 = 0
movlt r0, #100 @当cmp的结果 是 < 时, r0 = 100
*/
.end @基本格式的尾巴 通常 该行下边 加空行
九、指令--算术指令 :ADD ADC SUB SBC RSB RSC
add | 相加 add r2, r1,#3 //r2 = r1 + 4 |
adds | 会改变条件位的相加(配合adc使用) adds r0, r1, r2 // r0 = r1 + r2 && (cpsr ) v c CPSR:进位 -- c置为1 溢出 -- v置为1 补充--溢出:在两个正数相加时,如果结果超过了机器所能表示的最大正数,那么就发生了上溢。同样地,在两个负数相加时,如果结果小于机器所能表示的最小负数,那么就发生了下溢 |
adc | 带进位的加法(常用于64位加法,配合adds使用) adc r0, r1, r2 //r0 = r1 + r2 + (cpsr) c |
sub | 相减 sub r1, r2, r3 |
subs | 影响cpsr的条件位 没有借位 时 cpsr 'c' 位 置1 当有借位 c = 0 |
sbc | sbc 带借位的减法 |
rsb | rsb 逆向减法 rsb r1,r2, r3 //r1 = r3 - r2 |
mul | 乘法指令 mul r2, r0, r1 // r2 = r0 * r1 |
练习:64位加减法(高32位 低32位)
.text @基本格式的头
@算术指令
mov r0, #10
mov r1, #5
add r2, r0, r1 @r2 = r0 + r1
ldr r0, =0xff000000
ldr r1, =0xf0000000
@add r2, r0, r1 @(不带进位加法)
adds r2, r0, r1 @r2 = r0 + r1 , 如果有进位 那么会修改cpsr的进位值
mov r3, #1
mov r4, #1
adc r5,r3,r4 @r5 = r3 + r4 + 进位值
@64 bit 两个数的加法 r0, r1 表示被加数, r2, r3表示加数
@64 bit 两个数的减法
ldr r0, =0xffffffff @低32 bit
ldr r1, =0x1 @高32bit
ldr r2,=0x1 @低32bit
ldr r3,=0x5 @高32bit
adds r4, r0, r2
adc r5, r1, r3
subs r6, r2,r0
sbc r7, r3,r1
@add r2, r0, r1 @不带进位 不能计算 64 bit
.end @基本格式的尾巴 通常 该行下边 加空行
十、条件码(eq、ne、lt小于、gt大于)
十一、跳转
b 类似 goto bl 跳转之前 先保存 下一条指令的地址 bl: lr(r14) = pc (r15) -4 (由cpu 帮我们执行 ) |
练习:函数跳转
int main(void ) func2(int a) |
.text @基本格式的头
@跳转练习
main:
mov r0, #0
mov r1, #2
bl func1
main_end:
b main_end @死循环
func1:
cmp r1, #2
bleq func2
blne func3
func1_end:
func2:
add r1, #3
b func2_end
func2_end:
mov pc, lr
func3:
@.....字节补充一下
func3_end:
.end @基本格式的尾巴 通常 该行下边 加空行
练习:1加到100
.text @基本格式的头
@跳转指令
/*
mov r0, #1
mov r1, #10
b t @跳转指令, 跳到 t 后执行
add r2, r0, r1
t:
sub r3, r0, r1
@b t
*/
@实现 1-100的累加 和
/*
for(int i=1; i<=100; i++)
{
static int sum += i;
}
*/
mov r0, #0 @类似 sum 累加和
mov r1, #1 @类似 i 循环变量
loop:
@循环计算
cmp r1, #100
bgt loop_end @这句 相当于 判断 i 大于 100 跳出循环
add r0,r1 @相当于 sum += i;
add r1, #1 @相当于 i++
b loop @继续下次循环
loop_end:
@结束
mov r12,r0 @把计算结果 放到 r12
.end @基本格式的尾巴 通常 该行下边 加空行
练习:延时1s
.text @基本格式的头
@跳转指令
@实现延时 1 s
ldr r0, = 0x1f1fffff @具体多少为 1秒 需要计算
loop:
cmp r0, #0
beq loop_end
sub r0,#1
b loop
loop_end:
mov r12, #0
.end @基本格式的尾巴 通常 该行下边 加空行
十二、修改ARM状态和模式 ???
.text @基本格式的头
@判断 当前工作状态是否 是ARM 状态 ,如何切换到 user 模式或其他模式
mrs r0, cpsr @r0 = cpsr
mov r1, #0x20 @0010 0000 T 位
orr r1, r1, #0x1000000 @J 位, 结果r1把 T位和 J位置为1 ,其余为0
tst r0, r1 @判断 r0中的J和 T 是否为 0
biceq r0, #0x1f @把 mode 清0
orreq r0, #0x10 @把mode 变为 user模式: 10000
msr cpsr, r0 @cpsr = r0
.end