跟踪和反汇编 Linux 内核指令与内核映像文件不匹配
Tracing and disassembling Linux Kernel instructions not matching kernel image file
我正在尝试验证和理解在模拟框架中执行的指令。
模拟步骤如下:
- 二进制文件在主机 x86 机器上使用 gcc(带有 -fPIC 标志)交叉编译
- 然后将二进制文件移动到名为 SimNow 的 x86 虚拟机(AMD 用于测试其处理器)中执行
- SimNow 机器生成一个已执行指令的列表,这些指令被传递到一个框架,包括关于每条指令的信息:虚拟地址、物理地址、大小、操作码。
- 得益于 x86 反汇编程序(名为 distorm),框架会生成执行指令的踪迹,包括助记符和操作数。
这是跟踪输出的示例:
已执行指令列表包括包含在二进制和可能的内核指令中的指令。
我正在使用二进制文件中 objdump 的输出来验证跟踪的用户指令。相等,确认执行的正确性。
这是上图中指令的 objdump 输出:
对于内核指令,我必须应用预备步骤:
- 我将内核头文件安装到虚拟机中,并提取了 linux 映像以在其上执行 objdump。
- 我通过将内核指令的虚拟地址与包含在 /proc/kallsysms 中的虚拟地址进行比较,将内核符号添加到跟踪输出中。
对于验证步骤,我使用与用户说明相同的方法,将 linux 内核映像的 objdump 与跟踪输出进行比较。
但是,我注意到了一些差异……主要是在发现内核符号指令时。
这是跟踪的输出:
这是 linux 内核映像的相应部分:
从这些图片中可以看出,每个对应内核符号的callq(比较linux映像的虚拟地址与/proc/kallsyms)被替换为NOP DWORD(nopl指令) 在跟踪输出中。
我想做的是理解为什么内核符号是 NOP DWORD 而不是 callq。
搬迁的缘故?如果是,我该如何重建这样的指令的搬迁?
注意:我用 -dr
执行了 objdump 以检查 Linux 图像上的重定位,但输出没有改变。
我的内核指令验证方法有误?
(部分答案/猜测可能会为您指明正确的方向。更新:Jester 建议这看起来像 ftrace
机器:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/kernel/ftrace.c?h=v5.10)
我怀疑这些调用在加载后被 NOP,可能是在相关跟踪点或任何未启用时。出于某种原因 NOP 退出调用可以解释为什么存在与 NOP 关联的调用目标或符号重定位元数据。
我想我已经读过 Linux 对低开销跟踪点或其他东西使用代码修改,并且自修改代码是 Linux 通常肯定会做的事情。
Linux 使用自修改代码,在启动时修改一次,或者在非常罕见的配置更改时修改,以减少每次执行与分支的开销,对于一些不同的事情。 (例如,在 UP 机器上启动 SMP 内核将 NOP 出原子 RMW 中的 lock
前缀,它只需要是 SMP 安全的,而不是硬件设备。)内联 asm 宏定义符号和自定义部分,因此内核具有必要的元数据。最近还有一些关于修改 rel32
调用目标而不是使用间接分支的事情,以避免需要在这些站点上进行任何 Spectre 缓解,但这不是这里发生的事情。
所以一般来说,当您尝试验证内核映像文件的执行时,您应该会看到一些不匹配的情况,这可能就是其中之一
在这种情况下,这看起来像是一个函数的最顶部(在设置帧指针之前),这听起来很可能是找到某种特殊调用的地方,也许是为了跟踪(到一个特殊的函数保留所有寄存器)。
gcc 生成的代码永远不会 AFAIK 在 push %rbp
/ mov %rsp, %rbp
之前执行 call
。一方面,这将违反 16 字节堆栈对齐 ABI 要求。 (虽然内核可能使用 -mpreferred-stack-boundary=3
而不是 4?在两次推送之后,还有另一个更正常的调用,如果这是一个正常的函数,它也会有一个未对齐的 RSP。)无论如何,这是另一个迹象表明有一些自定义的内联 asm骇客之类的事情。
我正在尝试验证和理解在模拟框架中执行的指令。 模拟步骤如下:
- 二进制文件在主机 x86 机器上使用 gcc(带有 -fPIC 标志)交叉编译
- 然后将二进制文件移动到名为 SimNow 的 x86 虚拟机(AMD 用于测试其处理器)中执行
- SimNow 机器生成一个已执行指令的列表,这些指令被传递到一个框架,包括关于每条指令的信息:虚拟地址、物理地址、大小、操作码。
- 得益于 x86 反汇编程序(名为 distorm),框架会生成执行指令的踪迹,包括助记符和操作数。
这是跟踪输出的示例:
已执行指令列表包括包含在二进制和可能的内核指令中的指令。
我正在使用二进制文件中 objdump 的输出来验证跟踪的用户指令。相等,确认执行的正确性。
这是上图中指令的 objdump 输出:
对于内核指令,我必须应用预备步骤:
- 我将内核头文件安装到虚拟机中,并提取了 linux 映像以在其上执行 objdump。
- 我通过将内核指令的虚拟地址与包含在 /proc/kallsysms 中的虚拟地址进行比较,将内核符号添加到跟踪输出中。
对于验证步骤,我使用与用户说明相同的方法,将 linux 内核映像的 objdump 与跟踪输出进行比较。 但是,我注意到了一些差异……主要是在发现内核符号指令时。 这是跟踪的输出:
这是 linux 内核映像的相应部分:
从这些图片中可以看出,每个对应内核符号的callq(比较linux映像的虚拟地址与/proc/kallsyms)被替换为NOP DWORD(nopl指令) 在跟踪输出中。
我想做的是理解为什么内核符号是 NOP DWORD 而不是 callq。
搬迁的缘故?如果是,我该如何重建这样的指令的搬迁?
注意:我用 -dr
执行了 objdump 以检查 Linux 图像上的重定位,但输出没有改变。
我的内核指令验证方法有误?
(部分答案/猜测可能会为您指明正确的方向。更新:Jester 建议这看起来像 ftrace
机器:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/kernel/ftrace.c?h=v5.10)
我怀疑这些调用在加载后被 NOP,可能是在相关跟踪点或任何未启用时。出于某种原因 NOP 退出调用可以解释为什么存在与 NOP 关联的调用目标或符号重定位元数据。
我想我已经读过 Linux 对低开销跟踪点或其他东西使用代码修改,并且自修改代码是 Linux 通常肯定会做的事情。
Linux 使用自修改代码,在启动时修改一次,或者在非常罕见的配置更改时修改,以减少每次执行与分支的开销,对于一些不同的事情。 (例如,在 UP 机器上启动 SMP 内核将 NOP 出原子 RMW 中的 lock
前缀,它只需要是 SMP 安全的,而不是硬件设备。)内联 asm 宏定义符号和自定义部分,因此内核具有必要的元数据。最近还有一些关于修改 rel32
调用目标而不是使用间接分支的事情,以避免需要在这些站点上进行任何 Spectre 缓解,但这不是这里发生的事情。
所以一般来说,当您尝试验证内核映像文件的执行时,您应该会看到一些不匹配的情况,这可能就是其中之一
在这种情况下,这看起来像是一个函数的最顶部(在设置帧指针之前),这听起来很可能是找到某种特殊调用的地方,也许是为了跟踪(到一个特殊的函数保留所有寄存器)。
gcc 生成的代码永远不会 AFAIK 在 push %rbp
/ mov %rsp, %rbp
之前执行 call
。一方面,这将违反 16 字节堆栈对齐 ABI 要求。 (虽然内核可能使用 -mpreferred-stack-boundary=3
而不是 4?在两次推送之后,还有另一个更正常的调用,如果这是一个正常的函数,它也会有一个未对齐的 RSP。)无论如何,这是另一个迹象表明有一些自定义的内联 asm骇客之类的事情。