在适用于 Linux 的 Windows 子系统上的 Ubuntu 上使用 INT 0x80 编译的程序集可执行文件不会产生输出
- 2024-10-23 08:47:00
- admin 原创
- 226
问题描述:
我一直在看汇编教程,并尝试运行一个 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
然后程序似乎运行时没有分段错误或错误,但它没有产生任何输出。
我不明白为什么代码不会产生输出,但我想知道在 Windows 上使用 Ubuntu 上的 Bash 是否与此有关?为什么它没有产生输出,我该如何修复它?
解决方案 1:
相关:WSL2 允许 32 位用户空间程序,而 WSL1 不允许。请参阅WSL 2 是否真的支持 32 位程序?关于确保您确实在使用 WSL2。这个答案的其余部分是在 WLS2 出现之前写的。
问题出在 Ubuntu for Windows(Windows Subsystem for Linux 版本 1)上。它仅支持 64 位syscall
接口,而不支持32 位 x86int 0x80
系统调用机制。
除了无法int 0x80
在 64 位二进制文件中使用(32 位兼容性)之外,Windows 上的 Ubuntu(WSL1)也不支持运行 32 位可执行文件。(就像您在没有的CONFIG_IA32_EMULATION
情况下构建了真正的 Linux 内核一样,就像一些 Gentoo 用户所做的那样。)
您需要将 使用 转换int 0x80
为syscall
。这并不难。 使用不同的寄存器组,syscall
并且系统调用号与 32 位对应寄存器不同。Ryan Chapman 的博客包含syscall
有关接口、系统调用及其参数的信息。Sys_write
和Sys_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
还会破坏RCX和R11寄存器。它们被视为易失性寄存器。不要指望它们在 之后保持相同的值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 位(例如EAX、EBX、EDI、ESI等),则处理器会将结果零扩展至64 位寄存器的高 32 位mov edi,1
。具有与 相同的效果mov rdi,1
。
这个答案不是编写 64 位代码的入门指南,只是关于使用接口。如果您对编写调用Csyscall
库并符合 64 位 System V ABI的代码的细微差别感兴趣,那么有一些合理的教程可以帮助您入门,例如Ray Toal 的 NASM 教程。他讨论了堆栈对齐、红色区域、寄存器使用以及 64 位 System V 调用约定的基本概述。
解决方案 2:
正如 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