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)
12799정성태8/22/20218633.NET Framework: 1100. C# 10 - (5) 속성 패턴의 개선파일 다운로드1
12798정성태8/21/20219931개발 환경 구성: 598. PyCharm - 원격 프로세스를 디버그하는 방법
12797정성태8/21/20217657Windows: 197. TCP의 MSS(Maximum Segment Size) 크기는 고정된 것일까요?
12796정성태8/21/20218329.NET Framework: 1099. C# 10 - (4) 상수 문자열에 포맷 식 사용 가능파일 다운로드1
12795정성태8/20/20218929.NET Framework: 1098. .NET 6에 포함된 신규 BCL API - 스레드 관련
12794정성태8/20/20218368스크립트: 23. 파이썬 - WSGI를 만족하는 최소한의 구현 코드 및 PyCharm에서의 디버깅 방법 [1]
12793정성태8/20/20219103.NET Framework: 1097. C# 10 - (3) 개선된 변수 초기화 판정파일 다운로드1
12792정성태8/19/20219552.NET Framework: 1096. C# 10 - (2) 전역 네임스페이스 선언파일 다운로드1
12791정성태8/19/20217921.NET Framework: 1095. C# COM 개체를 C++에서 사용하는 예제 [3]파일 다운로드1
12790정성태8/18/202110137.NET Framework: 1094. C# 10 - (1) 구조체를 생성하는 record struct파일 다운로드1
12789정성태8/18/20219133개발 환경 구성: 597. PyCharm - 윈도우 환경에서 WSL을 이용해 파이썬 앱 개발/디버깅하는 방법
12788정성태8/17/20217708.NET Framework: 1093. C# - 인터페이스의 메서드가 다형성을 제공할까요? (virtual일까요?)파일 다운로드1
12787정성태8/17/20217920.NET Framework: 1092. (책 내용 수정) "4.5.1.4 인터페이스"의 "인터페이스와 다형성"
12786정성태8/16/20219426.NET Framework: 1091. C# - Python range 함수 구현 (2) INumber<T>를 이용한 개선 [1]파일 다운로드1
12785정성태8/16/20217670.NET Framework: 1090. .NET 6 Preview 7에 추가된 숫자 형식에 대한 제네릭 연산 지원 [1]파일 다운로드1
12784정성태8/15/20217087오류 유형: 757. 구글 메일 - 아웃룩에서 메일 전송 시 Sending' reported error (0x800CCC0F, 0x800CCC92)
12783정성태8/15/20216692.NET Framework: 1089. C# - Indexer에 Range 및 람다 식을 이용한 필터 구현 [1]파일 다운로드1
12782정성태8/14/20216480오류 유형: 756. 파이썬 - 윈도우 환경에서 pytagcloud의 한글 출력 방법
12781정성태8/14/20218622오류 유형: 755. 파이썬 - konlpy 사용 시 JVM과 jpype1 관련 오류
12780정성태8/13/20217018.NET Framework: 1088. C# - 버스 노선 및 위치 정보 조회 API 사용을 위한 기초 라이브러리 [2]
12779정성태8/13/20218859개발 환경 구성: 596. 공공 데이터 포털에서 버스 노선 및 위치 정보 조회 API 사용법
12778정성태8/12/20216170오류 유형: 755. PyCharm - "Manage Repositories"의 목록이 나오지 않는 문제
12777정성태8/12/20217816오류 유형: 754. Visual Studio - Input or output cannot be redirected because the specified file is invalid.
12776정성태8/12/20217129오류 유형: 753. gunicorn과 uwsgi 함께 사용 시 ERR_CONNECTION_REFUSED
12775정성태8/12/202117386스크립트: 22. 파이썬 - 윈도우 환경에서 개발한 Django 앱을 WSL 환경의 gunicorn을 이용해 실행
12774정성태8/11/20218711.NET Framework: 1087. C# - Collection 개체의 다중 스레드 접근 시 "Operations that change non-concurrent collections must have exclusive access" 예외 발생
... 31  32  [33]  34  35  36  37  38  39  40  41  42  43  44  45  ...