Microsoft MVP성태의 닷넷 이야기
디버깅 기술: 205. Windbg - KPCR, KPRCB [링크 복사], [링크+제목 복사],
조회: 4584
글쓴 사람
정성태 (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

비밀번호

댓글 작성자
 




... 61  62  63  64  65  66  67  68  [69]  70  71  72  73  74  75  ...
NoWriterDateCnt.TitleFile(s)
12211정성태4/27/202019261개발 환경 구성: 486. WSL에서 Makefile로 공개된 리눅스 환경의 C/C++ 소스 코드 빌드
12210정성태4/20/202020691.NET Framework: 903. .NET Framework의 Strong-named 어셈블리 바인딩 (1) - app.config을 이용한 바인딩 리디렉션 [1]파일 다운로드1
12209정성태4/13/202017405오류 유형: 614. 리눅스 환경에서 C/C++ 프로그램이 Segmentation fault 에러가 발생한 경우 (2)
12208정성태4/12/202015965Linux: 29. 리눅스 환경에서 C/C++ 프로그램이 Segmentation fault 에러가 발생한 경우
12207정성태4/2/202015802스크립트: 19. Windows PowerShell의 NonInteractive 모드
12206정성태4/2/202018425오류 유형: 613. 파일 잠금이 바로 안 풀린다면? - The process cannot access the file '...' because it is being used by another process.
12205정성태4/2/202015094스크립트: 18. Powershell에서는 cmd.exe의 명령어를 지원하진 않습니다.
12204정성태4/1/202015090스크립트: 17. Powershell 명령어에 ';' (semi-colon) 문자가 포함된 경우
12203정성태3/18/202017938오류 유형: 612. warning: 'C:\ProgramData/Git/config' has a dubious owner: '...'.
12202정성태3/18/202021198개발 환경 구성: 486. .NET Framework 프로젝트를 위한 GitLab CI/CD Runner 구성
12201정성태3/18/202018434오류 유형: 611. git-credential-manager.exe: Using credentials for username "Personal Access Token". [1]
12200정성태3/18/202018523VS.NET IDE: 145. NuGet + Github 라이브러리 디버깅 관련 옵션 3가지 - "Enable Just My Code" / "Enable Source Link support" / "Suppress JIT optimization on module load (Managed only)"
12199정성태3/17/202016167오류 유형: 610. C# - CodeDomProvider 사용 시 Unhandled Exception: System.IO.DirectoryNotFoundException: Could not find a part of the path '...\f2_6uod0.tmp'.
12198정성태3/17/202019528오류 유형: 609. SQL 서버 접속 시 "Cannot open user default database. Login failed."
12197정성태3/17/202018811VS.NET IDE: 144. .NET Core 콘솔 응용 프로그램을 배포(publish) 시 docker image 자동 생성 - 두 번째 이야기 [1]
12196정성태3/17/202015943오류 유형: 608. The ServicedComponent being invoked is not correctly configured (Use regsvcs to re-register).
12195정성태3/16/202018262.NET Framework: 902. C# - 프로세스의 모든 핸들을 열람 - 세 번째 이야기
12194정성태3/16/202020993오류 유형: 607. PostgreSQL - Npgsql.NpgsqlException: sorry, too many clients already
12193정성태3/16/202017903개발 환경 구성: 485. docker - SAP Adaptive Server Enterprise 컨테이너 실행 [1]
12192정성태3/14/202019928개발 환경 구성: 484. docker - Sybase Anywhere 16 컨테이너 실행
12191정성태3/14/202021042개발 환경 구성: 483. docker - OracleXE 컨테이너 실행 [1]
12190정성태3/14/202015622오류 유형: 606. Docker Desktop 업그레이드 시 "The process cannot access the file 'C:\Program Files\Docker\Docker\resources\dockerd.exe' because it is being used by another process."
12189정성태3/13/202021220개발 환경 구성: 482. Facebook OAuth 처리 시 상태 정보 전달 방법과 "유효한 OAuth 리디렉션 URI" 설정 규칙
12188정성태3/13/202026020Windows: 169. 부팅 시점에 실행되는 chkdsk 결과를 확인하는 방법
12187정성태3/12/202015579오류 유형: 605. NtpClient was unable to set a manual peer to use as a time source because of duplicate error on '...'.
12186정성태3/12/202017399오류 유형: 604. The SysVol Permissions for one or more GPOs on this domain controller and not in sync with the permissions for the GPOs on the Baseline domain controller.
... 61  62  63  64  65  66  67  68  [69]  70  71  72  73  74  75  ...