汇编语言中的符号
- 1.指令: 能够编译生成一条32位的机器码,且能被CPU识别和执行
- 2.伪指令:本身不是指令,编译器可以将其替换成若干条等效指令
- 3.伪操作:不会生成代码,只是在编译之前告诉编译器怎么编译
ARM指令
- 1.数据处理指令: 数学运算、逻辑运算
- 2.跳转指令: 实现程序的跳转,本质就是修改了PC寄存器
- 3.Load/Srore指令: 访问(读写)内存
- 4.状态寄存器传送指令:访问(读写)CPSR寄存器
- 5.软中断指令: 触发软中断异常
- 6.协处理器指令: 操控协处理器的指令
随手写一个例子
.text @表示当前段为代码段
.global _start @声明_start为全局符号
_start: @汇编程序的入口
MOV R1, #1 @ R1 = 1
MOV R2, R1 @ R2 = R1
STOP:
B STOP @死循环,防止程序跑飞
.end @汇编程序的结束
数据处理指令
指令:能够编译生成一条32位的机器码,且能被CPU识别和执行
数据处理指令:数学运算、逻辑运算
-
数据搬移指令
MOV R1, #1 @ R1 = 1 MOV R2, R1 @ R2 = R1 MVN R0, #0xFF @ R0 = ~0xFF(R0 = 0xFFFFFF00)
-
立即数
立即数的本质就是包含在指令当中的数,属于指令的一部分
立即数的优点:取指的时候就可以将其读取到CPU,不用单独去内存读取,速度快
立即数的缺点:不能是任意的32位的数字,有局限性@ MOV R0, #0x12345678 错误:不是立即数 MOV R0, #0x12 @ 编译器替换 MOV R0, #0xFFFFFFFF @ MVN R0,#0x00000000
-
数据运算指令基本格式
<操作码> <目标寄存器> <第一操作寄存器> <第二操作数>
操作码: 指示执行哪种运算
目标寄存器: 存储运算结果
第一操作寄存器: 第一个参与运算的数据(只能是寄存器)
第二操作数: 第二个参与运算的数据(可以是寄存器或立即数) -
加法指令
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
-
按位异或指令
EOR R1, R2, R3 @ sR1 = R2 ^ R3
-
左移指令
LSL R1, R2, R3 @ R1 = (R2 << R3)
-
右移指令
LSR R1, R2, R3 @ R1 = (R2 >> R3)
-
位清零指令 第二操作数中的哪一位为1,就将第一操作寄存器的中哪一位清零,然后将结果写入目标寄存器
MOV R2, #0xFF BIC R1, R2, #0x0F @ 把R2最后四位清零,结果存储到R1中
-
数据运算指令的格式扩展
MOV R2, #3 MOV R1, R2, LSL #1 @ R1 = (R2 << 1)
-
数据运算指令对条件位(N、Z、C、V)的影响
默认情况下数据运算不会对条件位产生影响,在指令后加 后缀"S" 才可以影响 -
带进位的加法指令
两个64位的数据做加法运算@ 第一个数 @ 0x00000001 FFFFFFFF @ 第二个数 @ 0x00000002 00000005 MOV R1, #0xFFFFFFFF @ 第一个数的低32位放在R1 MOV R2, #0x00000001 @ 第一个数的高32位放在R2 MOV R3, #0x00000005 @ 第二个数的低32位放在R3 MOV R4, #0x00000002 @ 第二个数的高32位放在R4 ADDS R5, R1, R3 @ 运算结果的低32位放在R5 ADC R6, R2, R4 @ 运算结果的高32位放在R6 @ 本质: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'
跳转指令
直接修改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 MOV R4, #4 MOV R5, #5 FUNC: MOV R6, #6 MOV R7, #7 MOV R8, #8 MOV PC, LR
比较指令
- CMP指令的本质就是一条减法指令(SUBS),只是没有将运算结果存入目标(N Z C V)寄存器
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
访问内存指令
- 写内存
MOV R1, #0xFF000000 MOV R2, #0x40000000 STR R1, [R2] @ -->将R1寄存器中的数据写入到R2指向的内存空间
- 读内存
LDR R3, [R2] @ <--将R2指向的内存空间中的数据读取到R3寄存器
- 读/写指定的数据类型
LDR指令同样支持以上后缀,以读取指定的数据类型MOV R1, #0xFFFFFFFF MOV R2, #0x40000000 STRB R1, [R2] @ 将R1寄存器中的数据的Bit[7:0]写入到R2指向的内存空间 (Byte) STRH R1, [R2] @ 将R1寄存器中的数据的Bit[15:0]写入到R2指向的内存空间 (halfword) STR R1, [R2] @ 将R1寄存器中的数据的Bit[31:0]写入到R2指向的内存空间 (word)
寻址方式
- 立即寻址
(立即数寻址)(参数来自于机器码)MOV R1, #1 @r1 = 1 @ 或者 ADD R1, R2, #1 @ r1 = r2 + 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
- 前索引
- 基址加变址寻址的索引方式
专用指令
-
状态寄存器传送指令:访问(读写)CPSR寄存器
@ 读CPSR MRS R1, CPSR @ R1 = CPSR @ 写CPSR MSR CPSR, #0x10 @CPSR = 0x10 @ 在USER模式下不能随意修改CPSR,因为USER模式属于非特权模式 MSR CPSR, #0xD3
-
软中断指令:触发软中断
@ 异常向量表******************************** B MAIN @ 入口为main B . @ B .跳转到自身 B SWI_HANDLER B . B . B . B . B . @ 应用程序****************************** MAIN: MOV SP, #0x40000020 @ 初始化"SVC模式"下的栈指针 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状态的恢复
-
协处理器指令:操控协处理器的指令
@ 1.协处理器数据运算指令 CDP @ 2.协处理器存储器访问指令 STC @ 将协处理器中的数据 写入到 存储器 LDC @ 将存储器中的数据 读取到 协处理器 @ 3.协处理器寄存器传送指令 MRC @ 将协处理器中寄存器中的数据 传送到 ARM处理器中的寄存器 MCR @ 将ARM处理器中寄存器中的数据 传送到 协处理器中的寄存器
-
伪指令:本身不是指令,编译器可以将其替换成若干条等效指令
@ 空指令 NOP @ 指令 LDR R1, [R2] @ 将 R2指向的内存空间中的数据 读取到 R1寄存器 @ 伪指令 LDR R1, =0x12345678 @ R1 = 0x12345678 @ LDR伪指令 可以将 任意一个32位的数据 放到 一个寄存器 LDR R1, =STOP @ 将STOP表示的地址 写入 R1寄存器 LDR R1, STOP @ 将STOP地址中的内容 写入 R1寄存器
-
伪操作:不会生成代码,只是在编译之前告诉编译器怎么编译
@ GNU的伪操作一般都以‘.’开头 .global symbol @ 将symbol声明成全局符号 .local symbol @ 将symbol声明成局部符号 .equ DATA, 0xFF @ #define MOV R1, #DATA .macro FUNC @ 封装一些语句(函数) MOV R1, #1 MOV R2, #2 .endm FUNC .if 0 @ 条件编译 MOV R1, #1 MOV R2, #2 .endif .rept 3 @ 重复3遍 MOV R1, #1 MOV R2, #2 .endr @ .weak <symbol> @ 弱化一个符号 .weak func @ 即告诉编译器即便没有这个符号也不要报错 B func @ 编译成为NOP @ .word VALUE MOV R1, #1 .word 0xFFFFFFFF @ 在当前地址申请一个字的空间 并 将其初始化为0xFFFFFFFF MOV R2, #2 @.byte VALUE MOV R1, #1 .byte 0xFF @ 在当前地址申请一个字节的空间并将其初始化为VALUE @ MOV R2, #2 @ 直接写会存储在不对的位置 @ .align N @ 告诉编译器后续的代码2的N次方对齐 .align 4 MOV R2, #2 .arm @ 告诉编译器后续的代码是ARM指令 .thumb @ 告诉编译器后续的代码是Thumb指令 .text @ 定义一个代码段 .data @ 定义一个数据段 .end @ 汇编程序的结束 @ .space N, VALUE @ 在当前地址申请N个字节的空间并将其初始化为VALUE MOV R1, #1 .space 12, 0x12 MOV R2, #2
不同的编译器伪操作的语法不同
C语言和汇编混合编程
C和汇编的混合 编程原则 :在哪种语言环境下符合哪种语言的语法规则
- 在汇编中将C中的函数当做标号处理
- 在C中将汇编中的标号当做函数处理
- 在C中内联的汇编当做C的语句来处理
例子
- 方式一:汇编语言调用C语言
MOV R1, #1 BL func_c @ 跳转到C函数 MOV R2, #2
- 方式二:C语言调用汇编语言
.global FUNC_ASM @ 定义为全局,这样才能被C程序调用 FUNC_ASM: MOV R3, #3 MOV R4, #4
- 方式三:C内联(内嵌)汇编
asm ( "MOV R5, #5\n" "MOV R6, #6\n" );
ATPCS协议(ARM-THUMB Procedure Call Standard)
ATPCS协议主要内容
- 栈的种类
- 使用满减栈
- 寄存器的使用
- R15(PC)用作程序计数器,不能作其他用途
- R14(LR)用作链接寄存器,不能作其他用途
- R13(SP)用作栈指针,不能作其他用途
- 当函数的参数不多于4个时使用R0-R3传递,当函数的参数多于4个时,多出的部分用栈传递
- 函数的返回值使用R0传递
- 其它寄存器主要用于存储局部变量