首页 > 解决方案 > 调用 mremap(2) 时出现奇怪的信号处理程序行为

问题描述

首先,这是代码

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h> /* mmap() is defined in this header */
#include <fcntl.h>
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <signal.h>
#include <execinfo.h>
#define FILE_NAME "mremap_test_file.txt"

void sighandler(int signum) {
    printf("Signal SIGSEGV recieved\n");
    exit(EXIT_FAILURE);
}

int main (int argc, char *argv[])
{


    //int fdin;
    int fdout = 0;
    static char stack[SIGSTKSZ];
    stack_t ss = {
        .ss_size = SIGSTKSZ,
        .ss_sp = stack,
    };
    struct sigaction sigact;
    sigaltstack(&ss, 0);
    int ret = sigaction(SIGSEGV, NULL, &sigact);
    if (ret == -1){
        perror("register sighandler");
        return -1;
    }
    sigact.sa_handler = sighandler;
    sigact.sa_flags |= SA_ONSTACK | SA_RESTART;
    ret = sigaction(SIGSEGV, &sigact, NULL);
    if (ret == -1){
        perror("register sighandler");
        return -1;
    }

    fdout = open(FILE_NAME, O_CREAT| O_RDWR);
    if( fdout < 0){

        perror("open");
        return -1;
    }
    char test_str[1024];
    memset(test_str,65,1024);
    write(fdout,test_str,1024);
    close(fdout);
    sync();
    if(argc > 2)
        return 0;
    fdout = open(FILE_NAME, O_CREAT| O_RDWR);
    if( fdout < 0){

        perror("open");
        return -1;
    }

    void * addr = NULL;
    addr = mmap(NULL, 1024, PROT_EXEC |PROT_WRITE, MAP_SHARED, fdout, 0);

    if(addr == MAP_FAILED ){
        perror("mmap");
        return -1;
    }
    void* remap_addr = addr;
    remap_addr = mremap(remap_addr, 1024, 512, (MREMAP_FIXED | MREMAP_MAYMOVE), (void*)((unsigned long)remap_addr + 4096) );
    if(remap_addr == MAP_FAILED){
        perror("mremap");
        return -1;
    }
    int a = *(int*)(0);

    remove(FILE_NAME);
    return 0;
}

我正在使用 gcc 版本 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.12) 来编译这段代码

你可以看到里面有int a = *(int*)(0);触发SIGSEGV的代码。奇怪的是,如果这行代码在mremap(2)自定义 SIGSEGV 处理程序被触发之前运行;如果这行代码在mremap(2)触发默认的 SIGSEGV 处理程序之后运行。

mremap(2)确实返回成功没有任何问题,所以我无法理解那些这样的行为

第一个案例 第二种情况

我还尝试了另一种情况,发现了这个 第三种情况 似乎同时调用了默认和自定义处理程序

标签: cgccsignals

解决方案


我没有看到这种行为,在这两种情况下,非法访问都会导致调用sighandler().

当然,这可能是因为我必须解决您的代码的几个问题(您提供的内容没有立即编译),特别是:

  • 创建具有允许我再次打开它们的权限的文件,因为它们是使用权限掩码零创建的:
    fdout = open(FILE_NAME, O_CREAT| O_RDWR, 0777);
  • 基于原始地址而不是尚未存在的重新映射地址重新映射:
    void *remap_addr = mremap(addr, 1024, 512, (MREMAP_FIXED | MREMAP_MAYMOVE), (void*)((unsigned long)addr + 4096) );

一旦这些问题得到解决,非法访问就会调用自定义处理程序,无论它是在mremap. 如果这不能解决您的问题,我建议您发布导致问题的实际代码:-)


您可能要考虑的另一件事是,这printf不是认为可以从信号处理程序中安全调用的函数之一。此链接详细说明了可以安全使用的内容,因此这可能是由printf自身内部的问题引起的。


顺便说一句,如果您在重新映射后安装(或重新安装)信号处理程序,看看会发生什么会很有趣。我之所以只提到这一点,是因为在其他情况下,调用某些函数会干扰最初被认为是一个单独的项目(sleepSIGALRM信号之间的冲突的模糊记忆,但这是很久以前的事了)。


我唯一可以建议的另一件事是,您可以sigaction在通话后使用它来获取信号的当前处置mremap。如果由于某种原因它已与您的处理程序分离,那应该希望能清楚地说明这一点。


推荐阅读