ARM64 架构基础知识
实验:汇编语言练习——查找最大数
本实验我们用汇编语言写一个查找最大数功能,借此了解和熟悉 ARM64 汇编语言。
- .section .data
- .align 3
- my_data:
- .quad 1
- .quad 2
- .quad 5
- .quad 8
- .quad 10
- .quad 12
- my_data_count:
- .quad 6
- .align 3
- print_data:
- .string "max data: %d\n"
- .section .text
- .globl main
- main:
- stp x29, x30, [sp, -16]!
- ldr x0, =my_data
- ldr x1, my_data_count
- add x4, x0, #40
- mov x3, xzr
- 1:
- ldr x2, [x0], #8
- cmp x2, x3
- csel x3, x2, x3, hi
- cmp x0, x4
- b.ls 1b
- ldr x0, =print_data
- mov x1, x3
- bl printf
- ldp x29, x30, [sp], 16
- ret
功能方面没什么进一步详细说的地方了,功能就是查找定义好的数值里的最大数,并打印出来。我们就从汇编指令着手,一个一个了解。
stp : Store Pair,用于将两个通用寄存器中的值存储到内存中。语法为 stp <Rt1>, <Rt2>, [<Xn|SP>, #-<imm>]!,最后的感叹号表示在存储数据之后,自动更新基地址的值,以便下一次存储操作使用更新后的地址。stp x29, x30, [sp, -16]! 的意思是将 x29 和 x30 寄存器中的值存储到 SP 寄存器减去 16 的地址处,并将 SP 寄存器的值更新为存储后的地址。
x29 是帧指针寄存器(Frame Pointer,FP)。FP 寄存器用于保存函数调用者的栈帧指针。在函数调用时,将调用者的 FP 值保存到栈中,并将自己的 FP 设置为栈顶指针(即 SP 寄存器的值)。这样,函数可以通过 FP 寄存器访问调用者的栈帧,并通过相对地址访问函数参数和局部变量。
x30 是链接寄存器(Link Register,LR)。LR 寄存器用于保存函数调用的返回地址。在函数调用时,将返回地址存储在 LR 寄存器中,并将控制转移至被调用函数。在函数返回时,将 LR 寄存器中的值恢复到程序计数器(PC)中,以便返回到调用者的代码位置。
xzr : 零寄存器(Zero Register)。零寄存器中的值恒为 0,且是只读的。
csel : 条件选择(Conditional Select)。语法为 csel Rd, Rn, Rm, cond。其中 cond 是条件代码,如果条件码为真,则将 Rn 的值存储到 Rd 中;否则将 Rm 的值存储到Rd中。
b.ls : b 是无条件分支指令(branch)。ls 是条件代码,代表小于或等于(lower or same)。
接着我们用 gcc 进行交叉编译(因为是 arm 指令):
- tim@tim:~$ aarch64-linux-gnu-gcc max.S -o max --static
运行结果符合预期:
- tim@tim:~$ qemu-aarch64-static max
- max data: 12
没有额外的链接操作,直接就可以调用到标准库的 printf。原因是 gcc 默认就会链接 libc 库。
实验:通过 C 语言调用汇编函数
如代码清单 2.1 所示,我们先实现汇编版本的返回最大数函数。
- .section .text
- .globl my_max
- my_max:
- cmp x0, x1
- csel x0, x0, x1, hi
- ret
如代码清单 2.2 所示,在 C 程序里调用定义的汇编函数。
- #include <stdio.h>
- int my_max(int a, int b);
- int main()
- {
- int max = my_max(5, 6);
- printf("max data: %d\n", max);
- return 0;
- }
编译:
- tim@tim:~$ aarch64-linux-gnu-gcc max.S main.c -o main --static
运行符合预期:
- tim@tim:~$ qemu-aarch64-static main
- max data: 6
实验:通过汇编语言调用 C 函数
如代码清单 3.1 所示,我们先实现 C 语言版本的返回最大值函数。
- int my_max(int a, int b)
- {
- return (a >= b) ? a : b;
- }
如代码清单 3.1 所示,我们调用定义的 C 语言函数,整体框架和代码清单 1 是一样的。
- .section .data
- .align 3
- print_data:
- .string "max data: %d\n"
- .section .text
- .global main
- main:
- stp x29, x30, [sp, -16]!
- mov x0, #6
- mov x1, #5
- bl my_max
- mov x1, x0
- ldr x0, =print_data
- bl printf
- ldp x29, x30, [sp], 16
- ret
- tim@tim:~$ aarch64-linux-gnu-gcc max.c main.S -o main --static
运行符合预期:
- tim@tim:~$ qemu-aarch64-static main
- max data: 6
实验:GCC 内联汇编
我们先看代码清单 4,是 GCC 内联汇编实现的返回最大数函数。
- #include <stdio.h>
- int my_max(int a, int b)
- {
- int val;
- asm volatile (
- "cmp %1, %2\n"
- "csel %0, %1, %2, hi\n"
- : "+r" (val)
- : "r" (a), "r" (b)
- : "memory"
- );
- return val;
- }
- int main()
- {
- int max = my_max(5, 6);
- printf("max data: %d\n", max);
- return 0;
- }
GCC 内联汇编的语法格式为:asm("assembly code" : output : input : clobber);。其中 "assembly code" 是嵌入的汇编代码;output 是输出的寄存器或变量;input 是输入的寄存器和变量;clobber 是需要维护的破坏上下文的寄存器。
“输入”是指 C 代码传递给汇编代码的数据。
“输出”是值汇编代码传递给 C 代码的数据。
内联汇编中的 %0 等符号对应着约束中指定的操作数,即 output 为开始的索引。