Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

warning C4747: Calling managed 'DllMain': Managed code may not be run under loader lock

/clr 옵션을 추가한 프로젝트를 만들고 이전 오류를 해결했더니,

error C7681: two-phase name lookup is not supported for C++/CLI or C++/CX; use /Zc:twoPhase-
; https://www.sysnet.pe.kr/2/0/13193

그래도 여전히 빌드하면 이런 경고가 발생합니다.

warning C4747: Calling managed 'DllMain': Managed code may not be run under loader lock, including the DLL entrypoint and calls reached from the DLL entrypoint

말이 경고지, 실제 이 상태로 빌드한 DLL을 .NET Framework C# 프로젝트에서 참조해 실행하면 Visual Studio의 디버깅 화면에서는 다음과 같은 MDA 예외를 보게 됩니다.

mc_loader_lock_1.png

Message=Managed Debugging Assistant 'LoaderLock' : 'DLL 'C:\temp\ConsoleApplication1\ConsoleApp1\bin\x64\Debug\Dll1.dll' is attempting managed execution inside OS Loader lock. Do not attempt to run managed code inside a DllMain or image initialization function since doing so can cause the application to hang.'


그러니까, DllMain 내에서 (/clr 옵션을 추가했으니) 혹시나 관리 코드를 실행한다면 응용 프로그램이 먹통이 될 수 있다는 내용입니다. 하지만, 우리가 만든 예제 코드는 DllMain에 아무런 코드도 추가하지 않았기 때문에 문제가 없는데요, 따라서 경고로 알고 지나갈 수 있게 해야 하는데 아예 저렇게 MDA 예외를 발생해 버리는 것입니다.

원래, MDA는 권고 수준의 예외라서 무시하고 지나갈 수 있는 경우가 있긴 한데요, 하지만 LoaderLock 종류의 예외는 그럴 수 없습니다. 만약 (무시하려고 위의 창에서) "Break when this exception type is thrown"을 해제하거나, 그냥 Visual Studio 디버그 환경이 아닌 상태로 직접 실행하면 다음과 같이 비정상 종료가 발생합니다.

C:\temp\ConsoleApplication1\ConsoleApp1\bin\x64\Debug> ConsoleApp1.exe

Unhandled Exception: System.IO.FileLoadException: Could not load file or assembly 'Dll1.dll' or one of its dependencies. A dynamic link library (DLL) initialization routine failed. (Exception from HRESULT: 0x8007045A)
   at ConsoleApp1.Program.Main(String[] args)




이에 대한 해결책은 문서에서 언급하고 있는데요,

Compiler Warning (level 1) C4747
; https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-1-c4747?view=msvc-170

managed and unmanaged pragma
; https://learn.microsoft.com/en-us/cpp/preprocessor/managed-unmanaged

따라서 그냥 DllMain에 다음과 같이 "#pragma unmanaged"를 적용하면 됩니다.

// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"

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

이렇게 해주면, 해당 DLL을 Native 환경에서 로드했을 때는 DllMain을 호출하지만, Managed 환경에서 로드하는 경우에는 DllMain을 무시합니다.




참고로, MDA LoaderLock은 "Debug" / "Windows" / "Exception Settings... (Ctrl + Alt + E)"을 열어,

mc_loader_lock_3.png

"Managed Debugging Assistant" 범주의 "LoaderLock"으로 예외 처리 여부를 조정할 수 있습니다. 닷넷 프로젝트인 경우, 이 옵션을 끄고, "Enable native code debugging" 옵션을 켠 상태에서 디버깅을 시작하면 다음과 같이 상세한 에러 정보를 얻을 수 있는데요,

mc_loader_lock_2.png

Unhandled exception at 0x00007FFBBF5703EC (KernelBase.dll) in ConsoleApp1.exe: 0xE0434352 (parameters: 0xFFFFFFFF80131016, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x00007FFBA7920000).


해당 파일의 경로는 이렇고,

C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.34.31933\crt\src\vcruntime\dll_dllmain.cpp

