在指向现有内存地址的 hpp 文件中声明成员函数的最佳方法

Best way to declare member functions in a hpp file that point to an existing memory address

本文关键字:声明 成员 函数 方法 最佳 文件 hpp 内存 地址      更新时间:2023-10-16

由于 MVSC x86/x64 编译器的更改,我无法使用__asm(jmp addr)通过项目中的内存地址执行直接函数。

对于非成员函数,此问答起到了作用:将内联程序集尾调用函数尾声替换为 x86/x64 msvc 的 Intrinsics

但是对于成员函数,我现在需要一个替代方案;必须指出的是,对于构造函数和解构函数,以及我的变体不起作用的同名函数,这也是必需的。当然,也欢迎提出改进建议,是的,我只有内存地址,所以没有其他方法可以调用它。

zstring.hpp:

#pragma once
#include <string>
#include "../asmjmp.h"
int __cdecl operator==(class zSTRING const &, char const * const);
int __cdecl operator==(class zSTRING const &, class zSTRING const &);
class zSTRING
{
public:
zSTRING() {
__asm( jmp 0x00402AF0);
}
zSTRING(zSTRING const &) {
__asm( jmp 0x00416500);
}
~zSTRING() {
__asm( jmp 0x00401160);
}
int Overwrite(unsigned int, class zSTRING const &) {
__asm( jmp 0x0046B6A0);
}
int Overwrite(string) {
__asm( jmp 0x0046B6FF);
}
int Insert(unsigned int, class zSTRING const &) {
__asm( jmp 0x0046B400);
}
/* My Variant: */
int (*Insert)(unsigned int, class zSTRING const &) = ((int(*)(unsigned int, class zSTRING const &))0x0046B400);
int Delete(class zSTRING const &, enum zTSTR_KIND) {
__asm( jmp 0x0046BCF0);
}
/* My Variant: */
int (*Delete)(class zSTRING const &, enum zTSTR_KIND) = ((int(*)(class zSTRING const &, enum zTSTR_KIND))0x0046BCF0);
char * ToChar() const
{
__asm( jmp 0x004639D0);
}
zSTRING Copied(unsigned int, unsigned long) const
{
__asm( jmp 0x0046C170);
}
zSTRING &operator+=(char const *) {
__asm( jmp 0x0067A7B0);
}
/* My Variant (without knowing if it would work): */
zSTRING (*&operator+=)(char const *) = ((zSTRING (*)(char const *))0x0067A7B0);
char & operator[](unsigned int) {
__asm( jmp 0x00445A20);
}
/* My Variant (without knowing if it would work): */
char & (operator[])(unsigned int) = ((char &(*)(unsigned int))0x00445A20);
};

如果你想要某个函数包含单个jmp指令到某个地址 - 你需要用__declspec(dllimport)声明它(这是Microsoft特定的,仅适用于CL编译器,但认为另一个编译器具有平等的语法(。 比如说

void __declspec(dllimport) TrimLeft(char);

如果你对类中的所有成员函数都使用了这个 - 用这个声明所有类:

class __declspec(dllimport) zSTRING
{
zSTRING();
// .. more declarations
};

这将对所有非虚拟成员函数和类的静态数据成员产生影响,就像它用__declspec(dllimport)声明的那样

当函数声明时__declspec(dllimport)编译器声明了 extern 指针变量:

extern void* __imp___FUNCDNAME__;

其中__FUNCDNAME__是修饰的函数名称 +__imp_前缀; 并且每次调用这样的函数编译器时都会生成call __imp___FUNCDNAME__指令(在将函数参数传递给寄存器或堆栈之后(。 使用"编辑并继续"选项 编译器通常会生成不太优化的代码:

call func
func:
jmp __imp___FUNCDNAME__ ; exactly what you try - single jmp in function body

这实际上相当于单call __imp___FUNCDNAME__

现在很明显,对于每个导入的函数,void* __imp___FUNCDNAME__必须在某个地方定义并包含实际函数地址。 否则你会得到众所周知的链接器错误LNK2001:未解析的外部符号

通常我们使用LIB文件,其中精确定义了__imp___FUNCDNAME__符号 - 在这种情况下,链接器将所有这些__imp_*变量放在 PEIAT部分中,并在导入部分中对其进行描述。 作为结果加载器,加载图像时为每个__imp___FUNCDNAME__分配正确的地址。

如果您尝试从某个 DLL 导入此函数并且此 DLL导出此函数 - 您必须为此提供 LIB 文件。 即使您没有LIB- 您也可以自己轻松创建 -创建单独的项目,其输出文件名与 DLL 名称完全匹配,您将从中调用代码并使用每个函数或类的__declspec(dllexport)"实现"所有这些函数。 每个函数的实现 - 可以是空的或单个的return 0;- 实际上当我们构建 lib 时 - 它不包含任何代码(因为结果实现可能是假的/空的(。它完全包含函数名称和DLL名称(因为项目的此输出文件名必须完全是DLL名称。通常 - 这必须如下所示:

void __declspec(dllexport) TrimLeft(char)
{
}
class __declspec(dllexport) zSTRING
{
public:
zSTRING()
{
}
int Overwrite(unsigned int, class zSTRING const &)
{
return 0;
}
//...
};

您可以轻松地构建此代码并获得所需的所有LIB文件(导入库(。

如果此功能未导出 - 不清楚您从哪里获得地址,但无论如何 - 这不能是硬编码的绝对地址。 在极端情况下,您可以使用来自DLL的硬编码RVA...无论如何,如果此功能未导出- 您需要自己定义所有__imp___FUNCDNAME__。并且您自己在开始时为其分配正确的函数地址。

因为__FUNCDNAME__通常包含非法的C/C++符号 - 你需要在ASM中声明它,如下所示:

_BSS segment
__imp_?TrimLeft@@YAXD@Z DQ ?
__imp_??0zSTRING@@QEAA@XZ DQ ?
__imp_??1zSTRING@@QEAA@XZ DQ ?
__imp_??0zSTRING@@QEAA@AEBV0@@Z DQ ?
__imp_?Insert@zSTRING@@QEAAHIAEBV1@@Z DQ ?
public __imp_?TrimLeft@@YAXD@Z
public __imp_??0zSTRING@@QEAA@XZ
public __imp_??1zSTRING@@QEAA@XZ
public __imp_??0zSTRING@@QEAA@AEBV0@@Z
public __imp_?Insert@zSTRING@@QEAAHIAEBV1@@Z
_BSS ends

并实现自己解决导入的功能

resolveimport proc
lea rax,[rcx + rva_1]
mov __imp_?TrimLeft@@YAXD@Z,rax
lea rax,[rcx + rva_2]
mov __imp_??0zSTRING@@QEAA@XZ,rax
;...
ret
resolveimport endp

假设您从地址为 DLL 的C++代码调用resolveimport-resolveimport(LoadLibraryW(L"my.dll"));