Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)
(시리즈 글이 9개 있습니다.)
.NET Framework: 216. 라이선스까지도 뛰어넘는 .NET Profiler
; https://www.sysnet.pe.kr/2/0/1046

.NET Framework: 336. .NET Profiler가 COM 개체일까?
; https://www.sysnet.pe.kr/2/0/1352

.NET Framework: 576. 기본적인 CLR Profiler 소스 코드 설명
; https://www.sysnet.pe.kr/2/0/10950

.NET Framework: 582. CLR Profiler - 별도 정의한 .NET 코드를 호출하도록 IL 코드 변경
; https://www.sysnet.pe.kr/2/0/10959

.NET Framework: 808. .NET Profiler - GAC 모듈에서 GAC 비-등록 모듈을 참조하는 경우의 문제
; https://www.sysnet.pe.kr/2/0/11810

오류 유형: 672. AllowPartiallyTrustedCallers 특성이 적용된 어셈블리의 struct 멤버 메서드를 재정의하면 System.Security.VerificationException 예외 발생
; https://www.sysnet.pe.kr/2/0/12384

.NET Framework: 987. .NET Profiler - FunctionID와 연관된 ClassID를 구할 수 없는 문제
; https://www.sysnet.pe.kr/2/0/12465

.NET Framework: 1041. C# - AssemblyID, ModuleID를 관리 코드에서 구하는 방법
; https://www.sysnet.pe.kr/2/0/12605

닷넷: 2228. .NET Profiler - IMetaDataEmit2::DefineMethodSpec 사용법
; https://www.sysnet.pe.kr/2/0/13576




C# - AssemblyID, ModuleID를 관리 코드에서 구하는 방법

예전에 설명한 .NET Profiler에서,

기본적인 CLR Profiler 소스 코드 설명
; https://www.sysnet.pe.kr/2/0/10950

ModuleLoadFinished 콜백의 인자로 ModuleID가 있습니다.

HRESULT CBasicClrProfiler::ModuleLoadFinished(ModuleID moduleId, HRESULT hrStatus)
{
    // ...[생략]...
    return S_OK;
}

혹시 이 값을 관리 코드에서 구하는 것이 가능할까요? 위의 값을 로그로 남겨 보고 관리 코드에서 이거저거 살펴보니 값 자체는 구할 수 있지만 아쉽게도 private 필드에 저장이 되어 있습니다.

using System;
using System.Reflection;
using System.Runtime.InteropServices;

class Program
{
    static void Main(string[] args)
    {
        // System.EnterpriseServices 어셈블리를 로딩하기 위해!
        System.EnterpriseServices.ApplicationQueuingAttribute aqa = new System.EnterpriseServices.ApplicationQueuingAttribute();

        foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
        {
            foreach (Module mod in asm.GetModules())
            {
                PrintModuleHandle(mod.Name, mod);
            }

            Console.WriteLine();
        }
    }

    private static void PrintModuleHandle(string name, Module module)
    {
        string dataFieldName = (Environment.Version.Major == 2) ? "m__pData" : "m_pData";
        IntPtr pData = (IntPtr)GetPrivateFieldValue(module, dataFieldName);
        Console.WriteLine($"{name}, {pData.ToInt64().ToString("x")}");
    }

    private static object GetPrivateFieldValue(object instance, string fieldName)
    {
        Type type = instance.GetType();

        FieldInfo fi = type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
        return fi.GetValue(instance);
    }
}

/* 출력 결과
mscorlib.dll, 7ffe89211000

ConsoleApp1.exe, 7ffe32084148

System.EnterpriseServices.dll, 7ffe4f061000
System.EnterpriseServices.Wrapper.dll, 7ffe4ecc1000

System.dll, 7ffe87ca1000
*/




위의 출력 결과를 이미지 로딩 주소와 비교해 볼까요? 간단하게 Visual Studio의 "Debug" / "Windows" / "Modules (Ctrl+Alt+U)" 창을 띄워,

