通过反汇编分析,我们可以从体系结构和底层汇编这样一个新视角去窥探程序的运行机制,如函数调用、参数传递、内存中堆栈的动态变化等,会对C语言有一个更深的理解
ARM体系结构
- 计算机的指令集一般可分为4种:复杂指令集(CISC)、精简指令集(RISC)、显式并行指令集(EPIC)和超长指令字指令集(VLIW)。
RISC指令集与CISC指令集的区别
- Load/Store架构:CPU不能直接处理内存中的数据,要先将内存中的数据Load(加载)到寄存器中才能操作,然后将处理结果Store(存储)到内存中。
- 固定的指令长度、单周期指令
- 倾向于使用更多的寄存器来存储数据,而不是使用内存中的堆栈,效率更高。
ARM指令集与RISC的区别
ARM指令集虽然属于RISC,但是和原汁原味的RISC相比,还是有一些差异的
- ARM有桶型移位寄存器,单周期内可以完成数据的各种移位操作
- 并不是所有的
ARM
指令都是单周期的。 - ARM有
16
位的Thumb
指令集,是32
位ARM指令集的压缩形式,提高了代码密度。 - 条件执行:通过指令组合,减少了分支指令数目,提高了代码密度。
- 增加了
DSP、SIMD/NEON
等指令。
ARM处理器工作模式
- 应用程序正常运行时,ARM处理器工作在用户模式(
User mode
),当程序运行出错或有中断发生时,ARM处理器就会切换到对应的特权工作模式。
处理器模式 | 模式编码 | 模式介绍 |
---|---|---|
User mode | 0B10000 | 应用程序正常运行时的工作模式 |
FIQ mode | 0B10001 | 快速中断模式,中断优先级比 IRQ 高 |
IRQ mode | 0B10010 | 中断模式 |
Supervisor mode | 0B10011 | 管理模式,保护模式,复位和软中断时一般都会进入该模式 |
Abort mode | 0B10111 | 数据存取异常、指令读取失败时会进入该模式 |
Undefined mode | 0B11011 | CPU 遇到无法识别、未定义的指令时,会进入该模式 |
System mode | 0B11111 | 类似用户模式,但可运行特权 OS 任务,如切换到其他模式 |
Monitor mode | 0B10110 | 仅限于安全扩展 |
- 处理器处于普通模式,没有权限对内存和底层硬件进行操作
- 首先通过系统调用或软中断进入处理器特权模式,运行操作系统内核或硬件驱动代码,才能对底层的硬件设备进行读写操作。
寄存器
- 在
ARM
处理器内部,除了基本的算术运算单元、逻辑运算单元、浮点运算单元和控制单元,还有一系列寄存器,包括各种通用寄存器、状态寄存器、控制寄存器,用来控制处理器的运行,保存程序运行时的各种状态和临时结果
寄存器分类
ARM处理器中的寄存器可分为通用寄存器和专用寄存器两种
R0~R12
属于通用寄存器- 除了
FIQ
工作模式,在其他工作模式下这些寄存器都是共用、共享的 R0~R3
通常用来传递函数参数,R4~R11
用来保存程序运算的中间结果或函数的局部变量等,R12
常用来作为函数调用过程中的临时寄存器。
ARM处理器有多种工作模式,除了这些在各个模式下通用的寄存器,还有一些寄存器在各自的工作模式下是独立存在的,如
R13
、R14
、R15
、CPSP
、SPSR
寄存器,在每个工作模式下都有自己单独的寄存器
R13
寄存器又称为堆栈指针寄存器(Stack Pointer,SP
),用来维护和管理函数调用过程中的栈帧变化,R13
总是指向当前正在运行的函数的栈帧,一般不能再用作其他用途R14
寄存器又称为链接寄存器(Link Register,LR
),在函数调用过程中主要用来保存上一级函数调用者的返回地址- 寄存器
R15
又称为程序计数器(Program Counter,PC
),CPU从内存取指令执行,就是默认从PC
保存的地址中取的,每取一次指令,PC寄存器的地址值自动增加 - 在ARM三级流水线中,PC指针的值等于当前正在运行的指令地址
+8
- 当前处理器状态寄存器(
Current Processor State Register,CPSR
)主要用来表征当前处理器的运行状态。 - 在每种工作模式下,都有一个单独的程序状态保存寄存器(
Saved Processor State Register
,SPSR)
ARM处理器切换工作模式或发生异常
SPSR
用来保存当前工作模式下的处理器现场,即将CPSR
寄存器的值保存到当前工作模式下的SPSR
寄存器。
从异常返回时
- 从
SPSR
寄存器中恢复原先的处理器状态,切换到原来的工作模式继续运行 FIQ
模式。为了快速响应中断,减少中断现场保护带来的时间开销,在FIQ
工作模式下,ARM处理器有自己独享的R8~R12
寄存器。
ARM汇编指令
一个完整的ARM指令通常由操作码 +
操作数组成
存储访问指令
ARM指令集属于RISC指令集
RISC
处理器采用典型的加载/存储体系结构,CPU无法对内存里的数据直接操作,只能通过Load/Store
指令来实现- ARM处理器属于冯·诺依曼架构,程序和数据都存储在同一存储器上,内存空间和I/O空间统一编址,ARM处理器对程序指令、数据、I/O空间中外设寄存器的访问都要通过
Load/Store
指令来完成。
LDR R1, [R0]; 将R0中的值作为地址,将该地址上的数据保存到R1
STR R1, [R0]; 将R0中的值作为地址,将R1中的值存储到这个内存地址
LDRB / STRB; 每次读写一字节,LDR/STR 默认每次读写四字节
LDM / STM; 批量装载/存储指令,在一组寄存器和一片内存之间传输数据
SWP R1, R1, [R0]; 将R1与R0中地址指向的内存单元中的数据进行交换
SWP R1, R2, [R0]; 将R0存储到R1,将R2写入[R0]这个内存存储单元
LDR/STR、LDM/STM
LDM/STM
指令常用来加载或存储一组寄存器到一片连续的内存,通过和堆栈格式符组合使用,LDM/STM
指令还可以用来模拟堆栈操作
堆栈格式 | 说明 | 备 注 |
---|---|---|
FA | Full Ascending | 满递增堆栈 |
FD | Full Descending | 满递减堆栈 |
EA | Empty Ascending | 空递增堆栈 |
ED | Empty Descending | 空递减堆栈 |
ARM
处理器使用的一般都是满递减堆栈
- 在一个堆栈内存结构中,如果堆栈指针
SP
总是指向栈顶元素,那么这个栈就是满栈; - 如果堆栈指针
SP
指向的是栈顶元素的下一个空闲的存储单元,那么这个栈就是空栈。 - 如果栈指针
SP
从高地址往低地址移动,那么这个栈就是递减栈;
在入栈和出栈过程中要留意栈中各个元素的入栈出栈顺序。
- 将一组寄存器入栈,或者从栈中弹出一组寄存器:
LDMFD SP!,{R0-R2,R14} ; 将内存栈中的数据依次弹出到 R14,R2,R1,R0
STMFD SP!,{R0-R2,R14} ; 将R0,R1,R2,R14依次压入内存栈
元素在入栈操作时,STMFD
会根据大括号{}
中寄存器列表中各个寄存器的顺序,从左往右依次压入堆栈
ARM还专门提供了
PUSH
和POP
指令来执行栈元素的入栈和出栈操作。PUSH和POP指令的使用方法如下。[插图]
PUSH {R0-R2,R14} ; 将R0、R1、R2、R14依次压入栈
POP {R0-R2,R14} ; 将栈中的数据依次弹出到 R14、R2、R1、R0
数据传输指令
LDR/STR
指令用来在寄存器和内存之间输送数据- 在寄存器之间传送数据,则可以使用
MOV
指令
MOV R1, #1 ;将立即数1发送到寄存器R1中
MOV R1, R0 ;将R0寄存器中的值传送到R1寄存器中
MOV PC, LR ;子程序返回
MVN R0, #0xFF ;将立即数0xFF取反后赋值给R0
MVN R0, R1 ;将R1寄存器的值取反后赋值给R0
比较指令
比较指令用来比较两个数的大小,或比较两个数是否相等
- 比较指令的运算结果会影响
CPSR
寄存器的N
、Z
、C
、V
标志位
条件执行指令
条件执行经常出现在跳转或循环的程序结构中
跳转指令
BL
跳转指令表示带链接的跳转
- 在跳转之前,
BL
指令会先将当前指令的下一条指令地址(即返回地址)保存到LR
寄存器中,然后跳转到label
处执行。 - 子函数执行结束后,
LR
寄存器中的地址被赋值给PC
,处理器就可以返回到原来的主函数中继续运行
BX
表示带状态切换的跳转
Rm
寄存器中保存的是跳转地址,要跳转的目标地址处可能是ARM
指令,也可能是Thumb
指令- 处理器根据
Rm[0]
位决定是切换到ARM状态还是切换到Thumb状态。
BLX
指令是BL
指令和BX
指令的综合,表示带链接和状态切换的跳转
ARM寻址方式
- ARM属于RISC体系架构,一个ARM汇编程序中的大部分汇编指令,基本上都和数据传输有关:在内存-寄存器、内存-内存、寄存器-寄存器之间来回传输数据。
寻址方式
- 比较常见的寻址方式有寄存器寻址、立即寻址、寄存器偏移寻址、寄存器间接寻址、基址寻址、多寄存器寻址、相对寻址等。
寄存器寻址
- 操作数保存在寄存器中,通过寄存器名就可以直接对寄存器中的数据进行读写
MOV R1, R2 ;;将寄存器 R2 中的值传递到 R1
ADD R1, R2, R3 ;;运行减法运算 R2-R3,并将结果保存到 R1 中
立即数寻址
- 在立即数寻址中,
ARM
指令中的操作数为一个常数。立即数以#
为前缀,0x
前缀表示该立即数为十六进制,不加前缀默认是十进制
ADD R1, R1, #1 ;将R1寄存器中的值加1,并将结果保存到R1中
MOV R1, #0xFF ;将十六进制常数0xFF写入R1寄存器中
MOV R1, #12 ;将十进制常数12放入R1寄存器中
ADD R1, R1, #16, 20 ;R1 = R1 + 立即数16循环右移20位
寄存器偏移寻址
- 寄存器偏移寻址可以看作寄存器寻址的一种特例
MOV R2, R1, LSL #3 ;R2 = R1 << 3
ADD R3, R2, R1, LSL #3 ;R3 = R2 + R1 << 3
ADD R3, R2, R1, LSL R0 ;R3 = R2 + R1 << R0
常见的移位操作有逻辑移位和算术移位
- 逻辑移位无论是左移还是右移,空缺位一律补
0
;而算术移位则不同,左移时空缺位补0,右移时空缺位使用符号位填充。
寄存器间接寻址
寄存器间接寻址主要用来在内存和寄存器之间传输数据
- 寄存器中保存的是数据在内存中的存储地址,我们通过这个地址就可以在寄存器和内存之间传输数据
C
语言中的指针操作,在汇编层次其实就是使用寄存器间接寻址实现的
LDR R1, [R2] ;将R2中的值作为地址,取该内存地址上的数据,保存到R1
STR R1, [R2] ;将R1寄存器的值写入该内存地址
基址寻址
- 基址寻址其实也属于寄存器间接寻址。两者的不同之处在于,基址寻址将寄存器中的地址与一个偏移量相加,生成一个新地址,然后基于这个新地址去访问内存。
- 基址寻址一般用在查表、数组访问、函数的栈帧管理等场合
LDR R1, [FP, #2] ;将FP中的值加2作为新地址,取该地址上的值保存到R1
LDR R1, [FP, #2]!;FP=FP+2,然后将FP指定的内存单元数据保存到R1中
LDR R1, [FP, R0] ;将FP+R0作为新地址,取该地址上的值保存到R1
LDR R1, [FP, R0, LSL #2] ;将FP+R0<<2作为新地址,读取该内存地址上的值保存到R1
LDR R1, [FP], #2 ;将FP中的值作为地址,读取该地址上的值保存到R1,然后FP中的值加2
STR R1, [FP, #-2];将FP中的值减2,作为新地址,将R1中的值写入该地址
STR R1, [FP], #-2;将FP中的值作为地址,将R1中的值写入此地址;然后FP中的值减2
多寄存器寻址
STM/LDM
指令就属于多寄存器寻址,一次可以传输多个寄存器的值
LDMIA SP!, {R0-R2,R14} ;将内存栈中的数据依次弹出到 R14,R2,R1,R0
STMDB SP!, {R0-R2,R14} ;将R0,R1,R2,R14依次压入内存栈
LDMFD SP!, {R0-R2,R14} ;将内存栈中的数据依次弹出到 R14,R2,R1,R0
STMFD SP!, {R0-R2,R14} ;将R0,R1,R2,R14依次压入内存栈
连续的寄存器,还可以使用连接符-连接
- 栈是程序运行过程中非常重要的一段内存空间,栈是C语言运行的基础,函数内的局部变量、函数调用过程中要传递的参数、函数的返回值一般都是保存在栈中的
- 在嵌入式系统的一些启动代码中,在运行C语言程序之前,必须要先运行一段汇编代码初始化内存和栈指针SP,然后才能跳到C语言程序中运行。
ARM没有专门的入栈和出栈指令,ARM中的栈操作其实就是通过上面所讲的
STM/LDM
指令和栈指针SP
配合操作完成的
ARM
默认使用满递减堆栈STMFD/LDMFD
指令配对使用,完成堆栈的入栈和出栈操作- ARM中的
PUSH
和POP
指令其实就是LDM/STM
的同义词,是LDMFD
和STMFD
组合指令的助记符。
STMFD SP!, {R0-R2,R14} ;将R0、R1、R2、R14依次压入内存栈
LDMFD SP!, {R0-R2,R14} ;将内存栈中的数据依次弹出到 R14、R2、R1、R0
PUSH {R0-R2,R14} ;将R0、R1、R2、R14依次压入栈
POP {R0-R2,R14} ;将栈中的数据依次弹出到 R14、R2、R1、R0
相对寻址
相对寻址其实也属于基址寻址
- 是基址寻址的一种特殊情况。它是以
PC
指针作为基地址进行寻址的,以指令中的地址差作为偏移,两者相加后得到的就是一个新地址 - 很多与位置无关的代码,如动态链接共享库,其在汇编代码层次的实现其实也是采用相对寻址的
- 程序中使用相对寻址访问的好处是不需要重定位,将代码加载到内存中的任何地址都可以直接运行。
3.4 ARM伪指令
ARM伪指令并不是
ARM
指令集中定义的标准指令,而是为了编程方便,各家编译器厂商自定义的一些辅助指令
- 伪指令有点类似C语言中的预处理命令,在程序编译时,这些伪指令会被翻译为一条或多条ARM标准指令
- 常见的ARM伪指令主要有4个:
ADR
、ADRL
、LDR
、NOP
ADR R0, LOOP ;将标号LOOP的地址保存到R0寄存器中
ADRL R0, LOOP ;中间范围的地址读取
LDR R0, =0x30008000 ;将内存地址0x30008000赋予R0
NOP ;空操作,用于延时或插入流水线中暂停指令的运行
- NOP伪指令比较简单,其实就相当于MOV R0,R0。
LDR伪指令
- ARM属于RISC架构,不能对内存中的数据直接操作,ARM通常会使用
LDR/STR
这对加载/存储指令,先将内存中的数据加载到寄存器,然后才能对寄存器中的数据进行操作,最后把寄存器中的处理结果存储到内存中 LDR
伪指令的主要用途是将一个32位的内存地址保存到寄存器中。- RISC指令的特点是单周期指令,指令的长度一般都是固定的。在一个32位的系统中,一条指令通常是32位的,指令中包括操作码和操作数, ARM指令的编码格式指令中的操作码和操作数共享32位的存储空间:一般前面的操作码要占据几个比特位,剩下来的留给操作数的编码空间就小于
32
位。 - 为了与
ARM
指令集中的加载指令LDR
区别开来,LDR伪指令中的操作数前一般会有一个等于号=
,用来表示该指令是个伪指令。
通过
LDR
伪指令,编译器就解决了向一个寄存器传送32
位的立即数时指令无法编码的难题
- 在程序编译期间,这些伪指令会被标准的ARM指令替代。编译器在处理伪指令时,根据伪指令中的操作数大小,会使用不同的ARM标准指令替代。
- 存放这些32位地址常量的文字池一般紧挨着当前指令的代码段,直接放置在当前代码段的后面。
LDR R0, =0x30008000 ;将立即数0x30008000送入R0
LDR R0, =LOOP ;将标号LOOP表示的地址送入R0
LDR R0, [R1] ;将R1中的值作为地址,取该地址上的值送入R0
LDR R0, [PC, #LOOP] ;将标号LOOP表示的内存地址上的数据送入R0
ADR伪指令
ADR
伪指令的功能与LDR
伪指令类似,将基于PC
相对偏移的地址值读取到寄存器中。
- ADR为小范围的地址读取伪指令,底层使用相对寻址来实现,因此可以做到代码与位置无关
ADR
伪指令和LDR
伪指令
- 两者都是为了加载一个地址到指定的寄存器中
LDR
伪指令通常被翻译为ARM
指令集中的LDR
或MOV
指令ADR
伪指令则通常会被ADD
或SUB
指令代替。
用途上
- LDR伪指令主要用来操作外部设备的寄存器
- ADR伪指令主要用来通过相对寻址,生成与位置无关的代码
- LDR使用绝对地址,而ADR则使用相对地址
- LDR和ADR伪指令的地址适用范围也不同,LDR伪指令适用的地址范围为
[0,32GB]
,而ADR伪指令则要求当前指令和标号必须在同一个段中,地址偏移范围也较小,地址对齐时偏移范围为[0,1020]
,地址未对齐时偏移范围为[0,4096]
。
ARM汇编程序设计
ARM汇编程序是以段(
section
)为单位进行组织的
- 在一个汇编文件中,可以有不同的
section
,分为代码段、数据段等,各个段之间相互独立 - 由
AREA
伪操作来标识一个段的起始、段名、段的属性(CODE
、DATA
)和读写权限(READONLY
、READWRITE
)。 ARM
汇编程序通过ENTRY
这个伪操作来标识汇编程序的运行入口,使用伪操作END
来标识汇编程序的结束。- 在
ARM
汇编程序中可以使用标号。像C语言一样,在汇编语言中,标号代表的指令地址 - 在汇编程序中使用分号
;
来注释代码。在一个空行的行首或者一条指令语句的末尾添加一个分号,然后就可以在分号后面添加注释,以增加程序的可读性。
符号与标号
使用符号来标识一个地址、变量或数字常量。当用符号来标识一个地址时,这个符号通常又被称为标号。
- 符号的命名规则和
C
语言的标识符命名规则一样:由字母、数字和下画线组成,符号的开头不能使用数字,但标号除外。 - 符号的命名在其作用域内必须唯一,不能与系统内部或系统预定义的符号同名,不能与指令助记符、伪指令同名,作用域是整个汇编源文件
- 直接通过数字
[0,99]
而不是使用字符来进行地址引用,我们称这种数字为局部标号。局部标号的作用域为当前段
伪操作
- 在汇编语言中,为了编程方便,汇编器也定义了一些特殊的指令助记符,以方便对汇编程序做各种处理。如使用
AREA
来定义一个段(section
),使用GBLA
来定义一个数据,使用ENTRY
来指定汇编程序的执行入口等,这些指令助记符统称为伪指令或伪操作
GBLA a ;定义一个全局算术变量a,并初始化为0
a SETA 10 ;给算术变量a赋值为10
GBLL b ;定义一个全局逻辑变量b,并初始化为{false}
b SETL 20 ;给逻辑变量b赋值为20
GBLS STR ;定义一个全局字符串变量STR,并初始化为0
STR SETS "zhaiXue.cc" ;给变量STR赋值为"zhaiXue.cc"
LCLA a ;定义一个局部算术变量a,并初始化为0
LCLL b ;定义一个局部逻辑变量b,并初始化为{false}
LCLS name ;定义一个局部字符串变量name,并初始化为0
name SETS "wanglitao";给局部字符串变量赋值
- 关于数据定义,常用的伪操作有DCD、DCB、SPACE、DATA
DATA1 DCB 10,20,30,40 ;分配一片连续的字节存储单元并初始化
STR DC "zhaiXue.cc" ;给字符串分配一片连续的存储单元并初始化
DATA2 DCB 10,20,30,40 ;分配一片连续的字存储单元并初始化
BUF SPACE 100 ;给 BUF 分配 100 字节的存储单元并初始化为 0
C语言和汇编语言混合编程
- 在ARM启动代码中,系统一上电首先运行的是汇编代码,等初始化好内存堆栈环境后,才会跳到C程序中执行。
ATPCS
ATPCS
的全称是ARM-Thumb Procedure Call Standard
,其核心内容就是定义了ARM
子程序调用的基本规则及堆栈的使用约定等。- ATPCS规定了ARM程序要使用满递减堆栈,入栈/出栈操作要使用
STMFD/LDMFD
指令
ATPCS最重要的内容是定义了子程序调用的具体规则
- 子程序间要通过寄存器
R0~R3
(可记作a0~a3)传递参数,当参数个数大于4
时,剩余的参数使用堆栈来传递。 - 子程序通过
R0~R1
返回结果。 - 子程序中使用
R4~R11
(可记作v1~v8)来保存局部变量 - R12作为调用过程中的临时寄存器,一般用来保存函数的栈帧基址,记作
FP
。 R13
作为堆栈指针寄存器,一般记作SP
R14
作为链接寄存器,用来保存函数调用者的返回地址,记作LR
R15
作为程序计数器,总是指向当前正在运行的指令,记作PC
。- 为了能在C程序中内嵌汇编代码,ARM编译器在
ANSI C
标准的基础上扩展了一个关键字__asm
。通过这个关键字,我们就可以在C程序中内嵌ARM汇编代码
__asm /* 开始汇编代码块 */
{
指令 /* 我是注释 */
...
[指令]
}
- 如果在内嵌的汇编代码中添加注释,记得要使用
C
语言的/**/
注释符,而不是汇编语言的分号注释符。 - 不同的编译器基于ANSI C标准扩展了不同的关键字,使用的汇编格式可能也不太一样
GNU ARM
编译器提供了一个__asm__
关键字(使用__volatile__
关键字修饰,用来告诉编译器不要优化)
__asm__ __volatile__(
"汇编语句;"
...
"汇编语句;"
);
GNU ARM汇编语言
- 编译器:用来将
C
源文件编译成汇编文件。 - 汇编器:用来将汇编文件汇编成目标文件。
- 链接器:用来将目标文件组装成可执行文件。
- 二进制转化工具:
objdump
、objcopy
、strip
等。 - 库打包工具:ar。
- 调试工具:
gdb
、nm。 - 库/头文件:根据C语言标准定义的API实现的C标准库及对应的头文件。
GNU ARM编译器的伪操作
ARM 编译器 | GNU ARM 编译器 | 伪操作说明 |
---|---|---|
AREA copy, CODE,… | .text | 定义一个代码段 |
AREA , dat, DATA,… | .data | 定义一个数据段 |
使用; | 注释 | 使用 /*/ 或 @ 注释 汇编程序中的注释方式 |
DCD | .long.word | 分配一片连续的字存储单元 |
Entry | ENTRY(_start) | 汇编程序的执行入口 |
END | .end | 汇编程序的结束标记 |
CODE32 | .arm / .code 32 | 告诉编译器后面指令为ARM指令 |
CODE16 | .thumb / .code 16 | 告诉编译器后面指令为THUMB指令 |
SPACE | .space | 分配一片连续的内存并初始化为0 |
GBLL、GBLA | .global | 定义一个全局变量 |
EXPORT、GLOBAL | .global | 全局符号声明,可以被其他文件引用 |
IMPORT、EXTERN | .extern | 引用其他文件的全局符号前要先声明 |
EQU、SETL、SETA | .equ_set | 赋值语句,为一个变量赋值 |
IF、ELSE、ENDIF | .ifdef.else .endif | 条件汇编 |
MACRO / MEND | .macro / .endm | 宏定义 |
GET INCLUDE | .include | 文件包含,并展开编译 |
INCBIN | .incbin | 文件包含,不编译 |
- GNU ARM汇编语言中的标识符可以由字母、数字、下画线和
.
构成,局部标号可以由纯数字构成 - GNU ARM汇编语言使用标号
_start
作为汇编程序的入口
.section伪操作
- 在GNU ARM汇编语言中,用户可以使用
.section
伪操作自定义一个段
.section ".mysection", "awx" // 定义一个可写、可执行的段
.align 2
- 在使用伪操作
.section
定义一个段时,每个段以段名开始,以下一个段名或文件结尾作为结束标记
基本数据格式
- 二进制数据通常以
0B
或0b
开头,八进制数据以0
开头,十六进制数据以0x
开头,十进制数据则以非0数字开头。负数前面加“-
”,取补用“~
”,不相等用“<>
”,其他运算符号如+、-、*、%、<、<<、>、>>、|、&、^、!、==、>=、&&
与C语言语法相似。 - 使用
.ascii
定义字符串时要自行在结尾加'\0'
- 使用
.string
伪操作可以定义多个字符串 - 使用
.asciz
伪操作可以定义一个以NULL
字符结尾的字符串 - 使用
.rept
伪操作可以重复定义数据。
在GNU ARM汇编程序中经常使用小圆点
.
表示当前指令的地址
数据定义
- 在GNU ARM汇编程序中,如果我们想定义一个浮点数
标签 f:
.float 3.14
.equ f, 3.1415
- 使用
.float
伪操作定义一个浮点数f
,并初始化为3.14。如果你想将这个浮点数重新赋值为3.1415
,则可以通过.equ
伪操作来完成 .equ
伪操作除了给数据赋值,还可以把常量定义在代码段中,然后在代码中直接引用。类似C语言中的#define
宏定义。