자식 스레드에 자동 상속되는 TEB의 SubProcessTag 필드
"Windows Internals" 책을 보다가,
Windows Internals 제6판 Vol. 1: 마이크로소프트 윈도우 커널 공식 가이드 6판
; http://www.yes24.com/24/goods/7877539
469페이지에 실린 "서비스 태그"라는 내용에서 SubProcessTag 필드의 존재를 처음 알게 되었습니다.
내용인 즉, 마이크로소프트 측에서 Vista부터 제공하고 있던 기능인데 이 필드의 내용은 하위 스레드에 자동으로 전달된다는 것입니다. 오호~~~ 재미있는 기능이군요. ^^
일단, windbg로 x64의 windows 8.1에서 _TEB 구조체를 살펴보면 다음과 같이 0x1720 옵셋에 위치한 것을 확인할 수 있습니다.
0: kd> dt _TEB
nt!_TEB
+0x000 NtTib : _NT_TIB
+0x038 EnvironmentPointer : Ptr64 Void
...[생략]...
+0x1720 SubProcessTag : Ptr64 Void
+0x1818 ReservedForWdf : Ptr64 Void
실제로 이 값을 확인하려면 다음과 같은 식의 코드를 작성하면 됩니다.
// 첨부 파일에 Visual Studio 프로젝트로 포함됨
#include "stdafx.h"
#include <winternl.h>
#include <intrin.h>
void showSbt()
{
#if defined WIN32
#define OFFSET_SUBPROCESSTAG 0x0f60
#elif defined _WIN64
#define OFFSET_SUBPROCESSTAG 0x1720
#else
#endif // _WIN32
unsigned long subProcessTag = 0;
HANDLE hProcess = GetCurrentProcess();
#if defined(_WIN64) // x64
auto pTeb = reinterpret_cast<PTEB>(__readgsqword(reinterpret_cast<DWORD>(&static_cast<NT_TIB*>(nullptr)->Self)));
#elif defined(WIN32) // x86
auto pTeb = reinterpret_cast<PTEB>(__readfsdword(reinterpret_cast<DWORD>(&static_cast<NT_TIB*>(nullptr)->Self)));
#endif
BOOL result = ReadProcessMemory(hProcess, reinterpret_cast<void *>(reinterpret_cast<ULONG_PTR>(pTeb)+OFFSET_SUBPROCESSTAG)
, reinterpret_cast<void *>(&subProcessTag)
, sizeof(subProcessTag),
nullptr);
if (result == TRUE)
{
printf("[TID: %d] %d (0x%x)\n", GetCurrentThreadId(), subProcessTag, subProcessTag);
// 출력결과: [TID: 10216] 0 (0x0)
}
CloseHandle(hProcess);
}
int _tmain(int argc, _TCHAR* argv[])
{
showSbt();
return 0;
}
기본값은 0입니다. 그렇다면 정말 스레드 간에 자동 상속되는지 확인을 해볼까요? ^^
#include "stdafx.h"
#include <winternl.h>
#include <intrin.h>
void showSbt(bool setSbt, DWORD newId)
{
#if defined WIN32
#define OFFSET_SUBPROCESSTAG 0x0f60
#elif defined _WIN64
#define OFFSET_SUBPROCESSTAG 0x1720
#else
#endif // _WIN32
unsigned long subProcessTag = 0;
HANDLE hProcess = GetCurrentProcess();
#if defined(_WIN64) // x64
auto pTeb = reinterpret_cast<PTEB>(__readgsqword(reinterpret_cast<DWORD>(&static_cast<NT_TIB*>(nullptr)->Self)));
#elif defined(WIN32) // x86
auto pTeb = reinterpret_cast<PTEB>(__readfsdword(reinterpret_cast<DWORD>(&static_cast<NT_TIB*>(nullptr)->Self)));
#endif
DWORD written = 0;
if (setSbt == true)
{
subProcessTag = newId;
WriteProcessMemory(hProcess, reinterpret_cast<void *>(reinterpret_cast<ULONG_PTR>(pTeb)+OFFSET_SUBPROCESSTAG)
, reinterpret_cast<void *>(&subProcessTag)
, sizeof(subProcessTag), &written);
}
subProcessTag = 0;
BOOL result = ReadProcessMemory(hProcess, reinterpret_cast<void *>(reinterpret_cast<ULONG_PTR>(pTeb)+OFFSET_SUBPROCESSTAG)
, reinterpret_cast<void *>(&subProcessTag)
, sizeof(subProcessTag),
nullptr);
if (result == TRUE)
{
printf("[TID: %d] %d (0x%x)\n", GetCurrentThreadId(), subProcessTag, subProcessTag);
}
CloseHandle(hProcess);
}
DWORD WINAPI newThreadProc2(PVOID pVoid)
{
showSbt(false, 0);
return 0;
}
DWORD WINAPI newThreadProc(PVOID pVoid)
{
showSbt(false, 0);
HANDLE newThread = ::CreateThread(nullptr, 0, newThreadProc2, nullptr, 0, nullptr);
::WaitForSingleObject(newThread, INFINITE);
::CloseHandle(newThread);
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
showSbt(false, 0x100);
HANDLE newThread = ::CreateThread(nullptr, 0, newThreadProc, nullptr, 0, nullptr);
::WaitForSingleObject(newThread, INFINITE);
::CloseHandle(newThread);
return 0;
}
주 스레드가 newThreadProc를 가진 2차 스레드를 생성하고, 다시 newThreadProc 내부에서는 newThreadProc2를 가진 3차 스레드를 생성했는데, 결과는 예상한 대로 상속된 것으로 나옵니다.
[TID: 12536] 256 (0x100)
[TID: 13384] 256 (0x100)
[TID: 14884] 256 (0x100)
이 기능의 아쉬운 점이 있다면? 스레드 풀에는 적용되지 않는다고 합니다. 즉, 현재 스레드에서 스레드 풀의 스레드에 작업을 던진 경우, 그 동작 간에는 SubProcessTag 값의 전달이 없습니다. 아마도, 스레드 생성 시에 부모 스레드의 SubProcessTag값을 전달하는 식으로 구현된 것이 아닌가 생각됩니다. 그렇기 때문에 이미 생성된 스레드 풀의 스레드에는 영향을 미치지 못하는 것이고!
이 기능을, 마이크로소프트 측에서는 다중 서비스를 호스팅하는 프로세스 내의 스레드에 적용한다고 합니다. 이에 대해서는 다음의 글을 참고하세요. ^^
ScTagQuery: Mapping Service Hosting Threads With Their Owner Service
; http://www.alex-ionescu.com/?p=52
제가 구현한 예제 코드에서는 단순히 DWORD 값을 넣었지만, 마이크로소프트가 설정한 정보의 구조는 좀 다릅니다. 문서화되지 않은(undocumented) 기능이지만, 다음과 같이 잘 공개(?)되어 있습니다. ^^
HOWTO: Use I_QueryTagInformation
; http://wj32.org/wp/2010/03/30/howto-use-i_querytaginformation/
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]