Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (seongtaejeong at gmail.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)
(시리즈 글이 8개 있습니다.)
디버깅 기술: 194. Windbg - x64 가상 주소를 물리 주소로 변환
; https://www.sysnet.pe.kr/2/0/13500

디버깅 기술: 203. Windbg - x64 가상 주소를 물리 주소로 변환 (페이지 크기가 2MB인 경우)
; https://www.sysnet.pe.kr/2/0/13836

디버깅 기술: 206. Windbg로 알아보는 PFN (_MMPFN)
; https://www.sysnet.pe.kr/2/0/13844

디버깅 기술: 207. Windbg로 알아보는 PTE (_MMPTE)
; https://www.sysnet.pe.kr/2/0/13845

디버깅 기술: 208. Windbg로 알아보는 Trans/Soft PTE와 2가지 Page Fault 유형
; https://www.sysnet.pe.kr/2/0/13846

디버깅 기술: 209. Windbg로 알아보는 Prototype PTE
; https://www.sysnet.pe.kr/2/0/13848

디버깅 기술: 210. Windbg - 논리(가상) 주소를 Segmentation을 거쳐 선형 주소로 변경
; https://www.sysnet.pe.kr/2/0/13849

디버깅 기술: 212. Windbg - (Ring 3 사용자 모드의) FS, GS Segment 레지스터
; https://www.sysnet.pe.kr/2/0/13852




Windbg - (Ring 3 사용자 모드의) FS, GS Segment 레지스터

지난 글에서,

Windbg - 논리(가상) 주소를 Segmentation을 거쳐 선형 주소로 변경
; https://www.sysnet.pe.kr/2/0/13849

선형 주소로의 변환 시 관여하는 세그먼트 레지스터를 살펴봤는데요, 이번에는 남은 2개의 세그먼트인 FS, GS 레지스터를 마저 살펴보겠습니다.




FS, GS 레지스터의 최초 역할은 아래의 글에 설명이 나옵니다.

What is the "FS"/"GS" register intended for?
; https://stackoverflow.com/questions/10810203/what-is-the-fs-gs-register-intended-for

즉, 16비트 CPU 시절에 64KB 단위의 메모리 구역을 DS, ES로 나눠 지정할 수 있었던 것을 동시에 4개까지 서로 다른 64KB 영역을 지정할 수 있도록 추가로 제공한 것이 FS, GS였다고 합니다.

So, by using FS and GS, you could effectively address two more 64KB memory segments from your program without the need to change DS or ES registers whenever you need to address other segments than were loaded in DS or ES.


하지만, 32비트 시절로 오면서는 주소(Address) 선이 32비트로 넓어지면서 4개의 데이터 세그먼트 레지스터가 (그다지 절실하게) 필요하지는 않게 되었고, 따라서 운영체제 측에서 임의의 목적으로 사용할 수 있게 되었습니다.

실제로, 운영체제마다 FS, GS를 각기 다른 목적으로 사용할 수 있는데요, x64 윈도우 운영체제의 경우에는,

What is the GS register used for on Windows?
; https://stackoverflow.com/questions/39137043/what-is-the-gs-register-used-for-on-windows

FS 레지스터를 32비트 프로세스의 Thread Environment Block(TEB)를 가리키도록 하고 GS 레지스터는 64비트 프로세스의 TEB를 가리키는 용도로 사용합니다. (x86 윈도우의 경우에는 FS 레지스터만 사용하고 GS 레지스터는 사용하지 않습니다.)

실습으로 FS, GS가 어떤 값을 가지고 있는지 WinDbg를 통해 직접 확인해 볼 텐데요, 이를 위해 notepad.exe를 실행하고 WinDbg를 Attach시켜 rM 명령어를 사용하면,

// Windows 10 x64 환경의 notepad.exe를 디버그 연결

0:049> rM 8
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!DbgBreakPoint:
00007ffe`07814090 cc              int     3

저렇게 fs, gs 세그먼트의 값이 나옵니다. x64 윈도우에서 x64 프로세스(notepad.exe)를 실행했으니, 일단 GS 레지스터가 사용될 텐데요 그것의 GDT offset 값은 5로 계산되고,

2b == 00000000 00101011 (offset 0n5)

GDT로부터 offset 5에 해당하는 selector를 찾아보면,

// 세그먼트 1개 데이터 크기 8 바이트 * offset 5 == 40 (0x28)

4: kd> dg 28
                                                    P Si Gr Pr Lo
Sel        Base              Limit          Type    l ze an es ng Flags
---- ----------------- ----------------- ---------- - -- -- -- -- --------
0028 00000000`00000000 00000000`ffffffff Data RW Ac 3 Bg Pg P  Nl 00000cf3

예상했던 것과는 달리 Base가 0으로 나옵니다. 말이 안 되죠? 가령, 흔히 하는 gs 레지스터 연산에서 gs:[0x30]을 접근하게 되는데, 그렇다면 Base 주소가 0이니 선형 주소로 00000000`00000030을 접근할 것이고, 당연히 해당 주소 영역은 윈도우에서 사용하지 않기 때문에 Access Violation 예외가 발생하게 됩니다.

