假设我们有一个位于已知地址的函数func
。我们对这个函数期望的参数数量或数据类型一无所知。
我们得到一个数组,其中包含与函数所期望的正确字节数相对应的数据。例如,假设我们有函数func(uint8_t a, uint16_t b, uint8_t c)
,数组将是0x0A, 0x0B, 0x0C, 0x0D
,其中0x0A
是a
的值,0x0B0C
是{{1}的值}},b
是0x0D
的值。
给定此数组和函数的地址,如何在C或内联汇编中调用此函数?
编辑:我还应该提到这段代码将在ARM处理器上运行。
答案 0 :(得分:4)
如果不知道函数调用约定,就没有办法做到这一点。您不能只是将数据转储到堆栈中并期望您的函数处理它。如果它是__cdecl
函数,则必须在执行后清除堆栈,否则会损坏堆栈。如果它是__fastcall函数,则它需要ecx/rcx
和edx/rdx
寄存器中的前两个参数。 (这也是平台依赖的!)如果是__thiscall,则必须通过ecx
寄存器(也取决于平台)提供指向对象实例的指针。
修改强> 根据{{3}},参数可以通过堆栈和寄存器传递(第18,30页)。所以上面写的所有内容仍然适用。
答案 1 :(得分:0)
鉴于你的最新评论,我认为你可以通过为每个函数调用传递一个额外的参数来完成你想要做的事情(这将是"类型"函数)。例如,假设您对要遇到的所有函数类型进行了枚举:
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;
}
...
}
当然你可能不会手工编写上述所有内容。您可能会通过编写一些脚本来自动生成该代码,以便为您生成每个案例。此外,签名类型和无符号类型可以合并为一种类型,因为就调用函数而言,它实际上并不重要。
只要函数类型的数量远小于函数的数量,该解决方案就有所帮助。如果你的所有功能都属于不同的类型,那么这并不会有帮助,因为你基本上只是在做一个所有功能的开关声明(就像你说你想避免的那样)。
答案 2 :(得分:0)
我发现了问题,并给了它一些时间&想法。让我重复一下这个问题:你希望能够在(任何)地址调用一个函数,并希望传递一个任意类型和长度的参数列表。让我们定义一个名为" 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)
答案 3 :(得分:-3)
这是我的镜头。它编译但我没有测试它。
0x12345678是函数的已知地址。
假设此函数不返回任何内容。
'data'var是传递给它的数据。
'data'var也可以被任意struct {}替换。
该函数必须知道如何从其单指针arg指向的地址中提取数据。
void (*fun)(void*) = (void (*)(void*))(0x12345678);
int main(int argc, char *argv[])
{
unsigned char data[] = {1, 2, 3, 4, 5};
fun(data);
}
======