managed_dll_info_1.png

값을 비교해 보면, C#의 출력 결과에 대해 0x1000(4096)만큼 뺀 값이 "Modules" 창의 "Address"에 나오고 있습니다. 즉, ModuleID가 dll의 로딩 주소를 정확하게 가리키고 있지는 않습니다.

그나저나 애매하군요, 저 0x1000 값의 기준을 알 수 없습니다. 그나마 엮어볼 수 있는 값이 mscorlib.dll의 IMAGE_NT_HEADERS.IMAGE_OPTIONAL_HEADER.BaseOfCode인데, 아쉽게도 PE Viewer 같은 도구로 보면 0x2000 값이 나옵니다. 게다가 Section Alignment도 0x2000이고.

이와 함께, ConsoleApp1.exe의 경우 로딩 주소가 0x5a0000으로 나오고, ModuleID는 7ffe32084148로 아예 다른 값이 나오는데요. 이것은 전에 설명했던,

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

Marshal.GetHINSTANCE의 반환값과 일치합니다.




하는 김에, AssemblyLoadStarted 콜백의 인자로 전달되는 AssemblyID도 찾아볼까요?

HRESULT CBasicClrProfiler::AssemblyLoadStarted(AssemblyID assemblyId)
{
    return S_OK;
}

애석하게도 이 값은 정확히 떨어지는 값으로는 Managed 코드에서 알 수 있는 방법이 없습니다. 관리 타입의 Assembly에는 AssemblyID를 나타내는 필드를 전혀 노출하지 않기 때문인데요.

ModuleID를 구했을 때와 약간 유사하게 Assembly 타입에서도 m__assembly/m_assembly 필드를 private으로 노출하고 있지만 실제로 값을 구해 보면 Profiler의 AssemblyID와는 다르다는 것을 알 수 있습니다. 대신 해당 값은 Unmanaged 측의 Assembly 인스턴스 값을 가리키고 있는데 .NET 4 + x64 환경의 경우 m_assembly가 가리키는 위치에서 [0x8 * 15]에 AssemblyID 값이 있다는 것을 실험적으로 알 수 있습니다.

foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
{
    IntPtr pAssemblyAddr = (IntPtr)GetPrivateFieldValue(asm, assemblyFieldName);
    long *ptrAssembly = (long *)(pAssemblyAddr + (0x8 * 15)).ToPointer();
    Console.WriteLine("Assembly.m_assembly: " + (*ptrAssembly).ToString("x"));
}

아시다시피, 이 방법으로 접근하는 것은 다양한 버전과 그것의 패치를 고려했을 때 안정성 측면에서 쓸만한 방법은 아닙니다.

어쩔 수 없습니다. 이런 경우에는 차선책으로 Profiler 측의 ICorProfilerInfo::GetModuleInfo Method를 호출하도록 pinvoke 호출을 만들어 ModuleId로부터 AssemblyID를 반환하는 GetModuleInfo 함수를 호출하는 것이 그나마 낫습니다.

ICorProfilerInfo::GetModuleInfo Method
; https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/icorprofilerinfo-getmoduleinfo-method

{
    ULONG cchModule = _MAX_PATH;
    ULONG rCchModule = 0;
    AssemblyID assemblyId = 0;
    LPCBYTE pModuleBaseLoadAddress;
    wchar_t szModule[_MAX_PATH];

    HRESULT hr = m_pICorProfilerInfo2->GetModuleInfo(moduleId,
        &pModuleBaseLoadAddress, cchModule, &rCchModule, szModule, &assemblyId);

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

    return S_OK;
}

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 11/7/2023]

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

비밀번호

댓글 작성자
 




