模块化编程 aarch64 汇编

Modular programming aarch64 assembly

我有一个用于 arm 架构 64 的简单程序集,它将从我制作的宏中打印“Hello World\n”。

我们知道macro类似于高级编程中的function

所以我想要单独的宏到另一个文件。这是我编写的汇编代码。

.data

.text
.globl _start

_start:

.macro print string
 b 2f // jump to label 2 forward (so it will not bother var)
 1: // label 1 (var declaration)
 .asciz "\string" // store argumen (string) in memory
 len_string = . - 1b // get len of string
 udf #0 // if i dont use this it will branch error :(
 2: // label 2 (main macro code)
 mov x0, #1 // file descriptor (1 means stdout)
 ldr x1, =1b // data to print (from label 1 back)
 ldr x2, =len_string //len of data
 mov x8, #0x40 // kernel convention 0x40 means writing
 svc #0 // SuperVisorCall kernel
.endm

print "Hello " // call macro
print "World\n" //call again

_exit: // exit code return 0
mov x0,#0
mov x8,#0x5d
svc #0

要验证它,请编译并 运行 该代码。在 aarch64 设备中另存为 hello.s 和 运行,例如 android 或 raspi

as hello.s -o hello.o && ld hello.o -o hello && ./hello

所以根据上面的代码,我创建了名为 print 的宏,参数为 string。但是正如您所见,我在主程序中定义了宏。我希望我可以在另一个源文件中导入 print。我该怎么办?

high-level 函数的对应项是汇编函数。汇编宏的对应物是高级语言中的宏或模板。

使用函数进行模块化编程

编写结构化或模块化代码的最简单方法是编写函数。函数与您编写的函数非常相似,但您需要使用 ret 指令从中 return 。此 print 函数获取要在 x0 中打印的字符串的地址及其在 x1 中的长度。它跟在 AArch64 ABI 之后并丢弃寄存器 x0x1x2x8

        .text
        .type print, @function  // these two directives make the function
        .globl print            // callable from other object files.
print:  mov     x2, x1          // move length into place for syscall
        mov     x1, x0          // move string address into place for syscall
        mov     x0, #1          // print to stdout
        mov     x8, #0x40       // do a write system call
        svc     #0
        ret                     // return to caller

然后您可以从您喜欢的任何目标文件中调用该函数:

        .data
hello:  .ascii "hello world\n"
len=    .-hello

        .text
        ldr     x0, =hello      // load string address from literal pool
        mov     x1, #len        // load string length
        bl      print           // call our print function

请注意,由于每个函数使用相同的 lr 寄存器来跟踪 return 地址,您可能需要将 lr 寄存器保存到调用函数的堆栈中其他功能。由于在 arm64 上必须将寄存器成对压入堆栈,因此我将 xzr 作为第二个寄存器压入。实际上,您可能想要压入一些需要保存的其他寄存器。

        str     lr, xzr, [sp, #-16]!  // push lr and xzr onto the stack
        ...
        ldr     x0, =hello            // load string address from literal pool
        mov     x1, #len              // load string length
        bl      print                 // call our print function
        ...
        ldr     lr, xzr, [sp], #16    // pop lr and xzr off the stack
        ret

使用宏进行模块化编程

您的宏观方法几乎是正确的。这是一个稍微改进的版本。请注意我们如何使用 \@ 为所使用的字符串生成唯一标签。这使我们不会丢弃编号标签,从而允许宏外的代码使用它们。

        .macro  print string    // define a macro print with argument string
        .pushsection .data      // temporarily go into .data               
str\@:  .ascii  "\string"       // define the string
len\@=  .-str\@                 // and its length

        .popsection             // go back to the previous section
        mov     x0, #1          // print to stdout
        ldr     x1, =str\@      // print our string
        mov     x2, #len\@      // of this length
        mov     x8, #0x40       // with a write system call
        svc     #0              // do the system call!
        .endm

如果你想调用在其他文件中定义的宏,你必须将这些文件包含到其他文件中。这可以通过 .include 指令来完成。例如,假设您的宏位于名为 macros.inc 的文件中,您可以像这样包含它们:

        .include "macros.inc"       // include macros

详情请参考GNU as manual