在C中序列化函数指针并将其保存在文件中?

在C中序列化函数指针并将其保存在文件中?,c,file,serialization,posix,function-pointers,C,File,Serialization,Posix,Function Pointers,我正在开发一个C文件寄存器程序,它可以处理任意的通用数据,因此用户需要提供要使用的函数,这些函数保存在寄存器结构的函数指针中,并且工作得很好。但是我需要能够在程序重新启动时再次运行这些函数,而不需要用户再次提供它们。我序列化了有关寄存器结构的重要数据,并将其写入头中 我想知道如何在那里保存函数,编译的c函数只是原始的二进制数据,对吗?因此,必须有一种方法将其存储到文件中,并从文件中的内容加载函数指针,但我不确定如何实现这一点。有人能给我指出正确的方向吗 我假设这是可能的,因为它允许你做几乎任何事

我正在开发一个C文件寄存器程序,它可以处理任意的通用数据,因此用户需要提供要使用的函数,这些函数保存在寄存器结构的函数指针中,并且工作得很好。但是我需要能够在程序重新启动时再次运行这些函数,而不需要用户再次提供它们。我序列化了有关寄存器结构的重要数据,并将其写入头中

我想知道如何在那里保存函数,编译的c函数只是原始的二进制数据,对吗?因此,必须有一种方法将其存储到文件中,并从文件中的内容加载函数指针,但我不确定如何实现这一点。有人能给我指出正确的方向吗

我假设这是可能的,因为它允许你做几乎任何事情,但我可能遗漏了一些东西,我可以不用系统调用来做吗?或者如果不是,在posix中最简单的方法是什么

创建寄存器或创建新的二级索引时提供以下函数:

registerHandler* createAndOpenRecordFile(
int overwrite, char *filename, int keyPos, fn_keyCompare userCompare, fn_serialize userSerialize, fn_deserialize userDeserialize, int type, ...)
并保存为函数指针:

typedef void* (*fn_serialize)(void*);
typedef void* (*fn_deserialize)(void*);
typedef int (*fn_keyCompare) (const void *, const void *);

typedef struct {
...
fn_serialize encode;
fn_deserialize decode;
fn_keyCompare compare;
} registerHandler;

虽然你的逻辑有些道理,但事情远比这复杂得多。我的答案将包含大部分已经在这里发表的评论,只是以答案的形式

假设您有一个指向函数的指针。如果该函数中有跳转指令,则该跳转指令可以跳转到绝对地址。这意味着,当您反序列化函数时,必须有一种方法强制将其加载到同一地址,以便绝对跳转跳到正确的地址

这就引出了下一点。考虑到您的问题是用posix标记的,没有符合posix的方法将代码加载到特定地址,MAP_是固定的,但除非您编写自己的动态链接器,否则它将无法工作。这有什么关系?由于各种原因,函数的汇编代码可能引用函数的起始地址,其中最突出的原因是函数本身是否将自己的地址作为参数提供给另一个函数

这实际上把我们带到了下一点。如果序列化函数调用其他函数,您也必须序列化它们。但这是最简单的部分。困难的部分是,如果函数跳转到另一个函数的中间,而不是调用另一个函数,这可能会发生,例如,由于尾部调用优化。这意味着您必须以递归方式序列化函数跳转到的所有内容,但是如果函数跳转到0x00000000ff173831,您将从该地址序列化多少字节

就这点而言,您如何知道任何函数何时以可移植的方式结束

更糟糕的是,您是否保证函数在内存中是连续的?当然,所有现有的、健全的硬件操作系统内存管理器和硬件体系结构都使它在内存中保持连续,但从现在起1年内它能保证如此吗

另一个问题是:如果用户根据动态的内容传递不同的函数,该怎么办?i、 如果环境变量X为真,我们需要函数X,否则我们需要y

我们甚至不会考虑讨论跨硬件体系结构、操作系统甚至同一硬件体系结构版本的可移植性

但我们要谈谈安全问题。假设您不再要求用户给您一个指向其代码的指针,该代码可能有一个bug,他们在新版本中修复了该bug,那么您将继续使用该bug版本,直到用户记得用新代码刷新您的数据结构为止

当我说上面的bug时,您应该阅读安全漏洞。如果您正在序列化的易受攻击的函数启动了一个shell,或者确实引用了进程之外的任何东西,那么它将成为一个持久性攻击

简而言之,没有办法以理智和经济的方式去做你想做的事情。相反,您可以做的是强制用户为您打包这些函数

最明显的方法是让他们传递一个库的文件名,然后用dlopen打开

另一种方法是传递Lua或JavaScript字符串,并嵌入一个引擎以代码形式执行这些字符串

另一种方法是向可执行文件传递路径,并在需要处理数据时执行这些路径。这是


但您可能应该做的只是要求用户始终传递这些函数。保持简单。

这不可能奏效。函数与它们加载到的进程的内存布局密切相关。无法在进程之间序列化或共享这些函数。请将这些函数放在共享库中,并使用dlXXX函数动态加载它们。提供要使用的函数的用户是如何使用的?请添加一些代码来说明此问题。用户如何“提供要使用的功能”?这些功能从何而来
从?您将所有代码放在一个共享对象中,如foo.so。然后使用lib=dlopen/path/to/foo.so、RTLD_LAZY来打开它,并使用func_ptr=dlsymlib、function_name来获取具有该名称的函数。