Microsoft MVP성태의 닷넷 이야기
VC++: 91. 자식 스레드에 자동 상속되는 TEB의 SubProcessTag 필드 [링크 복사], [링크+제목 복사],
조회: 14094
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

(시리즈 글이 5개 있습니다.)
VC++: 64. x64 Visual C++에서 TEB 주소 구하는 방법
; https://www.sysnet.pe.kr/2/0/1387

.NET Framework: 348. .NET x64 응용 프로그램에서 Teb 주소를 구하는 방법
; https://www.sysnet.pe.kr/2/0/1388

.NET Framework: 349. .NET Thread 인스턴스로부터 COM Apartment 유형 확인하는 방법
; https://www.sysnet.pe.kr/2/0/1389

VC++: 91. 자식 스레드에 자동 상속되는 TEB의 SubProcessTag 필드
; https://www.sysnet.pe.kr/2/0/10797

디버깅 기술: 150. windbg - Wow64, x86, x64에서의 커널 구조체(예: TEB) 구조체 확인
; https://www.sysnet.pe.kr/2/0/12097




자식 스레드에 자동 상속되는 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/




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 5/23/2015]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 16  17  18  19  20  [21]  22  23  24  25  26  27  28  29  30  ...
NoWriterDateCnt.TitleFile(s)
13126정성태9/15/20227476.NET Framework: 2049. C# 11 - 정적 메서드에 대한 delegate 처리 시 cache 적용
13125정성태9/14/20227718.NET Framework: 2048. C# 11 - 구조체 필드의 자동 초기화(auto-default structs)
13124정성태9/13/20227507.NET Framework: 2047. Golang, Python, C#에서의 CRC32 사용
13123정성태9/8/20227861.NET Framework: 2046. C# 11 - 멤버(속성/필드)에 지정할 수 있는 required 예약어 추가
13122정성태8/26/20227831.NET Framework: 2045. C# 11 - 메서드 매개 변수에 대한 nameof 지원
13121정성태8/23/20225668C/C++: 157. Golang - 구조체의 slice 필드를 Reflection을 이용해 변경하는 방법
13120정성태8/19/20227313Windows: 209. Windows NT Service에서 UI를 다루는 방법 [3]
13119정성태8/18/20226879.NET Framework: 2044. .NET Core/5+ 프로젝트에서 참조 DLL이 보관된 공통 디렉터리를 지정하는 방법
13118정성태8/18/20225663.NET Framework: 2043. WPF Color의 기본 색 영역은 (sRGB가 아닌) scRGB [2]
13117정성태8/17/20227885.NET Framework: 2042. C# 11 - 파일 범위 내에서 유효한 타입 정의 (File-local types)파일 다운로드1
13116정성태8/4/20228335.NET Framework: 2041. C# - Socket.Close 시 Socket.Receive 메서드에서 예외가 발생하는 문제파일 다운로드1
13115정성태8/3/20228716.NET Framework: 2040. C# - ValueTask와 Task의 성능 비교 [1]파일 다운로드1
13114정성태8/2/20228883.NET Framework: 2039. C# - Task와 비교해 본 ValueTask 사용법파일 다운로드1
13113정성태7/31/20228205.NET Framework: 2038. C# 11 - Span 타입에 대한 패턴 매칭 (Pattern matching on ReadOnlySpan<char>)
13112정성태7/30/20228568.NET Framework: 2037. C# 11 - 목록 패턴(List patterns) [1]파일 다운로드1
13111정성태7/29/20228325.NET Framework: 2036. C# 11 - IntPtr/UIntPtr과 nint/nuint의 통합파일 다운로드1
13110정성태7/27/20228382.NET Framework: 2035. C# 11 - 새로운 연산자 ">>>" (Unsigned Right Shift)파일 다운로드1
13109정성태7/27/20229784VS.NET IDE: 177. 비주얼 스튜디오 2022를 이용한 (소스 코드가 없는) 닷넷 모듈 디버깅 - "외부 원본(External Sources)" [1]
13108정성태7/26/20227731Linux: 53. container에 실행 중인 Golang 프로세스를 디버깅하는 방법 [1]
13107정성태7/25/20226821Linux: 52. Debian/Ubuntu 계열의 docker container에서 자주 설치하게 되는 명령어
13106정성태7/24/20226616오류 유형: 819. 닷넷 6 프로젝트의 "Conditional compilation symbols" 기본값 오류
13105정성태7/23/20227924.NET Framework: 2034. .NET Core/5+ 환경에서 (프로젝트가 아닌) C# 코드 파일을 입력으로 컴파일하는 방법 - 두 번째 이야기 [1]
13104정성태7/23/202211002Linux: 51. WSL - init에서 systemd로 전환하는 방법
13103정성태7/22/20227493오류 유형: 818. WSL - systemd-genie와 관련한 2가지(systemd-remount-fs.service, multipathd.socket) 에러
13102정성태7/19/20226956.NET Framework: 2033. .NET Core/5+에서는 구할 수 없는 HttpRuntime.AppDomainAppId
13101정성태7/15/202215930도서: 시작하세요! C# 10 프로그래밍
... 16  17  18  19  20  [21]  22  23  24  25  26  27  28  29  30  ...