在C

时间:2017-12-06 14:59:13

标签: c

我有(在内存中映射)两个目标文件,“Ao”“Bo”,具有相同的CPU指令集(不一定是英特尔 - 可以是x86,x86_64,MIPS(32/64),ARM(32/64),PowerPC(32/64),......,但在两个目标文件中总是相同的。)

此外,两个目标文件都使用相同的字节序编译(小端,或两个大端)。

然而(你知道有一个,否则就没有任何问题)“Ao”“Bo”可以有一个不同的函数调用约定,并使事情变得更糟,彼此不知道(“Ao”甚至没有关于“Bo”中函数的调用约定的任何想法,反之亦然。)

“Ao”“Bo”显然是为了在同一个目标文件中调用函数,但必须有(非常)有限的接口才能在它们之间进行通信(否则,如果执行从“Ao”中的某个功能开始,如果没有这样的界面,则不会执行“Bo”中的任何功能。)

执行开始的文件(让我们假设它是“Ao”)知道来自“Bo”的所有静态符号的地址(所有函数的地址和全局变量)。但事实恰恰相反(好吧,我试图编写的有限界面会克服这个问题,但“Bo”不知道来自“Ao”的任何地址在建立这样的界面之前)。

最后一个问题:如何执行从“Ao”中的函数跳转到“Bo”中的函数,然后返回,还要传达一些数据?

我需要它:

  • 以标准C(无装配)完成。
  • 是便携式C(不依赖于编译器,也不依赖于CPU)。
  • 线程安全。
  • 不要对所涉及的召唤惯例做出任何假设。
  • 能够在两个目标文件之间传递数据。

目前,我最好的想法似乎可以满足所有这些要求,除了线程安全。例如,如果我像这样定义struct

struct data_interface {
   int value_in;
   int value_out; };

我可以将“Ao”中的struct指针写入“Bo”的全局变量(事先知道这样的全局变量) “Bo”中的变量具有足以存储指针的空间。

然后,接口函数将是void interface(void)(我假设调用void(void)函数在不同的调用约定中是安全的......如果不是这样,那么我的想法就不会工作)。从“A.o”“B.o”调用此类函数会将数据传递给“B.o”中的代码。并且,手指交叉,当“Bo”中的被调用函数返回时,它会很好地返回(假设从{返回时)不同的调用约定不会改变行为{1}}功能)。

但是,当然这不是线程安全的。

为了保证线程安全,我想我唯一的选择是访问堆栈。

但是......可以在标准C中以便携方式访问堆栈吗?

3 个答案:

答案 0 :(得分:1)

以下是两条建议。

数据接口

这详细说明了您自己定义的结构。根据我过去看到的情况,编译器通常使用单个寄存器(例如eax)作为返回值(假设返回类型适合寄存器)。我的猜测是,以下函数原型很可能不受不同调用约定的影响。

struct data_interface *get_empty_data_interface(void);

如果是这样,那么您可以使用与您使用数组的想法类似的方式使用它。在B中定义以下结构和函数:

struct data_interface {
    int ready;
    int the_real_data;
};

struct data_interface *get_empty_data_interface(void)
{
    struct data_interface *ptr = malloc(sizeof(struct data_interface));
    add_to_list_of_data_block_pointers(ptr);
    ptr->ready = 0;
    return ptr;
}

void the_function(void)
{
    execute_functionality_for_every_data_block_in_my_list_that_is_flagged_ready_and_remove_from_list();
}

要调用此功能,请在A:

中执行此操作
struct data_interface *ptr = get_empty_data_interface();
ptr->the_real_data = 12345;
ptr->ready = 1;
the_function();

为了线程安全,请确保B维护的数据块列表是线程安全的。

  • 同时拨打get_empty_data_interface不应该覆盖列表中的对方插槽。
  • 同时拨打the_function不应该同时接听相同的列表元素。

包装函数

您可以尝试使用众所周知的调用约定(例如cdecl)公开包装函数;如果需要在单独的目标文件中定义 知道它包装的函数的调用约定。

不幸的是,您可能需要非便携式功能属性。

您可以通过声明可变参数包装函数(使用省略号参数,如printf)来欺骗您。编译器很可能会依赖于cdecl。这消除了非便携式功能属性,但可能不可靠;您必须验证我对您希望支持的每个编译器的假设。在测试时,请记住编译器选项(特别是优化)可能会发挥作用。总而言之,这是一种肮脏的方法。

答案 1 :(得分:1)

这个问题暗示两个目标文件的编译方式不同,除了字节顺序,它们被链接在一起成为一个可执行文件。

它说A.o知道来自B.o的所有静态符号,但事实恰恰相反。

不要对所涉及的电话会议做出任何假设。

所以我们只使用void f(void)类型的函数。

你在B.o中声明int X, Y;,在A.o中声明extern int X, Y;所以在你调用B.中的函数之前,你检查Y标志,如果被提升则等到它下降。当调用B函数时,它会引发Y标志,从X读取输入,进行一些计算,将结果写回X并返回。

然后A.o中的调用函数将X中的值复制到它自己的编译单元中并清除Y标志。

...如果调用void f(void)函数只是从代码中的一个点跳到另一个点。

另一种方法是在B.o中声明static int Y = 0;并在A.o中完全省略它。

然后当一个B.o函数被调用时,它会检查Y == 0是否增加Y,读取X,进行计算,写入X,减少Y并返回。如果不是这样,那么等待成为0并阻止调用函数。

或者甚至可能在每个B.o函数中都有一个静态标志,但由于通信数据在B.o中是全局的,所以我没有看到这种浪费的重点

答案 2 :(得分:1)

请记住,存在调用者保存和被调用者保存约定,以及使用寄存器传递值,使用或不使用帧指针的变体,甚至(在某些体系结构中,在某些优化级别中)使用分支中的延迟槽来保存子程序的第一条指令。如果不了解游戏中的调用约定,你将无法做到这一点,但幸运的是链接器无论如何都需要它。据推测,有一些更高级别的实体负责加载这些DLL并知道它们的调用约定?

如果不是技术上未定义的行为,那么你在这里做的任何事情都将最好深入实现定义的领域,并且你将需要对链接器和加载器进行深入研究(特别是链接器必须知道如何解析动态你的未知调用约定中的链接,或者你将无法以有意义的方式加载该共享对象,因此你可以使用libbfd或其他方式来保留它,但这超出了C)的范围。

这种事情可能会出错的地方是,如果共享资源在A中分配并在B中释放(记住弹簧,因为内存管理通常是基于库的操作系统SBRK或类似的包装器,并且这些内存管理的实现在内存布局中并不具有内在的兼容性,其他地方你可能会被这个包括IO(参见sjannanigans,你有时在c ++中混合printf和cout以获得良好的例子)和锁定​​。