关于NASM编写的HelloWorld程序strace输出异常的技术疑问
这问题我碰到过好几次了——核心原因是你的NASM代码用了32位的int 80h系统调用接口,但却编译成了64位目标文件,两者的调用规则完全不匹配,才导致strace输出一堆看似异常的内容。
为什么strace会显示write(0, NULL, 14)?
在32位Linux系统调用中,我们用int 80h,系统调用号存在eax,参数依次存在ebx、ecx、edx等寄存器里。但64位Linux下,正确的系统调用指令是syscall,并且寄存器映射完全不同:系统调用号存在rax,第一个参数在rdi,第二个在rsi,第三个在rdx...
当你把32位代码编译成64位程序后,执行int 80h时,内核会按照32位规则去解析寄存器,但64位寄存器的高32位可能有垃圾值,或者strace在64位环境下解析32位系统调用的参数时出现错位,所以才会把原本要传给write的ebx=1(STDOUT)解析成了文件描述符0(STDIN),其他参数也乱成了NULL,这完全是解析错位导致的,不是你的代码逻辑写错了,只是架构不匹配。
关于stat(NULL, ...) = 14的疑问
那个14不是错误码,而是你字符串Hello world!\n的长度(刚好14个字符)。因为系统调用被错误解析,strace把原本write系统调用的返回值(写入的字节数14)当成了stat系统调用的返回值,而stat的参数也是乱解析出来的,所以看起来莫名其妙。
两种修复方案
方案1:编译成32位程序
如果你想保留32位的int 80h代码,只需要修改编译和链接命令,生成32位可执行文件:
nasm -f elf32 hello.asm -o hello.o ld -m elf_i386 hello.o -o hello
64位Debian默认支持运行32位程序(如果没装可以用apt install libc6-i386安装依赖),运行后strace会输出正常的write(1, "Hello world!\n", 14) = 14。
方案2:改成64位系统调用代码
更推荐的方式是适配64位环境,使用syscall指令和64位系统调用号:
; Define variables in the data section SECTION .DATA hello: db 'Hello world!',10 helloLen: equ $-hello ; Code goes in the text section SECTION .TEXT GLOBAL _start _start: mov rax, 1 ; 64位write系统调用号是1(32位是4) mov rdi, 1 ; file descriptor 1 = STDOUT mov rsi, hello ; string to write mov rdx, helloLen ; length of string to write syscall ; call 64-bit kernel interface ; Terminate program mov rax, 60 ; 64位exit系统调用号是60(32位是1) mov rdi, 0 ; exit with error code 0 syscall
用你原来的命令编译链接:
nasm -f elf64 hello.asm -o hello.o ld hello.o -o hello
运行后strace会输出符合预期的结果:
execve("./hello", ["./hello"], 0x7ffc... /* 89 vars */) = 0 write(1, "Hello world!\n", 14) = 14 exit(0) = ? +++ exited with 0 +++
总结
不是strace的问题,是你的代码使用的系统调用接口和编译目标架构不匹配导致的。只要调整代码或者编译方式,就能解决这个异常输出的问题。
内容的提问来源于stack exchange,提问作者wx672

