一、加载和存储指令
ARM64指令集提供了很多加载和存储指令,比如:
寄存器与物理内存数据之间的加载与存储:
ldr
:加载指令,从寄存器B存储的地址内存中加载数据到寄存器ldr <A>, [<B>]
- 寄存器A <--- 寄存器B存储的地址中内存的数据
str
:存储指令,将寄存器A中的数据存储到寄存器B存储的 地址内存中str <A>, [<B>]
- 寄存器 A 中的数据 ---> 寄存器 B 存储的 地址内存中
ldp
:加载指令,ldp
指令可以加载 16 个字节ldp <A>, <B>, [<C>]
- 以 C 的值为起始地址,加载这个地址的数据到 A 中,然后 C+8 作为新的起始地址,加载这个地址的数据到 B 中
stp
:存储指令stp <A>, <B>, [<C>]
- 存储 A 的数据到 C 地址中,然后存储 B 的数据到 C+8 地址中
值域复制指令
mov
:移动指令mov <A> , value
比较指令:
cmp
:比较指令cmp <目标操作数>, <源操作数>
- 比较的结果会修改标志位
cmp
指令可以根据比较符号来进行跳转:
条件描述 | x86 标志位 | AArch64 条件码 | 含义 |
---|---|---|---|
ax = bx |
ZF = 1 |
eq (Z=1) |
相等 |
ax != bx |
ZF = 0 |
ne (Z=0) |
不相等 |
ax < bx |
CF = 1 |
lo / cc (C=0) 无符号lt
(N≠V) 有符号 |
小于 |
ax >= bx |
CF = 0 |
hs / cs (C=1) 无符号ge
(N=V) 有符号 |
大于等于 |
ax > bx |
CF = 0 且 ZF = 0 |
hi (C=1 ∧ Z=0) 无符号gt (Z=0 ∧ N=V)
有符号 |
大于 |
ax <= bx |
CF = 1 或 ZF = 1 |
ls (C=0 ∨ Z=1) 无符号le (Z=1 ∨ N≠V)
有符号 |
小于等于 |
使用实例:
1 | cmp x0, x2 ; 设置标志 |
二、简单地址偏移代码实践
在kernel.c
中 extern
实现加载和存储指令的汇编代码函数:
1 | extern void ldr_test(void); |
其中ldr_test
函数的实现在arm_test.S
中:
1 | .global ldr_test |
然后就能编译运行。
三、实现memcpy
汇编函数
从 0x80000 地址拷贝 32 个字节到 0x200000 地址。
用cmp
指令辅助判断是否已经copy了32个字节。 -
一个地址索引对应的存储单元 1 Byte(8 bit) -
每次ldr
指令只能 copy 8 个字节,因此 32 个字节需要 4
次ldr
指令
1 | //memcpy: copy 32 bytes from 0x80000 to 0x200000 |
以读第一次为例:
1 | 地址 内容(十六进制) |
读 ldr x4, [x0]
后
x4 = 0xD50320D53800A0
四、实现memset
函数
memset
函数的功能是把一个内存块的每个字节都设置为某个值。其
c 语言实现原型如下:
1 | void *__memset_1bytes(void *s, int num, size_t n){ |
这里保证传入的参数地址 s 和 n 是 16 的倍数(按16对齐),num 是 8
字节的。由于 n 的对齐要求是
16,因此我们可以使用ldp
和stp
指令进行内存操作,相比
8 字节 8 字节地写入而言,速度会快些。(支持 16 字节对齐)
同时,汇编中的函数允许传参,传参时会用 x0 ~ x7
寄存器传递参数,因此,函数的参数个数不能超过 8 个。
在汇编中,我们定义的方法为__memset_16bytes
,调用这个
memset
函数的语法例子为__memset_16bytes(0x200000, 0x5555555555555555, 128)
,表示往地址
0x200000 的起始地址中写入 128 个 0x55。
我们先实现一个假设传参对齐的汇编函数:
1 | //memset(void *s, int c, size_t n): set n bytes to 0x55 |
并用规范的声明和调用来进行测试:
1 | //kernel.c |
实际上我们在c语言调用时一般会memset(0x200004, 0x55, 102)
,也就是不一定保证
16 位对齐,且传入的 data 是单字节的,需要我们在封装的 c
函数中实现对齐判断、自动对齐、填充 data
为8字节的操作。(也就是传入0x55,填充为汇编函数__memset_16bytes可以用的0x5555555555555555)。
对齐到 16 的倍数就是要求地址的 低 4 位必须是
0。因此用if(addr & (align - 1))
来判断低4位是否为0。
下面在这个汇编函数的基础上,假设内存地址 s 和 n 都不是保证 16 字节对齐的,那么此时,我们在调用所写的汇编函数之前,应该先用 c 语言代码实现对齐:
1 | //memset.c |
1 | //memset.h |
1 | //kernel.c |