gpt4 book ai didi

c++ - 重新实现第 3 方非虚拟功能

转载 作者:行者123 更新时间:2023-12-03 06:49:45 27 4
gpt4 key购买 nike

我想知道是否有办法重新实现非虚拟函数 foo在第 3 方类(class)中 Base .
动机是我只需要将回调附加到 foo .但是,该函数是从类 Base 的许多地方调用的。 ,并且由于它不是虚拟的,因此需要更改 Base到位,或大幅重写将从 Base 派生的类的实现.我想避免这两种情况。
我根本不需要多态性,派生类将只有一个实例,并且类型将在编译时已知(因此例如 CRTP 而不是虚拟化就足够了)。
我尝试使用一个类,它继承自声明 foo 的辅助类。作为虚拟,但没有运气。这是一个示例,其中 bar模拟Base的任何地方s 实现来自 foo可以称为:

/// Ideally, do not modify the `Base` at all
struct Base {
void foo()
{
cout << "Base::foo" << endl;
}

void bar()
{
cout << "bar" << endl;
foo(); //< foo is not virtual !
}
};

struct Virtual {
virtual void foo() = 0;
};

struct Virtual_base : Virtual, Base {
void foo() override = 0; //< it still does not affect Base !
};

struct Virtual_derived : Virtual_base {
void foo() override
{
cout << "Virtual_derived::foo" << endl;
Base::foo();
}
};
好吧, Virtual_derived::foo确实覆盖,但 Base::bar ,不出所料,仍然没有改变。我也尝试了 CRTP 方法,但显然没有运气,因为它仍然与 Base 相同的问题发生冲突。保持封装状态。
我有点害怕答案是不可能的..是吗?

最佳答案

在 Windows 上,您可以使用热补丁:https://jpassing.com/2011/05/03/windows-hotpatching-a-walkthrough/ .
使用/hotpatch 编译。这将在每个函数的开头添加一个 2 字节的 NOP,并在之前添加一个 6 字节的 nop(32 位为 5),允许您在重定向中进行修补。您想要做的是修改开头的两字节 nop 以跳回 6 字节 nop block ,然后可以跳转到您的回调包装器,然后调用您的回调,然后跳回正确的函数。要实现它,请将其添加到 C++ 源文件中:

void pages_allow_all_access(void* range_begin, size_t range_size) {
DWORD new_settings = PAGE_EXECUTE_READWRITE;
DWORD old_settings;
VirtualProtect(
range_begin,
range_size,
new_settings,
&old_settings
);
}

void patch(void* patch_func, void* callback_wrapper) {
char* patch_func_bytes = (char*)patch_func;
char* callback_wrapper_bytes = (char*)callback_wrapper;

pages_allow_all_access(patch_func_bytes - 6, 8);

// jmp short -5 (to the jmp rel32 instruction)
patch_func_bytes[0] = 0xEB;
patch_func_bytes[1] = 0x100 - 0x7;
// nop (probably won't be executed)
patch_func_bytes[-6] = 0x90;
// jmp rel32 to callback_wrapper
patch_func_bytes[-5] = 0xE9;
*(int32_t*)&patch_func_bytes[-4]
= (int32_t)(callback_wrapper_bytes - patch_func_bytes);
}
回调包装器可能需要在程序集文件中定义:
callback_wrapper:
; save registers
pushad
pushfd
call QWORD PTR [callback]
popfd
popad
jmp QWORD PTR [after_trampoline]
符号回调和 after_trampoline 应该在 C++ 文件中公开(因此在全局范围内)。
void* callback = &callback_func;
void* after_trampoline = (char*)&patch_func + 2;
然后调用 patch在 main 或其他一些合适的初始化时间的顶部,你就设置好了。
此外,您可能必须使用 VirtualProtect 调用允许对正在修改的内存页面(patch_func 所在的内存页面)的写入权限: https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualprotect .编辑:我已将此代码添加到上面的示例中。
稍后我可能会在 Linux 或其他 Unixy 系统上添加执行此操作的方法。
当您没有一组方便的 NOP 可在函数中使用时, Hook 变得更加困难,尤其是在 x86 架构上,因为指令的长度变化很大,因此很难以编程方式找出指令在哪里结束以便您可以跳转回到下一条指令。 @ajm 建议使用这个库: https://github.com/kubo/funchook适用于 Linux 和 OSX。但是,就我个人而言,当我没有热补丁时,我通常会使用调试器在补丁目标中找出长度至少为 9 个字节的指令序列,我可以替换它们。然后在程序中,我使用与上述类似的技术,将这些指令替换为跳转到绝对立即数 64 位,但我还将这些替换指令添加到回调包装器末尾附近执行。避免替换 call 或 jmp 指令,因为它们通常与指令指针相关,在回调包装器中的值与在原始函数中的值不同。

关于c++ - 重新实现第 3 方非虚拟功能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64449856/

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