我们在学习 Linux 嵌入式开发的时候,了解 ARM 汇编是很有必要的,虽然我们使用汇编编写代码的情况很少,但是有些情况下我们需要简单编写一些汇编程序来协助我们调试板子(因为我们的 i.MX6 UL 终结者开发板使用的 cpu 是 ARM Cortex-A7 架构的,cpu 刚上电必须要运行汇编代码,来初始化 cpu 的一些内部功能,然后设置好 C 语言的环境,才能运行 C 语言),比如一块板子焊接回来,我们烧写镜像没有问题,但是烧写完,串口没有打印信息,不能正常启动,这时我们应该不能确定是软件问题还是硬件问题,我们可以在汇编里面通过点灯(点亮板子上的 led)的方式来确定下 cpu 有没有运行,此时我们就要用到汇编,因为 C 语言还没有被执行。所以我们需要了解并掌握下 ARM 汇编。 81 .1 GNU 汇编语法 汇编语法 GNU 汇编语法适用于所有的架构,并不是 ARM 独享的,GNU 汇编由一系列的语句组成,每行一条语句, 每条语句有三个可选部分,如下: label :instruction @ commentlabel 即标号,表示地址位置,有些指令前面可能会有标号,这样就可以通过这个标号得到指令的地址,标号也可以用来表示数据地址。注意 label 后面的“:”,任何以“:”结尾的标识符都会被识别为一个标号。 instruction 即指令,也就是汇编指令或伪指令。 @符号,表示后面的是注释,就跟 C 语言里面的“/*”和“*/”一样,其实在 GNU 汇编文件中我们也可以 使用“/*”和“*/”来注释。 comment 就是注释内容。 比如下面的代码: sum: MOVS R0, #0X5a @设置 R0=0X5a 上面代码中“add”就是标号,“MOVS R0, #0X5a”就是指令,后面的“@设置 R0=0X5a”就是注释。( 注意 :ARM 汇编中的指令 、 伪指令 、 伪操作 、 寄存器等 , 都可以全部使用大写 , 也可以全部使用小写 , 但是不能大小写混用) 我们也可以使用.section 伪操作来定义一个段名,汇编系统中预定义了一些段名,如下: .text //代码段 .bss //未初始化的数据段 .data //初始化的数据段 .rodata //只读数据段 我们自己也可以使用.section 定义一个段,如下: .section .test_section @定义一个 test_setcion 段 我们知道在 C 语言中,程序的入口是 main 函数,在汇编中程序的入口标号是“_start”,下面的代码就是使用_start 作为入口标号: .global _start _start: ldr r0, =0x5a @r0=0x5a 上面代码中.global 是伪操作,表示_start 是一个全局标号,类似 C 语言里面的全局变量,常见的伪操作有: .byte //定义单字节数据,比如.byte 0x5a .short //定义双字节数据,比如.short 0x5a5a .long //定义 4 字节数据,比如.long 0x5a5a5a5a .equ //赋值语句,格式为: .equ 变量名,表达式,比如.equ num, 0x5a,表 示 num=0x5a .align //数据字节对齐,比如:.align 4 表示 4 字节对齐 .end //表示源文件结束 .global //定义一个全局符号,格式为:.global symbol,比如:.global _start 上面这些是最常见的伪操作,GNU 汇编还有其他的伪操作,如果想更箱子的了解伪操作可以参考《ARM Cortex-A(armV7)编程手册 V4.0》(光盘目录:i.MX6UL 终结者光盘资料\10_其它参考资料) GNU 汇编也支持函数,格式如下: .type fun_name, @function fun_name: #content ret .type 指令指定 fun_name 为其它汇编程序调用此函数时的地址 fun_name 也为函数名 ret 指令表示函数结束,返回到父函数调用子函数处 定义函数的例子如下: .type add_fun, @function add_fun: add %ebx, %ebx movl %ebx, %eax ret 8.2 ARM 汇编指令 下面我们来学习下 ARM 的常用汇编指令,这里我们参考了文档《ARM ArchitectureReference Manual ARMv7-Aand ARMv7-R edition》(光盘目录:i.MX6UL 终结者光盘资料\10_其它参考资料)。 8.2.1 MOV 指令 MOV 指令用于将数据从一个寄存器拷贝到另外一个寄存器,或者将一个立即数传递到寄存器里面,使用示例如下: MOV R0,R1 @将寄存器 R1 中的数据传递给 R0,即 R0=R1 MOV R0, #0X12 @将立即数 0X12 传递给 R0 寄存器,即 R0=0X12 8.2.2 MRS 指令 MRS 指令用于将特殊寄存器(如 CPSR 和 SPSR)中的数据传递给通用寄存器,要读取特殊寄存器的数据只能 使用 MRS 指令!使用示例如下: MRS R0, CPSR @将特殊寄存器 CPSR 里面的数据传递给 R0,即 R0=CPSR 8.2.3 MSR 指令 MSR 指令和 MRS 刚好相反,MSR 指令用来将普通寄存器的数据传递给特殊寄存器,也就是写特殊寄存器,写 特殊寄存器只能使用 MSR,使用示例如下: MSR CPSR, R0 @将 R0 中的数据复制到 CPSR 中,即 CPSR=R0 8.2.4 LDR 指令 LDR 指令用于从存储器中将一个 32 位的字数据传送到目的寄存器中。该指令通常用于从存储器中读取32 位的字数据到通用寄存器,然后对数据进行处理。当程序计数器 PC 作为目的寄存器时,该指令从存储器中读取的字数据被当作目的地址,从而可以实现程序流程的跳转。该指令在程序设计中比较常用,其寻址 方式灵活多样。使用示例如下: LDR R0,[R1] //将存储器地址为 R1 的字数据读入寄存器 R0 LDR R0,[R1,R2] //将存储器地址为 R1+R2 的字数据读入寄存器 R0 LDR R0,[R1,#8] //将存储器地址为 R1+8 的字数据读入寄存器 R0 LDR R0,[R1],R2 //将存储器地址为 R1 的字数据读入寄存器 R0,幵将新地址 R1 +R2 写入 R1 8.2.5 STR 指令 STR 是将数据写入到存储器中,示例代码如下: STR R1, [R0] //将 R1 中的值写入到 R0 中所保存的地址中 8.2.6 入栈,出栈指令 栈被定义为一种先进后出的数据结构,即最后进栈的元素将被最先弹出来.这很像许多人进入一条窄得只能 容纳一个人通过的小道,如果要从这条道往回退出来的话,那么最先退出来的人是最后一个进入小道的人.所以栈具有后进先出的性质(LIFO)。我们使用 push 指令实现入栈,示例代码如下: PUSH {R0~R3, R12} @将 R0~R3 和 R12 压栈 出栈指令使用 POP,示例代码如下: POP {R0~R3,R12} @在恢复 R0~R3,R12 8.2.7 跳转指令 跳转指令用于实现程序流程的跳转,在 ARM 程序中有两种方法可以实现程序流程的跳转: 1. 使用专门的跳转指令(B B 、 BL 、 BX 、 BLX ) 2. 器 直接向程序计数器 C PC 写入跳转地址值 一般我们常用专门跳转指令实现程序的跳转,下面我们来看下跳转指令的使用: B 指令 B 指令是最简单的跳转指令。一旦遇到一个 B 指令,ARM 处理器将立即跳转到给定的目标地址,从那里继续执行。注意存储在跳转指令中的实际值是相对当前 PC 值的一个偏移量,而不是一个绝对地址,它的值由汇编器来计算(参考寻址方式中的相对寻址)。它是 24 位有符号数,左移两位后有符号扩展为 32 位,表示的有效偏移为 26 位(前后 32MB 的地址空间)。以下指令: B Label ;程序无条件跳转到标号 Label 处执行 BL 指令 跳转之前,会在寄存器 R14 中保存 PC 的当前内容,因此,可以通过将 R14 的内容重新加载到 PC 中,来返回到跳转指令之后的那个指令处执行。该指令是实现子程序调用的一个基本但常用的手段。以下指令: BL Label ;当程序无条件跳转到标号 Label 处执行时,同时将当前的 PC 值保存到 R14 中 BLX 指令 从 ARM 指令集跳转到指令中所指定的目标地址,并将处理器的工作状态由 ARM 状态切换到 Thumb 状态,该指令同时将 PC 的当前内容保存到寄存器 R14 中。因此,当子程序使用 Thumb 指令集,而调用者使用 ARM 指令集时,可以通过 BLX 指令实现子程序的调用和处理器工作状态的切换。同时,子程序的返回可以通过将寄存器 R14 值复制到 PC 中来完成。指令格式如下: BLX 目标地址 X BX 指令 跳转到指令中所指定的目标地址,目标地址处的指令既可以是 ARM 指令,也可以是 Thum 指令,指令格式如下: BX{条件} 目标地址 8.2.8 逻辑运算指令 常用的逻辑运算符如下: AND a, b //a = a & b 按位与 AND a, b, #c //a = b & #c 按位与 AND a, b, c //a = b & c 按位与 ORR a, b //a = a | b 按位或 ORR a, b, #c //a = b | #c 按位或 ORR a, b, c //a = b | c 按位或 BIC a, b //a = a & (~b) 位清除 BIC a, b, #c //a = b & (~#c) 位清除 BIC a, b, c //a = b & (~c) 位清除 ORN a, b //a = a | (b) 按位或非 ORN a, b, #c //a = b |(#c) 按位或非 ORN a, b, c //a = b |(c) 按位或非 EOR a, b //a = a ^b 按位异或 EOR a, b, #c //a = b ^ #c 按位异或 EOR a, b, c //a = b ^ c 按位异或 8.2.9 算数运算符 ADD, a, #b // a = a + #b 加法运算 ADD a, b, c //a = b + c 加法运算 ADD a, b, #c //a = b + #c 加法运算 SUB a, #b //a = a - #b 减法运算 SUB a, b, c //a = b - c 减法运算 SUB a, b, #c //a = b - #c 减法运算 MUL a, b, c //a = b * c 惩罚运行算(32 位) UDIV a, b, c //a = b/c 无符号除法 SDIV a, b, c //a = b/c 有符号除法 本节主要讲解了一些最常用的指令,还有很多不常用的指令没有讲解,但是够我们后续学习用了。要想详细的学习 ARM 的所有指令请参考《 ARM ArchitectureReference Manual ARMv7-A and ARMv7-Redition.pdf》和《ARM Cortex-A(armV7)编程手册 V4.0.pdf》这两份文档,他们分别在光盘资料的目录:i.MX6UL 终结者光盘资料\10_其它参考资料。 更多内容关注公众号:迅为电子
|