Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 6개 있습니다.)
(시리즈 글이 2개 있습니다.)
.NET Framework: 308. .NET System.Threading.Thread 개체에서 Native Thread Id를 구할 수 있을까?
; https://www.sysnet.pe.kr/2/0/1244

.NET Framework: 452. .NET System.Threading.Thread 개체에서 Native Thread Id를 구하는 방법 - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/1724




.NET System.Threading.Thread 개체에서 Native Thread Id를 구할 수 있을까?

결론부터 말씀드리면, 현재 '공식적인 방법'으로는 System.Threading.Thread 개체에서 관리되지 않은(Unmanaged) 형식의 스레드 ID를 구할 수 있는 방법은 없습니다.

이 글은 '비공식적인 방법'에 대해 설명하는 것이니, 이 점 감안하고 읽어주시기 바랍니다. ^^




평소에 가끔씩 궁금해 하다가, 이번에는 작정하고 웹 검색을 해보았지만 애석하게도 그에 대한 방법은 전혀 찾을 수가 없었습니다. 그래서, 혹시나 싶어 Visual Studio 디버깅 상태에서 Thread 개체의 내부 필드를 뜯어보기 시작했습니다. 다른 필드는 모두 희망이 없었는데 유독 한 필드가 눈에 띄었으니, 바로 "DONT_USE_InternalThread" 필드가 그것입니다.

수많은 보안 프로그램들도 "Undocumented API" 없이는 만들어지지 못했을 터... 사용하지 말라고 써 있는 것을 보니 더 사용하고 싶어집니다. ^^;

그래서 이에 대해 검색을 해봤는데,

Correlating between .NET and native thread in Windbg
; http://naveensrinivasan.com/2011/01/08/correlating-net-managed-thread-and-native-thread-in-windbg/ 

오호~~~ 그 값이 가리키고 있는 주소의 40번째 위치에 TEB(Thread Environment Block) 값이 있다는 것입니다. 윈도우 시스템 프로그래밍을 하시는 분들은 기억하시겠지만, TEB는 수행 중인 스레드 문맥 내의 FS 레지스터 0x18에서 구할 수 있는 값입니다. PEB(Process Environment Block)의 경우에도 스레드 문맥의 FS:[0x30]에서 구할 수 있는데, TEB 구조체 내에서 PEB를 가리키는 필드를 포함하고 있기 때문에 TEB를 구할 수 있다는 것은 곧, PEB까지 접근할 수 있다는 이야기가 됩니다. 한마디로, .NET에서도 어느 정도의 시스템 정보 값을 얻어오는 것이 가능하다는 이야기가 됩니다. ^^

하지만 아쉽게도 제가 테스트했을 때는 그 위치에 TEB 값이 없었습니다.

아마도, 제 환경이 Windows 7 x64 + .NET 4.0이어서 위의 글을 쓴 환경과는 틀려서 그런 것 같은데요... 그래도 어쨌든 "DONT_USE_InternalThread"가 가리키는 어딘가에는 그 값이 있다는 의미이니 한번 찾아봐야 할 것 같습니다.

이를 위해 TEB 값을 대상 스레드에서 구해야 할텐데요. ntdll.dll의 NtCurrentTeb 함수를 P/Invoke로 연결하고, DONT_USE_InternalThread 값을 구해오는 코드를 다음과 같이 작성을 한 후,

Thread newThread = new Thread(Run);
newThread.Start();

FieldInfo fieldInfo = typeof(Thread).GetField("DONT_USE_InternalThread", BindingFlags.NonPublic | BindingFlags.Instance);
IntPtr objValue = (IntPtr)fieldInfo.GetValue(newThread);
Console.WriteLine("DONT_USE_InternalThread: " + objValue.ToInt64().ToString("x"));

[DllImport("ntdll.dll")]
static extern IntPtr NtCurrentTeb();

static void Run()
{
    IntPtr teb = NtCurrentTeb();
    Console.WriteLine("Teb: " + teb.ToInt64().ToString("x"));

    while (true)
    {
        Thread.Sleep(1000);
    }
}

Visual Studio 디버거로 실행해서 DONT_USE_InternalThread 값을 얻은 라인에서 BP를 멈추었을 때 각각 다음과 같은 정보를 얻을 수 있었습니다.

DONT_USE_InternalThread: 0x05697eb8
Teb: 0x7efa6000

이제 메모리 윈도우(Ctrl + D,Y)를 띄워서 DONT_USE_InternalThread 위치를 확인해 보면 그 안에서 Teb 값을 찾을 수 있습니다.

thread_native_id_1.png

(Little endian이므로 0x7efa6000 값이 "00 60 fa 7e"로 메모리에 들어 있습니다.)

