在Windows子系统上使用Ubuntu,使用INT 0x80编译汇编可执行文件没有输出。

9

我一直在学习汇编语言的教程,现在尝试运行一个hello world程序。我正在使用Windows上的Ubuntu Bash。

以下是汇编代码:

section .text
    global _start     ;must be declared for linker (ld)

_start:             ;tells linker entry point
    mov edx,len     ;message length
    mov ecx,msg     ;message to write
    mov ebx,1       ;file descriptor (stdout)
    mov eax,4       ;system call number (sys_write)
    int 0x80        ;call kernel

    mov eax,1       ;system call number (sys_exit)
    int 0x80        ;call kernel

section .data
    msg db 'Hello, world!', 0xa  ;string to be printed
    len equ $ - msg     ;length of the string

我使用以下命令创建可执行文件:

nasm -f elf64 hello.asm -o hello.o
ld -o hello hello.o -m elf_x86_64

我使用以下命令运行它:
./hello

程序似乎没有出现分段错误或错误,但是它没有产生任何输出。
我无法弄清楚代码为什么不会产生输出,但我想知道是否使用Bash on Ubuntu on Windows有关系?为什么它不产生输出,我该如何解决?

7
您在64位可执行文件中使用32位系统调用接口可能会与此有关。 - Ross Ridge
4
我看到Ubuntu-on-Windows不支持32位可执行文件。也许64位的可执行文件中的 int 0x80 调用也不被支持。您的代码看起来可以在Linux上运行,在那里,int 0x80调用在64位模式下是被支持的(但不建议使用)。 - Peter Cordes
1
哦,或者是msg位于一个地址上,该地址不适合32位,在您的64位二进制文件中?这可以解释,尽管这不是通常的布局。尝试使用strace ./hello或使用GDB单步执行以查看eax中的错误返回值。 - Peter Cordes
2个回答

17

相关:WSL2可以允许32位用户空间程序,WSL1不支持。请参见 Does WSL 2 really support 32 bit program? 确保您实际使用的是WSL2。这个答案的其余部分是在WSL2出现之前编写的。


问题在于Ubuntu for Windows(Windows子系统Linux版本1)。它仅支持64位syscall接口,而不支持32位x86int 0x80系统调用机制。不支持

除了无法在64位二进制文件中使用int 0x80(32位兼容性)外,Windows上的Ubuntu(WSL1)也不支持运行32位可执行文件。(就像某些Gentoo用户所做的那样,如果您构建了一个真正的Linux内核没有启用CONFIG_IA32_EMULATION。)


您需要从使用int 0x80转换为syscall。这不难。syscall使用不同的寄存器,并且系统调用号与它们的32位计数器部分不同。Ryan Chapman的博客提供了关于syscall接口及其参数、系统调用及其参数的信息。Sys_writeSys_exit被定义为:

%rax  System call  %rdi               %rsi              %rdx          %r10 %r8 %r9
----------------------------------------------------------------------------------
0     sys_read     unsigned int fd    char *buf         size_t count          
1     sys_write    unsigned int fd    const char *buf   size_t count
60    sys_exit     int error_code     
使用 syscall 会重写 RCXR11 寄存器,这些寄存器被视为易失性。在 syscall 执行后不要依赖它们仍然保持相同的值。

你的代码可以修改为:

section .text
    global _start     ;must be declared for linker (ld)

_start:             ;tells linker entry point
    mov edx,len     ;message length
    mov rsi,msg     ;message to write
    mov edi,1       ;file descriptor (stdout)
    mov eax,edi     ;system call number (sys_write)
    syscall         ;call kernel

    xor edi, edi    ;Return value = 0
    mov eax,60      ;system call number (sys_exit)
    syscall         ;call kernel

section .data
    msg db 'Hello, world!', 0xa  ;string to be printed
    len equ $ - msg     ;length of the string

注意:在64位代码中,如果指令的目标寄存器是32位的(如EAXEBXEDIESI等),则处理器将结果扩展到64位寄存器的高32位中mov edi,1的效果和mov rdi,1一样。


这个答案不是关于编写64位代码的入门指南,只是关于使用syscall接口的说明。如果你对编写调用C库并符合64位System V ABI的代码的细节感兴趣,有一些不错的教程可以让你入门,比如Ray Toal的NASM教程。他讨论了栈对齐、红色区域、寄存器使用以及64位System V调用约定的基本概述。


1
我还要提醒一下64位系统中不同的ABI,其中将参数放入寄存器可能是比较容易理解的部分,但栈对齐和红区可能对于刚开始学习汇编语言的人来说有点棘手。通常情况下,我更倾向于建议像这样的问题编译为32位二进制文件,但在Windows中这不是一个选项,只支持64位Linux。 - Ped7g

4

正如Ross Ridge在评论中指出的那样,编译64位时不要使用32位内核函数调用。

要么编译为32位,或将代码“翻译”为64位系统调用。 以下是可能的解决方案:

section .text
    global _start     ;must be declared for linker (ld)

_start:             ;tells linker entry point
    mov rdx,len     ;message length
    mov rsi,msg     ;message to write
    mov rdi,1       ;file descriptor (stdout)
    mov rax,1       ;system call number (sys_write)
    syscall         ;call kernel

    mov rax,60      ;system call number (sys_exit)
    mov rdi,0       ;add this to output error code 0(to indicate program terminated without errors)
    syscall         ;call kernel

section .data
    msg db 'Hello, world!', 0xa  ;string to be printed
    len equ $ - msg     ;length of the string

1
与大多数Linux系统不同,在WSL上构建32位不是一个选项:即使对于32位可执行文件,int 0x80 ABI也根本不受支持。因此,这就像一个没有CONFIG_IA32_EMULATION的Linux内核构建。 - Peter Cordes

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