0:049> db 00000000`00000030 L4
00000000`00000030  ?? ?? ?? ??                                      ????

물론, 이게 다가 아니겠죠? ^^




x64 CPU는 FS, GS에 대해 특별한 대우를 하는데요, 그 2개의 세그먼트에 대해서는 주소 계산을 GDT를 이용하지 않고 별도의 MSR(Model Specific Register) 값을 기반으로 합니다.

// https://sites.uclouvain.be/SystInfo/usr/include/asm/msr-index.h.html

#define MSR_FS_BASE                0xc0000100 /* 64bit FS base */
#define MSR_GS_BASE                0xc0000101 /* 64bit GS base */

가령, GS Base 주소는 MSR_GS_BASE(0xc0000101)를 통해 얻을 수 있는데요, 아쉽게도 "사용자 모드 (Ring 3)" 디버깅 상태로는 WinDbg에서 이 값을 확인할 수 없습니다.

// rdmsr은 Ring 0의 특권에서만 실행 가능한 명령어
// https://modoocode.com/en/inst/rdmsr

0:049> rdmsr 0xc0000101 
           ^ Bad register error in 'rdmsr 0xc0000101'

대신, 우회적으로 !teb 명령을 실행하면 GS base의 주솟값과 그와 관련된 데이터 내용을 확인할 수 있습니다.

// 사용자 모드 응용 프로그램의 디버깅 중에 실행 (Windows 10 x64 환경)

0:049> !teb
TEB at 000000061ed12000
    ExceptionList:        0000000000000000
    StackBase:            0000000620740000
    StackLimit:           000000062073c000
    SubSystemTib:         0000000000000000
    FiberData:            0000000000001e00
    ArbitraryUserPointer: 0000000000000000
    Self:                 000000061ed12000
    EnvironmentPointer:   0000000000000000
    ClientId:             000000000000cd9c . 0000000000011290
    RpcHandle:            0000000000000000
    Tls Storage:          0000000000000000
    PEB Address:          000000061ecab000
    LastErrorValue:       0
    LastStatusValue:      0
    Count Owned Locks:    0
    HardErrorMode:        0

// !teb를 통해 알아낸 gs base 주소로부터 64바이트 값을 덤프
// 즉, gs:[0] ~ gs:[0x3f] 범위의 값을 8바이트 단위로 덤프
0:004> dq 000000061ed12000 L8
00000006`1ed12000  00000000`00000000 00000006`20740000
00000006`1ed12010  00000006`2073c000 00000000`00000000
00000006`1ed12020  00000000`00001e00 00000000`00000000
00000006`1ed12030  00000006`1ed12000 00000000`00000000

혹은, !teb가 출력한 주솟값을 대상으로 _TEB 구조체를 씌워 직접 모든 필드의 값을 확인하는 것도 가능합니다.

0:049> dt ntdll!_TEB 000000061ed12000 .
   +0x000 NtTib            : 
      +0x000 ExceptionList    : (null) 
      +0x008 StackBase        : 0x00000006`20740000 Void
      +0x010 StackLimit       : 0x00000006`2073c000 Void
      +0x018 SubSystemTib     : (null) 
      +0x020 FiberData        : 0x00000000`00001e00 Void
      +0x020 Version          : 0x1e00
      +0x028 ArbitraryUserPointer : (null) 
      +0x030 Self             : 0x00000006`1ed12000 _NT_TIB // GS base 주소
   +0x038 EnvironmentPointer : 
   +0x040 ClientId         : 
      +0x000 UniqueProcess    : 0x00000000`0000cd9c Void // Process ID
      +0x008 UniqueThread     : 0x00000000`00011290 Void // 스레드 ID
...[생략]...

위의 구조체에서 재미있는 점은 _TEB.Self 필드, 즉 gs:[0x30] 위치에 있는 값이 GS 세그먼트의 base 주소를 가리킨다는 점입니다. 이름이 의미하는 대로 자기 자신을 가리키는 포인터인데요, 실제로 !teb로 얻은 주솟값에서 0x30 위치의 값이 동일한 것을 확인할 수 있습니다.

