如何使用sigsegv捕获内存读写?

Wil*_*ill 8 c++ linux memory ubuntu kernel

我如何欺骗linux认为内存读/写成功?我正在编写一个C++库,以便所有读/写都被重定向并透明地处理给最终用户.无论何时写入或读取变量,库都需要捕获该请求并将其发送到硬件模拟,硬件模拟将处理来自那里的数据.

请注意,我的库依赖于以下平台:

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().我有一个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;
}
Run Code Online (Sandbox Code Playgroud)

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

int *num_ptr = (int *)nullptr;
*num_ptr = 10;                          // write segfault
Run Code Online (Sandbox Code Playgroud)

但是对于任何稍微复杂一点的东西,我收到一个SIGABRT:

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

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

如果我不增加REG_RIP,handle_sigsegv()内核会一遍又一遍地调用,直到内存区域可供读取或写入.我可以mprotect()针对该特定地址运行,但这有多个警告:

  • 由于内存区域现在​​具有PROT_WRITE能力,后续内存访问不会触发SIGSEGV.我试图创建一个连续将该区域标记为PROT_NONE的线程,但这并没有逃避下一点:
  • mprotect() 将在一天结束时执行读取或写入内存,使我的库的用例无效.

编写设备驱动程序

我还试图编写一个设备模块,以便库可以调用mmap()char设备,驱动程序将从那里处理读写操作.这在理论上是有道理的,但我无法(或者没有知识)捕获处理器问题的每个加载/存储到设备.我试图覆盖映射vm_operations_struct和/或inode的address_space_operations结构,但是当页面出现故障或页面被刷新到后备存储时,它只会调用读/写.

也许我可以用mmap()mprotect(),就像上面所解释的,在设备上,没有一个地方写入数据(类似/dev/null),然后有识别读取/从那里写和路由数据的过程(?).

利用syscall()并提供恢复器组装功能

segvcatch项目1中提取以下内容,将段错误转换为异常.

#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); 
Run Code Online (Sandbox Code Playgroud)

但这最终起到了与常规sigaction()配置无异的作用.如果我没有设置恢复器功能,则不会多次调用信号处理程序,即使内存区域仍然不可用.也许在这里我可以用内核信号做一些其他的诡计.


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


1个 segvcatch项目

Chr*_*odd 5

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

T标志使处理器执行单步操作,因此当SEGV处理程序返回时,它将执行该单指令,然后立即执行TRAP。

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