Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 1개 있습니다.)

coreclr 소스코드로 알아보는 .NET 4.0의 모듈 로딩 함수

(그냥 기록 차원에서 남겨둡니다. ^^)

.NET 4.0 이후부터 DLL을 LoadLibrary로 로딩하지 않는다고 했습니다.

CLR 4.0 환경에서 DLL 모듈의 로드 주소(Base address) 알아내는 방법
; https://www.sysnet.pe.kr/2/0/11325

정말 그럴까요? ^^ 이를 알아보기 위해 coreclr 소스 코드를 이용해 추적해 봤습니다.

우선 단서는 GetHINSTANCE를 역어셈블한 코드에서 시작했습니다.

[SecurityCritical]
public static IntPtr GetHINSTANCE(Module m)
{
    if (m == null)
    {
        throw new ArgumentNullException("m");
    }
    RuntimeModule internalModule = m as RuntimeModule;
    // ...[생략]...

    return GetHINSTANCE(internalModule.GetNativeHandle());
}

[SecurityCritical, SuppressUnmanagedCodeSecurity, SuppressUnmanagedCodeSecurity, DllImport("QCall", CharSet=CharSet.Unicode)]
private static extern IntPtr GetHINSTANCE(RuntimeModule m);

이제부터 coreclr 소스 코드 안으로 들어갑니다.

// https://github.com/dotnet/coreclr/blob/46ab1d132c9ad471d79afa20c188c2f9c85e5f20/src/vm/commodule.cpp
// coreclr / src / vm / commodule.cpp 

HINSTANCE QCALLTYPE COMModule::GetHINSTANCE(QCall::ModuleHandle pModule) 
{ 
    // ...[생략]...
    PEFile *pPEFile = pModule->GetFile(); 
    if (!pPEFile->IsDynamic() && !pPEFile->IsResource()) 
    { 
        hMod = (HMODULE) pModule->GetFile()->GetManagedFileContents(); 
    } 

    // ...[생략]...

    return (HINSTANCE)hMod; 
} 

보는 바와 같이 PEFile 인스턴스의 GetManagedFileContents 함수의 반환 결과로 나옵니다. 그렇다면 DLL은 PEFile에 의해 관리되는 것으로 짐작됩니다.

// https://github.com/dotnet/coreclr/blob/13e7c4368da664a8b50228b1a5ef01a660fbb2dd/src/vm/pefile.inl
// coreclr / src / vm / pefile.inl 

#ifndef DACCESS_COMPILE 
inline const void *PEFile::GetManagedFileContents(COUNT_T *pSize/*=NULL*/) 
{ 
    // ...[생략]...

    LoadLibrary(FALSE); 

    // ...[생략]...

    RETURN GetLoadedIL()->GetBase(); 
} 

inline PTR_PEImageLayout PEFile::GetLoadedIL()  
{ 
    // ...[생략]...
    
    return GetOpenedILimage()->GetLoadedLayout(); 
}; 

LoadLibrary가 눈에 띄는군요. 얼핏 Win32 API의 LoadLibrary가 호출된다고 생각할 수 있지만 유사한 역할을 하는 것에 불과합니다. LoadLibrary의 선언과 정의를 추적해 들어가면,

// https://github.com/dotnet/coreclr/blob/13e7c4368da664a8b50228b1a5ef01a660fbb2dd/src/vm/pefile.h
// coreclr / src / vm / pefile.h 

class PEFile
{
    // ...[생략]...
    static PEFile *Open(PEImage *image);
    PTR_PEImage              m_identity;

    void LoadLibrary(BOOL allowNativeSkip = TRUE);

    PTR_PEImage GetILimage() 
    { 
        // ...[생략]...

    #ifndef DACCESS_COMPILE 
        if (m_openedILimage == NULL && m_identity != NULL) 
        { 
            PEImage* pOpenedILimage; 
            m_identity->Clone(MDInternalImport_Default,&pOpenedILimage); 
            if (InterlockedCompareExchangeT(&m_openedILimage,pOpenedILimage,NULL) != NULL) 
                pOpenedILimage->Release(); 
        } 
    #endif 

        return m_openedILimage; 
    } 

    PEImage *GetOpenedILimage() 
    { 
        // ...[생략]...
        return m_openedILimage; 
    } 

    // ...[생략]...
};

LoadLibrary의 정의는 아래의 pefile.cpp에서 찾아볼 수 있는 데 결국 pefile.h에 정의된 GetILimage 함수를 다시 호출합니다. 따라서 GetILimage 함수의 내용에 따라 결국 이미 저장되어 있던 PTR_PEImage 타입의 m_identity에 DLL 정보가 관리됩니다. m_identity는 다시 아래의 pefile.cpp에 정의된 PEFile 생성자에서 할당되는데, 다시 PEFile 생성자가 호출되는 곳은 PEFile::Open static 함수입니다.

