【问题标题】:How can I make system calls invoke my SIGSEGV handler when given protected memory?当给定受保护的内存时,如何让系统调用调用我的 SIGSEGV 处理程序?
【发布时间】:2010-11-07 11:54:54
【问题描述】:

我正在开发一个内存跟踪库,我们使用mprotect 删除对程序大部分内存的访问,并使用 SIGSEGV 处理程序在程序接触到单个页面时恢复对它们的访问。这在大多数情况下都很有效。

我的问题是,当程序使用我的库标记为不可访问的内存调用系统调用(比如read)时,系统调用只返回-1 并将errno 设置为EFAULT。这会以奇怪的方式改变正在测试的程序的行为。我希望能够在系统调用实际进入内核之前恢复对系统调用的每一页内存的访问。

我目前的方法是为每个涉及内存的系统调用创建一个包装器。在将其交给真正的系统调用之前,每个包装器都会触及所有分配给它的内存。这似乎适用于直接从程序发出的调用,但不适用于 libc 发出的调用(例如,fread 将直接调用 read 而无需使用我的包装器)。有没有更好的方法?怎么可能得到这种行为?

【问题讨论】:

  • 这很愚蠢。在给系统调用无效内存时,谁期望 EFAULT 而不是段错误? :P 似乎是系统调用和用户空间函数之间的随机不一致...

标签: linux memory system-calls segmentation-fault


【解决方案1】:

您可以使用ptrace(2) 来实现此目的。它允许您监控流程并在发生某些事件时得到通知。出于您的目的,请查看PTRACE_SYSCALL,它允许您在系统调用进入和退出时停止进程。

但是,您将不得不更改一些内存跟踪基础架构,因为 ptrace 的操作方式是父进程监视子进程,而就子进程而言,它无法看到何时被监视事件发生。话虽如此,您应该能够按照以下方式做一些事情:

  • 设置 ptrace 父子节点,监控(至少)PTRACE_SYSCALL
  • 子进程执行系统调用;并通知家长。
  • Parent 保存请求的系统调用信息;并使用PTRACE_GETREGSPTRACE_SETREGS 更改子状态,而不是调用系统调用;子进程调用“内存解除保护”例程。
  • 孩子解除了它的记忆;然后引发SIGUSR1 或类似内容,告诉控制父母内存工作已完成。
  • 父母捕获SIGUSR,使用PTRACE_SETREGS恢复之前保存的系统调用信息并恢复孩子。
  • 子进程恢复并执行原始系统调用。

【讨论】:

  • lkml.org/lkml/2008/8/25/40 已经进行了一些实验以允许对您自己进行 ptrace,以捕获您自己的系统调用...但是,是的,这种多进程舞蹈通常是必要的。
  • 我认为这是正确的解决方案,但它不会按规定工作。当我在系统调用中停止子进程时,eip 的值位于系统调用返回点(在 __kernel_vsyscall 中将寄存器从堆栈中弹出)而不是 sysenter 指令。因此,当父级捕获它时,系统调用已经开始(并且注定会失败)。我可能会在它返回时捕获它,调用取消保护的例程,然后重新启动调用。
  • @Jay:您有机会发布(最小版本)您的代码吗?您应该能够在孩子实际执行系统调用之前抓住它......
  • @Dave Rigby,我将我之前正在处理的一些示例代码发布到jayconrod.com/scratch/ptrace_test.c。我正在使用 GDB 检查 user_info 结构的内容。我相信程序第一次以 SIGTRAP 停止时,它正在进入 write 调用。然后我使用带有来自 user_info.regs.eip 的值的反汇编命令。指令是“pop %ebp”,地址是__kernel_vsyscall+16(返回点)。感谢您一直以来的帮助。
  • @Jay:我运行了你链接的测试代码,它似乎对我有用 - 我看到第一个 SIGTRAP 发生在 SYS_write 的开头。
猜你喜欢
  • 2011-03-13
  • 1970-01-01
  • 1970-01-01
  • 2011-01-24
  • 2011-06-14
  • 2018-04-04
  • 2013-05-24
  • 2014-12-20
  • 2019-01-21
相关资源
最近更新 更多