【问题标题】:mprotect in linuxlinux中的mprotect
【发布时间】:2012-03-05 15:27:32
【问题描述】:

如果我 mprotect 使用 PROT_NONE 的段并且如果由于写入而发生 SIGSEGVsigactionsa_sigaction 处理,我们将能够使用 @987654325 找到发生故障的地址@的si_addr。但是有没有办法找到试图写入的数据和数据的长度?

我正在尝试这样做,因为我正在为我的项目尝试一种写时复制机制。

【问题讨论】:

  • 内核正在执行您在此处告诉它的所有操作,这就是 mprotect() 的全部设计目的。如果您想实现 CoW(如答案中所暗示的那样),您可以使用它,但是 期望 频繁的 SEGV 可能不是一个好的设计决策。最好的办法是管理自己的池,不幸的是,这意味着编写自己的分配器。您仍然可以在其中使用malloc(),但孩子们需要使用您提供的那个(以及对应的mprotect())。
  • 这不是硬件。我正在尝试将其作为学习过程的一部分并实施持续优化(或者它甚至可能去优化。我不知道。)。一旦我这样做了,我想再次对其进行基准测试 getcontext 和 setcontext

标签: linux copy-on-write mprotect


【解决方案1】:

你找不到进程试图写入的数据,询问它的大小也没有意义。如果您可以获取数据,则意味着内核已经将其复制到某个地方。

您会在整个页面上获得 SIGSEGV。也就是说,无论进程正在写入什么数据,每页都会出现一个错误——第一次尝试写入一个字节时。所以你需要做的就是:

  • 跟踪页面状态
  • 根据需要增加权限

【讨论】:

  • 期待大量 SEGV 照常营业是不是有点……好吧……坏主意?
  • @TimPost 是。但我怀疑这是一个作业,学生需要mmap 一些没有权限的内存,写入它并随时添加权限。我曾经不得不这样做(我看不出它有什么实际用途,但它很酷)。
  • 我怀疑你可能是正确的。我想我稍后会解决这个问题,只是为了好玩。
  • 我曾经为了一个非常实际的目的不得不做一些类似的事情。我有一些代码需要在 SMP 和 UP 机器上运行。在 UP 机器上,lock 前缀的开销是痛苦的。所以我们用ud2代替了锁。 ud2 操作码保证会导致错误。我们发现了错误并在运行时将指令“修补”到 SMP 上的 lock 和 UP 上的 nop。效果很好。
  • @cnicutar 1. “跟踪页面”到底是什么意思?你的意思是复制整个页面?如果是这样,我从哪里获得这些信息? 2. 多于1次更新时,是否复制与更新次数一样多?
【解决方案2】:

内核不知道,所以它不能告诉你。但是您可以尝试找出是否愿意。您在堆栈上有错误的代码地址,因此您可以反汇编那里的代码以尝试找出它。没有其他方法可以知道(如果您不明白为什么,请考虑一下)。该指令出错是因为它触及了一个受保护的页面,除非您分析汇编代码,否则这就是已知的一切。

如果您无法通过仅知道出错的页面来判断您正在处理的对象,我强烈建议您重新考虑更改您的设计,以便您这样做。 (posix_memalign,也许?)

更新:不要忘记您需要有一个在每次上下文切换时调用的钩子。您可能需要在每个挂钩上复制页面。例如:

  1. 上下文 A 正在使用 CoW 语义访问页面 Q。上下文 A 获得对页面的只读访问权限。

  2. 上下文 B 正在使用 CoW 语义访问页面 Q。上下文 B 获得对页面的只读访问权限。

  3. 上下文 A 去修改页面,我们为上下文 B 制作一个副本。上下文 A 现在拥有对页面的写入权限并对其进行修改。

  4. 我们从上下文 A 切换到上下文 B。此时,您必须切换为上下文 B 制作的页面副本。

请注意,解决此问题的另一种方法是让上下文对映射和锁定页面进行特定调用。如果您允许上下文在上下文切换中保存映射,那将无法正常工作——至少,如果没有很多额外的工作。

【讨论】:

  • 我认为我没有正确传达这个问题。方法是 CoW,但我想做一些不同的事情。执行写入时,我想复制现有页面并将其存储在数据结构中并继续对当前页面进行更改。当我想制作一个滚动时,我复制旧页面来代替当前现有页面。
【解决方案3】:

当您使用 mprotect() 时,您知道要保护的内存段的起始地址和长度。您需要将该信息存储在某处,以便以后使用。

一旦您保护了您的内存段,如果您以违反保护的方式访问它,您将收到一个 SIGSEGV 信号。在信号处理程序中,您将获得一个指向 siginfo_t 的指针。这将为您提供所需的信息。 si_addr 为您提供导致非法访问的指令的地址,而 si_ptr 为您提供非法访问的地址。然后,您将 si_ptr 与您保护的内存段进行比较,直到找到它所属的内存段,这就是您需要为“写时复制”复制的内存段......一旦完成,您就需要调用setcontext() 或 siglongjmp() 之类的东西可以从已知位置继续运行。

我希望这会有所帮助。

【讨论】:

    猜你喜欢
    • 2012-08-08
    • 2013-07-14
    • 2011-02-16
    • 1970-01-01
    • 2016-03-12
    • 2010-10-25
    • 2017-09-29
    • 2011-07-20
    • 2010-10-10
    相关资源
    最近更新 更多