如您所知,除了使用处理程序执行kill和stop/count之外,还可以捕获任何信号。
有三种无效地址访问:
试图在无效地址执行/跳转。
试图读取无效地址。
试图在无效地址写入。
我只对拒绝无效的读取访问感兴趣。因此,我们的想法是捕获所有分段错误,如果不是无效的读取访问,则捕获abort()
到目前为止,我只知道如何使用SEGV_MAPERRSEGV_ACCERRsigaction无关。

最佳答案

事实证明,在x86-64(又称AMD64)架构的Linux中,这实际上是非常可行的。
下面是一个示例程序crasher.c:

#define  _POSIX_C_SOURCE 200809L
#define  _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <ucontext.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

#if !defined(__linux__) || !defined(__x86_64__)
#error This example only works in Linux on x86-64.
#endif

#define  ALTSTACK_SIZE  262144

static const char hex_digit[16] = {
    '0', '1', '2', '3', '4', '5', '6', '7',
    '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};

static inline const char *signal_name(const int signum)
{
    switch (signum) {
    case SIGSEGV: return "SIGSEGV";
    case SIGBUS:  return "SIGBUS";
    case SIGILL:  return "SIGILL";
    case SIGFPE:  return "SIGFPE";
    case SIGTRAP: return "SIGTRAP";
    default:      return "(unknown)";
    }
}

static inline ssize_t internal_write(int fd, const void *buf, size_t len)
{
    ssize_t retval;
    asm volatile ( "syscall\n\t"
                 : "=a" (retval)
                 : "a" (1), "D" (fd), "S" (buf), "d" (len)
                 : "rcx", "r11" );
    return retval;
}

static inline int wrerr(const char *p, const char *q)
{
    while (p < q) {
        ssize_t n = internal_write(STDERR_FILENO, p, (size_t)(q - p));
        if (n > 0)
            p += n;
        else
        if (n == 0)
            return EIO;
        else
            return -n;
    }
    return 0;
}

static inline int wrs(const char *p)
{
    if (p) {
        const char *q = p;
        while (*q)
            q++;
        return wrerr(p, q);
    }
    return 0;
}

static inline int wrh(unsigned long h)
{
    static char buffer[4 + 2 * sizeof h];
    char       *p = buffer + sizeof buffer;

    do {
        *(--p) = hex_digit[h & 15];
        h /= 16UL;
    } while (h);

    *(--p) = 'x';
    *(--p) = '0';

    return wrerr(p, buffer + sizeof buffer);
}

static void crash_handler(int signum, siginfo_t *info, void *contextptr)
{
    if (info) {
        ucontext_t *const ctx = (ucontext_t *const)contextptr;
        wrs(signal_name(signum));
        if (ctx->uc_mcontext.gregs[REG_ERR] & 16) {
            const unsigned long sp = ctx->uc_mcontext.gregs[REG_RSP];
            /* Instruction fetch */
            wrs(": Bad jump to ");
            wrh((unsigned long)(info->si_addr));
            if (sp && !(sp & 7)) {
                wrs(" probably by the instruction just before ");
                wrh(*(unsigned long *)sp);
            }
            wrs(".\n");
        } else
        if (ctx->uc_mcontext.gregs[REG_ERR] & 2) {
            /* Write access */
            wrs(": Invalid write attempt to ");
            wrh((unsigned long)(info->si_addr));
            wrs(" by instruction at ");
            wrh(ctx->uc_mcontext.gregs[REG_RIP]);
            wrs(".\n");
        } else {
            /* Read access */
            wrs(": Invalid read attempt from ");
            wrh((unsigned long)(info->si_addr));
            wrs(" by instruction at ");
            wrh(ctx->uc_mcontext.gregs[REG_RIP]);
            wrs(".\n");
        }
    }

    raise(SIGKILL);
}

static int install_crash_handler(void)
{
    stack_t           altstack;
    struct sigaction  act;

    altstack.ss_size = ALTSTACK_SIZE;
    altstack.ss_flags = 0;
    altstack.ss_sp = mmap(NULL, altstack.ss_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_GROWSDOWN, -1, 0);
    if (altstack.ss_sp == MAP_FAILED) {
        const int retval = errno;
        fprintf(stderr, "Cannot map memory for alternate stack: %s.\n", strerror(retval));
        return retval;
    }
    if (sigaltstack(&altstack, NULL)) {
        const int retval = errno;
        fprintf(stderr, "Cannot use alternate signal stack: %s.\n", strerror(retval));
        return retval;
    }

    memset(&act, 0, sizeof act);
    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_SIGINFO | SA_ONSTACK;
    act.sa_sigaction = crash_handler;
    if (sigaction(SIGSEGV, &act, NULL) == -1 ||
        sigaction(SIGBUS,  &act, NULL) == -1 ||
        sigaction(SIGILL,  &act, NULL) == -1 ||
        sigaction(SIGFPE,  &act, NULL) == -1) {
        const int retval = errno;
        fprintf(stderr, "Cannot install crash signal handlers: %s.\n", strerror(retval));
        return retval;
    }

    return 0;
}

int main(int argc, char *argv[])
{
    void         (*jump)(void) = 0;
    unsigned char *addr = (unsigned char *)0;

    if (argc < 2 || argc > 3 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
        fprintf(stderr, "       %s call [ address ]\n", argv[0]);
        fprintf(stderr, "       %s read [ address ]\n", argv[0]);
        fprintf(stderr, "       %s write [ address ]\n", argv[0]);
        fprintf(stderr, "\n");
        return EXIT_SUCCESS;
    }
    if (argc > 2 && argv[2][0] != '\0') {
        char          *end = NULL;
        unsigned long  val;

        errno = 0;
        val = strtoul(argv[2], &end, 0);
        if (errno) {
            fprintf(stderr, "%s: %s.\n", argv[2], strerror(errno));
            return EXIT_FAILURE;
        }
        if (end)
            while (*end == '\t' || *end == '\n' || *end == '\v' ||
                   *end == '\f' || *end == '\r' || *end == ' ')
                end++;
        if (!end || end <= argv[2] || *end) {
            fprintf(stderr, "%s: Not a valid address.\n", argv[2]);
            return EXIT_FAILURE;
        }

        jump = (void *)val;
        addr = (void *)val;
    }

    if (install_crash_handler())
        return EXIT_FAILURE;

    if (argv[1][0] == 'c' || argv[1][0] == 'C') {
        printf("Calling address %p: ", (void *)jump);
        fflush(stdout);
        jump();
        printf("Done.\n");

    } else
    if (argv[1][0] == 'r' || argv[1][0] == 'R') {
        unsigned char  val;

        printf("Reading from address %p: ", (void *)addr);
        fflush(stdout);
        val = *addr;
        printf("0x%02x, done.\n", val);

    } else
    if (argv[1][0] == 'w' || argv[1][1] == 'W') {
        printf("Writing 0xC4 to address %p: ", (void *)addr);
        fflush(stdout);
        *addr = 0xC4;
        printf("Done.\n");
    }

    printf("No crash.\n");
    return EXIT_SUCCESS;
}

使用例如。
gcc -Wall -O2 crasher.c -o crasher

通过在命令行上指定操作和地址(可选),可以测试对任意地址的调用、读取或写入。不带参数运行以查看用法。
在我的机器上运行的一些示例:
./crasher call 0x100
Calling address 0x100: SIGSEGV: Bad jump to 0x100 probably by the instruction just before 0x400c4e.
Killed

./crasher write 0x24
Writing 0xC4 to address 0x24: SIGSEGV: Invalid write attempt to 0x24 by instruction at 0x400bad.
Killed

./crasher read 0x16
Reading from address 0x16: SIGSEGV: Invalid read attempt from 0x16 by instruction at 0x400ca3.
Killed

./crasher write 0x400ca3
Writing 0xC4 to address 0x400ca3: SIGSEGV: Invalid write attempt to 0x400ca3 by instruction at 0x400bad.
Killed

./crasher read 0x400ca3
Reading from address 0x400ca3: 0x41, done.
No crash.

注意,访问类型是从((ucontext_t *)contextptr)->uc_mcontext.gregs[REG_ERR]寄存器(从信号处理程序上下文)获得的;它与x86_pf_error_code in the Linux kernel sources中定义的arch/x86/mm/fault.c枚举匹配。
崩溃处理程序本身非常简单,只需要对前面提到的“register”进行exmine,就可以获得OP所要查找的信息。
为了输出崩溃报告,我打开了代码write()系统调用。(由于某些原因,wrh()函数所需的小缓冲区不能在堆栈上,所以我只是将其设为静态。)
我没有费心实现mincore()系统调用来验证堆栈地址(sp函数中的crash_handler());可能需要避免双重错误(SIGSEGV本身发生)。
类似地,我不必在crash_handler()结尾打开raise()代码,因为现在在x86-64上,它是在使用crash_handler()系统调用的C库中实现的,这意味着我还必须打开tgkill(pid, tid, signum)getpid()系统调用的代码。我只是懒惰。
最后,上面的代码编写得很粗心,因为我自己是在与OP user2284570交换了注释之后才发现这一点的,只是想把一些东西放在一起,看看这种方法是否真正可靠地工作。(看起来确实如此,但我只是在一台机器上进行了简单的测试。)因此,如果您注意到代码中有任何错误、拼写错误、thinkos或其他需要修复的地方,请在评论中告诉我,这样我就可以修复它。

关于c - 从内部捕获SIGSEGV时,如何知道所涉及的无效访问类型?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/43033177/

10-17 01:35