이것으로 옵셋값은 64바이트임을 알 수 있습니다. 자... 어찌되었든 이렇게 해서 System.Threading.Thread 개체에서 TEB 값을 구할 수 있었습니다. 이에 대한 코드를 다시 구성해 보면 다음과 같습니다.

Thread newThread = new Thread(Run);
newThread.Start();

FieldInfo fieldInfo = typeof(Thread).GetField("DONT_USE_InternalThread", BindingFlags.NonPublic | BindingFlags.Instance);
IntPtr objValue = (IntPtr)fieldInfo.GetValue(newThread);

int teb = Marshal.ReadInt32(objValue, 16 * 4);
Console.WriteLine("teb: " + teb.ToString("x"));

자... 이제 게임 끝이군요. ^^ TEB를 구했으니, TEB 구조체 안에 있는 Native Thread Id를 구하면 됩니다. 제 컴퓨터에서 .NET 4.0 x86 응용 프로그램을 Windbg로 디버깅 해보면 다음과 같은 TEB 구조를 얻을 수 있습니다.

0:006> dt _TEB
DotnetProfiler!_TEB
   +0x000 NtTib            : _NT_TIB
   +0x01c EnvironmentPointer : Ptr32 Void
   +0x020 ClientId         : _CLIENT_ID
   +0x028 ActiveRpcHandle  : Ptr32 Void
   +0x02c ThreadLocalStoragePointer : Ptr32 Void
   +0x030 ProcessEnvironmentBlock : Ptr32 _PEB
   +0x034 LastErrorValue   : Uint4B
   +0x038 CountOfOwnedCriticalSections : Uint4B
   +0x03c CsrClientThread  : Ptr32 Void
   +0x040 Win32ThreadInfo  : Ptr32 Void
   +0x044 User32Reserved   : [26] Uint4B
   +0x0ac UserReserved     : [5] Uint4B
   +0x0c0 WOW32Reserved    : Ptr32 Void
   +0x0c4 CurrentLocale    : Uint4B
   +0x0c8 FpSoftwareStatusRegister : Uint4B
   +0x0cc SystemReserved1  : [54] Ptr32 Void
   +0x1a4 ExceptionCode    : Int4B
   +0x1a8 ActivationContextStack : _ACTIVATION_CONTEXT_STACK
   +0x1bc SpareBytes1      : [24] UChar
   +0x1d4 GdiTebBatch      : _GDI_TEB_BATCH
   +0x6b4 RealClientId     : _CLIENT_ID
   +0x6bc GdiCachedProcessHandle : Ptr32 Void
   +0x6c0 GdiClientPID     : Uint4B
   +0x6c4 GdiClientTID     : Uint4B
   +0x6c8 GdiThreadLocalInfo : Ptr32 Void
   +0x6cc Win32ClientInfo  : [62] Uint4B
   ...[생략]...

그리고, 이것의 0x6b4 위치에 있는 _CLIENT_ID의 구조체에 우리가 원하던 정보가 있습니다.

0:006> dt _CLIENT_ID
DotnetProfiler!_CLIENT_ID
   +0x000 UniqueProcess    : Ptr32 Void
   +0x004 UniqueThread     : Ptr32 Void

이로써, System.Threading.Thread 개체에서 Native Thread Id를 구하는 최종 소스 코드는 다음과 같습니다.

Thread newThread = new Thread(Run);
newThread.Start();

FieldInfo fieldInfo = typeof(Thread).GetField("DONT_USE_InternalThread", BindingFlags.NonPublic | BindingFlags.Instance);
IntPtr objValue = (IntPtr)fieldInfo.GetValue(newThread);
Console.WriteLine("DONT_USE_InternalThread: " + objValue.ToInt64().ToString("x"));

IntPtr teb = new IntPtr(Marshal.ReadInt32(objValue, 16 * 4));
Console.WriteLine("teb: " + teb.ToString("x"));

int clientPid = Marshal.ReadInt32(teb, 0x6b4);
int clientTid = Marshal.ReadInt32(teb, 0x6b8);

Console.WriteLine("Pid2: " + clientPid.ToString("x"));
Console.WriteLine("Tid2: " + clientTid.ToString("x"));

물론 위의 코드는 적용되는 컴퓨터 환경에 따라 적절하게 옵셋값들이 수정되어야 하는데, 우선 .NET 버전에 따라 DONT_USE_InternalThread의 구조체 값이 바뀔 수 있으므로 64바이트 옵셋을 그에 맞게 테스트해서 수정해 주어야 합니다. 또한 TEB 구조체는 x86/x64에 달라질 수 있고 운영체제에도 의존적이니 역시 그것도 고려해 주어야 합니다.

그렇게 따지면, 정말 특별한 상황이 아니고서는 private DONT_USE_InternalThread 필드를 사용해서는 안될 것입니다. 단지, 위의 글은 "닷넷 프레임워크"를 좀 더 이해하는 차원에서 읽어주시면 되겠습니다. ^^

