Je me propose de vous faire découvrir une « stack storage » typique que l’on retrouve coté serveur. Il y a des différences notables par rapport aux « stacks storage » coté poste client en raison de la complexité de certaines technologies inhérentes au monde serveur. A savoir : le multi-pathing, le fait que l’on va travailler sur des LUNs (du FC, du iSCSI, du SCSI locale) etc… L’idée étant de comprendre le cheminement que va prendre une IO pour atteindre un disque. i.e. quels devices\drivers vont prendre part à la résolution d’une IO.
Attention, ça pique un peu. Bonne lecture !
Le dump que nous allons analyser a été généré sur un Windows 2016 (TP3) avec une topologie représentative (mais non exhaustive) de ce que l’on peut rencontrer.
1: kd> vertarget
Windows 10 Kernel Version 10514 MP (4 procs) Free x64
Product: Server, suite: TerminalServer DataCenter SingleUserTS
Built by: 10514.0.amd64fre.th2_release.150808-1529
Machine Name: "NUC1"
Kernel base = 0xfffff801`31873000 PsLoadedModuleList = 0xfffff801`31b9fb10
Debug session time: Wed Sep 30 00:04:08.423 2015 (UTC + 1:00)
System Uptime: 0 days 0:11:04.218
Pour connaitre le nombre de volumes que Windows « voit », il faut simplement rechercher les DEVICE_OBJECT gérés par le driver volmgr.sys. Dans notre cas, nous avons 4 devices:
1: kd> !drvobj \driver\volmgr
Driver object (ffffe000abe2cc00) is for:
\Driver\volmgr
Driver Extension List: (id , addr)
Device Object list:
ffffe000adb4bac0 ffffe000ac832c90 ffffe000ac835980 ffffe000ac3d5580
Il faut savoir que pour le driver volmgr.sys (comme pour certains autres), il y a toujours un device servant à « gérer » les autres devices. i.e. il y a un device qui ne représente pas un volume réel. Concernant volmgr.sys, il s’appelle toujours VolMgrControl et, est le premier device créé (donc le dernier dans la liste ci-dessus):
1: kd> !devobj ffffe000ac3d5580
Device object (ffffe000ac3d5580) is for:
VolMgrControl \Driver\volmgr DriverObject ffffe000abe2cc00
Current Irp 00000000 RefCount 0 Type 00000012 Flags 00000840
Dacl ffffc101230c67f0 DevExt ffffe000ac3d56d0 DevObjExt ffffe000ac3d5860
ExtensionFlags (0x00000800) DOE_DEFAULT_SD_PRESENT
Characteristics (0x00000100) FILE_DEVICE_SECURE_OPEN
AttachedTo (Lower) ffffe000abfa2e40 \Driver\PnpManager
Device queue is not busy.
Nous savons donc que Windows, au moment de la génération du dump, « voyait » 3 volumes => HarddiskVolume 1 à 3. Les voici :
1: kd> !devobj ffffe000adb4bac0
Device object (ffffe000adb4bac0) is for:
HarddiskVolume3 \Driver\volmgr DriverObject ffffe000abe2cc00
Current Irp 00000000 RefCount 22 Type 00000007 Flags 00003050
Vpb ffffe000adb3efa0 Dacl ffffc101230c67f0 DevExt ffffe000adb4bc10 DevObjExt ffffe000adb4bdc0 Dope ffffe000adb39d10 DevNode ffffe000ad6c7360
ExtensionFlags (0x00000800) DOE_DEFAULT_SD_PRESENT
Characteristics (0x00020000) FILE_DEVICE_ALLOW_APPCONTAINER_TRAVERSAL
AttachedDevice (Upper) ffffe000adaf7040 \Driver\volsnap
Device queue is not busy.
1: kd> !devobj ffffe000ac832c90
Device object (ffffe000ac832c90) is for:
HarddiskVolume2 \Driver\volmgr DriverObject ffffe000abe2cc00
Current Irp 00000000 RefCount 1867 Type 00000007 Flags 00001150
Vpb ffffe000ac83a5c0 Dacl ffffc101230c67f0 DevExt ffffe000ac832de0 DevObjExt ffffe000ac832f90 Dope ffffe000ac83a550 DevNode ffffe000ac832960
ExtensionFlags (0x00000800) DOE_DEFAULT_SD_PRESENT
Characteristics (0x00020000) FILE_DEVICE_ALLOW_APPCONTAINER_TRAVERSAL
AttachedDevice (Upper) ffffe000ac831040 \Driver\volsnap
Device queue is not busy.
1: kd> !devobj ffffe000ac835980
Device object (ffffe000ac835980) is for:
HarddiskVolume1 \Driver\volmgr DriverObject ffffe000abe2cc00
Current Irp 00000000 RefCount 20 Type 00000007 Flags 00203050
Vpb ffffe000ac8358c0 Dacl ffffc101230c67f0 DevExt ffffe000ac835ad0 DevObjExt ffffe000ac835c80 Dope ffffe000abdf4cd0 DevNode ffffe000ac8355e0
ExtensionFlags (0x00000800) DOE_DEFAULT_SD_PRESENT
Characteristics (0x00020000) FILE_DEVICE_ALLOW_APPCONTAINER_TRAVERSAL
AttachedDevice (Upper) ffffe000ac833040 \Driver\volsnap
Device queue is not busy.
Sur ces 3 volumes, il y en 2 qui correspondent : au volume de 300MB pour la base BCD et au volume Système de Windows (typiquement, ce que nous retrouverons sur un poste client). Ce sont respectivement les 2 premiers devices créés durant la phase de boot. Nous pouvons d’ailleurs retrouver notre volume système facilement avec cette commande :
1: kd> !driveinfo c:
Drive c:, DriveObject ffffc000230ea8d0
Directory Object: ffffc00022e1e630 Name: C:
Target String is '\Device\HarddiskVolume2'
Drive Letter Index is 3 (C:)
Volume DevObj: ffffe000ac832c90
Vpb: ffffe000ac83a5c0 DeviceObject: ffffe000abe18030
FileSystem: \FileSystem\Ntfs
Volume has 0x1703f4e (free) / 0x1bd32ff (total) clusters of size 0x1000
94271.3 of 113971 MB free
Nous pouvons également retrouver le nom des volumes (s’il y en a un) avec la commande !vpb (Volume Parameter Block ).
Pour le HarddiskVolume1 (BCD), nous avons « System Reserved »:
1: kd> !vpb ffffe000ac8358c0
Vpb at 0xffffe000ac8358c0
Flags: 0x1 mounted
DeviceObject: 0xffffe000acfc2030
RealDevice: 0xffffe000ac835980
RefCount: 20
Volume Label: System Reserved
Pour le HarddiskVolume2 (C:), nous n’avons pas de nom (car pas de label):
1: kd> !vpb ffffe000ac83a5c0
Vpb at 0xffffe000ac83a5c0
Flags: 0x1 mounted
DeviceObject: 0xffffe000abe18030
RealDevice: 0xffffe000ac832c90
RefCount: 1867
Volume Label:
Pour le HarddiskVolume3 (une LUN), nous avons « LU-08 »:
1: kd> !vpb ffffe000adb3efa0
Vpb at 0xffffe000adb3efa0
Flags: 0x1 mounted
DeviceObject: 0xffffe000adb30030
RealDevice: 0xffffe000adb4bac0
RefCount: 22
Volume Label: LU-08
Ce que nous avons découvert correspond pour le moment à ça :
![]()
L’idée de cet article consistant à montrer une stack storage « serveur », nous allons maintenant rentrer dans les détails de notre LUN (LU-08) qui se trouve être notre volume HarddiskVolume3. Une stack storage est, en réalité, composée de plusieurs sous stacks différentes. Dans cet exemple, et pour illustrer un cas complexe réel, nous rencontrerons une stack File System, une stack Volume, une stack disk (MPIO dans notre cas) et enfin les stacks Storport (pour la gestion de la partie SCSI). Pourquoi ? parce qu’une IO à destination de notre LUN va traverser un file system (NTFS\FAT et tous les File System minifilter drivers), puis un volume, puis va traverser un disque multi-path (MPIO) pour ensuite atteindre une LUN sous la forme d’une commande SCSI à destination du stockage.
Commençons par la stack file system. Le point d’entrée de la stack File System se trouve être le DeviceObject de notre commande !vpb (voir plus haut) :
1: kd> !devstack 0xffffe000adb30030
!DevObj !DrvObj !DevExt ObjectName
ffffe000ad961780 \FileSystem\FltMgr ffffe000ad9618d0
> ffffe000adb30030 \FileSystem\Ntfs ffffe000adb30180
Nous savons donc que la LUN a été formatée au format ntfs car il y a un device ntfs sur la stack. Il y a également moyen de récupérer tous les filter drivers file system à partir du device fltmgr (Filter Manager) mais ce sera l’objet d’un autre article. Une stack va se lire de haut en bas. i.e. pour notre stack file system, une IO va passer d’abord par fltmgr.sys (et tous les minifilter drivers file system) puis par ntfs.sys. Nous en sommes ici pour le moment :
![]()
Poursuivons avec la stack volume :
1: kd> !devstack 0xffffe000adb4bac0
!DevObj !DrvObj !DevExt ObjectName
ffffe000adaf7040 \Driver\volsnap ffffe000adaf7190
> ffffe000adb4bac0 \Driver\volmgr ffffe000adb4bc10 HarddiskVolume3
!DevNode ffffe000ad6c7360 :
DeviceInst is "STORAGE\Volume\{82402ec9-6156-11e5-89cc-00fec8f70d10}#0000000000100000"
ServiceName is "volsnap"
Une IO va donc d’abord passer par volsnap.sys. Volsnap gère les snapshots VSS. C’est au niveau de volsnap.sys que va se prendre la décision d’aller chercher des données plutôt dans la diffarea (si tel devait être le cas, en cas de snapshot), ou plutôt sur le volume réel. Je ferai un article sur le sujet plus tard. Si une IO doit continuer son chemin vers notre LUN, alors elle va passer par le driver volmgr.sys.
![]()
Nous allons maintenant traverser la stack disque. Comme le multi-pathing a été activé, notre disque sera un disque MPIO. Pour faire le lien entre le volume et le disque, il va falloir aller chercher l’information dans la structure DEVICE_OBJECT_EXTENSION de notre volume. Chaque DEVICE peut maintenir une structure (DEVICE_OBJECT_EXTENSION) pour stocker des informations qui lui sont propres. Les structures en question étant non-publiques, nous allons devoir retrouver l’information en tâtonnant. Il faut dans un premier temps rechercher les adresses qui correspondent à la structure de type DEVICE_OBJECT_EXTENSION de notre device volume. Pour se faire, il suffit d’utiliser la commande !devobj sur notre device volume puis de dumper les adresses comprises entre le champ DevObjExt (en rose) et DevExt (en bleu). Nous avons donc :
1: kd> !devobj ffffe000adb4bac0
Device object (ffffe000adb4bac0) is for:
HarddiskVolume3 \Driver\volmgr DriverObject ffffe000abe2cc00
Current Irp 00000000 RefCount 22 Type 00000007 Flags 00003050
Vpb ffffe000adb3efa0 Dacl ffffc101230c67f0 DevExt ffffe000adb4bc10 DevObjExt ffffe000adb4bdc0 Dope ffffe000adb39d10 DevNode ffffe000ad6c7360
ExtensionFlags (0x00000800) DOE_DEFAULT_SD_PRESENT
Characteristics (0x00020000) FILE_DEVICE_ALLOW_APPCONTAINER_TRAVERSAL
AttachedDevice (Upper) ffffe000adaf7040 \Driver\volsnap
Device queue is not busy.
1: kd> dq ffffe000adb4bc10 ffffe000adb4bdc0
ffffe000`adb4bc10 ffffe000`adb4bac0 ffffe000`ac3d56d0
ffffe000`adb4bc20 00000000`00000001 00000000`00000000
ffffe000`adb4bc30 ffffe000`ac3d57a0 ffffe000`ac832e00
ffffe000`adb4bc40 00000001`0000264f 00000000`00000000
ffffe000`adb4bc50 ffffe000`ad9759d0 00000001`00000002
ffffe000`adb4bc60 ffffe000`adb4bc60 ffffe000`adb4bc60
ffffe000`adb4bc70 ffffe000`ac3fe348 ffffe000`abfa8d90
ffffe000`adb4bc80 00000000`00000000 00000000`00000100
ffffe000`adb4bc90 00000001`00060000 ffffe000`adb4bc98
ffffe000`adb4bca0 ffffe000`adb4bc98 ffffe000`adb4bca8
ffffe000`adb4bcb0 ffffe000`adb4bca8 00000000`00000000
ffffe000`adb4bcc0 00000100`00010101 00000000`00000003
ffffe000`adb4bcd0 00000000`0070006e ffffc000`23941ef0
ffffe000`adb4bce0 00000000`00e400e2 ffffc000`23d4a010
ffffe000`adb4bcf0 00000000`00e400e2 ffffc000`23d48c00
ffffe000`adb4bd00 00000000`00000000 ffffe000`adafed00
ffffe000`adb4bd10 00000000`00000001 fffff801`5e9b7098
ffffe000`adb4bd20 fffff801`5e9bd5b0 fffff801`5e9bb180
ffffe000`adb4bd30 00000000`00000000 00000000`00000000
ffffe000`adb4bd40 00000000`00000000 fffff801`5e9bd300
ffffe000`adb4bd50 fffff801`31bda0a0 00000000`00000001
ffffe000`adb4bd60 00000000`00000000 00000000`00000000
ffffe000`adb4bd70 ffffe000`adb54b60 ffffe000`adb70060
ffffe000`adb4bd80 ffffe000`adb35b10 00000001`00000001
ffffe000`adb4bd90 00000000`00100000 0000000f`ffd00000
ffffe000`adb4bda0 00000000`715eb38b 00000000`00100000
ffffe000`adb4bdb0 00000000`00000000 00000000`00000000
ffffe000`adb4bdc0 00000000`0000000d
Nous avons à l’offset 0x168 ce que nous recherchons (faites-moi confiance). Si d’aventure dans une version ultérieure de Windows cette structure devait changer, vous pouvez toujours essayer de retrouver notre device en tâtonnant avec une commande !pool pour retrouver un pool tag de type Devi.
Notre stack disque ressemble donc à ça :
1: kd> !devstack ffffe000`adb70060
!DevObj !DrvObj !DevExt ObjectName
ffffe000adb35b10 \Driver\partmgr ffffe000adb35c60
ffffe000adb35060 \Driver\disk ffffe000adb351b0 DR1
> ffffe000adb70060 \Driver\mpio ffffe000adb701b0 MPIODisk0
!DevNode ffffe000adb3c850 :
DeviceInst is "MPIO\Disk&Ven_QNAP&Prod_iSCSI_Storage&Rev_4.0_\1&7f6ac24&0&3630303134303541323742433033304446343633443432304344393737384436"
ServiceName is "disk"
Ce qui nous donne le schéma suivant:
![]()
Pour continuer, il faut maintenant rechercher le nombre de paths, et les découvrir. Comme pour le device volume, la structure n’est pas publique mais peut être retrouvée en tâtonnant selon la même technique s’agissant de MPIO. Nous avons donc :
1: kd> !devobj ffffe000adb70060
Device object (ffffe000adb70060) is for:
MPIODisk0 \Driver\mpio DriverObject ffffe000ac3dbd50
Current Irp 00000000 RefCount 0 Type 00000007 Flags 00001050
Dacl ffffc10123202f40 DevExt ffffe000adb701b0 DevObjExt ffffe000adb709b0 DevNode ffffe000adb3c850
ExtensionFlags (0x00000800) DOE_DEFAULT_SD_PRESENT
Characteristics (0x00000100) FILE_DEVICE_SECURE_OPEN
AttachedDevice (Upper) ffffe000adb35060 \Driver\disk
Device queue is not busy.
1: kd> dq ffffe000adb701b0 ffffe000adb709b0
ffffe000`adb701b0 00000000`00000002 0000264f`00000000
ffffe000`adb701c0 00000000`00060001 ffffe000`adb701c8
ffffe000`adb701d0 ffffe000`adb701c8 ffffe000`adb70060
ffffe000`adb701e0 ffffe000`ac42d050 ffffe000`adb70060
ffffe000`adb701f0 ffffe000`ac3dbd50 ffffe000`ac42d050
ffffe000`adb70200 00000000`00010101 00000100`00000100
ffffe000`adb70210 00000000`00010000 0000264e`00000000
ffffe000`adb70220 00000000`00000000 00000001`00000001
ffffe000`adb70230 00000000`00000001 00000002`00000002
ffffe000`adb70240 00000002`00000003 00000000`77010001
ffffe000`adb70250 00000000`00000000 ffffe000`adb6fd80
ffffe000`adb70260 ffffe000`adb6fee0 00000000`00000000
ffffe000`adb70270 00000000`00000000 ffffe000`adb6fcd0
ffffe000`adb70280 ffffe000`adb42630 00000000`00000000
ffffe000`adb70290 00000000`00000000 00000000`00000000
…
ffffe000`adb70910 ffffffff`ffffffff 00000000`00000000
ffffe000`adb70920 00000000`00000000 00000000`00000000
ffffe000`adb70930 00000000`00000000 00000000`00000000
ffffe000`adb70940 00000000`00000000 00000000`00000001
ffffe000`adb70950 00000000`00000000 00000000`00000000
ffffe000`adb70960 00000000`00060001 ffffe000`adb70968
ffffe000`adb70970 ffffe000`adb70968 00000000`00000000
ffffe000`adb70980 00000000`00000000 00000000`00000000
ffffe000`adb70990 00000000`00000000 00000000`00000000
ffffe000`adb709a0 00000000`00000000 00000000`00000000
A l’offset 0x78, nous avons le nombre de paths (2 pour cette configuration). A l’offset 0xb8 et 0x90 nous avons les pointeurs vers la structure (non publique) qui décrit nos 2 paths :
1: kd> dq ffffe000`adb6fcd0
ffffe000`adb6fcd0 ffffe000`ac927050 00000000`77010000
ffffe000`adb6fce0 00000000`00000000 ffffe000`adb70060
ffffe000`adb6fcf0 ffffe000`adb6fa50 ffffe000`adb22390
ffffe000`adb6fd00 00000000`00000000 00000000`77010000
ffffe000`adb6fd10 ffffe000`adb6f638 ffffe000`ada28060
ffffe000`adb6fd20 ffffe000`adb0b040 fffff801`5f091200
ffffe000`adb6fd30 ffffe000`adb0b750 00040000`00000009
ffffe000`adb6fd40 00000001`ffffffff ffffe000`ad772b20
1: kd> dq ffffe000`adb42630
ffffe000`adb42630 ffffe000`ac927050 00000000`77010001
ffffe000`adb42640 00000000`00000000 ffffe000`adb70060
ffffe000`adb42650 ffffe000`adb22490 ffffe000`adb493a0
ffffe000`adb42660 00000000`00000001 00000000`77010001
ffffe000`adb42670 ffffe000`adb6f638 ffffe000`adb6d060
ffffe000`adb42680 ffffe000`adb409b0 fffff801`5f091200
ffffe000`adb42690 ffffe000`adb6f010 00040000`00000009
ffffe000`adb426a0 00000001`ffffffff ffffe000`ad751070
Nous retrouvons en rouge ci-dessus les 2 paths ID, ainsi que les devices mini-port storport qui correspondent au 2 LUNs qui ont été présentées à cette machine. Ce qui nous donne :
1: kd> !devstack ffffe000`ada28060
!DevObj !DrvObj !DevExt ObjectName
ffffe000adb49b10 \Driver\partmgr ffffe000adb49c60
ffffe000adb0b040 \Driver\disk ffffe000adb0b190
> ffffe000ada28060 \Driver\iScsiPrt ffffe000ada281b0 0000003d
!DevNode ffffe000adb427b0 :
DeviceInst is "SCSI\Disk&Ven_QNAP&Prod_iSCSI_Storage\1&1c121344&0&000000"
ServiceName is "disk"
1: kd> !devstack ffffe000`adb6d060
!DevObj !DrvObj !DevExt ObjectName
ffffe000adb3fb10 \Driver\partmgr ffffe000adb3fc60
ffffe000adb409b0 \Driver\disk ffffe000adb40b00
> ffffe000adb6d060 \Driver\iScsiPrt ffffe000adb6d1b0 0000003e
!DevNode ffffe000adb49010 :
DeviceInst is "SCSI\Disk&Ven_QNAP&Prod_iSCSI_Storage\1&1c121344&0&000100"
ServiceName is "disk"
Il s’agit ici de disques iSCSI vers une baie de disque QNAP. Le driver mini-port storport dans ce cas est celui de Microsoft (msiscsi.sys). S’il avait été question de LUNs fiber channel, nous aurions eu des devices QLogic ou Emulex (i.e. du code non Microsoft).
Ce qui nous donne le schéma suivant:
![]()
Pour résumer : 2 LUNs ont été présentées à Windows pour attaquer le même disque coté baie mais via 2 chemins différents (redondance). Ces 2 LUNs ont été « multipathées » coté Windows ce qui fait que le disque manager ne voit qu’un disque (le disque MPIO). Les IOs (si elles ne peuvent être résolues par le cache), vont traverser l’ensemble des devices\drivers depuis fltmgr.sys jusqu’au miniport storport pour transfert via la carte HBA ou adaptateur réseau (si iSCSI).
Si le multi-pathing est paramétré pour faire du round-robin (d’autres modes existent), alors les IOs seront distribuées successivement vers une LUN puis vers l’autres etc…
Merci de m’avoir lu.
Hervé Chapalain
Senior Escalation Engineer
Microsoft