gpt4 book ai didi

c++ - 如何在 C++ 中将 x64 机器代码写入虚拟内存并在 Windows 中执行

转载 作者:可可西里 更新时间:2023-11-01 10:03:45 26 4
gpt4 key购买 nike

我一直想知道 V8 JavaScript 引擎和任何其他 JIT 编译器如何执行生成的代码。

以下是我在尝试编写小型演示时阅读的文章。

我对汇编知之甚少,所以我最初使用 http://gcc.godbolt.org/ 编写了一个函数并获得反汇编输出,但代码在 Windows 上无法运行。

然后我写了一小段 C++ 代码,用 -g -Og 编译,然后用 gdb 得到反汇编的输出。

#include <stdio.h>

int square(int num) {
return num * num;
}

int main() {
printf("%d\n", square(10));
return 0;
}

输出:

Dump of assembler code for function square(int):
=> 0x00000000004015b0 <+0>: imul %ecx,%ecx
0x00000000004015b3 <+3>: mov %ecx,%eax
0x00000000004015b5 <+5>: retq

我将输出('%' 已删除)复制粘贴到 online x86 assembler 并得到 { 0x0F, 0xAF, 0xC9, 0x89, 0xC1, 0xC3 }

这是我的最终代码。如果我用 gcc 编译它,我总是得到 1。如果我用 VC++ 编译它,我得到随机数。这是怎么回事?

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <windows.h>

typedef unsigned char byte;
typedef int (*int0_int)(int);

const byte square_code[] = {
0x0f, 0xaf, 0xc9,
0x89, 0xc1,
0xc3
};

int main() {
byte* buf = reinterpret_cast<byte*>(VirtualAlloc(0, 1 << 8, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
if (buf == nullptr) return 0;
memcpy(buf, square_code, sizeof(square_code));
{
DWORD old;
VirtualProtect(buf, 1 << 8, PAGE_EXECUTE_READ, &old);
}
int0_int square = reinterpret_cast<int0_int>(buf);
int ans = square(100);
printf("%d\n", ans);
VirtualFree(buf, 0, MEM_RELEASE);
return 0;
}

注意事项

我正在尝试学习 JIT 的工作原理,所以请不要建议我使用 LLVM 或任何库。我保证我会在实际项目中使用合适的 JIT 库,而不是从头开始编写。

最佳答案

注意:正如 Ben Voigt 在评论中指出的那样,这实际上只对 x86 有效,对 x86_64 无效。对于 x86_64,正如 Ben Voigt 在他的回答中指出的那样,您的程序集中只有一些错误(在 x86 中仍然是错误)。

发生这种情况是因为您的编译器在生成程序集时可以看到函数调用的两边。由于编译器控制着为调用者和被调用者生成代码,因此它不必遵循 cdecl 调用约定,它也没有。

MSVC 的默认调用约定是 cdecl。基本上,函数参数以与它们列出的顺序相反的顺序被压入堆栈,因此调用 foo(10, 100) 可能导致汇编:

push 100
push 10
call foo(int, int)

在您的情况下,编译器将在调用站点生成类似于以下内容的内容:

push 100
call esi ; assuming the address of your code is in the register esi

但这不是您的代码所期望的。您的代码期望其参数在寄存器 ecx 中传递,而不是在堆栈中传递。

编译器使用了看起来像 fastcall 调用约定的东西。如果我编译一个类似的程序(我的程序集略有不同),我会得到预期的结果:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <windows.h>

typedef unsigned char byte;
typedef int (_fastcall *int0_int)(int);

const byte square_code[] = {
0x8b, 0xc1,
0x0f, 0xaf, 0xc0,
0xc3
};

int main() {
byte* buf = reinterpret_cast<byte*>(VirtualAlloc(0, 1 << 8, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
if (buf == nullptr) return 0;
memcpy(buf, square_code, sizeof(square_code));
{
DWORD old;
VirtualProtect(buf, 1 << 8, PAGE_EXECUTE_READ, &old);
}
int0_int square = reinterpret_cast<int0_int>(buf);
int ans = square(100);
printf("%d\n", ans);
VirtualFree(buf, 0, MEM_RELEASE);
return 0;
}

请注意,我已经告诉编译器使用 _fastcall 调用约定。如果您想使用 cdecl,程序集需要看起来更像这样:

push ebp
mov ebp, esp
mov eax, DWORD PTR _n$[ebp]
imul eax, eax
pop ebp
ret 0

(免责声明:我不擅长汇编,它是由 Visual Studio 生成的)

关于c++ - 如何在 C++ 中将 x64 机器代码写入虚拟内存并在 Windows 中执行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41865196/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com