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

如何在Sigaction中区分SIGSEGV的读/写内存访问类型?

问题分析与解决方案

你的代码存在两个核心问题,导致无法正确区分读写类型的SIGSEGV:

1. mprotect操作粒度不符合要求

mprotect要求操作的内存区域必须页对齐,且大小是页的整数倍。你当前用info->si_addr(大概率不是页起始地址)和sizeof(int)调用mprotect,会直接导致调用失败(可通过perror("mprotect")验证),内存权限未被正确修改,后续访问仍触发错误,干扰错误码判断。

2. 错误码获取方式依赖架构且不正确

你直接使用uc->uc_mcontext.gregs[REG_ERR]获取错误码,这仅适用于32位x86架构。在64位x86(x86_64)架构下,错误码存储在uc->uc_mcontext.err字段中:

  • 错误码的位1置1时,表示是写访问触发的SIGSEGV;
  • 位1置0时,表示是读访问触发的SIGSEGV。

修正后的代码

#include <signal.h>
#include <ucontext.h>
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>

void handler(int sig, siginfo_t* info, void* ucontext)
{
    int access_type = -1;
    ucontext_t* uc = (ucontext_t *)ucontext;
    // x86_64架构下,错误码存在uc_mcontext.err中
    unsigned long err_code = uc->uc_mcontext.err;

    // 位1标识写访问
    if (err_code & (1 << 1)) {
        access_type = 2; // 写操作
    } else {
        access_type = 4; // 读操作
    }

    // 将地址对齐到当前页的起始位置
    long PAGE_SIZE = sysconf(_SC_PAGE_SIZE);
    void* page_start = (void *)((uintptr_t)info->si_addr & ~(PAGE_SIZE - 1));
    
    // 修改整页的内存权限
    if (access_type == 2) {
        if (mprotect(page_start, PAGE_SIZE, PROT_READ | PROT_WRITE) == -1) {
            perror("mprotect set write");
            exit(1);
        }
    } else if (access_type == 4) {
        if (mprotect(page_start, PAGE_SIZE, PROT_READ) == -1) {
            perror("mprotect set read");
            exit(1);
        }
    }
}

int main()
{
    void *vm_ptr;
    long PAGE_SIZE = sysconf(_SC_PAGE_SIZE);
    int vm_size = 16 * PAGE_SIZE;
    if (posix_memalign(&vm_ptr, PAGE_SIZE, vm_size))
    {
        fprintf(stderr, "posix_memalign failed\n");
        return -1;
    }
    if (mprotect(vm_ptr, vm_size, PROT_NONE))
    {
        fprintf(stderr, "mprotect failed\n");
        return -1;
    }

    struct sigaction sa;
    sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
    sigemptyset(&sa.sa_mask);
    sa.sa_sigaction = handler;
    if (sigaction(SIGSEGV, &sa, NULL) == -1) {
        perror("sigaction");
        return -1;
    }
    
    // 触发写类型SIGSEGV
    int *ptr = (int *)vm_ptr;
    *ptr = 10;
    printf("Write completed: %d\n", *ptr);

    // 可选:测试读类型SIGSEGV(先将权限重置为PROT_NONE)
    if (mprotect(vm_ptr, PAGE_SIZE, PROT_NONE) == -1) {
        perror("mprotect reset for read test");
        return -1;
    }
    printf("Read completed: %d\n", *ptr);

    return 0;
}

额外说明

  • 如果程序运行在32位x86架构下,需将错误码获取改回uc->uc_mcontext.gregs[REG_ERR],同样检查位1是否置1;
  • 信号处理函数中修改权限后,内核会重新执行触发错误的指令,务必确保权限修改正确,避免无限触发SIGSEGV;
  • 用perror可以快速排查系统调用的错误原因。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 12:00:51