内存管理-17-Arm64汇编

一、指令


1. lsr

LSR是ARM架构的位移动指令,用于逻辑右移操作。它将第一个操作数的位向右移动指定位数,并根据需要将符号位(在有符号数操作中)扩展到空出来的位。

语法:

LSR{条件}{S} 移位量,寄存器

条件是可选的,指定为如 EQ、NE 等,用来指明只有在特定条件下才能执行指令。
S 是可选的,指定为 S 表示指令应该影响 CPSR 中的条件标志位,不指定则不影响条件标志位。
移位量是一个立即数,用来指定位移动的位数,范围通常是0到31。
寄存器是一个通用寄存器,包含要移位的值。

实例代码:

LSR R0, R0, #5 ; 将寄存器R0中的值逻辑右移5位

这条指令将寄存器R0中的值移动5位右边的位进入CPSR中,左边空出的位用0填充,符号位扩展也遵循这个规则。


2. add

ADD 用于执行加法操作。它可以用于两个通用寄存器、一个通用寄存器和一个寄存器的内存地址、或者一个寄存器和一个立即数之间的加法。

以下是一些使用 ADD 指令的例子:

两个通用寄存器之间的加法:

ADD w0, w1, w2 ; 将 w1 和 w2 的内容相加,结果存储在 w0 中

通用寄存器与内存地址之间的加法:

ADD w0, w1, [x2] ; 将 w1 和内存地址 x2 指向的值相加,结果存储在 w0 中

通用寄存器与立即数之间的加法:

ADD w0, w1, #10 ; 将 w1 和立即数 10 相加,结果存储在 w0 中

请注意,ADD 指令可以有条件执行,例如,ADD 指令可以跟有条件后缀,如 EQ、NE、HS 等,这样只有在特定条件满足时才会执行加法操作。

 

3. orr

orr 是 ARM 汇编语言中的一个逻辑指令,用于执行两个值的按位或操作。orr 指令可以对两个寄存器或寄存器和立即数进行操作。

语法格式如下:

orr {<cond>} <rd>, <rn>, <operand2>

<cond> 是可选的条件代码。
<rd> 是目标寄存器,用于保存结果。
<rn> 是第一个源寄存器,其内容将被操作。
<operand2> 是第二个源操作数,可以是一个寄存器或立即数值。

实例代码:

orr r0, r1, #0xF ; 将寄存器 r1 的值与立即数 0xF 进行按位或操作,结果存储在 r0 中
orr r2, r3, r4 ; 将寄存器 r3 的值与寄存器 r4 的值进行按位或操作,结果存储在 r2 中

在这个例子中,r0 将会是 r1 或上 0xF 的结果,r2 将会是 r3 或 r4 的结果。


4. str

str 是 ARM 汇编语言中的一个指令,用于将数据从寄存器传送到内存地址。####在 ARM 架构中,有多种不同的 str 指令,用于处理不同的数据类型和操作模式。

以下是一些使用 str 指令的示例:

(1) 将一个32位的寄存器值存储到内存中:

str r1, [r2]

这条指令会将寄存器 r1 的值存储到 r2 寄存器值指定的内存地址中。

(2) 将一个64位的寄存器值存储到内存中:

