You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无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,但系统调用号和链接规则有差异。

关键注意点:

  1. 退出的系统调用号是0x2000001(macOS系统调用需要加0x2000000前缀);
  2. 推荐用clang编译,链接选项加-nostdlib,x86_64架构需指定-arch x86_64;
  3. 入口点符号是_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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 06:50:45