WinDbg - PCI 장치 열거
PC에 연결된 PCI Device 장치는 "Device Manager"를 통해 "View" / "Devices by connection" 메뉴를 통해 알 수 있습니다.
[그림: Hyper-V + Generation 1 VM 환경에서 본 PCI 장치]

위의 경우, "Intel(R) 82371AB/EB PCI Bus Master IDE Controller" 장치가 PCI 0번 버스에 Device == 7, Function == 1로 연결돼 있는 것을 보여줍니다.
혹시, 이론상 장착 가능한 PCI 장치의 수가 있을까요? 이에 대해 검색해 보면,
What limits the number of buses, devices and functions on a PCI bus?
; https://electronics.stackexchange.com/questions/393830/what-limits-the-number-of-buses-devices-and-functions-on-a-pci-bus
Bus Number로 8비트, Device Number로 5비트, Function Number로 3비트를 사용하므로, 각각 256, 32, 8개까지 지정할 수 있습니다. 정리해 보면, 1대의 PC가 256개의 Bus를 가질 수 있고, 개별 버스는 32개의 장치를, 개별 장치는 최대 8개의 Function을 내장하는 것이 가능합니다. 물론, 이것은 이론적으로 그렇다는 것이지 실제 규격은 Motherboard의 재량에 따라 달라집니다.
PCI 장치 열거는 WinDbg로도
!pcitree 명령어를 이용해 확인할 수 있는데요,
// Hyper-V + "Generation 1" 유형의 VM
4: kd> !pcitree
Bus 0x0 (FDO Ext ffff878f10a1ca20)
(d=0, f=0) 80867192 devext 0xffff878f10a174b0 devstack 0xffff878f10a17360 0600 Bridge/HOST to PCI
(d=7, f=0) 80867110 devext 0xffff878f10a394b0 devstack 0xffff878f10a39360 0601 Bridge/PCI to ISA
(d=7, f=1) 80867111 devext 0xffff878f10a3b4b0 devstack 0xffff878f10a3b360 0101 Mass Storage Controller/IDE
(d=8, f=0) 14145353 devext 0xffff878f10a3f4b0 devstack 0xffff878f10a3f360 0300 Display Controller/VGA
Total PCI Root busses processed = 1
Total PCI Segments processed = 1
Device Manager에서 확인했던 "Intel(R) 82371AB/EB PCI Bus Master IDE Controller" 장치가 !pcitree에서는 "Mass Storage Controller/IDE"라고 보입니다. 뿐만 아니라
!pci 명령어로도 장치 열거가 가능한데요,
4: kd> !pci
PCI Segment 0 Bus 0
00:0 8086:7192.03 Cmd[0006:.mb...] Sts[0200:.....] Intel Host Bridge
07:0 8086:7110.01 Cmd[0007:imb...] Sts[0200:.....] Intel ISA Bridge SubID:1414:0000
07:1 8086:7111.01 Cmd[0005:i.b...] Sts[0280:.....] Intel IDE Controller
07:3 8086:7113.02 Cmd[0001:i.....] Sts[0280:.....] Intel Other Bridge
08:0 1414:5353.00 Cmd[011e:.mb..s] Sts[0000:.....] VGA Compatible Controller
// 각 장치의 출력에서 처음 "xx:y"는 "Device xx, Function y"를 의미합니다.
특이하게도 !pcitree와 Device Manager에는 보이지 않던 장치인 "Bus: 0, Device: 7, Function: 3"인 "Intel Other Bridge" 장치가 보입니다. 또한, !pci 명령어의 경우 특정 장치만을 골라 자세한 정보를 확인할 수도 있는데, 가령 bus == 0, device == 7, function == 3인 장치에 대한 정보는 이렇게 확인합니다.
// !pci [flags == 0x1FF] [bus] [device] [function]
// 0x1ff == 0b111111111 (0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80 | 0x100)
0: kd> !pci 0x1FF 0 7 3
PCI Configuration Space (Segment:0000 Bus:00 Device:07 Function:03)
Common Header:
00: VendorID 8086 Intel Corporation
02: DeviceID 7113
04: Command 0001 IOSpaceEn
06: Status 0280 FB2BCapable DEVSELTiming:1
08: RevisionID 02
09: ProgIF 00
0a: SubClass 80 Other Bridge
0b: BaseClass 06 Bridge Device
0c: CacheLineSize 0000
0d: LatencyTimer 00
0e: HeaderType 00
0f: BIST 00
10: BAR0 00000000
14: BAR1 00000000
18: BAR2 00000000
1c: BAR3 00000000
20: BAR4 00000000
24: BAR5 00000000
28: CBCISPtr 00000000
2c: SubSysVenID 0000
2e: SubSysID 0000
30: ROMBAR 00000000
34: CapPtr 00
3c: IntLine 00
3d: IntPin 01
3e: MinGnt 00
3f: MaxLat 00
Device Private:
40: 00000401 00000000 00000000 00000000
50: 00000000 00000000 02000000 10000000
60: 40000000 10c700e0 00000000 00000000
70: 00000000 00000000 00000000 00000000
80: 00000001 00000000 00000000 00000000
90: 00000000 00000000 00000000 00000000
a0: 00000000 00000000 00000000 00000000
b0: 00000000 00000000 00000000 00000000
c0: 00000000 00000000 00000000 00000000
d0: 00000000 00000000 00000000 00000000
e0: 00000000 00000000 00000000 00000000
f0: 00000000 00000000 00000000 00000000
출력의 최상단을 보면 "PCI Configuration Space"라는 문자열이 나오는데요, 이는 PCI 장치가 제공하는 256 바이트 크기의 메모리 영역을 의미합니다. 즉, BIOS/OS가 부팅 시에 PCI 장치를 찾아 해당 장치로부터 저 정보를 읽어와 초기화를 진행하는 것입니다.
따라서, OS는 PCI 장치로부터 Vendor ID, Device ID, Revision ID, BaseClass, SubClass 등의 정보를 읽어올 수 있고 이를 통해 해당 장치를 다룰 수 있는 Device Driver를 로드할 수 있습니다.
참고로, 근래의 Hyper-V 환경에서는 Generation 2 유형의 VM을 주로 만들게 되는데요, 이런 VM에서는 PCI 장치가 아예 열거되지 않습니다.
8: kd> !pcitree
Total PCI Root busses processed = 0
Total PCI Segments processed = 1
0: kd> !pci 602 ffff ff
0: kd>
왜냐하면, 어차피 소프트웨어로 구현된 장치들이기 때문에 PCI로 추상화시키지 않고 아예 Hyper-V에 맞게 최적화시킨 구조로 재작성했기 때문에,
보다시피, PCI 버스가 아닌 별도로 구현한 "Microsoft Hyper-V Virtual Machine Bus"로 돼 있어 PCI 장치가 아예 존재하지 않습니다.
PCI 장치의 Configuration Space는 256 바이트였지만, PCIe로 오면서 4,096 바이트까지 늘었습니다. 이러한 정보를 읽어오는 2가지 방법이 있는데요,
Introduction to PCIe
; https://astralvx.com/introduction-to-pcie/
- Mechanism #1: Configuration Access Mechanism (CAM)
- Mechanism #2: Enhanced Configuration Access Mechanism (ECAM)
1번 방법이 기존의 PCI 장치에 대해 0xcf8, 0xcfc I/O 포트를 이용해 256 바이트를 접근할 수 있는 방법이고 2번 방법이 PCIe 장치에 대해 운영체제가 메모리 맵핑된 주소 공간을 제공해 4,096 바이트까지 접근할 수 있는 방법입니다.
하지만, 하위 호환성을 유지하기 위해 PCIe 장치로 CAM 방식의 접근 방식을 통해 256 바이트를 읽어오는 것이 가능합니다.
재미 삼아 WinDbg를 이용해 약간의 실습을 해볼까요? ^^ 우선, 1번 CAM 방법은 I/O 포트를 이용해 접근해야 하는데 WinDbg에서는
IN,
OUT 명령어를 사용할 수는 없으므로 실습이 안 됩니다. 대신 2번 방법은 가능한데요, WinDbg에서는 PCIe 장치의 Configuration Space를 메모리 매핑된 주소 공간을
!acpicache 명령어로 확인할 수 있기 때문입니다.
그런데 여기서도 한 가지 문제가 있는데요, Hyper-V의 Generation 1 VM 환경은 PCIe 규격까지는 지원하지 않는 것 같습니다. 어차피 가상이라 굳이 그렇게까지 맞춰 줄 필요는 없었던 것 같은데, 그래서 출력에 (PCIe의 ECAM에 대한 메모리 매핑 정보를 제공하는) MCFG 테이블이 없습니다.
// Hyper-V + "Generation 1" 유형의 VM
4: kd> !acpicache
Dumping cached ACPI tables...
RSDT @(fffff7d100003018) Rev: 0x1 Len: 0x000040 TableID: MICROSFT
FACP @(fffff7d10000c018) Rev: 0x2 Len: 0x000081 TableID: MICROSFT
SRAT @(fffff7d10000d018) Rev: 0x2 Len: 0x000190 TableID: MICROSFT
WAET @(fffff7d10000f018) Rev: 0x1 Len: 0x000028 TableID: MICROSFT
APIC @(fffff7d100011018) Rev: 0x1 Len: 0x0000b2 TableID: MICROSFT
SLIC @(ffff878f0f8e31b8) Rev: 0x1 Len: 0x000176 TableID: MICROSFT
OEM0 @(ffff878f0f8e3358) Rev: 0x1 Len: 0x000064 TableID: MICROSFT
OEMB @(ffff878f0f8e3688) Rev: 0x1 Len: 0x000064 TableID: MICROSFT
DSDT @(ffff878f0fab1018) Rev: 0x1 Len: 0x003cd5 TableID: MSFTVM02
이는 Generation 2 VM 환경에서도 동일합니다.
// Hyper-V + "Generation 2" 유형의 VM (어차피 PCI 버스가 없으므로 굳이 MCFG 테이블을 제공하지 않는 듯!)
0: kd> !acpicache
Dumping cached ACPI tables...
XSDT @(fffff7a140003018) Rev: 0x1 Len: 0x00005c TableID: MICROSFT
FACP @(fffff7a14000c018) Rev: 0x6 Len: 0x000114 TableID: MICROSFT
SRAT @(fffff7a14000d018) Rev: 0x2 Len: 0x0003b0 TableID: MICROSFT
WAET @(fffff7a14000e018) Rev: 0x1 Len: 0x000028 TableID: MICROSFT
APIC @(fffff7a140010018) Rev: 0x4 Len: 0x0000c8 TableID: MICROSFT
OEM0 @(ffff9a0313747f38) Rev: 0x1 Len: 0x000064 TableID: MICROSFT
TPM2 @(ffff9a03137484e8) Rev: 0x3 Len: 0x000034 TableID: VTPM
BGRT @(ffff9a0313748548) Rev: 0x1 Len: 0x000038 TableID: MICROSFT
DSDT @(ffff9a031399f018) Rev: 0x2 Len: 0x01e191 TableID: DSDT01
그래서 이에 대한 실습을 하려면 직접 물리 PC를 이용해야 하는데요, 다음은 Surface Pro 6에서의 출력을 보여줍니다.
// Surface Pro 6 환경
lkd> !acpicache
Dumping cached ACPI tables...
XSDT @(fffff785c0002018) Rev: 0x1 Len: 0x0000b4 TableID: MSFT
MCFG @(fffff785c0003018) Rev: 0x1 Len: 0x00003c TableID: MSFT
FACP @(fffff785c000f018) Rev: 0x5 Len: 0x00010c TableID: MSFT
APIC @(fffff785c0010018) Rev: 0x3 Len: 0x00012c TableID: MSFT
DMAR @(fffff785c0012018) Rev: 0x1 Len: 0x000088 TableID: MSFT
HPET @(fffff785c001b018) Rev: 0x1 Len: 0x000038 TableID: MSFT
FPDT @(ffffc58c446f2098) Rev: 0x1 Len: 0x000034 TableID: MSFT
WSMT @(ffffc58c446f2ed8) Rev: 0x1 Len: 0x000028 TableID: MSFT
MSDM @(ffffc58c446f2f28) Rev: 0x1 Len: 0x000055 TableID:
SSDT @(ffffc58c448e5018) Rev: 0x2 Len: 0x01a714 TableID: DptfTabl
SSDT @(ffffc58c448ff758) Rev: 0x2 Len: 0x000574 TableID: Tpm2Tabl
TPM2 @(ffffc58c448ffcf8) Rev: 0x3 Len: 0x000034 TableID: MSFT
SSDT @(ffffc58c44aee018) Rev: 0x2 Len: 0x0033bf TableID: SaSsdt
LPIT @(ffffc58c44af1638) Rev: 0x1 Len: 0x000094 TableID: MSFT
SSDT @(ffffc58c44af2018) Rev: 0x2 Len: 0x003461 TableID: RTD3_CA
SSDT @(ffffc58c44af54a8) Rev: 0x2 Len: 0x0008f2 TableID: xh_ca000
SSDT @(ffffc58c44af6018) Rev: 0x2 Len: 0x0017ae TableID: CpuSsdt
NHLT @(ffffc58c44af78a8) Rev: 000 Len: 0x00002d TableID: MSFT
BGRT @(ffffc58c44af7968) Rev: 0x1 Len: 0x000038 TableID: MSFT
DSDT @(ffffc58c449c5018) Rev: 0x2 Len: 0x017fc2 TableID: MSFT
보다시피 MCFG 테이블이 있고 이에 대해 더 자세한 정보를 확인하면,
lkd> !acpitable MCFG
HEADER - fffff785c0003018
Signature: MCFG
Length: 0x0000003c
Revision: 0x01
Checksum: 0xf9
OEMID: MSFT
OEMTableID: MSFT
OEMRevision: 0x00000001
CreatorID: MSFT
CreatorRev: 0x0000005f
BODY - fffff785c000303c
fffff785`c000303c 00 00 00 00 00 00 00 00-00 00 00 e0 00 00 00 00 ................
fffff785`c000304c 00 00 00 ff 00 00 00 00 ........
MCFG @(fffff785c0003018) 주소에는 헤더 정보가 있고,
// RSDT
// https://wiki.osdev.org/RSDT#Structure
struct ACPISDTHeader {
char Signature[4];
uint32_t Length; // 헤더 크기 (위의 경우, 0x0000003c)
uint8_t Revision;
uint8_t Checksum;
char OEMID[6];
char OEMTableID[8];
uint32_t OEMRevision;
uint32_t CreatorID;
uint32_t CreatorRevision;
};
sizeof(ACPISDTHeader) == 36 (0x24)
ACPISDTHeader.Length (0x3c) 값에서 sizeof(ACPISDTHeader)를 뺀 0x18 (24) 바이트에 해당하는 BODY 정보가 "BODY - fffff785c000303c" 주소부터 이어집니다.
MCFG 테이블의 BODY 해석은 아래의 문서에서 찾을 수 있는데요,
Enhanced Configuration Mechanism
; https://wiki.osdev.org/PCI_Express#Enhanced_Configuration_Mechanism
따라서 다음과 같이 정리할 수 있습니다.
// MCFG 테이블의 BODY 정보 (24바이트)
lkd> dq /c1 fffff785c000303c L3
fffff785`c000303c 00000000`00000000 // Reserved
fffff785`c0003044 00000000`e0000000 // Base address of enhanced configuration mechanism
fffff785`c000304c 00000000`ff000000 // 0 ~ 2: PCI Segment Group Number
// 3: Start PCI bus number decoded by this host bridge (따라서 0x0)
// 4: End PCI bus number decoded by this host bridge (따라서 0xff)
// 5 ~ 8: Reserved
즉, ECAM 정보가 매핑된 물리 주소는 00000000`e0000000이고, Bus Number로 0 ~ 0xff까지 담고 있으니까, (256개의 버스) * (버스 하나당 32개의 Device) * (Device 하나당 8개의 Function) * (1개의 ECAM 정보 크기 4,096 바이트)개가 매핑되어 있으니 총 256MB의 공간(00000000`e0000000 ~ 00000000`efffffff)이 ECAM으로 메모리 매핑된 것입니다.
그리고, 해당 공간에서 특정 PCI 장치의 Configuration Space에 해당하는 위치는 다음의 수식으로 구할 수 있습니다.
Physical_Address = MMIO_Starting_Physical_Address + ((Bus) << 20 | Device << 15 | Function << 12)
예를 들기 위해 장치 하나를 골라 볼까요? ^^
lkd> !pcitree
Bus 0x0 (FDO Ext ffffc58c479e21d0)
(d=0, f=0) 80865914 devext 0xffffc58c45ef04b0 devstack 0xffffc58c45ef0360 0600 Bridge/HOST to PCI
(d=2, f=0) 80865917 devext 0xffffc58c476e44b0 devstack 0xffffc58c476e4360 0300 Display Controller/VGA
(d=4, f=0) 80861903 devext 0xffffc58c472e44b0 devstack 0xffffc58c472e4360 1180 Unknown Base Class/Unknown Sub Class
(d=5, f=0) 80861919 devext 0xffffc58c472e64b0 devstack 0xffffc58c472e6360 0480 Multimedia Device/'Other'
(d=13, f=0) 80869d35 devext 0xffffc58c472e84b0 devstack 0xffffc58c472e8360 0000 Pre PCI 2.0/Pre PCI 2.0 Non-VGA Device
(d=14, f=0) 80869d2f devext 0xffffc58c479e44b0 devstack 0xffffc58c479e4360 0c03 Serial Bus Controller/USB
(d=14, f=2) 80869d31 devext 0xffffc58c479e64b0 devstack 0xffffc58c479e6360 1180 Unknown Base Class/Unknown Sub Class
(d=14, f=3) 80869d32 devext 0xffffc58c479e84b0 devstack 0xffffc58c479e8360 0480 Multimedia Device/'Other'
(d=15, f=0) 80869d60 devext 0xffffc58c479ea4b0 devstack 0xffffc58c479ea360 1180 Unknown Base Class/Unknown Sub Class
(d=15, f=1) 80869d61 devext 0xffffc58c479ec4b0 devstack 0xffffc58c479ec360 1180 Unknown Base Class/Unknown Sub Class
(d=15, f=2) 80869d62 devext 0xffffc58c479ee4b0 devstack 0xffffc58c479ee360 1180 Unknown Base Class/Unknown Sub Class
(d=15, f=3) 80869d63 devext 0xffffc58c479f04b0 devstack 0xffffc58c479f0360 1180 Unknown Base Class/Unknown Sub Class
(d=16, f=0) 80869d3a devext 0xffffc58c45fe44b0 devstack 0xffffc58c45fe4360 0780 Simple Serial Communications Controller/'Other'
(d=16, f=4) 80869d3e devext 0xffffc58c45fe64b0 devstack 0xffffc58c45fe6360 0780 Simple Serial Communications Controller/'Other'
(d=1c, f=0) 80869d13 devext 0xffffc58c45fe84b0 devstack 0xffffc58c45fe8360 0604 Bridge/PCI to PCI
Bus 0x1 (FDO Ext ffffc58c479f1190)
(d=0, f=0) 11ab2b38 devext 0xffffc58c470e21f0 devstack 0xffffc58c470e20a0 0200 Network Controller/Ethernet
(d=1d, f=0) 80869d18 devext 0xffffc58c45fec4b0 devstack 0xffffc58c45fec360 0604 Bridge/PCI to PCI
Bus 0x2 (FDO Ext ffffc58c471ea250)
(d=0, f=0) 144da806 devext 0xffffc58c470e51b0 devstack 0xffffc58c470e5060 0108 Mass Storage Controller/Unknown Sub Class
(d=1e, f=0) 80869d27 devext 0xffffc58c45ff04b0 devstack 0xffffc58c45ff0360 1180 Unknown Base Class/Unknown Sub Class
(d=1f, f=0) 80869d4e devext 0xffffc58c471e44b0 devstack 0xffffc58c471e4360 0601 Bridge/PCI to ISA
(d=1f, f=2) 80869d21 devext 0xffffc58c471e64b0 devstack 0xffffc58c471e6360 0580 Memory Controller/'Other'
(d=1f, f=3) 80869d71 devext 0xffffc58c472ef1b0 devstack 0xffffc58c472ef060 0403 Multimedia Device/Unknown Sub Class
Total PCI Root busses processed = 1
Total PCI Segments processed = 1
bus = 2, device = 0, function = 0에 해당하는 장치의 PCI Configuration Space 장치를 확인하려면,
Physical_Address = 00000000`e0000000 + ((2) << 20 | 0 << 15 | 0 << 12)
= 0xe0200000
이렇게 계산한 물리 주소를 덤프해 보면 됩니다.
// 아래의 출력은 MCFG 테이블이 있는 경우에만 실습이 가능합니다.
// 따라서 Hyper-V (Gen1, Gen2 상관없이) VM에서는 실습할 수 없습니다.
lkd> dd /p 0xe0200000 L64
00000000`e0200000 a806144d 00100406 01080200 00000000
00000000`e0200010 a1600004 00000000 00000000 00000000
00000000`e0200020 00000000 00000000 00000000 a801144d
00000000`e0200030 00000000 00000040 00000000 00000100
00000000`e0200040 00035001 00000008 00000000 00000000
00000000`e0200050 00867005 00000000 00000000 00000000
00000000`e0200060 00000000 00000000 00000000 00000000
00000000`e0200070 0002b010 17e88fc1 00102037 00477823
00000000`e0200080 10230142 00000000 00000000 00000000
00000000`e0200090 00000000 0000081f 00000400 0000000e
00000000`e02000a0 001e0003 00000000 00000000 00000000
00000000`e02000b0 80070011 00003000 00002000 00000000
00000000`e02000c0 00000000 00000000 00000000 00000000
00000000`e02000d0 00000003 00000000 00000000 00000000
00000000`e02000e0 00000000 00000000 00000000 00000000
00000000`e02000f0 00000000 00000000 00000000 00000000
00000000`e0200100 14820001 00000000 00000000 00062030
00000000`e0200110 00000000 00000000 000000a0 00000000
00000000`e0200120 00000000 00000000 00000000 00000000
00000000`e0200130 00000000 00000000 00000000 00000000
00000000`e0200140 00000000 00000000 15810003 00000000
00000000`e0200150 00000000 00000000 16810004 00000000
00000000`e0200160 00000000 00000001 18810019 00000000
00000000`e0200170 00000000 75007500 00000000 00000000
00000000`e0200180 00000000 00000000 19010018 10021002
e0200000의 첫 번째 2바이트가 Vendor ID, 그다음 2바이트가 Device ID이므로 a806144dd의 경우 (little endian임을 고려해) Vendor ID == 144d, Device ID == a806인 것으로 해석됩니다. 실제로
!pci 명령어를 통해 확인해 보면,
lkd> !pci 1 2 0 0
PCI Segment 0 Bus 0x2
00:0 144d:a806.00 Cmd[0406:.mb...] Sts[0010:c....] Class:1:8:2 SubID:144d:a801
cf8:80020000 IntPin:1 IntLine:0 Rom:0 cis:0 cap:40
MEM[0]:a1600004
정확히 일치하는 것을 볼 수 있습니다. 일단 이 정도만 알아봐도 소프트웨어 개발자로서는 충분할 것 같습니다. ^^
혹시 Device Driver에서 PCI Configuration Space를 ECAM으로부터 읽어오는 방법이 있을까요?
Accessing PCI device configuration space
; https://learn.microsoft.com/en-us/windows-hardware/drivers/pci/accessing-pci-device-configuration-space
Get ECAM base address from KMDF
; https://community.osr.com/t/get-ecam-base-address-from-kmdf/58296
BUS_INTERFACE_STANDARD::GetBusData and location (bus, slot, function)
; https://community.osr.com/t/bus-interface-standard-getbusdata-and-location-bus-slot-function/54670
과거에는
HalGetBusDataByOffset 함수를 사용할 수 있었던 것 같은데,
PCI_SLOT_NUMBER slot = { 0 };
slot.u.bits.DeviceNumber = dwSlot;
slot.u.bits.FunctionNumber = dwFunction;
data_len_ret = HalGetBusDataByOffset(PCIConfiguration, dwBus, slot.u.AsULONG, ulData, dwOffset, sizeof(ulData));
현재는 GUID_BUS_INTERFACE_STANDARD를 대상으로 IRP_MN_QUERY_INTERFACE를 사용하라고 합니다. 이렇게 얻은 ECAM 주소는 이전에 설명한 방법에 따라 bus/device/function을 계산해 덤프하면 될 것입니다.
혹은 버스 드라이버 측에
IRP_MN_READ_CONFIG 메시지를 보내,
Understanding PCI Configuration Space
; https://bsodtutorials.blogspot.com/2014/01/understanding-pci-configuration-space.html
보다 더 안전하게 조회하는 방법도 있습니다. 마지막으로, 약간 불안한 방법이라면, 아마도 "0xe0000000"라는 주소에서 느껴지듯이
Windows 운영체제는 고정적으로 메모리 매핑을 하는 것 같으므로 상황에 따라 그냥 하드 코딩해도 될 것입니다. ^^; 주로 그 주소로 나오긴 하지만, 간혹 테스트를 해보면 0xc0000000 또는
0xd0000000으로 나오는 경우도 있으니 사용 전 확인이 필요합니다.
참고로,
!pci 명령어가 꽤나 복잡한 옵션을 제공하는데요,
!pci [Flags [Segment] [Bus [Device [Function [MinAddress MaxAddress]]]]]
첫 번째로 지정한 Flags 값에 따라 이후의 옵션 해석이 달라진다는 점에 주의해야 합니다. 가령, 도움말에서 Flags의 Bit 1을 보면,
Bit 1 (0x2)
Causes the display to include all buses in the range from bus 0 (zero) to the specified Bus.
라고 나오는데요, 따라서 flags에 bit 1이 켜져 있으면 Bus 옵션 값이 함께 지정된 것으로 간주합니다. 예를 들어, Hyper-V VM의 경우 Bus == 0에 모든 장치들이 연결된 것으로 구성하므로 그런 환경에서는 "!pci 2 0"이든 "!pci 2 ff"든 결과는 같습니다.
또 다른 예를 들어볼까요?
Bit 9 (0x200)
Causes the display to include segment information. When this bit is included, the Segment parameter must be included.
즉, bit 9가 켜져 있으면 segment도 지정한 것으로 간주하게 되는데요, 만약 이것이 Bit 1과 함께 켜져 있다면, Segment와 Bus가 모두 지정된 것으로 간주합니다.
// Flags에 따라 "!pci [flags] [segment] [bus]"로 해석
0: kd> !pci 0x202 0 0
PCI Segment 0 Bus 0
00:0 8086:7192.03 Cmd[0006:.mb...] Sts[0200:.....] Intel Host Bridge
07:0 8086:7110.01 Cmd[0007:imb...] Sts[0200:.....] Intel ISA Bridge SubID:1414:0000
07:1 8086:7111.01 Cmd[0005:i.b...] Sts[0280:.....] Intel IDE Controller
07:3 8086:7113.02 Cmd[0001:i.....] Sts[0280:.....] Intel Other Bridge
08:0 1414:5353.00 Cmd[011e:.mb..s] Sts[0000:.....] VGA Compatible Controller
0: kd> !pci 0x202 1 0
1f // 1번 segment에는 장치가 없으므로
대충 어떤 식인지 아시겠죠? ^^ 다른 예를 하나 더 들어 볼까요? 가령 MinAddress MaxAddress라고 나오는 옵션이 유효하려면 그것을 사용하라고 지시한 비트를 켜야 합니다. 이에 해당하는 것이 Bit 2니까,
Bit 2 (0x4)
Causes the display to include information in raw byte format. If MinAddress, MaxAddress, or flag bit 0x8 is set, this bit is automatically set as well.
이제 각각의 옵션 값은 다음과 같은 의미를 갖게 됩니다.
// !pci 0x4 [bus] [device] [function] [MinAddress] [MaxAddress]
0: kd> !pci 0x4 0 7 3 0 0x1f
PCI Segment 0 Bus 0
07:3 8086:7113.02 Cmd[0001:i.....] Sts[0280:.....] Intel Other Bridge
00000000: 86 80 13 71 01 00 80 02-02 00 80 06 00 00 00 00 *...q............*
00000010: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 *................*
문서에 따르면 PCI segment 1개가 256개의 버스를 지원한다고 하니, 아마도 웬만한 PC에서는 거의 segment 옵션은 그냥 0으로 지정하면 될 듯합니다.
!pcitree 명령어에 대해 이런 식으로 오류가 발생하는 경우가 있습니다.
8: kd> !pcitree
Error retrieving address of PciFdoExtensionListHead
Symbol 파일을 다시 로드하면 정상 동작할 텐데요,
8: kd> .reload /f
만약 그래도 오류가 발생한다면 해당 환경이 Hyper-V의 Generation 2 VM일 경우가 있습니다. 또한, Generation 2 VM의 경우 PCI 장치가 없어 !pci 명령어를 수행하는 경우 다음과 같이 출력되는 것을 볼 수 있습니다.
0: kd> !pci 1
1f
아마도 !pci 명령어는 bus에 연결됐을 가능성이 있는 범위인 0 ~ 0x1f 개를 모두 확인하기 때문에 마지막에 1f가 출력되는 듯합니다.
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]