如何调用从先前执行中保存的函数指针失败?

Wea*_*ish 8 c++ undefined-behavior

我很好奇,如果函数指针可以存储在一个文件中,并在程序退出并重新启动的某个未来时间点使用.例如,我的第一个测试程序看起来像这个伪代码:

void f(){}

typedef void(*Fptr)();

int main() {
    int i;
    cin >> i;
    if (i == 1) {
        std::ofstream out(/**/);
        out << &f;
    }
    else {
        std::ifstream in(/**/);
        Fptr fp;
        in >> fp;
        fp();
    }
}
Run Code Online (Sandbox Code Playgroud)

这只是我想要做的逻辑.我会用输入启动它1,让它退出,然后用输入再次运行它2.不要认为这是我的真实代码,因为我删除了原始测试,因为......

只有在我不更改可执行文件所在的目录时才有效!

将新文件添加到目录(可能是删除文件)并将可执行文件移动到新位置都会导致fp();崩溃.新函数地址将是不同的值.

所以我做了一个新的测试,计算旧函数指针和当前函数地址之间的差异.将该偏移量应用于旧函数指针并调用它会产生正确的函数调用,无论我对目录执行什么操作.

我相信这是UB.但是,像取消引用空指针一样会导致段错误,UB非常一致.

除了用垃圾重写数据,并假设函数没有加载到DLL中,这种方法有多大可能成功?它会以什么方式失效?

Eld*_*Bug 3

正如其他人提到的,这个问题是由“地址空间布局随机化”(ASLR)引起的。这种随机化是针对每个模块(即每个可执行映像)进行的。这意味着,如果所有函数都包含在 .exe 中,则保证它们始终与模块基址具有相同的偏移量。如果某些函数位于 DLL 中,则同样适用,但来自 DLL 模块的基础。相关模块地址保持相同非常重要,否则将无法找到入口点和 DLL 函数。

在 Windows 环境中:

在 Visual Studio(和 MSVC)中,ASLR 默认处于打开状态,但您可以在“链接器 > 高级 > 随机基址”选项中禁用它(命令行中的 /DYNAMICBASE:NO)。通过禁用此选项,函数将始终位于同一地址。

您还可以在运行时确定偏移量。模块基地址可以通过以下方式获得GetModuleHandle()(模块句柄实际上就是基地址)。这样,您就可以使用指针的相对地址。

uintptr_t base_address = (uintptr_t)GetModuleHandle(NULL);

uintptr_t offset = (uintptr_t)&f - base_address;
out << offset;

in >> offset;
fp = (Fptr)(offset + base_address);
fp();
Run Code Online (Sandbox Code Playgroud)