(첨부된 파일은 위의 코드를 포함한 예제 프로젝트입니다.)




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 7/10/2021]

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

비밀번호

댓글 작성자
 



2017-01-22 06시02분
.NET System.Threading.Thread 개체에서 Native Thread Id를 구하는 방법 - 두 번째 이야기
; http://www.sysnet.pe.kr/2/0/1724

--------------------------------------

DONT_USE_InternalThread 필드 값을 .NET 8의 'UnsafeAccessor' 특성을 이용해,

C# - .NET 8 런타임부터 (Reflection 없이) 특성을 이용해 public이 아닌 멤버 호출 가능
; https://www.sysnet.pe.kr/2/0/13436

좀 더 우아하게 구하는 코드를,

Dumping the managed heap in C#
; https://minidump.net/dumping-the-managed-heap-in-csharp/

위의 글에서 담고 있는데, 아래와 같이 정리할 수 있습니다. ^^

static void Main()
{
    ref var nativeThread = ref GetNativeThread(Thread.CurrentThread);
}

[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_DONT_USE_InternalThread")]
static extern ref IntPtr GetNativeThread(Thread thread);
정성태

[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13608정성태4/26/2024395닷넷: 2249. C# - 부모의 필드/프로퍼티에 대해 서로 다른 자식 클래스 간에 Reflection 접근이 동작할까요?파일 다운로드1
13607정성태4/25/2024392닷넷: 2248. C# - 인터페이스 타입의 다중 포인터를 인자로 갖는 C/C++ 함수 연동
13606정성태4/24/2024407닷넷: 2247. C# - tensorflow 연동 (MNIST 예제)파일 다운로드1
13605정성태4/23/2024631닷넷: 2246. C# - Python.NET을 이용한 파이썬 소스코드 연동파일 다운로드1
13604정성태4/22/2024656오류 유형: 901. Visual Studio - Unable to set the next statement. Set next statement cannot be used in '[Exception]' call stack frames.
13603정성태4/21/2024854닷넷: 2245. C# - IronPython을 이용한 파이썬 소스코드 연동파일 다운로드1
13602정성태4/20/2024918닷넷: 2244. C# - PCM 오디오 데이터를 연속(Streaming) 재생 (Windows Multimedia)파일 다운로드1
13601정성태4/19/2024939닷넷: 2243. C# - PCM 사운드 재생(NAudio)파일 다운로드1
13600정성태4/18/2024945닷넷: 2242. C# - 관리 스레드와 비관리 스레드
13599정성태4/17/2024910닷넷: 2241. C# - WAV 파일의 PCM 사운드 재생(Windows Multimedia)파일 다운로드1
13598정성태4/16/2024949닷넷: 2240. C# - WAV 파일 포맷 + LIST 헤더파일 다운로드2
13597정성태4/15/2024916닷넷: 2239. C# - WAV 파일의 PCM 데이터 생성 및 출력파일 다운로드1
13596정성태4/14/20241077닷넷: 2238. C# - WAV 기본 파일 포맷파일 다운로드1
13595정성태4/13/20241065닷넷: 2237. C# - Audio 장치 열기 (Windows Multimedia, NAudio)파일 다운로드1
13594정성태4/12/20241081닷넷: 2236. C# - Audio 장치 열람 (Windows Multimedia, NAudio)파일 다운로드1
13593정성태4/8/20241090닷넷: 2235. MSBuild - AccelerateBuildsInVisualStudio 옵션
13592정성태4/2/20241226C/C++: 165. CLion으로 만든 Rust Win32 DLL을 C#과 연동
13591정성태4/2/20241201닷넷: 2234. C# - WPF 응용 프로그램에 Blazor App 통합파일 다운로드1
13590정성태3/31/20241081Linux: 70. Python - uwsgi 응용 프로그램이 k8s 환경에서 OOM 발생하는 문제
13589정성태3/29/20241158닷넷: 2233. C# - 프로세스 CPU 사용량을 나타내는 성능 카운터와 Win32 API파일 다운로드1
13588정성태3/28/20241274닷넷: 2232. C# - Unity + 닷넷 App(WinForms/WPF) 간의 Named Pipe 통신 [2]파일 다운로드1
13587정성태3/27/20241359오류 유형: 900. Windows Update 오류 - 8024402C, 80070643
13586정성태3/27/20241532Windows: 263. Windows - 복구 파티션(Recovery Partition) 용량을 늘리는 방법
13585정성태3/26/20241324Windows: 262. PerformanceCounter의 InstanceName에 pid를 추가한 "Process V2"
13584정성태3/26/20241363개발 환경 구성: 708. Unity3D - C# Windows Forms / WPF Application에 통합하는 방법파일 다운로드1
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...