0:049> dq 000000061ed12000 L8
00000006`1ed12000  00000000`00000000 00000006`20740000
00000006`1ed12010  00000006`2073c000 00000000`00000000
00000006`1ed12020  00000000`00001e00 00000000`00000000
00000006`1ed12030  00000006`1ed12000 00000000`00000000

다시 말해, 만약 rdmsr 명령어가 수행 가능했다면 MSR_GS_BASE(0xc0000101)를 통해 얻은 값이 0x000000061ed12000이었을 것으로 예측할 수 있습니다.

// 만약 WinDbg 사용자 모드 디버깅에서 아래의 명령어가 수행 가능했다면!

0:049> rdmsr 0xc0000101
msr[c0000101] = 00000006`1ed12000




그러고 보니, 예전 글에서 TEB를 구하기 위해 GS 레지스터를 사용한 적이 있었는데요,

x64 Visual C++에서 TEB 주소 구하는 방법
; https://www.sysnet.pe.kr/2/0/1387

따라서, C/C++에서 GS base 주소(MSR_GS_BASE)를 gs:[0x30]으로부터 구할 수 있고 결국 그 값이 gs:0의 위치를 의미한다는 것을 다음과 같이 테스트할 수 있습니다.

unsigned __int64* gsBaseAddress = (unsigned __int64*)__readgsqword(0x30);
assert(gsBaseAddress[6] == __readgsqword(0x30));

printf("GS base address (MSR_GS_BASE, rdmsr 0xc0000101): 0x%p\n", gsBaseAddress);

아래는 이것을 좀 더 확장한 예제 코드입니다.

#include <stdio.h>
#include <windows.h>

extern "C"
{
    void FuncInThread1()
    {
        Sleep(1000);
    }

    void FuncInThread2()
    {
        Sleep(1000);
    }

    void PrintThreadInfo()
    {
        DWORD tid = GetCurrentThreadId();
        printf("\nThread ID: %d (0x%x)\n", tid, tid);
        for (int i = 0; i < 8 * 8; i += (8 * 2))
        {
            unsigned __int64 dqValue1 = __readgsqword(i);
            unsigned __int64 dqValue2 = __readgsqword(i + 8);
            printf("gs:[%04x] %016I64x %016I64x\n", i, dqValue1, dqValue2);
        }

        printf("\n");

        unsigned __int64 *gsBaseAddress = (unsigned __int64* )__readgsqword(0x30);
        {
            for (int i = 0; i < 8; i += 2)
            {
                unsigned __int64 dqValue1 = gsBaseAddress[i];
                unsigned __int64 dqValue2 = gsBaseAddress[i + 1];
                printf("gs:[%04x] %016I64x %016I64x\n", i * 8, dqValue1, dqValue2);
            }
        }

        {
            unsigned __int64* gsBaseAddress = (unsigned __int64*)__readgsqword(0x30);
            assert(gsBaseAddress[6] == __readgsqword(0x30));

            printf("GS base address (MSR_GS_BASE, rdmsr 0xc0000101): 0x%p\n", gsBaseAddress);
        }
    }

    void ThreadProc1()
    {
        PrintThreadInfo();
        while (true)
        {
            FuncInThread1();
        }
    }

    void ThreadProc2()
    {
        PrintThreadInfo();
        while (true)
        {
            FuncInThread2();
        }
    }

    int main()
    {
        DWORD pid = ::GetProcessId(::GetCurrentProcess());
        printf("PID: %d (0x%x)\n", pid, pid);

        ::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc1, NULL, 0, NULL);
        Sleep(1000);
        ::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc2, NULL, 0, NULL);

        Sleep(-1);
    }
}

실행해 보면, 이런 식의 결과가 나옵니다.

Thread ID: 55672 (0xd978)
gs:[0000] 0000000000000000 0000005937f00000
gs:[0010] 0000005937efd000 0000000000000000
gs:[0020] 0000000000001e00 0000000000000000
gs:[0030] 0000005937998000 0000000000000000

gs:[0000] 0000000000000000 0000005937f00000
gs:[0010] 0000005937efd000 0000000000000000
gs:[0020] 0000000000001e00 0000000000000000
gs:[0030] 0000005937998000 0000000000000000
GS base address (MSR_GS_BASE, rdmsr 0xc0000101): 0x0000005937998000

Thread ID: 49868 (0xc2cc)
gs:[0000] 0000000000000000 0000005938000000
gs:[0010] 0000005937ffd000 0000000000000000
gs:[0020] 0000000000001e00 0000000000000000
gs:[0030] 000000593799a000 0000000000000000

gs:[0000] 0000000000000000 0000005938000000
gs:[0010] 0000005937ffd000 0000000000000000
gs:[0020] 0000000000001e00 0000000000000000
gs:[0030] 000000593799a000 0000000000000000
GS base address (MSR_GS_BASE, rdmsr 0xc0000101): 0x000000593799A000

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 12/26/2024]

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

비밀번호

댓글 작성자
 




... 46  47  [48]  49  50  51  52  53  54  55  56  57  58  59  60  ...
NoWriterDateCnt.TitleFile(s)
12736정성태7/28/202115252오류 유형: 743. Active Azure Directory에서 "API permissions"의 권한 설정이 "Not granted for ..."로 나오는 문제
12735정성태7/27/202115158.NET Framework: 1081. C# - Azure AD 인증을 지원하는 데스크톱 애플리케이션 예제(Windows Forms) [2]파일 다운로드1
12734정성태7/26/202131965스크립트: 20. 특정 단어로 시작하거나/끝나는 문자열을 포함/제외하는 정규 표현식 - Look-around
12733정성태7/23/202119844.NET Framework: 1081. Self-Contained/SingleFile 유형의 .NET Core/5+ 실행 파일을 임베딩한다면? [1]파일 다운로드2
12732정성태7/23/202113297오류 유형: 742. SharePoint - The super user account utilized by the cache is not configured.
12731정성태7/23/202115318개발 환경 구성: 584. Add Internal URLs 화면에서 "Save" 버튼이 비활성화 된 경우
12730정성태7/23/202116764개발 환경 구성: 583. Visual Studio Code - Go 코드에서 입력을 받는 경우
12729정성태7/22/202115252.NET Framework: 1080. xUnit 단위 테스트에 메서드/클래스 수준의 문맥 제공 - Fixture
12728정성태7/22/202115275.NET Framework: 1079. MSTestv2 단위 테스트에 메서드/클래스/어셈블리 수준의 문맥 제공
12727정성태7/21/202116459.NET Framework: 1078. C# 단위 테스트 - MSTestv2/NUnit의 Assert.Inconclusive 사용법(?) [1]
12726정성태7/21/202116232VS.NET IDE: 169. 비주얼 스튜디오 - 단위 테스트 선택 시 MSTestv2 외의 xUnit, NUnit 사용법 [1]
12725정성태7/21/202114694오류 유형: 741. Failed to find the "go" binary in either GOROOT() or PATH
12724정성태7/21/202117588개발 환경 구성: 582. 윈도우 환경에서 Visual Studio Code + Go (Zip) 개발 환경 [1]
12723정성태7/21/202114512오류 유형: 740. SharePoint - Alternate access mappings have not been configured 경고
12722정성태7/20/202114258오류 유형: 739. MSVCR110.dll이 없어 exe 실행이 안 되는 경우
12721정성태7/20/202115412오류 유형: 738. The trust relationship between this workstation and the primary domain failed. - 세 번째 이야기
12720정성태7/19/202114677Linux: 43. .NET Core/5+ 응용 프로그램의 Ubuntu (Debian) 패키지 준비
12719정성태7/19/202113870오류 유형: 737. SharePoint 설치 시 "0x800710D8 The object identifier does not represent a valid object." 오류 발생
12718정성태7/19/202113899개발 환경 구성: 581. Windows에서 WSL로 파일 복사 시 root 소유권으로 적용되는 문제파일 다운로드1
12717정성태7/18/202114095Windows: 195. robocopy에서 파일의 ADS(Alternate Data Stream) 정보 복사를 제외하는 방법
12716정성태7/17/202115230개발 환경 구성: 580. msbuild의 Exec Task에 robocopy를 사용하는 방법파일 다운로드1
12715정성태7/17/202122430오류 유형: 736. Windows - MySQL zip 파일 버전의 "mysqld --skip-grant-tables" 실행 시 비정상 종료 [1]
12714정성태7/16/202115795오류 유형: 735. VCRUNTIME140.dll, MSVCP140.dll, VCRUNTIME140.dll, VCRUNTIME140_1.dll이 없어 exe 실행이 안 되는 경우
12713정성태7/16/202117181.NET Framework: 1077. C# - 동기 방식이면서 비동기 규약을 따르게 만드는 Task.FromResult파일 다운로드1
12712정성태7/15/202116106개발 환경 구성: 579. Azure - 리눅스 호스팅의 Site Extension 제작 방법
12711정성태7/15/202116123개발 환경 구성: 578. Azure - Java Web App Service를 위한 Site Extension 제작 방법
... 46  47  [48]  49  50  51  52  53  54  55  56  57  58  59  60  ...