从 DllMain 调用 LoadLibrary

发布于 2024-10-06 11:29:19 字数 749 浏览 4 评论 0原文

MSDN 说:

不得调用 LoadLibrary 或 LoadLibraryEx 函数 (或调用这些函数的函数), 因为这可能会在 DLL 加载顺序中创建依赖循环。 这可能会导致 DLL 在系统执行其初始化代码之前被使用。

我尝试从 DllMain 调用 LoadLibrary,但什么也没发生。

我看到的唯一问题是加载的 DLL 将在 DllMain 的其余部分执行之前使用我的 DLL 中的函数。

为什么我不能在 DllMain 中调用 LoadLibrary?

编辑:

好的,我意识到我不能在 DllMain 中调用 LoadLibrary 只是因为我必须像其他信徒一样相信 MSDN(我看到那里有一些错误的事情,但我也应该忘记它们)。
而且因为较新版本的 Windows 中可能会发生某些情况(尽管过去十年没有任何变化)。

但是任何人都可以显示一段代码,该代码会重现在 DllMain 中调用 LoadLibrary 时会发生什么情况吗?在任何现有的 Windows 操作系统中?
不仅仅是在另一个单例初始化函数中调用一个单例初始化函数,而是在 DllMain 中调用 LoadLibrary?

MSDN says:

It must not call the LoadLibrary or LoadLibraryEx function
(or a function that calls these functions),
because this may create dependency loops in the DLL load order.
This can result in a DLL being used before the system has executed its initialization code.

I tried to call LoadLibrary from DllMain and nothing happened.

The only issue that I see is that loaded DLL will use functions in my DLL before rest of my DllMain executes.

Why I must not call LoadLibrary in DllMain?

EDIT:

OK, I realized that I must not call LoadLibrary in DllMain just because I must believe MSDN as other believers do (I saw some wrong things there, but I should forget them too).
And because something may happen in newer versions of Windows (although there nothing was changed for last ten years).

But can anyone show a code which will reproduce something bad what happens when LoadLibrary is called in DllMain? In any existing Windows OS?
Not just a call of one singleton initialization function inside another, but LoadLibrary in DllMain?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

时光无声 2024-10-13 11:29:19

在一些简单甚至不那么简单的情况下,从 DllMain 调用 LoadLibrary 是完全安全的。但设计是DllMain 被信任不会更改加载模块的列表。

尽管拥有加载器锁确实限制了 DllMain 中可以执行的操作,但它仅与 LoadLibrary 规则间接相关。加载器锁的相关目的是串行访问已加载模块的列表。当 NTDLL 在一个线程中处理此列表时,拥有加载程序锁可确保该列表不会被在另一线程中执行的 NTDLL 代码更改。然而,装载机锁是一个关键部分。它不会阻止同一线程重新获取加载程序锁并更改列表。

如果 NTDLL 在处理列表时完全保持自己的状态,这并不重要。然而,NTDLL 允许在这项工作中涉及其他代码,例如初始化新加载的 DLL 时。每次 NTDLL 在处理列表时调用自身外部时,都会为设计做出选择。一般来说,有两种选择。一种是稳定列表并释放加载器锁,调用外部,然后获取加载器锁并恢复列表上的工作,就像从头开始一样,因为外部调用可能已经更改了它。另一种是保持加载程序锁定并相信被调用的代码不会执行任何更改列表的操作。因此,LoadLibrary 在 DllMain 中成为禁区。

这并不是说加载程序锁会执行任何操作来阻止 DllMain 调用 LoadLibrary,甚至加载程序锁本身也会使此类调用变得不安全。相反,通过保留加载程序锁,NTDLL 信任 DllMain 不会调用 LoadLibrary。

作为对比,请考虑有关不等待同步对象的 DllMain 规则。在这里,装载机锁直接导致了这种不安全。在 DllMain 中等待同步对象可能会导致死锁。所需要的只是另一个线程已经持有您正在等待的对象,然后这个另一个线程调用任何等待加载器锁的函数(例如,LoadLibrary,还有看似无害的 GetModuleHandle 等函数)。