DllMain 라인에 BP를 걸고 다시 디버깅을 시도하면 (다른 DLL 로드에 의해 몇 번의 BP가 지난 후, 우리가 만든 dll이 로드될 때) 아래의 호출 스택 상태에서 BP가 멈춥니다.

static BOOL __cdecl dllmain_dispatch(
    HINSTANCE const instance,
    DWORD     const reason,
    LPVOID    const reserved
    )
{
    ...[생략]...

        if (reason == DLL_PROCESS_ATTACH || reason == DLL_THREAD_ATTACH)
        {
            result = dllmain_raw(instance, reason, reserved);
            if (!result)
                __leave;

            result = dllmain_crt_dispatch(instance, reason, reserved);
            if (!result)
                __leave;
        }

        // 여기에 BP를 설정
        result = DllMain(instance, reason, reserved);

    ...[생략]...


    return result;
}

>    Dll1.dll!dllmain_dispatch(HINSTANCE__ * const instance, const unsigned long reason, void * const reserved) Line 281 C++ Symbols loaded.
    Dll1.dll!_DllMainCRTStartup(HINSTANCE__ * const instance, const unsigned long reason, void * const reserved) Line 335   C++ Symbols loaded.
    mscoreei.dll!_CorDllMain() Unknown Symbols loaded.
    mscoree.dll!ShellShim__CorDllMain()    Unknown Symbols loaded.
    mscoree.dll!_CorDllMain_Exported() Unknown Symbols loaded.
    ntdll.dll!LdrpCallInitRoutine() Unknown Symbols loaded.
    ntdll.dll!LdrpInitializeNode()  Unknown Symbols loaded.
    ntdll.dll!LdrpInitializeGraphRecurse()  Unknown Symbols loaded.
    ntdll.dll!LdrpPrepareModuleForExecution()   Unknown Symbols loaded.
    ntdll.dll!LdrpLoadDllInternal() Unknown Symbols loaded.
    ntdll.dll!LdrpLoadDll()    Unknown Symbols loaded.
    ntdll.dll!LdrLoadDll()  Unknown Symbols loaded.
    KernelBase.dll!LoadLibraryExW() Unknown Symbols loaded.
    clrjit.dll!Compiler::lvaInitTypeRef() Line 262  C++ Symbols loaded.
    clrjit.dll!Compiler::compCompileHelper(CORINFO_MODULE_STRUCT_ * classPtr, ICorJitInfo * compHnd, CORINFO_METHOD_INFO * methodInfo, void * * methodCodePtr, unsigned long * methodCodeSize, JitFlags * compileFlags, CorInfoInstantiationVerification) Line 6161 C++ Symbols loaded.
    clrjit.dll!Compiler::compCompile(CORINFO_METHOD_STRUCT_ * methodHnd, CORINFO_MODULE_STRUCT_ * classPtr, ICorJitInfo * compHnd, CORINFO_METHOD_INFO * methodInfo, void * * methodCodePtr, unsigned long * methodCodeSize, JitFlags * compileFlags) Line 5644 C++ Symbols loaded.
    clrjit.dll!jitNativeCode(CORINFO_METHOD_STRUCT_ * methodHnd, CORINFO_MODULE_STRUCT_ * classPtr, ICorJitInfo * compHnd, CORINFO_METHOD_INFO * methodInfo, void * * methodCodePtr, unsigned long * methodCodeSize, JitFlags * compileFlags, void * inlineInfoPtr) Line 6953   C++ Symbols loaded.
    clrjit.dll!CILJit::compileMethod(ICorJitInfo * compHnd, CORINFO_METHOD_INFO * methodInfo, unsigned int flags, unsigned char * * entryAddress, unsigned long * nativeSizeOfCode) Line 312    C++ Symbols loaded.
    mscoreei.dll!_CorExeMain() Unknown Symbols loaded.
    mscoree.dll!_CorExeMain_Exported() Unknown Symbols loaded.
    kernel32.dll!BaseThreadInitThunk()  Unknown Symbols loaded.
    ntdll.dll!RtlUserThreadStart() Unknown Symbols loaded.

