模块化编程 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 之后并丢弃寄存器 x0
、x1
、x2
和 x8
。
.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。
我有一个用于 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 之后并丢弃寄存器 x0
、x1
、x2
和 x8
。
.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。