如何在C/C++中存储和调用已编译的函数?

Jos*_*vin 5 c c++ compiler-construction optimization assembly

对于非常低级别的优化目的,如果我可以将编译函数直接存储在变量中,而不是指向函数的指针,那将对我有用.也就是说,如果我有一个函数foo,我想创建一个足够大的char缓冲区来保存为foo生成的机器指令,然后能够通过某种方式告诉C将执行跳转到该缓冲区的内容来调用foo (假设我有一个编译器内在函数,以确保char缓冲区为我的架构正确对齐).理想情况下,我想这样做可以将组件使用保持在最低限度(我意识到可能需要一些).

到目前为止,我最好的解决方案是编译一个程序,它只具有我想用GCC组装的函数,然后编译成机器代码,然后使用输出组件中的地址从可执行文件中提取所需的机器代码,然后手动填充我的程序中的缓冲区,然后使用内联汇编跳转到缓冲区的起始地址.虽然这比我想要的更加虚伪和手工.

我不需要在运行时编译新函数,只需让缓冲区包含与运行时期间已编译的不同函数相对应的指令.例如,我可能有3个编译函数和1个缓冲区.这3个函数在编译时是已知的,但是在运行时,缓冲区可以在不同时间对应于3中的任何一个.

编辑:澄清会得到什么:我有一个结构,这个缓冲区将成为一个成员,以及各种指向该结构实例的指针.每个struct的缓冲区可能包含不同的编译函数.如果我使用函数指针而不是缓冲区,我将不得不加载struct的函数指针,然后deref函数指针.使用缓冲区,我可以将程序计数器跳转到结构基础的偏移量(缓冲区的相对位置).这是一个较少的间接水平.对于非常小的功能,这可以节省.

编辑2:进一步澄清:

使用函数指针:

  1. 从&struct + offsetof(指针)加载指针
  2. 跳转到指针中包含的位置

使用包含机器代码的缓冲区:

  1. 跳转到&struct + offsetof(缓冲区)

第二个是较少的步骤.

Ecl*_*pse 12

我不确定你为什么要排除函数指针 - 如果你没有在运行时修改代码,那么我想不出任何包含函数的缓冲区可以做函数指针不能做的事情.

话虽如此,你有一个指向包含你的函数的缓冲区的指针,只是将指向该缓冲区的指针强制转换为正确的函数指针类型并调用它.当然,如果您的操作系统/ CPU支持它,您还必须清除阻止您运行非可执行代码的NX标志.

此外,您不能假设您可以复制包含可执行数据的缓冲区,就好像它们包含常规数据一样 - 您必须修复分支和跳转以及其他内存访问.

编辑:我看到你得到了什么,但我认为你不会找到一个符合标准的方法来将函数放入缓冲区--C++对内存结构和存储函数的位置很少说一个相当抽象的概念.您可能会遇到一堆黑客,或者使用可以在编译时组装并在运行时从文件加载的单独的汇编文件.

另一方面 - 我认为您可能会发现存储函数指针(可能是4-8个字节)的双重解除引用要比存储缓存未能存储能够保存您需要的最大函数的缓存错误更快.具有附加函数的结构与类上的虚函数有许多相似之处.有大多数(所有?)C++编译器使用vtable实现虚函数而不是为每个类的实例存储整个函数定义的原因.这并不是说你不可能通过将整个函数存储在一个缓冲区中而获得性能提升,它并不像看起来那样直截了当.

  • 多么小?给定缓存效果,我愿意猜测(没有任何我的数字的基础),如果函数超过大约12-16字节,函数指针版本将更快,因为它将导致更多的L1缓存命中.(IIRC有些CPU使用不同的L1缓存来处理数据和代码,所以即使对struct的数据访问也不会导致L1代码缓存的完美) (2认同)

Bar*_*lly 12

请注意,大多数现代架构都支持对内存页面执行无执行保护,并且具有一定意义的操作系统会利用它来提高安全性.这意味着您不能使用例如堆栈内存或随机malloc内存来存储此代码; 您需要使用例如VirtualProtect在Windows或mprotectUnices 上调整权限.

  • 并且(取决于架构)记得刷新icache以获取包含代码的地址范围. (3认同)

Ada*_*eld 8

如果您要在运行时编译和优化代码,我建议您查看LLVM. ffcall也是一个有用的包.请注意,这两个问题都解决了诸如确保为函数分配的内存可执行以及指令缓存是否针对各种体系结构正确刷新的问题.你不想重新发明轮子.


Fre*_*abe 5

由于函数实际上只是标签,因此您需要某种方法来确定函数的大小.一个技巧可能是获取以下函数的地址.这里有一个小例子,它使用这个技巧将"乘法"函数的字节码转换为字符向量,然后重写代码以始终返回一个常量,然后通过将代码转换为适当的函数指针来调用代码.

这是一个肮脏的黑客,并可能在许多情况/编译器下打破.但是,如果您处于受控环境中并且它适合您,它可能会帮助您.

#include <iostream>
#include <vector>

using namespace std;

int multiply( int arg )
{
    return arg * 2;
}

int main()
{
    // Show that multiply apparently multiplies the given value by two.
    cout << multiply( 13 ) << endl;

    // Copy the bytes between &multiply and &main (which is hopefully the multiply code) into a vector
    vector<char> code( (char*)&multiply, (char*)&main );

    // Optimize it by making multiply always return 26
    code[0] = 0xB8;  // mov eax, 1Ah
    code[1] = 0x1A;
    code[2] = 0x00;
    code[3] = 0x00;
    code[4] = 0x00;
    code[5] = 0xC3;  // ret

    // Call the faster code, prints 26 even though '3' is given
    cout << ((int (*)(int))&code[0])( 3 ) << endl;
}
Run Code Online (Sandbox Code Playgroud)


sam*_*moz 0

听起来像是一些好的 goto 语句和内联汇编的工作。

请小心。