arm汇编基础一

来源:互联网 发布:130万网络摄像头 编辑:程序博客网 时间:2024/05/20 12:47

本文出自http://blog.csdn.net/scyangzhu/article/details/8018761 作者著有好几本技术书籍,在此谢谢作者的分享


ARM的汇编编程,本质上就是针对CPU寄存器的编程,所以我们首先要弄清楚ARM有哪些寄存器?这些寄存器都是如何使用的?

ARM寄存器分为2类,普通寄存器和状态寄存器,如表1-1所列。


表1 - 1  ARM寄存器

寄存器类别

寄存器在汇编中的名称

各模式下实际访问的寄存器

用户

系统

管理

中止

未定义

中断

快中断

通用寄存器和程序计数器

R0(a1)

R0

R1(a2)

R1

R2(a3)

R2

R3(a4)

R3

R4(v1)

R4

R5(v2)

R5

R6(v3)

R6

R7(v4)

R7

R8(v5)

R8

R8_fiq

R9(SB,v6)

R9

R9_fiq

R10(SL,v7)

R10

R10_fiq

R11(FP,v8)

R11

R11_fiq

R12(IP)

R12

R12_fiq

R13(SP)

R13

R13_svc

R13_abt

R13_und

R13_irq

R13_fiq

R14(LR)

R14

R14_svc

R14_abt

R14_und

R14_irq

R14_fiq

R15(PC)

R15

状态寄存器

CPSR

CPSR

SPSR

SPSR_abt

SPSR_abt

SPSR_und

SPSR_irq

SPSR_fiq

 


请看表1-1的第2列,普通寄存器总共16个,分别为R0-R15;状态寄存器共2个,分别为CPSR和SPSR。

 1.1.1普通寄存器R0 – R15

普通寄存器中特别要提出来的是R13、R14、R15。

R15 别名PC(program counter),中文称为程序计数器,它的值是当前正在执行的指令在内存中的位置(不考虑流水线的影响,参见“流水线对PC值的影响”),而当指令执行结束后,CPU硬件会自动将PC的值加上一个单位,从而使得PC的值为下一条即将执行的指令在内存中的位置,这样CPU硬件就可以根据PC的值自动完成取指的操作。正是由于有PC的存在,以及CPU硬件会自动增加PC的值,并根据PC的值完成取指操作,才使得CPU一旦上电就永不停歇地运转,由此可见PC寄存器对于计算机的重要性。对于我们进行汇编程序编写而言,PC寄存器亦是十分重要,因为当程序员通过汇编指令完成了对PC寄存器的赋值操作的时候,其实就是完成了一次无条件跳转,这一点非常重要,请务必要牢记。

R14别名LR(linked register),中文称为链接寄存器,它与子程序调用密切相关,用于存放子程序的返回地址,它是ARM程序实现子程序调用的关键所在。下面我们用C语言中对子程序调用的实现细节来说明LR是如何被使用的。

1 int main(void)

2 {

3    int k, i = 1, j = 2;

4    addsub(i, j);

5    k = 3;

6 }

7 int addsub(inta, int b)

8 {

9    int c;

10    c = a+ b;

11    returnc;

12 }

对于上面的程序,编译器会将第4行编译为指令:BL addsub,将第11行编译为指令:MOV pc, lr。(关于BL和MOV指令详见“基本寻址模式与基本指令”)

在这里,关键指令BL addsub会完成2件事情:                 

1. 将子程序的返回地址(也就是第5行代码在内存中的位置)保存到寄存器LR中;

2. 跳转到子程序addsub的第1条指令处。

这样就完成了子程序的调用。

而指令MOV pc, lr则将保存在lr中的返回地址赋给pc,这样就完成了从子程序的返回。

由此可见,lr是专门用于存放子程序的返回地址的。

