【问题标题】:Tracing and disassembling Linux Kernel instructions not matching kernel image file跟踪和反汇编与内核映像文件不匹配的 Linux 内核指令
【发布时间】:2021-04-18 10:24:21
【问题描述】:

我正在尝试验证和理解在模拟框架中执行的指令。 仿真步骤如下:

  1. 二进制文件在 x86 主机中使用 gcc(带有 -fPIC 标志)进行交叉编译
  2. 然后在名为 SimNow 的虚拟机 x86 中移动并执行二进制文件(AMD 使用该虚拟机测试其处理器)
  3. SimNow 机器生成一个已执行指令列表,这些指令被传递给框架,其中包括有关每条指令的信息:虚拟地址、物理地址、大小、操作码。
  4. 借助 x86 反汇编程序(名为 distorm),框架生成执行指令的跟踪,包括助记符和操作数。 这是跟踪输出的示例:

已执行指令列表包括二进制中包含的指令和可能的内核指令。

我正在使用二进制文件上 objdump 的输出来验证跟踪的用户指令。它们相等,证实了执行的正确性。

这是上图中指令的objdump输出:

对于内核指令,我必须应用初步步骤:

  1. 我将内核头文件安装到虚拟机中,并提取了用于在其上执行 objdump 的 linux 映像。
  2. 我通过将内核指令的虚拟地址与 /proc/kallsysms 中包含的虚拟地址进行比较,将内核符号添加到跟踪输出中。

对于验证步骤,我使用与用户说明相同的方法,将 linux 内核映像的 objdump 与跟踪输出进行比较。 但是,我注意到了一些差异……主要是在发现内核符号指令时。 这是跟踪的输出:

这是linux内核镜像的对应部分:

从这些图片中可以看出,每个 callq 对应一个内核符号(将 linux 映像的虚拟地址与 /proc/kallsyms 进行比较)在跟踪输出中被替换为 NOP DWORD(nopl 指令)。

我想要做的是了解为什么内核符号使用 NOP DWORD 而不是 callq。

是搬迁造成的吗?如果是,我该如何重建此类指令的重定位?

注意:我使用 -dr 执行了 objdump 以检查 Linux 映像上的重定位,但输出没有改变。

我的内核指令验证方法不对?

【问题讨论】:

  • 参见elf(5) 并使用objdump(1)readelf(1)。使用最近的GCC 编译您的foo.cgcc -Wall -fverbose-asm -O2 -S foo.c· 并使用less(1) 查看foo.s 内部。另请阅读Linkers and loaders
  • 我在二进制文件上使用了 elf、objdump 和 readelf,它适用于用户指令。但是,它不适用于内核指令,它不应该包含在二进制文件中......不是吗?
  • 这些可能是内核在加载后动态修改的内容的占位符吗?例如将它们排除在外,或将它们从 NOP 更改为 callq,以实现非常低的开销跟踪? Linux 确实有一些 inline-asm hack 可以生成符号以对其他事物进行自我修改,例如如果 SMP 内核在 UP 机器上启动,则 NOPing lock 前缀。而且我想我已经阅读了有关使用低开销跟踪点或其他东西的代码修改的Linux。所以这可以解释为什么内核在磁盘上的 ELF 映像与内存中的执行不匹配。
  • 是的,这看起来像是一个函数的最顶层(在制作堆栈帧之前),这听起来像是一个可能找到跟踪调用的地方(对保留所有寄存器的特殊函数)。出于某种原因 NOP 调用可以解释为什么 NOP 上有一个调用目标的符号。
  • 这些看起来像 ftrace 自我修改代码。见arch/x86/kernel/ftrace.c

标签: assembly gcc linux-kernel x86 trace


【解决方案1】:

(部分答案/猜测可能会为您指明正确的方向。更新:Jester 建议这看起来像 ftrace机械:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/kernel/ftrace.c?h=v5.10

我怀疑这些调用在加载后会得到 NOPed,可能是在相关跟踪点或其他任何未启用的情况下。出于某种原因 NOP 调用可以解释为什么存在与 NOP 关联的调用目标或符号重定位元数据。

我想我已经阅读了有关使用低开销跟踪点或其他东西的代码修改的 Linux 的文章,而自修改代码是 Linux 通常肯定会做的事情。

Linux 使用在启动时修改一次的自修改代码,或在非常罕见的配置更改时修改,以减少每次执行与分支相比的开销, 用于一些不同的事情。 (例如,在 UP 机器上启动 SMP 内核将 NOP 出原子 RMW 中的 lock 前缀,它只需要 SMP 安全,而不是硬件设备。)内联 asm 宏定义符号和自定义部分,因此内核具有必要的元数据.最近还有一些关于修改 rel32 调用目标而不是使用间接分支的内容,以避免在这些站点上需要任何 Spectre 缓解措施,但这不是这里发生的事情。

因此,一般来说,当您尝试针对内核映像文件验证执行时,您应该会看到一些不匹配的情况,这可能就是其中之一

在这种情况下,这看起来像是一个函数的最顶部(在设置帧指针之前),这听起来像是一个可能找到某种特殊调用的地方,也许是为了跟踪(到一个保留所有寄存器)。

gcc 生成的代码永远不会在 push %rbp / mov %rsp, %rbp 之前 AFAIK 执行 call。一方面,这将违反 16 字节堆栈对齐 ABI 要求。 (虽然内核可能使用-mpreferred-stack-boundary=3而不是4?在两次推送之后,还有另一个更正常的调用,如果这是一个正常的函数,它也会有一个未对齐的RSP。)无论如何,这是另一个迹象表明存在一些自定义内联asm黑客或发生了什么事。

【讨论】:

  • 感谢您提供如此详细的回答!是的,像 ftrace 这样的指令的 NOP 可能是可能的......事实上,这个跟踪是“在运行时”生成的......所以跟踪打印出只执行的指令......在可能的 NOP 之后!我正在实现一个 python 脚本来了解是否存在其他一些差异......或者只是这样的 NOPing
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-01-10
  • 2011-10-05
  • 1970-01-01
  • 1970-01-01
  • 2016-05-18
  • 2011-12-21
  • 2016-01-22
相关资源
最近更新 更多