无Glibc/CRT时,C中用内联汇编获取_start参数并调用main()
无依赖Glibc/CRT时在
_start中获取命令行参数并调用main(多平台适配) 先直接解决你遇到的Linux x86_64代码问题:程序返回值始终为0的核心原因是你自定义的exit函数没有正确实现系统调用,另外编译时也没指定禁用标准库的链接选项。下面是各平台的完整实现和问题排查细节:
一、Linux 平台实现
1. x86_64 架构
Linux x86_64下,内核会把argc放在rdi寄存器、argv放在rsi寄存器传递给_start——你之前的寄存器读取逻辑是对的,但关键要正确实现退出的系统调用,并且编译时要彻底禁用标准库。
正确代码:
// 用系统调用实现退出,完全不依赖Glibc的exit() void _exit(int code) { asm volatile ( "movq $60, %%rax\n" // Linux x86_64的_exit系统调用号是60 "movq %0, %%rdi\n" "syscall" : : "r"(code) : "%rax", "%rdi" ); } void _start() { int argc; char **argv; // 从寄存器读取argc和argv asm volatile ( "movq %%rdi, %0\n" "movq %%rsi, %1\n" : "=r"(argc), "=r"(argv) : : "%rdi", "%rsi" ); // 调用main函数,用返回值退出 int ret = main(argc, argv); _exit(ret); } // 示例main函数,返回argc作为退出码验证功能 int main(int argc, char **argv) { return argc; }
编译命令:
gcc -nostdlib -static -o no_glibc_start no_glibc_start.c
你的旧代码问题排查:
- 自定义的
exit函数是空实现,没有触发系统调用,导致程序异常终止,内核默认返回0; - 编译时如果没加
-nostdlib,链接器会偷偷链接Glibc的启动代码,覆盖你自己的_start函数;
2. i386 架构
Linux i386下,内核会把参数压入栈中:栈顶是argc,紧接着是argv[0]指针、argv[1]指针……最后是NULL,再之后是环境变量数组。
正确代码:
void _exit(int code) { asm volatile ( "movl $1, %%eax\n" // Linux i386的_exit系统调用号是1 "movl %0, %%ebx\n" "int $0x80" : : "r"(code) : "%eax", "%ebx" ); } void _start() { int argc; char **argv; // 从栈中读取argc和argv asm volatile ( "popl %0\n" // 栈顶弹出argc "movl %%esp, %1\n" // 当前栈顶就是argv数组的起始地址 : "=r"(argc), "=r"(argv) : : "%esp" ); int ret = main(argc, argv); _exit(ret); } int main(int argc, char **argv) { return argc; }
编译命令:
gcc -m32 -nostdlib -static -o no_glibc_start_32 no_glibc_start_32.c
二、Mac OS X 平台指引
macOS(原Mac OS X)的x86_64架构下,内核传递argc和argv的方式和Linux x86_64类似:rdi存argc,rsi存argv,但系统调用号和链接规则有差异。
关键注意点:
- 退出的系统调用号是
0x2000001(macOS系统调用需要加0x2000000前缀); - 推荐用
clang编译,链接选项加-nostdlib,x86_64架构需指定-arch x86_64; - 入口点符号是
_start,兼容大部分macOS版本;
示例代码片段:
void _exit(int code) { asm volatile ( "movq $0x2000001, %%rax\n" // macOS的_exit系统调用 "movq %0, %%rdi\n" "syscall" : : "r"(code) : "%rax", "%rdi" ); } void _start() { int argc; char **argv; asm volatile ( "movq %%rdi, %0\n" "movq %%rsi, %1\n" : "=r"(argc), "=r"(argv) : : "%rdi", "%rsi" ); int ret = main(argc, argv); _exit(ret); } int main(int argc, char **argv) { return argc; }
编译命令:
clang -nostdlib -arch x86_64 -o mac_start mac_start.c
三、Windows 平台指引
Windows没有_start这个标准入口点,默认CRT入口是_mainCRTStartup或WinMainCRTStartup。要实现无CRT的自定义入口,需要:
1. x86_64 架构
- 自定义入口函数(比如
my_entry),通过链接器选项/ENTRY:my_entry指定; - 内核传递参数方式:
rcx=argc,rdx=argv,r8=envp; - 必须调用
ExitProcess函数(来自kernel32.dll)退出,不建议直接用Windows系统调用(系统调用号随版本变化);
示例代码(MSVC编译):
#include <windows.h> // __declspec(naked)表示函数无自动生成的 prologue/epilogue void __declspec(naked) my_entry() { asm volatile ( "mov rcx, rcx\n" // argc在rcx寄存器 "mov rdx, rdx\n" // argv在rdx寄存器 "call main\n" // 调用main,返回值存在rax "mov rcx, rax\n" "call ExitProcess" // 调用系统退出函数 ); } int main(int argc, char **argv) { return argc; }
编译链接命令:
cl /ENTRY:my_entry /NODEFAULTLIB kernel32.lib win_entry.c
2. x86 架构
- 参数通过栈传递:栈顶是返回地址,之后是
argc,接着是argv指针数组; - 同样需要指定
/ENTRY:my_entry链接选项,调用ExitProcess退出;
示例入口代码:
void __declspec(naked) my_entry() { asm volatile ( "pop eax\n" // 弹出栈顶的返回地址 "pop ecx\n" // 读取argc到ecx "mov edx, esp\n" // argv数组起始地址在esp "call main\n" "push eax\n" "call ExitProcess" ); }
内容的提问来源于stack exchange,提问作者C Perfomance
相关产品推荐
相关产品推荐

