The way the disk configuration is determined on a Windows system is by a process called “discovery”. If the disk configuration is ISCSI or Fibre the process is very similar. I will describe the process on each and you will see they are very similar. To simplify things this blog will not describe multipath facilities, the availability of multiple paths to a LUN/Target can cause storport to create more device objects than number of LUNs available.
First, let me explain some SCSI terminologies I will be using to describe this process:
Target - A SCSI target is an addressable unit that contains logical units (see LUN). A target can be described as a container of LUNs. The address can be an IP address as in the case of ISCSI or a 16 bit node address if fibre. Commands are sent to a target using its address.
LUN - A LUN is a logical unit that is in a target. A LUN most often describes a disk or tape unit inside a target. The LUN that is the target of a command is specified in the command packet.
Check Condition - A check condition is a status response to a SCSI command. There are a number of status codes a target can return, one of them is a check condition. A check condition requires the host to send a command called “request sense”. The target will respond with data containing sense codes defining the event that caused the check condition to occur.
Now let’s talk about the discovery process on the different mediums and how it is accomplished:
Whenever a scan is requested for a bus controlled by an iSCSI or Fibre Channel adapter, the Microsoft storport driver will attempt to send to all targets a “report LUNS” command. The response to a report LUNS command will contain information regarding all the LUNS on the target. Then storport will send “inquiry” commands to all LUNS reported to get specific LUN information. Even though the number of targets can be as high as 128, the time this takes is usually very short. The adapter keeps a table to translate OS Target ID to the Destination ID which is the actual address on the fabric (or SessionID in case of iscsi). The Adapter miniport driver creates this table from data it receives from the name server. No I/O is done if the command fails due to a nonexistent target.
The device object representing the LUN is built from the inquiry data received from the LUN. One device object will be built in storport for each LUN discovered.
The iSCSI discovery occurs the same way as far as sending the report LUN command to each target as done in Fibre. However, the discovery will happen when a connection is made to a target. When the iSCSI miniport driver successfully logs into a target it will notify the port driver to do a bus scan of the ISCSI adapter. The enumeration is identical as far as the scan itself.
Common question are:
What happens when I add a LUN to an existing target?
When a LUN is added, the target does not immediately notify the host. When an I/O is sent to an existing LUN, the I/O will get a “check condition” status. When the request sense is issued the sense data will indicate that the “report LUNS data has changed”. The port driver (usually storport) picks this up and the port driver will scan the bus and discover the new LUN. This would be the same for iSCSI and fibre. This means that if the target does not issue a check condition to notify the OS of a new LUN the LUN will not appear on the system and the user will need to manually scan for the new disk.
How does the operating system know that a new target was added?
If a new target is added on an iSCSI bus, the scan will be initiated after the miniport driver successfully logs in to the target. So what about fiber? When a new target is added to a fibre channel, the adapter will receive a RSCN (registered state change notification) packet from the fabric. This indication will be passed on to the port driver by the miniport (depending on adapter driver) to scan the bus. Then the new target will be picked up by the scan and the target will be sent a report LUNS to get the LUNS on the target. Again, this means if the miniport or the fibre does not notify the OS of a new target its devices will not appear on the system and the user will need to manually scan for the new disks.
Editor's Note: The external links provided in this article contained accurate information at the time of publication. For official information regarding the SCSI specification, please see the latest revision of the SAM working draft at http://www.t10.org/drafts.htm#sam4. The T10 webpage may ask for personal information and agreements before giving access to the drafts, this is outside of the control of The NtDebugging Blog.
Every Wednesday (usually) I post a debug tip to our twitter page at https://twitter.com/#!/ntdebugging. This blog is an archive of these tips to allow our readers to find this information easily. I will update this blog periodically with the new tips; follow us on twitter if you want to see the tips as I post them.
The goal of these tips is to share debug commands, and forms of commands (parameters, flags, etc) that my colleagues and I find useful. I hope you can add these commands to your toolkit and they will help you debug more efficiently.
!thread/!process [address] e - on x64 will not show you the meaningless Args to Child information.
.frame /c [FrameNumber] - sets context to specified stack frame. Provides more reliable information than .trap on x64.
kn - Dumps call stack with frame numbers, easier than counting stacks for .frame.
.frame /r [FrameNumber] - same as .frame /c, but shows registers without changing context.
Note: With .frame /c or /r you can only trust the nonvolatile registers. See http://msdn.microsoft.com/en-us/library/9z1stfyw(VS.80).aspx for vol/nonvol regs.
k=rbp rip FrameCount - Dumps call stack starting at rbp/rip on x64. Useful when the stack is corrupt. #debug ^DN
.process/.thread /p /r [address] - sets new process context, sets .cache forcedecodeuser, and reloads user symbols. #debug ^DebugNinja
!process [address] 17 - Sets the context for this command, avoids the need for .process to see user stacks. Try !process 0 17 #debug ^DN
~~[ThreadID]s - Changes threads in user mode. Use Thread ID number from output such as !locks. Ex: ~~[1bd4]s #debug ^DN
runas /netonly /u:<account> windbg.exe - Launch windbg with domain account. Use when dbg computer isn't in domain and symbol server is. ^DN
!heap -p -a <address> - Shows information about the heap block containing <address>, even if you aren't using pageheap. #debug ^DN
ub - Unassembles starting at a location prior to your address. Accepts l<number> to specify how many instructions to go back. ub . l20 ^DN
!stacks 2 [FilterString] - Finds kernel mode call stacks that contain the FilterString in a symbol. #debug ^DN
!thread [address] 17 (or 1e on x64) - Sets context for this command, avoids the need for .thread/.process for user stacks. #debug ^DN
.hh [Text] - Opens the debugger help. [Text] is the topic to lookup in the index. Example: .hh !pte #debug ^DN
?? can dump structs using C++ style expressions. Ex: ??((nt!_KTHREAD*)(0xfffffa800ea43bb0))->ApcState #debug ^DN
bp /t EThread - Sets a kernel mode breakpoint that only triggers when hit in the context of this thread. #debug ^DN
bp /p EProcess - Sets a kernel mode breakpoint that only triggers when hit in the context of this process. #debug ^DN
gc - If you run 'p' and hit a breakpoint, gc takes you where p would have gone if you had not hit the bp. #debug ^DN
gu - Go until the current function returns. Effectively this unwinds one stack frame. #debug #windbg ^DN
pc - Steps through until the next 'call' instruction. Combine with other commands to find who returned your error> pc;p;r eax #debug ^DN
pt - Steps through until the next 'ret' instruction. Similar to gu, but pt stops on the ret and gu stops after the ret. #debug ^DN
.ignore_missing_pages 1 - supresses the error: "Page 2a49 not present in the dump file. Type ".hh dbgerr004" for details" #debug ^DN
.exr -1 shows the most recent exception. Useful in user dumps of crashes, especially for no execute crashes (NX/DEP). #debug ^DN
wt - Trace calls until they return to the current address. More useful with -or to get return values. Use -l for depth. ^DN #debug
.thread /w - Changes to the WOW64 32-bit context from 64-bit kernel mode. Wow64exts doesn't work in kernel mode. #debug ^DN
??sizeof(structure) - Gets the size of a structure, it's easier than counting. #debug ^DN
sxe ld:module.dll - Enables an exception which will break into the debugger when module.dll is loaded. #debug ^DN
vertarget - Shows OS version of the debug target. Also shows machine name, uptime, and session time (when the dump was taken). #debug ^DN
!vm 1 - In a kernel debugger, shows basic information about memory usage. Available, committed, pagefile, pool, sysptes, etc. #debug ^DN
.time - Shows session time (when dump was taken) and system uptime. In user mode shows process uptime, kernel/user time. #debug ^DN
ba w size [address] - Break on write access only. Replace size with the num bytes you want to watch. Ex: ba w 4 005d5f10 #debug ^DN
.bugcheck - Displays the bugcheck code of a blue screen crash. The format is more concise than !analyze. #debug ^DN
.process -i <address> - Make the process active and break into the debugger. Use in live kernel debugs to get into process context. ^DN
.reload /f /o - Overwrites cached files in your downstream symbol store. Useful when your store has corrupt pdbs. #debug ^DN
->* - Use with dt to dump pointers. Example: dt _EPROCESS [Address] ObjectTable->*
!for_each_module s -a @#Base @#End "PTag" - Find the drivers using pool tag "PTag". #debug ^DN
.unload [DllName] - Unloads the debug extension you didn't intend to load. Omit DllName to unload the last dll loaded. #debug ^DN
!exqueue dumps the executive worker queues. Use flags 7f to dump the worker threads and the queues. #debug ^DN
lmvm <module> - Dumps information about the module. Remember to use <module> and not <module.dll>. #debug ^DN