x86_64 Ubuntu无启动文件与标准库时GCC内联汇编运行无输出问题
问题根因
你的代码编译无报错但运行无输出的核心原因是架构与系统调用约定不匹配:你在x86_64平台上混用了32位i386的系统调用触发方式与64位系统调用号,二者完全不兼容。
具体错误点:
- x86_64原生系统调用使用
syscall指令触发,不是32位平台的int $0x80软中断,二者的系统调用号、参数传递寄存器规则完全不同。你定义的__NR_write=1、__NR_exit=60是x86_64的系统调用号,但搭配int $0x80触发时,CPU会按照32位规则解析调用号和参数,根本不会执行你预期的write、exit逻辑。 - 即使要使用
int $0x80兼容接口,32位模式下exit的调用号是1、write的调用号是4,和你定义的64位调用号完全对不上,且32位中断无法正确访问64位高地址空间的字符串指针,必然运行异常。
x86_64系统调用正确约定
编写无标准库依赖的GCC内联汇编系统调用,必须严格遵守x86_64 SysV调用规范:
- 触发指令:
syscall - 系统调用号存入
rax寄存器 - 参数按顺序存入对应寄存器:第1参数→
rdi、第2参数→rsi、第3参数→rdx、第4参数→r10、第5参数→r8、第6参数→r9 - 系统调用返回值存入
rax - 注意:
syscall指令会自动覆盖rcx(存返回地址)、r11(存rflags寄存器值),内联汇编的破坏列表(clobber)必须声明这两个寄存器,否则会导致编译器寄存器分配错误,引发不可预知的问题。
修正后的可运行代码
#define __NR_write 1 #define __NR_exit 60 long sys_write(unsigned int fd, const char *buf, size_t count) { long ret; asm volatile ( "syscall" : "=a"(ret) : "a"(__NR_write), "D"(fd), "S"(buf), "d"(count) : "rcx", "r11", "memory" ); return ret; } _Noreturn void sys_exit(int status) { long ret; asm volatile ( "syscall" : "=a"(ret) : "a"(__NR_exit), "D"(status) : "rcx", "r11", "memory" ); __builtin_unreachable(); } void _start(void) { sys_write(1, "Hello World\n", 12); sys_exit(0); }
使用你原来的编译命令即可正常编译运行:
gcc -nostartfiles -nodefaultlibs -nostdlib hello.c -o hello
额外说明
- 不建议在64位程序中使用
int $0x80接口,这是内核为32位兼容保留的legacy接口,性能比原生syscall差,还存在指针截断、地址空间访问限制的问题。 - 内联汇编的寄存器约束要对应正确:
"D"对应rdi、"S"对应rsi,不要错用32位约定下的ebx、ecx对应约束。 - 原代码中字符串末尾手动添加的
\0是多余的,write系统调用按照传入的长度参数输出内容,不需要C字符串的结束符,计算长度时注意把换行符计入即可。
内容的提问来源于stack exchange,提问作者Jaeger
相关产品推荐
相关产品推荐

