armv8汇编绝对地址赋值_ARM汇编编程基础学步园(⼀) -- ARM CPU寄存器
本系列⽂章,所需代码请从以下地址下载:
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)
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_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 这个结论在所有的流⽔线级别上都是相同的。作为编程⼈员⽽⾔,我们只需要知道这个结论即可。
要想进⾏ARM的汇编编程,⾸当其冲要知道最基本、最常⽤的指令,⽽要了解指令则必须要了解寻址⽅式。所以这⾥将聚焦在——基本寻址⽅式和基本指令。
⾸先,来看⼀看我们已经见过的2条指令:MOV pc, lr和BL addsub
最简单的汇编指令格式是操作码(例如:MOV、BL)和操作数(例如:pc, lr, addsub)。操作码易于理解,例如MOV表⽰将某个值从⼀处传送到另⼀处,BL表⽰跳转到某处;⽽操作数则表⽰⼀处和另⼀处到底是哪⾥(是在寄存器中还是内存中),要跳转的位置在哪⾥(或者是绝对地址或者是相对地址)。
操作数部分要解决的问题是:到哪⾥去获得操作数?因此就有了寻址⽅式的分类。基本上来讲,ARM共有8种寻址⽅式,这⾥我们先了解其中最基本的3种寻址⽅式:寄存器寻址、⽴即数寻址、寄存器间接寻址。
1.2.1最常见寻址⽅式精解
1. 寄存器寻址
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论