// https://github.com/dotnet/coreclr/blob/46ab1d132c9ad471d79afa20c188c2f9c85e5f20/src/vm/pefile.cpp
// coreclr/src/vm/pefile.cpp 

void PEFile::LoadLibrary(BOOL allowNativeSkip/*=TRUE*/) // if allowNativeSkip==FALSE force IL image load 
{ 
    // ...[생략]...

             if (GetILimage()->IsFile()) 
             { 
 #ifdef PLATFORM_UNIX 
                 if (GetILimage()->IsILOnly()) 
                 { 
                     GetILimage()->Load(); 
                 } 
                 else 
 #endif // PLATFORM_UNIX 
                 { 
                     GetILimage()->LoadFromMapped(); 
                 } 
             } 
             else 
             { 
                 GetILimage()->LoadNoFile(); 
             } 

    // ...[생략]...
}

PEFile::PEFile(PEImage *identity, BOOL fCheckAuthenticodeSignature/*=TRUE*/) :
// ...[생략]...
{
    // ...[생략]...
     if (identity) 
     { 
         identity->AddRef(); 
         m_identity = identity; 
 
         if(identity->IsOpened()) 
         { 
             //already opened, prepopulate 
             identity->AddRef(); 
             m_openedILimage = identity; 
         } 
     } 
    // ...[생략]...
}

PEFile *PEFile::Open(PEImage *image) 
{ 
    // ...[생략]...

    PEFile *pFile = new PEFile(image, FALSE); 

    // ...[생략]...

    RETURN pFile; 
} 

그렇다면, 다시 DLL에 대한 관리가 내부적으로는 PEImage에서 된다는 것을 짐작게 합니다. 그래서 이번에는 PEImage 인스턴스의 생성을 찾아봤습니다.

// https://github.com/dotnet/coreclr/blob/ef8d1522eb15cb0371f31a9392891c942547a91f/src/vm/coreassemblyspec.cpp
// src / vm / coreassemblyspec.cpp

// 이 함수 외에도 coreclr / src / vm / compile.cpp 경로의 CEECompileInfo::LoadAssemblyByPath 함수에서도 PEImage::OpenImage를 사용

STDAPI BinderAcquirePEImage(LPCWSTR   wszAssemblyPath, 
                            PEImage **ppPEImage, 
                            PEImage **ppNativeImage, 
                            BOOL      fExplicitBindToNativeImage) 
{ 
    // ...[생략]...

    EX_TRY 
    { 
        PEImageHolder pImage = NULL; 
        PEImageHolder pNativeImage = NULL; 

        // ...[생략]...
         { 
            pImage = PEImage::OpenImage(wszAssemblyPath, MDInternalImport_Default); 
 
            // Make sure that the IL image can be opened if the native image is not available. 
            hr=pImage->TryOpenFile(); 
           // ...[생략]...
        } 
 

        if (pImage) 
             *ppPEImage = pImage.Extract(); 
 

        // ...[생략]...
     } 

    // ...[생략]...
} 

그런데, PEImageHolder는 뭘까요? 이에 대한 힌트는 peimage.h에서 찾을 수 있습니다.

// https://github.com/dotnet/coreclr/blob/31b5ee0bc7df328fd9187d25dbadebe7c1623d38/src/vm/peimage.h
// coreclr / src / vm / peimage.h

class PEImage 
{
    PTR_PEImageLayout m_pLayouts[IMAGE_COUNT];

    // ...[생략]...
    void  Init(LPCWSTR pPath);
    // ...[생략]...
};

FORCEINLINE void PEImageRelease(PEImage *i) 
{ 
    WRAPPER_NO_CONTRACT; 
    i->Release(); 
} 

typedef Wrapper<PEImage *, DoNothing, PEImageRelease> PEImageHolder;

하지만 본론으로 돌아와서 ^^ PEImage::OpenImage의 코드를 보겠습니다.

// https://github.com/dotnet/coreclr/blob/ef8d1522eb15cb0371f31a9392891c942547a91f/src/vm/peimage.inl
// coreclr / src / vm / peimage.inl 

inline PTR_PEImage PEImage::OpenImage(LPCWSTR pPath, MDInternalImportFlags flags /* = MDInternalImport_Default */) 
{ 
    // ...[생략]...

    PEImageHolder pImage(new PEImage); 
    pImage->Init(pPath); 
    return dac_cast<PTR_PEImage>(pImage.Extract()); 

    // ...[생략]...
} 

