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)
12720정성태7/19/20216975Linux: 43. .NET Core/5+ 응용 프로그램의 Ubuntu (Debian) 패키지 준비
12719정성태7/19/20216133오류 유형: 737. SharePoint 설치 시 "0x800710D8 The object identifier does not represent a valid object." 오류 발생
12718정성태7/19/20216752개발 환경 구성: 581. Windows에서 WSL로 파일 복사 시 root 소유권으로 적용되는 문제파일 다운로드1
12717정성태7/18/20216727Windows: 195. robocopy에서 파일의 ADS(Alternate Data Stream) 정보 복사를 제외하는 방법
12716정성태7/17/20217517개발 환경 구성: 580. msbuild의 Exec Task에 robocopy를 사용하는 방법파일 다운로드1
12715정성태7/17/20219238오류 유형: 736. Windows - MySQL zip 파일 버전의 "mysqld --skip-grant-tables" 실행 시 비정상 종료 [1]
12714정성태7/16/20217942오류 유형: 735. VCRUNTIME140.dll, MSVCP140.dll, VCRUNTIME140.dll, VCRUNTIME140_1.dll이 없어 exe 실행이 안 되는 경우
12713정성태7/16/20218492.NET Framework: 1077. C# - 동기 방식이면서 비동기 규약을 따르게 만드는 Task.FromResult파일 다운로드1
12712정성태7/15/20217894개발 환경 구성: 579. Azure - 리눅스 호스팅의 Site Extension 제작 방법
12711정성태7/15/20218239개발 환경 구성: 578. Azure - Java Web App Service를 위한 Site Extension 제작 방법
12710정성태7/15/202110010개발 환경 구성: 577. MQTT - emqx.io 서비스 소개
12709정성태7/14/20216682Linux: 42. 실행 중인 docker 컨테이너에 대한 구동 시점의 docker run 명령어를 확인하는 방법
12708정성태7/14/202110032Linux: 41. 리눅스 환경에서 디스크 용량 부족 시 원인 분석 방법
12707정성태7/14/202177244오류 유형: 734. MySQL - Authentication method 'caching_sha2_password' not supported by any of the available plugins.
12706정성태7/14/20218557.NET Framework: 1076. C# - AsyncLocal 기능을 CallContext만으로 구현하는 방법 [2]파일 다운로드1
12705정성태7/13/20218681VS.NET IDE: 168. x64 DLL 프로젝트의 컨트롤이 Visual Studio의 Designer에서 보이지 않는 문제 - 두 번째 이야기
12704정성태7/12/20217825개발 환경 구성: 576. Azure VM의 서비스를 Azure Web App Service에서만 접근하도록 NSG 설정을 제한하는 방법
12703정성태7/11/202113458개발 환경 구성: 575. Azure VM에 (ICMP) ping을 허용하는 방법
12702정성태7/11/20218585오류 유형: 733. TaskScheduler에 등록된 wacs.exe의 Let's Encrypt 인증서 업데이트 문제
12701정성태7/9/20218250.NET Framework: 1075. C# - ThreadPool의 스레드는 반환 시 ThreadStatic과 AsyncLocal 값이 초기화 될까요?파일 다운로드1
12700정성태7/8/20218636.NET Framework: 1074. RuntimeType의 메모리 누수? [1]
12699정성태7/8/20217462VS.NET IDE: 167. Visual Studio 디버깅 중 GC Heap 상태를 보여주는 "Show Diagnostic Tools" 메뉴 사용법
12698정성태7/7/202111439오류 유형: 732. Windows 11 업데이트 시 3% 또는 0%에서 다운로드가 멈춘 경우
12697정성태7/7/20217332개발 환경 구성: 574. Windows 11 (Insider Preview) 설치하는 방법
12696정성태7/6/20217884VC++: 146. 운영체제의 스레드 문맥 교환(Context Switch)을 유사하게 구현하는 방법파일 다운로드2
12695정성태7/3/20217971VC++: 145. C 언어의 setjmp/longjmp 기능을 Thread Context를 이용해 유사하게 구현하는 방법파일 다운로드1
... 31  32  33  34  35  [36]  37  38  39  40  41  42  43  44  45  ...