C++ 如何在运行时以编程方式更改调用指令

问题描述 投票:0回答:1

在进入问题的细节之前,我想透露一下,这是作为一项研究任务交给我的,所以它可能会也可能不会。简而言之,最终目标将是拥有一个类,该类能够根据运行时条件调用两个函数之一,并且开销最小。我们的类将有两个主要方法:

(1) set_direction(bool) :在这里,我们想以编程方式更改下一个方法中的调用指令。我们可以在这里使用任何方法来做到这一点,我探索过的一些方法是二进制编辑、挂钩,甚至更改已定义的可执行缓冲区的内容。

(2) branch() :这里我们将调用通过上述方法选择的两个函数之一。关键部分是我们希望以最小的开销来做到这一点。这意味着,没有指针取消引用和条件,只是对绝对地址的简单调用(或 JMP)。

我们的代码看起来像这样:

class BranchChanger
{
   private:
       func if_branch_func;
       func else_branch_func;
       /* possibly other stuff here as well */

   public:
       BranchChanger(func if_branch, func else_branch);
       void set_direction(bool); // change method called by 'branch' programatically
       auto branch(); // call either if_branch or else_branch directly with minimal overhead
}

我们的主程序应该是这样的:

#include <branch.h>

int add(int a, int b) { return a + b; }

int sub(int a, int b) { return a - b; }

int main() {
   BranchChanger branch = BranchChanger(&add, &sub);
   bool condition = rand() % 2; // our runtime condition
   branch.set_direction(condition);
   branch.branch(a, b); // call either add or sub with minimal overhead
}

问题是,当分支被调用时我们想要发生的是:

CALL <some_address>

但在现实中,总是这样:

CALL QWORD PTR [<some_address>]

由于函数指针的工作方式,我们无法通过这种方式获取函数的地址,因为取消引用函数指针只会衰减回指向函数的指针。如果我们用我们可以编辑的字节码定义一个可执行缓冲区,我们可以用一些简洁的 linux 系统调用分配它,即使这样也需要通过我们不想要的指针来访问。我尝试过函数挂钩(包括 trampolines、detours 等)、内联 asm、间接函数编译器属性,甚至尝试用符号填充。但是我没有成功,因为 branch() 方法总是会由于指针取消引用而涉及某种间接。

我有一个非常蛮力的想法是修改当前的 ELF,但是我不确定这有多可行。我想做的是在调用 set_direction 时手动复制 ELF 中任一函数的地址,将其粘贴到分支主体中。如您所见,我的想法已经用完了,我希望能有一些新鲜的眼光来看待这个问题,也许我遗漏了一些东西(或者这根本不可能)。

注意,将类的所需语法视为占位符,使用它可能无法实际执行我们想要的操作,但这是我们的目标。

所有回复将不胜感激。

编辑:这是我的主管想要的(旧对话的片段)

""""""""""""""""""""""""""""""""""

不想在运行时这样做:

if(方向)code1();否则代码 2();

另外,不想取消引用函数指针。 目标:最小化运行时开销。

(1) 运行时没有条件检查; (2) 没有指针/函数指针解除引用

理想情况下,单个汇编 JMP 指令(无条件),我们可以通过编程方式编辑其地址。

如何在 C++ 中以编程方式编辑汇编代码?例如。如何更改无条件 JMP 的地址? (您可以直接将机器代码写入内存。)因为汇编指令只是内存中的数字。函数指针应该可以给你编辑的地址。

  1. CMP ...
  2. JE ...

这在运行时需要时间。相反:

JMP

我们可以通过编程将其设置为两个值之一。 这消除了开销。

function1() { ... }
function2() { ... }

<address> = &function1
<address> = &function2

""""""""""""""""""""""""""""""""" 编辑:感谢您的评论,我只是想指出,解决方案的任何内容都摆在桌面上,无论它多么骇人听闻。我们正在寻找的是概念证明和原型实现。我们想要在 linux 机器上的 gcc 编译器(最新版本 pref)上工作的原型。

c++ assembly c++17 variadic-templates self-modifying
1个回答
0
投票

在大多数平台上改变机器代码(作为避免间接调用的唯一方法)确保巨大的开销,如果可能的话。

  • 你必须在硬件和操作系统级别上破坏代码段的保护。
  • 您必须等待指令缓存更新的解决
  • 你把棍子插入分支预测的轮子里。

通过比较,只有通过间接调用实现指针解引用的开销是从内存中读取地址。现有的 ISA 旨在有效地支持调用表。

© www.soinside.com 2019 - 2024. All rights reserved.