Microsoft MVP성태의 닷넷 이야기
디버깅 기술: 205. Windbg - KPCR, KPRCB [링크 복사], [링크+제목 복사],
조회: 2263
글쓴 사람
정성태 (seongtaejeong at gmail.com)
홈페이지
첨부 파일
 
(연관된 글이 1개 있습니다.)
(시리즈 글이 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




Windbg - KPCR, KPRCB

프로세서가 사용자 모드의 코드를 실행하는 동안에는 GS 레지스터가 TEB 영역을 가리키지만, 커널 모드에서는 PCR 및 PRCB 값을 담은 영역을 가리킵니다.

// Fooling Windows about its internal CPU
// https://rayanfam.com/topics/fooling-windows-about-cpu/

// nt!_KPCR
// https://codemachine.com/articles/kernel_structures.html#KPCR

// (local kernel 디버깅이 아닌) live 커널 디버깅 모드 상태 (vCPU == 8개인 경우)

0: kd> dq gs:[0]
002b:00000000`00000000  fffff800`4e0c9fb0 fffff800`4e0c8000
002b:00000000`00000010  00000047`fdacf648 fffff800`4cf53000
002b:00000000`00000020  fffff800`4cf53180 fffff800`4cf53870
002b:00000000`00000030  00000047`fdc38000 fffff800`4e0c7000
002b:00000000`00000040  00000000`00000000 00000000`00000000
002b:00000000`00000050  00000000`00001000 00000000`00000000
002b:00000000`00000060  00000e10`00010001 00000000`00000000
002b:00000000`00000070  00000000`00000000 00000000`00000000

0: kd> ~7

7: kd> dq gs:[0]
002b:00000000`00000000  ffff8100`766b2fb0 ffff8100`766b1000
002b:00000000`00000010  00000047`fde7f228 ffff8100`766a2000
002b:00000000`00000020  ffff8100`766a2180 ffff8100`766a2870
002b:00000000`00000030  00000047`fdc40000 ffff8100`766b0000
002b:00000000`00000040  00000000`00000000 00000000`00000000
002b:00000000`00000050  00000000`00071000 00000000`00000000
002b:00000000`00000060  00000e10`00010001 00000000`00000000
002b:00000000`00000070  00000000`00000000 00000000`00000000

gs:[0]의 출력에서 어떤 오프셋에 KPCR과 KPRCB 값을 담고 있는지 확인하고 싶다면 각각 !pcr, !prcb 명령어로 알아낼 수 있는데요,

7: kd> !pcr
KPCR for Processor 7 at ffff8100766a2000:
    Major 1 Minor 1
    NtTib.ExceptionList: ffff8100766b2fb0
        NtTib.StackBase: ffff8100766b1000
       NtTib.StackLimit: 00000047fde7f228
     NtTib.SubSystemTib: ffff8100766a2000
          NtTib.Version: 00000000766a2180
      NtTib.UserPointer: ffff8100766a2870
          NtTib.SelfTib: 00000047fdc40000

                SelfPcr: 0000000000000000
                   Prcb: ffff8100766a2180
                   Irql: 0000000000000000
                    IRR: 0000000000000000
                    IDR: 0000000000000000
          InterruptMode: 0000000000000000
                    IDT: 0000000000000000
                    GDT: 0000000000000000
                    TSS: 0000000000000000

          CurrentThread: ffff9485cf602080
             NextThread: 0000000000000000
             IdleThread: ffff9485cf602080

              DpcQueue: 
7: kd> !prcb
PRCB for Processor 7 at ffff8100766a2180:
Current IRQL -- 0
Threads--  Current ffff9485cf602080 Next 0000000000000000 Idle ffff9485cf602080
Processor Index 7 Number (0, 7) GroupSetMember 80
Interrupt Count -- 00006b89
Times -- Dpc    00000001 Interrupt 00000000 
         Kernel 00003191 User      00000009 

그러니까, (물론 운영체제 버전에 따라서도 달라질 수 있지만) 대충 이렇게 정리할 수 있습니다.

// Windows 10 x64인 경우

gs:[18] == KPCR     == ffff8100`766a2000 == Processor Control Region
gs:[20] == KPRCB    == ffff8100`766a2180 == Processor Control Block

KPCR/KPRCB는 이름에서도 알 수 있듯이 물리 장치인 CPU를 소프트웨어, 즉 운영체제 측에서 추상화한 구조체인데요, 게다가 KPRCB는 KPCR 내에 포함된 구조체에 불과합니다. 그래서 일단 KPCR만 알아내면, KPRCB도 Prcb 필드를 이용해 확인할 수 있습니다.

7: kd> dt _KPCR ffff8100766a2000
nt!_KPCR
   +0x000 NtTib            : _NT_TIB
   +0x000 GdtBase          : 0xffff8100`766b2fb0 _KGDTENTRY64
   +0x008 TssBase          : 0xffff8100`766b1000 _KTSS64
   +0x010 UserRsp          : 0x00000047`fde7f228
   +0x018 Self             : 0xffff8100`766a2000 _KPCR
   +0x020 CurrentPrcb      : 0xffff8100`766a2180 _KPRCB
   +0x028 LockArray        : 0xffff8100`766a2870 _KSPIN_LOCK_QUEUE
   +0x030 Used_Self        : 0x00000047`fdc40000 Void
   +0x038 IdtBase          : 0xffff8100`766b0000 _KIDTENTRY64
   +0x040 Unused           : [2] 0
   +0x050 Irql             : 0 ''
   +0x051 SecondLevelCacheAssociativity : 0x10 ''
   +0x052 ObsoleteNumber   : 0x7 ''
   +0x053 Fill0            : 0 ''
   +0x054 Unused0          : [3] 0
   +0x060 MajorVersion     : 1
   +0x062 MinorVersion     : 1
   +0x064 StallScaleFactor : 0xe10
   +0x068 Unused1          : [3] (null) 
   +0x080 KernelReserved   : [15] 0
   +0x0bc SecondLevelCacheSize : 0x400000
   +0x0c0 HalReserved      : [16] 0xd691cf40
   +0x100 Unused2          : 0
   +0x108 KdVersionBlock   : (null) 
   +0x110 Unused3          : (null) 
   +0x118 PcrAlign1        : [24] 0
   +0x180 Prcb             : _KPRCB

// dt _KPRCB ffff8100766a2000+0x180
// dt _KPRCB ffff8100`766a2180

7: kd> dt _KPCR ffff8100`766a2000 Prcb.
nt!_KPCR
   +0x180 Prcb  : 
       +0x000 MxCsr            : 0x1f80
       +0x004 LegacyNumber     : 0x7 ''
       +0x005 ReservedMustBeZero : 0 ''
       +0x006 InterruptRequest : 0 ''
       +0x007 IdleHalt         : 0x1 ''
       +0x008 CurrentThread    : 0xffff9485`cf602080 _KTHREAD
       +0x010 NextThread       : (null) 
       +0x018 IdleThread       : 0xffff9485`cf602080 _KTHREAD
       +0x020 NestingLevel     : 0 ''
       +0x021 ClockOwner       : 0 ''
       +0x022 PendingTickFlags : 0 ''
       +0x022 PendingTick      : 0y0
       +0x022 PendingBackupTick : 0y0
       +0x023 IdleState        : 0 ''
       +0x024 Number           : 7
       +0x028 RspBase          : 0xfffff686`1fcbcc70
       +0x030 PrcbLock         : 0
       ...[생략]...
       +0x9eac DbgMceNestingLevel : 0
       +0x9eb0 DbgMceFlags      : 0
       +0x9eb4 PrcbPad139b      : 0
       +0x9eb8 CacheProcessorSet : [5] _KAFFINITY_EX
       +0xa3e0 PrcbPad140       : [340] 0
       +0xae80 PrcbPad140a      : [8] 0
       +0xaec0 PrcbPad141       : [512] 0
       +0xbec0 RequestMailbox   : [1] _REQUEST_MAILBOX

// 0xe10 == 3600 == 3.6GHz
7: kd> dt _KPRCB ffff8100`766a2180 MHz VendorString
nt!_KPRCB
   +0x044 MHz          : 0xe10
   +0x8990 VendorString : [13]  "AuthenticAMD"




기왕에 KPRCB까지 왔으니 그것의 필드 중에 DispatcherReadyListHead를 알아볼까요? ^^

Deep Dive into Everything - Dispatcher Database
; https://haewon83.tistory.com/m/125

위의 글에서 아주 친절하게 설명하고 있으므로 저는 그냥 그대로 실습만 해보겠습니다.

우선, windbg의 !thread 명령어로는 현재 문맥으로 지정된 CPU에 실행 중인 스레드를 확인할 수 있습니다.

// 0번 CPU에 실행 중인 스레드 확인 (전체 CPU에 실행 중인 모든 스레드를 확인하고 싶다면 "!running -it" 명령어 사용)

0: kd> !thread
THREAD fffff8071c14c700  Cid 0000.0000  Teb: 0000000000000000 Win32Thread: 0000000000000000 RUNNING on processor 0
Not impersonating
DeviceMap                 ffff9f80e7a59d50
Owning Process            fffff8071c148f40       Image:         Idle
Attached Process          ffffd707b1abe040       Image:         System
Wait Start TickCount      814672         Ticks: 1 (0:00:00:00.015)
Context Switch Count      399519         IdealProcessor: 0             
UserTime                  00:00:00.000
KernelTime                03:31:44.921
Win32 Start Address nt!KiIdleLoop (0xfffff8071b819cc0)
Stack Init fffff80718f13c70 Current fffff80718f13c00
Base fffff80718f14000 Limit fffff80718f0e000 Call 0000000000000000
Priority 0  BasePriority 0  IoPriority 0  PagePriority 5
Child-SP          RetAddr               : Args to Child                                                           : Call Site
fffff807`18f137e8 fffff807`22fc2b4c     : ffffd707`b2f61890 00000000`00000000 00000000`00000000 00000000`00000000 : nt!DbgBreakPointWithStatus
fffff807`18f137f0 ffffd707`b2f61890     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : 0xfffff807`22fc2b4c
fffff807`18f137f8 00000000`00000000     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : 0xffffd707`b2f61890

보시면, callstack과 함께 Priority == 0이 나오고, 현재 스레드의 상태를 _KTHREAD.State 필드로 확인할 수 있습니다. (당연히 !thread 명령어 자체가 실행 중인 스레드를 보여주는 것이므로 Running을 의미하는 2가 나옵니다.)

0: kd> dt _KTHREAD fffff8071c14c700 State
nt!_KTHREAD
   +0x184 State : 0x2 ''

/*
0	Init
1	Ready
2	Running
3	Standby
4	Terminate
5	Waiting
6	Transition
7	Deferred Ready
*/

반면 실행 중이 아닌, 대기 중인 스레드를 확인하고 싶다면 !ready 명령어를 사용하면 된다고 하는데요,

// Windows 10+ x64인 경우

0: kd> !ready
KSHARED_READY_QUEUE fffff8071c14b040: (00) ****------------------------------------------------------------
SharedReadyQueue fffff8071c14b040: No threads in READY state
Processor 0: No threads in READY state
Processor 1: No threads in READY state
Processor 2: No threads in READY state
Processor 3: No threads in READY state
KSHARED_READY_QUEUE ffffd707b1a97640: (00) ----****--------------------------------------------------------
SharedReadyQueue ffffd707b1a97640: No threads in READY state
Processor 4: No threads in READY state
Processor 5: No threads in READY state
Processor 6: No threads in READY state
Processor 7: No threads in READY state

절묘한 타이밍인 줄은 알 수 없지만 보는 바와 같이 Ready 상태로 대기하는 스레드가 모든 프로세서에 걸쳐 하나도 없다고 나옵니다. 저 출력이 올바른지에 대한 확인을 위해 KPRCB에 있는 32개의 고정 크기를 가진 DispatcherReadyListHead 배열을 살펴볼 수 있습니다. 가령 아래는 0번 프로세서의 DispatcherReadyListHead 목록을 보여주는데요,

0: kd> !prcb
PRCB for Processor 0 at fffff80717d8c180:
Current IRQL -- 2
Threads--  Current fffff8071c14c700 Next 0000000000000000 Idle fffff8071c14c700
Processor Index 0 Number (0, 0) GroupSetMember 1
Interrupt Count -- 001519ee
Times -- Dpc    00000004 Interrupt 0000000e 
         Kernel 000c6a5b User      000003f6 

0: kd> dt _KPRCB fffff80717d8c180 DispatcherReadyListHead
nt!_KPRCB
   +0x7f40 DispatcherReadyListHead : [32] _LIST_ENTRY [ 0xfffff807`17d940c0 - 0xfffff807`17d940c0 ]

0: kd> dt _KPRCB fffff80717d8c180 -a DispatcherReadyListHead
nt!_KPRCB
   +0x7f40 DispatcherReadyListHead : 
    [00] _LIST_ENTRY [ 0xfffff807`17d940c0 - 0xfffff807`17d940c0 ]
    [01] _LIST_ENTRY [ 0xfffff807`17d940d0 - 0xfffff807`17d940d0 ]
    [02] _LIST_ENTRY [ 0xfffff807`17d940e0 - 0xfffff807`17d940e0 ]
    [03] _LIST_ENTRY [ 0xfffff807`17d940f0 - 0xfffff807`17d940f0 ]
    ...[생략]...
    [28] _LIST_ENTRY [ 0xfffff807`17d94280 - 0xfffff807`17d94280 ]
    [29] _LIST_ENTRY [ 0xfffff807`17d94290 - 0xfffff807`17d94290 ]
    [30] _LIST_ENTRY [ 0xfffff807`17d942a0 - 0xfffff807`17d942a0 ]
    [31] _LIST_ENTRY [ 0xfffff807`17d942b0 - 0xfffff807`17d942b0 ]

첫 번째 배열의 경우 0xfffff807`17d940c0 - 0xfffff807`17d940c0으로 FLink와 BLink의 값이 같다는 점에서 ready 상태의 스레드가 없다는 것을 유추할 수 있습니다. 즉, "!ready" 명령어에서 확인한 것처럼 0번 CPU의 32개 LIST_ENTRY가 모두 비어 있다는 것이 확인됩니다.

여기서 32개인 이유는, 스레드에 대한 윈도우의 스케줄링 우선순위가 32단계로 결정돼 있기 때문입니다.

Scheduling Priorities
; https://learn.microsoft.com/en-us/windows/win32/procthread/scheduling-priorities

The priority levels range from zero (lowest priority) to 31 (highest priority).


아마도, 저 환경이 VM이라서 그런지 모르겠지만, 어쨌든 32개의 우선순위 큐에서 ready 상태의 스레드가 하나도 없고, 저 테스트를 나머지 7개의 CPU 문맥에서 모두 실행해 봐도 같다는 것을 확인할 수 있습니다.




참고로, PCR/PRCB는 MSR(model-specific register)을 이용해서도 알아낼 수 있습니다.

MSRs
; https://wiki.osdev.org/CPU_Registers_x86-64#MSRs

Intel® 64 and IA-32 Architectures Software Developer’s Manual
 - B.1 ARCHITECTURAL MSRS (630 페이지)
https://www.intel.com/content/dam/support/us/en/documents/processors/pentium4/sb/253669.pdf

가령 IA32_GS_BASE 주소에 KPCR의 주소가 담겨 있는데요,

// 0xc0000101 == IA32_GS_BASE
// 아래는 7번 CPU가 실행 중인 프로세스/스레드 문맥의 KPRCB 주소를 출력

7: kd> rdmsr 0xc0000101
msr[c0000101] = ffff8100`766a2000

// 참고로, 원래 커널 모드의 GS base 주소를 담고 있는 MSR은 IA32_KERNEL_GS_BASE인데요, 
// 왜? IA32_GS_BASE에 KPCR의 값이 담겨 있는지는 "swapgs 명령어와 (Ring 0 커널 모드의) FS, GS Segment 레지스터" 글에서 설명합니다.

이뿐만 아니라 tsc 값도 확인할 수 있고,

4: kd> rdmsr 0x10
msr[10] = 000182b2`54a00dd5

4: kd> rdmsr 0x10
msr[10] = 000182b5`bbeaf118

Extended Feature Enable Register로 알려진 값까지 구할 수 있습니다.

// 0xc0000080 == IA32_EFER (Extended Feature Enable Register)
5: kd> rdmsr 0xc0000080
msr[c0000080] = 00000000`00000d01

5: kd> .formats d01
Evaluate expression:
  Hex:     00000000`00000d01
  Decimal: 3329
  Decimal (unsigned) : 3329
  Octal:   0000000000000000006401
  Binary:  00000000 00000000 00000000 00000000 00000000 00000000 00001101 00000001
  Chars:   ........
  Time:    Thu Jan  1 09:55:29 1970
  Float:   low 4.66492e-042 high 0
  Double:  1.64474e-320

위의 출력 결과를 IA32_EFER 명세에 따라 해석해 보면,

1       : SCE (System Call Extensions)
0000000 : 0 (Reserved)
1       : LME (Long Mode Enable)
0       : (?)
1       : LMA (Long Mode Active)
1       : NXE (No-Execute Enable)
0       : SVME (Secure Virtual Machine Enable)
0       : LMSLE (Long Mode Segment Limit Enable)
0       : FFXSR (Fast FXSAVE/FXRSTOR)
0       : TCE (Translation Cache Extension)
16~63   : 0 (Reserved) 

0번째 비트에 해당하는 SCE가 바로 (AMD64 K6 프로세서 이상부터 지원하는) user->kernel로의 전환을 보다 고속으로 실행할 수 있게 하는 syscall/sysret 명령어에 대한 허용 여부를 가리킵니다.

즉, 이 값이 1이면 syscall로 인한 진입 주소가 c0000082에 기록돼 있어야 합니다.

5: kd> rdmsr c0000082
msr[c0000082] = fffff806`08e12000




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

[연관 글]






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

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

비밀번호

댓글 작성자
 




[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13861정성태1/11/2025454Windows: 276. 명령행에서 원격 서비스를 동기/비동기로 시작/중지
13860정성태1/10/2025507디버깅 기술: 216. WinDbg - 2가지 유형의 식 평가 방법(MASM, C++)
13859정성태1/9/2025592디버깅 기술: 215. Windbg - syscall 이후 실행되는 KiSystemCall64 함수 및 SSDT 디버깅
13858정성태1/8/2025653개발 환경 구성: 738. PowerShell - 원격 호출 시 "powershell.exe"가 아닌 "pwsh.exe" 환경으로 명령어를 실행하는 방법
13857정성태1/7/2025751C/C++: 187. Golang - 콘솔 응용 프로그램을 Linux 데몬 서비스를 지원하도록 변경파일 다운로드1
13856정성태1/6/2025815디버깅 기술: 214. Windbg - syscall 단계까지의 Win32 API 호출 (예: Sleep)
13855정성태12/28/20241435오류 유형: 941. Golang - os.StartProcess() 사용 시 오류 정리
13854정성태12/27/20241414C/C++: 186. Golang - 콘솔 응용 프로그램을 NT 서비스를 지원하도록 변경파일 다운로드1
13853정성태12/26/20241582디버깅 기술: 213. Windbg - swapgs 명령어와 (Ring 0 커널 모드의) FS, GS Segment 레지스터
13852정성태12/25/20241596디버깅 기술: 212. Windbg - (Ring 3 사용자 모드의) FS, GS Segment 레지스터파일 다운로드1
13851정성태12/23/20241514디버깅 기술: 211. Windbg - 커널 모드 디버깅 상태에서 사용자 프로그램을 디버깅하는 방법
13850정성태12/23/20241524오류 유형: 940. "Application Information" 서비스를 중지한 경우, "This file does not have an app associated with it for performing this action."
13849정성태12/20/20241558디버깅 기술: 210. Windbg - 논리(가상) 주소를 Segmentation을 거쳐 선형 주소로 변경
13848정성태12/18/20241784디버깅 기술: 209. Windbg로 알아보는 Prototype PTE파일 다운로드2
13847정성태12/18/20241676오류 유형: 939. golang - 빌드 시 "unknown directive: toolchain" 오류 빌드 시 이런 오류가 발생한다면?
13846정성태12/17/20241768디버깅 기술: 208. Windbg로 알아보는 Trans/Soft PTE와 2가지 Page Fault 유형파일 다운로드1
13845정성태12/16/20241958디버깅 기술: 207. Windbg로 알아보는 PTE (_MMPTE)
13844정성태12/14/20241833디버깅 기술: 206. Windbg로 알아보는 PFN (_MMPFN)파일 다운로드1
13843정성태12/13/20241869오류 유형: 938. Docker container 내에서 빌드 시 error MSB3021: Unable to copy file "..." to "...". Access to the path '...' is denied.
13842정성태12/12/20242263디버깅 기술: 205. Windbg - KPCR, KPRCB
13841정성태12/11/20242198오류 유형: 937. error MSB4044: The "ValidateValidArchitecture" task was not given a value for the required parameter "RemoteTarget"
13840정성태12/11/20241998오류 유형: 936. msbuild - Your project file doesn't list 'win' as a "RuntimeIdentifier"
13839정성태12/11/20241971오류 유형: 936. msbuild - error CS1617: Invalid option '12.0' for /langversion. Use '/langversion:?' to list supported values.
13838정성태12/4/20242244오류 유형: 935. Windbg - Breakpoint 0's offset expression evaluation failed.
13837정성태12/3/20242257디버깅 기술: 204. Windbg - 윈도우 핸들 테이블 (3) - Windows 10 이상인 경우
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...