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

如何修复Linux Ubuntu下用内联汇编实现的_start调用main的代码?

修复Linux下自定义_start调用main的内联汇编代码

你的代码在Linux下无法运行,核心原因是Windows与Linux的系统调用约定、寄存器使用规则、符号命名完全不同,以下是问题分析和修复方案:

核心问题点

  • 寄存器宽度不匹配:你使用了32位寄存器(如%edi、%ebx),但64位Linux系统必须使用64位寄存器(%rdi、%rsi等),否则会截断数据导致错误。
  • envp获取逻辑错误:Linux x86_64的_start入口中,envp不在rdx寄存器,而是在argv数组末尾之后(argv[argc]为NULL,紧接着就是envp的起始指针)。
  • 函数符号错误:Linux下C编译器生成的main函数符号是main,而非Windows平台的_main。
  • 返回值处理失效:未将main的返回值传递给exit系统调用,导致程序退出码始终为0。
  • 编译链接未跳过默认启动文件:系统默认的crt0会提供_start入口,必须显式跳过才能使用自定义的_start。

修复后的代码

#include <stdio.h>

int main(int argc, char *argv[], char *envp[])
{
    printf("Hello, World!\n");
    // 可选:打印环境变量验证逻辑正确性
    // for (int i = 0; envp[i]; i++) printf("envp[%d]: %s\n", i, envp[i]);
    return 42; // 自定义返回值,用于验证exit系统调用的参数传递
}

// 标记_start为不会返回的函数,避免编译器生成冗余代码
__attribute__((noreturn)) void _start()
{
    int argc;
    char **argv, **envp;
    int ret;

    // 从_start入口寄存器中获取argc、argv,计算envp的地址
    asm volatile (
        "mov %%rdi, %0\n\t"          // _start启动时,rdi寄存器存储argc
        "mov %%rsi, %1\n\t"          // rsi寄存器存储argv指针
        "lea 8(%%rsi, %0, 8), %2"    // envp = argv + argc + 1(每个指针占8字节)
        : "=r"(argc), "=r"(argv), "=r"(envp)
        :
        : "rdi", "rsi"
    );

    // 调用main并保存其返回值
    asm volatile (
        "mov %0, %%rdi\n\t"
        "mov %1, %%rsi\n\t"
        "mov %2, %%rdx\n\t"
        "call main\n\t"              // Linux下直接调用符号main而非_main
        "mov %%eax, %3"              // 将main的返回值存入ret变量
        : "=r"(ret)
        : "r"(argc), "r"(argv), "r"(envp)
        : "rdi", "rsi", "rdx", "rax" // 声明被修改的寄存器,避免编译器优化干扰
    );

    // 调用exit系统调用终止程序
    asm volatile (
        "mov %0, %%rdi\n\t"          // exit的参数(退出码)存入rdi寄存器
        "mov $60, %%rax\n\t"         // syscall编号60对应exit系统调用
        "syscall"
        :
        : "r"(ret)
        : "rdi", "rax"
    );

    // 由于noreturn属性,此处不会执行,仅作冗余保护
    while(1);
}

编译运行命令

编译时必须跳过系统默认的启动文件,才能使用自定义的_start:

gcc -nostartfiles -o custom_start custom_start.c

运行后可通过echo $?查看程序退出码,会输出42(对应main的返回值),验证功能正常。

更简洁的合并写法

如果想减少内联汇编块的数量,可将逻辑合并为一个块:

#include <stdio.h>

int main(int argc, char *argv[], char *envp[])
{
    printf("Hello, World!\n");
    return 42;
}

__attribute__((noreturn)) void _start()
{
    asm volatile (
        // 暂存argc和argv,计算envp地址
        "mov %%rdi, %%r8\n\t"
        "mov %%rsi, %%r9\n\t"
        "lea 8(%%rsi, %%rdi, 8), %%rdx\n\t"
        // 调用main函数
        "call main\n\t"
        // 调用exit系统调用退出程序
        "mov %%eax, %%rdi\n\t"
        "mov $60, %%rax\n\t"
        "syscall"
        :
        :
        : "rdi", "rsi", "rdx", "r8", "r9", "rax", "memory"
    );

    while(1);
}

内容的提问来源于stack exchange,提问作者Muhab Joumaa

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 20:00:55