재미있는 건, 이 상태에서 DllMain 진입점에 BP를 걸고 F10/F11로 진행하려고 시도하면 진입도 안 하고 예외가 발생한다는 점입니다. 사실 _DllMainCRTStartup, dllmain_dispatch이 Native 함수이고 DllMain도 Native이기 때문에 저 호출 과정에서 예외가 발생한다는 것이 이해가 안 됩니다.

하지만, 이때의 DllMain 호출 주소를 자세하게 확인해 보면,

mc_loader_lock_4.png

나온 주소(0x00007ffa8d3c5000)에는 jmp 문이 있고 연이어 jmp로 이어집니다.

00007FFA8D3C5000  jmp         DllMain+0Ah (07FFA8D3C500Ah)  
...
00007FFA8D3C500A  jmp         qword ptr [__mep@?DllMain@@$$HYAHPEAUHINSTANCE__@@KPEAX@Z (07FFA8D3D2008h)]  
...

/* undname으로 풀어보면,
c:\temp> undname ?DllMain@@$$HYAHPEAUHINSTANCE__@@KPEAX@Z
Microsoft (R) C++ Name Undecorator
Copyright (C) Microsoft Corporation. All rights reserved.

Undecoration of :- "?DllMain@@$$HYAHPEAUHINSTANCE__@@KPEAX@Z"
is :- "int __cdecl DllMain(struct HINSTANCE__ * __ptr64,unsigned long,void * __ptr64)"
*/

이름에서 느낌이 오겠지만 일반적인 Native DllMain이 아니고, /clr 옵션에 의해 관리 모듈 처리가 된 코드를 우선 경유하도록 처리가 된 듯합니다. 실제로 disassembly 창을 이용해 디버깅을 진행하면,

00007FFBA906EE60 48 83 EC 68          sub         rsp,68h  
00007FFBA906EE64 66 0F 7F 44 24 20    movdqa      xmmword ptr [rsp+20h],xmm0  
00007FFBA906EE6A 66 0F 7F 4C 24 30    movdqa      xmmword ptr [rsp+30h],xmm1  
00007FFBA906EE70 66 0F 7F 54 24 40    movdqa      xmmword ptr [rsp+40h],xmm2  
00007FFBA906EE76 66 0F 7F 5C 24 50    movdqa      xmmword ptr [rsp+50h],xmm3  
00007FFBA906EE7C 48 89 4C 24 70       mov         qword ptr [rsp+70h],rcx  
00007FFBA906EE81 48 89 54 24 78       mov         qword ptr [rsp+78h],rdx  
00007FFBA906EE86 4C 89 84 24 80 00 00 00 mov         qword ptr [rsp+80h],r8  
00007FFBA906EE8E 4C 89 8C 24 88 00 00 00 mov         qword ptr [rsp+88h],r9  
00007FFBA906EE96 49 8B CA             mov         rcx,r10  
00007FFBA906EE99 E8 A2 78 FE FF       call        VTableBootstrapThunkInitHelper (07FFBA9056740h) 

VTableBootstrapThunkInitHelper 호출로 이어지고 아마도 이것은 예전에 살펴본 VTableFixups 관련한 managed 측의 전처리 단계로 보입니다.

C# - PE 파일로부터 IMAGE_COR20_HEADER 및 VTableFixups 테이블 분석
; https://www.sysnet.pe.kr/2/0/12126

그러니까, 우리가 만든 DllMain에는 관리 코드가 없지만, /clr 옵션에 의해 강제로 바뀐 진입점으로 인해 말 그대로 "Do not attempt to run managed code inside a DllMain or image initialization function since doing so can cause the application to hang."의 MDA 경고를 어긴 상황이 된 것입니다.

따라서 C4747 경고는 차라리 오류라고 여기는 것이 속편할 것입니다. ^^

