跳转与储存器访问指令
汇编中的符号
1.指令: 能够编译生成一条32位的机器码,且能被CPU识别和执行
2.伪指令:本身不是指令,编译器可以将其替换成若干条等效指令
3.伪操作:不会生成代码,只是在编译之前告诉编译器怎么编译
ARM指令
1.数据处理指令: 数学运算、逻辑运算
2.跳转指令: 实现程序的跳转,本质就是修改了PC寄存器
3.Load/Srore指令: 访问(读写)内存
4.状态寄存器传送指令:访问(读写)CPSR寄存器
5.软中断指令: 触发软中断异常
6.协处理器指令: 操控协处理器的指令
1. 指令
指令是什么?
能够编译成一条32bit机器码,并且能被CPU识别和执行
1.1 数据处理指令: 数学运算、逻辑运算
.text @表示当前段为代码段
.global _start @声明_start为全局符号
_start: @汇编程序的入口
@ 数据搬移指令
@ MOV R1,#1
@ R1 = 1
@ MOV R2,R1
@ R2 = R1
@ MVN R0, #0xFF
@ R0 = ~0xFF
@ 立即数
@ 立即数的本质是包含在指令当中的数,属于指令的一部分
@
@ 优点:
@ 取指的时候就可以将其读取到CPU,不用单独去内存取,速度快
@ 缺点:
@ 不能是任意的32位的数字,由局限性
@
@ MOV R0, #0x12345678 (一个数值应该只占指令的一部分,而这里的数值是32bit,不是立即数)
@ MOV R0, #0x12
@ MOV R0, #0xFFFFFFFF (算是一个伪指令,编译器会把这个编译成CPU认识的,并且是等效效果)
@ 编译器替换
@ MOV R0, #0xFFFFFFFF
@ 数据运算指令基本格式
@ 《操作码》《目标寄存器》《第一操作寄存器》《第二操作数》
@ 操作码 指示执行哪种运算
@ 目标寄存器: 存储运算结果
@ 第一操作寄存器:第一个参与运算的数据(只能是寄存器)
@ 第二操作数: 第二个参与运算的数据(可以是寄存器或立?词?
@ 加法指令
@ MOV R2, #5
@ MOV R3, #3
@ ADD R1, R2, R3
@ R1 = R2 + R3
@ ADD R1, R2, #5
@ R1 = R2 + 5
@ 减法指令
@ SUB R1, R2, R3
@ R1 = R2 - R3
@ SUB R1, R2, #3
@ R1 = R2 - 3
@ 逆向减法指令
@ RSB R1, R2, #3
@ R1 = 3 - R2
@ 乘法指令
@ MUL R1, R2, R3
@ R1 = R2 * R3
@ 乘法指令只能是两个寄存器相乘
@ 按位与指令
@ AND R1, R2, R3
@ R1 = R2 & R3
@ 按位或指令
@ ORR R1, R2, R3
@ R1 = R2 | R3
@ 按位异或指令(相同为0,相异为1)
@ EOR R1, R2, R3
@ R1 = R2 ^ R3
@ 左移指令
@ MOV R2, #0xF0
@ MOV R3, #0x2
@ LSL R1, R2, R3
@ R1 = (R2 << R3)
@ 右移指令
@ LSR R1, R2, R3
@ R1 = (R2 >> R3)
@ 位清零指令
@ MOV R2, #0xFF
@ BIC R1, R2, #0x0F
@ 第二操作数中的哪一位为1,就将第一操作寄存器的中哪一位清零,然后将结果写入目标寄存器
@ 格式扩展
@ MOV R2, #3
@ MOV R1, R2, LSL #1
@ R1 = (R2 << 1)
@ 数据运算指令对条件位(N、Z、C、V)的影响
@ 默认情况下数据运算不会对条件位产生影响,在指令后加后缀”S“才可以影响!!!!!!!
@ MOV R2, #3
@ SUBS R1, R2, #5
@ MOV R2, #0xFFFFFFFF
@ ADDS R1, R2, #3
@ MOV R2, #0x7FFFFFFF
@ ADDS R1, R2, #1
@ 带进位的加法指令
@ 两个64位的数据做加法运算
@ 第一个数的低32位放在R1
@ 第一个数的高32位放在R2
@ 第二个数的低32位放在R3
@ 第二个数的高32位放在R4
@ 运算结果的低32位放在R5
@ 运算结果的高32位放在R6
@ 第一个数
@ 0x00000001 FFFFFFFF
@ 第二个数
@ 0x00000002 00000005
@ MOV R1, #0xFFFFFFFF
@ MOV R2, #0x00000001
@ MOV R3, #0x00000005
@ MOV R4, #0x00000002
@ ADDS R5, R1, R3
@ ADC R6, R2, R4
@ 本质:R6 = R2 + R4 + 'C'
@ 带借位的减法指令
@ 第一个数
@ 0x00000002 00000001
@ 第二个数
@ 0x00000001 00000005
@ MOV R1, #0x00000001
@ MOV R2, #0x00000002
@ MOV R3, #0x00000005
@ MOV R4, #0x00000001
@ SUBS R5, R1, R3
@ SBC R6, R2, R4
@ 本质:R6 = R2 - R4 - '!C'
.global STOP
STOP:
B STOP @死循环,防止程序跑飞
.end @汇编程序的结束
1.2 跳转指令:实现程序的跳转,本质就是修改了PC寄存器
.text @表示当前段为代码段
.global _start @声明_start为全局符号
_start: @汇编程序的入口
@ 方式一:直接修改PC寄存器的值(不建议使用,需要自己计算目标指令的绝对地址)
@ MAIN:
@ MOV R1, #1
@ MOV R2, #2
@ MOV R3, #3
@ MOV PC, #0x18
@ MOV R4, #4
@ MOV R5, #5
@ FUNC:
@ MOV R6, #6
@ MOV R7, #7
@ MOV R8, #8
@ 方式二:不带返回的跳转指令,本质就是将PC寄存器的值修改成跳转标号下指令的地址
@ MAIN:
@ MOV R1, #1
@ MOV R2, #2
@ MOV R3, #3
@ B FUNC
@ MOV R4, #4
@ MOV R5, #5
@ FUNC:
@ MOV R6, #6
@ MOV R7, #7
@ MOV R8, #8
@ 方式三:带返回的跳转指令,本质就是将PC寄存器的值修改成跳转标号下指令的地址,同时将跳转指令下一条指令的地址存储到LR寄存器
@ MAIN:
@ MOV R1, #1
@ MOV R2, #2
@ MOV R3, #3
@ BL FUNC (这里是 BL, B不能让LR保存返回地址)
@ MOV R4, #4
@ MOV R5, #5
@ FUNC:
@ MOV R6, #6
@ MOV R7, #7
@ MOV R8, #8
@ MOV PC, LR
@ 程序返回
@ ARM指令的条件码
@ 比较指令
@ CMP指令的本质就是一条减法指令(SUBS),只是没有将运算结果存入目标寄存器
@ MOV R1, #1
@ MOV R2, #2
@ CMP R1, R2
@ BEQ FUNC
@ 执行逻辑:if(EQ){B FUNC} 本质:if(Z==1){B FUNC}
@ BNE FUNC
@ 执行逻辑:if(NQ){B FUNC} 本质:if(Z==0){B FUNC}
@ MOV R3, #3
@ MOV R4, #4
@ MOV R5, #5
@ FUNC:
@ MOV R6, #6
@ MOV R7, #7
@ ARM指令集中大多数指令都可以带条件码后缀
@ MOV R1, #1
@ MOV R2, #2
@ CMP R1, R2
@ MOVGT R3, #3
@ 练习:用汇编语言实现以下逻辑
@ int R1 = 9;
@ int R2 = 15;
@ START:
@ if(R1 == R2)
@ {
@ STOP();
@ }
@ else if(R1 > R2)
@ {
@ R1 = R1 - R2;
@ goto START;
@ }
@ else
@ {
@ R2 = R2 - R1;
@ goto START;
@ }
@ 练习答案
@ MOV R1, #9
@ MOV R2, #15
@ START:
@ CMP R1,R2
@ BEQ STOP
@ SUBGT R1, R1, R2
@ SUBLT R2, R2, R1
@ B START
@ STOP:
@ B STOP
.global STOP
STOP:
B STOP @死循环,防止程序跑飞
.end @汇编程序的结束
1.3 Load/Store指令:访问(读写)内存
.text @表示当前段为代码段
.global _start @声明_start为全局符号
_start: @汇编程序的入口
@ 写内存
@ MOV R1, #0xFF000000
@ MOV R2, #0x40000000
@ STR R1, [R2]
@ 将R1寄存器中的数据写入到R2指向的内存空间
@ 读内存
@ LDR R3, [R2]
@ 将R2指向的内存空间中的数据读取到R3寄存器
@ 读/写指定的数据类型
@ MOV R1, #0xFFFFFFFF
@ MOV R2, #0x40000000
@ STRB R1, [R2]
@ 将R1寄存器中的数据的Bit[7:0]写入到R2指向的内存空间
@ STRH R1, [R2]
@ 将R1寄存器中的数据的Bit[15:0]写入到R2指向的内存空间
@ STR R1, [R2]
@ 将R1寄存器中的数据的Bit[31:0]写入到R2指向的内存空间
@ LDR指令同样支持以上后缀
@ 寻址方式就是CPU去寻找操作数的方式
@ 立即寻址
@ MOV R1, #1 @ 1是立即数
@ ADD R1, R2, #1 @ 算加法时会用到1,而1被放在了机器码里,CPU就会去机器码里把1取出来
@ 寄存器寻址
@ ADD R1, R2, R3
@ 寄存器移位寻址
@ MOV R1, R2, LSL #1
@ 寄存器间接寻址
@ STR R1, [R2]
@ ...
@ 基址加变址寻址
@ MOV R1, #0xFFFFFFFF
@ MOV R2, #0x40000000
@ MOV R3, #4
@ STR R1, [R2,R3]
@ 将R1寄存器中的数据写入到R2+R3指向的内存空间
@ STR R1, [R2,R3,LSL #1]
@ 将R1寄存器中的数据写入到R2+(R3<<1)指向的内存空间
@ 基址加变址寻址的索引方式
@ 前索引
@ MOV R1, #0xFFFFFFFF
@ MOV R2, #0x40000000
@ STR R1, [R2,#8]
@ 将R1寄存器中的数据写入到R2+8指向的内存空间
@ 后索引
@ MOV R1, #0xFFFFFFFF
@ MOV R2, #0x40000000
@ STR R1, [R2],#8
@ 将R1寄存器中的数据写入到R2指向的内存空间,然后R2自增8
@ 自动索引
@ MOV R1, #0xFFFFFFFF
@ MOV R2, #0x40000000
@ STR R1, [R2,#8]!
@ 将R1寄存器中的数据写入到R2+8指向的内存空间,然后R2自增8
@ 以上寻址方式和索引方式同样适用于LDR
@ 多寄存器内存访问指令
@ MOV R1, #1
@ MOV R2, #2
@ MOV R3, #3
@ MOV R4, #4
@ MOV R11,#0x40000020
@ STM R11,{R1-R4}
@ 将R1-R4寄存器中的数据写入到以R11为起始地址的内存空间中
@ LDM R11,{R6-R9}
@ 将以R11为起始地址的内存空间中的数据读取到R6-R9寄存器中
@ 当寄存器编号不连续时,使用逗号分隔
@ STM R11,{R1,R2,R4}
@ 不管寄存器列表中的顺序如何,存取时永远是低地址对应小编号的寄存器
@ STM R11,{R3,R1,R4,R2}
@ 自动索引照样适用于多寄存器内存访问指令
STM R11!,{R1-R4}
@ 多寄存器内存访问指令的寻址方式
@ MOV R1, #1
@ MOV R2, #2
@ MOV R3, #3
@ MOV R4, #4
@ MOV R11,#0x40000020
@ STMIA R11!,{R1-R4}
@ 先存储数据,后增长地址 Increase After
@ STMIB R11!,{R1-R4}
@ 先增长地址,后存储数据 Increase Before
@ STMDA R11!,{R1-R4}
@ 先存储数据,后递减地址 Decrease After
@ STMDB R11!,{R1-R4}
@ 先递减地址,后存储数据 Decrease Before
.global STOP
STOP:
B STOP @死循环,防止程序跑飞
.end @汇编程序的结束
栈
.text @表示当前段为代码段
.global _start @声明_start为全局符号
_start: @汇编程序的入口
@ 栈的种类与使用
@ MOV R1, #1
@ MOV R2, #2
@ MOV R3, #3
@ MOV R4, #4
@ MOV R11,#0x40000020
@ STMFD R11!,{R1-R4}
@ LDMFD R11!,{R6-R9}
@ 栈的应用举例
@ 1.叶子函数的调用过程举例
@ 初始化栈指针
@ MOV SP, #0x40000020
@ MIAN:
@ MOV R1, #3
@ MOV R2, #5
@ BL FUNC
@ ADD R3, R1, R2
@ B STOP
@ FUNC:
@ 压栈保护现场
@ STMFD SP!, {R1,R2}
@ MOV R1, #10
@ MOV R2, #20
@ SUB R3, R2, R1
@ 出栈恢复现场
@ LDMFD SP!, {R1,R2}
@ MOV PC, LR
@ 2.非叶子函数的调用过程举例
@ MOV SP, #0x40000020
@ MIAN:
@ MOV R1, #3
@ MOV R2, #5
@ BL FUNC1
@ ADD R3, R1, R2
@ B STOP
@ FUNC1:
@ STMFD SP!, {R1,R2,LR}
@ MOV R1, #10
@ MOV R2, #20
@ BL FUNC2
@ SUB R3, R2, R1
@ LDMFD SP!, {R1,R2,LR}
@ MOV PC, LR
@ FUNC2:
@ STMFD SP!, {R1,R2}
@ MOV R1, #7
@ MOV R2, #8
@ MUL R3, R1, R2
@ LDMFD SP!, {R1,R2}
@ MOV PC, LR
@ 执行叶子函数时不需要对LR压栈保护,执行非叶子函数时需要对LR压栈保护
.global STOP
STOP:
B STOP @死循环,防止程序跑飞
.end @汇编程序的结束
为什么C中用局部变量时要先初始化?
局部变量在调用的时候临时在栈里分配空间(比如:int a ),栈指针指到那儿,a的空间就分配到那儿,但是这些空间之前可能就已经存有数据,如果不初始化,a的值就是之前的这些数据
为什么C中全局变量不用初始化就是0?
如果全局变量没有初始化,编译器编译时,会把它放在BSS段 ,而操作系统在调用程序前会执行一段代码,这段代码会把BSS段清零
1.4 状态寄存器传送指令:访问(读写)CPSR寄存器
.text @表示当前段为代码段
.global _start @声明_start为全局符号
_start: @汇编程序的入口
@ 1.4 状态寄存器传送指令:访问(读写)CPSR寄存器
@ 读CPSR
@ MRS R1, CPSR
@ R1 = CPSR
@ 写CPSR
@ MSR CPSR, #0x10
@ CPSR = 0x10
@ 在USER模式下不能随意修改CPSR,因为USER模式属于非特权模式
@ MSR CPSR, #0xD3
.global STOP
STOP:
B STOP @死循环,防止程序跑飞
.end @汇编程序的结束
1.5 软中断指令:触发软中断
- 当复位或者软中断时,CPU进入SVC模式
- CPU刚上电时,默认是ARM状态
- CPU在刚上电时,会进行一些核心的初始化工作,在执行这些程序时不希望被外界(比如网卡,键盘等)的影响;当操作系统启动完成以后,应用程序能够运行时,CPU才会对外围的硬件的一些信号做出响应;
故,刚上电时FIQ和IRQ禁止
.text @表示当前段为代码段
.global _start @声明_start为全局符号
_start: @汇编程序的入口
@ 异常向量表
@ B MAIN
@ B .
@ B SWI_HANDLER
@ B .
@ B .
@ B .
@ B .
@ B .
@ 应用程序
@ MAIN:
@ MOV SP, #0x40000020
@ 初始化SVC模式下的栈指针(不同模式的R13(SP)寄存器都不一样)!!!!!!
@ MSR CPSR, #0x10
@ 切换成USER模式,开启FIQ、IRQ
@ MOV R1, #1
@ MOV R2, #2
@ SWI #1
@ 触发软中断异常
@ ADD R3, R2, R1
@ B STOP
@ 异常处理程序
@ SWI_HANDLER:
@ STMFD SP!,{R1,R2,LR}
@ 压栈保护现场
@ MOV R1, #10
@ MOV R2, #20
@ SUB R3, R2, R1
@ LDMFD SP!,{R1,R2,PC}^
@ 出栈恢复现场
@ 将压入到栈中的LR(返回地址)出栈给PC,实现程序的返回
@ ‘^’表示出栈的同时将SPSR的值传递给CPSR,实现CPU状态的恢复
.global STOP
STOP:
B STOP @死循环,防止程序跑飞
.end @汇编程序的结束
1.6 协处理器指令:操控协处理器的指令
@ 1.协处理器数据运算指令
@ CDP
@ 2.协处理器存储器访问指令
@ STC 将协处理器中的数据写入到存储器
@ LDC 将存储器中的数据读取到协处理器
@ 3.协处理器寄存器传送指令
@ MRC 将协处理器中寄存器中的数据传送到ARM处理器中的寄存器
@ MCR 将ARM处理器中寄存器中的数据传送到协处理器中的寄存器
2.伪指令
伪指令是什么?
本身不是指令,编译器可以将其替换成若干条等效指令
@ 空指令
@ NOP
@ 指令
@ LDR R1, [R2]
@ 将R2指向的内存空间中的数据读取到R1寄存器
@ 伪指令
@ LDR R1, =0x12345678
@ R1 = 0x12345678
@ LDR伪指令可以将任意一个32位的数据放到一个寄存器
@ LDR R1, =STOP
@ 将STOP表示的地址写入R1寄存器
@ LDR R1, STOP
@ 将STOP地址中的内容写入R1寄存器
3.伪操作
伪操作是什么?
不会生成代码,只是在编译之前告诉编译器怎么编译
@ GNU的伪操作一般都以‘.’开头
@ .global symbol
@ 将symbol声明成全局符号
@ .local symbol
@ 将symbol声明成局部符号
@ .equ DATA, 0xFF
@ MOV R1, #DATA
@ .macro FUNC
@ MOV R1, #1
@ MOV R2, #2
@ .endm
@ FUNC
@ .if 0
@ MOV R1, #1
@ MOV R2, #2
@ .endif
@.rept 3
@ MOV R1, #1
@ MOV R2, #2
@.endr
@ .weak symbol
@ 弱化一个符号,即告诉编译器即便没有这个符号也不要报错
@ .weak func
@ B func
@ .word VALUE
@ 在当前地址申请一个字的空间并将其初始化为VALUE
@ MOV R1, #1
@ .word 0xFFFFFFFF
@ MOV R2, #2
@ .byte VALUE
@ 在当前地址申请一个字节的空间并将其初始化为VALUE
@ MOV R1, #1
@ .byte 0xFF
@ .align N
@ 告诉编译器后续的代码2的N次方对其
@ .align 4
@ MOV R2, #2
@ .arm
@ 告诉编译器后续的代码是ARM指令
@ .thumb
@ 告诉编译器后续的代码是Thumb指令
@ .text
@ 定义一个代码段
@ .data
@ 定义一个数据段
@ .space N, VALUE
@ 在当前地址申请N个字节的空间并将其初始化为VALUE
@ MOV R1, #1
@ .space 12, 0x12
@ MOV R2, #2
@ 不同的编译器伪操作的语法不同
C和汇编的混合编程
C和汇编的混合编程原则:在哪种语言环境下符合哪种语言的语法规则
1)在汇编中将C中的函数当做标号处理
2)在C中将汇编中的标号当做函数处理
3)在C中内联的汇编当做C的语句来处理
arm-asm.s
1. 方式一:汇编语言调用(跳转)C语言
@ MOV R1, #1
@ MOV R2, #2
@ BL func_c @结束后自动跳到下一条指令,因为c语言编译后最后是一个语句是跳转
@ MOV R3, #3
2. 方式二:C语言调用(跳转)汇编语言
@ .global FUNC_ASM
@ FUNC_ASM:
@ MOV R4, #4
@ MOV R5, #5
3. C内联(内嵌)汇编 (往下看)
main.c
void func_c(void)
{
int a;
a ++;
//C内联(内嵌)汇编
asm
(
"MOV R6, #6\n"
"MOV R7, #7\n"
);
//C语言调用(跳转)汇编语言
FUNC_ASM();
a --;
}
ATPCS协议(ARM-THUMB Procedure Call Standard)
@ ATPCS协议主要内容
@ 1.栈的种类
@ 1.1 使用满减栈
@ 2.寄存器的使用
@ 2.1 R15用作程序计数器,不能作其他用途
@ 2.2 R14用作链接寄存器,不能作其他用途
@ 2.3 R13用作栈指针,不能作其他用途
@ 2.4 当函数的参数不多于4个时使用R0-R3传递,当函数的参数多于4个时,多出的部分用栈传递
@ 2.5 函数的返回值使用R0传递
@ 2.6 其它寄存器主要用于存储局部变量