【问题标题】:Call functions from x86_64 assembly从 x86_64 程序集调用函数
【发布时间】:2017-06-18 21:15:38
【问题描述】:

我正在尝试创建自己的 JIT,到目前为止,我设法运行了非常简单的汇编代码(在机器代码中),但是在弄清楚如何以这种方式调用函数时遇到了麻烦。在 Visual Studio 中,我可以在反汇编窗口中看到函数。

另一个相关问题是如何在机器码中调用 Win32 MessageBox()?

下一个问题是如何以这种方式调用外部 DLL/LIB 函数?

还有任何书籍或教程可以进一步教我这个主题吗?我试图搜索它,但得到的结果如 .NET、JVM 和 LLVM,我认为这并不是我真正想要的。

这是我正在处理的代码的简化版本:

#include <iostream>
#include <Windows.h>

int main(int argc, char* argv[])
{
    // b8 03 00 00 00 83 c0 02 c3
    unsigned char code[] = {
        0xb8,                   // mov eax, 3
        0x03, 0x00, 0x00, 0x00, // 3 (32 bit)
        0x83,                   // add eax, 2 // 0x83 = add,
        0xc0,                   // ModR/M with immediate 8 bit value
        0x02,                   // 2 (8 bit)
        0xc3                    // ret
    };

    void* mem = VirtualAlloc(0, sizeof(code), MEM_COMMIT, PAGE_EXECUTE_READWRITE);

    memcpy(mem, code, sizeof(code));

    DWORD old;
    VirtualProtect(mem, sizeof(mem), PAGE_EXECUTE_READ, &old);

    int(*func)() = reinterpret_cast<int(*)()>(mem);

    printf("Number is %d\n", func());

    VirtualFree(mem, 0, MEM_RELEASE);

    return 0;
}

是否可以让 JIT 汇编代码调用 C++ 函数?

在这个项目之前,我用 C++ 制作了一个字节码解释器,但是在将它与 C# 中的等效测试程序进行比较时,我对它的速度并不满意。 C# 大约快 25 倍。所以我偶然发现了一种叫做 JIT 的东西来让它更快。所以我希望你们都能看到我把这个 JIT 项目带到了哪里。如果可能的话,也许让它处理 GUI。

【问题讨论】:

  • 当然可以。周围有无数的例子,也许可以先看看hello world。不过,手工组装不会很有趣。请注意,函数调用不太可能成为您的瓶颈。
  • 我一般用C/C++编写一个示例程序,然后让编译器输出汇编代码,以获得汇编级别名称和调用顺序。对于 Visual Studio 2015,printf 现在是包含文件的一部分,这意味着它可以有效地与 C/C++ 代码内联。解决此问题的一种方法是创建一个项目,其中包含一个用于 printf 的 C 文件,以及一个用于项目其余部分的程序集文件。可以选择导入仍包含旧式 printf 的特定库。
  • 好吧,IIRC,可以弯曲clang将C源代码编译到内存中,然后通过LLVM将其JIT成机器代码并运行它,因此通过研究LLVM源代码,您可能会得到答案。几年后......我也不清楚为什么你会被解释语言的速度所困扰,只是用 C++ 和汇编编写性能部分,JIT 在最好的情况下通常很难达到标准,并且在任何适当调整的性能情况下表现不佳.对于非性能关键部件,25 倍也不重要。尽管这可能是一个很好的练习,可以让您了解 C++ 有多酷。 :)

标签: c++ assembly x86-64 jit machine-code


【解决方案1】:

您可能会找到一些关于编写编译器/链接器的教程。它可能有助于实现/调用动态库。

我不确定您调用 C++ 函数的确切含义。无论如何,我编写了以下演示程序,您可以看看它是否有帮助。

#include <Windows.h>
#include <iostream>


using namespace std;

__int64 sub(__int64 a, __int64 b)
{
    return a - b;
}

int main(int argc, char **argv)
{
    char code[] =
    {
        0x48, 0x89, 0xC8,           // mov rax, rcx
        0xC3,                       // ret

        0x48, 0x83, 0xEC, 0x20,     // sub rsp, 0x20
        0xFF, 0xD0,                 // call rax
        0x48, 0x83, 0xC4, 0x20,     // add rsp, 0x20
        0xC3                        // ret
    };


    char *mem = static_cast<char *>(VirtualAlloc(0, sizeof(code), MEM_COMMIT, PAGE_EXECUTE_READWRITE));

    MoveMemory(mem, code, sizeof(code));

    auto setFunc = reinterpret_cast<void *(*)(void *)>(mem);
    auto callFunc = reinterpret_cast<__int64 (*)(__int64, __int64)>(mem + 4);

    setFunc(sub);
    __int64 r = callFunc(0, 1);
    cout << "r = " << r << endl;

    VirtualFree(mem, 0, MEM_RELEASE);


    cin.ignore();
    return 0;
}

【讨论】:

  • 如何防止 C++ 在setFunc(sub);__int64 r = callFunc(0, 1); 之间污染rax? IMO 这不是一个稳定的例子(尽管它可能在相当低的优化级别和足够的运气下工作)。
  • @Ped7g 我的意思不是您可以按原样使用代码。如果您可以直接调用一个函数,为什么还要这样做。我的意思是说你可以通过这种方式检索函数的地址,然后你可以用它做任何你想做的事情,例如设置一个跳转表。
  • @MegaStupidMonkeys 这是我真正感兴趣的。谢谢!
猜你喜欢
  • 2014-05-02
  • 1970-01-01
  • 2015-03-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-21
  • 1970-01-01
相关资源
最近更新 更多