【问题标题】:Low-level function invocation in C?C中的低级函数调用?
【发布时间】:2015-02-01 19:53:12
【问题描述】:

假设我们有一个位于已知地址的函数func。我们对这个函数需要多少参数或什么样的数据类型一无所知。

我们得到一个数组,其中包含与函数期望的正确字节数相对应的数据。例如,假设我们有函数func(uint8_t a, uint16_t b, uint8_t c),数组将是0x0A, 0x0B, 0x0C, 0x0D,其中0x0Aa 的值,0x0B0Cb 的值,0x0D 是@ 的值987654329@.

给定这个数组和函数的地址,如何在 C 或内联汇编中调用这个函数?

编辑:我还应该提到,这段代码将在 ARM 处理器上运行。

【问题讨论】:

  • 一点也不。问题是我们不知道如何将它放在堆栈和寄存器中,或者该函数的未知调用约定想要的任何东西。
  • 您说您不知道该函数需要多少个参数,然后说它需要 3 个参数,一个字节,一个短字节和一个字节。您可以为函数声明一个 typedef,然后将函数的地址转换为指向该类型函数的指针,并使用您指定的参数调用它。
  • 这是一个解释数组的示例函数。我们只有数组和函数的地址。我们对这个函数需要多少参数或什么样的数据类型一无所知。
  • 将每个参数以相反的顺序压入堆栈,从函数返回时调用该函数,将每个参数从堆栈中弹出。
  • @user3629249 但是你不能在不知道每个参数的大小的情况下这样做(如果你有四个字节,是四个chars 还是一个int?)。

标签: c function assembly arguments low-level


【解决方案1】:

如果不知道函数调用约定,就无法做到这一点。您不能只是将数据转储到堆栈中并期望您的函数来处理它。如果它是__cdecl 函数,则必须在执行后清除堆栈,否则会损坏它。如果它是一个 __fastcall 函数,它需要 ecx/rcxedx/rdx 寄存器中的前两个参数。 (这也取决于平台!)如果是 __thiscall,您必须通过ecx 寄存器(也取决于平台)提供指向对象实例的指针。

编辑: 根据Procedure Call Standard for the ARM architecture,参数可以通过堆栈和寄存器传递(第 18、30 页)。所以上面写的仍然适用。

【讨论】:

  • 我没有提到这将在 ARM 架构上运行,因此 x86 寄存器定义不适用。然而,基本理论应该是合理的。你能推荐一篇关于参数和堆栈如何相关的文章吗?
  • 正确。正如我在之前的评论中提到的,这个理论应该仍然是合理的。我正在对我的程序进行反汇编,并正在设计一种方法来手动增加适当的寄存器和堆栈。编译器可以很好地预测它们将参数分配给哪些寄存器,所以我几乎可以肯定它可以做到。
【解决方案2】:

鉴于您的最新评论,我认为您可以通过为每个函数调用传递一个额外的参数(这将是函数的“类型”)来完成您想要做的事情。例如,假设您对将遇到的所有函数类型进行了枚举:

enum funcType {
    V_U8_U16_U8,  // Means: void func(uint8_t, uint16_t, uint8_t)
    V_U16_U16,    // Means: void func(uint16_t, uint16_t)
    U32_U32,      // Means: uint32_t func(uint32_t)
    ...
}

然后在主机端,您可以将 funcType 与其他信息一起传输。在目标端,您需要执行以下操作:

type = GetFunctypeFromStream();
switch(type) {
    case V_U8_U16_U8:
    {
        void (*func)(uint8_t, uint16_t, uint8_t) = GetFuncPointerFromStream();
        uint8_t  arg1 = GetU8ArgFromStream();
        uint16_t arg2 = GetU16ArgFromStream();
        uint8_t  arg3 = GetU8ArgFromStream();

        func(arg1, arg2, arg3);
        break;
    }
    case V_U16_U16:
    {
        void (*func)(uint16_t, uint16_t) = GetFuncPointerFromStream();
        uint16_t arg1 = GetU16ArgFromStream();
        uint16_t arg2 = GetU16ArgFromStream();

        func(arg1, arg2);
        break;
    }
    case U32_U32:
    {
        uint32_t (*func)(uint32_t) = GetFuncPointerFromStream();
        uint32_t arg1 = GetU32ArgFromStream();

        ret = func(arg1);
        break;
    }
    ...
}

当然,您可能不会手写以上所有内容。您可能会通过编写一些脚本为您生成每个案例来自动生成该代码。此外,有符号类型和无符号类型可以合并为一种类型,因为就调用函数而言,这并不重要。

只要函数类型的数量远小于函数的数量,此解决方案就会有所帮助。如果您的所有函数都是不同类型的,那么这将无济于事,因为您基本上只是对所有函数进行 switch 语句(就像您说要避免的那样)。

【讨论】:

  • 感谢您非常详尽的解释。不幸的是,这种方法并不比我在之前的评论中概述的方法好多少。按照您所做的操作,我需要创建一个相当长的 switch 语句,正如您已经提到的,我试图避免这种情况。当然,与为每个函数编写一个案例相比,我会节省一些案例,但这个过程仍然很严格,并且很可能需要一个脚本才能准确生成。正如@Ari0nhh 建议的那样,正如我最初的想法,我也许可以通过操纵堆栈来实现我想要的。我会尽快发布后续消息
【解决方案3】:

我发现了问题,并给了它一些时间和想法。让我重复一下这个问题:您希望能够在(任何)地址调用函数,并希望将任意类型和长度的参数列表传递给它。让我们定义一个名为“call_it”的函数,我们提供地址和指向参数数组的指针;这个函数必须做堆栈管理和调用。 “call_it”可以是纯 C 函数吗?答案是“不”,因为您不能以我们必须的方式操作 C 中的堆栈。特别是你不能在调用之后动态生成推送指令和堆栈清理指令。

但是,您可以短暂退出汇编程序以进行推送和清理。讨论的其他很多答案/参与者都给出了相关方面和示例,所以我只提供一个伪代码解决方案/示例:

typedef struct PRM {int type; void *arg;};
#define T_INT   1

int call_it(int (*address)(), struct PRM *prms, int n_prms)
{
    int nbytes=0;
    prms += n_prms-1;
    for (;n_prms>0; n_prms--, prms--) {
        switch (prms->type) {
            case T_INT:
                __asm mov ax,prms.arg
                __asm push ax
                nbytes += sizeof(int);
                break;
        }
    }
    address();
    __asm add sp, nbytes;
}

(对不起,我只会说X86)

【讨论】:

    【解决方案4】:

    这是我的尝试。它可以编译,但我没有测试它。

    0x12345678 是函数的已知地址。

    假设这个函数不返回任何东西。

    'data' var 是传递给它的数据。

    'data' var 也可以被任意结构 {} 替换。

    该函数必须知道如何从其单个指针 arg 指向的地址中提取数据。

    void (*fun)(void*) = (void (*)(void*))(0x12345678);
    
    int main(int argc, char *argv[])
    {
        unsigned char   data[] = {1, 2, 3, 4, 5}; 
    
        fun(data);
    }
    

    =========

    【讨论】:

    • 你不能指定函数接受一个 void 指针,因为你不知道函数接受什么样的参数,或者有多少。编译器将简单地将data 的地址传递给函数,假设void * 是它所需要的参数。
    • 问题的重点是如何为具有任意一组参数的函数构建调用序列,您无法控制。
    猜你喜欢
    • 2016-08-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多