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)
13434정성태11/1/20232722스크립트: 62. 파이썬 - class의 정적 함수를 동적으로 교체
13433정성태11/1/20232447스크립트: 61. 파이썬 - 함수 오버로딩 미지원
13432정성태10/31/20232530오류 유형: 878. 탐색기의 WSL 디렉터리 접근 시 "Attempt to access invalid address." 오류 발생
13431정성태10/31/20232888스크립트: 60. 파이썬 - 비동기 FastAPI 앱을 gunicorn으로 호스팅
13430정성태10/30/20232732닷넷: 2153. C# - 사용자가 빌드한 ICU dll 파일을 사용하는 방법
13429정성태10/27/20233011닷넷: 2152. Win32 Interop - C/C++ DLL로부터 이중 포인터 버퍼를 C#으로 받는 예제파일 다운로드1
13428정성태10/25/20233087닷넷: 2151. C# 12 - ref readonly 매개변수
13427정성태10/18/20233296닷넷: 2150. C# 12 - 정적 문맥에서 인스턴스 멤버에 대한 nameof 접근 허용(Allow nameof to always access instance members from static context)
13426정성태10/13/20233455스크립트: 59. 파이썬 - 비동기 호출 함수(run_until_complete, run_in_executor, create_task, run_in_threadpool)
13425정성태10/11/20233231닷넷: 2149. C# - PLinq의 Partitioner<T>를 이용한 사용자 정의 분할파일 다운로드1
13423정성태10/6/20233210스크립트: 58. 파이썬 - async/await 기본 사용법
13422정성태10/5/20233347닷넷: 2148. C# - async 유무에 따른 awaitable 메서드의 병렬 및 예외 처리
13421정성태10/4/20233425닷넷: 2147. C# - 비동기 메서드의 async 예약어 유무에 따른 차이
13420정성태9/26/20235644스크립트: 57. 파이썬 - UnboundLocalError: cannot access local variable '...' where it is not associated with a value
13419정성태9/25/20233256스크립트: 56. 파이썬 - RuntimeError: dictionary changed size during iteration
13418정성태9/25/20233944닷넷: 2146. C# - ConcurrentDictionary 자료 구조의 동기화 방식
13417정성태9/19/20233482닷넷: 2145. C# - 제네릭의 형식 매개변수에 속한 (매개변수를 가진) 생성자를 호출하는 방법
13416정성태9/19/20233284오류 유형: 877. redis-py - MISCONF Redis is configured to save RDB snapshots, ...
13415정성태9/18/20233782닷넷: 2144. C# 12 - 컬렉션 식(Collection Expressions)
13414정성태9/16/20233551디버깅 기술: 193. Windbg - ThreadStatic 필드 값을 조사하는 방법
13413정성태9/14/20233733닷넷: 2143. C# - 시스템 Time Zone 변경 시 이벤트 알림을 받는 방법
13412정성태9/14/20237029닷넷: 2142. C# 12 - 인라인 배열(Inline Arrays) [1]
13411정성태9/12/20233527Windows: 252. 권한 상승 전/후 따로 관리되는 공유 네트워크 드라이브 정보
13410정성태9/11/20235065닷넷: 2141. C# 12 - Interceptor (컴파일 시에 메서드 호출 재작성) [1]
13409정성태9/8/20233908닷넷: 2140. C# - Win32 API를 이용한 모니터 전원 끄기
13408정성태9/5/20233872Windows: 251. 임의로 만든 EXE 파일을 포함한 ZIP 파일의 압축을 해제할 때 Windows Defender에 의해 삭제되는 경우
1  2  3  4  5  6  7  [8]  9  10  11  12  13  14  15  ...