You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

在mmap内存中执行代码触发段错误的问题排查

一级间接调用正常但二级间接调用触发段错误的函数复制问题

先把你提供的代码补全并整理成可复现的版本(补全了mmap的缺失参数):

#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>

#define BODY_SIZE 100

int f(void) { return 42; }

int (*G(void))(void) { return f; }

int (*(*H(void))(void))(void) { return G; }

int (*g(void))(void) {
    void *r = mmap(0, BODY_SIZE, PROT_READ|PROT_WRITE|PROT_EXEC, 
                   MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
    if (r == MAP_FAILED) {
        perror("mmap failed");
        return NULL;
    }
    // 复制f函数的汇编代码到内存区域
    memcpy(r, f, BODY_SIZE);
    return (int (*)(void))r;
}

int main() {
    // 一级间接调用:正常运行
    int (*fp1)(void) = g();
    printf("一级调用结果:%d\n", fp1());

    // 二级间接调用:触发段错误
    int (*(*fp2)(void))(void) = g();
    printf("二级调用结果:%d\n", (*fp2())());
    return 0;
}

问题根源分析

你遇到的段错误本质是类型不匹配导致的非法地址访问,或者是函数复制时的逻辑错位:

  1. 类型误用是直接原因

    • 一级调用时,你把g()返回的指针当成int (*)(void)(指向返回int的函数),调用它执行的是复制后的f,自然返回42,完全正常。
    • 二级调用时,你强行把同一个指针转换成int (*(*)(void))(void)(指向返回函数指针的函数),调用fp2()时相当于执行f()并把返回的42当作函数指针去调用——42是一个极低的无效地址,访问它必然触发段错误。
  2. 函数复制的目标错位
    如果你原本想复制的是GH这类返回函数指针的函数,却错误复制了f,那类型不匹配的问题就必然出现。另外如果复制的是位置相关代码(非PIC编译的函数),函数内部的相对偏移在新内存地址会失效,也可能触发段错误。

修复方案

方案1:严格匹配类型与复制目标

如果你想实现二级间接调用的功能,需要让g()复制G这类返回函数指针的函数,同时保证返回值类型正确:

// 修改g函数,复制G而非f,返回类型匹配G的函数指针类型
int (*(*g(void))(void))(void) {
    void *r = mmap(0, BODY_SIZE, PROT_READ|PROT_WRITE|PROT_EXEC, 
                   MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
    if (r == MAP_FAILED) {
        perror("mmap failed");
        return NULL;
    }
    memcpy(r, G, BODY_SIZE);
    return (int (*(*)(void))(void))r;
}

int main() {
    // 二级调用现在可以正常运行
    int (*(*fp2)(void))(void) = g();
    int (*fp1)(void) = fp2();
    printf("二级调用结果:%d\n", fp1());
    return 0;
}

方案2:使用PIC编译避免位置相关错误

如果你的函数复制场景需要支持任意函数,编译时要开启位置无关代码(PIC)选项,确保复制后的函数在新地址仍能正确解析内部引用:

  • GCC/Clang编译时添加参数:
gcc your_code.c -o your_program -fPIC

PIC模式下,函数内部的地址引用都是基于当前指令指针的相对偏移,复制到任何内存地址都能正确计算目标地址,避免因位置变化导致的无效访问。

方案3:准确计算函数大小

你定义的BODY_SIZE=100太随意,可能截断函数代码或复制无关数据。可以通过标记函数估算实际大小:

// 在目标函数后定义空函数作为结束标记
int f_end(void) { return 0; }

// 计算f的实际大小
size_t f_size = (size_t)f_end - (size_t)f;
// 复制时使用实际大小
memcpy(r, f, f_size);

额外注意事项

  • 确保mmap申请的内存有PROT_EXEC权限,否则执行时会触发权限错误;
  • 避免复制包含动态链接重定位信息的函数(比如调用动态库的函数),这类函数复制后无法正确解析重定位;
  • 编译时可以添加-fno-inline禁用内联优化,防止目标函数被编译器内联导致复制的不是实际函数代码。

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

火山引擎 最新活动