繁体   English   中英

C语言中的低级函数调用?

[英]Low-level function invocation in C?

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

我们给了一个包含数据的数组,该数据对应于函数期望的正确字节数。 例如,假设我们有功能func(uint8_t a, uint16_t b, uint8_t c) ,所述阵列将是0x0A, 0x0B, 0x0C, 0x0D其中0x0A是值a0x0B0C是的值b ,和0x0D是值的c

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

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

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

编辑:根据ARM体系结构过程调用标准 ,可以通过堆栈和寄存器传递参数(第18、30页)。 因此,以上所有内容仍然适用。

考虑到您的最新评论,我认为您可以通过在每次函数调用中传递一个额外的参数(这将是函数的“类型”)来完成您要执行的操作。 例如,假设您对将遇到的所有函数类型进行了枚举:

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语句(就像您说要避免的那样)。

我发现问题很棘手,并花了一些时间和想法。 让我重复一个问题:您希望能够在(任意)地址处调用函数,并希望向其传递一个任意类型和长度的参数列表。 让我们定义一个名为“ 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)

这是我的照片。 它可以编译,但我没有对其进行测试。

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);
}

=========

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM