为什么我在 AArch64 Linux hello world 程序中遇到分段错误?

问题描述 投票:0回答:1

我运行这个程序在汇编中打印“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
linux assembly segmentation-fault arm64
1个回答
0
投票

大部分的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 完全无关。

© www.soinside.com 2019 - 2024. All rights reserved.