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

Linux用户态程序[vvar]段转储:解决SIGBUS与映射页识别难题

问题

需要从x86、ARM或MIPS架构的Linux用户态程序中转储[vvar]段,但遇到以下问题:

  • [vvar]段仅前几页被映射,访问未填充页面会触发SIGBUS信号。
  • 尝试安装SIGBUS信号处理程序:返回后未映射页面仍未填充,会递归触发SIGBUS,无法恢复。
  • 尝试用/proc/self/pagemap识别已映射页面:该文件对[vvar]页无效,返回全0,无法判断页面是否填充。

简化代码如下:

// 从/proc/self/maps读取段信息:
get_local_segment_info("[vvar]", &e);  
// 返回 e.start_addr=0x7fffb4b6e000, e.size=16384
ASSERT(e.start_addr != NULL);

#if 1

gzwrite(gz_fd, e.start_addr, e.size);
// 触发SIGBUS失败

#else

self_pagemap_fd = open("/proc/self/pagemap",O_RDONLY);
ASSERT(self_pagemap_fd >= 0);

off64_t off = lseek64(self_pagemap_fd , 
             (((uintptr_t)e.start_addr) / page_size) * sizeof(uint64_t),
             SEEK_SET);
ASSERTf(off >= 0, "ERROR: could not lseek64 (%s)",strerror(errno));

size_t nread = read( self_pagemap_fd , 
                     (uint8_t *)pagemaps,
                     sizeof(uint64_t) * num_pages);
ASSERT(nread > 0);
printf("pagemap[0]=0x%" PRIx64 "\n", pagemaps[0]);  
// 输出 pagemap[0]=0x0

...
// 本应遍历页面仅复制已填充的部分,但pagemaps全为0,无法判断

#endif
可行解决方案

1. 用mincore检测已驻留页面

mincore系统调用可查询指定内存区域中哪些页面已加载到物理内存(即已映射),不受[vvar]特殊属性限制。

示例代码片段:

size_t page_size = sysconf(_SC_PAGESIZE);
size_t num_pages = (e.size + page_size - 1) / page_size;
unsigned char *vec = malloc(num_pages);
if (!vec) { /* 错误处理 */ }

// 调用mincore获取驻留页面信息
if (mincore(e.start_addr, e.size, vec) == -1) {
    perror("mincore failed");
    free(vec);
    return;
}

// 遍历每个页面,仅复制已驻留的
for (size_t i = 0; i < num_pages; i++) {
    if (vec[i] & 1) { // 位0表示页面已驻留
        uintptr_t page_addr = e.start_addr + i * page_size;
        size_t copy_size = (i == num_pages - 1) ? (e.size % page_size) : page_size;
        gzwrite(gz_fd, (void*)page_addr, copy_size);
    } else {
        // 未驻留页面,可写入0或跳过,根据需求处理
        unsigned char zero_buf[page_size] = {0};
        size_t copy_size = (i == num_pages - 1) ? (e.size % page_size) : page_size;
        gzwrite(gz_fd, zero_buf, copy_size);
    }
}

free(vec);

2. 改进SIGBUS信号处理程序,用siglongjmp跳过故障页面

直接返回会重复触发SIGBUS,可结合setjmp和siglongjmp在触发SIGBUS时跳转到安全点,跳过当前故障页面。注意信号处理程序中需使用异步信号安全的函数。

示例代码片段:

#include <setjmp.h>

static sigjmp_buf jmp_buf;
static volatile sig_atomic_t fault_occurred = 0;
static uintptr_t current_fault_addr = 0;

static void sigbus_handler(int sig, siginfo_t *info, void *ctx) {
    fault_occurred = 1;
    current_fault_addr = (uintptr_t)info->si_addr;
    siglongjmp(jmp_buf, 1);
}

// 注册信号处理程序
struct sigaction sa = {0};
sa.sa_sigaction = sigbus_handler;
sa.sa_flags = SA_SIGINFO;
if (sigaction(SIGBUS, &sa, NULL) == -1) {
    perror("sigaction failed");
    return;
}

size_t page_size = sysconf(_SC_PAGESIZE);
uintptr_t addr = e.start_addr;
size_t remaining = e.size;

while (remaining > 0) {
    fault_occurred = 0;
    size_t copy_size = (remaining > page_size) ? page_size : remaining;

    if (sigsetjmp(jmp_buf, 1) == 0) {
        gzwrite(gz_fd, (void*)addr, copy_size);
        addr += copy_size;
        remaining -= copy_size;
    } else {
        // 计算故障页面的起始地址,跳过该页
        uintptr_t page_start = current_fault_addr & ~(page_size - 1);
        size_t skip_size = page_start + page_size - addr;
        // 写入对应长度的0或跳过
        unsigned char zero_buf[page_size] = {0};
        gzwrite(gz_fd, zero_buf, skip_size);
        addr += skip_size;
        remaining -= skip_size;
    }
}

3. 读取/proc/self/mem指定范围

/proc/self/mem可直接访问进程内存空间,读取未映射页面时会返回EIO错误而非触发SIGBUS。可逐页读取,根据返回值判断页面是否存在。

示例代码片段:

int mem_fd = open("/proc/self/mem", O_RDONLY);
if (mem_fd == -1) {
    perror("open /proc/self/mem failed");
    return;
}

size_t page_size = sysconf(_SC_PAGESIZE);
uintptr_t addr = e.start_addr;
size_t remaining = e.size;

while (remaining > 0) {
    size_t read_size = (remaining > page_size) ? page_size : remaining;
    unsigned char buf[page_size];

    // 定位到目标地址
    if (lseek64(mem_fd, addr, SEEK_SET) == -1) {
        perror("lseek64 failed");
        close(mem_fd);
        return;
    }

    ssize_t n = read(mem_fd, buf, read_size);
    if (n == -1) {
        if (errno == EIO) {
            // 页面未映射,写入0
            memset(buf, 0, read_size);
            gzwrite(gz_fd, buf, read_size);
        } else {
            perror("read failed");
            close(mem_fd);
            return;
        }
    } else {
        gzwrite(gz_fd, buf, n);
    }

    addr += read_size;
    remaining -= read_size;
}

close(mem_fd);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 10:43:14