1  2  3  [4]  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13843정성태12/13/20244381오류 유형: 938. Docker container 내에서 빌드 시 error MSB3021: Unable to copy file "..." to "...". Access to the path '...' is denied.
13842정성태12/12/20244522디버깅 기술: 205. Windbg - KPCR, KPRCB
13841정성태12/11/20244855오류 유형: 937. error MSB4044: The "ValidateValidArchitecture" task was not given a value for the required parameter "RemoteTarget"
13840정성태12/11/20244428오류 유형: 936. msbuild - Your project file doesn't list 'win' as a "RuntimeIdentifier"
13839정성태12/11/20244857오류 유형: 936. msbuild - error CS1617: Invalid option '12.0' for /langversion. Use '/langversion:?' to list supported values.
13838정성태12/4/20244595오류 유형: 935. Windbg - Breakpoint 0's offset expression evaluation failed.
13837정성태12/3/20245052디버깅 기술: 204. Windbg - 윈도우 핸들 테이블 (3) - Windows 10 이상인 경우
13836정성태12/3/20244613디버깅 기술: 203. Windbg - x64 가상 주소를 물리 주소로 변환 (페이지 크기가 2MB인 경우)
13835정성태12/2/20245055오류 유형: 934. Azure - rm: cannot remove '...': Directory not empty
13834정성태11/29/20245284Windows: 275. C# - CUI 애플리케이션과 Console 윈도우 (Windows 10 미만의 Classic Console 모드인 경우) [1]파일 다운로드1
13833정성태11/29/20244965개발 환경 구성: 737. Azure Web App에서 Scale-out으로 늘어난 리눅스 인스턴스에 SSH 접속하는 방법
13832정성태11/27/20244901Windows: 274. Windows 7부터 도입한 conhost.exe
13831정성태11/27/20244381Linux: 111. eBPF - BPF_MAP_TYPE_PERF_EVENT_ARRAY, BPF_MAP_TYPE_RINGBUF에 대한 다양한 용어들
13830정성태11/25/20245183개발 환경 구성: 736. 파이썬 웹 앱을 Azure App Service에 배포하기
13829정성태11/25/20245143스크립트: 67. 파이썬 - Windows 버전에서 함께 설치되는 py.exe
13828정성태11/25/20244439개발 환경 구성: 735. Azure - 압축 파일을 이용한 web app 배포 시 디렉터리 구분이 안 되는 문제파일 다운로드1
13827정성태11/25/20245091Windows: 273. Windows 환경의 파일 압축 방법 (tar, Compress-Archive)
13826정성태11/21/20245320닷넷: 2313. C# - (비밀번호 등의) Console로부터 입력받을 때 문자열 출력 숨기기(echo 끄기)파일 다운로드1
13825정성태11/21/20245652Linux: 110. eBPF / bpf2go - BPF_RINGBUF_OUTPUT / BPF_MAP_TYPE_RINGBUF 사용법
13824정성태11/20/20244745Linux: 109. eBPF / bpf2go - BPF_PERF_OUTPUT / BPF_MAP_TYPE_PERF_EVENT_ARRAY 사용법
13823정성태11/20/20245287개발 환경 구성: 734. Ubuntu에 docker, kubernetes (k3s) 설치
13822정성태11/20/20245151개발 환경 구성: 733. Windbg - VirtualBox VM의 커널 디버거 연결 시 COM 포트가 없는 경우
13821정성태11/18/20245079Linux: 108. Linux와 Windows의 프로세스/스레드 ID 관리 방식
13820정성태11/18/20245240VS.NET IDE: 195. Visual C++ - C# 프로젝트처럼 CopyToOutputDirectory 항목을 추가하는 방법
13819정성태11/15/20244477Linux: 107. eBPF - libbpf CO-RE의 CONFIG_DEBUG_INFO_BTF 빌드 여부에 대한 의존성
13818정성태11/15/20245265Windows: 272. Windows 11 24H2 - sudo 추가
1  2  3  [4]  5  6  7  8  9  10  11  12  13  14  15  ...