如何捕捉使用sigsegv的内存读取和写入?

如何欺骗Linux认为内存读/写成功? 我正在编写一个C ++库,以便所有读取/写入操作都被redirect并最终用户透明地处理。 任何时候一个variables被写入或读取,库将需要捕获该请求,并将其发送到将处理来自那里的数据的硬件模拟。

请注意,我的图书馆依赖于以下平台:

Linux ubuntu 3.16.0-39-generic#53〜14.04.1 –Ubuntu SMP x86_64 GNU / Linux

gcc(Ubuntu 4.8.2-19ubuntu1)4.8.2

当前方法:捕获SIGSEGV并增加REG_RIP

我目前的做法是使用mmap()获取内存区域,并使用mprotect()closures访问。 我有一个SIGSEGV处理程序来获取包含内存地址的信息,在其他地方导出读/写,然后递增上下文REG_RIP。

 void handle_sigsegv(int code, siginfo_t *info, void *ctx) { void *addr = info->si_addr; ucontext_t *u = (ucontext_t *)ctx; int err = u->uc_mcontext.gregs[REG_ERR]; bool is_write = (err & 0x2); // send data read/write to simulation... // then continue execution of program by incrementing RIP u->uc_mcontext.gregs[REG_RIP] += 6; } 

这适用于非常简单的情况,例如:

 int *num_ptr = (int *)nullptr; *num_ptr = 10; // write segfault 

但是对于任何更复杂的事情,我都会收到一个SIGABRT:

30729非法指令(核心转储)./$target

在SIGSEGV处理程序中使用mprotect()

如果我不增加REG_RIP, handle_sigsegv()将被内核一次又一次地调用,直到内存区域可用于读取或写入。 我可以为该特定的地址运行mprotect() ,但有多个注意事项:

  • 后续的内存访问将不会触发SIGSEGV,因为内存区域现在​​具有PROT_WRITEfunction。 我试图创build一个不断将该区域标记为PROT_NONE的线程,但是这并不能解决下一个问题:
  • mprotect()将在一天结束时执行读取或写入内存,使我的库的用例无效。

编写设备驱动程序

我还试图编写一个设备模块,以便库可以在char设备上调用mmap() ,驱动程序将在其中处理读取和写入操作。 这在理论上是有道理的,但是我还没有能够(或者没有这方面的知识)来捕捉每一个加载/存储处理器问题的设备。 我试图覆盖映射的vm_operations_struct和/或inode的address_space_operations结构,但是只有当页面出现故障或页面刷新到后台存储时才会调用读/写操作。

也许我可以像上面解释的那样,在无处写入数据的设备(类似于/dev/null )上使用mmap()mprotect() ,然后有一个进程来识别读/写并从那里路由数据(? )。

使用syscall()并提供一个恢复器组合函数

以下是从segvcatch项目1中提取的, segvcatch转换为exception。

 #define RESTORE(name, syscall) RESTORE2(name, syscall) #define RESTORE2(name, syscall)\ asm(\ ".text\n"\ ".byte 0\n"\ ".align 16\n"\ "__" #name ":\n"\ " movq $" #syscall ", %rax\n"\ " syscall\n"\ ); RESTORE(restore_rt, __NR_rt_sigreturn) void restore_rt(void) asm("__restore_rt") __attribute__ ((visibility("hidden"))); extern "C" { struct kernel_sigaction { void (*k_sa_sigaction)(int, siginfo_t *, void *); unsigned long k_sa_flags; void (*k_sa_restorer)(void); sigset_t k_sa_mask; }; } // then within main ... struct kernel_sigaction act; act.k_sa_sigaction = handle_sigegv; sigemptyset(&act.k_sa_mask); act.k_sa_flags = SA_SIGINFO|0x4000000; act.k_sa_restorer = restore_rt; syscall(SYS_rt_sigaction, SIGSEGV, &act, NULL, _NSIG / 8); 

但是这最终的function与普通的sigaction()configuration没有区别。 如果我没有设置恢复function,信号处理程序不会被调用多次,即使内存区域仍然不可用。 也许在这里我可以用内核信号做一些其他的诡计。


同样,库的整个目标是透明地处理对内存的读写操作。 也许有更好的办法,可能用ptrace()甚至更新生成segfault信号的内核代码,但重要的是最终用户的代码不需要改变。 我已经看到使用setjmp()longjmp()在segfault之后继续的示例,但是这需要将这些调用添加到每个内存访问。 将段落转换为try / catch也是一样的。


1个 segvcatch项目

您可以使用mprotect并通过让SIGSEGV处理程序在标志寄存器中设置T标志来避免您注意到的第一个问题。 然后,您添加一个SIGTRAP处理程序,恢复mprotected内存并清除T标志。

T标志使处理器单步执行,所以当SEGV处理程序返回时,它将执行该单个指令,然后立即进行TRAP。

这仍然给你带来第二个问题 – 读/写指令实际上会发生。 您可以通过在两个信号处理程序中的指令之前和/或之后仔细修改内存来解决该问题。