汇编编译的可执行文件在 Windows 上的 Ubuntu 上使用 INT 0x80 Linux 子系统不产生输出
Posted
技术标签:
【中文标题】汇编编译的可执行文件在 Windows 上的 Ubuntu 上使用 INT 0x80 Linux 子系统不产生输出【英文标题】:Assembly compiled executable using INT 0x80 on Ubuntu on Windows Subsystem for Linux doesn't produce output 【发布时间】:2017-12-10 05:14:35 【问题描述】:我一直在查看组装教程,并且正在尝试运行一个 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 是否与它有关?为什么它不产生输出,我该如何解决?
【问题讨论】:
您在 64 位可执行文件中使用 32 位系统调用接口这一事实可能与此有关。 我听说 Ubuntu-on-Windows 根本不支持 32 位可执行文件;也许他们也不支持 64 位可执行文件中的int 0x80
32 位 ABI。您的代码看起来可以在 Linux 上运行,the int 0x80
ABI is supported (but not recommended) in 64-bit mode.
哦,或者msg
在您的 64 位二进制文件中位于不适合 32 位的地址?这可以解释它,虽然这不是通常的布局。尝试使用strace ./hello
或单步与GDB 来查看eax
中的错误返回值
【参考方案1】:
问题在于适用于 Windows 的 Ubuntu(适用于 Linux 的 Windows 子系统)。它只支持64位syscall
接口和not the 32-bit x86 int 0x80
系统调用机制。
除了不能在 64 位二进制文件中使用 int 0x80
(32 位兼容性)之外,Windows 上的 Ubuntu (WSL) doesn't support running 32-bit executables 也一样。
您需要从使用int 0x80
转换为syscall
。这并不难。 syscall
使用了一组不同的寄存器,并且系统调用号与它们的 32 位对应项不同。 Ryan Chapman's blog 包含有关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 位代码中,如果指令的 destination 寄存器是 32 位(如 EAX、EBX、EDI、ESI 等)64 位寄存器的processor zero extends the result into the upper 32-bits。 mov edi,1
与mov rdi,1
效果相同。
这个答案不是编写 64 位代码的入门书,只是关于使用 syscall
接口。如果您对编写调用 C 库并符合 64 位 System V ABI 的代码的细微差别感兴趣,那么有一些合理的教程可以帮助您入门,例如 Ray Toal's NASM tutorial。他讨论了堆栈对齐、红色区域、寄存器使用以及 64 位 System V 调用约定的基本概述。
【讨论】:
我还要警告 64b 中的不同 ABI,将参数放入寄存器可能是容易掌握的部分,但对于刚开始学习的人来说,堆栈对齐和红色区域可能有点棘手部件。一般来说,我宁愿建议编译为 32b 二进制来回答这样的问题,但这在 windows 中不是一个选项,只支持 64b linux)【参考方案2】:正如 Ross Ridge 在 cmets 中已经指出的,在编译 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
【讨论】:
与大多数 Linux 系统不同,构建为 32 位不是 WSL 上的选项:int 0x80
ABI 完全不受支持,即使对于 32 位可执行文件也是如此。所以它就像一个没有CONFIG_IA32_EMULATION
的Linux内核。以上是关于汇编编译的可执行文件在 Windows 上的 Ubuntu 上使用 INT 0x80 Linux 子系统不产生输出的主要内容,如果未能解决你的问题,请参考以下文章