在运行时组合函数体

时间:2009-11-18 05:12:05

标签: c++

这听起来超级hackish但是有没有人知道在C ++中运行时组合方法体的方法?我目前正在抓住函数的地址,然后将memcopy写入可执行内存,但它存在不需要的prolog / epilog的问题。

基本上我有几十个简单的操作,它们采用相同的参数并且什么都不返回,我想在这些简单的操作中在运行时构建一个函数。

7 个答案:

答案 0 :(得分:4)

实现所需功能的简单解决方案是将函数指针存储在数组中 然后,您可以在运行时创建函数指针列表(您的脚本) 您可以通过迭代列表并调用函数来执行脚本。

答案 1 :(得分:3)

为什么不使用汇编?

答案 2 :(得分:3)

除了它之外,我看不出任何实际用途: - )

首先,你应该决定一个平台。 你不可能以跨平台的方式做到这一点。 实际上,以一种适用于多个编译器的方式实现它可能非常困难,即使在同一平台上也是如此。 那么处理器类型当然是: - )

然后你应该以某种方式检查你正在复制的代码是否可以重新定位。并非所有东西都可以随心所欲地移动。

你必须非常了解调用约定,这样才能确保不会弄乱堆栈。

了解编译器生成的prolog / epilog。您可以通过在函数的开头和结尾添加一些无效的代码序列来作弊,但是您可以使用签名然后查找(即.nop; nop; nop; xor ax,ax; nop; push ax; pop ax; nop; nop; nop;)。确保编译器未对其进行优化: - )

确保您可以编写/执行该代码。现代CPU和操作系统通常不允许用户在代码段中写入或执行非代码段。因此,您必须找出更改权限的方法(100%特定于操作系统)。

然后玩“地址空间布局随机化”,“堆栈随机化”,“数据执行预防”,“堆随机化”之类的乐趣。

无论如何,做了很多工作。毫无意义,除了享受一个好的挑战,并在此过程中学习一些程序集和操作系统内部。 O用于证明自己是“1337”,但是,然后,询问stackoverflow如何做到这一点并不完全是“1337”,如果你问我: - )

无论如何,祝你好运。

答案 3 :(得分:2)

它不起作用,但我告诉你我所知道的。您可以使用setjmp()获取当前进程计数器位置,并使用您获得的填充结构。

如果我不得不像你要问的那样,我会在例程的开头和结尾放置两个setjmp,分配可执行内存(甚至可能吗?我认为文本部分是readonly,但是是...缓冲区溢出漏洞通过执行堆栈来工作,所以是的,我想你可以,除非你有NX技术,然后从四个setjmp获得的括号中复制块。在这个过程中,你会发现一堆乱码不可重定位的操作码,我想,就像它们引用汇编代码中其他操作码的绝对地址一样。当然,如果你复制,这个地址必须相应地改变。

祝你好运。

答案 4 :(得分:2)

模板?

如果你有这个:

template <int Operations>
struct Foo
{

  static void Do(int a, int b)
  {
    if (Operations & 1)
    {
      /// op 1 
    }

    if (Operations & 2)
    {
      /// op 2
    }
    if (Operations & 4)
    {
      /// op 3 
    }
    //...
  }
};

优化器会丢弃与您的专业化无关的块,例如:

Foo<6>::Do(77,88)

只会处理第2步和第3步,而不会为第1步生成代码。

(您应该检查编译器的输出,但大多数应该能够。我正在使用它来检查所选的数组属性,在VC6和后来的编译器中工作,如魅力)

答案 5 :(得分:1)

对于一个动态的解决方案......将它们物理地组合在一起:

  • 假设您的操作需要大约相同数量的指令。
  • 确保它们保持参数不变(即保持堆栈和调用寄存器不变)。
  • 用一条罕见的指示标记结尾。

现在您已准备好构建指令向量:

struct Op {
    char code[MAX_OP_SIZE];
    public Op(const void (*op)()) { 
        /* fill code with no-ops */ 
        const char* opCode = (char*)op;
        char* opCodeCopy = &code[0];
        while (*opCode != GUARD_INSTRUCTION) {
            *opCodeCopy++ = *opCode++;
        }
    }
}

std::vector<Op> combinedOperation;

combinedOperation.push_back(op1);
// ...

void (*combinedOpFunc)(params) = void *()(params)&combinedOperation[0]; // not sure about syntax here

combinedOpFunc(data);
// start praying

另一种选择是根本不复制代码。这是使用setjmp / longjmp的Stano Borini答案的变体。我们没有在每种方法中进行跳转,而是将它们设置一次。

如果你的操作如下:

void op1(paramtype param) { ... }

成功     typedef void(* OPTYPE)(paramtype);

void op1(paramtype param, OPTYPE nextop, jmp_buf returnFrame)
{
     // ...
     if (nextop) {
         // place (paramtype+1) into argument slot 2  (assume +1 does pointer arithmetic)
         // update program counter to value of paramtype
     } else {
         longjmp(returnFrame, 0);
     }
}

然后列出一系列操作:

std::vector<OPTYPE> ops; ops.push_back(&op1); // ...

和辅助函数

void callOps(const std::vector<OPTYPE>& opList, paramtype param)
{
     OPTYPE firstOp = (OPTYPE)&opList[0];
     jmp_buf frame;
     int ret = setjmp(&frame);
     if (ret == 0) firstOp(param, firstOp + 1, frame);
}

并执行它

callOps(ops, opParameter);

答案 6 :(得分:0)

它有点重量级,所以它可能不是您想要的,但您可以尝试使用LLVM JIT代码。