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

... 46  47  48  49  50  51  [52]  53  54  55  56  57  58  59  60  ...
NoWriterDateCnt.TitleFile(s)
12365정성태10/12/202011041.NET Framework: 949. C# 9.0 - (3) 람다 메서드의 매개 변수 무시(Lambda discard parameters)파일 다운로드1
12364정성태10/11/202012250.NET Framework: 948. C# 9.0 - (2) localsinit 플래그 내보내기 무시(Suppress emitting localsinit flag)파일 다운로드1
12363정성태10/11/202013360.NET Framework: 947. C# 9.0 - (1) 대상으로 형식화된 new 식(Target-typed new expressions) [2]파일 다운로드1
12362정성태10/11/202010096VS.NET IDE: 151. Visual Studio 2019에 .NET 5 rc/preview 적용하는 방법
12361정성태10/11/202011746.NET Framework: 946. C# 9.0을 위한 개발 환경 구성
12360정성태10/8/20208794오류 유형: 666. The type or namespace name '...' does not exist in the namespace 'Microsoft.VisualStudio.TestTools' (are you missing an assembly reference?)
12359정성태10/7/202010314오류 유형: 665. Windows - 재부팅 후 iSCSI 연결이 끊기는 문제
12358정성태10/7/202010434오류 유형: 664. Web Deploy 설치 시 "A newer version of Microsoft Web Deploy 3.6 was found on this machine." 오류 [3]
12357정성태10/7/20208349오류 유형: 663. 이벤트 로그 - The storage optimizer couldn't complete retrim on New Volume
12356정성태10/7/202023519오류 유형: 662. ASP.NET Core와 500.19, 500.21 오류 (0x8007000d)
12355정성태10/3/20208498오류 유형: 661. Hyper-V Linux VM의 Internal 유형의 가상 Switch에 대한 IP 연결이 되지 않는 경우
12354정성태10/2/202021463오류 유형: 660. Web Deploy (msdeploy.axd) 실행 시 오류 기록 [1]
12353정성태10/2/202011288개발 환경 구성: 518. 비주얼 스튜디오에서 IIS 웹 서버로 "Web Deploy"를 이용해 배포하는 방법
12352정성태10/2/202011849개발 환경 구성: 517. Hyper-V Internal 네트워크에 NAT을 이용한 인터넷 연결 제공
12351정성태10/2/202011338오류 유형: 659. Nox 실행이 안 되는 경우 - Unable to bind to the underlying transport for ...
12350정성태9/25/202014905Windows: 175. 윈도우 환경에서 클라이언트 소켓의 최대 접속 수 [2]파일 다운로드1
12349정성태9/25/20209600Linux: 32. Ubuntu 20.04 - docker를 위한 tcp 바인딩 추가
12348정성태9/25/202010427오류 유형: 658. 리눅스 docker - Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock
12347정성태9/25/202024809Windows: 174. WSL 2의 네트워크 통신 방법 [4]
12346정성태9/25/20209620오류 유형: 657. IIS - http://localhost 방문 시 Service Unavailable 503 오류 발생
12345정성태9/25/20209186오류 유형: 656. iisreset 실행 시 "Restart attempt failed." 오류가 발생하지만 웹 서비스는 정상적인 경우파일 다운로드1
12344정성태9/25/202010465Windows: 173. 서비스 관리자에 "IIS Admin Service"가 등록되어 있지 않다면?
12343정성태9/24/202020417.NET Framework: 945. C# - 닷넷 응용 프로그램에서 메모리 누수가 발생할 수 있는 패턴 [5]
12342정성태9/24/202011549디버깅 기술: 171. windbg - 인스턴스가 살아 있어 메모리 누수가 발생하고 있는지 확인하는 방법
12341정성태9/23/202010505.NET Framework: 944. C# - 인스턴스가 살아 있어 메모리 누수가 발생하고 있는지 확인하는 방법파일 다운로드1
12340정성태9/23/202010276.NET Framework: 943. WPF - WindowsFormsHost를 담은 윈도우 생성 시 메모리 누수
... 46  47  48  49  50  51  [52]  53  54  55  56  57  58  59  60  ...