str r1, [r2]
str r1, [r2, #8]!

第一条指令会将 r1 的低64位存储到内存地址中。第二条指令会将 r1 的高64位存储到内存地址中,并且更新 r2 的值(使用后续的内存地址)。

(3) 将一个32位的寄存器值存储到带有偏移的内存地址中:

str r1, [r2, #16]

这条指令会将 r1 的值存储到 r2 的值加上16后得到的内存地址中。

(4) 将一个32位的寄存器值存储到带有索引的内存地址中:

str r1, [r2, lr]

这条指令会将 r1 的值存储到 r2 的值加上 lr 寄存器值得到的内存地址中。

(5) 将一个32位的常数存储到内存中:

str w3, [sp, #-4]!

这条指令会将常数w3存储到栈上,并更新 sp 寄存器的值(向下增长)。

(6) 将一个16位的寄存器值存储到内存中:

strh r1, [r2]

这条指令会将 r1 的低16位存储到内存地址中。

(7) 将一个8位的寄存器值存储到内存中:

strb r1, [r2]

这条指令会将 r1 的低8位存储到内存地址中。

(8) 使用 str 指令复制数据:

str r1, [r2]

这条指令会将 r1 寄存器中的值复制到 r2 寄存器指定的内存地址中。

注意:在使用 str 指令时,确保目标地址是可写的,否则会产生异常。

补充:这里给出不同指令下进行数据操作的位数:
ldrb/strb 8位操作
ldrh/strh 16位操作
ldr/str 32位操作
ldrd/strd 64位操作


5. .macro

.macro 是 ARM 汇编语言中用于定义宏的指令。宏是一段可以重复使用的汇编代码,类似于 C 语言中的宏。使用 .macro 指令定义宏,使用 .endm 指令结束宏定义。

下面是一个简单的 .macro 使用例子:

.macro BLK_MOV_ALIGNED_I4 inptr, outptr, len, incr
mov x0, \inptr
mov x1, \outptr
mov x2, \len
mov x3, \incr
blk_mov_aligned_i4 x0, x1, x2, x3
.endm

在这个例子中,BLK_MOV_ALIGNED_I4 是宏的名称。宏接受四个参数:inptr, outptr, len, 和 incr。这些参数在宏的代码中通过 \param 的方式使用,其中 \inptr 代表输入指针参数,\outptr 代表输出指针参数,依此类推。

当宏被调用时,例如通过 BLK_MOV_ALIGNED_I4 x0, x1, x2, x3,它将把 x0, x1, x2, x3 分别赋值给 \inptr, \outptr, \len, \incr,然后执行 blk_mov_aligned_i4 x0, x1, x2, x3 指令。

注意:这只是一个假设的 blk_mov_aligned_i4 实现的宏例子,实际的 blk_mov_aligned_i4 是内核提供的一个函数,宏用于封装函数调用的参数。


6. mov

在ARM架构中,mov 指令用于数据传送。在ARMv8 (arm64) 汇编中,mov 指令可以用于不同的数据类型,包括通用寄存器,寄存器的特定位,或者是寄存器和内存之间的数据传送。

以下是一些使用 mov 指令的例子:

(1) 将一个常数加载到寄存器中:

mov x0, #0x12345678 // 将常数0x12345678加载到x0寄存器中

(2) 将一个寄存器的值复制到另一个寄存器:

mov x1, x0 // 将x0寄存器中的值复制到x1寄存器中

(3) 将内存地址中的值加载到寄存器中:

mov x2, [x1] // 将x1寄存器指向的内存地址中的值加载到x2寄存器中

(4) 将一个特定的位从一个寄存器复制到另一个寄存器:

movk x0, #0x12, lsl #16 // 将0x12左移16位后,加载到x0寄存器的高16位中

(5) 将一个寄存器的值复制到另一个特定的寄存器(例如,将程序计数器的值复制到x0寄存器):

mov x0, pc // 将程序计数器的值复制到x0寄存器中

(6) 将一个寄存器的值复制到另一个特定的寄存器(例如,将链接寄存器x30的值复制到x0寄存器):

mov x0, x30 // 将链接寄存器的值复制到x0寄存器中

注意:在使用 mov 指令时,确保目标寄存器和源操作数的大小是兼容的,例如,不能直接将一个32位的值移动到一个64位的寄存器中,除非使用适当的扩展指令(如 movw 或 movn)。


7.  b.ls

b.ls 是一条条件分支指令,在 ARM 64 位汇编中,用于“less than and equal”的条件分支,即当比较的两个值中一个小于或等于另一个时,程序会跳转到指定的位置执行。

这条指令的全称是 "Branch Less Than or Equal",其语法格式如下:

b.ls <target_address>

其中 <target_address> 是你想要跳转到的地址。这条指令需要和状态寄存器中的条件标志一起工作,比如之前的一个比较指令 cmp 或者 ccmp 等可以设置条件标志的指令。

下面是一个使用 b.ls 的简单例子:

cmp x0, x1 // 比较寄存器x0和x1
b.ls label // 如果x0小于或等于x1,则跳转到label处执行

在这个例子中,cmp 指令用于比较寄存器 x0 和 x1,然后 b.ls 指令会检查比较的结果,如果 x0 小于或等于 x1,则程序会跳转到标签 label 指定的位置执行。

注意:b.ls 是 ARM 汇编的一个示例,具体的汇编语言和指令集可能会根据不同的处理器架构有所不同。


8. adrp

ADRP 是 ARM 64 位汇编语言中的一个指令,用于生成一个基于程序计数器 PC 的相对偏移地址。ADRP 指令通常用于加载 4KB 对齐的内存地址,###### 通过页(4KB)对齐的方式,可以有效地利用硬件的缓存和内存系统。

ADRP 指令的格式如下:ADRP register, label | offset

其中,register 是目标寄存器,label 是代码中的标签或者其他位置的标签引用,offset 是一个21位的有符号偏移量。下面是一个使用 ADRP 指令的简单例子:

ADRP X0, LABEL  // 加载标签LABEL的地址到X0寄存器
 
LABEL:
    MOV X1, X0  // 将LABEL的地址移动到X1

在这个例子中,ADRP 用于加载标签 LABEL 的地址到寄存器 X0。注意,ADRP 仅用于页对齐的地址,并且只能加载到64位寄存器中。如果需要非对齐地址或更精确的加载,你可能需要结合使用 ADR 或其他内存加载指令。

 

补充:

adrp <寄存器>,<立即数>, 作用是把pc寄存器跟立即数按照一定规则计算后赋值给寄存器。这个规则就是将pc指针的低12bit清0,然后加上<立即数>左移12bit后的值,将结果赋值给<寄存器>

adrp x8,1 //等效与 x8 = pc & ~0xfff + 1<<12

 

9. ldr

LDR 是 ARM 汇编中的一个 load register 指令,用于将数据从内存中加载到寄存器中。对于 ARM64 汇编,LDR 指令有多种变体,用于处理不同的数据加载和地址计算需求。

以下是一些使用 ARM64 汇编语言中 LDR 指令的示例:

将一个常量加载到寄存器中:

ldr x0, [0x12345678] // 将内存地址 0x12345678 处的值加载到寄存器 x0 中
将一个地址处的值加载到寄存器中,该地址是另一个寄存器的偏移量:

ldr x0, [x1, #0x10] // 将内存地址为 x1 寄存器值加上 0x10 的地址处的值加载到 x0 寄存器中
将一个符号地址处的值加载到寄存器中:

ldr x0, LABEL // 将标签 LABEL 表示的地址处的值加载到 x0 寄存器中
// ...
LABEL:
.quad 0x12345678 // 一些数据
将一个地址处的值加载到寄存器中,该地址是另一个寄存器的值与一个常数的和:

ldr x0, [x1, #0x10]! // 将内存地址为 x1 寄存器值加上 0x10 的地址处的值加载到 x0 寄存器中,同时将新地址写回到 x1 寄存器
使用 LDR 指令加载一个函数指针到寄存器中:

ldr x16, .LPIC0 // 加载一个 PIC (Position Independent Code) 偏移量到 x16 寄存器
// ...
.LPIC0:
.quad LPIC-.-
在使用 LDR 指令时,确保你的操作数和操作对应的寄存器是兼容的,并且考虑到 ARM64 寄存器的命名规则。此外,还需要注意指令的字节序和对齐问题。

举例:

LDR R0, [R1]    //R1寄存器对应地址的数据被取出放入R0
LDR R0, =NAME   //R0寄存器的值将为NAME标号对应的地址。
LDR R0, =0X123  //R0寄存器的值将为立即数的值

 

10. bl

bl 是 ARM 汇编语言中的一个跳转指令,用于调用子程序。bl 指令在跳转到子程序前会将下一条指令的地址保存在链接寄存器(LR)中,这样子程序在返回时可以正确继续执行。

bl 指令的格式如下:bl <target_label>

其中 <target_label> 是你想要跳转到的子程序的标签。

例如,假设你有一个子程序 my_subroutine,你可以使用 bl 指令从另一个标签(比如 my_label)调用它:

my_label:
    ...
    bl my_subroutine
    ...
 
my_subroutine:
    ...
    mov x0, #0  @ 返回0
    ret         @ 返回到bl指令后续指令

在这个例子中,当执行到 bl my_subroutine 时,执行流会跳转到 my_subroutine,并保存 my_label 后面的指令地址以便返回。当 my_subroutine 执行完毕并使用 ret 指令返回时,执行流会回到保存在 LR 中的地址继续执行。

 

11. stp

stp 是 ARM 汇编语言中的一个指令,用于将一组寄存器的值存储到内存中。stp 是 "Store Registers in Pair" 的缩写。

语法:

stp { xn | sp }, { xn | sp }, [r], {#imm}

参数:

{ xn | sp } 是一个寄存器,可以是 x30(通常是 lr 或链接寄存器)或 sp(堆栈指针)。
{ xn | sp } 是另一个寄存器,用于与第一个寄存器配对。
[r] 是目标内存地址的基地址寄存器。
{#imm} 是一个可选的偏移量,用于指定存储位置的具体偏移量。

示例代码:

stp x29, x30, [sp, -16]! // 将 x29 和 x30 压栈,并更新 sp 的值

在这个例子中,stp 指令将寄存器 x29 和 x30 的值存储到当前堆栈顶部,然后将堆栈顶部向下移动 16 字节,并更新堆栈指针 sp。后缀 ! 表示压栈后对寄存器 sp 进行修改

stp x2, x3, [x0, #16]  //将x2和x3压栈,在压栈前向上移动16字节。


12. b.ne

b.ne 是 ARM 汇编语言中的一个条件分支指令,它的全称是 "Branch if Not Equal",也就是当两个操作数不相等时才会跳转。

这个指令通常用于比较两个寄存器或者寄存器和立即数的值,然后根据比较结果是否相等来决定是否跳转到另一个标签(Label)或者指定的地址。

下面是一个使用 b.ne 指令的简单例子:

cmp x0, x1 // 比较寄存器x0和x1的值
b.ne label // 如果不相等,跳转到标签label处执行

在这个例子中,cmp 指令用于设置条件标志位,根据 x0 和 x1 的值进行比较。b.ne 指令检查不相等(NE)标志位,如果该标志被设置(表示x0不等于x1),则执行跳转到标签 label。

需要注意的是,b.ne 是一个短跳转指令,它的跳转范围是有限的,通常在当前指令的后面或者前面128MB范围内。#######如果需要更远的跳转,你应该使用 b 指令或其他分支指令。


13. cmp

CMP 是 ARM 汇编语言中的一个指令,用于比较两个操作数。当两个操作数作减法时,如果 Minuend(被减数)大于 Subtrahend(减数),则结果为正;如果 Minuend(被减数)小于 Subtrahend(减数),则结果为负;如果两者相等,结果为零。

CMP 指令不会改变操作数本身,只会设置一些特殊的程序状态寄存器(PSR)的标志位,这些标志位可以用来决定接下来应该执行什么样的代码分支。

下面是一些使用 CMP 指令的例子:

(1) 基本的比较操作:

cmp r0, r1 @ 将寄存器 r0 的值和 r1 的值进行比较

(2) 比较立即数:

cmp r0, #0 @ 将寄存器 r0 的值和立即数 0 进行比较

(3) 比较并根据结果决定是否跳转:

cmp r0, r1
beq next @ 如果 r0 等于 r1,则跳转到标签 next 的指令

(4) 比较数组中的元素:

ldr r2, [r0], #4 @ 加载数组的下一个元素到 r2
cmp r2, r1 @ 将 r2 和 r1 进行比较
bne difference @ 如果 r2 和 r1 不相等,则跳转到标签 difference 的指令

注意:CMP 指令不会改变操作数本身,只会改变状态寄存器的标志位。如果你需要保留操作数用于其他目的,你应该在执行 CMP 指令后将操作数拷贝到另一个寄存器。


14. dc

在ARM架构中,DC是一个用于数据复制的指令,通常用于初始化数据。DC指令是ARM汇编器的扩展,用于在ELF (Executable and Linkable Format) 文件中生成一个专用的 .data.rel.ro 节。

DC指令的语法如下:

DC <constant> ; 复制一个常量到寄存器
DC { <constant_expression> } ; 复制一个常量表达式到寄存器
DC { <symbol> } ; 复制一个符号的地址到寄存器
DC { :<alignment> } ; 设置下一个数据标签的对齐
DC { ^ } ; 结束数据标签的定义
例如,以下是一个使用DC指令的简单ARM64汇编代码片段:

.data
.align 3
my_data:
DC 42 ; 将常量42初始化到一个字
DC 23 ; 将常量23初始化到下一个字
DC {my_symbol} ; 将符号my_symbol的地址初始化到一个字
DC {^} ; 结束数据初始化

在这个例子中,my_data 被初始化为包含四个字,分别是42, 23, my_symbol的地址,和一个结束标记。这种初始化方法通常用于初始化数组或者其他数据结构。


15.  adr_l

ADR_P 是 ARM 汇编器的一个指令,用于生成相对于 PC 的地址。它是 ADR 指令的一个变种,用于处理大于 4KB 的加载。

ADR_P 指令的语法如下:

ADR_P <register>, <expression>

其中 <register> 是目标寄存器,<expression> 是目标地址。

这个指令会尝试生成一个有效的地址,但是如果目标地址超出了 ADR_P 指令的有效范围(即,目标地址与当前 PC 的偏移量超过了 2^12 - 1 字节),那么这个指令将失败。

例如,如果你有一个标签 .label 在 4KB 之后,你可以使用 ADR_P 来加载它的地址:

adrp x0, .label
add x0, x0, :lo12:.label

这里,adrp 用于获取 .label 的高 32 位地址,add 用于获取低 12 位的偏移量。

注意:ADR_P 是在 ARMv8-A 架构中引入的,因此它不能在不支持此功能的旧架构中使用。


16. mov_q

movq 是一个用于64位操作数的指令,用于将数据从一个位置移动到另一个位置。


17. ret

在ARM架构中,ret 指令用于从子程序返回到调用者。在ARMv8-A (arm64) 汇编中,ret 指令可以用于从子程序返回并恢复程序状态。

ret 指令的基本语法如下:

ret{cond}

其中 {cond} 是可选的条件代码。如果不指定,则默认条件代码为 al (即所有条件码)。

下面是一个简单的例子,展示了如何在ARM64汇编中使用 ret 指令:

sub sp, sp, #0x20 // 分配栈空间
stp x29, x30, [sp, #0x10]! // 保存调用者的链接寄存器和程序计数器
// ...子程序代码 ...
ldp x29, x30, [sp], #0x20 // 恢复调用者的链接寄存器和程序计数器
ret // 返回到调用者

在这个例子中,我们在调用子程序前保存了链接寄存器 x29 (fp) 和程序计数器 x30 (lr),在子程序结束前恢复它们,并使用 ret 指令返回到调用者。


18. ldp

ldp 是 ARM 汇编语言中用于从寄存器组或栈中加载双精度浮点寄存器(FP Double-precision)的指令。它用于从内存地址加载 128 位的数据到两个浮点寄存器(通常是单独的浮点寄存器,但也可以是一个的高 64 位和另一个的低 64 位)。

语法如下:

LDP <Wt1>, <Wt2>, [<Xn|SP>], #<imm>
LDP <Wt1>, <Wt2>, [<Xn|SP>, #<imm>]!
LDP <Xt1>, <Xt2>, [<Xn|SP>], #<imm>
LDP <Xt1>, <Xt2>, [<Xn|SP>, #<imm>]!

这里 <Wt1> 和 <Wt2> 是目标浮点寄存器,<Xn|SP> 是基址寄存器,<imm> 是一个常量偏移量。

例子:

ldp x29, x30, [sp], #0x20 // 将栈上偏移为 sp 的 32 字节处的数据加载到 x29 和 x30,然后 sp 增加 0x20

在这个例子中,ldp 指令用于从当前的栈指针 sp 所指向的位置加载两个 64 位的值到浮点寄存器 x29 和 x30,然后更新栈指针 sp 增加 0x20 字节。


19. ENTRY

在ARM架构的汇编语言中,ENTRY 是一个宏指令,用于定义一个符号的入口点,即该符号是程序的入口或者开始点。在ARM64汇编语言中,通常用于定义一个函数的开始。下面是一个简单的ARM64汇编语言示例,展示了如何使用ENTRY指令定义一个函数的入口点:

// 定义一个名为my_function的函数
ENTRY(my_function)
// 函数的实现代码
mov x0, #0 // 将返回值设置为0
ret // 函数返回
END(my_function)

在这个例子中,ENTRY(my_function)定义了一个名为my_function的函数入口点,而END(my_function)表示该函数的结束。在这两个指令之间,是函数的具体实现代码。


20. .quad

.quad 是 ARM 汇编器(如 GNU 汇编器,GAS)中用于定义一个 64 位的常数的指令。例如,如果你想在数据段中定义一个 64 位的常数,你可以这样做:

.data
my_constant:
    .quad 0x123456789abcdef0

如果你想在未初始化数据段中为一个变量预留空间,你可以这样做:

.bss
my_variable:
    .quad 0

注意:.quad 指令只能用于定义数据,不能用于定义代码。如果你想在代码段中定义一个 64 位的值,你应该使用 .code64 指令开启 64 位代码模式,然后使用适当的指令来定义值。


21. .section

.section 是 GAS 汇编器(GNU Assembler)中用来定义段(section)的指令。在 ELF 格式的目标文件中,段是文件的一部分,它包含了特定类型的数据。在 ARM 汇编中,.section 指令用来定义自定义的段,以便在链接时包含在最终的可执行文件中。

例如,在 ARM 汇编代码中,你可能会看到如下的段定义:

.section .data, "aw", @progbits

这行代码定义了一个名为 .data 的段,该段包含已初始化的数据。其中,参数的含义如下:

"aw": 指定段的访问权限和类型。a 表示段可以自动增长,w 表示段是可写的。

@progbits:指定段中的数据是程序的一部分,应当被链接器复制到输出文件中。

在实际应用中,你可以定义自己的段来组织代码,例如:

.section my_section
my_global_symbol:
    mov x0, #1
    ret

在这个例子中,my_section 是自定义的段名称,my_global_symbol 是一个全局的标号,它可以在其他的汇编文件中引用。

需要注意的是,.section 指令只是定义了段的开始,如果你想要在段的末尾有明确的界限,你可能需要使用 .section 的变体,如 .text 用于定义代码段,.data 用于定义初始化的数据段等


22. blr

带返回的跳转指令,跳转到指令后边跟随寄存器中保存的地址(例:blr x8; 跳转到x8保存的地址中去执行)

 

23. bne 1b 和 beq 1f

1:
   ldr r4,[r2],#4
   str r4,[r1],#4
   cmp r1,r3
   bne 1b

bne 1b 这条语句的意思是向后跳转到标签1处,这里的b是backward的意思,既然有backward就有forward,所有就有bne 1f语句:

1: //A
cmp r0, #0
beq 1f  //r0==0那么向前跳转到B处执行,而不是A处
bne 1b  //否则向后跳转到A处执行
1: //B

 

24. clz

CLZ 是 ARM 汇编指令的一种,用于计算参数在二进制表示中前导的零的数量。这是很有用的,因为它可以用来快速确定一个数字的大小,或者用于优化某些算法。

在 ARM 汇编中,CLZ 指令的格式如下:CLZ{条件} 目标寄存器,操作数

例如,如果你想计算 8 位数的前导零数量,你可以这样做:

MOV R0, #0x00 ; 将 R0 设置为 0x00
CLZ R1, R0 ; 计算 R0 的前导零数量,并将结果存储在 R1 中

在这个例子中,R0 包含了要检查的数字(在这个例子中是 0,因此有 32 个前导零),R1 将包含结果(在这个例子中是 32)。

注意:CLZ 指令只能用于 32 位的操作数,如果你试图对一个 16 位的数使用它,你会得到一个错误。

 

25. mul

mul 指令在 ARM 汇编中用于两个操作数的乘法。在 ARMv8 架构(也就是 AArch64)中,mul 指令有多个版本,可以处理不同的数据类型。

以下是一些使用 mul 指令的示例:

(1) 64位乘法:
mul x0, x1, x2 //将 x1 和 x2 的乘积存储在 x0 中,假设x0, x1, x2都是64位的

(2) 32位乘法:
smulw x0, x1, x2 //将 x1 和 x2 的乘积的低32位存储在 x0 中,x0和x1,x2都是32位的

(3) 64位乘法,结果为128位:
umulh x0, x1, x2 //将 x1 和 x2 的乘积的高64位存储在 x0 中,假设x1和x2都是64位的

注意:在使用 mul 指令时,确保目标寄存器的大小与操作数的大小相匹配。例如,smulw 指令的目标寄存器应该是32位的,而 mul 指令的目标寄存器则应该是64位的。

 

26. bic

BIC是ARM汇编语言中的一个指令,用于按位清零(Bit Clear)。它将第二个操作数对应的位设置为0,而保留第一个操作数的其他位不变。

BIC指令的格式如下:

BIC{条件}{S} 目的寄存器,操作数1,操作数2

其中:
条件:指令执行的条件码。
S:指令执行时是否影响CPSR中的标志位。
目的寄存器:用于保存结果的寄存器。
操作数1:操作数1放在目的寄存器中。
操作数2:操作数2是一个32位的掩码,用于指定要清零的位。

例如,如果你想将一个寄存器的某些位清零,你可以使用BIC指令:

BIC R0, R0, #0x1F ; 将R0寄存器的最低5位清零

 

27. dmb

DMB 是 ARM 架构中的一个指令,全称是 Data Memory Barrier,中文名称是数据内存屏障。DMB 指令确保了在 DMB 指令之前的所有内存访问操作都执行完毕,之后的操作都等待这些内存访问操作完成。

这是一个非常重要的指令,在多处理器和多核心系统中,它用于保证内存一致性。在多处理器环境中,如果你希望多个处理器或核心同步执行,或者确保特定的内存访问顺序,DMB 可以提供这种保证。

DMB 指令有以下几种使用方式:
DMB :这会阻塞所有在这条指令之后的内存访问操作,直到所有在这条指令之前的内存访问操作都完成。
DMB ST:这会阻塞所有在这条指令之后的内存访问操作,直到所有在这条指令之前的内存访问操作都完成,并且所有的缓存操作都已经同步到了存储器。
DSB :这会阻塞所有在这条指令之后的内存访问操作,并且确保所有在这条指令之前的指令都执行完成。
DSB ST:这会阻塞所有在这条指令之后的内存访问操作,并且确保所有在这条指令之前的指令都执行完成,并且所有的缓存操作都已经同步到了存储器。

例如,如果你想确保在执行一段代码之前,所有之前的内存访问操作都完成了,你可以在这段代码之前插入一个 DMB 指令。

以下是一个 ARM64 汇编语言的例子,它使用 DMB 指令来保证内存访问顺序:

// 示例代码
mov x0, #10
dmb sy // 数据内存屏障,确保上面的移动指令完成
str x0, [x29, #-16]! // 将值 10 存储到堆栈中

在这个例子中,DMB 指令确保了前一个指令(移动指令)在后续的存储指令(STR)执行前完成。这样可以保证程序的内存访问顺序,避免出现竞态条件。

 

28. msr

MSR 是 ARM 汇编语言中的一个指令,用于将寄存器的值传送到状态寄存器或者其他系统寄存器中。

MSR 指令的格式如下:MSR{cond}{.W} <register>, <system_register>

cond 是条件码。
.W 是选项,表示写入状态寄存器时使用宽寄存器的值。
<register> 是寄存器,包括通用寄存器如 R0-R15,或者其他特殊寄存器如 PC。
<system_register> 是系统寄存器,包括 CPSR 和 SPSR 等。
例如,将 R0 的值传送到 CPSR 中:

MSR CPSR_cf, R0

这里,CPSR_cf 是状态寄存器中的控制标志位字段。

注意:具体的系统寄存器名称和用法可能根据不同的 ARM 架构版本而有所不同。

 

29. mrs

在 ARMv8 架构(也就是 AArch64)中,mrs 指令用于将系统寄存器的值传送到通用 64 位寄存器中。

(1) 例如,如果你想要获取当前程序的 Exception Level 和 Current EL 的状态,可以使用 mrs 指令来获取 ELR_ELn 寄存器的值,其中 n 是 Exception level 的数值。

以下是一个简单的示例,用于获取当前的 Exception Level:

mrs x0, CurrentEL // 将CurrentEL寄存器的值传送到x0寄存器

在这个例子中,x0 是目标寄存器,CurrentEL 是系统寄存器的名称。

注意:在 AArch64 中,系统寄存器的名称是区分大小写的。####

(2) 另外,mrs 指令也可以用于获取其他的系统寄存器的值,例如程序状态寄存器(PSR)的值等。

以下是一个示例,用于获取程序状态寄存器(PSR)的值:

mrs x0, DAIF // 将DAIF寄存器的值传送到x0寄存器

在这个例子中,DAIF 是程序状态寄存器(PSR)的名称,x0 是目标寄存器。

注意:在 AArch64 中,程序状态寄存器(PSR)的名称是区分大小写的。####

(3) 最后,mrs 指令也可以用于获取系统寄存器的特定位的值,例如,你可以使用 mrs 指令来获取中断禁用位(I, F, A)的值。

以下是一个示例,用于获取中断禁用位的值:

mrs x0, DAIF // 将DAIF寄存器的值传送到x0寄存器

在这个例子中,DAIF 是包含中断禁用位的寄存器的名称,x0 是目标寄存器。

注意:在 AArch64 中,包含中断禁用位的寄存器的名称是区分大小写的。

 

30. blr

BLR 是 ARM64 汇编中的一个跳转指令,用于子程序的返回,同时可以将一个寄存器的值作为返回值。BLR 指令的全称是 Branch with Link and Return,其中 Branch 表示跳转,Link 表示保存返回地址,Return 表示返回。

BLR 指令的格式如下:BLR {Xn}

其中 {Xn} 是一个寄存器,可以是 X0 到 X30 之间的任何一个。

BLR 指令的功能是:##### 将 X30(链接寄存器,用于保存返回地址)的值复制到寄存器 {Xn},然后跳转到程序计数器(PC)中的地址执行。

例如,假设我们有以下的汇编代码:

func1:
...
mov x8, #100 //func2的地址
blr x8

func2:
...
mov x0, #200
ret

在这个例子中,func1 调用 func2,并将 func2 的返回值(这里假设是 200)存储在 x8 寄存器中。

注意:在 ARM64 中,通常使用 X0 寄存器传递第一个函数参数,而 X8 寄存器通常用于其他目的,因此在这个例子中,我们使用 X8 作为返回值寄存器。

 

31. tst

tst 是 ARM 汇编语言中的一个指令,用于测试操作数与寄存器的位模式,但是它不会改变操作数本身,只会设置条件标志位来反映结果。

tst 指令的语法如下:tst {cond} register, operand

其中:
cond 是可选的条件代码。
register 是要测试的寄存器。
operand 是要测试的操作数,可以是寄存器或者立即数。

例如,以下代码测试寄存器 r0 的第 3 位是否为 1:

tst r0, #0x8

如果第 3 位是 1,则 tst 会设置零标志位(Z),因为结果为零。如果第 3 位为 0,则不会设置零标志位。其他条件标志位(如进位标志位(C))也可能会被影响,以反映操作的结果,但这取决于操作数和寄存器的实际值。

 

32. cbz

CBZ 是 ARM 汇编语言中的一个条件分支指令,它的全称是 Compare and Branch if Zero,也就是如果操作数为零则分支。

CBZ 指令的语法格式如下:CBZ{条件} 寄存器,标签

其中,“寄存器”是源操作数,“标签”是分支的目标地址。如果寄存器的值为零,处理器就会将程序的执行流程转移到“标签”指定的地址。

下面是一个使用 CBZ 指令的简单例子:

CBZ R0, LABEL; 如果 R0 寄存器的值为零,则跳转到 LABEL 标签处执行

在这个例子中,如果寄存器 R0 的值为零,则程序的执行流程会跳转到标签 LABEL 指定的位置。这个跳转是条件执行的,因此不会无条件地改变程序的执行流程。

 

33. .asciz

.asciz 是 ARM 汇编器中的一个指令,用于分配一段以 null 结尾的字符串字面量。这个字符串在内存中以 null 字符结尾,这样处理字符串的库函数就可以正确识别字符串的结束。

这里是一个简单的使用 .asciz 指令的例子:

.data
msg: .asciz "Hello, World!"

.text
.global _start
_start:
mov x0, msg
bl printf
mov w0, 0 // 系统调用号 0 表示 _exit
svc #0x80

在这个例子中,msg 是一个包含 "Hello, World!" 字符串的标签,并且以 null 字符结尾。程序启动后,它会打印这条消息,然后退出。这里使用了 printf 和 _exit 系统调用,这是在 Linux 系统上运行的 ARM64 程序的常见做法。

 

34. eret 

ERET指令用于异常返回,返回地址和处理器状态是从当前EL(exception level)下的 ELR 和 SPSR 寄存器中恢复的。即 ELR 寄存器中的值就是BL1最后跳转的目的地址,SPSR寄存器的值就是跳转之后处理器的状态。

(1) 处理异常的流程

ARMv8的四个异常级别只能通过异常获取/返回的方式在异常等级之间跳转。

a. 将 PSTATE 寄存器内容保存到对应等级的 SPSR_ELx 中(保存 PSTATE 现场)
b. 保存返回地址到对应等级的 ELR_ELx 寄存器中(保存返回地址)
c. 将 PSTATE 中的 DAIF 设1,即关闭调试异常、SError、IRQ 和 FIQ
d. 设置对应异常等级下的栈指针,自动切换 SP 到 SP_ELx
e. 切换到对应目标异常等级,跳转到异常向量表执行

以上都是处理器自动完成的,OS所需要做的事就是从中断向量表开始,根据发生的异常类型,跳转到合适的异常向量。当异常处理完后,执行 eret 指令从异常返回。eret 指令会从 ELR_ELx 中恢复 PC 指针并且从 SPSR_ELx 中恢复到 PSTATE 中。

fucntion el3 exit  
msr  spsr_el3  x0
msr elr_el3 x1
isb 
eret

(2) 软件产生的异常

ARMv8提供了3中软件产生的异常,发生此异常的原因是软件企图进入更高的异常等级。

SVC 允许用户模式下的程序请求os服务
HVC 允许客户机(Linux os)请求主机服务
SMC 允许普通世界的程序请求安全服务

(3) 对EL2/EL3的系统调用

SVC指令可以用来从EL0的用户应用程序调用到EL1的内核。HVC和SMC系统调用指令以类似的方式将处理器移动到EL2和EL3。当处理器在EL0(应用程序)执行时,它不能直接调用管理程序(EL2)或安全监视器(EL3)。这只有在EL1和以上的地方才有可能。因此,应用程序必须使用SVC来调用内核,并允许内核代表它们调用更高的异常级别。

在操作系统内核(EL1),软件可以用HVC指令调用管理程序(EL2),或用SMC指令调用安全监视器(EL3)。如果处理器是用EL3实现的,就可以提供让EL2从EL1捕获SMC指令的能力。如果没有EL3,SMC是未分配的,并在当前的异常级别触发。

同样,从管理程序代码(EL2)中,程序可以用SMC指令调用安全监视器(EL3)。如果你在EL2或EL3时进行SMC调用,它仍然会在同一个异常级别上引起一个同步异常,该异常级别的处理程序可以决定如何响应

 

二、语法

1. 宏定义

汇编中宏定义是以 .macro 开头,以 .endm 结尾。宏定义中以  \x 来引用宏定义中的参数 x

2. 输出到指定的段中

.pushsection和.popsection是GNU汇编器(GAS)的指令,用于改变当前的段(section)。这对于在编写跨平台的代码或者需要动态控制代码段布局时非常有用。.pushsection用于将当前段添加到栈中,并开始使用新的段。.popsection用于从栈中移除最后一个添加的段,并可选地开始使用新的段。

例如,在ARM64汇编中,你可能会这样使用它们:

.section .data
data_section_start:
    .word 0x12345678
 
.section .text
.global _start
_start:
    adr x0, data_section_start
    ldr x1, [x0]
    ...
 
.pushsection .data
    .word 0x87654321
.popsection

在这个例子中,我们首先定义了一个.data段,其中包含一些初始化的数据。然后我们定义了一个.text段,其中包含程序的入口点_start。在_start中,我们使用adr指令获取到data_section_start标签的地址,并使用ldr指令从该地址加载数据。

接下来,我们使用.pushsection和.popsection来动态地改变当前的段。我们将新的数据(0x87654321)推送到.data段中,并在使用完成后通过.popsection指令将段恢复到之前的状态。

这种技术在处理复杂的数据段和代码段布局时非常有用,可以让你更灵活地控制输出的二进制文件。

例如linux内核中的 .idmap.text 段的填充:

//arch/arm64/kernel/sleep.S
.text
.pushsection ".idmap.text", "awx"
XXX
.popsection

//arch/arm64/kernel/cpu-reset.S
.text
.pushsection    .idmap.text, "awx"
XXX
.popsection
//arch/arm64/kvm/hyp-init.S
.text
.pushsection    .hyp.idmap.text, "ax"
XXX
.popsection

验证是否的确在段中:

//arch/arm64/kernel/head.S
.section ".idmap.text","awx"  //line 477
XXX
.pushsection ".mmuoff.data.write", "aw" //line 674
YYY
.popsection //line 692

从477行开始不包括 674--692行中定义的函数有:
kimage_vaddr
el2_setup
set_cpu_boot_mode_flag
secondary_holding_pen
secondary_entry
secondary_startup
__secondary_switched
__secondary_too_slow
__enable_mmu
__cpu_secondary_check52bitva
...
这些函数 cat /proc/kallsyms 看,都在 __idmap_text_start 和 __idmap_text_end 之间。674--692行中的 __boot_cpu_mode 则不在区间内。

 

三、示例

1. 复杂指令

str \tmp2, [\tbl, \tmp1, lsl #3] //翻译成C就是 *((long *)(tbl + (tmp1 << 3))) = tmp2;

 

posted on 2024-07-10 09:21  Hello-World3  阅读(812)  评论(0编辑  收藏  举报

导航