想要扩展或破坏 DllMain 规则可能是恶作剧,甚至是彻头彻尾的愚蠢。然而,我必须指出,对于人们询问这些规则有多强大或多有意义,微软至少负有部分责任。毕竟,有些内容并不总是被清晰而有力地记录下来,当我最后一次查看时,它们仍然没有在所有确实需要它们的情况下被记录下来。 (我想到的例外是,至少在 Visual Studio 2005 之前,编写 DLL 的 MFC 程序员被告知将其初始化代码放在 CWinApp::InitInstance 中,但没有被告知此代码受 DllMain 规则的约束。)

此外,它对于来自 Microsoft 的任何人来说,如果说 DllMain 规则应该毫无疑问地被遵守,那就太丰富了。微软自己的程序员违反了规则,甚至在违反规则被认为造成了严重的现实问题后仍然继续这样做。

There are simple, and even not so simple, circumstances in which calling LoadLibrary from DllMain is perfectly safe. But the design is that DllMain is trusted not to change the list of loaded modules.

Though possession of the loader lock does indeed constrain what can be done in DllMain, it is only indirectly relevant to the LoadLibrary rule. The relevant purpose of the loader lock is serialise access to the list of loaded modules. While NTDLL works on this list in one thread, possession of the loader lock ensures that the list won't be changed by NTDLL code that's executing in another thread. However, the loader lock is a critical section. It does nothing to stop the same thread from re-acquiring the loader lock and changing the list.

This would not matter if NTDLL kept entirely to itself while working on the list. However, NTDLL provides for involving other code in this work, as when initialising a newly loaded DLL. Each time NTDLL calls outside itself while working on the list, there is a choice to make for the design. Broadly, there are two options. One is to stabilise the list and release the loader lock, call outside, then acquire the loader lock and resume work on the list as if from scratch because the outside call may have changed it. The other is to keep the loader lock and trust the called code not to do anything that changes the list. And thus does LoadLibrary become off-limits in DllMain.

It's not that the loader lock does anything to stop DllMain from calling LoadLibrary or even that the loader lock itself makes such a call unsafe. It is instead that by retaining the loader lock, NTDLL trusts DllMain not to call LoadLibrary.

For contrast, consider the DllMain rule about not waiting on synchronisation objects. Here, the loader lock has a direct role in making this unsafe. Waiting on a synchronisation object in DllMain sets up the possibility of deadlock. All that's needed is that another thread already holds the object you're waiting on, and then this other thread calls any function that would wait on the loader lock (e.g., LoadLibrary but also such functions as the seemingly inocuous GetModuleHandle).

Wanting to stretch or break the DllMain rules may be mischievous or even outright stupid. However, I must point out that Microsoft is at least partly to blame for people asking how strong or meaningful are these rules. After all, some have not always been documented clearly and forcefully, and when last I looked they were still not documented in all the situations where they're surely needed. (The exception I have in mind is that at least until Visual Studio 2005, MFC programmers writing DLLs were told to put their initialisation code in CWinApp::InitInstance but were not told that this code is subject to the DllMain rules.)

Moreover, it would be a bit rich for anyone from Microsoft to speak as if the DllMain rules ought be followed without question. Examples exist where Microsoft's own programmers break the rules, and continue to even after breaking the rules is seen to have caused serious real-world trouble.

执手闯天涯 2024-10-13 11:29:19

你支持继续这样做的论点似乎是,解释一下:

微软说不要这样做,但我的
单个测试用例似乎有效,因此我不明白为什么没有人应该这样做。

您在一个很大的假设下进行操作:您假设 Windows 加载程序的底层实现永远不会改变。如果“Windows 8”中的加载程序发生更改,导致您的代码不再正常工作怎么办?现在微软因此受到指责,他们必须包含另一个兼容性黑客来解决他们告诉不要在第一个中编写的代码地方。

遵循指南。它们的存在不仅仅是为了让您的生活变得更加困难,它们的存在是为了保证您的代码在未来的 Windows 上能够像现在一样运行良好。

Your argument in favor of going ahead with this seems to be, to paraphrase:

Microsoft says don't do this, but my
single test case seems to work, therefore I fail to see why nobody should be doing this.

