Microsoft MVP성태의 닷넷 이야기
글쓴 사람
홈페이지
첨부 파일
 

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@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)
11365정성태11/25/20172869오류 유형: 429. 이벤트 로그 - User Policy could not be updated successfully
11364정성태8/9/20183481사물인터넷: 11. Raspberry Pi Zero(OTG)를 다른 컴퓨터에 연결해 가상 마우스로 쓰는 방법 (절대 좌표) [2]
11363정성태8/9/20183981사물인터넷: 10. Raspberry Pi Zero(OTG)를 다른 컴퓨터에 연결해 가상 마우스 + 키보드로 쓰는 방법 (두 번째 이야기)
11362정성태12/26/20194473오류 유형: 428. 윈도우 업데이트 KB4048953 - 0x800705b4 [2]
11361정성태11/22/20174299오류 유형: 427. 이벤트 로그 - Filter Manager failed to attach to volume '\Device\HarddiskVolume??' 0xC03A001C
11360정성태11/22/20175652오류 유형: 426. 이벤트 로그 - The kernel power manager has initiated a shutdown transition.
11359정성태11/16/20175772오류 유형: 425. 윈도우 10 Version 1709 (OS Build 16299.64) 업그레이드 시 발생한 문제 2가지
11358정성태8/9/20186963사물인터넷: 9. Visual Studio 2017에서 Raspberry Pi C++ 응용 프로그램 제작 [1]
11357정성태11/15/20176182개발 환경 구성: 336. 윈도우 10 Bash 쉘에서 C++ 컴파일하는 방법
11356정성태5/13/20207482사물인터넷: 8. Raspberry Pi Zero(OTG)를 다른 컴퓨터에 연결해 가상 마우스 + 키보드로 쓰는 방법 [3]
11355정성태4/27/20205322사물인터넷: 7. Raspberry Pi Zero(OTG)를 다른 컴퓨터에 연결해 가상 마우스로 쓰는 방법 [2]파일 다운로드2
11354정성태8/9/20186876사물인터넷: 6. Raspberry Pi Zero(OTG)를 다른 컴퓨터에 연결해 가상 키보드로 쓰는 방법 [4]
11353정성태8/9/20186048사물인터넷: 5. Raspberry Pi Zero(OTG)를 다른 컴퓨터에 연결해 가상 이더넷 카드로 쓰는 방법 [1]
11352정성태8/9/20184073사물인터넷: 4. Samba를 이용해 윈도우와 Raspberry Pi간의 파일 교환 [1]
11351정성태12/22/20174984.NET Framework: 698. C# 컴파일러 대신 직접 구현하는 비동기(async/await) 코드파일 다운로드1
11350정성태11/1/20173497디버깅 기술: 108. windbg 분석 사례 - Redis 서버로의 호출을 기다리면서 hang 현상 발생
11349정성태10/31/20173664디버깅 기술: 107. windbg - x64 SOS 확장의 !clrstack 명령어가 출력하는 Child SP 값의 의미
11348정성태10/31/20173877디버깅 기술: 106. windbg - x64 역어셈블 코드에서 닷넷 메서드 호출의 인자를 확인하는 방법
11347정성태10/28/20176007오류 유형: 424. Visual Studio - "클래스 다이어그램 보기" 시 "작업을 완료할 수 없습니다. 해당 인터페이스를 지원하지 않습니다." 오류 발생
11346정성태10/25/20172992오류 유형: 423. Windows Server 2003 - The client-side extension could not remove user policy settings for 'Default Domain Policy {...}' (0x8007000d)
11338정성태10/25/20173409.NET Framework: 697. windbg - SOS DumpMT의 "BaseSize", "ComponentSize" 값에 대한 의미파일 다운로드1
11337정성태10/24/20173250.NET Framework: 696. windbg - SOS DumpClass/DumpMT의 "Vtable Slots", "Total Method Slots", "Slots in VTable" 값에 대한 의미파일 다운로드1
11336정성태10/20/20173503.NET Framework: 695. windbg - .NET string의 x86/x64 메모리 할당 구조
11335정성태10/18/20173883.NET Framework: 694. 닷넷 - <Module> 클래스의 용도
11334정성태10/18/20173587디버깅 기술: 105. windbg - k 명령어와 !clrstack을 조합한 호출 스택을 얻는 방법
11333정성태10/17/20174127오류 유형: 422. 윈도우 업데이트 - Code 9C48 Windows update encountered an unknown error.
... 31  32  33  34  [35]  36  37  38  39  40  41  42  43  44  45  ...