目录
ARM的寄存器
ARM汇编指令
存储访问
数据传送指令
算数逻辑运算指令
操作数2的详细说明
比较指令
条件组合指令
跳转指令
ARM寻址方式
ARM伪指令
ARM汇编程序的基本格式
符号和标号
伪操作
本博客是一个速记,不是专门的讲解!打算后修再进一步细究!
ARM的寄存器
ARM的寄存器比x86下的体系要多得多。在ARM架构中,寄存器可以根据功能和用途进行分类,主要包括通用寄存器、状态寄存器、浮点寄存器、专用寄存器、访问控制寄存器和调试寄存器。
通用寄存器通常有16个(R0-R15),用于存储操作数、中间结果和函数参数。其中,R13是堆栈指针(SP),R14是链接寄存器(LR),R15是程序计数器(PC)。状态寄存器包括程序状态寄存器(CPSR)和中断控制寄存器(SPSR),CPSR保存当前程序的状态信息,SPSR用于保存异常处理前的CPSR值。
浮点寄存器专门用于处理浮点运算和SIMD操作,支持多种浮点数据类型。专用寄存器用于特定功能,例如控制和中断处理。访问控制寄存器存储访问权限信息,确保系统安全。调试寄存器则用于调试和性能监控,帮助开发人员跟踪程序执行。通过这些分类,ARM架构的寄存器有效支持了各种操作模式和计算需求。
ARM汇编指令
<opcode> {<cond> {s} <Rd>, <Rn>, {, <oprand2>}}
<>是必选项,{}是可选的
-
<opcode>是二进制机器指令的操作助记符, <cond>是执行条件。S表达是否影响CPSR寄存器的标志位
-
<Rd>: 目标寄存器
-
<Rn>:第一个操作数
-
<oprand>:第二个操作数
存储访问
ARM是典型的加载/存储架构体系,只能通过Load, Store指令对内存进行操作。
-
LDR R1, [R0]
-
STR R1, [R0]
-
LDRB/STRB
-
LDM/STM
-
SWP R1, R1, [R0]
-
SWP R1, R2, [R0]
STM / LDM 指令可以用来模拟栈。这里有四种类型的堆栈:满递增堆栈,满递减堆栈,空递增堆栈,空递减堆栈。
-
满递增堆栈是指堆栈从低地址向高地址增长,数据项按顺序填充至堆栈顶部。此格式适用于当堆栈在初始化时已经分配了固定的空间,并且数据逐渐增加。
-
满递减堆栈则是从高地址向低地址增长,类似于满递增堆栈,但堆栈顶部位于低地址。这种格式通常用于需要在程序运行中向堆栈中压入数据的情况。
-
空递增堆栈意味着堆栈从低地址向高地址增长,但在实际使用中,堆栈并未被填满,通常用于动态分配内存的场景,以便在后续操作中逐渐填充数据。
-
空递减堆栈与空递增堆栈相反,它从高地址向低地址增长,但同样未被完全填充。这种格式通常用于动态管理堆栈空间,以便灵活地处理数据。
当然,堆栈满足的栈特性,我们不再赘述
数据传送指令
MOV {cond} {S} Rd, oprand2 MVN {cond} {S} Rd, oprand2
MOV指令用于将一个寄存器的值传送到另一个寄存器。它可以用来加载常数值或者将一个寄存器的内容复制到另一个寄存器。MOV指令支持多种寻址方式,可以直接传递立即数,也可以传递寄存器的值。该指令在进行数据处理时非常常用。
MVN指令则是“Move Negative”的缩写,用于将立即数的补码(即反转所有位并加一)加载到目标寄存器。MVN指令的作用与MOV相似,但它的结果是将目标寄存器设置为立即数的逻辑非(按位取反)。这使得MVN在需要对某个值取反时非常有用。
算数逻辑运算指令
让我们先看看格式:
ADD {cond} {S} Rd, oprand2 ADC {cond} {S} Rd, oprand2 SUB {cond} {S} Rd, oprand2 AND {cond} {S} Rd, oprand2 ORR {cond} {S} Rd, oprand2 EOR {cond} {S} Rd, oprand2 BIC {cond} {S} Rd, oprand2
加法指令
-
ADD:将两个寄存器的值相加,并将结果存储在目标寄存器。
-
ADC:带进位的加法,用于处理多字节加法,考虑了前一操作的进位。
减法指令:
-
SUB:将一个寄存器的值从另一个寄存器中减去,并将结果存储在目标寄存器。
-
SBC:带借位的减法,用于处理多字节减法,考虑了前一操作的借位。
乘法指令:
-
MUL:计算两个寄存器的乘积,并将结果存储在目标寄存器。
-
MLA:乘法后加,计算两个寄存器的乘积并加上第三个寄存器的值。
逻辑运算指令:
-
AND:按位与操作,将两个寄存器的对应位进行与运算。
-
ORR:按位或操作,将两个寄存器的对应位进行或运算。
-
EOR:按位异或操作,将两个寄存器的对应位进行异或运算.
-
BIC:按位清除操作,用于清除一个寄存器中与另一个寄存器对应位相同的位。
移位指令:
-
LSL(逻辑左移):将寄存器的位向左移动,右侧用零填充。
-
LSR(逻辑右移):将寄存器的位向右移动,左侧用零填充。
-
ASR(算术右移):将寄存器的位向右移动,保持符号位。
-
ROR(循环右移):将寄存器的位循环右移。
操作数2的详细说明
我们看到了太多的例子说明oprand2的使用了,现在我们说说oprand2的,这里既可以是一个操作数:#N(N是一个常数),也可以是一个寄存器
比较指令
在ARM架构中,比较指令主要用于对两个操作数进行比较,以设置条件标志而不存储结果。常见的比较指令包括:
CMP指令,用于比较两个寄存器的值。该指令会计算第一个寄存器的值减去第二个寄存器的值,并更新状态寄存器中的条件标志(如零标志、负标志和进位标志)。
CMN指令,用于比较一个寄存器的值与另一个寄存器的值相加。实际上,它执行的是加法运算并更新条件标志。CMN通常用于检查是否存在溢出或负值。
TST指令,用于按位与操作两个寄存器的值,并根据结果更新状态标志。TST不改变任何寄存器的值,只是用于设置条件标志。
TEQ指令,执行按位异或操作以比较两个寄存器的值,并更新状态标志。TEQ也不会改变寄存器的值,而是用于检查两个值是否相等。
这些比较指令为条件执行和分支提供了基础,使得ARM架构能够高效地处理条件逻辑
条件组合指令
在ARM架构中,CPSR(Current Program Status Register)寄存器包含多个标志位,用于指示程序的状态,并用于条件执行指令的判断。这些标志位主要包括:
-
N(Negative Flag):当结果为负数时设置,用于指示最后一次运算的符号。
-
Z(Zero Flag):当结果为零时设置,指示最后一次运算的结果是否为零。
-
C(Carry Flag):在加法操作中,表示是否发生了进位;在减法操作中,表示是否发生了借位。
-
V(Overflow Flag):当发生溢出时设置,指示最后一次运算的结果超出了能够表示的范围。
在ARM指令集中,各种条件码与这些标志位的组合如下:
-
EQ(Equal):条件为真,当Z标志被设置。
-
NE(Not Equal):条件为真,当Z标志未被设置
-
GT(Greater Than):条件为真,当N标志和Z标志都未被设置。
-
LT(Less Than):条件为真,当N标志被设置。
-
GE(Greater Than or Equal):条件为真,当N标志和Z标志一致
-
LE(Less Than or Equal):条件为真,当N标志被设置或Z标志被设置。
-
MI(Minus):条件为真,当N标志被设置。
-
PL(Plus):条件为真,当N标志未被设置。
-
VS(Overflow):条件为真,当V标志被设置。
-
VC(No Overflow):条件为真,当V标志未被设置。
-
HI(Higher):条件为真,当C标志被设置且Z标志未被设置。
-
LS(Lower or Same):条件为真,当C标志未被设置。
-
AL(Always):无条件执行,始终为真。
跳转指令
-
B(Branch):无条件跳转到指定的地址。该指令将程序计数器(PC)更新为目标地址,实现跳转。
-
BL(Branch with Link):无条件跳转并将返回地址(下一条指令的地址)保存到链接寄存器(LR),通常用于调用函数。返回时可以使用BL指令所保存的LR值进行返回。
-
BX(Branch and Exchange):跳转到指定地址,并根据目标地址的最低位来决定是切换到ARM状态还是Thumb状态。如果目标地址最低位为1,则进入Thumb状态;如果为0,则保持ARM状态。
-
CBZ(Compare and Branch if Zero):比较寄存器的值,如果值为零则跳转到指定地址。
-
CBNZ(Compare and Branch if Not Zero):比较寄存器的值,如果值不为零则跳转到指定地址。
-
TBZ(Test and Branch if Zero):测试寄存器中的某一位,如果该位为零则跳转到指定地址。
-
TBNZ(Test and Branch if Not Zero):测试寄存器中的某一位,如果该位不为零则跳转到指定地址。
ARM寻址方式
-
立即数寻址方式使用直接指定的数值作为操作数。立即数通常以特定格式编码在指令中,便于快速访问常量。
-
寄存器寻址方式通过指定一个寄存器来获取操作数,指令直接使用寄存器中的值,适合快速访问数据。
-
基址寻址方式结合基址寄存器和偏移量,使用基址寄存器中的值加上一个立即数或另一个寄存器的值来计算目标地址。这种方式常用于访问数组或结构体元素。
-
索引寻址方式类似于基址寻址,通常用于数组访问。它将一个寄存器作为基址,另一个寄存器作为索引,计算出目标地址。
-
相对寻址方式将当前程序计数器的值与立即数相加,计算出跳转目标地址。该方式广泛应用于控制流指令,如跳转和分支。
-
间接寻址方式通过寄存器中的地址来获取操作数,适合动态数据结构,如链表和树。
ARM伪指令
LDR(Load Register)指令用于从内存加载数据到寄存器。它可以直接使用地址、基址加偏移量的形式,或通过立即数加载常量到寄存器。其基本语法为:
LDR Rn, [address] ADR(Address of Label)是一个伪指令,用于将一个标签(地址)加载到寄存器中。它允许程序员将一个特定的内存地址赋值给寄存器,便于后续的操作。它的语法通常如下:
ADR Rn, label ADR会计算标签所对应的地址,并将其加载到指定的寄存器中。与LDR相比,ADR不会访问内存,只是处理地址,因此通常用于跳转或调用函数时。
ARM汇编程序的基本格式
AREA my_code, CODE, READONLY ; 定义代码区域ENTRY _start ; 定义入口点 _start: ; 程序开始MOV R0, #5 ; 将5放入R0寄存器MOV R1, #10 ; 将10放入R1寄存器ADD R2, R0, R1 ; R2 = R0 + R1 (5 + 10) ; 结束程序MOV R7, #1 ; 系统调用号,1表示退出SVC 0 ; 调用系统服务 AREA my_data, DATA, READWRITE ; 定义数据区域 message: .asciz "Hello, ARM!" ; 字符串数据 END ; 程序结束
上面就是一个最平凡的纯汇编程序,当然,在没有汇编链接器的作用下,不得不使用ENTRY告诉我们的程序入口在哪里。
符号和标号
在ARM汇编程序中,符号和标签是理解和编写程序的关键元素。这些元素不仅帮助程序员组织代码,还增强了可读性和可维护性。
符号在汇编语言中充当标识符,通常用于代表变量、常量、函数或内存地址。它们允许程序员使用更具描述性的名称来代替硬编码的数值或地址,使代码更加易读。例如,假设我们需要在程序中存储一个整数,如果直接使用数字,可能会让人感到困惑。通过定义一个符号,我们可以清晰地表达这个整数的意义,比如“MAX_SIZE”。在代码中引用该符号时,编译器会自动将其替换为相应的值,这样,程序员可以更容易理解和维护代码。
符号的使用不仅限于数据定义。在函数调用和返回值处理中,符号也起到了至关重要的作用。通过符号,程序员可以清楚地指示函数的参数和返回值的位置,而不是依赖于固定的寄存器或内存地址。这种方式减少了错误的可能性,使得函数的使用更加灵活。
标签则是指在代码中用于标识特定位置的名称,通常以一个标识符加冒号的形式出现。标签的主要目的是为跳转指令提供目标地址,使得程序的控制流能够在不同位置之间跳转。在ARM汇编中,常见的控制流指令包括条件跳转和无条件跳转。通过使用标签,程序员可以在执行过程中改变程序的执行路径,实现循环、条件分支和函数调用等功能。
伪操作
查阅下表,伪操作是不隶属于架构体系下的汇编语言,他只是方便!
伪操作 | 说明 |
---|---|
DATA | 指示后续内容为数据段,通常用于定义变量和常量。 |
TEXT | 指示后续内容为代码段,包含可执行指令。 |
BSS | 指示未初始化的全局和静态变量,分配内存但不初始化。 |
GLOBAL | 定义一个符号为全局可见,使得其他文件可以引用。 |
EXTERN | 声明一个外部符号,表示该符号在其他文件中定义。 |
EQU | 定义一个常量符号,使得符号可以在程序中以该值引用。 |
ORG | 设置程序计数器(PC)到指定的地址。 |
SECTION | 定义一个新的节,允许程序员组织代码和数据。 |
ASCIZ | 定义以零结尾的字符串,常用于存储字符串数据。 |
ALIGN | 确保接下来的数据在指定的字节边界上对齐。 |
INCLUDE | 包含其他文件的内容,便于模块化和重用代码。 |
END | 指示汇编文件的结束。 |
SIZE | 定义一个符号的大小,通常与数据结构的长度相关联。 |
TYPE | 定义一个符号的类型,指示该符号是函数、变量等。 |
MACRO | 定义一个宏,可以在程序中多次调用以简化代码。 |
ENDM | 结束宏定义。 |
SET | 定义或修改符号的值。 |