You're operating under a big assumption: you're assuming that the underlying implementation of the Windows loader will never change. What if the loader is changed in "Windows 8" in a way such that your code no longer works properly? Now Microsoft gets blamed for it and they have to include yet another compatibility hack to work around code that they told you not to write in the first place.

Follow the guidelines. They're not there just to make your life more difficult, they're there to guarantee that your code will work just as well on the Windows of the future as it does now.

叹梦 2024-10-13 11:29:19

http://msdn.microsoft.com/ 中所述en-us/library/ms682583%28VS.85%29.aspx

DllMain 中的线程持有加载程序锁,因此无法动态加载或初始化其他 DLL。

干杯

As stated in http://msdn.microsoft.com/en-us/library/ms682583%28VS.85%29.aspx:

Threads in DllMain hold the loader lock so no additional DLLs can be dynamically loaded or initialized.

Cheers

好久不见√ 2024-10-13 11:29:19

我正在研究一个可能需要在 DllMain 中使用 LoadLibrary 的案例,因此在调查时发现了此讨论。根据我今天的经验对此进行更新

阅读此内容可能会变得非常可怕 http://blogs.msdn.com/b/oleglv/archive/2003/10/28/56142.aspx。不仅各种锁很重要,库传递给链接器的顺序也很重要。情况是这样的

,我已经在win7下用vc9尝试过了。是的,也是如此。根据库传递给链接器的顺序,使用 LoadLibrary 是否有效。但是,无论链接顺序如何,win8 下的 vc11 都可以正常工作。应用程序验证者不会对此进行指责。

我不要求现在和任何地方都以这种方式使用它:)但仅供参考,如果它与 win10 及更高版本相同 - 这可能会有更多用处。无论如何,win8下的loader机制似乎发生了一些明显的变化。

谢谢。

I was working on a case that could require using LoadLibrary in DllMain, so while investigating found this discussion. An update on this from my todays experience

Reading this one can get really scary http://blogs.msdn.com/b/oleglv/archive/2003/10/28/56142.aspx . Not only various locks matter, but also the order in which the libs was passed to the linker. The case is say one bi

Now, I've tried this with vc9 under win7. Yes, so is it. Depending on the order of how the libs are passed to the linker, using LoadLibrary works or not. However, the same with vc11 under win8 works properly disregarding the link order. Application Verifier doesn't blame about that.

I'm not calling to use it this way right now and everywhere :) But just FYI, if it's the same with win10 and further - this might have more usefulness. Anyway seems that the loader mechanism under win8 undergone some noticeable changes.

Thanks.

情深已缘浅 2024-10-13 11:29:19

虽然已经很晚了,但

如果在线程 1 (T1) 上您的 DllMain 加载其他库,那么其他库的 DllMain 将被调用;这本身是可以的,但是假设他们的 DLLMain 创建了一个线程 (T2) 并等待 T2 完成的事件。

现在,如果 T2 在其处理中加载库,加载器将无法获取锁,因为 T1 已经获取了它。由于 T2 挂在 LoaderLock 上,因此它永远不会发出事件 T1 正在等待的信号。

这将导致僵局。

可能会有更多这样的场景,我想这里的广泛推理是我们无法确定哪些代码将在其他库中运行,所以不这样做是一个好主意(变成了最佳实践)。

It is extremely late but still,

If on thread 1 (T1) you DllMain loads other libraries, those other lib's DllMain will be called; which in itself is okay but say their DLLMain creates a thread (T2) and waits on an event for T2 to finish.

Now if T2 loads a library in its processing, loader will not be able to acquire the lock as T1 has already acquired it. As T2 is hung on LoaderLock it will never signal the event T1 is waiting on.

Which will result in a deadlock.

There could be more such scenario, I guess the broad reasoning here is that we can not be sure of what code will run in other libraries, so it is a good idea (turned best practice), to not do it.

◇流星雨 2024-10-13 11:29:19

以下是如何在 Windows 8 / Server 2012 及更高版本中重现加载程序锁定挂起。请注意,此代码不是直接调用加载库,而是使用触发加载库调用的 Windows API。

创建一个 Visual Studio C++ DLL 项目并在 DLL main 中使用此代码:

#define WIN32_LEAN_AND_MEAN

#include "framework.h"

