Microsoft MVP성태의 닷넷 이야기
디버깅 기술: 205. Windbg - KPCR, KPRCB [링크 복사], [링크+제목 복사],
조회: 4037
글쓴 사람
정성태 (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)
13882정성태2/10/20252223스크립트: 70. 파이썬 - oracledb 패키지 연동 시 Thin / Thick 모드
13881정성태2/7/20252294닷넷: 2316. C# - Port I/O를 이용한 PCI Configuration Space 정보 열람파일 다운로드1
13880정성태2/5/20252191오류 유형: 947. sshd - Failed to start OpenSSH server daemon.
13879정성태2/5/20252445오류 유형: 946. Ubuntu - N: Updating from such a repository can't be done securely, and is therefore disabled by default.
13878정성태2/3/20252288오류 유형: 945. Windows - 최대 절전 모드 시 DRIVER_POWER_STATE_FAILURE 발생 (pacer.sys)
13877정성태1/25/20252468닷넷: 2315. C# - PCI 장치 열거 (레지스트리, SetupAPI)파일 다운로드1
13876정성태1/25/20252679닷넷: 2314. C# - ProcessStartInfo 타입의 Arguments와 ArgumentList파일 다운로드1
13875정성태1/24/20252591스크립트: 69. 파이썬 - multiprocessing 패키지의 spawn 모드로 동작하는 uvicorn의 workers
13874정성태1/24/20252626스크립트: 68. 파이썬 - multiprocessing Pool의 기본 프로세스 시작 모드(spawn, fork)
13873정성태1/23/20252505디버깅 기술: 217. WinDbg - PCI 장치 열거
13872정성태1/23/20252467오류 유형: 944. WinDbg - 원격 커널 디버깅이 연결은 되지만 Break (Ctrl + Break) 키를 눌러도 멈추지 않는 현상
13871정성태1/22/20252551Windows: 278. Windows - 윈도우를 다른 모니터 화면으로 이동시키는 단축키 (Window + Shift + 화살표)
13870정성태1/18/20252877개발 환경 구성: 741. WinDbg - 네트워크 커널 디버깅이 가능한 NIC 카드 지원 확대
13869정성태1/18/20252854개발 환경 구성: 740. WinDbg - _NT_SYMBOL_PATH 환경 변수에 설정한 경로로 심벌 파일을 다운로드하지 않는 경우
13868정성태1/17/20252715Windows: 277. Hyper-V - Windows 11 VM의 Enhanced Session 모드로 로그인을 할 수 없는 문제
13867정성태1/17/20253077오류 유형: 943. Hyper-V에 Windows 11 설치 시 "This PC doesn't currently meet Windows 11 system requirements" 오류
13866정성태1/16/20253033개발 환경 구성: 739. Windows 10부터 바뀐 device driver 서명 방법
13865정성태1/15/20253055오류 유형: 942. C# - .NET Framework 4.5.2 이하의 버전에서 HttpWebRequest로 https 호출 시 "System.Net.WebException" 예외 발생
13864정성태1/15/20252979Linux: 114. eBPF를 위해 필요한 SELinux 보안 정책
13863정성태1/14/20252819Linux: 113. Linux - 프로세스를 위한 전용 SELinux 보안 문맥 지정
13862정성태1/13/20252814Linux: 112. Linux - 데몬을 위한 SELinux 보안 정책 설정
13861정성태1/11/20253017Windows: 276. 명령행에서 원격 서비스를 동기/비동기로 시작/중지
13860정성태1/10/20253003디버깅 기술: 216. WinDbg - 2가지 유형의 식 평가 방법(MASM, C++)
13859정성태1/9/20253087디버깅 기술: 215. Windbg - syscall 이후 실행되는 KiSystemCall64 함수 및 SSDT 디버깅
13858정성태1/8/20253133개발 환경 구성: 738. PowerShell - 원격 호출 시 "powershell.exe"가 아닌 "pwsh.exe" 환경으로 명령어를 실행하는 방법
13857정성태1/7/20253134C/C++: 187. Golang - 콘솔 응용 프로그램을 Linux 데몬 서비스를 지원하도록 변경파일 다운로드1
1  [2]  3  4  5  6  7  8  9  10  11  12  13  14  15  ...