C中的低级函数调用?
假设我们有一个位于已知地址的函数C中的低级函数调用?,c,function,assembly,arguments,low-level,C,Function,Assembly,Arguments,Low Level,假设我们有一个位于已知地址的函数func。我们不知道这个函数需要多少参数或什么样的数据类型 我们得到了一个数组,其中包含与函数所期望的适当字节数相对应的数据。例如,假设我们有functionfunc(uint8a,uint16b,uint8c),数组将是0x0A,0x0B,0x0C,0x0D,其中0x0A是a的值,0x0B0C是b的值,0x0D是c的值 给定此数组和函数的地址,如何在C或内联汇编中调用此函数 编辑:我还应该提到,这段代码将在ARM处理器上运行。这是我的尝试。它可以编译,但我没有测
func
。我们不知道这个函数需要多少参数或什么样的数据类型
我们得到了一个数组,其中包含与函数所期望的适当字节数相对应的数据。例如,假设我们有functionfunc(uint8a,uint16b,uint8c)
,数组将是0x0A,0x0B,0x0C,0x0D
,其中0x0A
是a
的值,0x0B0C
是b
的值,0x0D
是c
的值
给定此数组和函数的地址,如何在C或内联汇编中调用此函数
编辑:我还应该提到,这段代码将在ARM处理器上运行。这是我的尝试。它可以编译,但我没有测试它 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);
}
==========如果不知道函数调用约定,就无法做到这一点。您不能只是将数据转储到堆栈中,然后期望函数处理它。如果它是一个
\uu cdecl
函数,则必须在执行后清除堆栈,否则会损坏它。如果它是一个uu fastcall函数,它需要ecx/rcx
和edx/rdx
寄存器中的前两个参数。(这也依赖于平台!)如果是_uthis调用,则必须通过ecx
寄存器(也依赖于平台)提供指向对象实例的指针
编辑:
根据,参数可以通过堆栈和寄存器传递(第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中的堆栈。特别是,您不能在调用后动态生成推送指令和堆栈清理指令 不过,您可以短暂退出到assembler,以执行推送和清理。讨论中的许多其他答案/参与者给出了相关方面和示例,因此我只提供了一个伪代码解决方案/示例:
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)一点也不会。问题是,我们不知道如何将它放在堆栈和寄存器中,或者该函数的未知调用约定想要什么。您可以声明不知道该函数需要多少个参数,然后声明它需要3个参数,一个字节、一个短字节和一个字节。您只需为函数声明一个typedef,然后将函数的地址强制转换为指向该类型函数的指针,并使用指定的参数调用它。这是一个解释数组的示例函数。我们只有数组和函数的地址。我们不知道这个函数需要多少个参数或什么样的数据类型。按相反的顺序将每个参数推到堆栈上,从函数返回时调用函数,将每个参数从堆栈中弹出。@user3629249但如果不知道每个参数的大小,就不能这样做(如果有四个字节,是四个
char
s还是一个int
?)。您不能指定函数采用void指针,因为您不知道函数采用的参数类型或数量。编译器只需将数据的地址
传递给函数,并假设void*
是它将采用的参数。问题的关键是如何构建invo具有任意参数集的函数的kation序列,一个您无法控制的参数集。我没有提到这将在ARM体系结构上运行,因此x86寄存器定义不适用。但是,基本理论应该是合理的。您能推荐一篇关于参数和堆栈如何相关的文章吗?正确。正如我在pr中提到的在前面的评论中,这个理论应该仍然是正确的。我正在对我的程序进行反汇编,并正在设计一种方法,在这种方法中,我手动增加适当的寄存器和堆栈。编译器可以很好地预测它们将参数分配给哪些寄存器,所以我几乎可以肯定这是可以做到的。感谢您的非常透彻的解释不幸的是,这种方法不是m