大家周末晚上好,今天给大家分享一些简单的汇编知识;说起汇编,不管是学习或者说工作中,都会或多或少的接触到,比如说学习中,在进入c语言编程世界之前,都会有一段汇编作为引导来进入c的;当然在实际开发当中,现在用汇编来开发的比较少,不是没有;做一为嵌入式软件工程师,我觉得还是非常有必要要掌握一些基本的汇编指令知识的,不要你会写汇编代码,要求自身会分析以.s结尾的文件里面的汇编代码就差不多了,看的懂常规汇编指令就行(这里顺便插一句题外话,我们知道一般ARM都是采用risc架构的,如果有网友对risc-v架构感兴趣的,可以来交流学习),好了,废话就不多说了,开始进入主题啦!
一、ARM体系之寄存器介绍:
在写这个寄存器介绍之前,给大家看一下linux内核代码文件head.S里面的汇编代码,感受一下,暂时看不懂没关系:
代码语言:javascript复制 @ and irqs disabled
mrc p15, 0, r9, c0, c0 @ get processor id
bl __lookup_processor_type @ r5=procinfo r9=cpuid
movs r10, r5 @ invalid processor (r5=0)?
beq __error_p @ yes, error 'p'
bl __lookup_machine_type @ r5=machinfo
movs r8, r5 @ invalid machine (r5=0)?
beq __error_a @ yes, error 'a'
这里讲的汇编代码书写风格是基于arm处理器,如果是Intel(AMD)的汇编风格是这样的:
代码语言:javascript复制 subq $16, %rsp
movl $9, -4(%rbp)
movl -4(%rbp), �x
movl �x, %esi
movl $.LC0, �i
movl $0, �x
call printf
movl $0, �x
好了,这里也只是感受一下,具体Intel(X86-64)的汇编风格,我后面会在c语言专辑里面详细写到(结合我们平时的c语言程序类具体分析!),这里我就不偏离主题了。那么为什么CPU在运行的时候要有寄存器这么东西呢,我之前看过一段话,解释的比较到位:
想象CPU是一个圈一直在运转,然后寄存器里面有大量的指令,这些指令不知道从哪里来的,但是一般情况下我们的CPU在计算我们的程序,我们的程序一般是放在内存里面的,它从内存里面把这些程序读进来之后,再运行,但是如果现在这个程序在运行时异常,那么就要进行CPU状态的切换,除了状态切换之外,当前的一些数据结果需要进行一个保存,但是如果要把这个结果存到内存去,内存并不稳定并且很慢,所以就要想办法能不能找到一个临时空间保存一下,这就是为什么会诞生寄存器。
接下来我们先来看一下ARM里面的寄存器分类:
image
ARM 处理器一般共有 37 个寄存器,其中包括:
1、 31 个通用寄存器,包括 PC(程序计数器)在内,都是 32 位的寄存器。
2、 6 个状态寄存器,也都是 32 位的寄存器。
同时ARM处理器有7种处理模式:
代码语言:javascript复制1、用户模式(User)
2、快速中断模式(FIQ)
3、普通中断模式(IRQ)
4、管理模式(Svc)
5、数据访问中止模式(Abort)
6、未定义指令中止模式(Und)
7、系统模式(Sys)
在任意一种处理器模式下,可见的寄存器包括 15 个通用寄存器(R0~R14)、一个或者二个状态寄存器以及程序计数器(PC)。在所有的寄存器中,有些是各模式共用同一个物理寄存器,有些寄存器是各个模式自己拥有独立的物理寄存器,这里就不具体展开叙述了。
(1)ARM状态下的通用寄存器与程序计数器:
(2)ARM状态下的程序寄存器:
注意上面表格中小影阴直角三角形表示的是分组寄存器,所谓分组寄存器,就是说是当前模式下独有的,不共享。
注解:
--1 其中 r0~r3 主要用于子程序间传递参数, r4~r11 主要用于保存局部变量,但在 Thumb 程序中,通常只能使用 r4~r7 来保存局部变量;r12 用作子程序间scratch 寄存器,即 ip 寄存器;r13 通常用做栈指针,即 sp;r14 寄存器又被称为连接寄存器(lr),用于保存子程序以及中断的返回地址,也就是说它就是去连接某一个地方;r15 用作程序计数器(pc),由于 ARM 采用了流水线机制,当正确读取了 PC 的值后,该值为当前指令地址加 8 个字节,即 PC 指向当前指令的下两条指令地址:
SP:栈指针,存储栈地址,比如有个CPU,然后还有个外部资源也就是内存,我们想象CPU在程序跳转的时候在运行是最核心的ALU单元,然后外部资源存储着程序A和程序B还有程序C,实际上在CPU在读程序A的时候,可能下面的时候会跳到程序B单元,这个时候它可能不知道地址在哪里,那么这个时候就存储在SP这个寄存器里面,SP这个寄存器表示后面将要执行的程序。
LR:链接寄存器,存储于程序返回地址。这个链接寄存器主要用在函数A和函数B,A正在运行时,突然要调用B,那么就引了一个分支了,然后这个函数B去运行,运行完之后还是要返回到最初然后继续往下走,那么这个时候返回值应该要有个记录,这就是链接寄存器。
CPSR和SPSR都是程序状态寄存器,其中SPSR是用来保存中断前的CPSR中的值,以便在中断返回之后恢复处理器程序状态;CPSR是当前程序状态寄存器的意思,SPSR是程序状态保存寄存器,这里我在网上看到一个非常通俗易通的解释这两个寄存器的用法:
已存储程序状态寄存器。想象一个程序正在运行,这个程序当前状态正常,这个状态就先把它保存到CPS里面,这个时候突然发生异常,那么当前状态就应该变成异常,就把这个状态存到CPSR上面,但是异常处理完了之后,我们希望还是能够回到之前那个状态,但是这个时候当时的状态已经被清理掉了,这个时候我们就可以用SPSR把原来那个状态保存,这样当状态在发生改变的时候,要还原就可以去SPSR里面读取之前那个状态,这就是它们之间的关系,就类似有一个A的变量,给A这个变量赋了一个值,然后还赋了一个新值,但是又希望原来那个值要保存,所以有个变量B,然后把变量A赋给变量B。
--2 通过上面的图片,我们仔细观察到:
各个模式的R0到R12与USR模式是共享的(除了FIQ,R8-R12),PC,CPSR是共享的。而且要注意到USR模式没有SPSR的。
二、汇编指令和伪指令:
本来上次的文章中有介绍这个,当时可能没有系统给大家分享完,所以我这里再稍微提一下,这次专题,把完整分享完。
1、(汇编)指令:
它是CPU机器指令的助记符, 经过编译后会得到一串10组成的机器码, 可以由CPU读取执行。
2、(汇编)伪指令:
它本质上不是指令(只是和 指令一起写在代码中),它是编译器环境 提供的,目的是用来指导编译过程,经过 编译后伪指令最终不会生成机器码。
3、两个寄存器:
代码语言:javascript复制ldr(load register)指令将内存内容加载入 通用寄存器。
str(store register)指令将寄存器内容存入 内存空间中。
4、ARM的8种寻址方式(同样这里先理性了解一下):
代码语言:javascript复制• 寄存器寻址 mov r1, r2
• 立即寻址 mov r0, #0xFF00
• 寄存器移位寻址 mov r0, r1, lsl #3
• 寄存器间接寻址 ldr r1, [r2]
• 基址变址寻址 ldr r1, [r2, #4]
• 多寄存器寻址 ldmia r1!, {r2-r7, r12}
• 堆栈寻址 stmfd sp!, {r2-r7, lr}
• 相对寻址 beq flag
6、指令后缀:
同一指令经常附带不同后缀,变成不同的 指令。经常使用的后缀有:
• B(byte)功能不变,操作长度变为8位
• H(half word)功能不变,长度变为16位
• S(signed)功能不变,操作数变为有符号 如 ldr ldrb ldrh ldrsb ldrsh
• S(S标志)功能不变,影响CPSR标志位 如 mov和movs movs r0, #0
7、条件执行后缀:
三、总结:
今天的简单分享了一些ARM汇编概念,下期我们就开始具体学习汇编常用的汇编指令了,也就是我们在实际分析汇编代码中会遇到有用的汇编指令了。