【问题标题】:How to dynamically allocate function code?如何动态分配功能代码?
【发布时间】:2019-06-08 11:39:40
【问题描述】:

在 C 语言中,函数指针作为来自某个库的回调的通常途径是包含一个 void* 指针用于用户的上下文:

void (*fp)(void* ctx);

允许库使用上下文ctx 调用回调。

假设我使用的库在回调中不包含上下文指针。我需要为每个上下文设置一个回调。在 C 中动态分配函数指针以提供回调的最可移植的方法是什么?我怎样才能malloc()一个可以从库中调用的函数代码?

例如:

typedef void (*my_fp_t)(char);

my_fp_t fp = (my_fp_t) malloc(sizeof(...));
init_function_ptr(fp, "Hello, there");
my_library_callback(fp);
...

void my_library_callback(my_fp_t fp) {
  fp('a'); // prints "Hello, there"
}

【问题讨论】:

标签: c pointers callback function-pointers


【解决方案1】:

假设您希望分配实际的函数代码,而不是函数指针。编写引导加载程序和类似的方法时这样做:

  • 完全确定编译器和 ABI 使用的底层调用约定。您需要确切地知道堆栈帧中的哪些寄存器和/或位置是函数调用和返回的地方。
  • 通过在 C 编译器中编写函数并仔细复制/粘贴程序集来生成汇编器代码,或者在汇编器中手动编写函数。
  • 将汇编程序转换为操作码。
  • 确保有一块内存可以存储数据和执行代码。这也取决于 ABI 和 MMU 设置。
  • 了解如何在此自定义段中分配内存。通常涉及一些链接器脚本摆弄和各种非标准关键字。 #pragma __declspec __attribute__blabla,上面加糖。高度特定于编译器。
  • 将内存作为原始 uint8_t func [n] 数组分配给该区域,其中包含十六进制的原始 OP 代码。
  • 关闭严格别名和其他此类邪恶的指针转换事情。从对象指针到函数指针时,请确保您的编译器具有一些定义明确的非标准扩展。
  • 通过((func_ptr)func) ()拨打代码。

正如您所希望的那样,这可能是一项艰巨的任务,具体取决于系统。对于使用嵌入式系统编译器的小型微控制器来说,这在某种程度上是很容易实现的。对于像 x86 或 PowerPC 这样更复杂的系统,使用像 gcc 这样的编译器,则要少得多。您将依赖于各种指定不当的行为,并且代码将完全是系统特定的。

【讨论】:

  • 总而言之,这听起来像是:“顺其自然。” ;-)
  • @alk 取决于系统。我已经为低端微控制器编写了各种类似这样的引导加载程序,这并不太痛苦。当您需要从 RAM 而不是闪存等执行代码时。但是在高端 CPU 上执行类似的操作将是另一回事。
  • @Lundin 我不能只写一个“模板”函数,然后 malloc+memcpy 函数的代码,其中局部变量初始化为已知值,并通过搜索和替换更新一些局部变量值?
  • @vz0 如果忽略指针转换问题,您如何知道函数的大小、结束位置等?而且,您能否在优化的 C 函数中找到您正在寻找的值并不明显。 void f (void) { size_t n=5; ... switch(n) { case 5: do_stuff(); break } ... } 之类的东西可能会被翻译成“call do_stuff”,并且在机器代码中的任何地方都找不到 5。
  • @Lundin 我在void f (void) { void* n = 0x4242424242424242; do_stuff(n); } 的行上想得更多,然后我会搜索并替换 0x4242424242424242。从 GCC 调用 __builtin_object_size 对函数对象不起作用,虽然 :(
【解决方案2】:

我想我得到了答案。

真正的问题是在没有接收到的回调中获取void* 上下文指针。而且我不能使用(原则上)全局变量,因为可能会调用很多回调,我无法确定哪个是回调。

我可以采取与 JIT VM 相同的路线:为每个可能的 void* ctx 值动态生成机器代码,例如:

void do_stuff(void* ctx) {
  // Finally have the ctx!
}

void* my_ctx;
my_fn_ptr p = allocate_function_that_calls_f_with_ctx(do_stuff, ctx);
library_register_callback_fn(p);

这里的p 是一个函数指针,指向带有签名void (*)(void) 的函数,当依次调用它时,它会调用f(ctx)

不过,这只是针对库的回调,回调的次数很少。然后我可以编写一堆函数,每个函数都从表中获取一个 void*,每个函数都来自不同的索引:

void do_stuff_0(void) { do_stuff(do_stuff_TABLE[0]); }
void do_stuff_1(void) { do_stuff(do_stuff_TABLE[1]); }
void do_stuff_2(void) { do_stuff(do_stuff_TABLE[2]); }
...

我可以编写一些简单的脚本,在一个新的 .c 文件中写入从 0 到几千的所有函数。然后我需要跟踪哪些功能已经注册,哪些没有注册,并提供一些机制来选择下一个可用的并释放那些不再需要的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-12-17
    • 2012-09-27
    • 1970-01-01
    • 2011-09-16
    • 2017-01-23
    • 1970-01-01
    • 2019-01-02
    • 1970-01-01
    相关资源
    最近更新 更多