microsoft / STL

MSVC's implementation of the C++ Standard Library.
Other
9.89k stars 1.45k forks source link

`to_string( stacktrace )` gets stuck if first called during DLL loading #4675

Closed Fedr closed 1 month ago

Fedr commented 1 month ago

Describe the bug

If to_string( std::stacktrace::current() ) first called during DLL loading, then the application can hang forever.

Test case

Put a global variable in your DLL calling to_string( std::stacktrace::current() ) in its constructor:

struct A {
    A() {
        (void)to_string( std::stacktrace::current() );
    }
} a_;

then during loading of this DLL by means of LoadLibraryW, for example, on Windows 11 the program hangs with the stack as follows:

ntdll.dll!NtWaitForSingleObject()
KernelBase.dll!WaitForSingleObjectEx()
winnsi.dll!NsiRpcRegisterChangeNotificationEx()
winnsi.dll!NsiRpcRegisterChangeNotification()
IPHLPAPI.DLL!InternalRegisterChangeNotification()
IPHLPAPI.DLL!NotifyIpInterfaceChange()
cryptnet.dll!I_CryptNetGetConnectivity()
crypt32.dll!ChainGetConnectivity(void)
crypt32.dll!CChainCallContext::IsConnected(void)
crypt32.dll!CCertChainEngine::TriggerPreFetch()
crypt32.dll!CCertChainEngine::GetChainContext()
crypt32.dll!CertGetCertificateChain()
wintrust.dll!_WalkChain()
wintrust.dll!WintrustCertificateTrust()
wintrust.dll!I_VerifyTrust()
wintrust.dll!WinVerifyTrust()
dbgeng.dll!DbgWinVerifyTrust(struct HWND__ *,struct _GUID *,void *)
dbgeng.dll!VerifyFileSignature(unsigned short const *)
dbgeng.dll!ExtensionSecurityValidate()
dbgeng.dll!ExtensionInfo::Load(class DebugClient *,class TargetInfo *,unsigned short const *,bool)
dbgeng.dll!TargetInfo::AddSpecificExtensions(class DebugClient *)
dbgeng.dll!NotifyDebuggeeActivation(void)
dbgeng.dll!LiveUserTargetInfo::WaitForEvent(unsigned long,unsigned long,unsigned long,unsigned long *)
dbgeng.dll!WaitForAnyTarget(unsigned long,unsigned long,unsigned long,unsigned long,unsigned long *)
dbgeng.dll!RawWaitForEvent(class DebugClient *,unsigned long,unsigned long)
dbgeng.dll!DebugClient::WaitForEvent()
MyDLL.dll!`anonymous namespace'::dbg_eng_data::try_initialize() Line 113    C++
MyDLL.dll!__std_stacktrace_to_string(const void * const * const _Addresses, const unsigned __int64 _Size, void * const _Str, unsigned __int64(*)(unsigned __int64, void *, void *, unsigned __int64(*)(char *, unsigned __int64, void *) noexcept) _Fill) Line 306  C++
MyDLL.dll!std::to_string(const std::basic_stacktrace<std::allocator<std::stacktrace_entry>> &) Line 343 C++
MyDLL.dll!A::{ctor}() Line 265  C++
MyDLL.dll!`dynamic initializer for 'a_''() Line 267 C++
ucrtbase.dll!_initterm()
MyDLL.dll!dllmain_crt_process_attach(HINSTANCE__ * const instance, void * const reserved) Line 66   C++
...

Expected behavior

I would expect that to_string( stacktrace ) will return an empty string or an error, but does not get stuck.

STL version

Visual Studio 2022 version 17.9.2

Additional context

The problem is that I try logging stacktraces in my exception handler, which can be invoked during DLL loading as well (and global object is just to simplify demonstration). It is extremely inconvenient that I must track by myself the moments when stacktrace can be used and when it is not because of possible hangs.

AlexGuteniev commented 1 month ago

Recreated.

I happen to contribute the <stacktrace> feature, and I have no clear idea what is going on and how to fix it.

Generally, arbitrary API's are not supported in DllMain because of the loader lock problem. See Dynamic-Link Library Best Practices.

However, I don't see this hang as an occurrence of the loader lock problem, it looks like something else.

Also, I don't see a documented way how to detect the problematic situation. A call from DllMain can be detected by querying loader lock state. But there's no documented way to query the loader lock state. Also, it is not clear ow strictly this problem is connected with DllMain.

It is possible that switching the implementation from DbgEng.dll to DbgHelp.dll can fix the problem, because on <stacktrace> implementation side it hangs in this place, which would not be needed in case of DbgHelp.dll usage:

https://github.com/microsoft/STL/blob/8dc4faadafb52e3e0a627e046b41258032d9bc6a/stl/src/stacktrace.cpp#L101-L103

Maybe it can be seen better from maintainers' side with Windows source available.

StephanTLavavej commented 1 month ago

This looks by design to me - even if the loader lock specifically isn't the cause, calling STL functions during DLL attach is fraught with peril because of the loader lock, so I don't think we can support it in general. I'm not a DLL expert though, so I'll leave this issue open for now.

StephanTLavavej commented 1 month ago

We talked about this at the weekly maintainer meeting and concluded that this is by design - we can't support stringizing stacktraces during DLL attach.

AlexGuteniev commented 4 days ago

I've reported this as one of DevCom-10692305 issues.

I think we should attempt to support this scenario if underlying API can allow it.