1

我知道这是操纵堆栈的大事,但我认为这对我来说将是一个很好的教训。我搜索了互联网,我发现了调用约定。我知道它是如何工作的以及为什么。我想模拟一些“被调用者清理堆栈”可能是stdcall,fastcall没关系,重要的是谁清理堆栈,那么我将有更少的工作要做:)

例如。我在 C 中有函数

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

会是凯莉

我有指向这个函数的指针,类型为 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

4

1 回答 1

1

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

您需要(如您所说)“手动”调用函数的唯一条件是从asm.

作为一门语言,C 不能直接控制堆栈或程序计数器。

引用 x86 的 GCC 手册fastcall

 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自动清理堆栈的方式翻译您的函数。

上面的代码实际上是一个 hack,我使用 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 代码时,编译器会知道哪些寄存器正在使用,并将保存它们以便被调用者不会覆盖它们。在这里它没有。如果您的被调用者使用调用者正在使用的寄存器 - 它们将被覆盖!

于 2013-08-09T12:45:07.013 回答