C用堆栈和寄存器手动调用函数

时间:2013-08-09 12:22:46

标签: c assembly fastcall

我知道这对操纵堆栈很重要,但我认为这对我来说是一个很好的教训。 我搜索了互联网,我发现了呼叫惯例。我知道它的工作原理和原因。我想模拟一些“Callee清理堆栈”或许是stdcall,fastcall它没关系,重要的是认为谁清理堆栈,那么我将会做更少的工作来做:)

例如,

。 我在C

中有功能
double __fastcall Add(int a, int b) {
    return a + b;
}

这将是Calee

我有这个函数的指针,类型为void *,

void* p = reinterpreted_cast<void*>(Add);

我有功能来电

void Call(void* p, int a, int b) {

    //some code about push and pop arg
    //some code about save p into register
    //some code about move 'p' into CPU to call this function manually
    //some code about take of from stack/maybe register the output from function
}

就是这样,当我使用调用约定“Calle clean-up”时它很有用,因为我不需要

//some code about cleans-up this mess

我不知道该怎么做,我知道可以用汇编程序完成。但我害怕它,我从来没有'触摸'这种语言。我很乐意模拟用C调用,但是当任何人都可以用ASM做这件事时我会很生气:)

我还告诉我要做什么, 当我将知道如何手动调用函数时,我将能够调用具有多个参数的函数(如果我知道它的数量和大小)和任何类型的函数。 所以当该函数处于正确的调用约定时,我将能够调用任何语言的任何函数。

我正在使用Windows OS x64和MinGw

1 个答案:

答案 0 :(得分:1)

首先:C旨在隐藏调用约定以及特定于代码如何从程序员执行的所有内容,并在其上方提供一个抽象层。

当您需要(如您所说)“手动”调用函数时,唯一的条件是从asm执行此操作。

C作为一种语言无法直接控制堆栈或程序计数器。

从GCC手册中引用fastcall for x86:

 On the Intel 386, the `fastcall' attribute causes the compiler to
 pass the first two arguments in the registers ECX and EDX.
 Subsequent arguments are passed on the stack. The called function
 will pop the arguments off the stack. If the number of arguments
 is variable all arguments are pushed on the stack.

另据我记得,返回值在EAX传递。

因此,为了以这种方式调用函数,您需要在ECXEDX中提供参数,然后在函数地址上调用call指令

int __fastcall Add(int a, int b) {
    return a + b;
}

请注意我已将返回类型更改为int,因为我不记得如何传回双打。

int a, b;
// set a,b to something

void* p = reinterpreted_cast<void*>(Add);
int return_val;
asm (
    "call %3"
    : "=a" (return_val) // return value is passed in eax
    : "c" (a) // pass c in ecx
    , "d" (b) // pass b in edx
    , "r" (p) // pass p in a random free register
);

通过调用约定,由被调用者清理任何使用过的堆栈空间。在这种情况下,我们没有使用任何,但如果我们这样做,那么你的编译器将翻译你的函数Add,以便它自动清理堆栈。

上面的代码实际上是一种破解方式,我使用GCC扩展asm语法自动将变量放入适当的寄存器中。它将围绕此asm调用生成足够的代码,以确保数据一致。

如果您希望使用基于堆栈的调用约定,则cdecl是标准的

int __cdecl Add(int a, int b) {
    return a + b;
}

然后我们需要在调用

之前将参数推送到堆栈
asm (
    "push %1\n" // push a to the stack
    "push %2\n" // push b to the stack
    "call %3"   // the callee will pop them from the stack and clean up
    : "=a" (return_val) // return value is passed in eax
    : "r" (a) // pass c in any register
    , "r" (b) // pass b in any register
    , "r" (p) // pass p in any register
);

我没有提到的一件事是这个asm调用没有保存我们正在使用的任何寄存器,所以我不建议将它放在一个其他任何东西的函数中。在32位x86中,有一条指令pushad将所有通用寄存器推送到堆栈,并使用等效(popad)来恢复它们。但是,x86_64的等效项不可用。通常,当您编译C代码时,编译器将知道正在使用哪些寄存器并将其保存,以便被调用者不会覆盖它们。在这里它没有。如果被调用者使用调用者正在使用的寄存器 - 它们将被覆盖!