如何修复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
相关产品推荐
相关产品推荐

