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

(시리즈 글이 6개 있습니다.)
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

디버깅 기술: 205. Windbg - KPCR, KPRCB
; https://www.sysnet.pe.kr/2/0/13842




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

비밀번호

댓글 작성자
 




... 121  122  123  [124]  125  126  127  128  129  130  131  132  133  134  135  ...
NoWriterDateCnt.TitleFile(s)
10823정성태7/7/201527950오류 유형: 300. SqlException (0x80131904): Unable to open the physical file
10822정성태7/7/201527316오류 유형: 299. The 'Visual C++ Project System Package' package did not load correctly.
10821정성태7/7/201519552오류 유형: 298. Unable to start debugging on the web server. IIS does not list a web site that matches the launched URL.
10820정성태7/7/201525540오류 유형: 297. HTTP Error 503. The service is unavailable. - 두 번째
10819정성태7/2/201528957오류 유형: 296. SQL Server Express 시작 오류 - error code 3417
10818정성태7/1/201527793오류 유형: 295. HTTP Error 503. The service is unavailable. [1]
10817정성태6/29/201531501.NET Framework: 523. C# 람다(Lambda)에서 변수 캡처 방식 [3]
10816정성태6/25/201527817.NET Framework: 522. 닷넷의 어셈블리 서명 데이터 확인 방법파일 다운로드1
10815정성태6/23/201525670Graphics: 1. 자네 나와 함께... UNITY 하지 않겠는가! [4]
10814정성태6/22/201523501.NET Framework: 521. Roslyn을 이용해 C# 문법 변형하기 (2) [5]
10813정성태6/21/201525275.NET Framework: 520. Roslyn을 이용해 C# 문법 변형하기 (1)
10812정성태6/20/201526391.NET Framework: 519. C# 6.0 오픈 소스 컴파일러 Roslyn - 빌드 및 테스트 방법 [1]
10811정성태6/20/201523180오류 유형: 294. OpenAuth 사용 시 System.Data.SqlClient.SqlException 예외가 Output 창에 출력되는 문제
10810정성태6/18/201522177개발 환경 구성: 270. Visual Studio에서 github 오픈 소스를 fork해서 테스트하는 방법 [1]
10809정성태6/18/201520150.NET Framework: 518. AllowPartiallyTrustedCallers 특성이 적용된 GAC 어셈블리에서 DynamicMethod의 calli 명령어 사용파일 다운로드1
10808정성태6/17/201522499.NET Framework: 517. calli IL 호출이 DllImport 호출보다 빠를까요? [1]파일 다운로드1
10807정성태6/16/201523580.NET Framework: 516. Microsoft.AspNet.Membership.OpenAuth 사용 시 "Local Database Runtime error occurred" 오류
10806정성태6/16/201541427.NET Framework: 515. OpenAuth.VerifyAuthentication 호출 시 The remote server returned an error: (400) Bad Request
10805정성태6/15/201522761Java: 17. 자바의 재미있는 상수 처리 방식
10804정성태6/10/201522463.NET Framework: 514. .NET CLR2 보안 모델에서의 APTCA 역할 (2)파일 다운로드1
10803정성태6/2/201524408.NET Framework: 513. UWP(Universal Windows Platform) 응용 프로그램의 새로운 라이브러리 버전 관리 해법 [2]
10802정성태6/2/201524444개발 환경 구성: 269. 마이크로소프트 온라인 강좌 소개 - Azure VPN 구성 방법 [1]
10801정성태5/31/201528968.NET Framework: 512. async/await 사용 시 hang 문제가 발생하는 경우 - 두 번째 이야기 [3]
10800정성태5/29/201524448개발 환경 구성: 268. 소개 - 프로세싱(https://processing.org/)
10799정성태5/29/201522177사물인터넷: 3. 책 소개 - 라즈베리 파이로 구현하는 사물 인터넷 프로젝트 [1]
10798정성태5/26/201522044기타: 53. 2015년 6월 10일 밤 10시 온라인 세미나 - 새로운 Windows 10 App을 개발하는 방법
... 121  122  123  [124]  125  126  127  128  129  130  131  132  133  134  135  ...