(첨부 파일은 이 글의 예제 환경을 포함합니다.)




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 12/15/2022]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 46  47  48  49  50  51  52  [53]  54  55  56  57  58  59  60  ...
NoWriterDateCnt.TitleFile(s)
12325정성태9/12/20209543개발 환경 구성: 515. OpenVPN - 재부팅 후 ICS(Internet Connection Sharing) 기능이 동작 안하는 문제
12324정성태9/11/202010827개발 환경 구성: 514. smigdeploy.exe를 이용한 Windows Server 2016에서 2019로 마이그레이션 방법
12323정성태9/11/20209751오류 유형: 649. Copy Database Wizard - The job failed. Check the event log on the destination server for details.
12322정성태9/11/202010947개발 환경 구성: 513. Azure VM의 RDP 접속 위치 제한 [1]
12321정성태9/11/20208996오류 유형: 648. netsh http add urlacl - Error: 183 Cannot create a file when that file already exists.
12320정성태9/11/202010166개발 환경 구성: 512. RDP(원격 데스크톱) 접속 시 비밀 번호를 한 번 더 입력해야 하는 경우
12319정성태9/10/20209962오류 유형: 647. smigdeploy.exe를 Windows Server 2016에서 실행할 때 .NET Framework 미설치 오류 발생
12318정성태9/9/20209386오류 유형: 646. OpenVPN - "TAP-Windows Adapter V9" 어댑터의 "Network cable unplugged" 현상
12317정성태9/9/202011746개발 환경 구성: 511. Beats용 Kibana 기본 대시 보드 구성 방법
12316정성태9/8/202010133디버깅 기술: 170. WinDbg Preview 버전부터 닷넷 코어 3.0 이후의 메모리 덤프에 대해 sos.dll 자동 로드
12315정성태9/7/202012463개발 환경 구성: 510. Logstash - FileBeat을 이용한 IIS 로그 처리 [2]
12314정성태9/7/202011120오류 유형: 645. IIS HTTPERR - Timer_MinBytesPerSecond, Timer_ConnectionIdle 로그
12313정성태9/6/202012187개발 환경 구성: 509. Logstash - 사용자 정의 grok 패턴 추가를 이용한 IIS 로그 처리
12312정성태9/5/202016055개발 환경 구성: 508. Logstash 기본 사용법 [2]
12311정성태9/4/202011317.NET Framework: 937. C# - 간단하게 만들어 보는 리눅스의 nc(netcat), json_pp 프로그램 [1]
12310정성태9/3/202010586오류 유형: 644. Windows could not start the Elasticsearch 7.9.0 (elasticsearch-service-x64) service on Local Computer.
12309정성태9/3/202010324개발 환경 구성: 507. Elasticsearch 6.6부터 기본 추가된 한글 형태소 분석기 노리(nori) 사용법
12308정성태9/2/202011607개발 환경 구성: 506. Windows - 단일 머신에서 단일 바이너리로 여러 개의 ElasticSearch 노드를 실행하는 방법
12307정성태9/2/202012338오류 유형: 643. curl - json_parse_exception / Invalid UTF-8 start byte
12306정성태9/1/202010490오류 유형: 642. SQL Server 시작 오류 - error code 10013
12305정성태9/1/202011408Windows: 172. "Administered port exclusions"이 아닌 포트 범위 항목을 삭제하는 방법
12304정성태8/31/202010334개발 환경 구성: 505. 윈도우 - (네트워크 어댑터의 우선순위로 인한) 열거되는 IP 주소 순서를 조정하는 방법
12303정성태8/30/202010550개발 환경 구성: 504. ETW - 닷넷 프레임워크 기반의 응용 프로그램을 위한 명령행 도구 etrace 소개
12302정성태8/30/202010437.NET Framework: 936. C# - ETW 관련 Win32 API 사용 예제 코드 (5) - Private Logger파일 다운로드1
12301정성태8/30/202010753오류 유형: 641. error MSB4044: The "Fody.WeavingTask" task was not given a value for the required parameter "IntermediateDir".
12300정성태8/29/202010139.NET Framework: 935. C# - ETW 관련 Win32 API 사용 예제 코드 (4) CLR ETW Consumer파일 다운로드1
... 46  47  48  49  50  51  52  [53]  54  55  56  57  58  59  60  ...