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

为何无标准库编译的MIPS32(R3000)简易C程序出现段错误?

问题描述

尝试编写一个调用函数并将返回值作为退出码的MIPS架构C程序,代码如下:

int increment(int a) {
    return a + 1;
}

void __start() {
    // 调用increment函数并获取返回值
    int exitcode = increment(1337);

    // 执行exit系统调用
    asm volatile (
        "li $v0, 0x1096\n"    // MIPS的exit系统调用号(0x1096)
        "move $a0, %0\n"  // 退出码
        "syscall\n"       // 触发系统调用
        :
        : "r" (exitcode)
    );

    // 此行永远不会被执行
    while (1) {}
}

使用以下命令编译:

/usr/bin/mips-linux-gnu-gcc -static -mno-mt -mfp32 -march=R3000 -EB -O0 -DNDEBUG -nostdlib main.c -o program

通过qemu-mips运行时触发段错误,调试输出如下:

$ qemu-mips -d in_asm,cpu program
----------------
IN: __start
0x0040015c:  lui        gp,0x2
0x00400160:  addiu      gp,gp,-32716
0x00400164:  addu       gp,gp,t9
0x00400168:  addiu      sp,sp,-32
0x0040016c:  sw ra,28(sp)
0x00400170:  sw gp,16(sp)
0x00400174:  li a0,1337
0x00400178:  lw t9,-32744(gp)
0x0040017c:  nop
0x00400180:  bal        0x400150
0x00400184:  nop

pc=0x0040015c HI=0x00000000 LO=0x00000000 ds 00a2 00000000 0
GPR00: r0 00000000 at 00000000 v0 00000000 v1 00000000
GPR04: a0 00000000 a1 00000000 a2 00000000 a3 00000000
GPR08: t0 00000000 t1 00000000 t2 00000000 t3 00000000
GPR12: t4 00000000 t5 00000000 t6 00000000 t7 00000000
GPR16: s0 00000000 s1 00000000 s2 00000000 s3 00000000
GPR20: s4 00000000 s5 00000000 s6 00000000 s7 00000000
GPR24: t8 00000000 t9 00000000 k0 00000000 k1 00000000
GPR28: gp 00000000 sp 407fff50 s8 00000000 ra 00000000
CP0 Status  0x20000010 Cause   0x00000000 EPC    0x00000000
    Config0 0x80008482 Config1 0x9e190c8f LLAddr 0x0000000000000000
    Config2 0x80000000 Config3 0x00000000
    Config4 0x00000000 Config5 0x00000000
qemu: uncaught target signal 11 (Segmentation fault) - core dumped
Segmentation fault (core dumped)

使用-O3编译时,increment函数会被编译器内联优化,程序可正常运行;但在-O0级别下,任何函数调用都会触发段错误,需排查原因。

问题根源

核心问题是全局指针($gp)初始化无效,导致函数调用时通过$gp访问全局偏移表(GOT)触发非法内存访问。

在MIPS架构中,低优化级别(如-O0)下,编译器会依赖$gp寄存器来定位全局变量和函数的GOT条目,进而完成函数调用。但编译时使用了-nostdlib选项,跳过了标准库的启动代码——而标准库启动代码的关键职责之一就是正确初始化$gp,使其指向GOT的有效地址。

当前__start函数中的$gp初始化逻辑依赖t9寄存器的值,但程序启动时t9并未被正确设置,导致计算出的$gp地址完全无效。当执行到lw t9,-32744(gp)时,会访问非法内存地址,直接触发段错误。

而-O3优化时,编译器会将increment函数内联展开,无需通过GOT查找函数地址,自然绕开了$gp初始化失败的问题,因此程序能正常运行。

解决方案

有两种可行的修复方式:

方式一:保留必要启动代码

将编译选项中的-nostdlib替换为-nostartfiles。-nostartfiles会跳过标准库中main函数的包装逻辑,但保留用于初始化$gp等关键寄存器的启动代码。修改后的编译命令:

/usr/bin/mips-linux-gnu-gcc -static -mno-mt -mfp32 -march=R3000 -EB -O0 -DNDEBUG -nostartfiles main.c -o program

启动代码会自动完成$gp的正确初始化,函数调用即可正常执行。

方式二:手动初始化$gp

如果必须使用-nostdlib,可以手动通过汇编代码正确初始化$gp。需要先确定二进制文件中GOT的绝对地址,然后直接将其加载到$gp中。例如:

void __start() {
    // 手动初始化gp为GOT的绝对地址(需根据实际二进制调整0x4000f0)
    asm volatile ("li $gp, 0x4000f0");

    int exitcode = increment(1337);

    asm volatile (
        "li $v0, 0x1096\n"
        "move $a0, %0\n"
        "syscall\n"
        :
        : "r" (exitcode)
    );

    while (1) {}
}

这种方式需要通过objdump等工具查看二进制的GOT地址,灵活性较差,仅适合特殊场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 04:35:54