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)
12618정성태4/27/202110927사물인터넷: 58. NodeMCU v1 ESP8266 CP2102 Module을 이용한 WiFi UDP 통신 [1]파일 다운로드1
12617정성태4/26/20218602.NET Framework: 1050. C# - ETW EventListener의 Keywords별 EventId에 따른 필터링 방법파일 다운로드1
12616정성태4/26/20218598.NET Framework: 1049. C# - ETW EventListener를 상속받았을 때 초기화 순서파일 다운로드1
12615정성태4/26/20216778오류 유형: 712. Microsoft Live 로그인 - 계정을 선택하는(Pick an account) 화면에서 진행이 안 되는 문제
12614정성태4/24/20219441개발 환경 구성: 570. C# - Azure AD 인증을 지원하는 ASP.NET Core/5+ 웹 애플리케이션 예제 구성 [4]파일 다운로드1
12613정성태4/23/20218506.NET Framework: 1048. C# - ETW 이벤트의 Keywords에 속한 EventId 구하는 방법 (2) 관리 코드파일 다운로드1
12612정성태4/23/20218584.NET Framework: 1047. C# - ETW 이벤트의 Keywords에 속한 EventId 구하는 방법 (1) PInvoke파일 다운로드1
12611정성태4/22/20217892오류 유형: 711. 닷넷 EXE 실행 오류 - Mixed mode assembly is build against version 'v2.0.50727' of the runtime
12610정성태4/22/20217708.NET Framework: 1046. C# - 컴파일 시점에 참조할 수 없는 타입을 포함한 이벤트 핸들러를 Reflection을 이용해 구독하는 방법파일 다운로드1
12609정성태4/22/20218990.NET Framework: 1045. C# - 런타임 시점에 이벤트 핸들러를 만들어 Reflection을 이용해 구독하는 방법파일 다운로드1
12608정성태4/21/202110019.NET Framework: 1044. C# - Generic Host를 이용해 .NET 5로 리눅스 daemon 프로그램 만드는 방법 [9]파일 다운로드1
12607정성태4/21/20218540.NET Framework: 1043. C# - 실행 시점에 동적으로 Delegate 타입을 만드는 방법파일 다운로드1
12606정성태4/21/202112383.NET Framework: 1042. C# - enum 값을 int로 암시적(implicit) 형변환하는 방법? [2]파일 다운로드1
12605정성태4/18/20218501.NET Framework: 1041. C# - AssemblyID, ModuleID를 관리 코드에서 구하는 방법파일 다운로드1
12604정성태4/18/20217261VS.NET IDE: 163. 비주얼 스튜디오 속성 창의 "Build(빌드)" / "Configuration(구성)"에서의 "활성" 의미
12603정성태4/16/20218128VS.NET IDE: 162. 비주얼 스튜디오 - 상속받은 컨트롤이 디자인 창에서 지원되지 않는 문제
12602정성태4/16/20219310VS.NET IDE: 161. x64 DLL 프로젝트의 컨트롤이 Visual Studio의 Designer에서 보이지 않는 문제 [1]
12601정성태4/15/20218411.NET Framework: 1040. C# - REST API 대신 github 클라이언트 라이브러리를 통해 프로그래밍으로 접근
12600정성태4/15/20218590.NET Framework: 1039. C# - Kubeconfig의 token 설정 및 인증서 구성을 자동화하는 프로그램
12599정성태4/14/20219269.NET Framework: 1038. C# - 인증서 및 키 파일로부터 pfx/p12 파일을 생성하는 방법파일 다운로드1
12598정성태4/14/20219428.NET Framework: 1037. openssl의 PEM 개인키 파일을 .NET RSACryptoServiceProvider에서 사용하는 방법 (2)파일 다운로드1
12597정성태4/13/20219446개발 환경 구성: 569. csproj의 내용을 공통 설정할 수 있는 Directory.Build.targets / Directory.Build.props 파일
12596정성태4/12/20219244개발 환경 구성: 568. Windows의 80 포트 점유를 해제하는 방법
12595정성태4/12/20218646.NET Framework: 1036. SQL 서버 - varbinary 타입에 대한 문자열의 CAST, CONVERT 변환을 C# 코드로 구현
12594정성태4/11/20218117.NET Framework: 1035. C# - kubectl 명령어 또는 REST API 대신 Kubernetes 클라이언트 라이브러리를 통해 프로그래밍으로 접근 [1]파일 다운로드1
12593정성태4/10/20219270개발 환경 구성: 567. Docker Desktop for Windows - kubectl proxy 없이 k8s 대시보드 접근 방법
... 31  32  33  34  35  36  37  38  39  [40]  41  42  43  44  45  ...