inline void  PEImage::Init(LPCWSTR pPath) 
{ 
    // ...[생략]...

    m_path = pPath; 
    m_path.Normalize(); 
    SetModuleFileNameHintForDAC(); 
}

inline PTR_PEImageLayout PEImage::GetLoadedLayout()
{
    // ...[생략]...
    return m_pLayouts[IMAGE_LOADED]; //no addref
}

new PEImage와 함께 Init 메서드를 호출하지만 PEImage::Init은 별다르게 로딩 코드가 없습니다. 다시 BinderAcquirePEImage 함수로 가면 pImage 인스턴스에 대해 TryOpenFile을 호출하는데 아마도 여기서 로딩할 것으로 짐작됩니다.

// https://github.com/dotnet/coreclr/blob/31b5ee0bc7df328fd9187d25dbadebe7c1623d38/src/vm/peimage.cpp
// coreclr / src / vm / peimage.cpp 

HRESULT PEImage::TryOpenFile() 
{ 
    // ...[생략]...
     
    if (m_hFile!=INVALID_HANDLE_VALUE) 
        return S_OK; 
    { 
        ErrorModeHolder mode(SEM_NOOPENFILEERRORBOX|SEM_FAILCRITICALERRORS); 
        m_hFile=WszCreateFile((LPCWSTR) m_path, 
                                            GENERIC_READ, 
                                            FILE_SHARE_READ|FILE_SHARE_DELETE, 
                                            NULL, 
                                            OPEN_EXISTING, 
                                            FILE_ATTRIBUTE_NORMAL, 
                                            NULL); 
    } 

    // ...[생략]...
} 

PTR_PEImage PEImage::LoadImage(HMODULE hMod)
{
    // ...[생략]...

    StackSString path;
    GetPathFromDll(hMod, path);
    PEImageHolder pImage(PEImage::OpenImage(path,(MDInternalImportFlags)(MDInternalImport_CheckLongPath|MDInternalImport_CheckShortPath)));

    if (pImage->HasLoadedLayout())
        RETURN dac_cast<PTR_PEImage>(pImage.Extract());

    SimpleWriteLockHolder lock(pImage->m_pLayoutLock);

    if(pImage->m_pLayouts[IMAGE_LOADED]==NULL)
        pImage->SetLayout(IMAGE_LOADED,PEImageLayout::CreateFromHMODULE(hMod,pImage,WszGetModuleHandle(NULL)!=hMod));

    // ...[생략]...

    RETURN dac_cast<PTR_PEImage>(pImage.Extract());
}

WszCreateFile은 결국 내부적으로 CreateFileW를 호출하게 됩니다. 확인 끝났군요. ^^

// https://github.com/dotnet/coreclr/blob/c440335be80ee0762856d0be6e91ec3ea2f90504/src/inc/winwrap.h
// coreclr / src / inc / winwrap.h 

#define WszCreateFile          CreateFileWrapper


// https://github.com/dotnet/coreclr/blob/52a816d3011f4d03b80b5940dc036b40d701f52d/src/utilcode/longfilepathwrappers.cpp
// coreclr / src / utilcode / longfilepathwrappers.cpp 

HANDLE 
CreateFileWrapper( 
        _In_ LPCWSTR lpFileName, 
        _In_ DWORD dwDesiredAccess, 
        _In_ DWORD dwShareMode, 
        _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, 
        _In_ DWORD dwCreationDisposition, 
        _In_ DWORD dwFlagsAndAttributes, 
        _In_opt_ HANDLE hTemplateFile 
        ) 
{ 
    // ...[생략]...
    ret = CreateFileW(path.GetUnicode(), 
            dwDesiredAccess, 
            dwShareMode, 
            lpSecurityAttributes, 
            dwCreationDisposition, 
            dwFlagsAndAttributes, 
            hTemplateFile); 
    // ...[생략]...
 
    return ret; 
} 




중간에 잠시 살펴봤던 PEImageHolder에 대해 살펴보겠습니다.

typedef Wrapper<PEImage *, DoNothing, PEImageRelease> PEImageHolder;

Wrapper는 단순히 PEImageRelease 인자로 객체 해제 방법을 아는 스마트 포인터 관리 클래스의 일종이라고 보시면 됩니다. 나머지 정의는 다음의 파일에서 각각 찾을 수 있습니다.

// https://github.com/dotnet/coreclr/blob/0ad57d9177ac88aabbf9354b985c5ac192a307e7/src/inc/holder.h

