gpt4 book ai didi

c - 从内部捕获 SIGSEGV 时,如何知道涉及的无效访问类型?

转载 作者:太空宇宙 更新时间:2023-11-04 08:05:41 26 4
gpt4 key购买 nike

如您所知,可以捕获任何信号,但使用处理程序杀死并停止/计数。
存在三种无效地址访问:

  • 尝试在无效地址执行/跳转
  • 尝试在无效地址读取
  • 尝试在无效地址写入

我只对拒绝无效读取访问感兴趣。所以我的想法是捕获所有段错误,如果不是无效读取访问,则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] 寄存器(来自信号处理程序上下文)中获得的;它与 arch/x86/mm/fault.c in the Linux kernel sources 中定义的 x86_pf_error_code 枚举相匹配.

崩溃处理程序本身非常简单,只需要检查上述“寄存器”即可获取 OP 寻求的信息。

为了输出崩溃报告,我对 write() 系统调用进行了开放编码。 (由于某些原因,wrh() 函数所需的小缓冲区不能在堆栈上,所以我只是将其设为静态。)

我没有费心去实现 mincore() 系统调用来验证堆栈地址(crash_handler() 函数中的 sp) );可能有必要避免双重故障(SIGSEGV 发生在 crash_handler() 本身)。

同样,我也懒得去打开 crash_handler() 末尾的 raise() 代码,因为如今在 x86-64 上它是在使用 tgkill(pid, tid, signum) 系统调用的 C 库,这意味着我还必须对 getpid()gettid( ) 系统调用。我只是懒惰。

最后,上面的代码写得很粗心,因为我自己是在与 OP user2284570 交换意见后才发现的,只是想把一些东西放在一起看看这种方法是否真的可靠。 (似乎是这样,但我只在一台机器上对此进行了轻微测试。)因此,如果您发现代码中有任何错误、错别字、想法或其他需要修复的问题,请在评论中告诉我,所以我可以修复它。

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

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com