如果I mprotect
是一个具有PROT_NONE的段,并且如果由于由sigaction
和sa_sigaction
处理的写入而发生SIGSEGV
,我们将能够使用siginfo_t
的si_addr
找到故障发生的地址。但是,有没有一种方法可以找到试图写入的数据和数据长度?
我之所以尝试这样做,是因为我为我的项目尝试了一种写时复制机制。
您找不到进程试图写入的数据,询问其大小也没有意义。如果你能得到数据,那就意味着内核已经把它复制到了某个地方。
你会在整个页面上得到一个SIGSEGV
。也就是说,无论进程写入的数据是什么,每次尝试写入字节时,每页都会出现一个错误。所以你所需要做的就是:
- 跟踪页面状态
- 根据需要增加权限
内核不知道,所以它不能告诉你。但如果你愿意,你可以试着找出答案。堆栈上有出错的代码地址,因此可以在那里反汇编代码以尝试找出它。没有其他方法可以知道(如果你不清楚为什么,请想想)。指令出错是因为它触及了受保护的页,除非您分析程序集代码,否则这就是已知的全部内容。
如果你只知道出错的页面就无法判断你正在处理的对象,我强烈建议你重新考虑改变你的设计。(也许是posix_memalign
?)
更新:不要忘记,您需要在每个上下文开关上都调用一个钩子。您可能需要在每个挂钩上复制页面。例如:
-
上下文A正在使用CoW语义访问页面Q。上下文A获得对页面的只读访问权限。
-
上下文B正在使用CoW语义访问页面Q。上下文B获得对页面的只读访问权限。
-
上下文A去修改页面,我们为上下文B做一个副本。上下文A现在可以写入页面并修改它。
-
我们从上下文A切换到上下文B。此时,您必须切换到为上下文B创建的页面副本。
请注意,另一种解决方法是让上下文进行特定的调用来映射和锁定页面。如果允许上下文在上下文切换中保存映射,那么这将不起作用——至少,如果没有大量额外的工作,这也是不起作用的。
使用mprotect()时,您知道要保护的内存段的起始地址和长度。您需要将这些信息存储在某个地方,以便以后使用。
一旦你保护了你的内存段,如果你以违反保护的方式访问它,你会得到一个SIGSEGV信号。在信号处理程序中,您将获得一个指向siginfo_t的指针。这会为您提供所需的信息。siaddr为您提供导致非法访问的指令的地址,siptr为您提供非法访问的地址。然后,将si_ptr与您保护的内存段进行比较,直到找到它所属的内存段,这就是您需要为"写时复制"复制的内存段。。。完成后,您需要调用setcontext()或siglongjmp()之类的函数,以便从已知位置继续运行。
我希望这能有所帮助。