template <typename TYPE, typename BASE, 
          UINT_PTR DEFAULTVALUE = 0, BOOL IS_NULL(TYPE, TYPE) = CompareDefault<TYPE>, HolderStackValidation VALIDATION_TYPE = HSV_ValidateNormalStackReq>  
class BaseWrapper : public BaseHolder<TYPE, BASE, DEFAULTVALUE, IS_NULL, VALIDATION_TYPE> 
{ 
    // ...[생략]...
};

template 
    < 
        typename TYPE, 
        void (*ACQUIREF)(TYPE), 
        void (*RELEASEF)(TYPE), 
        UINT_PTR DEFAULTVALUE = 0, 
        BOOL IS_NULL(TYPE, TYPE) = CompareDefault<TYPE>, 
        HolderStackValidation VALIDATION_TYPE = HSV_ValidateNormalStackReq, 
        // For legacy compat (see EEJitManager::WriterLockHolder), where default ctor 
        // causes ACQUIREF(DEFAULTVALUE), but ACQUIREF ignores the argument and 
        // operates on static or global value instead. 
        bool DEFAULT_CTOR_ACQUIRE = true 
    > 
class Wrapper : public BaseWrapper<TYPE, FunctionBase<TYPE, ACQUIREF, RELEASEF, VALIDATION_TYPE>, 
                                   DEFAULTVALUE, IS_NULL, VALIDATION_TYPE> 
{ 
    typedef BaseWrapper<TYPE, FunctionBase<TYPE, ACQUIREF, RELEASEF, VALIDATION_TYPE>, 
                                   DEFAULTVALUE, IS_NULL, VALIDATION_TYPE> BaseT; 
    // ...[생략]...
};  // Wrapper<> 


template
    <
        typename TYPE,
        typename BASE,
        UINT_PTR DEFAULTVALUE = 0,
        BOOL IS_NULL(TYPE, TYPE) = CompareDefault<TYPE>,
        HolderStackValidation VALIDATION_TYPE = HSV_ValidateNormalStackReq
    >
class BaseHolder : protected BASE
{
    // ...[생략]...

    FORCEINLINE TYPE Extract()
    {
        STATIC_CONTRACT_WRAPPER;
        SuppressRelease();
        return GetValue();
    }

    FORCEINLINE TYPE GetValue()
    {
        STATIC_CONTRACT_LEAF;
        return this->m_value;
    }
};




알아본 김에 모듈의 로딩 주소를 반환하는 과정에서 GetLoadedIL()이 반환하는 PEImage의 GetLoadedLayout 함수의 layout 관련 코드도 살펴봤습니다. 우선 상속 구조는 이렇고,

// https://github.com/dotnet/coreclr/blob/0a8bd5554a736de337ca6c24eba3de3ec0decfd1/src/vm/peimagelayout.h
// coreclr / src / vm / peimagelayout.h 

class PEImageLayout : public PEDecoder 
{ 
    // ...[생략]...
};

class RawImageLayout: public PEImageLayout
{
    // ...[생략]...
};

CreateFromHMODULE에 의해 HMODULE hModule 값을 생성자에서 받아둡니다.

// https://github.com/dotnet/coreclr/blob/0a8bd5554a736de337ca6c24eba3de3ec0decfd1/src/vm/peimagelayout.cpp
// coreclr / src / vm / peimagelayout.cpp 

PEImageLayout* PEImageLayout::CreateFromHMODULE(HMODULE hModule,PEImage* pOwner, BOOL bTakeOwnership) 
{ 
    // ...[생략]...
    return new RawImageLayout(hModule,pOwner,bTakeOwnership,TRUE); 
}

맨 처음으로 돌아가 load address를 반환하는 GetManagedFileContents 메서드가 결국 PEDecoder의 GetBase를 호출하는데, 구현은 HMODULE hModule 값을 담고 있는 m_base를 반환하는 것에 불과합니다.

// https://github.com/dotnet/coreclr/blob/master/src/inc/pedecoder.h
// coreclr / src / inc / pedecoder.h 

// https://github.com/dotnet/coreclr/blob/master/src/inc/pedecoder.inl
// coreclr / src / inc / pedecoder.inl 

inline PTR_VOID PEDecoder::GetBase() const 
{ 
    // ...[생략]...
    return PTR_VOID(m_base); 
}

inline PEDecoder::PEDecoder(PTR_VOID mappedBase, bool fixedUp /*= FALSE*/)
  : m_base(dac_cast<TADDR>(mappedBase)),
    // ...[생략]...
{
    // ...[생략]...
}

