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

... 121  122  123  124  125  126  127  128  129  130  131  132  [133]  134  135  ...
NoWriterDateCnt.TitleFile(s)
1763정성태9/30/201425173.NET Framework: 468. PDB 파일을 연동해 소스 코드 라인 정보를 알아내는 방법파일 다운로드1
1762정성태9/30/201426019.NET Framework: 467. 닷넷에서 EIP/RIP 레지스터 값을 구하는 방법 [1]파일 다운로드1
1761정성태9/29/201423116.NET Framework: 466. 윈도우 운영체제의 보안 그룹 이름 및 설명 문자열을 바꾸는 방법파일 다운로드1
1760정성태9/28/201421111.NET Framework: 465. ICorProfilerInfo::GetILToNativeMapping 메서드가 0x80131358을 반환하는 경우
1759정성태9/27/201432475개발 환경 구성: 240. Visual C++ / x64 환경에서 inline-assembly를 매크로 어셈블리로 대체하는 방법파일 다운로드1
1758정성태9/23/201439044개발 환경 구성: 239. 원격 데스크톱 접속(RDP)을 기존의 콘솔 모드처럼 사용하는 방법 [1]
1757정성태9/23/201419664오류 유형: 242. Lync로 모임 참여 시 소리만 들리지 않는 경우 - 두 번째 이야기
1756정성태9/23/201428793기타: 48. NVidia 제품의 과다한 디스크 사용 [2]
1755정성태9/22/201435620오류 유형: 241. Unity Web Player를 설치해도 여전히 설치하라는 화면이 나오는 경우 [4]
1754정성태9/22/201425943VC++: 80. 내 컴퓨터에서 C++ AMP 코드가 실행이 될까요? [1]
1753정성태9/22/201421738오류 유형: 240. Lync로 세미나 참여 시 소리만 들리지 않는 경우 [1]
1752정성태9/21/201441883Windows: 100. 윈도우 8 - RDP 연결을 이용해 VNC처럼 사용자 로그온 화면을 공유하는 방법 [5]
1751정성태9/20/201440029.NET Framework: 464. 프로세스 간 통신 시 소켓 필요 없이 간단하게 Pipe를 열어 통신하는 방법 [1]파일 다운로드1
1750정성태9/20/201425271.NET Framework: 463. PInvoke 호출을 이용한 비동기 파일 작업파일 다운로드1
1749정성태9/20/201425009.NET Framework: 462. 커널 객체를 위한 null DACL 생성 방법파일 다운로드1
1748정성태9/19/201426554개발 환경 구성: 238. [Synergy] 여러 컴퓨터에서 키보드, 마우스 공유
1747정성태9/19/201429803오류 유형: 239. psexec 실행 오류 - The system cannot find the file specified.
1746정성태9/18/201426751.NET Framework: 461. .NET EXE 파일을 닷넷 프레임워크 버전에 상관없이 실행할 수 있을까요? - 두 번째 이야기 [6]파일 다운로드1
1745정성태9/17/201424292개발 환경 구성: 237. 리눅스 Integration Services 버전 업그레이드 하는 방법 [1]
1744정성태9/17/201432207.NET Framework: 460. GetTickCount / GetTickCount64와 0x7FFE0000 주솟값 [4]파일 다운로드1
1743정성태9/16/201422024오류 유형: 238. 설치 오류 - Failed to get size of pseudo bundle
1742정성태8/27/201428358개발 환경 구성: 236. Hyper-V에 설치한 리눅스 VM의 VHD 크기 늘리는 방법 [2]
1741정성태8/26/201422465.NET Framework: 459. GetModuleHandleEx로 알아보는 .NET 메서드의 DLL 모듈 관계파일 다운로드1
1740정성태8/25/201433847.NET Framework: 458. 닷넷 GC가 순환 참조를 해제할 수 있을까요? [2]파일 다운로드1
1739정성태8/24/201427689.NET Framework: 457. 교착상태(Dead-lock) 해결 방법 - Lock Leveling [2]파일 다운로드1
1738정성태8/23/201423382.NET Framework: 456. C# - CAS를 이용한 Lock 래퍼 클래스파일 다운로드1
... 121  122  123  124  125  126  127  128  129  130  131  132  [133]  134  135  ...