성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
[정성태] 만드실 수 있습니다. 단지, Unity 엔진 내의 스크립트와 W...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>.NET System.Threading.Thread 개체에서 Native Thread Id를 구하는 방법 - 두 번째 이야기</h1> <p> (<a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=870&boardid=331301885'>이 글에 사용된 모든 코드는 첨부 파일</a>에 있습니다.)<br /> <br /> 예전에 한번 이에 대해서 글을 썼었죠!<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .NET System.Threading.Thread 개체에서 Native Thread Id를 구할 수 있을까? ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1244'>http://www.sysnet.pe.kr/2/0/1244</a> </pre> <br /> 오늘도 잠시 뭔가 테스트를 하다 Native 스레드 ID가 필요해서 다시 그 글을 보았습니다. (사실, 제가 쓴 글이지만 저도 시간 지나면 참고하게 됩니다. ^^;)<br /> <br /> 정리하면 다음과 같은 작업을 해야 합니다.<br /> <br /> <ol> <li>Thread 객체로부터 private 필드인 "DONT_USE_InternalThread" 값을 구한다.</li> <li>"DONT_USE_InternalThread" 값이 가리키는 포인터의 64바이트 위치에 TEB 값을 읽는다. (참고로, 윈도우 8.1 + .NET 4.5가 설치된 제 PC의 경우 2번째의 64바이트 위치가 60바이트 위치로 바뀌었습니다.)</li> <li>TEB값에서 (0x6b4 + 0x4)에 위치한 Thread Id를 읽는다.</li> </ol> <br /> 근데, "DONT_USE_InternalThread" 값의 의미가 도대체 뭘까요? 이에 대한 단서는 다음의 글이 거의 유일한 것 같습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Thread, System.Threading.Thread, and !Threads (II) ; http://blogs.msdn.com/b/yunjin/archive/2005/08/29/457150.aspx </pre> <br /> 위의 글에 보면, DONT_USE_InternalThread 값으로부터 m_ExposedObject 값을 구하는 dt 명령어가 나옵니다. <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:043> dt mscorwks!Thread 0x00180c10 m_ExposedObject +0x0c0 m_ExposedObject : 0x00a71054 </pre> <br /> (적어도 .NET 1.1 시절에는) mscorwks 모듈의 Thread 구조체로 존재하는 것으로 보이는 데요. 이 정보를 바탕으로 혹시 <a target='tab' href='http://www.microsoft.com/en-us/download/details.aspx?id=4917'>SSCLI 소스 코드</a>를 뒤져보면 뭔가 나오지 않을까 싶었는데 역시나 ".\clr\src\vm\threads.h" 파일에서 그 정의를 찾을 수 있었습니다.<br /> <br /> 해당 파일에서 Thread 타입을 대상으로 필드만 대충 정리해 보면 다음과 같습니다.<br /> <br /> <pre style='height: 400px; margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > class Thread: public ICLRTask { static LONG m_DetachCount; static LONG m_ActiveDetachCount; // Count how many non-background detached static volatile LONG m_threadsAtUnsafePlaces; volatile ThreadState m_State; // Bits for the state of the thread volatile ULONG m_fPreemptiveGCDisabled; PTR_Frame m_pFrame; // The Current Frame PTR_Frame m_pUnloadBoundaryFrame; // Roots to scan CLRRootBase *m_pRoot; DWORD m_dwLockCount; DWORD m_ThreadId; IHostTask *m_pHostTask; LockEntry *m_pHead; LockEntry m_embeddedEntry; PTR_Context m_Context; alloc_context m_alloc_context; StackingAllocator m_MarshalAlloc; ThreadTasks m_ThreadTasks; ThreadStateNoConcurrency m_StateNC; #ifdef CALLDESCR_RETBUF BYTE m_SmallVCRetVal[ENREGISTERED_RETURNTYPE_MAXSIZE]; #endif private: DWORD m_dwBeginLockCount; // lock count when the thread enters current domain DWORD m_dwBeginCriticalRegionCount; // lock count when the thread enters current domain DWORD m_dwNonHostLockCount; DWORD m_dwCriticalRegionCount; DWORD m_dwDelayAbortCount; DWORD m_dwThreadAffinityCount; #ifdef _DEBUG DWORD m_dwSuspendThread; EEThreadId m_Creater; #endif volatile LONG m_dwForbidSuspendThread; private: DWORD m_dwHashCodeSeed; DeadlockAwareLock * volatile m_pBlockingLock; public: private: LoadLevelLimiter *m_pLoadLimiter; private: BOOL m_fSecurityStackwalk; private: // static bool s_fSysSuspendInProgress; private: // Specifies type of thread abort. DWORD m_AbortInfo; DWORD m_AbortType; ULONGLONG m_AbortEndTime; ULONGLONG m_RudeAbortEndTime; BOOL m_fRudeAbortInitiated; LONG m_AbortController; static ULONGLONG s_NextSelfAbortEndTime; LONG m_AbortRequestLock; public: #ifdef _DEBUG BOOL m_fRudeAborted; DWORD m_dwAbortPoint; #endif public: DWORD m_ThrewControlForThread; PTR_CONTEXT m_OSContext; private: SLink m_LinkStore; DWORD m_dwLastError; private: static CLREvent * g_pGCSuspendEvent; DWORD m_Win32FaultAddress; DWORD m_Win32FaultCode; LONG m_UserInterrupt; #if defined(_DEBUG) && defined(TRACK_SYNC) public: Dbg_TrackSync *m_pTrackSync; #endif private: CLREvent m_SafeEvent; CLREvent m_UserSuspendEvent; CLREvent m_DebugSuspendEvent; CLREvent m_EventWait; WaitEventLink m_WaitEventLink; HANDLE m_ThreadHandle; HANDLE m_ThreadHandleForClose; HANDLE m_ThreadHandleForResume; BOOL m_WeOwnThreadHandle; <span style='color: blue; font-weight: bold'>DWORD m_OSThreadId;</span> <span style='color: blue; font-weight: bold'>OBJECTHANDLE m_ExposedObject;</span> OBJECTHANDLE m_StrongHndToExposedObject; DWORD m_Priority; // initialized to INVALID_THREAD_PRIORITY, set to actual priority when a // thread does a busy wait for GC, reset to INVALID_THREAD_PRIORITY after wait is over ULONG m_ExternalRefCount; ULONG m_UnmanagedRefCount; LONG m_TraceCallCount; DWORD m_fPromoted; private: OBJECTHANDLE m_LastThrownObjectHandle; // Unsafe to use directly. Use accessors instead. private: ADID m_pKickOffDomainId; ThreadExceptionState m_ExceptionState; UINT_PTR m_ProbeLimit; IA64_ONLY(UINT_PTR m_BSPProbeLimit); UINT_PTR m_LastAllowableStackAddress; IA64_ONLY(UINT_PTR m_LastAllowableBackingStoreStackAddress); PTR_AppDomain m_pDomain; PTR_AppDomain m_pDomainAtTaskSwitch; PTR_CONTEXT m_debuggerFilterContext; CONTEXT *m_pProfilerFilterContext; volatile LONG m_hijackLock; DWORD m_debuggerCantStop; DWORD m_debuggerWord; BOOL m_fInteropDebuggingHijacked; DWORD m_profilerCallbackState; private: AppDomainStack m_ADStack; DomainFile* m_pLoadingFile; OBJECTHANDLE m_AbortReason; ADID m_AbortReasonDomainID; protected: EEIntHashTable* m_pDLSHash; private: int m_PreventAsync; int m_PreventThreadAbort; int m_nNestedMarshalingExceptions; static LONG m_DebugWillSyncCount; #define CLEANUP_IPS_PER_CHUNK 4 struct CleanupIPs { IUnknown *m_Slots[CLEANUP_IPS_PER_CHUNK]; CleanupIPs *m_Next; CleanupIPs() {LEAF_CONTRACT; memset(this, 0, sizeof(*this)); } }; CleanupIPs m_CleanupIPs; public: DWORD m_GCOnTransitionsOK; ULONG m_ulForbidTypeLoad; #define OBJREF_TABSIZE 256 DWORD_PTR dangerousObjRefs[OBJREF_TABSIZE]; // Really objectRefs with lower bit stolen static DWORD_PTR OBJREF_HASH; private: PEXCEPTION_REGISTRATION_RECORD * m_pExceptionList; private: PTR_CONTEXT m_pSavedRedirectContext; private: PTR_STATIC_DATA m_pUnsharedStaticData; PTR_STATIC_DATA m_pSharedStaticData; PTR_EEPtrHashTable m_pStaticDataHash; Crst *m_pSDHCrst; // Mutex protecting m_pStaticDataHash private: BOOL m_fStressHeapCount; public: size_t *m_pCleanedStackBase; #ifdef STRESS_THREAD public: LONG m_stressThreadCount; #endif private: PVOID m_pFiberData; TASKID m_TaskId; CONNID m_dwConnectionId; #ifdef _DEBUG private: static int MaxThreadRecord; static int MaxStackDepth; static const int MaxThreadTrackInfo; struct FiberSwitchInfo { unsigned __int64 timeStamp; DWORD threadID; size_t callStack[1]; }; FiberSwitchInfo *m_pFiberInfo[ThreadTrackInfo_Max]; DWORD m_FiberInfoIndex[ThreadTrackInfo_Max]; #endif private: DWORD m_dwPrepareCer; private: static int m_offset_counter; static const int offset_multiplier = 128; typedef struct { LPTHREAD_START_ROUTINE lpThreadFunction; PVOID lpArg; } intermediateThreadParam; #ifdef _DEBUG private: BOOL m_bGCStressing; // the flag to indicate if the thread is doing a stressing GC BOOL m_bUniqueStacking; // the flag to indicate if the thread is doing a UniqueStack #endif private: DWORD m_dwAVInRuntimeImplOkayCount; #ifdef _DEBUG private: DWORD m_dwUnbreakableLockCount; #endif // _DEBUG private: LONG m_dwHostTaskRefCount; private: Exception* m_pExceptionDuringStartup; #if defined(USE_DBGHELP_TO_WALK_STACK_IN_DAC) private: static HMODULE s_hDbgHelp; static PDBGHELP__STACKWALK s_pfnStackWalk; static PDBGHELP__SYMINITIALIZE s_pfnSymInitialize; static PDBGHELP__SYMCLEANUP s_pfnSymCleanup; static HANDLE s_hFakeProcess; static HANDLE s_hFakeThread; #endif // USE_DBGHELP_TO_WALK_STACK_IN_DAC #if defined(STRESS_HEAP) && defined(_DEBUG) private: BYTE* m_pbDestCode; BYTE* m_pbSrcCode; #endif // defined(STRESS_HEAP) && defined(_DEBUG) #if defined(STACK_GUARDS_DEBUG) private: BaseStackGuard *m_pCurrentStackGuard; #endif private: BOOL m_fCompletionPortDrained; private: SIZE_T m_RequestedStackSize; private: volatile PVOID m_WorkingOnThreadContext; private: EXCEPTION_POINTERS m_SOExceptionInfo; private: BOOL m_fAllowProfilerCallbacks; private: volatile LONG m_dwThreadHandleBeingUsed; private: static BOOL s_fCleanFinalizedThread; } </pre> <br /> 중간 쯤에 보면 "OBJECTHANDLE m_ExposedObject;" 필드 정의가 나오고 그것이 대략 0xc0 위치라고 하니 Thread 타입이 얼마나 거대한지 대략 짐작케 합니다. 정리해 보면, CLI의 Thread 클래스는 m_ExposedObject로 .NET Thread 개체를 가리키고, .NET Thread 개체는 내부에 DONT_USE_InternalThread 필드로 CLI의 Thread 클래스를 가리킵니다.<br /> <br /> 재미있는 것은, CLI의 Thread 정의를 보면 "DWORD m_OSThreadId;" 필드도 볼 수 있습니다. 그렇다면 ThreadObject를 덤프해 보면 분명히 Native Thread ID가 출력된다는 것인데요. 그래서 windbg를 통해 테스트를 해봤습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:004> <span style='color: blue; font-weight: bold'>.loadby sos clr</span> 0:004> <span style='color: blue; font-weight: bold'>!threads</span> ThreadCount: 3 UnstartedThread: 0 BackgroundThread: 2 PendingThread: 0 DeadThread: 0 Hosted Runtime: no Lock ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception 0 1 553c 00b73b28 203a220 Preemptive 0254D900:00000000 00b6e1f0 0 MTA 2 2 452c 00b811a0 2b220 Preemptive 00000000:00000000 00b6e1f0 0 MTA (Finalizer) 3 3 <span style='color: blue; font-weight: bold'>18a0 00ba9870</span> 202b020 Preemptive 0250428C:00000000 00b6e1f0 0 MTA </pre> <br /> 테스트는 3번 스레드로 DONT_USE_InternalThread == ThreadOBJ == 0x00ba9870이고 그것의 Native Thread ID는 0x18a0입니다. 그리고 덤프를 3번정도 해보니 정말로 Native Thread ID가 포함되어 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:003> <span style='color: blue; font-weight: bold'>dd 0xba9870</span> 00ba9870 736c80a0 0202b020 00000000 04a7f0c0 00ba9880 00000000 00b6e1f0 00000000 00000003 00ba9890 00ba9894 00ba9894 00ba9894 00000000 00ba98a0 00000000 00000000 00b64228 7f3cf000 00ba98b0 0250428c 02505fe8 00002000 00000000 00ba98c0 00000000 00000000 00000000 00000000 00ba98d0 00000000 00000013 727e21b4 00ba9238 00ba98e0 00000000 00000000 00000000 00000000 0:003> <span style='color: blue; font-weight: bold'>dd</span> 00ba98f0 00000000 00000000 04000180 00000000 00ba9900 00000000 00000000 00000000 00000000 00ba9910 3b802c20 00000000 00000000 00000000 00ba9920 00000000 00000000 ffffffff ffffffff 00ba9930 ffffffff ffffffff 00000000 00000000 00ba9940 00000000 00000000 00ba38e8 00000000 00ba9950 00000000 00000000 04a80000 04980000 00ba9960 04a00000 cccccccc cccccccc 00000175 0:003> <span style='color: blue; font-weight: bold'>dd</span> 00ba9970 00000000 00000000 00000184 0000004b 00ba9980 00000000 00000214 00000000 00000218 00ba9990 00000000 0000021c 00000000 00000220 00ba99a0 00000000 00000000 000001bb 0000005e 00ba99b0 00000000 00000000 00000063 00000224 00ba99c0 ffffffff ffffffff 00000001 <span style='color: blue; font-weight: bold'>000018a0</span> 00ba99d0 009e12f4 009e11d0 80000000 00000002 00ba99e0 00000000 00000000 0000006e 00000000 </pre> <br /> 이것의 옵셋 위치는 0x15c입니다. (위의 테스트는 Windows 8.1 x64에서 x86 EXE로 테스트한 것입니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 하지만 역시 이번에도 0x15c 옵셋값은 하드 코딩할 수밖에 없습니다. 따라서 각종 닷넷 버전에 따라 이 옵셋은 바뀔 수 있고 심지어 .NET 서비스 팩에서조차 바뀔 가능성이 있습니다. 그래도 하드 코딩 값의 횟수가 "<a target='tab' href='http://www.sysnet.pe.kr/2/0/1244'>.NET System.Threading.Thread 개체에서 Native Thread Id를 구할 수 있을까?</a>"에서 소개한 것보다 줄긴 했습니다. ^^<br /> <br /> 잘하면 이 하드 코딩 값을 없앨 수도 있지 않을까요? 가령, ^^ 두 번 정도 (원한다면 더 많이) 스레드를 생성해서 해당 옵셋값을 찾아내는 것을 생각해 볼 수 있습니다. 이런 기준을 가지고 다음의 클래스를 만들어 봤습니다.<br /> <br /> <pre style='height: 400px; margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public class OSThreadIdFinder { const int _maxOffset = 2048; // 최대 2KB까지 thread id 탐색 static int _osThreadIdOffset = 0; const int MUST_MATCH_COUNT = 2; [StructLayout(LayoutKind.Sequential)] public struct MEMORY_BASIC_INFORMATION { public IntPtr BaseAddress; public IntPtr AllocationBase; public uint AllocationProtect; public IntPtr RegionSize; public uint State; public uint Protect; public uint Type; } [DllImport("kernel32.dll")] static extern int VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress, ref MEMORY_BASIC_INFORMATION lpBuffer, uint dwLength); internal static bool Initialize() { int oldOffset = 0; int count = 0; int maxTry = 10; while (maxTry -- > 0) // (10 - 1)개의 스레드를 테스트하는 동안, 같은 위치의 thread id 옵셋 값을 연이어 MUST_MATCH_COUNT만큼 발견해야 한다. { int currentOffset = FindOffset(); if (oldOffset == 0 || oldOffset != currentOffset) { count = 0; oldOffset = currentOffset; continue; } count ++; if (count == MUST_MATCH_COUNT) { _osThreadIdOffset = currentOffset; break; } } return _osThreadIdOffset != 0; } public class ThreadParam { public EventWaitHandle ChildWaitHandle { get; set; } public EventWaitHandle ParentWaitHandle { get; set; } public int ThreadId { get; set; } } public static int GetOsThreadId(Thread thread) { if (_osThreadIdOffset == 0) { return 0; } IntPtr ptr = GetThreadObjectAddress(thread); return Marshal.ReadInt32(ptr, _osThreadIdOffset); } private static int FindOffset() { EventWaitHandle ewhChild = new EventWaitHandle(false, EventResetMode.ManualReset); EventWaitHandle ewhParent = new EventWaitHandle(false, EventResetMode.ManualReset); Thread thread = new Thread(dummyFunc); ThreadParam threadParam = new ThreadParam { ChildWaitHandle = ewhChild, ParentWaitHandle = ewhParent }; thread.Start(threadParam); ewhChild.WaitOne(); IntPtr objValue = GetThreadObjectAddress(thread); if (objValue.ToInt64() == 0) { return 0; } IntPtr processHandle = Process.GetCurrentProcess().Handle; MEMORY_BASIC_INFORMATION mbi = new MEMORY_BASIC_INFORMATION(); uint length = (uint)Marshal.SizeOf(mbi); int returnLength = VirtualQueryEx(processHandle, objValue, ref mbi, length); if (returnLength == 0) { return 0; } long pageLimit = mbi.BaseAddress.ToInt64() + mbi.RegionSize.ToInt64() - objValue.ToInt64(); int maxOffset = (int)Math.Min((long)_maxOffset, pageLimit); try { for (int i = 0; i < maxOffset; i += 4) { int osThreadIdCandidate = Marshal.ReadInt32(objValue, i); if (osThreadIdCandidate == threadParam.ThreadId) { return i; } } } catch { } finally { ewhParent.Set(); } return 0; } private static IntPtr GetThreadObjectAddress(Thread thread) { try { FieldInfo fieldInfo = typeof(Thread).GetField("DONT_USE_InternalThread", BindingFlags.NonPublic | BindingFlags.Instance); if (fieldInfo == null) { return IntPtr.Zero; } return (IntPtr)fieldInfo.GetValue(thread); } catch { return IntPtr.Zero; } } private static void dummyFunc(object obj) { ThreadParam threadParam = obj as ThreadParam; if (threadParam == null) { return; } threadParam.ThreadId = AppDomain.GetCurrentThreadId(); threadParam.ChildWaitHandle.Set(); threadParam.ParentWaitHandle.WaitOne(); } } </pre> <br /> 이 클래스를 사용해 다음과 같이 OS Thread ID를 구할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <span style='color: blue; font-weight: bold'>OSThreadIdFinder.Initialize();</span> Thread newThread = new Thread(Run); newThread.Start(); Console.WriteLine("OS Thrad ID: " + <span style='color: blue; font-weight: bold'>OSThreadIdFinder.GetOsThreadId(newThread)</span>); </pre> <br /> 이 정도면, 하드 코딩 값을 어느 정도는 없앴기 때문에 "<a target='tab' href='http://www.sysnet.pe.kr/2/0/1244'>.NET System.Threading.Thread 개체에서 Native Thread Id를 구할 수 있을까?</a>" 글의 방식보다는 훨씬 안정적으로 Native Thread Id 값을 구할 수 있습니다.<br /> <br /> 뭐랄까... 좀 멋지게 말하면 thread id를 구하기 위해 machine learning을 집어 넣었다고 할 수도... (쿨럭!)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 재미있는 것이 하나 더 있다면? DONT_USE_InternalThread 필드가 가리키는 C/C++ ThreadObject의 클래스 정의를 보면 ICLRTask 인터페이스를 상속받았음을 알 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > class Thread: public <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/hosting/iclrtask-interface'>ICLRTask</a> { // ...[생략]... } </pre> <br /> C++/CLI의 도움을 받아 DONT_USE_InternalThread 필드의 IntPtr 값으로부터 인터페이스 타입으로 형변환을 하면 ICLRTask의 메서드를 호출하는 것이 가능합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // ClassLibrary1.h #pragma once #include <mscoree.h> using namespace System; namespace ClassLibrary1 { public ref class Class1 { public: __int64 LocksHeld(void *pTask) { ICLRTask *pClrTask = (ICLRTask *)pTask; SIZE_T lockCount = 0; <span style='color: blue; font-weight: bold'>pClrTask->LocksHeld(&lockCount);</span> return lockCount; } __int64 GetPerThreadAllocation(void *pTask) { ICLRTask *pClrTask = (ICLRTask *)pTask; COR_GC_THREAD_STATS memUsage; int size = sizeof(COR_GC_THREAD_STATS); memset(&memUsage, 0, size); <span style='color: blue; font-weight: bold'>pClrTask->GetMemStats(&memUsage);</span> return memUsage.PerThreadAllocation; } }; } </pre> <br /> 재미있는 시도이긴 한데, ICLRTask로 별다르게 구해올 수 있는 유용한 정보가 없습니다. 그나마 제가 선택한 것이 <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/hosting/iclrtask-locksheld-method'>LocksHeld</a>, <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/hosting/iclrtask-getmemstats-method'>GetMemStats</a>였는데 아래와 같이 사용해 보면 사실상 우리가 원하는 그런 정보는 아닌 듯 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static object _lockClass = new object(); static void Main(string[] args) { lock (_lockClass) { IntPtr threadObjectAddress = OSThreadIdFinder.GetThreadObjectAddress(newThread); ClassLibrary1.Class1 cl = new ClassLibrary1.Class1(); // C++/CLI 클래스 long curLocks = cl.<span style='color: blue; font-weight: bold'>LocksHeld</span>(threadObjectAddress.ToPointer()); Console.WriteLine("LocksHeld: " + curLocks); // 출력 결과: 0 (1을 기대했는데!) } unsafe { IntPtr threadObjectAddress = OSThreadIdFinder.GetThreadObjectAddress(newThread); ClassLibrary1.Class1 cl = new ClassLibrary1.Class1(); long perThreadAllocation = cl.<span style='color: blue; font-weight: bold'>GetPerThreadAllocation</span>(threadObjectAddress.ToPointer()); Console.WriteLine("Old: " + perThreadAllocation); // 출력 결과: 16384 GC.Collect(); List<int> list = new List<int>(); for (int i = 0; i < 1024; i++) { list.Add(i); } long newPerThreadAllocation = cl.<span style='color: blue; font-weight: bold'>GetPerThreadAllocation</span>(threadObjectAddress.ToPointer()); Console.WriteLine("New: " + newPerThreadAllocation); // 출력 결과: 16384 Console.WriteLine(newPerThreadAllocation - perThreadAllocation); // 출력 결과: 0 (변화를 기대했는데!) } } </pre> <br /> <hr style='width: 50%' /><br /> <br /> 그 외에 제가 시도한 방법이 하나 더 있었는데요. Managed Thread의 DONT_USE_InternalThread로부터 TEB 값을 읽어들인 후 현재 실행 중인 모든 Native 스레드를 열람해서 같은 TEB 값을 찾는 것이었습니다. 일단 Native 스레드를 열람하는 코드에서,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > foreach (ProcessThread item in Process.GetCurrentProcess().Threads) { // ...[item.Id == Native 스레드 ID]... } </pre> <br /> 스레드 ID로로부터 Thread Handle 값을 얻어내고, 이후 NtQueryInformationThread API를 이용해 TEB 값을 구할 수 있습니다. 최종 코드는 다음과 같습니다.<br /> <br /> <pre style='height: 400px; margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading; namespace ConsoleApplication1 { class Program { [DllImport("ntdll.dll", SetLastError = true, EntryPoint = "NtQueryInformationThread")] static extern int NtQueryInformationThread(IntPtr pHandle, _THREAD_INFORMATION_CLASS infoClass, ref ThreadBasicInformation instance, int sizeOfInstance, out int length); [DllImport("kernel32.dll", SetLastError = true)] static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, int dwThreadId); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool CloseHandle(IntPtr hObject); static void Main(string[] args) { Thread newThread = new Thread(Run); newThread.Start(); foreach (ProcessThread item in Process.GetCurrentProcess().Threads) { IntPtr pHandle = OpenThread(ThreadAccess.QUERY_INFORMATION, false, item.Id); Console.Write("TID: " + item.Id + ", "); int sizeOfTbi = 0; int length = 0; int result = 0; int error = 0; ThreadBasicInformation tbi = new ThreadBasicInformation(); sizeOfTbi = Marshal.SizeOf(tbi); result = NtQueryInformationThread(pHandle, _THREAD_INFORMATION_CLASS.ThreadBasicInformation, ref tbi, sizeOfTbi, out length); if (result == 0) { Console.WriteLine("TEB: " + tbi.TebBaseAddress.ToString("x")); } else { error = Marshal.GetLastWin32Error(); } if (error != 0) { Console.WriteLine("Failed: " + error); } CloseHandle(pHandle); } } static void Run() { while (true) { Thread.Sleep(1000); } } } [Flags] public enum ThreadAccess : int { TERMINATE = (0x0001), SUSPEND_RESUME = (0x0002), GET_CONTEXT = (0x0008), SET_CONTEXT = (0x0010), SET_INFORMATION = (0x0020), QUERY_INFORMATION = (0x0040), SET_THREAD_TOKEN = (0x0080), IMPERSONATE = (0x0100), DIRECT_IMPERSONATION = (0x0200), } public enum _THREAD_INFORMATION_CLASS { ThreadBasicInformation, ThreadTimes, ThreadPriority, ThreadBasePriority, ThreadAffinityMask, ThreadImpersonationToken, ThreadDescriptorTableEntry, ThreadEnableAlignmentFaultFixup, ThreadEventPair, ThreadQuerySetWin32StartAddress, ThreadZeroTlsCell, ThreadPerformanceCount, ThreadAmILastThread, ThreadIdealProcessor, ThreadPriorityBoost, ThreadSetTlsArrayAddress, ThreadIsIoPending, ThreadHideFromDebugger } [StructLayout(LayoutKind.Sequential, Pack = 4)] public struct ClientId { IntPtr UniqueProcess; IntPtr UniqueThread; } [StructLayout(LayoutKind.Sequential)] public struct ThreadBasicInformation { public int ExitStatus; public IntPtr TebBaseAddress; public ClientId ClientId; public IntPtr AffinityMask; // x86 == 4, x64 == 8 public int Priority; public int BasePriority; } } </pre> <br /> 참고로, NtQueryInformationThread은 ntdll.dll에서 export하고 있는 API인데 C/C++에서는 이렇게 코딩할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > #pragma comment(lib,"ntdll.lib") typedef long NTSTATUS; typedef long KPRIORITY; typedef struct _CLIENT_ID { HANDLE UniqueProcess; HANDLE UniqueThread; } CLIENT_ID, *PCLIENT_ID; typedef struct _THREAD_BASIC_INFORMATION { NTSTATUS ExitStatus; PVOID TebBaseAddress; CLIENT_ID ClientId; KAFFINITY AffinityMask; // KAFFINITY ==> ULONG_PTR ==> unsigned __int64 KPRIORITY Priority; // KPRIORITY ==> LONG ==> long (C++) KPRIORITY BasePriority; } THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION; typedef enum _THREADINFOCLASS { ThreadBasicInformation, ThreadTimes, ThreadPriority, ThreadBasePriority, ThreadAffinityMask, ThreadImpersonationToken, ThreadDescriptorTableEntry, ThreadEnableAlignmentFaultFixup, ThreadEventPair_Reusable, ThreadQuerySetWin32StartAddress, ThreadZeroTlsCell, ThreadPerformanceCount, ThreadAmILastThread, ThreadIdealProcessor, ThreadPriorityBoost, ThreadSetTlsArrayAddress, ThreadIsIoPending, ThreadHideFromDebugger, ThreadBreakOnTermination, MaxThreadInfoClass } THREADINFOCLASS; extern "C" { NTSTATUS WINAPI NtQueryInformationThread( _In_ HANDLE ThreadHandle, _In_ THREADINFOCLASS ThreadInformationClass, _Inout_ PVOID ThreadInformation, _In_ ULONG ThreadInformationLength, _Out_opt_ PULONG ReturnLength ); } THREAD_BASIC_INFORMATION ThreadInfo; DWORD ntstatus = NtQueryInformationThread( GetCurrentThread(), ThreadBasicInformation, &ThreadInfo, sizeof(THREAD_BASIC_INFORMATION), 0); </pre> <br /> 마이크로소프트에서는 kernel32.dll을 통해 NtQueryInformationThread 대신 GetThreadInformation API를 제공하고 있는데요.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > GetThreadInformation function ; <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadinformation'>https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadinformation</a> </pre> <br /> 아쉽게도 문서에 설명된 것처럼 ThreadInformationClass로 전달할 수 있는 인자가 ThreadMemoryPriority로 제한되기 때문에 ThreadBasicInformation 값으로는 오류가 반환됩니다. <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > int size = sizeof(THREAD_BASIC_INFORMATION); THREAD_BASIC_INFORMATION threadInfo; // 아래의 API는 동작하지 않음. BOOL Success = GetThreadInformation(GetCurrentThread(), (THREAD_INFORMATION_CLASS)0, &threadInfo, sizeof(threadInfo)); DWORD dwError = ::GetLastError(); // 이렇게 ThreadMemoryPriority로만 사용이 제한됨. //MEMORY_PRIORITY_INFORMATION MemPrio; //Success = GetThreadInformation(GetCurrentThread(), // ThreadMemoryPriority, // &MemPrio, // sizeof(MemPrio)); </pre> <br /> 아직은 NtQueryInformationThread API가 비공식이지만 사용법에 대해서는 low-level 작업하는 분야에서는 제법 사용되고 있으므로 마이크로소프트 측에서도 쉽게 바꿀 수는 없을 것입니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 스레드 열거하기 ; <a target='tab' href='http://www.jiniya.net/wp/archives/7676'>http://www.jiniya.net/wp/archives/7676</a> _THREAD_BASIC_INFORMATION Struct Reference ; <a target='tab' href='http://processhacker.sourceforge.net/doc/struct___t_h_r_e_a_d___b_a_s_i_c___i_n_f_o_r_m_a_t_i_o_n.html'>http://processhacker.sourceforge.net/doc/struct___t_h_r_e_a_d___b_a_s_i_c___i_n_f_o_r_m_a_t_i_o_n.html</a> </pre> <br /> 허긴... 그렇다고는 해도 ntdll.dll에 있던 NtCurrentTeb API의 경우 32비트에만 제공되었고 64비트에서는 삭제되는 이변이 발생하긴 했습니다.<br /> <br /> 암튼, 위의 방법은 .NET Thread 개체로부터 TEB 주소를 구할 때 사용하면 좋을 것 같습니다. 만약 다른 스레드가 아닌 현재 스레드의 TEB를 구하는 것이라면 다음의 방법도 고려해 볼 수 있겠고. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .NET x64 응용 프로그램에서 Teb 주소를 구하는 방법 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1388'>http://www.sysnet.pe.kr/2/0/1388</a> </pre> </p><br /> <br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1276
(왼쪽의 숫자를 입력해야 합니다.)