我运行这个程序在汇编中打印“Hello World”,然后将其发送到Linux服务器。该程序编译正确,但当我尝试运行代码时出现分段错误。有什么想法吗?
// Program 2: Hello World
// A program that prints "Hello World!" to the standard output
// (terminal monitor)
.global _start
.text
_start:
// code for writing message to stdout
ldr x8, #64 // set register x8 with syscall number for write command
mov x0, #1 // set register x0 with the file descriptor number
adr x1, hello_String // set register x1 with the address of the string
mov x2, x1 // set register x2 with the string length
svc #0x80 // make supervisory call to the OS
// code for exiting the program
mov x8, #93 // set register x8 with syscall number for exit command
mov x0, #0 // set register x0 with return value (=0 for no errors)
svc #0x80 // make supervisory call to the OS
.data
hello_string: .ascii "Hello World!\n"
// end of program
大部分的bug都在评论中指出了,所以我就收集在这里:
ldr x8, #64
:尝试从内存中将值加载到x8
,其中地址是程序计数器加上 64 个字节。要将一个小常量放入寄存器中,例如 -65536..65536
范围内的任何内容,请像在其他地方一样使用 mov
:mov x8, #64
。
您可能一直在想
ldr x8, =64
。这确实具有将值 64
放入 x8
的效果,但效率较低:它将值 64
作为 64 位整数组装到附近某个位置的内存中,并执行内存加载检索它。因此它使用了额外的 8 字节内存,并产生了运行时从内存加载的费用。另一方面,在 mov x8, #64
中,常量 64 直接编码到指令中,并且不需要任何内存访问(除了获取指令以执行,无论如何都必须发生)。
adr x1, hello_String
:这里打字错误,应该是hello_string
。也许您的代码中已经是正确的,这只是一个转录错误。但如果您使用 hello_String
,您会遇到一些可能令人困惑的链接器错误。
mov x2, x1 // set register x2 with the string length
:不,它会将包含 x1
的 address 的 hello_string
复制到 x2
中,这样现在它们都包含该地址。
您可以手动计算字符串长度,然后执行
mov x2, #13
。但是,您也可以让汇编器为您完成这项工作。在组装字符串的 .ascii
指令之后,您可以使用 .
特殊符号来获取当前地址(即字符串后面的下一个字节)并减去起始地址 hello_string
。然后,您可以使用结果定义像 hello_len
这样的符号,并将其用作放入 x2
的值:mov x2, #hello_len
。
请注意,所有这些算术都是由汇编器在构建时完成的;生成的机器代码仅包含与您手写相同的
mov x2, #13
。所以它对运行时性能没有影响。
这是一个完整的修复版本。
.global _start
.text
_start:
// code for writing message to stdout
mov x8, #64 // set register x8 with syscall number for write command
mov x0, #1 // set register x0 with the file descriptor number
adr x1, hello_string // set register x1 with the address of the string
mov x2, #hello_len // set register x2 with the string length
svc #0x80 // make supervisory call to the OS
mov x8, #93 // set register x8 with syscall number for exit command
mov x0, #0 // set register x0 with return value (=0 for no errors)
svc #0x80 // make supervisory call to the OS
.data
hello_string: .ascii "Hello World!\n"
hello_len = . - hello_string
顺便说一下,作为
#0x80
的立即操作数的值 svc
没有意义,因为它会被内核忽略:请参阅arm svc 指令如何工作?。它不会造成任何伤害,但任何其他值也可以,例如#0
。这可能会避免让代码读者感到困惑,因为他们想知道 0x80
是否有特殊意义。
您可能会想到 x86-32 Linux,它使用
int 0x80
指令进行系统调用;在这种情况下,立即数指定要调用哪个中断向量,并且它必须是 0x80
,因为这是 Linux 用于系统调用的特定向量。但同样,这完全特定于 32 位 x86 代码,与 AArch64 完全无关。