【问题标题】:On windows, why are there different addresses for the same module?在windows上,为什么同一个模块有不同的地址?
【发布时间】:2015-03-20 07:04:11
【问题描述】:

我在 Windows 上读到,可执行文件的模块映射到相同的地址空间。 我不明白为什么

typedef int (__stdcall *fptr)();

int     main(void)
{

    HINSTANCE   h;
    fptr        f;
    std::stringstream oss;

    h = LoadLibrary("test.dll");
    if (! h)
        return EXIT_FAILURE;
    f = (fptr)GetProcAddress(h, "function");
    if (! f)
        return EXIT_FAILURE;

    oss << (DWORD *)f;
    std::cout <<"main: "<< oss << std::endl;

    _getch();
    return EXIT_SUCCESS;
}

extern "C" {
    void __declspec(dllexport) function() {
        return ;
    }
}

int     main(HMODULE m)
{
    std::stringstream oss;  

    oss << (DWORD *)function;

    std::cout << "dll: " << oss << std::endl;
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        main(hModule);
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

产生这个结果:

> "test.exe"
dll: 007BFDB8
main: 0039F944

另外,地址 007BFD88 不能从主进程访问。 为什么这两个地址不同?

【问题讨论】:

  • 由 MSVC++ 链接器实现并在调试版本中使用的增量链接是一种解释。您在 DLL 中获得的地址是跳转到实际函数的 JMP 指令的地址。使用 Release 版本再试一次。
  • 非常感谢,这正是我想要的。你解决了我的问题。
  • @HansPassant 如果完全相同的函数由于某些代码生成/链接约定而具有不同的地址,则 C++ 编译器将不符合标准。
  • 嗯,让我参考描述模块应该如何表现的 C++ 标准。

标签: c++ windows api dll module


【解决方案1】:

基本规则是 DLL总是加载到使用它的进程的地址空间中。所以你的函数指针必须是一样的。

两边地址相同:

我用一个小的变体尝试了你的代码 sn-p。在 DLL 的 main() 中,我使用了:

    std::cout << "f in dll: " << (DWORD *)function << std::endl;

在程序的 main() 中,我使用过:

    std::cout << "f in main: " << (DWORD *)f << std::endl;

一模一样的地址出来了!永远!

你的代码有什么问题?

在你的代码sn-p中,你不直接cout函数地址,而是使用oss

std::stringstream oss;
oss << (DWORD *)function;
std::cout << "f in dll: " << oss << std::endl;  // ??? 

在我的编译器 (MSVC13) 上,我收到 oss 的编译错误。我不知道为什么它对你有用,但我怀疑你打印的是你的字符串流的地址而不是它的内容。由于 oss 是一个局部变量,它在两个函数中都有不同的地址,因此您担心的原因。

试试alternative

    std::cout << "f in dll: " << oss.str() << std::endl;

你会在两边再次得到相同的结果(地址打印出来)。

在 GCC 上,您的代码可以编译,但不会产生您期望的结果,如 online snippet 所示。

补充说明

如果同一个 DLL 函数有不同的地址,这意味着您存在两个不同的进程,每个进程都有自己的地址空间。

当您使用可选的 DllMain() 入口点并在那里调用(非导出的)main() 函数时,它可能会给人一种不同进程的印象,但事实并非如此。

我还想补充一点,函数指针是函数指针。它没有黑魔法。两个完全相同类型的指针,指向完全相同的函数,将具有完全相同的地址。这里是 C++ 标准中的规范参考:

5.10/1:两个相同类型的指针比较相等当且仅当它们都为空,都指向同一个函数,或两者 代表相同的地址。

【讨论】:

  • AFAIK,一旦你引入动态链接,你就超出了 C++ 标准的覆盖范围。特别是,该标准不要求您通过动态链接获得的函数必须与 DLL 中的函数相同。它可以是调用实际函数的代理函数。
  • 如前所述,我进行了测试,它与我在这里记录的代码更正完美配合:两边的地址相同。我请你测试在线sn-p,这突出了op的问题:在他的代码中他根本不打印函数指针,而只打印其本地stringrtream的地址!
  • 这里需要注意 - 如果您无法重现问题,则无法确定原因。请注意,OP 报告说,一旦他从 Debug 切换到 Release,问题就消失了,您的答案无法解释。无论如何,即使您的假设实际上是正确的,也不会改变 C++ 标准不需要这种行为的事实。
  • 我可以重现 op 的奇怪行为,这完全是由于使用提取器的错误引起的。我可以通过纠正他的错误来证明函数指针都具有相同的值。最后是关于标准引用:它适用于整个 c++ 实现,包括编译器和链接器(参见第 1 章)。那么这些确凿的事实@harryjohnston 还有什么不清楚的地方?
  • 好的,我可以确认在 Visual Studio 2010 中,OPs 代码不会打印函数指针的值。进入那条线,它似乎在 ios_base 类上调用 operator void *() - 天知道为什么,但是,那是 C++ 给你的。我仍然认为您对标准要求指针相同是错误的,但是发现输出问题是一个很好的问题,所以无论如何+1。 :-)
【解决方案2】:

地址 007BFD88 不必是从 DLL 导出的函数的地址(GetProcAddress() 返回);它只是一个函数指针。 C/C++ 中的函数指针有一些有趣的属性,请查看:Why do function pointer definitions work with any number of ampersands '&' or asterisks '*'?

【讨论】:

  • 一个非常有趣的链接!但是该示例中所有不同的函数指针都包含完全相同的地址。所以它并没有真正回答这个问题......
  • 这不是地址。它是一个函数指针。现在,GetProcAddress() 返回的确实是一个地址。
  • 它可能是也可能不是地址。从您引用的标准中查看 sn-p:具有相同地址和指向函数之间存在区别。
  • 好的,但不管指针内容的名称如何,5.10/1 中的 c++ 标准状态不是 两个相同类型的指针比较等于 if 和仅当它们都为空,都指向同一个函数,或者都代表同一个地址。 ?
  • 这正是我所指的 sn-p。指向函数与表示地址不同。一个指针表示一个地址(来自 GetProcAddress 的那个),另一个是指向函数的指针(更准确地说,thunk,调试构建的工件)。它们是不同类型的 BTW。
猜你喜欢
  • 1970-01-01
  • 2021-12-18
  • 1970-01-01
  • 2021-10-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多