背景
我正在编写一个框架,以实现在模拟器和未修改的主机软件中运行的 RTL 的协同仿真。编写主机软件是为了控制实际硬件,通常以两种方式之一工作:
- 通过驱动程序读取/写入调用
- 使用 mmap 进行内存映射访问
前一种情况非常简单——编写一个库来实现与驱动程序相同的读/写调用,并在运行模拟时链接到它。这一切都非常有效,我可以运行未修改的生产软件作为我的 RTL 模拟的激励。
事实证明,第二种情况比第一种情况要困难得多...
捕获mmap
最初我以为我可以使用 LD_PRELOAD
来拦截 mmap 调用。在我的 mmap
实现中,我会分配一些页面对齐的内存,然后对其进行 mprotect
并设置一个信号处理程序来捕获 SIGSEGV
。
这种方法有很多问题:
读与写
我可以从 siginfo_t->si_addr
确定访问地址,但不能确定访问是读还是写。
捕捉重复访问
在信号处理程序中,我需要取消对内存区域的保护,否则一旦我的处理程序退出,我就会重复 SIGSEGV
s,主机代码将永远无法继续。但是,如果我取消对该区域的保护,那么我的信号处理程序将不会捕获后续访问。
信号处理程序肮脏
在模拟器驱动 RTL 并返回结果时阻塞信号处理程序违反了各种编程规则 - 特别是考虑到模拟器可以触发各种其他事件并在从该访问返回结果之前执行任意代码。
其他方法
我想知道是否可以创建一个行为类似于磁盘的类文件对象,而不是在缓冲区上使用 mprotect
。我还没有找到任何信息表明这是可行的。
问题
是否可以捕获所有对 mmap 区域的访问以及如何捕获?
- 访问需要阻塞一段不确定的时间(当模拟器运行时)
- 读取访问需要检索我的陷阱放置的新值
假设 LD_PRELOAD
和 mprotect
是最佳路径:
- 我可以确定访问是读调用还是写调用吗?
- 既然我必须取消
mprotect
区域,我该如何捕获后续访问?
相关问题
最佳答案
在 X86 上,您可以为调用者的上下文设置 Trap 标志,以便在一条指令后获得 SIGTRAP(该标志通常用于单步执行)。也就是说,当遇到 SIGSEGV 时,您在调用者的 EFLAGS 中设置 TF(参见 ucontext.h
),使用 mprotect
启用读取并返回。如果使用同一 IP 立即重复 SIGSEGV,则启用写入(如果要区分读取-修改-写入访问和只写访问,则可以选择禁用读取)。如果从同一个IP获取SIGSEGV进行只读和只写保护,则启用读写。
无论何时收到 SIGTRAP,您都可以分析写入的值(如果是写访问),您还可以重新保护页面以捕获 future 的访问。
更正:如果读和写都可能有副作用,先尝试只写保护,然后应用读副作用并尝试只读保护,然后在最后的 SIGTRAP 中启用写入并处理写入的副作用处理程序。
更新:我在推荐假设的只写保护时大错特错,事实证明在大多数架构上都不存在。幸运的是,有一种更直接的方法可以知道失败的操作是否尝试读取内存,至少在 x86 上是这样:
页面错误异常将错误代码压入堆栈,在 Linux SIGSEGV 处理程序中作为 sigcontext
结构的 err
成员可用。 Bit 1 of the error code对于写入 错误为 1,否则为 0。对于读取-修改-写入操作,它最初将为 0(在这里您可以模拟读取,确切地知道它会发生)。
关于c - 捕获对地址范围的所有访问 (Linux),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21068714/