inline void PEDecoder::Init(void *flatBase, COUNT_T size)
{
    // ...[생략]...
    m_base = (TADDR)flatBase;
    m_size = size;
    m_flags = FLAG_CONTENTS;
}




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 10/15/2017]

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

비밀번호

댓글 작성자
 



2017-10-17 12시40분
정성태

... 31  32  33  34  35  36  37  38  39  40  41  42  [43]  44  45  ...
NoWriterDateCnt.TitleFile(s)
12555정성태3/8/20218944Windows: 190. C# - 레지스트리에 등록된 DigitalProductId로부터 라이선스 키(Product Key)를 알아내는 방법파일 다운로드2
12554정성태3/8/20218772.NET Framework: 1027. 닷넷 응용 프로그램을 위한 PDB 옵션 - full, pdbonly, portable, embedded
12553정성태3/5/20219231개발 환경 구성: 548. 기존 .NET Framework 프로젝트를 .NET Core/5+ 용으로 변환해 주는 upgrade-assistant, try-convert 도구 소개 [4]
12552정성태3/5/20218497개발 환경 구성: 547. github workflow/actions에서 Visual Studio Marketplace 패키지 등록하는 방법
12551정성태3/5/20217413오류 유형: 702. 비주얼 스튜디오 - The 'CascadePackage' package did not load correctly. (2)
12550정성태3/5/20217084오류 유형: 701. Live Share 1.0.3713.0 버전을 1.0.3884.0으로 업데이트 이후 ContactServiceModelPackage 오류 발생하는 문제
12549정성태3/4/20217568오류 유형: 700. VsixPublisher를 이용한 등록 시 다양한 오류 유형 해결책
12548정성태3/4/20218337개발 환경 구성: 546. github workflow/actions에서 nuget 패키지 등록하는 방법
12547정성태3/3/20218878오류 유형: 699. 비주얼 스튜디오 - The 'CascadePackage' package did not load correctly.
12546정성태3/3/20218478개발 환경 구성: 545. github workflow/actions에서 빌드시 snk 파일 다루는 방법 - Encrypted secrets
12545정성태3/2/202111236.NET Framework: 1026. 닷넷 5에 추가된 POH (Pinned Object Heap) [10]
12544정성태2/26/202111456.NET Framework: 1025. C# - Control의 Invalidate, Update, Refresh 차이점 [2]
12543정성태2/26/20219802VS.NET IDE: 158. C# - 디자인 타임(design-time)과 런타임(runtime)의 코드 실행 구분
12542정성태2/20/202112137개발 환경 구성: 544. github repo의 Release 활성화 및 Actions를 이용한 자동화 방법 [1]
12541정성태2/18/20219365개발 환경 구성: 543. 애저듣보잡 - Github Workflow/Actions 소개
12540정성태2/17/20219684.NET Framework: 1024. C# - Win32 API에 대한 P/Invoke를 대신하는 Microsoft.Windows.CsWin32 패키지
12539정성태2/16/20219603Windows: 189. WM_TIMER의 동작 방식 개요파일 다운로드1
12538정성태2/15/20219976.NET Framework: 1023. C# - GC 힙이 아닌 Native 힙에 인스턴스 생성 - 0SuperComicLib.LowLevel 라이브러리 소개 [2]
12537정성태2/11/202111005.NET Framework: 1022. UI 요소의 접근은 반드시 그 UI를 만든 스레드에서! - 두 번째 이야기 [2]
12536정성태2/9/202110003개발 환경 구성: 542. BDP(Bandwidth-delay product)와 TCP Receive Window
12535정성태2/9/20219118개발 환경 구성: 541. Wireshark로 확인하는 LSO(Large Send Offload), RSC(Receive Segment Coalescing) 옵션
12534정성태2/8/20219751개발 환경 구성: 540. Wireshark + C/C++로 확인하는 TCP 연결에서의 closesocket 동작 [1]파일 다운로드1
12533정성태2/8/20219369개발 환경 구성: 539. Wireshark + C/C++로 확인하는 TCP 연결에서의 shutdown 동작파일 다운로드1
12532정성태2/6/20219888개발 환경 구성: 538. Wireshark + C#으로 확인하는 ReceiveBufferSize(SO_RCVBUF), SendBufferSize(SO_SNDBUF) [3]
12531정성태2/5/20218862개발 환경 구성: 537. Wireshark + C#으로 확인하는 PSH flag와 Nagle 알고리듬파일 다운로드1
12530정성태2/4/202113024개발 환경 구성: 536. Wireshark + C#으로 확인하는 TCP 통신의 Receive Window
... 31  32  33  34  35  36  37  38  39  40  41  42  [43]  44  45  ...