@ *****************************************************************@ 汇编中的符号@ 1.指令: 能够编译生成一条32位的机器码,且能被CPU识别和执行@ 2.伪指令:本身不是指令,编译器可以将其替换成若干条等效指令@ 3.伪操作:不会生成代码,只是在编译之前告诉编译器怎么编译@ ARM指令@ 1.数据处理指令: 数学运算、逻辑运算@ 2.跳转指令: 实现程序的跳转,本质就是修改了PC寄存器@ 3.Load/Srore指令: 访问(读写)内存@ 4.状态寄存器传送指令:访问(读写)CPSR寄存器@ 5.软中断指令: 触发软中断异常@ 6.协处理器指令: 操控协处理器的指令@ *****************************************************************.text @表示当前段为代码段
.global _start @声明_start为全局符号
_start: @汇编程序的入口@ 1.指令:能够编译生成一条32位的机器码,且能被CPU识别和执行@ 1.1 数据处理指令:数学运算、逻辑运算@ 数据搬移指令@ MOV R1, #1@ R1 = 1@ MOV R2, R1@ R2 = R1@ MVN R0, #0xFF @ R0 = ~0xFF@ 立即数@ 立即数的本质就是包含在指令当中的数,属于指令的一部分@ 立即数的优点:取指的时候就可以将其读取到CPU,不用单独去内存读取,速度快@ 立即数的缺点:不能是任意的32位的数字,有局限性@ MOV R0, #0x12345678@ MOV R0, #0x12@ 编译器替换@ 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@ 按位异或指令@ EOR R1, R2, R3@ R1 = R2 ^ R3@ 左移指令@ 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“才可以影响@ 带进位的加法指令@ 两个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'@ 1.2 跳转指令:实现程序的跳转,本质就是修改了PC寄存器@ 方式一:直接修改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@ 程序返回@ 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@ 1.3 Load/Srore指令:访问(读写)内存@ 写内存@ 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@ ADD 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@ 以上寻址方式和索引方式同样适用于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}@ 先存储数据,后增长地址@ STMIB R11!,{R1-R4}@ 先增长地址,后存储数据@ STMDA R11!,{R1-R4}@ 先存储数据,后递减地址@ STMDB R11!,{R1-R4}@ 先递减地址,后存储数据@ 栈的种类与使用@ 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压栈保护@ 1.4 状态寄存器传送指令:访问(读写)CPSR寄存器@ 读CPSR@ MRS R1, CPSR@ R1 = CPSR@ 写CPSR@ MSR CPSR, #0x10@ CPSR = 0x10@ 在USER模式下不能随意修改CPSR,因为USER模式属于非特权模式@ MSR CPSR, #0xD3@ 1.5 软中断指令:触发软中断@ 异常向量表@ B MAIN@ 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.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不执行里面语句,跟1执行@ .if 0@ MOV R1, #1@ MOV R2, #2@ .endif@ .rept后面跟几就是循环几遍@.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的语句来处理@ 1. 方式一:汇编语言调用(跳转)C语言@ MOV R1, #1@ MOV R2, #2@ BL func_c@ MOV R3, #3@ 2. 方式二:C语言调用(跳转)汇编语言
@ .global FUNC_ASM
@ FUNC_ASM:@ MOV R4, #4@ MOV R5, #5@ 3. C内联(内嵌)汇编@ *****************************************************************@ 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 其它寄存器主要用于存储局部变量.global STOP
STOP: B STOP @死循环,防止程序跑飞 .end @汇编程序的结束
一、栈