Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 6개 있습니다.)
(시리즈 글이 5개 있습니다.)
.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 Framework: 1189. C# - 런타임 환경에 따라 달라진 AppDomain.GetCurrentThreadId 메서드
; https://www.sysnet.pe.kr/2/0/13024

닷넷: 2311. C# - Windows / Linux 환경에서 Native Thread ID 가져오기
; https://www.sysnet.pe.kr/2/0/13814

닷넷: 2312. C#, C++ - Windows / Linux 환경의 Thread Name 설정
; https://www.sysnet.pe.kr/2/0/13816




.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);
정성태

... 76  77  78  79  [80]  81  82  83  84  85  86  87  88  89  90  ...
NoWriterDateCnt.TitleFile(s)
11936정성태6/10/201918318Math: 58. C# - 최소 자승법의 1차, 2차 수렴 그래프 변화 확인 [2]파일 다운로드1
11935정성태6/9/201919870.NET Framework: 843. C# - PLplot 출력을 파일이 아닌 Window 화면으로 변경
11934정성태6/7/201921200VC++: 133. typedef struct와 타입 전방 선언으로 인한 C2371 오류파일 다운로드1
11933정성태6/7/201919552VC++: 132. enum 정의를 C++11의 enum class로 바꿀 때 유의할 사항파일 다운로드1
11932정성태6/7/201918737오류 유형: 544. C++ - fatal error C1017: invalid integer constant expression파일 다운로드1
11931정성태6/6/201919269개발 환경 구성: 441. C# - CairoSharp/GtkSharp 사용을 위한 프로젝트 구성 방법
11930정성태6/5/201919797.NET Framework: 842. .NET Reflection을 대체할 System.Reflection.Metadata 소개 [1]
11929정성태6/5/201919350.NET Framework: 841. Windows Forms/C# - 클립보드에 RTF 텍스트를 복사 및 확인하는 방법 [1]
11928정성태6/5/201918139오류 유형: 543. PowerShell 확장 설치 시 "Catalog file '[...].cat' is not found in the contents of the module" 오류 발생
11927정성태6/5/201919323스크립트: 15. PowerShell ISE의 스크립트를 복사 후 PPT/Word에 붙여 넣으면 한글이 깨지는 문제 [1]
11926정성태6/4/201919912오류 유형: 542. Visual Studio - pointer to incomplete class type is not allowed
11925정성태6/4/201919724VC++: 131. Visual C++ - uuid 확장 속성과 __uuidof 확장 연산자파일 다운로드1
11924정성태5/30/201921338Math: 57. C# - 해석학적 방법을 이용한 최소 자승법 [1]파일 다운로드1
11923정성태5/30/201920980Math: 56. C# - 그래프 그리기로 알아보는 경사 하강법의 최소/최댓값 구하기파일 다운로드1
11922정성태5/29/201918513.NET Framework: 840. ML.NET 데이터 정규화파일 다운로드1
11921정성태5/28/201924361Math: 55. C# - 다항식을 위한 최소 자승법(Least Squares Method)파일 다운로드1
11920정성태5/28/201916029.NET Framework: 839. C# - PLplot 색상 제어
11919정성태5/27/201920275Math: 54. C# - 최소 자승법의 1차 함수에 대한 매개변수를 단순 for 문으로 구하는 방법 [1]파일 다운로드1
11918정성태5/25/201921128Math: 53. C# - 행렬식을 이용한 최소 자승법(LSM: Least Square Method)파일 다운로드1
11917정성태5/24/201922094Math: 52. MathNet을 이용한 간단한 통계 정보 처리 - 분산/표준편차파일 다운로드1
11916정성태5/24/201919927Math: 51. MathNET + OxyPlot을 이용한 간단한 통계 정보 처리 - Histogram파일 다운로드1
11915정성태5/24/201923051Linux: 11. 리눅스의 환경 변수 관련 함수 정리 - putenv, setenv, unsetenv
11914정성태5/24/201921997Linux: 10. 윈도우의 GetTickCount와 리눅스의 clock_gettime파일 다운로드1
11913정성태5/23/201918752.NET Framework: 838. C# - 숫자형 타입의 bit(2진) 문자열, 16진수 문자열 구하는 방법파일 다운로드1
11912정성태5/23/201918698VS.NET IDE: 137. Visual Studio 2019 버전 16.1부터 리눅스 C/C++ 프로젝트에 추가된 WSL 지원
11911정성태5/23/201917478VS.NET IDE: 136. Visual Studio 2019 - 리눅스 C/C++ 프로젝트에 인텔리센스가 동작하지 않는 경우
... 76  77  78  79  [80]  81  82  83  84  85  86  87  88  89  90  ...