另外一个要引起注意的问题是,如果子程序又调用了孙子程序,那么根据前面的分析,在调用孙子程序时,lr寄存器中的值将从子程序的返回地址变为孙子程序的返回地址,这将导致从孙子程序返回子程序没有问题,但从子程序返回父程序则会出错。那么这个问题如何解决呢?其实,如果我们编写的是C程序,那么我们一点也不用担心,因为编译器会为我们考虑一切,针对这个问题,编译器会在子程序的入口处增加入栈操作将lr的值入栈,然后在子程序即将返回前增加出栈操作,将lr的值恢复(一般情况下而言,是将该值恢复到PC,从而完成返回到父程序),从而解决这个难题。不过我们一定要保持头脑的清醒,因为你要知道,我们现在是在编写汇编子程序,此时编译器已经不能在这方面给我们提供保障,所以当你在编写汇编子程序的时候,发现该子程序还要再调用孙子程序,那么请你务必记住,一定要在子程序的入口处保存lr寄存器的值。

好了,现在轮到寄存器R13了,R13又名SP(stack pointer),中文名称栈指针寄存器。顾名思义,它是用于存放堆栈的栈顶地址的。也就是说,每次当我们进行出栈和入栈的时候,都将根据该寄存器的值来决定访问内存的位置(即:出入栈的内存位置),同时在出栈和入栈操作完成后,SP寄存器的值也应该相应增加或减少。这里要特别说明的是,其实在32位的 ARM指令集中没有专门的入栈指令和出栈指令,所以并不是一定要用SP来作为栈指针寄存器,除了PC外,任何普通寄存器均可作为栈指针寄存器,只不过约定俗成,都使用SP罢了。我们将在“其它寻址模式与其它指令”中见到ARM中使用SP作为栈指针寄存器来实现出入栈的汇编指令。

寄存器R0-R12是普通的数据寄存器,可用于任何地方。在不涉及ATPCS规则(在“ATPCS与混合编程”中详细介绍)的情况下,它们并没有什么特别的用法。

1.1.2状态寄存器CPSR与SPSR

1. 状态寄存器CPSR(current program status register)

中文名称:当前程序状态寄存器,顾名思义它是用于保存程序的当前状态的。那么,程序的哪些状态是需要保存的呢?


图1 -1 CPSR寄存器

 

上图是CPSR寄存器的内容,主要由以下部分组成:

(1)条件代码标志位。它们是ARM指令条件执行的依据。

l        N:运算结果的最高位反映在该标志位。对于有符号二进制补码,结果为负数时N=1,结果为正数或零时N=0;

l        Z:指令结果为0时Z=1(通常表示比较结果“相等”),否则Z=0;

l        C:当进行加法运算(包括CMN指令),并且最高位产生进位时C=1,否则C=0。当进行减法运算(包括CMP 指令),并且最高位产生借位时C=0,否则C=1。对于结合移位操作的非加法/减法指令,C为从最高位最后移出的值,其它指令C通常不变。

l        V:当进行加/减法运算,并且发生有符号溢出时V=1,否则V=0,其它指令V通常不变

(2)控制位。它们将控制CPU是否响应中断。

I:中断禁止位,当I位置位时,IRQ中断被禁止。

F:快中断禁止位,当F位置位时,FIQ中断被禁止。

T:反映了CPU当前的状态。当T位置位时,处理器正在Thumb状态下运行;当T位清零时,处理器正在ARM状态下运行。

(3)模式位

包括M4、M3、M2、M1和M0,这些位决定了处理器的模式(关于处理器模式详见“ARM处理器模式与异常初步”)。

总共有7种模式:用户、快中断、中断、管理、中止、未定义、系统,分别会用于不同的情况和异常。由此可见,不是所有模式位的组合都定义了有效的处理器模式,如果使用了错误的设置,将引起一个无法恢复的错误。

2. SPSR(saved program status register)

中文名称:保存的程序状态寄存器

