段错误...在hello world上。

7

这段代码非常简单,但在我的x86_64 Linux系统上出现了段错误。这让我很烦恼。我刚开始学习汇编语言,请耐心等待!

使用NASM汇编 nasm -f elf64 test.asm

链接 ld -o test test.o

SECTION .text
    GLOBAL _start

    _start:
        ; print name
        mov eax,4     ; sys_write
        mov ebx,1     ; stdout
        mov ecx,name  ; start address of name
        mov edx,1     ; length
        int 80H       ; syscall

        ; exit program
        mov eax,1     ; sys_exit
        mov ebx,0     ; success
        int 80H       ; sys_call  

SECTION .data
    name DB 'R'

我的电脑是 Gentoo x86_64 nomultilib!我自己编译了内核,没有 IA32 模拟。我应该说明一下我的系统只支持 64 位。这会导致我收到的错误吗?

$ uname -a
Linux rcepeda 4.4.1-2-ARCH #1 SMP PREEMPT Wed Feb 3 13:12:33 UTC 2016 x86_64 GNU/Linux

解决方案

使用64位寄存器和64位Linux分发程序。

使用syscall(而不是int 80H)。

感谢Nate和Michael

32位Linux SYSCALL表

64位Linux SYSCALL表

SECTION .text
    GLOBAL _start

    _start:
        ; print name
        mov rax,1     ; sys_write
        mov rdi,1     ; stdout
        mov rsi,name  ; start address of name
        mov rdx,7 ; length
        syscall

        ; exit program
        mov rax,60    ; sys_exit
        mov rdi,0     ; success
        syscall

SECTION .data
    name DB "Rafael",10

.

rafael@rcepeda ~/asm $ ./a.out 
Rafael

1
@MichaelPetch:它打印的是“R”,而不是“Hello world”,但除此之外,它是真正的代码。 - Nate Eldredge
1
@NateEldredge 我知道这是一个完整的例子,但用户可能没有给出他的确切代码。由于他的标题暗示了不同的输出,可能表明崩溃的代码可能不是显示的代码。因此,我请求澄清。除非此代码在低于4GB的地址空间之外运行,否则即使它是一个使用32位 int 0x80 机制的64位应用程序,它也应该正常工作。 - Michael Petch
2
我也支持你。我非常好奇 uname -a 返回的结果是什么。在你的问题中展示它会很有帮助。 - Michael Petch
1
如果你在构建内核时启用了IA32仿真支持,你的程序很可能会正常工作。大多数Linux发行版都在64位内核中启用了IA32仿真,以便与旧的32位用户代码保持向后兼容性。 - Michael Petch
1
在64位代码中,您可以使用32位寄存器。 32位操作数生成32位结果,并在目标通用寄存器中零扩展为64位结果。因此,如果您有一个32位通用寄存器作为操作数并在其中放置一个值,则处理器会将结果零扩展到整个64位寄存器。您也可以使用16位和8位寄存器操作数,但是当写入它们时,它们不会被零扩展到整个64位寄存器。简短的答案是,您可以在64位代码中使用8/16/32/64位寄存器。在您的情况下,问题很可能是不支持的“int 0x80”接口。 - Michael Petch
显示剩余7条评论
1个回答

4

你正在以64位模式运行,但这是32位代码。如果你想要64位代码,你需要重写它。

你应该使用64位寄存器 rax、rbx 等。在64位Linux中,系统调用不再使用 int 80h 而是使用新的 syscall 指令。参见http://cs.lmu.edu/~ray/notes/linuxsyscalls/(注意此文档使用AT&T汇编语法而不是Intel)。

另外,你可以保持代码不变,并以32位模式进行汇编和链接,使用 nasm -f elf32ld -m elf_i386。但那样你就学习了(相对)过时的技术。(编辑:实际上,似乎在你的特定系统上没有启用32位兼容性,所以这根本行不通。)


3
这段32位代码应该能在64位Linux环境下运行,因为示例很可能没有运行到地址空间底部4GB之外。64位代码仍然可以使用int 0x80,但不是首选机制。需要使用syscall的一种情况是访问堆栈上的内存。由于64位中堆栈值在4GB以上,所以它们会被截断并且在32位int 0x80调用中无效。 - Michael Petch
我也有同样的疑问,但理论上,32位寄存器与64位寄存器是相同的,只是大小减半,正如@MichaelPetch所述。我的系统严格为64位,我没有多库支持和内核上的IA32仿真。似乎是我的机器出了问题,但我不确定是什么原因... - Rafael
谢谢你的链接,Nate。它似乎正是我在我的64位机器上所做的事情。当我回家后,我会试一下这个。我的系统严格是64位的,但我不知道我不能使用32位寄存器吗? - Rafael
@Rafael:一般来说,在64位模式下,您可以使用32位寄存器。但例如,在64位模式下,“write(2)”的第二个参数应该是一个指针,应该是64位。当您将“name”的地址移动到“ecx”中时,您最终得到的是“rcx”的低半部分包含地址的低32位,而高半部分包含0。内核将在“rcx”中查找64位指针。如果地址不在地址空间的低2 GB中,则结果将是错误的。因此,您可以使用 32位寄存器,但这可能不是正确的做法。 - Nate Eldredge

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接