#include <windows.h>
#include <winsock2.h>
#include <iphlpapi.h>
#include <ws2tcpip.h>
#include <stdio.h>
#pragma comment(lib, "IPHLPAPI.lib")

#define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x))
#define FREE(x) HeapFree(GetProcessHeap(), 0, (x))
// Need to link with Ws2_32.lib
#pragma comment(lib, "ws2_32.lib")

BOOL APIENTRY DllMain(HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved
)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        WORD wVersionRequested;
        WSADATA wsaData;
        int err;

        /* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
        wVersionRequested = MAKEWORD(2, 2);

        err = WSAStartup(wVersionRequested, &wsaData);
        if (err != 0) {
            printf("WSAStartup failed with error: %d\n", err);
            return 1;
        }
    
        if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
            printf("Could not find a usable version of Winsock.dll\n");
            WSACleanup();
            return 1;
        }
        else
            printf("The Winsock 2.2 dll was found okay\n");
    
        
        FIXED_INFO* pFixedInfo;
        ULONG ulOutBufLen;
        DWORD dwRetVal;
        IP_ADDR_STRING* pIPAddr;

        pFixedInfo = (FIXED_INFO*)MALLOC(sizeof(FIXED_INFO));
        if (pFixedInfo == NULL) {
            printf("Error allocating memory needed to call GetNetworkParams\n");
            return 1;
        }
        ulOutBufLen = sizeof(FIXED_INFO);

        // Make an initial call to GetAdaptersInfo to get
        // the necessary size into the ulOutBufLen variable
        if (GetNetworkParams(pFixedInfo, &ulOutBufLen) == ERROR_BUFFER_OVERFLOW) {
            FREE(pFixedInfo);
            pFixedInfo = (FIXED_INFO*)MALLOC(ulOutBufLen);
            if (pFixedInfo == NULL) {
                printf("Error allocating memory needed to call GetNetworkParams\n");
                return 1;
            }
        }

        if (dwRetVal = GetNetworkParams(pFixedInfo, &ulOutBufLen) == NO_ERROR) {

            printf("Host Name: %s\n", pFixedInfo->HostName);
            printf("Domain Name: %s\n", pFixedInfo->DomainName);

            printf("DNS Servers:\n");
            printf("\t%s\n", pFixedInfo->DnsServerList.IpAddress.String);

            pIPAddr = pFixedInfo->DnsServerList.Next;
            while (pIPAddr) {
                printf("\t%s\n", pIPAddr->IpAddress.String);
                pIPAddr = pIPAddr->Next;
            }

            printf("Node Type: ");
            switch (pFixedInfo->NodeType) {
            case BROADCAST_NODETYPE:
                printf("Broadcast node\n");
                break;
            case PEER_TO_PEER_NODETYPE:
                printf("Peer to Peer node\n");
                break;
            case MIXED_NODETYPE:
                printf("Mixed node\n");
                break;
            case HYBRID_NODETYPE:
                printf("Hybrid node\n");
                break;
            default:
                printf("Unknown node type %0lx\n", pFixedInfo->NodeType);
                break;
            }

            printf("DHCP scope name: %s\n", pFixedInfo->ScopeId);

            if (pFixedInfo->EnableRouting)
                printf("Routing: enabled\n");
            else
                printf("Routing: disabled\n");

            if (pFixedInfo->EnableProxy)
                printf("ARP proxy: enabled\n");
            else
                printf("ARP Proxy: disabled\n");

            if (pFixedInfo->EnableDns)
                printf("DNS: enabled\n");
            else
                printf("DNS: disabled\n");

        }
        else {
            printf("GetNetworkParams failed with error: %d\n", dwRetVal);
            return 1;
        }

        if (pFixedInfo)
            FREE(pFixedInfo);
        //WSACleanup();
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

从第二个应用程序(尚未导入任何网络 API 或调用任何网络函数)创建一个包含以下代码的控制台桌面 C++ 应用程序:

HMODULE hModule;
hModule = LoadLibrary(L"<specify DLL created in previous example>"); // application will hang here

Here is how to reproduce a loader lock hang in Windows 8 / Server 2012 and later. Note this code is not directly calling load library but uses Windows APIs that trigger Load Library calls.

Create a Visual Studio C++ DLL project and use this code in DLL main:

#define WIN32_LEAN_AND_MEAN

#include "framework.h"

#include <windows.h>
#include <winsock2.h>
#include <iphlpapi.h>
#include <ws2tcpip.h>
#include <stdio.h>
#pragma comment(lib, "IPHLPAPI.lib")

#define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x))
#define FREE(x) HeapFree(GetProcessHeap(), 0, (x))
// Need to link with Ws2_32.lib
#pragma comment(lib, "ws2_32.lib")

BOOL APIENTRY DllMain(HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved
)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        WORD wVersionRequested;
        WSADATA wsaData;
        int err;

        /* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
        wVersionRequested = MAKEWORD(2, 2);

        err = WSAStartup(wVersionRequested, &wsaData);
        if (err != 0) {
            printf("WSAStartup failed with error: %d\n", err);
            return 1;
        }
    
        if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
            printf("Could not find a usable version of Winsock.dll\n");
            WSACleanup();
            return 1;
        }
        else
            printf("The Winsock 2.2 dll was found okay\n");
    
        
        FIXED_INFO* pFixedInfo;
        ULONG ulOutBufLen;
        DWORD dwRetVal;
        IP_ADDR_STRING* pIPAddr;

        pFixedInfo = (FIXED_INFO*)MALLOC(sizeof(FIXED_INFO));
        if (pFixedInfo == NULL) {
            printf("Error allocating memory needed to call GetNetworkParams\n");
            return 1;
        }
        ulOutBufLen = sizeof(FIXED_INFO);

        // Make an initial call to GetAdaptersInfo to get
        // the necessary size into the ulOutBufLen variable
        if (GetNetworkParams(pFixedInfo, &ulOutBufLen) == ERROR_BUFFER_OVERFLOW) {
            FREE(pFixedInfo);
            pFixedInfo = (FIXED_INFO*)MALLOC(ulOutBufLen);
            if (pFixedInfo == NULL) {
                printf("Error allocating memory needed to call GetNetworkParams\n");
                return 1;
            }
        }

        if (dwRetVal = GetNetworkParams(pFixedInfo, &ulOutBufLen) == NO_ERROR) {

            printf("Host Name: %s\n", pFixedInfo->HostName);
            printf("Domain Name: %s\n", pFixedInfo->DomainName);

            printf("DNS Servers:\n");
            printf("\t%s\n", pFixedInfo->DnsServerList.IpAddress.String);

            pIPAddr = pFixedInfo->DnsServerList.Next;
            while (pIPAddr) {
                printf("\t%s\n", pIPAddr->IpAddress.String);
                pIPAddr = pIPAddr->Next;
            }

            printf("Node Type: ");
            switch (pFixedInfo->NodeType) {
            case BROADCAST_NODETYPE:
                printf("Broadcast node\n");
                break;
            case PEER_TO_PEER_NODETYPE:
                printf("Peer to Peer node\n");
                break;
            case MIXED_NODETYPE:
                printf("Mixed node\n");
                break;
            case HYBRID_NODETYPE:
                printf("Hybrid node\n");
                break;
            default:
                printf("Unknown node type %0lx\n", pFixedInfo->NodeType);
                break;
            }

            printf("DHCP scope name: %s\n", pFixedInfo->ScopeId);

            if (pFixedInfo->EnableRouting)
                printf("Routing: enabled\n");
            else
                printf("Routing: disabled\n");

            if (pFixedInfo->EnableProxy)
                printf("ARP proxy: enabled\n");
            else
                printf("ARP Proxy: disabled\n");

            if (pFixedInfo->EnableDns)
                printf("DNS: enabled\n");
            else
                printf("DNS: disabled\n");

        }
        else {
            printf("GetNetworkParams failed with error: %d\n", dwRetVal);
            return 1;
        }

        if (pFixedInfo)
            FREE(pFixedInfo);
        //WSACleanup();
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

From a 2nd application (That does not import any network APIs or called any network functions yet) create a console desktop C++ application containing the following code:

HMODULE hModule;
hModule = LoadLibrary(L"<specify DLL created in previous example>"); // application will hang here
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文