该寄存器的结构与CPSR完全一样,在异常发生时(关于异常,请参见“ARM处理器模式与异常初步”),由硬件自动将异常发生前的CPSR的值存放到SPSR中,以便将来在异常处理结束后,程序能恢复原来CPSR的值。


1.1.3流水线对PC值的影响


图1 - 2  CPU内部结构框图

从上图中我们看到CPU内部有3个主要组成部分:指令寄存器,指令译码器,指令执行单元(包括ALU和通用寄存器组)。

CPU 在执行一条指令的时候,主要有3个步骤:取指(将指令从内存或指令cache中取入指令寄存器);译码(指令译码器对指令寄存器中的指令进行译码操作,从而辨识出该指令是要执行add,或是sub,或是其它操作,从而产生各种时序控制信号);执行(指令执行单元根据译码的结果进行运算并保存结果)

现在我们假设一下:CPU串行执行程序(即:执行完1条指令后,再执行下一条指令);指令执行的3个步骤中每个步骤都耗时1秒;整个程序共10条指令。那么,这个程序总的执行时间是多少呢?显然,是30秒。但这个结果令我们非常不满意,因为它太慢了。有没有办法让它座上京津高铁提速3倍呢?当然有!仔细观察上图,我们发现:取指阶段占用的CPU硬件是指令通路和指令寄存器;译码阶段占用的CPU硬件是指令译码器;执行阶段占用的CPU硬件是指令执行单元和数据通路。三者占用的CPU硬件完全不同,这样就使得如下的操作得以同时进行:在对第一条指令进行译码的时候,可以同时对第二条指令进行取指操作;在对第一条指令进行执行的时候,可以同时对第2条指令进行译码操作,对第三条指令进行取指操作。显然,这样就可以将该程序的运行总时间从30秒缩减为12秒,提速近 3倍。上面所述并行运行指令的方式就被称为流水线操作。可见:流水线操作的本质是利用指令运行的不同阶段使用的CPU硬件互不相同,并发的运行多条指令,从而提高时间效率。



图1 - 3  流水线指令执行图

 

流水线的引入,的确提高了CPU运行指令的时间效率,但却为我们的汇编程序编写引入了新的问题。请看下面的分析:

寄存器PC的值是即将被取指的指令的地址,正常情况下,在该条指令被取入CPU后执行期间,PC的值保持不变,在该条指令执行完成的时间点上,硬件会自动将 PC的值增加一个单位的大小,这样PC就指向了下一条将被取指和执行的指令。而在引入流水线后,PC值的情况发生了变化,假定第1条指令的内存地址为X, 则在时刻T,PC的值变为X,并在时刻T至时刻T+1期间维持不变;在时刻T+1,PC的值变为X+1个单位,并在时刻T+1至时刻T+2期间维持不变;在时刻T+2,PC的值变为X+2个单位,并在时刻T+2至时刻T+3期间维持不变;在时刻T+3,PC的值将变为X+3个单位。由此可见,在第1条指令的执行阶段,PC的值不再是该指令在内存中的位置,而是该指令在内存中的位置+2个单元。对于ARM指令集而言,每条指令的长度为32bit,占4byte,所以1条指令在内存中需要4byte存储。因此,我们的结论是:

指令执行时,PC的值 = 当前正在执行指令在内存中的地址 + 8

请牢记以上结论。虽然目前我们并不明白这个结论有何作用,但在后续的课程中,特别是通过查看反汇编代码的方式理解伪指令和编译器行为的时候,这个结论将会很有帮助。

最后说明一点:其实ARM现在的CPU的流水线级数早已经突破了3级。但我仍然以3级流水线来进行讲解,是因为:1、较之多级流水线,3级流水线最简单,因此也最便于初学者理解;2、虽然存在多种级别的流水线,但ARM出于统一和前后兼容的考虑,PC的值 = 当前正在执行指令在内存中的地址 + 8 这个结论在所有的流水线级别上都是相同的。作为编程人员而言,我们只需要知道这个结论即可。


0 0
原创粉丝点击