【问题标题】:Is there a userspace equivalent to the Linux kernel's alternative() macro?是否有与 Linux 内核的 Alternative() 宏等效的用户空间?
【发布时间】:2017-08-30 21:44:16
【问题描述】:

Linux 内核有一个alternative()macro,它允许开发人员为一系列代码指定多个实现,并在运行时选择特定的替代方案(在启动过程的早期在内核alternative()宏的情况下)。

对于用户模式应用程序是否有合理的方法来实现类似的功能?特别是替代位置和运行时补丁代码的记录。

它可能对半静态检测很有用:检测可以在运行时启用或禁用,但仍仅“编译”到特定位置。

【问题讨论】:

  • 查找“GNU IFUNC”
  • 我看不出您要求的内容与简单的if 声明有何不同。读取环境变量或命令行标志,并在上面加上if
  • @Aganju:功能上,没有区别。这只是为了性能。
  • 显然可执行文件的“文本”通常是 MAP_SHARED,加载后修改指令需要私有映射(因为您不希望它更改磁盘上的内容)。我认为你可以用mprotect 做到这一点,如果你以与 Linux(内核)相同的方式实现它。当然,内核的代码没有块设备支持,所以它可以在早期启动时自行编辑,它主副本。
  • 参见ld.so(1) 手册页...您可以在比IFUNC 更粗略的级别上为多个子架构完全重新编译代码。虽然我怀疑经过适当优化的 IFUNC 几乎是一回事。

标签: linux performance x86 instrumentation


【解决方案1】:

如果您在 Linux 上使用 Intel 的 icc 编译器,则可以使用 __notify_intrinsic 功能来允许附加修补。也就是说,它是一种允许您安全地挂钩现有函数并添加其他行为的方法 - 这与 alternative() 有点不同,后者侧重于在两个序列之间进行选择。

__notify_intrinsic 确保在您放置内部函数的代码中的位置有一个 probe-ready 指令序列。这允许您在该点以安全的方式注入“探针”(实际上可以是任意代码),即使在代码正在执行时也是如此。

原则上,这可能具有零开销:准备就绪序列只是一个由 6 个指令字节组成的序列1,您可以安全地将其重新定位到其他地方:主要是指这 6 个字节在指令边界上均匀对齐,没有位置相关代码2,并且没有任何跳转到范围内。

这个想法是,您可以使用此探针就绪序列来修改运行时的行为,方法是将字节复制到其他地方,然后是您的新代码,然后使用 jmp 指令修补探针序列到您的 out-of -line "probe"(但实际上,探针可以是任何东西)。

实际上,icc 版本 13 到 17 似乎只是插入了一个 6 字节 NOP,而不是实际尝试使用现有指令。作为example,C代码如下:

int add(int a, int b) {
  if (a < b) {
    __notify_intrinsic("a lt b", 0);
    a *= b;
    return a * b;
  } else {
    __notify_intrinsic("a gt b", 0);
    return a << b;
  }
}

生成此程序集:

add:
        cmp       edi, esi                                      #2.11
        jge       ..B1.4        # Prob 50%                      #2.11
        xor       edx, edx                                      #3.5
        .byte     102                                           #
        .byte     15                                            #
        .byte     31                                            #
        .byte     68                                            #
        .byte     0                                             #
        .byte     0                                             #
        imul      edi, esi                                      #4.5
        imul      esi, edi                                      #5.16
        mov       eax, esi                                      #5.16
        ret                                                     #5.16
..B1.4:                         # Preds ..B1.1
        xor       edx, edx                                      #7.5
        .byte     102                                           #
        .byte     15                                            #
        .byte     31                                            #
        .byte     68                                            #
        .byte     0                                             #
        .byte     0                                             #
        mov       ecx, esi                                      #8.17
        shl       edi, cl                                       #8.17
        mov       eax, edi                                      #8.17
        ret   

6 个.byte 指令系列构成一个6 字节长的nop 指令。

编译器会在 ELF 二进制文件的特殊 .itt_not_tab 部分中记录每个 __notify_intrinsic 的位置以及注释的值等其他信息,您可以在运行时检查这些信息。

您可以在in the documentation 找到有关此方法的更多详细信息,但不幸的是,目前它似乎仅限于英特尔专有的icc 编译器。


1 在 32 位 x86 上是 5 个字节。

2 例如相对跳转或rip-相对寻址。

【讨论】:

  • 那么如何将这些 NOP 修补成有用的东西呢?这不仅仅是jmpcall 吗?
  • @PeterCordes 如上所述,这个想法是您将 6 个字节离线复制到某处(因为它们可能不是 nops!),然后使用您想要的任何代码跟随它修补,然后是 jmp 回到 6 个字节后面的指令。然后用jmp 替换这6 个字节:这就是为什么它是6 个字节,刚好足以编码FF 25 /32 跳转。当然,在特殊情况下,序列 isnop 并且您可以在其中放置一些有用的代码,您也许可以直接将其修补而不使用外线代码,但这就是不是主要用例。
  • 您可能会在gcc 中使用volatile 内联asm 块以及一些GAS 指令来创建指向某个特殊部分中的nop 的符号,从而大致破解相同的内容。开销只有一个nop,所以不会太靠后。
  • 不,“高性能”状态是未打补丁的状态,您有 0 或 1 个额外的 nop 和直线代码。即,您将组织它成为您关心的情况。较慢的情况是跳跃的情况(例如,用户启用了额外的分析/诊断)。不过,我明白您的意思:始终修补该位置的想法,这可能与alternative() 最接近。我主要对两种状态感兴趣,在“默认”情况下性能更重要。 @PeterCordes
  • ...但只让你在A和A+B之间进行选择。当然,你经常可以组织B来撤消A的效果,所以有时候真的是“另类”之类的。您也可以“始终修补”以获得 A 或 B,但正如您所指出的那样,这有点糟糕。在 x86 上有很多关于“热补丁”的论文,但是如果您在其他线程同时运行代码时不尝试这样做,那么与安全补丁序列相关的许多复杂性就会消失。 @PeterCordes
猜你喜欢
  • 1970-01-01
  • 2014-01-14
  • 1970-01-01
  • 1970-01-01
  • 2019-11-18
  • 1970-01-01
  • 1970-01-01
  • 2011-12-30
  • 2011-02-11
相关资源
最近更新 更多