Quantcast
Channel: Ntdebugging Blog
Viewing all 98 articles
Browse latest View live

For testing purposes only, and other desktop watermarks

$
0
0

Hi all, Matt here again.  One of our team’s main functions is to work with our development teams to create hotfixes when customers run into issues that can only be resolved through a code change.  The developers will often prepare a private test fix that either tests the proposed change, or adds additional instrumentation to help pinpoint the issue. The private test fix is sent to the customer reporting the problem so they can confirm that it does indeed correct (or identify) the flaw.

 

When testing a private test fix, customers frequently ask, why does my desktop now show a message on the lower right corner of the desktop, and what does it mean?  The message reads “For testing purposes only”, and looks like this:

For testing purposes only

 

Often, users are concerned that his message means that they aren’t allowed to use the server in production, or maybe that it is now “unsupported.”  These aren’t the case!  Since this message appears as a result of installing a fix during the course of a Microsoft Support case, the servers are, by definition, being supported. 

 

The purpose of this message is simply to remind users that code that Microsoft Support has asked them to test has been installed on the system, and this code may not have yet undergone the full suite of quality assurance testing that fixes that are made public usually do.   

 

For comparison, let’s look at some of the other watermarks you may find in the lower corner of the desktop – as these can often be confused for the above message, and may explain some of the customer concerns around these messages.

 

First up is the old trusty text you see when a box is booted into ‘Safe Mode’. I’m sure every IT Pro has seen this at one time or another, so I won’t go into detail, but rest assured, the testing purposes text is completely unrelated to booting in safe mode or having a subset of services running.

Safe Mode

 

Next up is our ‘Evaluation copy’ watermark.  This message is shown on the desktops of copies of Windows that have a “time bomb” (ones that will cease to function after a certain date.)  This message is typically seen on beta versions of Windows which are designed to stop functioning sometime after the desired beta testing period ends. 

Evaluation copy

 

Third, we have our Windows is not genuine message.  This is shown if, for example, a copy of Windows is not activated during the grace period after the installation process, or if a number of hardware changes have been made and Windows needs to be re-activated.  This has nothing to do with the ‘testing purposes’ message.  See http://technet.microsoft.com/en-us/library/dd979803.aspx for more information about this message.

This copy of Windows is not genuine

 

Fourth, we have the general Windows build stamp.  This is enabled via the registry using the PaintDesktopVersion DWORD (http://technet.microsoft.com/en-us/library/cc782257(WS.10).aspx).  Some administrators like to enable this option so they always know what version of Windows they are using, sort of like a mini-bginfo.  Unlike the others, this message does not indicate anything else about a server’s state.

Windows 7

 

Finally, we have ‘Test Mode’.  This is actually somewhat related to the testing purposes message.  This ‘Test Mode’ text is shown when test signing is enabled on a PC.  This is done by running “bcdedit /set testsigning on” from an UAC-elevated command prompt.  Test signing is used to allow developers to load drivers they are still working on that have not yet been code signed with an official certificate.  This is actually one of the steps we need to do when loading our test fixes.  For more information on Test Signing, see http://msdn.microsoft.com/en-us/library/ff553484%28v=VS.85%29.aspx.

Test Mode

 

So now that you know what causes these various watermarks to appear, perhaps you’re wondering how to make the “For testing purposes only” message disappear.  This is a question we are frequently asked.  While you are running a private test fix, there is no way to disable this message.  Your only option is to remove the private test fix from your system.  This is something your engineer will ask you to do before you install the final, public version of a hotfix.  You can easily identify and uninstall private test fixes by going into Control Panel, Programs and Features, View Installed Updates, then look for hotfixes with the words FOR TESTING PURPOSES ONLY in their name, like the one shown in the image below.  You may also notice that the KB number listed for these fixes is often a place holder, and not a real KB article ID.

Installed Updates

 

If the ‘For testing purposes only’ message is still displayed even after uninstalling the private test fix, there is one more place to check.  If a system has the Microsoft Test Root Authority certificate installed into its Trusted Root Certification Authorities store, the text will be displayed.  We use this certificate to allow a PC to run test code that has been signed by our development team, but not yet fully tested and signed off with the official Microsoft certificate.  To remove this certificate from your system, go to Start -> Run, and enter certmgr.msc and hit enter.  In the Certificate Manager MMC, browse to Trusted Root Certification Authorities, then into Certificates.  You should see one labeled Microsoft Test Root Authority, as shown below.  This will need to be deleted and the system rebooted to remove the testing purposes message.  Do not do this if you still have a private test fix installed though, as it would prevent that binary from continuing to function and may mean you can no longer boot in to Normal or Safe mode.

certmgr

 

If you reboot and find that ‘Test Mode’ has replaced the ‘For testing purposes only’ text, you’ll need to launch a command prompt with administrative privileges, then run “bcdedit /set testsigning off” and reboot.  You can always check if test signing is enabled by running “bcdedit /enum” and looking for this line:

bcdedit /enum

 

That’s all for today.  Hopefully this post helped clear up any confusion about our different desktop watermarks.


Determining The Interrupt Line For A Particular PCI-E Slot

$
0
0

Hi debuggers, this is Graham McIntyre again. These days I’m working more closely with hardware so I thought I’d share some hardware related debugging tips.  I recently debugged an issue where a PCI-E storage device failed to work after hot swapping it from one slot to another slot on the system without rebooting.  We determined the issue was due to the device not receiving interrupts once it was moved.   So in the process I learned how line based interrupts are routed to a particular PCI slot.    Interrupt routing is quite a hefty subject, but here’s one example of how to determine what the expected interrupt line is for a particular PCI-E slot using a live kernel debug.

 

There are two ways the routing can be defined in the ACPI tables:

  1. Static routing (most common for APIC systems)
  2. Link Node routing (most common for PIC systems)

 

Since APIC is much more common, I am focusing on method 1 for static routing. Though, it is legal to use Link Node routing with IOAPICs, it’s not common, so I am omitting how to parse that.  This is also specifically for devices that use physical line based interrupts (LBI), not Message Signaled Interrupts (MSI).

 

Here is the general method for determining the static routing IRQ for a particular device:

  1. Locate the devstack for the device, and determine its parent devices in the PCI hierarchy. (!pcitree)
  2. Determine the interrupt pin which the device uses
  3. Walk the parent devices to find the closest PCI Routing Table (_PRT) which will describe the mapping of interrupt pin to IRQ.
    1. If the parent device does not have a _PRT, then swizzle the pin, since the pin number can change when moving to the upstream side of the PCI bridge (you may end up swizzling the pin several times).  We will discuss how to swizzle the pin number later in this article.
    2. If the parent device has a _PRT, then move to the next step
  4. Convert the IntPin number from PCI to ACPI numbering
  5. Parse the _PRT method to find the static routing table
  6. Find the routing entry which represents our IntPin

 

Here’s the in-depth steps, along with an example:

 

Step 1:  Locate the devstack for the device, and determine its parent devices in the PCI hierarchy.

 

To determine this, use !pcitree to dump the PCI hierarchy. Then locate your device by ven/dev ID.  You could also use !devnode to dump the hierarchy.

 

The way !pcitree shows the hierarchy may be a little confusing.  When it encounters a PCI bridge, it dumps the child buses under the bridge. The indenting tells you what bus a device is on. A device is always indented one level from the entry of the parent bus.  In my case, I know the device I'm interested in is VEN FEFE DEV 1550.

kd> !pcitree

Bus 0x0 (FDO Ext fffffa80053efe00)

  (d=0,  f=0) 80863406 devext 0xfffffa80054d51b0 devstack 0xfffffa80054d5060 0600 Bridge/HOST to PCI

  (d=1,  f=0) 80863408 devext 0xfffffa80054d9b70 devstack 0xfffffa80054d9a20 0604 Bridge/PCI to PCI

  Bus 0x1 (FDO Ext fffffa80054e8680)

    (d=0,  f=0) 14e41639 devext 0xfffffa80051b91b0 devstack 0xfffffa80051b9060 0200 Network Controller/Ethernet

    (d=0,  f=1) 14e41639 devext 0xfffffa80051ba1b0 devstack 0xfffffa80051ba060 0200 Network Controller/Ethernet

  (d=3,  f=0) 8086340a devext 0xfffffa80054dab70 devstack 0xfffffa80054daa20 0604 Bridge/PCI to PCI

  Bus 0x2 (FDO Ext fffffa80054e9460)

    (d=0,  f=0) 14e41639 devext 0xfffffa80051bcb70 devstack 0xfffffa80051bca20 0200 Network Controller/Ethernet

    (d=0,  f=1) 14e41639 devext 0xfffffa80051cab70 devstack 0xfffffa80051caa20 0200 Network Controller/Ethernet

  (d=4,  f=0) 8086340b devext 0xfffffa80054dbb70 devstack 0xfffffa80054dba20 0604 Bridge/PCI to PCI

  Bus 0x3 (FDO Ext fffffa80054ec190)

    (d=0,  f=0) 10000079 devext 0xfffffa80051cd1b0 devstack 0xfffffa80051cd060 0104 Mass Storage Controller/RAID

  (d=5,  f=0) 8086340c devext 0xfffffa80054dcb70 devstack 0xfffffa80054dca20 0604 Bridge/PCI to PCI

  Bus 0x4 (FDO Ext fffffa80054ede00)

    No devices have been enumerated on this bus.

  (d=6,  f=0) 8086340d devext 0xfffffa80054ddb70 devstack 0xfffffa80054dda20 0604 Bridge/PCI to PCI

  Bus 0x5 (FDO Ext fffffa80054ee9c0)

    No devices have been enumerated on this bus.

  (d=7,  f=0) 8086340e devext 0xfffffa80054deb70 devstack 0xfffffa80054dea20 0604 Bridge/PCI to PCI << Root Port

  Bus 0x6 (FDO Ext fffffa80054f1190)

    (d=0,  f=0) abcd8632 devext 0xfffffa80051d91b0 devstack 0xfffffa80051d9060 0604 Bridge/PCI to PCI << Upstream switch port

    Bus 0x7 (FDO Ext fffffa80051cd850)

      (d=4,  f=0) abcd8632 devext 0xfffffa80051d71b0 devstack 0xfffffa80051d7060 0604 Bridge/PCI to PCI

      Bus 0x8 (FDO Ext fffffa8006f44ac0)

        No devices have been enumerated on this bus.

      (d=5,  f=0) abcd8632 devext 0xfffffa80058d6a10 devstack 0xfffffa80058d68c0 0604 Bridge/PCI to PCI

      Bus 0x9 (FDO Ext fffffa80051ba850)

        No devices have been enumerated on this bus.

      (d=6,  f=0) abcd8632 devext 0xfffffa8007075b70 devstack 0xfffffa8007075a20 0604 Bridge/PCI to PCI << Parent PDO (Downstream Switch Port)

      Bus 0xa (FDO Ext fffffa8007312b60)

        (d=0,  f=0) fefe1550 devext 0xfffffa8006f67b70 devstack 0xfffffa8006f67a20 0180 Mass Storage Controller/'Other' << Device

      (d=7,  f=0) abcd8632 devext 0xfffffa80051e5b70 devstack 0xfffffa80051e5a20 0604 Bridge/PCI to PCI

      Bus 0xb (FDO Ext fffffa80052d2e00)

        No devices have been enumerated on this bus.

  (d=14, f=0) 8086342e devext 0xfffffa80054dfb70 devstack 0xfffffa80054dfa20 0800 Base System Device/Interrupt Controller

  (d=14, f=1) 80863422 devext 0xfffffa80054e0b70 devstack 0xfffffa80054e0a20 0800 Base System Device/Interrupt Controller

  (d=14, f=2) 80863423 devext 0xfffffa80054e1b70 devstack 0xfffffa80054e1a20 0800 Base System Device/Interrupt Controller

  (d=1a, f=0) 80862937 devext 0xfffffa80054e2b70 devstack 0xfffffa80054e2a20 0c03 Serial Bus Controller/USB

  (d=1a, f=1) 80862938 devext 0xfffffa80054e31b0 devstack 0xfffffa80054e3060 0c03 Serial Bus Controller/USB

  (d=1a, f=7) 8086293c devext 0xfffffa80054e3b70 devstack 0xfffffa80054e3a20 0c03 Serial Bus Controller/USB

  (d=1d, f=0) 80862934 devext 0xfffffa80054e41b0 devstack 0xfffffa80054e4060 0c03 Serial Bus Controller/USB

  (d=1d, f=1) 80862935 devext 0xfffffa80054e4b70 devstack 0xfffffa80054e4a20 0c03 Serial Bus Controller/USB

  (d=1d, f=7) 8086293a devext 0xfffffa80054e51b0 devstack 0xfffffa80054e5060 0c03 Serial Bus Controller/USB

  (d=1e, f=0) 8086244e devext 0xfffffa80054e5b70 devstack 0xfffffa80054e5a20 0604 Bridge/PCI to PCI

  Bus 0xc (FDO Ext fffffa80054f2e00)

    (d=3,  f=0) 102b0532 devext 0xfffffa80051d51b0 devstack 0xfffffa80051d5060 0300 Display Controller/VGA

  (d=1f, f=0) 80862918 devext 0xfffffa80054e61b0 devstack 0xfffffa80054e6060 0601 Bridge/PCI to ISA

  (d=1f, f=2) 80862921 devext 0xfffffa80054e6b70 devstack 0xfffffa80054e6a20 0101 Mass Storage Controller/IDE

Total PCI Root busses processed = 1

Total PCI Segments processed = 1

 

To recap the devices in the tree (Bus,Device,Function):

(0,7,0) : Root Port, PCI-PCI Bridge (devstack 0xfffffa80054dea20)

    (6,0,0) : Upstream Switch Port (devstack 0xfffffa80051d9060)

        (7,6,0) : Downstream Switch Port (the PDO for the slot) (devstack 0xfffffa8007075a20)

            (a,0,0) : Device  (devstack 0xfffffa8006f67a20)

 

I scanned the output looking for my ven/dev ID, and found it at Bus A, Device 0, Function 0.

 

Step 2:  Determine which interrupt pin the device uses.

 

For this step, you can use !pci to dump the PCI config space for the device. The output will show you the interrupt pin the device uses, labeled as IntPin.

!pci 1 a 0 0

PCI Bus 10

00:0  FEFE:1550.01  Cmd[0007:imb...]  Sts[0018:c....]  Device  SubID:1344:1008  Other mass storage controller

      cf8:800a0000  IntPin:1  IntLine:2e  Rom:0  cis:0  cap:40

      MEM[2]:df5fd000  MEM[3]:df5fc000  IO[4]:cff1       MEM[5]:df5fe000 

 

So our IntPin is 1.

 

Step 3: Walk the parent devices to find the closest PCI Routing Table (_PRT) which will describe the mapping of interrupt pin to IRQ.

 

Now, we will traverse the parent PCI devnodes until we find a PCI bridge which has an associated ACPI object with a _PRT method. This may be the root port, or an integrated bridge.

  1. Start by running !devstack on the parent.  We can determine the parent device using the indentations of the !pcitree output.
  2. If the devstack shows an ACPI filter driver, then dump the filter using !acpikd.acpiext to find the associated AcpiObject
  3. Dump the ACPI object and its children to see if it has a _PRT method defined
    1. If it does not have a _PRT, then you need to swizzle the Interrupt Pin to find what the pin number will be on the upstream side of the bridge
      1. We have to use a method called “swizzling” because the pin may become a different pin on the upstream side of the bridge. The way to calculate the pin is:
        1. IntPin = ((((IntPin -1) + DeviceNumber) % 4) +1)
      2. Where IntPin is the current IntPin value, and DeviceNumber the device number of the device you’re swizzling.
      3. You will start with the IntPin value from !pci output of the device itself. If you need to swizzle multiple times, you take the result of the previous swizzle as the input to the next swizzle
      4. The device number for the first time will be the device number of the target device, and subsequent times will be the device number of the parent device you’re swizzling.
    2. If it does have a _PRT, then move onto Step 4.

 

Example:

First, we’ll swizzle the pin of the device itself (a,0,0).  The IntPin is 1 so:

                IntPin = ((((1-1)+0) % 4) +1)    << The Swizzled Pin is still IntPin 1

 

Next, I dumped the parent device (7,6,0), !devstack  0xfffffa8007075a20. It didn’t have an ACPI filter driver on the stack. So I need to swizzle the pin.

      IntPin = ((((1-1)+6) % 4) +1)    << The Swizzled Pin is now 3

 

I now dump the next parent up, (6,0,0), !devstack 0xfffffa80051d9060. It also didn’t have an ACPI filter driver on the stack so I need to swizzle the pin again.

      IntPin = ((((3-1)+0) % 4) +1)    << The Swizzled Pin is still 3

 

I am now at the root port. The first devstack which has a _PRT method in my case is the root port.

kd> !devstack 0xfffffa80054dea20

  !DevObj   !DrvObj            !DevExt   ObjectName

  fffffa80054f1040  \Driver\pci        fffffa80054f1190 

  fffffa80054e5800  \Driver\ACPI       fffffa80051c1510  << Has an ACPI filter driver in the devstack

> fffffa80054dea20  \Driver\pci        fffffa80054deb70  NTPNP_PCI0006

!DevNode fffffa80054e1750 :

  DeviceInst is "PCI\VEN_8086&DEV_340E&SUBSYS_02351028&REV_13\3&33fd14ca&0&38"

  ServiceName is "pci"

kd> !acpikd.acpiext fffffa80051c1510

ACPI!DEVICE_EXTENSION fffffa80051c1510 - 70000

  DevObject  fffffa80054e5800  PhysObject  fffffa80054dea20  NextObject   fffffa80054dea20

  AcpiObject fffffa80052e3890  ParentExt   fffffa80051c07d0

  PnpState   Started   OldPnpState Stopped

  Dispatch   fffff880011cbb50

  RefCounts  4-Device 1-Irp 0-Hiber 0-Wake

  State      D0       

  SxD Table  S0->D0 S4->D3 S5->D3

  Flags      0540100002000240

    Types    Filter Enumerated ValidPnP

    Caps     PCIBus

    Props    HasAddress Enabled AcpiPower


Dump the namespace object. Use /s to display the subtree under this object and look for a _PRT method.

kd> !amli dns /s fffffa80052e3890

 

ACPI Name Space: \_SB.PCI0.PEX7 (fffffa80052e3890)

Device(PEX7)

| Integer(_ADR:Value=0x0000000000070000[458752])

| Integer(_STA:Value=0x000000000000000f[15])

| Method(_PRT:Flags=0x0,CodeBuff=fffffa80052e3aa9,Len=144) << A _PRT method exists for this object

 

Now, we have a swizzled IntPin value of 3, and a pointer to the _PRT method.  We can move on to the next step.

 

Step 4: Convert the pin number from PCI to ACPI numbering

 

The !pci or !devext output, and subsequent swizzling will show pin numbering in PCI format where 1 = INTA. But the ACPI table uses 0 for INTA. So you need to subtract one from the PCI pin number to get the ACPI pin number.

 

PCI pin number

ACPI pin number

INTA

1

0

INTB

2

1

INTC

3

2

INTD

4

3

 

Once you’ve converted to ACPI pin numbering, you have to dump the _PRT method to find the package which maps to that pin number.

 

For my example since the PCI IntPin value is 3, which corresponds to INTC, the ACPI pin number is 2

 

Step 5: Parse the _PRT method to find the static routing table

 

Now that we located the correct _PRT entry, we need to use the AMLI debugger extension to parse the method and find the static routing table.  The command !amli u will unassemble an ACPI method

kd> !amli u \_SB.PCI0.PEX7._PRT

AMLI_DBGERR: Failed to get address of ACPI!gDebugger

 

fffffa80052e3aa9 : If(LNot(PICF))

fffffa80052e3ab1 : {

fffffa80052e3ab1 : | Name(P10B, Package(0x4)

fffffa80052e3ab9 : | {

fffffa80052e3ab9 : | | Package(0x4)

fffffa80052e3abc : | | {

fffffa80052e3abc : | | | 0xffff,

fffffa80052e3abf : | | | 0x0,

fffffa80052e3ac1 : | | | LK00,

fffffa80052e3ac5 : | | | 0x0

fffffa80052e3ac7 : | | },

fffffa80052e3ac7 : | | Package(0x4)

fffffa80052e3aca : | | {

fffffa80052e3aca : | | | 0xffff,

fffffa80052e3acd : | | | 0x1,

fffffa80052e3acf : | | | LK01,

fffffa80052e3ad3 : | | | 0x0

fffffa80052e3ad5 : | | },

fffffa80052e3ad5 : | | Package(0x4)

fffffa80052e3ad8 : | | {

fffffa80052e3ad8 : | | | 0xffff,

fffffa80052e3adb : | | | 0x2,

fffffa80052e3add : | | | LK02,

fffffa80052e3ae1 : | | | 0x0

fffffa80052e3ae3 : | | },

fffffa80052e3ae3 : | | Package(0x4)

fffffa80052e3ae6 : | | {

fffffa80052e3ae6 : | | | 0xffff,

fffffa80052e3ae9 : | | | 0x3,

fffffa80052e3aeb : | | | LK03,

fffffa80052e3aef : | | | 0x0

fffffa80052e3af1 : | | }

fffffa80052e3af1 : | })

fffffa80052e3af1 : | Store(P10B, Local0)

fffffa80052e3af7 : }

fffffa80052e3af7 : Else

fffffa80052e3af9 : {

fffffa80052e3af9 : | Name(A10B, Package(0x4)

fffffa80052e3b01 : | {

fffffa80052e3b01 : | | Package(0x4)

fffffa80052e3b04 : | | {

fffffa80052e3b04 : | | | 0xffff,

fffffa80052e3b07 : | | | 0x0,

fffffa80052e3b09 : | | | 0x0,

fffffa80052e3b0b : | | | 0x26

fffffa80052e3b0d : | | },

fffffa80052e3b0d : | | Package(0x4)

fffffa80052e3b10 : | | {

fffffa80052e3b10 : | | | 0xffff,

fffffa80052e3b13 : | | | 0x1,

fffffa80052e3b15 : | | | 0x0,

fffffa80052e3b17 : | | | 0x2d

fffffa80052e3b19 : | | },

fffffa80052e3b19 : | | Package(0x4)

fffffa80052e3b1c : | | {

fffffa80052e3b1c : | | | 0xffff,

fffffa80052e3b1f : | | | 0x2,

fffffa80052e3b21 : | | | 0x0,

fffffa80052e3b23 : | | | 0x2f

fffffa80052e3b25 : | | },

fffffa80052e3b25 : | | Package(0x4)

fffffa80052e3b28 : | | {

fffffa80052e3b28 : | | | 0xffff,

fffffa80052e3b2b : | | | 0x3,

fffffa80052e3b2d : | | | 0x0,

fffffa80052e3b2f : | | | 0x2e

fffffa80052e3b31 : | | }

fffffa80052e3b31 : | })

fffffa80052e3b31 : | Store(A10B, Local0)

fffffa80052e3b37 : }

fffffa80052e3b37 : Return(Local0)

fffffa80052e3b39 : Zero

fffffa80052e3b3a : Zero

fffffa80052e3b3b : Zero

fffffa80052e3b3c : Zero

fffffa80052e3b3d : Zero

fffffa80052e3b3e : Zero

fffffa80052e3b3f : Zero

fffffa80052e3b40 : HNSO

fffffa80052e3b44 : Not(Zero, )

fffffa80052e3b47 : Zero

fffffa80052e3b48 : Zero

fffffa80052e3b49 : AMLI_DBGERR: UnAsmOpcode: invalid opcode class 0

AMLI_DBGERR: Failed to unassemble scope at 382d4a0 (size=4096)

 

There are 2 different _PRT tables here, each with 4 packages (think of it as 2 arrays, each containing 4 structures). The first is using link nodes, the second is using static interrupts.  The first list is used if we are in PIC mode, the second if we are in APIC mode.

 

We can check the value of PICF to determine the mode. (I expect it to be APIC but let’s check)

kd> !amli dns \PICF

 

ACPI Name Space: \PICF (fffffa80052ded18)

Integer(PICF:Value=0x0000000000000001[1])

 

So we’re in APIC mode (PICF != 0), we use the static routing mode. So we will use the 2nd table.  What does each package represent?  Each is a PCI Routing Table. From ACPI spec section 6.2.12, which describes the _PRT:

Table 6-14   Mapping Fields

Field

Type

Description

Address

DWORD

The address of the device (uses the same format as _ADR).

Pin

BYTE

The PCI pin number of the device (0–INTA, 1–INTB, 2–INTC, 3–INTD).

Source

NamePath

Or

BYTE

Name of the device that allocates the interrupt to which the above pin is connected. The name can be a fully qualified path, a relative path, or a simple name segment that utilizes the namespace search rules. Note: This field is a NamePath and not a String literal, meaning that it should not be surrounded by quotes. If this field is the integer constant Zero (or a BYTE value of 0), then the interrupt is allocated from the global interrupt pool.

Source Index

DWORD

Index that indicates which resource descriptor in the resource template of the device pointed to in the Source field this interrupt is allocated from. If the Source field is the BYTE value zero, then this field is the global system interrupt number to which the pin is connected.

 

fffffa80052e3af9 : | Name(A10B, Package(0x4)

fffffa80052e3b01 : | {

fffffa80052e3b01 : | | Package(0x4)

fffffa80052e3b04 : | | {

fffffa80052e3b04 : | | | 0xffff,

fffffa80052e3b07 : | | | 0x0,          << INTA

fffffa80052e3b09 : | | | 0x0,

fffffa80052e3b0b : | | | 0x26  << Interrupt line

fffffa80052e3b0d : | | },

fffffa80052e3b0d : | | Package(0x4)

fffffa80052e3b10 : | | {

fffffa80052e3b10 : | | | 0xffff,

fffffa80052e3b13 : | | | 0x1,          << INTB

fffffa80052e3b15 : | | | 0x0,

fffffa80052e3b17 : | | | 0x2d

fffffa80052e3b19 : | | },

fffffa80052e3b19 : | | Package(0x4)

fffffa80052e3b1c : | | {

fffffa80052e3b1c : | | | 0xffff,

fffffa80052e3b1f : | | | 0x2,          << INTC

fffffa80052e3b21 : | | | 0x0,

fffffa80052e3b23 : | | | 0x2f

fffffa80052e3b25 : | | },

fffffa80052e3b25 : | | Package(0x4)

fffffa80052e3b28 : | | {

fffffa80052e3b28 : | | | 0xffff,

fffffa80052e3b2b : | | | 0x3,          << INTD

fffffa80052e3b2d : | | | 0x0,

fffffa80052e3b2f : | | | 0x2e

fffffa80052e3b31 : | | }

fffffa80052e3b31 : | })

 

Step 6 - Find the routing entry which represents our IntPin

 

Now, we just have to locate the entry in the routing table with a IntPin value of 2.

 

fffffa80052e3b19 : | | Package(0x4)

fffffa80052e3b1c : | | {

fffffa80052e3b1c : | | | 0xffff,

fffffa80052e3b1f : | | | 0x2, <<< IntPin 2 (INTC)

fffffa80052e3b21 : | | | 0x0,

fffffa80052e3b23 : | | | 0x2f << IRQ is 0x2f

 

So the device should be assigned IRQ 0x2F.   However, you may have noticed from the !pci output above that in this case the device was actually assigned IntLine (IRQ) 0x2e!  Since the wrong interrupt line was assigned after the device changed slots in the system, the device did not receive interrupts and hence was not functional.

 

I hope this was useful to help understand how interrupts are assigned to LBI devices.

 

More reading / references:

 

PCI IRQ Routing on a Multiprocessor ACPI System:

http://msdn.microsoft.com/en-us/windows/hardware/gg454523.aspx#EDD

 

ACPI 4.0 spec

http://www.acpi.info/DOWNLOADS/ACPIspec40a.doc

Bcdedit Tips and Tricks For Debugging Part 1

$
0
0

Hello everyone, my name is Sean Walker, and I am on the Platforms OEM team in Washington.  This article is for those people who have had a hard time switching from the old boot.ini configuration to the new BCD store (myself included). Doing the simple tasks such as enabling kernel debugging over com1 are easy to do with bcdedit.exe or the msconfig GUI, you just enable them and reboot the computer. However, if you need to do something more advanced such as break into the early boot process during resume from hibernation, things get a lot more complicated.

 

This article has some samples for enabling and disabling debug settings that you may not be familiar with, and a list of bcdedit debug settings for Windows Vista/Server 2008 and Windows 7/Server 2008 R2.  This information has been helpful to me for quickly and accurately getting to the debug at hand rather than fumbling around with bcdedit.  Much of the following information has been taken from various sources, including the windbg help files, the OEM team blog, the MSDN bcdedit reference, and the WHDC debugger site.

 

NOTE: For the examples below, you will need to run bcdedit.exe from an administrator (UAC-elevated) command prompt.  To output a summary view of the current state of the BCD store, just run "bcdedit.exe" from the command prompt.  To get detailed information about all of the store(s) that Windows knows about, use the following command:

bcdedit /enum all

 

What is a BCD store?

A BCD store is a binary file that contains boot configuration data for Windows, basically it is a small registry file.  Boot applications use the system BCD store, located on the system partition, during the boot process.  You can also create additional BCD stores in separate files but only one store at a time can be designated as the system store.

 

NOTE: The "/store" switch can be used to specify a particular BCD store for bcdedit commands (instead of the default store).  To enumerate all the settings in another BCD store, in this case e:\bcd_store\BCD, use the following command:

bcdedit /store e:\bcd_store\BCD /enum all

 

This will show you which options are currently set, and what their values are.  When /store switch is omitted, the system store is used.

 

Using bootdebug

To enable debugging for early boot problems, you may need to enable the bootdebug switch.  This is easy to do with bcdedit:

bcdedit /set bootdebug on

 

However, this only sets bootdebug for the current "boot application", which is generally winload.exe, so it does not break into the very early boot process.  There are multiple applications used for booting, hibernating, and resuming (bootmgr.exe, winload.exe and winresume.exe are examples of these).  Each application (called BCD Objects) has its own settings (called BCD Elements) in the BCD store and each can be modified globally and/or individually.

 

So, to deal with different (or multiple) debug scenarios, you just enable boot debugging based on the boot application you are concerned with.  For early debugging, you can enable bootdebug for bootmgr:

bcdedit /set {bootmgr} bootdebug on

 

To set bootdebug for winload.exe (which will most often be your current, and default, boot object) all three of the following will give you the same result:

bcdedit /set bootdebug on

bcdedit /set {current} bootdebug on

bcdedit /set {default} bootdebug on

 

If you are modifying the settings in another store, or are booted into another OS on the same computer (such as WinPE), you need to specify the location of the BCD store:

bcdedit /store d:\Boot\BCD /set {default} bootdebug on

 

Not all of the boot objects have "friendly" names, so you may need to specify the full GUID (Globally Unique ID) to modify it.  As an example, if you wanted to enable bootdebug on resume from hibernation, you would include the identifier (see figure 1) for the "Resume from Hibernate" object:

bcdedit /set {89a932d0-d5bc-11e0-a0af-00215add5ebc} bootdebug on

 

image001

Figure 1: Color coded bcdedit output

 

Why won't my USB or 1394 debug work?

When there are multiple debug ports of a certain type in a computer Windows may not default to the correct one for your situation.  This happens most commonly when there are either multiple 1394 host controllers or USB EHCI controllers.  When this occurs it can range from a slight inconvenience (different port is used so the cable needs to be plugged into another port), to complete failure (internal port is used, which is not accessible).  In the case of USB debugging the Intel USB 2.0 specification only provides one debug port, so debugging is not possible if the wrong host controller is used.

 

There are several caveats with USB debugging, not the least of which is that you need to buy a separate, expensive, debug cable.  Some of the difficulties and implementation details necessary to get USB debugging to work are encompassed in the WHDC USB FAQ and in Setting Up Kernel Debugging with USB 2.0.

 

NOTE: A correction to the WHDC USB documentation for Windows 7/Windows 2008 R2 is that the busparams switch now takes decimal rather than hexadecimal values, and the "loadoptions" parameter is no longer required.  So, to enable the busparams element (for USB or 1394 debugging) in Vista/2008, you would use something like this:

bcdedit /set {current} loadoptions busparams=0.1D.7

 

And the Win7/2008 R2 example would be:

bcdedit /set {current} busparams 0.29.7

 

In the case of loadoptions or busparams, deleting the setting is not as easy as changing a flag from yes to no. You must specifically delete the value to get rid of it, and one of the examples below can be used:

 

For Vista/2008:

bcdedit /deletevalue {current} loadoptions

 

And Windows 7/2008 R2:

bcdedit /deletevalue {current} busparams

 

Bcdedit settings and examples

This is just scratching the surface of using bcdedit for your troubleshooting and/or debugging needs, so there are more articles to follow. Part 2 will include some more detailed debugging scenarios, such as Hyper-V guest and host debugging.  Below is a consolidated table with many of the debugging switches/settings as well as a number of different usage examples.

 

Table of debug-related bcdedit settings

Option

Description

bootdebug

Enables or disables the boot debugger for a specified boot entry. Although this command works for any boot entry, it is effective only for boot applications.

Enable value(s): on, 1

Disable value(s): off, 0

Bcdedit /set bootdebug on

debug

Enables or disables the kernel debugger for a specified boot entry.

Enable value(s): on, 1

Disable value(s): off, 0

/dbgsettings

Used to modify the global settings for the debug connection (does not include hypervisor).  Values:

Can change all settings at once instead of using the /set command to change them individually. Usage example:

bcdedit /dbgsettings 1394 channel:30

debugport

Used to specify the debugger type.

Values:

Serial port – com1, com2, comx

1394 port – 1394

USB port - USB

channel

Specifies 1394 channel used.

Values:

Decimal integer between 0 and 62, inclusive.

baudrate

Used to specify the baud rate of a serial debug port.

Values: 9600, 19200, 38400, 57600, 115200

targetname

Specifies a string to use as the identification for the USB 2.0 connection. This string can be any value.

Usage example:

bcdedit /dbgsettings usb targetname:usbdebug

/hypervisorsettings

Used the same way as /dbgsettings to configure all settings at once.

Usage example:

bcdedit /hypervisorsettings 1394 channel:10

hypervisordebug

Enables or disables hypervisor debug mode. This is for debugging a Hyper-V host system.

Enable value(s): on, 1

Disable value(s): off, 0

Usage example:

bcdedit /set {current} hypervisordebug on

/noumex

Specifies that the kernel debugger ignores user-mode exceptions. By default, the kernel debugger breaks for certain user-mode exceptions, such as STATUS_BREAKPOINT and STATUS_SINGLE_STEP. The /noumex parameter is effective only when there is no user-mode debugger attached to the process.

/start

This option specifies the debugger start policy. If a start policy is not specified, ACTIVE is the default.

Values: active, disable, autoenable

loadoptions

Used to describe settings that are not covered by other types. One setting that is relevant here is busparams.

Values: Any value followed by the setting.

Usage example (Vista/2008):

bcdedit /set {current} loadoptions busparams=0.1d.0

busparams

A boot setting (specified with loadoptions key word) used to point to the PCI address of the debugger in use. The PCI bus, device, and function are used, in the format bb.dd.ff. This is generally used to identify the location of a 1394 or USB debug port. In Vista/2008, hexadecimal values are used, whereas decimal values are used for Win7.

Values: Decimal values between 0 and 255.

Usage example:

In Win7 - bcdedit /set busparams 0.29.0

In Vista - bcdedit /set loadoptions busparams=0.1d.0

kernel

The loadoptions parameter used to point to a different kernel binary. This can be used to test with a checked or instrumented version of the kernel without replacing the existing one. The updated binary MUST be placed in the %windir%\system32 folder to be used

Values: The 8.3 filename of the replacement kernel include the exe extension.

Usage examples:

In Win7 – bcdedit /set kernel kernchk.exe

In Vista - bcdedit /set loadoptions kernel=kernchk.exe

hal

The loadoptions parameter used to point to a different hal binary. This can be used to test with a checked or instrumented version of the kernel without replacing the existing one. The updated binary MUST be placed in the %windir%\system32 folder to be used

Values: the 8.3 filename of the replacement kernel include the .dll extension.

Usage examples:

In Win7 – bcdedit /set hal halchk.dll

In Vista - bcdedit /set loadoptions hal=halchk.dll

testsigning

Controls whether Windows 7, Windows Server 2008, or Windows Vista will load any type of test-signed kernel-mode code. This option is not set by default, which means test-signed kernel-mode drivers on 64-bit versions of Windows 7, Windows Server 2008, and Windows Vista will not load without setting the testsigning switch

Enable value(s): on, 1

Disable value(s): off, 0

Usage example:

Bcdedit /set testsigning on

 

Debugging a CLOCK_WATCHDOG_TIMEOUT Bugcheck

$
0
0

Hi debuggers, Andrew Richards here for my first NT Debugging post. I thought I’d share a recent case that used a lot of discovery techniques to uncover the details of what was going on. Most bugchecks give you the information you need as arguments, but in the case of bugcheck 0x101, I had to go digging for a trap frame, the thread stack, look at the disassembly of the running threads, and lots of other goodies.

 

As usual, the first thing I did was run !analyze -v to get a more detailed explanation of what a bugcheck 0x101 “CLOCK_WATCHDOG_TIMEOUT” is.

 

0: kd> !analyze -v
*******************************************************************************
*                                                                             *
*                        Bugcheck Analysis                                    *
*                                                                             *
*******************************************************************************

CLOCK_WATCHDOG_TIMEOUT (101)
An expected clock interrupt was not received on a secondary processor in an
MP system within the allocated interval. This indicates that the specified
processor is hung and not processing interrupts.
Arguments:
Arg1: 00000031, Clock interrupt time out interval in nominal clock ticks.
Arg2: 00000000, 0.
Arg3: 87337120, The PRCB address of the hung processor.
Arg4: 00000003, 0.

 

A bugcheck 0x101 occurs when the Clock interrupt (IRQL #28) has not been processed by each processor within a timeout.  The Clock interrupt is quite high in the IRQL table for x86; only the Inter-Processor Interrupt (IPI), Power-Fail and High interrupts are higher.

 

31

High

30

Power Fail

29

Inter-processor Interrupt

28

Clock

27

Profile/Synch

26

Device n

 

5

CMCI

4

3

Device 1

2

DPC/Dispatch

1

APC

0

Passive

 

The immediate hypothesis was that one of the processors was stuck processing an IPI (a common interrupt), causing it to be above the Clock IRQ level.

The questions were:

1.  At what IRQL was the problem processor?  Was it at IPI?

2.  Why was it stuck?

3.  Who did it?

 

This was a 4 core box, so I ran !prcb four times to view the Processor Resource control Block (PRCB) of each processor. Processor #3 matched the PRCB specified in the bugcheck’s 3rd argument (Arg3 = 87337120). The first question was answered; Processor #3 was the culprit. Interestingly, the Arg4 value in the bugcheck has a value of 3.  Per the documentation it should be 0, but it seems that the processor number is provided.

 

0: kd> !prcb 0
PRCB for Processor 0 at 82b34d20:
Current IRQL -- 28
Threads--  Current 82b3e380 Next 00000000 Idle 82b3e380
Processor Index 0 Number (0, 0) GroupSetMember 1
Interrupt Count -- 000475e1
Times -- Dpc    000008d6 Interrupt 0000007b
         Kernel 0001920f User      00003b0b

0: kd> !prcb 1
PRCB for Processor 1 at 807c7120:
Current IRQL -- 0
Threads--  Current 9bb65d48 Next 9bf52c10 Idle 807cc800
Processor Index 1 Number (0, 1) GroupSetMember 2
Interrupt Count -- 00030ae8
Times -- Dpc    000005ca Interrupt 00000098
         Kernel 00017e4a User      00004eab

0: kd> !prcb 2
PRCB for Processor 2 at 87300120:
Current IRQL -- 0
Threads--  Current 9b41b6e8 Next 00000000 Idle 87305800
Processor Index 2 Number (0, 2) GroupSetMember 4
Interrupt Count -- 0002ab35
Times -- Dpc    00000568 Interrupt 000000ac
         Kernel 0001a788 User      00002565

0: kd> !prcb 3
PRCB for Processor 3 at 87337120:
Current IRQL -- 0
Threads--  Current 8aaa17c8 Next 00000000 Idle 8733c800
Processor Index 3 Number (0, 3) GroupSetMember 8
Interrupt Count -- 00026c0d
Times -- Dpc    00000620 Interrupt 0000008b
         Kernel 0001ac65 User      00001e33

 

IRQL

The Processor Context Records reported that Processors #0, #1 and #2 were at IRQL 31 (1f - HIGH), and Processor #3 was at IRQL 27 (1b - SYNCH). Having a value of 31 is expected at bugcheck as that is how the bugcheck gains control of the processor to gather the current context. It was strange that Processor #3 was different and that is matched the processor mentioned in the bugcheck

0: kd> !pcr 0
KPCR for Processor 0 at 82b34c00:
...
                Irql: 0000001f
...

0: kd> !pcr 1
KPCR for Processor 1 at 807c7000:
...
                Irql: 0000001f
...

0: kd> !pcr 2
KPCR for Processor 2 at 87300000:
...
                Irql: 0000001f
...

0: kd> !pcr 3
KPCR for Processor 3 at 87337000:
...
                Irql: 0000001b
...

 

The next step was to look at the stacks of the processors to see what the threads were all involved in.

 

Processor #0

To determine the stack of the Processor #0’s thread before the bugcheck, the trap frame needed to be found. The trap frame is stored immediately above the interrupt handler. To find that, I looked at the Interrupt Descriptor Table to find out the name of the handler for clock interrupt and then I searched for that symbol on the stack.

 

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
==> Processor #0
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
0: kd> knL
# ChildEBP RetAddr 
00 82b31674 82a84a6f nt!KeBugCheckEx+0x1e
01 82b316b0 82a840be nt!KeAccumulateTicks+0x242
02 82b316f0 82a83f6b nt!KeUpdateRunTime+0x145
03 82b3174c 82a88c17 nt!KeUpdateSystemTime+0x613
04 82b3174c 82a85e79 nt!KeUpdateSystemTimeAssist+0x13
05 82b317e0 82abfa17 nt!KiIpiSendPacket+0xdd
06 82b31820 82af0866 nt!KeFlushSingleTb+0x136
07 82b3190c 82b2ab90 nt!MmFreeSpecialPool+0x2b4
08 82b31970 82d4a06e nt!ExFreePoolWithTag+0xd6
09 82b31980 82d3fab2 nt!ViCtxFreeIsrContext+0xf
0a 82b31998 82d3a1c0 nt!VfIoFreeIrp+0xd3
0b 82b319a8 8ced5986 nt!IovFreeIrpPrivate+0x47
WARNING: Stack unwind information not available. Following frames may be wrong.
0c 82b319c4 82d3acd4 irsir+0x2986
0d 82b319f4 82a81933 nt!IovpLocalCompletionRoutine+0x14b
0e 82b31a3c 82d3ab64 nt!IopfCompleteRequest+0x128
0f 82b31aa4 8b7c6abd nt!IovCompleteRequest+0x133
10 82b31ab4 8b7c6461 serial!SerialGetNextIrpLocked+0x61
11 82b31ad8 8b7c7567 serial!SerialGetNextIrp+0x27
12 82b31b00 8b7b9eb7 serial!SerialTryToCompleteCurrent+0x7a
13 82b31b38 82a83039 serial!SerialReadTimeout+0x68
14 82b31b7c 82a82fdd nt!KiProcessTimerDpcTable+0x50
15 82b31c68 82a82e9a nt!KiProcessExpiredTimerList+0x101
16 82b31cdc 82a8100e nt!KiTimerExpiration+0x25c
17 82b31d20 82a80e38 nt!KiRetireDpcList+0xcb
18 82b31d24 00000000 nt!KiIdleLoop+0x38

 

0: kd> !irql
Debugger saved IRQL for processor 0x0 -- 28 (CLOCK2_LEVEL)

 

0: kd> !idt

Dumping IDT:

37:   82e35104 hal!PicSpuriousService37

51:   8aba1558 serial!SerialCIsrSw (KINTERRUPT 8aba1500)

71:   89003cd8 i8042prt!I8042KeyboardInterruptService (KINTERRUPT 89003c80)

72:   8aba17d8 USBPORT!USBPORT_InterruptService (KINTERRUPT 8aba1780)

82:   89123058 ataport!IdePortInterrupt (KINTERRUPT 89123000)

               ataport!IdePortInterrupt (KINTERRUPT 89123a00)

               ataport!IdePortInterrupt (KINTERRUPT 89123780)

               ataport!IdePortInterrupt (KINTERRUPT 89123500)

92:   8aba1058 Impcd+0x8540 (KINTERRUPT 8aba1000)

a0:   8aba1a58 ndis!ndisMiniportMessageIsr (KINTERRUPT 8aba1a00)

a2:   8aba1cd8 USBPORT!USBPORT_InterruptService (KINTERRUPT 8aba1c80)

b0:   891232d8 ndis!ndisMiniportMessageIsr (KINTERRUPT 89123280)

b1:   89123cd8 ACPI!ACPIInterruptServiceRoutine (KINTERRUPT 89123c80)

b2:   8aba12d8 serial!SerialCIsrSw (KINTERRUPT 8aba1280)

c1:   82e353f4 hal!HalpBroadcastCallService

d1:   82e1d634 hal!HalpHpetClockInterrupt

d2:   82e1d898 hal!HalpHpetRolloverInterrupt

df:   82e351dc hal!HalpApicRebootService

e1:   82e35958 hal!HalpIpiHandler

e3:   82e356f8 hal!HalpLocalApicErrorService

fd:   82e35f2c hal!HalpProfileInterrupt

fe:   82e361a8 hal!HalpPerfInterrupt

 

The search was between the nt!KiIpiSendPacket call (I just chose it as it didn’t seem to be bugcheck related) and the current stack pointer.  Using the dereferenced pointer (poi) and some maths, the trap frame location is retrieved.

 

0: kd> dps @esp 82b317e0
...
82b31744  82b31760 nt!KiDoubleFaultStack+0x2760 <<<<< This is not a real symbol; it actually is a part of the trap frame
82b31748  82e1d72a hal!HalpHpetClockInterrupt+0xf6
82b3174c  82b31760 nt!KiDoubleFaultStack+0x2760
82b31750  82a88c17 nt!KeUpdateSystemTimeAssist+0x13
...

 

0: kd> .trap poi(82b31748-4)
ErrCode = 00000000
eax=87300120 ebx=841882dc ecx=4cdfc4c4 edx=82b34d20 esi=807c7120 edi=82b738c4
eip=82a85e79 esp=82b317d4 ebp=82b31820 iopl=0         nv up ei pl nz na po nc
cs=0008  ss=0010  ds=0000  es=dea0  fs=040f  gs=0008             efl=00000202
nt!KiIpiSendPacket+0xdd:
82a85e79 f390            pause

 

So you might be asking yourself, can this be done an easier way?  The answer is definitely Yes. You just need to use kv instead; it adds the trap frame information on the end of the line.

 

0: kd> kv

ChildEBP RetAddr  Args to Child             

82b31674 82a84a6f 00000101 00000031 00000000 nt!KeBugCheckEx+0x1e

82b316b0 82a840be 00026161 00000000 0001cd00 nt!KeAccumulateTicks+0x242

82b316f0 82a83f6b 82a85e79 807c7120 00000000 nt!KeUpdateRunTime+0x145

82b3174c 82a88c17 ffffff1b ffffff1b 000000d1 nt!KeUpdateSystemTime+0x613

82b3174c 82a85e79 ffffff1b ffffff1b 000000d1 nt!KeUpdateSystemTimeAssist+0x13 (FPO: [0,2] TrapFrame @ 82b31760)

82b317e0 82abfa17 00000001 00000000 82a3cbe1 nt!KiIpiSendPacket+0xdd (FPO: [6,2,0])

82b31820 82af0866 a33d6f20 00000001 a33d00cf nt!KeFlushSingleTb+0x136

82b3190c 82b2ab90 a33d6f20 8a2096d8 8a2096d8 nt!MmFreeSpecialPool+0x2b4

82b31970 82d4a06e a33d6f20 00000000 82b31998 nt!ExFreePoolWithTag+0xd6

82b31980 82d3fab2 a33d6f20 a33d6f20 a33d6f20 nt!ViIrpFree+0xf

82b31998 82d3a1c0 8ced5986 905cadb0 82b319c4 nt!VfIoFreeIrp+0xd3

82b319a8 8ced5986 a33d6f20 8ced58f0 82b31a6c nt!IovFreeIrpPrivate+0x47

WARNING: Stack unwind information not available. Following frames may be wrong.

82b319c4 82d3acd4 00000000 a33d6f20 905cadb0 irsir+0x2986

82b319f4 82a81933 00000000 a33d6f20 82b31a6c nt!IovpLocalCompletionRoutine+0x14b

82b31a3c 82d3ab64 a33d6f20 8ab151ac 8ab150f0 nt!IopfCompleteRequest+0x128

82b31aa4 8b7c6abd 00000000 8ab151ac 82b31ad8 nt!IovCompleteRequest+0x133

82b31ab4 8b7c6461 8ab151ac 8ab1518c 82b31b0c serial!SerialGetNextIrpLocked+0x61 (FPO: [Non-Fpo])

82b31ad8 8b7c7567 8ab151ac 8ab1518c 82b31b0c serial!SerialGetNextIrp+0x27 (FPO: [Non-Fpo])

82b31b00 8b7b9eb7 8ab150f0 00000000 8ab15002 serial!SerialTryToCompleteCurrent+0x7a (FPO: [Non-Fpo])

82b31b38 82a83039 8ab15314 8ab15002 3c171d26 serial!SerialReadTimeout+0x68 (FPO: [Non-Fpo])

82b31b7c 82a82fdd 82b34d20 82b31ca8 00000001 nt!KiProcessTimerDpcTable+0x50

82b31c68 82a82e9a 82b34d20 82b31ca8 00000000 nt!KiProcessExpiredTimerList+0x101

82b31cdc 82a8100e 0001cacf 9bb374c0 82b3e380 nt!KiTimerExpiration+0x25c

82b31d20 82a80e38 00000000 0000000e 00000000 nt!KiRetireDpcList+0xcb

82b31d24 00000000 0000000e 00000000 00000000 nt!KiIdleLoop+0x38 (FPO: [0,0,0])

 

By using the stack based maths, or the value provide by kv, the trap frame address is used to set the context (.trap <addr>) to the code running before the interrupt.  These are the stored registers and the stack at the time of the interrupt.

 

0: kd> .trap 82b31760

ErrCode = 00000000

eax=87300120 ebx=841882dc ecx=4cdfc4c4 edx=82b34d20 esi=807c7120 edi=82b738c4

eip=82a85e79 esp=82b317d4 ebp=82b31820 iopl=0         nv up ei pl nz na po nc

cs=0008  ss=0010  ds=0000  es=dea0  fs=040f  gs=0008             efl=00000202

nt!KiIpiSendPacket+0xdd:

82a85e79 f390            pause

 

0: kd> knL
  *** Stack trace for last set context - .thread/.cxr resets it
# ChildEBP RetAddr 
00 82b317e0 82abfa17 nt!KiIpiSendPacket+0xdd
01 82b31820 82af0866 nt!KeFlushSingleTb+0x136
02 82b3190c 82b2ab90 nt!MmFreeSpecialPool+0x2b4
03 82b31970 82d4a06e nt!ExFreePoolWithTag+0xd6
04 82b31980 82d3fab2 nt!ViCtxFreeIsrContext+0xf
05 82b31998 82d3a1c0 nt!VfIoFreeIrp+0xd3
06 82b319a8 8ced5986 nt!IovFreeIrpPrivate+0x47
WARNING: Stack unwind information not available. Following frames may be wrong.
07 82b319c4 82d3acd4 irsir+0x2986
08 82b319f4 82a81933 nt!IovpLocalCompletionRoutine+0x14b
09 82b31a3c 82d3ab64 nt!IopfCompleteRequest+0x128
0a 82b31aa4 8b7c6abd nt!IovCompleteRequest+0x133
0b 82b31ab4 8b7c6461 serial!SerialGetNextIrpLocked+0x61
0c 82b31ad8 8b7c7567 serial!SerialGetNextIrp+0x27
0d 82b31b00 8b7b9eb7 serial!SerialTryToCompleteCurrent+0x7a
0e 82b31b38 82a83039 serial!SerialReadTimeout+0x68
0f 82b31b7c 82a82fdd nt!KiProcessTimerDpcTable+0x50
10 82b31c68 82a82e9a nt!KiProcessExpiredTimerList+0x101
11 82b31cdc 82a8100e nt!KiTimerExpiration+0x25c
12 82b31d20 82a80e38 nt!KiRetireDpcList+0xcb
13 82b31d24 00000000 nt!KiIdleLoop+0x38

 

Dissassembling the first few instructions reveals a jump (jmp) that is back up in the nt!KiIpiSendPacket function. Using the jmp location and the instruction after the jmp as the bound, we can disassemble the loop. At the time of the bugcheck, the thread was executing a pause (a CPU based delay), and seemingly doing this in a loop while waiting for to release it.

 

0: kd> u @eip
nt!KiIpiSendPacket+0xdd:
82a85e79 f390            pause  <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @eip
82a85e7b eb9e            jmp     nt!KiIpiSendPacket+0x7f (82a85e1b)
82a85e7d 8d4900          lea     ecx,[ecx]

...

0: kd> u 82a85e1b 82a85e7d
nt!KiIpiSendPacket+0x7f:
82a85e1b 8b86a4180000    mov     eax,dword ptr [esi+18A4h]
82a85e21 0bc0            or      eax,eax <<<<<< Checking if value is nonzero
82a85e23 7538            jne     nt!KiIpiSendPacket+0xc1 (82a85e5d) <<<<<< Take jmp to stay in loop
82a85e25 f00fb196a4180000 lock cmpxchg dword ptr [esi+18A4h],edx
82a85e2d 75ec            jne     nt!KiIpiSendPacket+0x7f (82a85e1b)
82a85e2f 59              pop     ecx
82a85e30 d1e9            shr     ecx,1
82a85e32 8d7f04          lea     edi,[edi+4]
82a85e35 72df            jb      nt!KiIpiSendPacket+0x7a (82a85e16)
82a85e37 75f7            jne     nt!KiIpiSendPacket+0x94 (82a85e30)
82a85e39 59              pop     ecx
82a85e3a 64890d8c190000  mov     dword ptr fs:[198Ch],ecx
82a85e41 8b4c240c        mov     ecx,dword ptr [esp+0Ch]
82a85e45 8b542410        mov     edx,dword ptr [esp+10h]
82a85e49 64ff0590360000  inc     dword ptr fs:[3690h]
82a85e50 52              push    edx
82a85e51 51              push    ecx
82a85e52 ff15a4a0a082    call    dword ptr [nt!_imp__HalRequestIpi (82a0a0a4)]
82a85e58 5f              pop     edi
82a85e59 5e              pop     esi
82a85e5a c21800          ret     18h
82a85e5d 41              inc     ecx
82a85e5e 850d7c3ab782    test    dword ptr [nt!HvlLongSpinCountMask (82b73a7c)],ecx
82a85e64 7513            jne     nt!KiIpiSendPacket+0xdd (82a85e79)
82a85e66 f605783ab78240  test    byte ptr [nt!HvlEnlightenments (82b73a78)],40h <<<<<< Don’t spin if you’re an enlightened VM, just pause
82a85e6d 740a            je      nt!KiIpiSendPacket+0xdd (82a85e79)
82a85e6f 52              push    edx
82a85e70 51              push    ecx
82a85e71 51              push    ecx
82a85e72 e8c8b60500      call    nt!HvlNotifyLongSpinWait (82ae153f)
82a85e77 59              pop     ecx
82a85e78 5a              pop     edx
82a85e79 f390            pause  <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @eip
82a85e7b eb9e            jmp     nt!KiIpiSendPacket+0x7f (82a85e1b)

 

The summary so far: Since processor #0 was the thread that created the bugcheck, it must have been interrupted by a Clock interrupt so as to trigger a CLOCK_WATCHDOG_TIMEOUT bugcheck. It is not surprising then that the value of !irql is CLOCK2.

The existence of the nt!KiIpiSendPacket function lends weight to the thought that this was the creator of an IPI and is not one of the processors that wasn’t processing IPIs.

 

Processor #1

Using the same technique as Processor #0, the disassembly for the loop on Processor #1 is determined. Processor #1 is in a tight loop within the nt!KeFlushMultipleRangeTb function, an ancestor function of nt!ExAllocatePoolWithTag, a memory related operation.  This is an interesting function to see, considering that nt!ExFreePoolWithTag, the counterpart, is on Processor #0’s stack.

 

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
==> Processor #1
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
1: kd> knL
# ChildEBP RetAddr 
00 95bfc7bc 82abb217 nt!KeFlushMultipleRangeTb+0x2d3
01 95bfc89c 82aa7d11 nt!MiFlushTbAsNeeded+0x12e
02 95bfc8dc 82b29487 nt!MiAllocatePagedPoolPages+0x567
03 95bfc940 82aa4674 nt!MiAllocatePoolPages+0x1f
04 95bfc998 82b2a132 nt!ExpAllocateBigPool+0xa6
05 95bfc9fc 82aab6b1 nt!ExAllocatePoolWithTag+0x12d
06 95bfca20 82c4cc62 nt!ExAllocatePoolWithTagPriority+0x196
07 95bfca78 82c7a662 nt!IopQueryNameInternal+0x60
08 95bfca98 82c57d88 nt!IopQueryName+0x1b
09 95bfcb1c 82c71a50 nt!ObpQueryNameString+0x7f
0a 95bfcb38 82c75e12 nt!ObQueryNameString+0x18
0b 95bfcc14 82c65788 nt!EtwTraceProcess+0xa2
0c 95bfcc38 82c73625 nt!PspExitProcess+0x37
0d 95bfccb4 82c87051 nt!PspExitThread+0x59a
0e 95bfcccc 82aba8c0 nt!PsExitSpecialApc+0x22
0f 95bfcd1c 82a472a4 nt!KiDeliverApc+0x28b
10 95bfcd1c 77556fc0 nt!KiServiceExit+0x64
WARNING: Frame IP not in any known module. Following frames may be wrong.
11 014ef918 00000000 0x77556fc0

 

1: kd> !irql

Debugger saved IRQL for processor 0x1 -- 0 (LOW_LEVEL)  <<<<<< Windows Internals 4th Edition notes that IRQL may not be saved; this explains the 0

 

1: kd> u @eip
nt!KeFlushMultipleRangeTb+0x2d3:
82a40c31 f390            pause <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @eip
82a40c33 8b07            mov     eax,dword ptr [edi]
82a40c35 85c0            test    eax,eax
82a40c37 75de            jne     nt!KeFlushMultipleRangeTb+0x2b9 (82a40c17)
82a40c39 8a4d0f          mov     cl,byte ptr [ebp+0Fh]
82a40c3c ff1558a1a082    call    dword ptr [nt!_imp_KfLowerIrql (82a0a158)]
82a40c42 5f              pop     edi
82a40c43 5e              pop     esi

 

1: kd> u 82a40c17 82a40c39
nt!KeFlushMultipleRangeTb+0x2b9:
82a40c17 46              inc     esi
82a40c18 85357c3ab782    test    dword ptr [nt!HvlLongSpinCountMask (82b73a7c)],esi
82a40c1e 7511            jne     nt!KeFlushMultipleRangeTb+0x2d3 (82a40c31)
82a40c20 f605783ab78240  test    byte ptr [nt!HvlEnlightenments (82b73a78)],40h
82a40c27 7408            je      nt!KeFlushMultipleRangeTb+0x2d3 (82a40c31)
82a40c29 56              push    esi
82a40c2a e810090a00      call    nt!HvlNotifyLongSpinWait (82ae153f)
82a40c2f eb02            jmp     nt!KeFlushMultipleRangeTb+0x2d5 (82a40c33)
82a40c31 f390            pause <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @eip
82a40c33 8b07            mov     eax,dword ptr [edi]
82a40c35 85c0            test    eax,eax
82a40c37 75de            jne     nt!KeFlushMultipleRangeTb+0x2b9 (82a40c17)

 

This function tests a variable at edi in each loop. While the signal is not set, the thread goes around loop, eventually executes a pause instruction and then tries the test again. This thread seems to be waiting on someone to set a flag.

 

Processor #2

Processor #2 is also trying to send an IPI using nt!KiIpiSendPacket. It was caught in the same function that Processor #0 is in.  In this case, it is executing the jmp instruction that is one instruction after the pause which Processor #0 is executing.

 

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
==> Processor #2
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
2: kd> knL
# ChildEBP RetAddr 
00 a0c4fac0 82a40bcd nt!KiIpiSendPacket+0xdf
01 a0c4fafc 82b29431 nt!KeFlushMultipleRangeTb+0x26f
02 a0c4fbe8 82b2aef1 nt!MiFreePoolPages+0x42c
03 a0c4fc50 82aa6b37 nt!ExFreePoolWithTag+0x436
04 a0c4fc64 82c3745d nt!MmFreeAccessPfnBuffer+0x2f
05 a0c4fcc0 82c4b83a nt!PfpFlushBuffers+0x2ba
06 a0c4fd50 82c12f5e nt!PfTLoggingWorker+0xaa
07 a0c4fd90 82aba219 nt!PspSystemThreadStartup+0x9e
08 00000000 00000000 nt!KiThreadStartup+0x19

 

2: kd> !irql

Debugger saved IRQL for processor 0x2 -- 0 (LOW_LEVEL)  <<<<<< Windows Internals 4th Edition notes that IRQL may not be saved; this explains the 0

 

2: kd> u @eip
nt!KiIpiSendPacket+0xdf:
82a85e7b eb9e            jmp     nt!KiIpiSendPacket+0x7f (82a85e1b) <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @eip
82a85e7d 8d4900          lea     ecx,[ecx]
...

 

Summary so far

So at this point we can say (without any real knowledge of how IPI handling is achieved) that:

 

Processor #0 is waiting for Processor #1, #2 and/or #3 to respond to its IPI

Processor #0 is running nt!ExFreePoolWithTag

Processor #0 is reported to be at IRQL 28 (CLOCK2_LEVEL)

 

Processor #1 is waiting for a flag to bet set; probably an IPI flag

Processor #0 is running nt!ExAllocatePoolWithTag

Processor #1 is reported to be at IRQL 0 (LOW_LEVEL); probably incorrect

 

Processor #2 is waiting for Processor #0, #1 and/or #3 to respond to its IPI

Processor #0 is running nt!ExFreePoolWithTag

Processor #2 is reported to be at IRQL 0 (LOW_LEVEL); probably incorrect

 

Processor #3 has been tagged by bugcheck as being the cause

Processor #3 hasn’t been looked at yet

 

Processor #3

Analyzing Processor #3 was problematic. The Processor Context Record (PCR) was not completely captured in the dump. This happens when the bugcheck thread is unable to interrupt the target processor to gather the context – usually because it is running at the HIGH (31) IRQL.

When the context is missing, all of the registers are treated as being zero. The zero value instruction pointer causes the stack to be incorrect when calling knL and various other commands.

 

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
==> Processor #3
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
1: kd> ~3
WARNING: Process directory table base 7B59C400 doesn't match CR3 00185000
WARNING: Process directory table base 7B59C400 doesn't match CR3 00185000

 

3: kd> r
eax=00000000 ebx=00000000 ecx=00000000 edx=00000000 esi=00000000 edi=00000000
eip=00000000 esp=00000000 ebp=00000000 iopl=0         nv up di pl nz na po nc
cs=0000  ss=0000  ds=0000  es=0000  fs=0000  gs=0000             efl=00000000
00000000 ??              ???

 

3: kd> knL
# ChildEBP RetAddr 
WARNING: Frame IP not in any known module. Following frames may be wrong.
00 00000000 00000000 0x0

 

To determine the thread running on the processor, the !thread command was used. The value for the _ETHREAD (8aaa17c8) comes from the PCR that Windows maintains as part of scheduling. Once again, like the knL command, because the context was missing, the stack was not shown.

 

3: kd> !pcr
KPCR for Processor 3 at 87337000:
...
       CurrentThread:
8aaa17c8
...

 

3: kd> !thread
THREAD
8aaa17c8  Cid 0454.08ec  Teb: 00000000 Win32Thread: 00000000 RUNNING on processor 3
IRP List:
    8f8c6f68: (0006,0094) Flags: 40000404  Mdl: 00000000
Not impersonating
DeviceMap                 89970d50
Owning Process            89f93ac0       Image:         ZyxApp.exe
Attached Process          N/A            Image:         N/A
Wait Start TickCount      117454         Ticks: 589 (0:00:00:09.188)
Context Switch Count      37             IdealProcessor: 3            
UserTime                  00:00:00.000
KernelTime                00:00:00.000
Win32 Start Address 0x7753fd0f
Stack Init 91aecfd0 Current 91aeca78 Base
91aed000 Limit 91aea000 Call 0
Priority 10 BasePriority 8 UnusualBoost 0 ForegroundBoost 0 IoPriority 2 PagePriority 5
ChildEBP RetAddr  Args to Child             
00000000 00000000 00000000 00000000 00000000 0x0

 

The important piece of information in the !thread output was the stack limits. These values allow me to do a search of the raw stack for known symbols. I used dps <limit> <base>. What I was looking for was the first symbol after the 0xffffffff entries which has a value above it that points to a location within this stack. In this case, is it the hal!KfLowerIrql symbol that has a value (base pointer) above it. This symbol is the deepest function that the stack has got to; it doesn’t necessarily mean that the stack is at this depth now.

 

3: kd> dps 91aea000 91aed000
91aea000  ffffffff
91aea004  ffffffff

91aec85c  ffffffff
91aec860  ffffffff
91aec864  ffffffff
91aec868  ffffffff
91aec86c  ffffffff
91aec870  ffffffff
91aec874  0001187f
91aec878  00000010
91aec87c  00011a1f
91aec880  ffffffff
91aec884  0001187f
91aec888  82b7561f nt!MmSystemSpaceLock+0x1f
91aec88c  91aec89c
91aec890  82e20ba9 hal!KfLowerIrql+0x61
91aec894  00011a00
91aec898  82b7561f nt!MmSystemSpaceLock+0x1f
91aec89c  91aec95c
91aec8a0  82e1e92d hal!KeReleaseQueuedSpinLock+0x2d
91aec8a4  82a3bdc7 nt!MiReturnNonPagedPoolVa+0x1d4
91aec8a8  00000001

91aec8ac  00000000

91aec8b0  83cac9e8

91aec8b4  ffffffff

91aec8b8  ffffffff

91aec8bc  ffffffff

91aec8c0  ffffffff

91aec8c4  ffffffff

91aec8c8  ffffffff

91aec8cc  ffffffff

91aec8d0  82e21cee hal!HalpLegacyApicWriteIcr+0xa

91aec8d4  91aec8f0

91aec8d8  82e2aea0 hal!HalpRequestIpiSpecifyVector+0x40

91aec8dc  00000000

91aec8e0  83cac9e8

91aec8e4  82b738cc nt!KiProcessorBlock+0xc

91aec8e8  87300120

91aec8ec  00000202

91aec8f0  91aec914

91aec8f4  82aa6e36 nt!MiInsertPageInFreeOrZeroedList+0x25b

91aec8f8  83cac9e8

91aec8fc  00000000

91aec900  0000001f

91aec904  00000001

91aec908  83cac9e8

91aec90c  00000003

91aec910  00000007

91aec914  00000fff

91aec918  00000003

91aec91c  82a40bcd nt!KeFlushMultipleRangeTb+0x26f

91aec920  00000001

91aec924  00000000

91aec928  82a9fa43 nt!KiFlushTargetMultipleRangeTb

91aec92c  00000000

91aec930  91aec9bc

91aec934  91aec9b8

91aec938  00000001

91aec93c  00000000

91aec940  83e9cc04

91aec944  00018736

91aec948  8733a480

91aec94c  00000003

91aec950  91aec9bc

91aec954  91aeca40

91aec958  82b29431 nt!MiFreePoolPages+0x42c

91aec95c  91aec9b8

91aec960  1f000000

91aec964  91aec9b8

91aec968  00000000

...

 

Using the first location found, I used the stack variables to build up a k= command. With an x86 stack, if you select the stack location above the symbol as the base pointer and stack pointer, and the symbol’s address as the instruction pointer, you’ll get a reconstructed stack.

 

3: kd> k= 91aec89c 91aec88c 82e20ba9

ChildEBP RetAddr 

91aec89c 82e1e92d hal!KfLowerIrql+0x61

91aec8a0 82a3bdc7 hal!KeReleaseQueuedSpinLock+0x2d

91aec9a8 82aab049 nt!MiReturnNonPagedPoolVa+0x1d4

91aec9c0 82ab1685 nt!FindNodeOrParent+0x2091aec9d8 82abd820 nt!RtlLookupElementGenericTableFullAvl+0x16

91aeca0c 82e20ba9 nt!RtlLookupElementGenericTableAvl+0x18

91aeca1c 82b199d2 hal!KfLowerIrql+0x61

91aeca24 82d41ad1 nt!VfAvlUnlockShared+0x15

91aeca40 82b2aef1 nt!VfRemLockDeleteMemoryRange+0x5c

91aecaa8 963a55c5 nt!ExFreePoolWithTag+0x436

91aecac8 963a61d4 Zyx+0x5c5
91aecb00 82d3a6c3 Zyx+0x11d4
91aecb24 82a4054a nt!IovCallDriver+0x258
91aecb38 82c3b975 nt!IofCallDriver+0x1b
91aecb7c 82c2c591 nt!IopDeleteFile+0x10c
91aecb94 82a81d60 nt!ObpRemoveObjectRoutine+0x59
91aecba8 82a81cd0 nt!ObfDereferenceObjectWithTag+0x88
91aecbb0 82c4f308 nt!ObfDereferenceObject+0xd
91aecbf0 82c7dba9 nt!ObpCloseHandleTableEntry+0x21d
91aecc20 82c65f86 nt!ExSweepHandleTable+0x5f
91aecc40 82c73666 nt!ObKillProcess+0x54
91aeccb4 82c87051 nt!PspExitThread+0x5db
91aecccc 82aba8c0 nt!PsExitSpecialApc+0x22
91aecd1c 82a472a4 nt!KiDeliverApc+0x28b
91aecd1c 775570b4 nt!KiServiceExit+0x64
0147ff88 00000000 0x775570b4

 

In this case, because nt!KiFlushTargetMultipleRangeTb was present on Processor #1 as well as being in the Processor #3 raw stack, I found the first symbol lower than this symbol on the (raw) stack that had a valid base pointer above it.  The stack was thus built around the nt!MiFreePoolPages function instead.

 

91aec94c  00000003

91aec950  91aec9bc

91aec954  91aeca40

91aec958  82b29431 nt!MiFreePoolPages+0x42c

91aec95c  91aec9b8

91aec960  1f000000

91aec964  91aec9b8

91aec968  00000000

 

3: kd> k= 91aeca40 91aec954 82b29431

ChildEBP RetAddr 

91aeca40 82b2aef1 nt!MiFreePoolPages+0x42c

91aecaa8 963a55c5 nt!ExFreePoolWithTag+0x436

WARNING: Stack unwind information not available. Following frames may be wrong.

91aecac8 963a61d4 Zyx+0x5c5

91aecb00 82d3a6c3 Zyx+0x11d4

91aecb24 82a4054a nt!IovCallDriver+0x258

91aecb38 82c3b975 nt!IofCallDriver+0x1b

91aecb7c 82c2c591 nt!IopDeleteFile+0x10c

91aecb94 82a81d60 nt!ObpRemoveObjectRoutine+0x59

91aecba8 82a81cd0 nt!ObfDereferenceObjectWithTag+0x88

91aecbb0 82c4f308 nt!ObfDereferenceObject+0xd

91aecbf0 82c7dba9 nt!ObpCloseHandleTableEntry+0x21d

91aecc20 82c65f86 nt!ExSweepHandleTable+0x5f

91aecc40 82c73666 nt!ObKillProcess+0x54

91aeccb4 82c87051 nt!PspExitThread+0x5db

91aecccc 82aba8c0 nt!PsExitSpecialApc+0x22

91aecd1c 82a472a4 nt!KiDeliverApc+0x28b

91aecd1c 775570b4 nt!KiServiceExit+0x64

0147ff88 00000000 0x775570b4

 

It’s very hard to get much further this stack since the exact registers are not known. In particular, it is hard to determine what function above nt!MiFreePoolPages the thread is executing at the moment.  As with the Processor #1 investigation, it is interesting to note that Processor #3 is also involved in a memory operation; specifically, it is doing a nt!ExFreePoolWithTag much like Processor #0 is.

 

Inter-Processor Interrupts

The only time an IPI (interrupt) is not processed immediately is when the target processor is at IPI IRQL or higher. The most common example being when it is already processing an IPI (interrupts of the same level cannot interrupt the handler for the same IRQL). In this case, the interrupt has to be queued until the interrupt mask allows its arrival. Usually, this design allows only one IPI to be processed at any one time.

 

A deadlock (like) condition can arise though if an IPI is issued to a processor that is at a higher IRQL, and this processor (thread) attempts to send an IPI. The IPI logic blocks the send if there is an outstanding IPI to complete on the processor. The assumption being that interrupt queuing should be avoided as there is a probability of loss if the queue overflows.

 

Looking in the Windows Internals book, there is a single sentence that says “Each interrupt level has a specific purpose. For example, the kernel issues an interprocessor interrupt (IPI) to request that another processor perform an action, such as dispatching a particular thread for execution or updating its translation look-aside buffer cache.”.  This is very interesting as the translation look-aside buffer is part of the memory manager, and the memory operations are being undertaken on all the processors.

 

3rd Party Driver

Instead of pulling my hair out combing through the threads on the system, the IPI code, the Memory Manager code or bugcheck code, I decided to look at the 3rd party driver in processor #3 to see if it was changing the IRQL.

 

The first step was to find the bound of the Zyx+0x5c5 function.  The end address of the assembler is easy to determine, it is Zyx+0x5c5.  The question is, how big is the function? To work that out, you look at the assembler of the caller.  The caller’s assembler will point to the starting instruction in the Zyx+0x5c5 function.

 

3: kd> k= 91aeca40 91aec954 82b29431

ChildEBP RetAddr 

91aeca40 82b2aef1 nt!MiFreePoolPages+0x42c

91aecaa8 963a55c5 nt!ExFreePoolWithTag+0x436

WARNING: Stack unwind information not available. Following frames may be wrong.

91aecac8 963a61d4 Zyx+0x5c5

91aecb00 82d3a6c3 Zyx+0x11d4

91aecb24 82a4054a nt!IovCallDriver+0x258

91aecb38 82c3b975 nt!IofCallDriver+0x1b

91aecb7c 82c2c591 nt!IopDeleteFile+0x10c

...

 

3: kd> ub Zyx+0x11d4
Zyx+0x11b7:
963a61b7 ff7008          push    dword ptr [eax+8]
963a61ba 52              push    edx
963a61bb 6a01            push    1
963a61bd ff7018          push    dword ptr [eax+18h]
963a61c0 e827fdffff      call    Zyx+0xeec (963a5eec)
963a61c5 e9f1000000      jmp     Zyx+0x12bb (963a62bb)
963a61ca e8d3fcffff      call    Zyx+0xea2 (963a5ea2)
963a61cf e89ef3ffff      call    Zyx+0x572 (963a5572)

 

3: kd> u 963a5572 Zyx+0x5c5
Zyx+0x572:
963a5572 8bff            mov     edi,edi
963a5574 55              push    ebp
963a5575 8bec            mov     ebp,esp
963a5577 51              push    ecx
963a5578 53              push    ebx
963a5579 56              push    esi
963a557a 57              push    edi
963a557b b11f            mov     cl,1Fh <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 0x1F = 0n31 (HIGH)
963a557d ff15006b3a96    call    dword ptr [Zyx+0x1b00 (963a6b00)] <<<<<<<< hal!KfRaiseIrql (via lookup)
963a5583 bb14813a96      mov     ebx,offset Zyx+0x3114 (963a8114)
963a5588 8bcb            mov     ecx,ebx
963a558a 8845ff          mov     byte ptr [ebp-1],al
963a558d e8c2100000      call    Zyx+0x1654 (963a6654) <<<<<<<<<<<<<<<< [Unresolved]
963a5592 a1746d3a96      mov     eax,dword ptr [Zyx+0x1d74 (963a6d74)]
963a5597 85c0            test    eax,eax
963a5599 8b35586b3a96    mov     esi,dword ptr [Zyx+0x1b58 (963a6b58)] <<<< nt!ExFreePoolWithTag (via lookup)
963a559f 7413            je      Zyx+0x5b4 (963a55b4)
963a55a1 8b7808          mov     edi,dword ptr [eax+8]
963a55a4 6a00            push    0
963a55a6 50              push    eax
963a55a7 ffd6            call    esi  <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< nt!ExFreePoolWithTag (via 963a5599)
963a55a9 85ff            test    edi,edi
963a55ab 8bc7            mov     eax,edi
963a55ad a3746d3a96      mov     dword ptr [Zyx+0x1d74 (963a6d74)],eax
963a55b2 75ed            jne     Zyx+0x5a1 (963a55a1)
963a55b4 a1a06d3a96      mov     eax,dword ptr [Zyx+0x1da0 (963a6da0)]
963a55b9 85c0            test    eax,eax
963a55bb 7413            je      Zyx+0x5d0 (963a55d0)
963a55bd 8b7808          mov     edi,dword ptr [eax+8]
963a55c0 6a00            push    0
963a55c2 50              push    eax
963a55c3 ffd6            call    esi <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< nt!ExFreePoolWithTag (via 963a5599)

 

3: kd> ln poi(963a6b00)
(82e20844)   hal!KfRaiseIrql   |  (82e208c6)   hal!HalpDispatchSoftwareInterrupt
Exact matches:

 

3: kd> ln poi(963a6b58)
(82b2aaba)   nt!ExFreePoolWithTag   |  (82b2b660)   nt!ExDeferredFreePool
Exact matches:

 

The next step was to work out what functions the Zyx+0x5c5 function called, and with what parameters. The assembler didn’t make this immediately obvious. The address of the function calls were lookups. This is common when the address is imported by the code, instead of being bound. To get the function name of the call, I dereferenced the pointer (poi(<addr>) and passed the address to ln so that it listed the nearest symbols.

 

The first function confirmed some the theory. A call to hal!KfRaiseIrql was being made with a IRQL of HIGH (31). And this was prior to a code path that called functions.

 

The value of the @esi register was determined to be nt!ExFreePoolWithTag. This matched the function name in the reconstructed stack (nt!ExFreePoolWithTag+0x436) and confirmed that the stack was reconstructed in this area correctly.

 

Conclusion

The Zyx function raised IRQL to HIGH_LEVEL before calling nt!ExFreePoolWithTag. This function caused a Translation Look-aside Buffer (TLB) flush via nt!KiFlushTargetMultipleRangeTb) to occur. This in turn caused an IPI notification to the other processors (via nt!KiIpiSendPacket) to indicate the release of the TLB memory.

 

The Processor #0 IPI was stuck as incomplete because Processor #3 could not be interrupted, and Processor #3 did not lower its IRQL as it was waiting to send its own IPI upon completion of the Processor #0 IPI – thus a deadlock was formed.

 

I contacted the driver’s author and organized a new version that used the SYNCH (27) IRQL (the level immediately under IPI). The new build was sent to the customer and the immediate bugcheck that they were observing in their test environment disappeared.  Although this is not an ideal solution, it was sufficient for the time being.  An ideal solution would be to follow the documentation for ExFreePoolWithTag and not make the call with an IRQL greater than DISPATCH_LEVEL.

 

I hope this case has provided a good foundation in how to recover and navigate through stacks without symbols or context. Once you understand the mechanics of calling conventions, the values needed to bound a disassembling operation and/or a stack reassemble are quite easy to determine.

 

Call Stacks for Pool Allocations

$
0
0

Hello, it's the Debug Ninja back again for another NtDebugging Blog article.  For as long as I can remember user mode debuggers have had an easy way to get call stacks for heap allocations.  On more recent versions of Windows this has been as simple as using gflags +ust and umdh or !heap -k.  Kernel debuggers have not always had an easy way to determine who allocated a pool block.  Sure, we have pool tags to help us out, but often a programmer will use the same tag in many places and devalue this as a troubleshooting technique.

 

Fortunately, starting in Windows Vista and Server 2008, kernel debuggers can get call stacks from pool allocations.  We can even get call stacks from pool frees.  This little known technique is not quite as useful as gflags +ust is for heap, but when it is needed it is very useful.

 

First, you need to turn on special pool using driver verifier.  Verifier will obtain and track the call stack for the allocation and the free, so this technique will not work with traditional special pool as documented in KB188831 because those settings do not use driver verifier.  Because special pool requires additional memory overhead to run, this technique is not valuable for large memory leaks.  However, this technique is a good way to determine what code allocated or freed your pool block in other conditions.  For example, this works well if you find that pool has been freed when you expected it to be allocated.  This also works for smaller memory leaks, especially those for which you can easily reproduce the leak.  Analyzing the allocations and stacks for a leak must be done by hand, as there is no umdh-like tool for kernel mode.

 

Step 1 - Turning on verifier

In this example I am using Sysinternals’ notmyfault tool to generate the pool allocations.  Because I know the driver in question I set verifier to only monitor that driver.  Note that a reboot is required to make this setting take effect.

 

Verifier /flags 1 /driver myfault.sys

 

Step 2 - Finding the pool allocation to analyze

For this example I am going to find the call stack of a leaked pool allocation.  First find the tag that is using the most pool by using !poolused.

 

kd> !poolused 4

   Sorting by  Paged Pool Consumed

 

  Pool Used:

            NonPaged            Paged

 Tag    Allocs     Used    Allocs     Used

Leak        0        0        23 23552000 UNKNOWN pooltag 'Leak', please update pooltag.txt

CM31        0        0     20520 18514560 Internal Configuration manager allocations , Binary: nt!cm

CIcr        0        0      2977  8511504 Code Integrity allocations for image integrity checking , Binary: ci.dll

 

Next find the pool allocations for that tag with !poolfind.  There is some guessing to be done with all pool leak debugging techniques; you can’t be sure that the allocation you’re looking at has really been leaked and is not just in a state where it has not yet been freed.  You need to make an educated guess because there is no umdh-type functionality to analyze allocates and frees.  If you have the benefit of a live debug you can go the debugger and check back later to see if the memory has been freed or not.

 

kd> !poolfind Leak

 

Scanning large pool allocation table for Tag: Leak (fffffa8002e00000 : fffffa8002f80000)

 

*fffff8a006a00000 :large page allocation, Tag  is Leak, size  is 0xfa000 bytes

*fffff8a0058fa000 :large page allocation, Tag  is Leak, size  is 0xfa000 bytes

*fffff8a006200000 :large page allocation, Tag  is Leak, size  is 0xfa000 bytes

*fffff8a0068fa000 :large page allocation, Tag  is Leak, size  is 0xfa000 bytes

*fffff8a0060fa000 :large page allocation, Tag  is Leak, size  is 0xfa000 bytes

*fffff8a005a00000 :large page allocation, Tag  is Leak, size  is 0xfa000 bytes

*fffff8a006c00000 :large page allocation, Tag  is Leak, size  is 0xfa000 bytes

*fffff8a006400000 :large page allocation, Tag  is Leak, size  is 0xfa000 bytes

*fffff8a0062fa000 :large page allocation, Tag  is Leak, size  is 0xfa000 bytes

*fffff8a005afa000 :large page allocation, Tag  is Leak, size  is 0xfa000 bytes

*fffff8a005c00000 :large page allocation, Tag  is Leak, size  is 0xfa000 bytes

*fffff8a006e00000 :large page allocation, Tag  is Leak, size  is 0xfa000 bytes

*fffff8a006600000 :large page allocation, Tag  is Leak, size  is 0xfa000 bytes

*fffff8a0064fa000 :large page allocation, Tag  is Leak, size  is 0xfa000 bytes

*fffff8a005cfa000 :large page allocation, Tag  is Leak, size  is 0xfa000 bytes

*fffff8a006afa000 :large page allocation, Tag  is Leak, size  is 0xfa000 bytes

*fffff8a005e00000 :large page allocation, Tag  is Leak, size  is 0xfa000 bytes

*fffff8a006800000 :large page allocation, Tag  is Leak, size  is 0xfa000 bytes

*fffff8a0066fa000 :large page allocation, Tag  is Leak, size  is 0xfa000 bytes

*fffff8a005efa000 :large page allocation, Tag  is Leak, size  is 0xfa000 bytes

*fffff8a006cfa000 :large page allocation, Tag  is Leak, size  is 0xfa000 bytes

*fffff8a006000000 :large page allocation, Tag  is Leak, size  is 0xfa000 bytes

*fffff8a005800000 :large page allocation, Tag  is Leak, size  is 0xfa000 bytes

 

Step 3 – Dump the call stack for the allocation

This step is the easy one.  Once you have the address of the allocation use !verifier 0x80 Address.  If you were interested in all of the call stacks in the log you can run !verifier 0x80 without the Address parameter.

 

kd> !verifier 0x80 fffff8a005800000

 

Log of recent kernel pool Allocate and Free operations:

 

There are up to 0x10000 entries in the log.

 

Parsing 0x0000000000010000 log entries, searching for address 0xfffff8a005800000.

 

 

======================================================================

Pool block fffff8a005800000, Size 00000000000fa000, Thread fffffa8002be4060

fffff80001923cc6 nt!VeAllocatePoolWithTagPriority+0x2b6

fffff80001923d3d nt!VerifierExAllocatePoolEx+0x1d

fffff880042881f6 myfault+0x11f6

fffff8800428842f myfault+0x142f

fffff8000192e750 nt!IovCallDriver+0xa0

fffff800017a3a97 nt!IopXxxControlFile+0x607

fffff800017a42f6 nt!NtDeviceIoControlFile+0x56

fffff80001487ed3 nt!KiSystemServiceCopyEnd+0x13

======================================================================

Pool block fffff8a005800000, Size 0000000000001000, Thread fffffa8002187060

fffff8000192393a nt!VfFreePoolNotification+0x4a

fffff800015b6a6f nt!ExDeferredFreePool+0x107b

fffff800017273eb nt!HvFreeDirtyData+0x7f

fffff800017269bb nt!HvOptimizedSyncHive+0x53

fffff80001726303 nt!CmFlushKey+0xaf

fffff80001726b22 nt!NtFlushKey+0x142

fffff80001487ed3 nt!KiSystemServiceCopyEnd+0x13

Parsed entry 0000000000010000/0000000000010000...

Finished parsing all pool tracking information.

 

Keep in mind that the log may contain allocate and free information that predates the current use of the pool block, and that the log is of a fixed size so eventually old data will fall off the end.  The most recent use of the pool will be at the top of the output.  Usually this is the stack at the top of the output is what you are interested in, I have highlighted the relevant call stack in red.  In this instance we can see that the pool was most recently allocated by myfault.sys.

 

Sometimes it is useful to have historical information about previous uses of the pool block such as when dealing with pool that was improperly freed.  In that scenario the most recent call stack may be from an allocate call when the pool block was reused by the memory manager and so you may need to go down several levels to find out where the pool was improperly freed.

 

For more information on using !verifier you can refer to the debugger help in MSDN, http://msdn.microsoft.com/en-us/library/ff565591.aspx.

Where Did My Disk I/O Go?

$
0
0

Hello, Mr. Ninja back again.  I recently discovered that although my team often tracks I/O from the file system through to the disk controller, we have never publicly documented the steps required to do this.  This seems like a great opportunity for a blog because most of the structures are known, and they are even included in the public symbols.

 

When debugging a system that is hung, slow, or otherwise unresponsive you will likely encounter an IRP that has been sent from ntfs to the disk.  Running !irp against such a request will show that the request has gone to disk.sys, but that is not really where the story ends.

 

Below is one such example of ntfs waiting with an IRP that appears to be stuck in disk.sys.  You can determine what driver last handled the IRP by looking for the > character, this points to the current io stack location.

 

2: kd> !thread fffffa804f151040 e

THREAD fffffa804f151040  Cid 0004.33f8  Teb: 0000000000000000 Win32Thread: 0000000000000000 WAIT: (Executive) KernelMode Non-Alertable

    fffff8803836e730  NotificationEvent

IRP List:

    fffffa804f379440: (0006,0310) Flags: 00060043  Mdl: fffffa803c854090

Not impersonating

DeviceMap                 fffff8a000008720

Owning Process            fffffa8030cdeb30       Image:         System

Attached Process          N/A            Image:         N/A

Wait Start TickCount      34797397       Ticks: 1118 (0:00:00:17.440)

Context Switch Count      5893

UserTime                  00:00:00.000

KernelTime                00:00:00.296

Win32 Start Address nt!ExpWorkerThread (0xfffff80002ae2ef0)

Stack Init fffff88038370db0 Current fffff8803836e0d0

Base fffff88038371000 Limit fffff8803836b000 Call 0

Priority 16 BasePriority 13 UnusualBoost 0 ForegroundBoost 0 IoPriority 2 PagePriority 5

Child-SP          RetAddr           Call Site

fffff880`3836e110 fffff800`02addf32 nt!KiSwapContext+0x7a

fffff880`3836e250 fffff800`02ae074f nt!KiCommitThreadWait+0x1d2

fffff880`3836e2e0 fffff880`0164b3ff nt!KeWaitForSingleObject+0x19f

fffff880`3836e380 fffff880`01654224 Ntfs!NtfsNonCachedIo+0x23f

fffff880`3836e550 fffff880`0164f507 Ntfs!NtfsNonCachedUsaWrite+0x64

fffff880`3836e5e0 fffff880`016501a3 Ntfs!NtfsCommonWrite+0x2ca4

fffff880`3836e790 fffff800`02abebff Ntfs!NtfsFsdWrite+0x1c3

fffff880`3836ea10 fffff800`02b1cc00 nt!IoSynchronousPageWrite+0x24f

fffff880`3836ea90 fffff800`02b1b2d8 nt!MiFlushSectionInternal+0xb30

fffff880`3836ecc0 fffff800`02b1a83c nt!MmFlushSection+0x1f4

fffff880`3836ed80 fffff880`01653bb7 nt!CcFlushCache+0x7bc

fffff880`3836ee80 fffff880`01700037 Ntfs!LfsFlushLfcb+0x647

fffff880`3836f000 fffff880`017025b0 Ntfs!LfsFlushToLsnPriv+0x143

fffff880`3836f090 fffff880`0172445f Ntfs!LfsWriteLfsRestart+0xf0

fffff880`3836f0d0 fffff880`017242d0 Ntfs!LfsCloseLogFile+0x17f

fffff880`3836f190 fffff880`01715810 Ntfs!NtfsStopLogFile+0x70

fffff880`3836f1d0 fffff880`0172bfdb Ntfs!NtfsPerformDismountOnVcb+0x184

2: kd> !irp fffffa804f379440

Irp is active with 8 stacks 5 is current (= 0xfffffa804f379630)

 Mdl=fffffa803c854090: No System Buffer: Thread fffffa804f151040:  Irp stack trace.

     cmd  flg cl Device   File     Completion-Context

 [  0, 0]   0  0 00000000 00000000 00000000-00000000

 

                        Args: 00000000 00000000 00000000 00000000

 [  0, 0]   0  0 00000000 00000000 00000000-00000000

 

                        Args: 00000000 00000000 00000000 00000000

 [  0, 0]   0  0 00000000 00000000 00000000-00000000

 

                        Args: 00000000 00000000 00000000 00000000

 [  0, 0]   0  0 00000000 00000000 00000000-00000000

 

                        Args: 00000000 00000000 00000000 00000000

>[  4,34]  1c e0 fffffa8032052060 00000000 fffff880011bb010-fffffa803a604c90 Success Error Cancel

               \Driver\Disk     volmgr!VmpReadWriteCompletionRoutine

                        Args: 00001000 00000000 b5f8a000 00000000

 [  4, 0]   c e0 fffffa803a604b40 00000000 fffff88001cb5150-fffffa803a1ec180 Success Error Cancel

               \Driver\volmgr   volsnap!VspRefCountCompletionRoutine

                        Args: 00001000 00000000 b5e8a000 00000000

 [  4, 0]   c e1 fffffa803a1ec030 00000000 fffff8800164c344-fffff8803836e728 Success Error Cancel pending

               \Driver\volsnap  Ntfs!NtfsMasterIrpSyncCompletionRoutine

                        Args: 00001000 00000000 b5e8a000 00000000

 [  4, 0]   0  0 fffffa803d1bf030 fffffa803b268540 00000000-00000000

               \FileSystem\Ntfs

                        Args: 00001000 00000000 01d0c000 00000000

 

To learn more about what disk.sys is doing with this request we start by looking at the device extension.  Disk.sys is a miniclass driver, it depends on classpnp.sys to do most of the work.  The device extension will be a FUNCTIONAL_DEVICE_EXTENSION structure from classpnp.

 

2: kd> !devobj fffffa8032052060

Device object (fffffa8032052060) is for:

 DR36 \Driver\Disk DriverObject fffffa80319fa990

Current Irp 00000000 RefCount 0 Type 00000007 Flags 01002050

Vpb fffffa803204aba0 Dacl fffff9a100463450 DevExt fffffa80320521b0 DevObjExt fffffa8032052858 Dope fffffa803204ab30

ExtensionFlags (0x00000800)

                             Unknown flags 0x00000800

AttachedDevice (Upper) fffffa8032052b90 \Driver\partmgr

AttachedTo (Lower) fffffa8031dcc060 \Driver\mpio

Device queue is not busy.

2: kd> dt classpnp!_FUNCTIONAL_DEVICE_EXTENSION fffffa80320521b0

   +0x000 Version          : 3

   +0x008 DeviceObject     : 0xfffffa80`32052060 _DEVICE_OBJECT

   +0x000 CommonExtension  : _COMMON_DEVICE_EXTENSION

   +0x200 LowerPdo         : 0xfffffa80`31dcc060 _DEVICE_OBJECT

   +0x208 DeviceDescriptor : 0xfffffa80`320afeb0 _STORAGE_DEVICE_DESCRIPTOR

   +0x210 AdapterDescriptor : 0xfffffa80`32043910 _STORAGE_ADAPTER_DESCRIPTOR

   +0x218 DevicePowerState : 1 ( PowerDeviceD0 )

   +0x21c DMByteSkew       : 0

   +0x220 DMSkew           : 0

   +0x224 DMActive         : 0 ''

   +0x228 DiskGeometry     : _DISK_GEOMETRY

   +0x240 SenseData        : 0xfffffa80`320a65c0 _SENSE_DATA

   +0x248 TimeOutValue     : 0x3c

   +0x24c DeviceNumber     : 0x24

   +0x250 SrbFlags         : 0x200102

   +0x254 ErrorCount       : 0

   +0x258 LockCount        : 0n1

   +0x25c ProtectedLockCount : 0n0

   +0x260 InternalLockCount : 0n0

   +0x268 EjectSynchronizationEvent : _KEVENT

   +0x280 DeviceFlags      : 4

   +0x282 SectorShift      : 0x9 ''

   +0x283 CdbForceUnitAccess : 0 ''

   +0x288 MediaChangeDetectionInfo : (null)

   +0x290 Unused1          : (null)

   +0x298 Unused2          : (null)

   +0x2a0 KernelModeMcnContext : _FILE_OBJECT_EXTENSION

   +0x2b8 MediaChangeCount : 6

   +0x2c0 DeviceDirectory  : 0xffffffff`800003cc Void

   +0x2c8 ReleaseQueueSpinLock : 0

   +0x2d0 ReleaseQueueIrp  : (null)

   +0x2d8 ReleaseQueueSrb  : _SCSI_REQUEST_BLOCK

   +0x330 ReleaseQueueNeeded : 0 ''

   +0x331 ReleaseQueueInProgress : 0 ''

   +0x332 ReleaseQueueIrpFromPool : 0 ''

   +0x333 FailurePredicted : 0 ''

   +0x334 FailureReason    : 0

   +0x338 FailurePredictionInfo : (null)

   +0x340 PowerDownInProgress : 0 ''

   +0x344 EnumerationInterlock : 0

   +0x348 ChildLock        : _KEVENT

   +0x360 ChildLockOwner   : (null)

   +0x368 ChildLockAcquisitionCount : 0

   +0x36c ScanForSpecialFlags : 0

   +0x370 PowerRetryDpc    : _KDPC

   +0x3b0 PowerRetryTimer  : _KTIMER

   +0x3f0 PowerContext     : _CLASS_POWER_CONTEXT

   +0x478 PrivateFdoData   : 0xfffffa80`320bc010 _CLASS_PRIVATE_FDO_DATA

   +0x480 Reserved2        : 0

   +0x488 Reserved3        : 0

   +0x490 Reserved4        : 0

 

The information about requests is stored in the PrivateFdoData .

2: kd> dt 0xfffffa80`320bc010 _CLASS_PRIVATE_FDO_DATA

CLASSPNP!_CLASS_PRIVATE_FDO_DATA

   +0x000 SqmData          : 0x62a05

   +0x008 TrackingFlags    : 0

   +0x00c UpdateDiskPropertiesWorkItemActive : 0

   +0x010 LocalMinWorkingSetTransferPackets : 0x200

   +0x014 LocalMaxWorkingSetTransferPackets : 0x800

   +0x018 AllFdosListEntry : _LIST_ENTRY [ 0xfffffa80`320be028 - 0xfffffa80`320b8028 ]

   +0x028 Perf             : <unnamed-tag>

   +0x038 HackFlags        : 0

   +0x040 HotplugInfo      : _STORAGE_HOTPLUG_INFO

   +0x048 Retry            : <unnamed-tag>

   +0x0f0 TimerInitialized : 0 ''

   +0x0f1 LoggedTURFailureSinceLastIO : 0 ''

   +0x0f2 LoggedSYNCFailure : 0 ''

   +0x0f3 ReleaseQueueIrpAllocated : 0x1 ''

   +0x0f8 ReleaseQueueIrp  : 0xfffffa80`320bcc40 _IRP

   +0x100 AllTransferPacketsList : _LIST_ENTRY [ 0xfffffa80`320bbe60 - 0xfffffa80`4ed53d10 ]

   +0x110 FreeTransferPacketsList : _SLIST_HEADER

   +0x120 NumFreeTransferPackets : 0xff

   +0x124 NumTotalTransferPackets : 0x100

   +0x128 DbgPeakNumTransferPackets : 0x100

   +0x130 DeferredClientIrpList : _LIST_ENTRY [ 0xfffffa80`320bc140 - 0xfffffa80`320bc140 ]

   +0x140 HwMaxXferLen     : 0x80000

   +0x148 SrbTemplate      : _SCSI_REQUEST_BLOCK

   +0x1a0 SpinLock         : 0

   +0x1a8 LastKnownDriveCapacityData : _READ_CAPACITY_DATA_EX

   +0x1b4 IsCachedDriveCapDataValid : 0x1 ''

   +0x1b8 ErrorLogNextIndex : 6

   +0x1c0 ErrorLogs        : [16] _CLASS_ERROR_LOG_DATA

   +0x9c0 NumHighPriorityPagingIo : 0

   +0x9c4 MaxInterleavedNormalIo : 0

   +0x9c8 ThrottleStartTime : _LARGE_INTEGER 0x0

   +0x9d0 ThrottleStopTime : _LARGE_INTEGER 0x0

   +0x9d8 LongestThrottlePeriod : _LARGE_INTEGER 0x0

   +0x9e0 IdlePrioritySupported : 0x1 ''

   +0x9e8 IdleListLock     : 0

   +0x9f0 IdleIrpList      : _LIST_ENTRY [ 0xfffffa80`320bca00 - 0xfffffa80`320bca00 ]

   +0xa00 IdleTimer        : _KTIMER

   +0xa40 IdleDpc          : _KDPC

   +0xa80 IdleTimerInterval : 0x19

   +0xa82 StarvationCount  : 0x14

   +0xa84 IdleTimerTicks   : 0

   +0xa88 IdleTicks        : 0

   +0xa8c IdleIoCount      : 0

   +0xa90 IdleTimerStarted : 0n0

   +0xa98 LastIoTime       : _LARGE_INTEGER 0x1cc8bde`4f571cca

   +0xaa0 ActiveIoCount    : 0n1

   +0xaa4 ActiveIdleIoCount : 0n0

   +0xaa8 InterpretSenseInfo : (null)

   +0xab0 MaxPowerOperationRetryCount : 0

   +0xab8 PowerProcessIrp  : 0xfffffa80`320bd010 _IRP

   +0xac0 PerfCounterFrequency : _LARGE_INTEGER 0x23c3c4

 

The outstanding requests are stored in the AllTransferPacketsList.  Classpnp uses a transfer packet to send the request to the lower level drivers.  This allows the request to be split into smaller packets if necessary, and for the request to be retried if there is a failure.

 

We can dump the AllTransferPacketsList with !list and then search for our irp, it will be in the OriginalIrp field of one of the transfer packets.  Note that the output from dt will displayed with a `, while the output from !thread does not, so you will need to add a ` when searching through the !list output.  Also, there may be multiple transfer packets with the same OriginalIrp.

 

2: kd> !list "-t classpnp!_TRANSFER_PACKET.AllPktsListEntry.Flink -e -x \"??@$extret; dt classpnp!_TRANSFER_PACKET @$extret\" 0xfffffa80`320bbe60"

??@$extret; dt classpnp!_TRANSFER_PACKET @$extret

unsigned int64 0xfffffa80`399ad5e0

   +0x000 AllPktsListEntry : _LIST_ENTRY [ 0xfffffa80`3bae2b40 - 0xfffffa80`3bc7cb30 ]

   +0x010 SlistEntry       : _SLIST_ENTRY

   +0x020 Irp              : 0xfffffa80`3bb71570 _IRP

   +0x028 Fdo              : 0xfffffa80`32052060 _DEVICE_OBJECT

   +0x030 OriginalIrp      : 0xfffffa80`4f379440 _IRP

   +0x038 CompleteOriginalIrpWhenLastPacketCompletes : 0x1 ''

   +0x03c NumRetries       : 8

   +0x040 RetryTimer       : _KTIMER

   +0x080 RetryTimerDPC    : _KDPC

   +0x0c0 RetryIn100nsUnits : 0n0

   +0x0c8 SyncEventPtr     : (null)

   +0x0d0 DriverUsesStartIO : 0 ''

   +0x0d1 InLowMemRetry    : 0 ''

   +0x0d8 LowMemRetry_remainingBufPtr : (null)

   +0x0e0 LowMemRetry_remainingBufLen : 0

   +0x0e8 LowMemRetry_nextChunkTargetLocation : _LARGE_INTEGER 0x0

   +0x0f0 BufPtrCopy       : 0xfffffa80`40d79000  "RCRD("

   +0x0f8 BufLenCopy       : 0x1000

   +0x100 TargetLocationCopy : _LARGE_INTEGER 0xb5f8a000

   +0x108 SrbErrorSenseData : _SENSE_DATA

   +0x120 Srb              : _SCSI_REQUEST_BLOCK

   +0x178 UsePartialMdl    : 0 ''

   +0x180 PartialMdl       : 0xfffffa80`3bfda010 _MDL

   +0x188 RetryHistory     : (null)

   +0x190 RequestStartTime : 0

 

Now we can view the irp that classpnp sent to the lower level drivers and determine what it is doing.

 

2: kd> !irp 0xfffffa80`3bb71570

Irp is active with 3 stacks 3 is current (= 0xfffffa80`3bb71688)

 Mdl=fffffa803c854090: No System Buffer: Thread 00000000:  Irp stack trace.  Pending has been returned

     cmd  flg cl Device   File     Completion-Context

 [  0, 0]   0  2 00000000 00000000 00000000-00000000

 

                        Args: 00000000 00000000 00000000 ffffffffc0000185

>[  f, 0]  1c  0 fffffa8031dcc060 00000000 fffff8800107d1a0-fffffa80413ec4c0

               \Driver\elxstor  mpio!MPIOPdoCompletion

                        Args: fffffa80399ad700 00000000 00000000 fffffa80413ec4c0

 [  f, 0]  1c e1 fffffa8031dcc060 00000000 fffff88001d61a00-fffffa80399ad5e0 Success Error Cancel pending

               \Driver\mpio     CLASSPNP!TransferPktComplete

                        Args: fffffa80399ad700 00000000 00000000 fffffa80413ec4c0

 

We can see that the request has been sent to the disk driver.  More specifically the request has been sent to the storport miniport driver elxstor.  From this data we can usually assume that the request has been sent to the disk drive and we are waiting for the disk to respond.  There may be conditions where the request is stuck in storport, or in the miniport, however those conditions are beyond the scope of this article.

 

As you can see, there are several drivers between the disk.sys mini class driver and the actual physical disk drive.  It is often necessary to determine how far down the storage driver stack a request has been before you can determine where it is stuck.

Fixing an ICorDebugUnmanagedCallback induced hang

$
0
0

Hi debuggers, Andrew Richards here with a NTDebugging post that is a little different to what is usually posted.  Instead of talking about debugging, I’m going to talk about an issue I just faced while writing a debugger.

 

This debugger work is an extension of an upcoming article that I’ve written for MSDN Magazine (scheduled for the December 2011 issue). The MSDN Magazine article goes over how to write a native debugger using the DbgHelp API. It also explains how you can use this code to then make a plugin for Sysinternals ProcDump.

 

When debugging a managed application, you can take debugging one step further by being both a managed and unmanaged (native) debugger. To do this, you use the CLR Debugger API instead of the DbgHelp API.

 

What prompted this post was an issue that I hit while implementing the ICorDebugUnmanagedCallback::DebugEvent function of my unmanaged interface implementation. I was finding that the target process was hung after I processed in-band debug events but not out-of-band debug events. This was despite calling ICorDebugController::Continue, with or without calling ICorDebugProcess::ClearCurrentException first.

 

ICorDebug Interface:

Firstly, let’s take a step back and look at what it takes to get to the point of my issue. The goal in the initialization code is to get an instance of an ICorDebug based object.

 

Below is an abridged version of the code to do this using .NET 4.0; I have omitted the error handling and some of the cleanup (IUnknown::Release) to keep the code brief.

 

// Start COM

CoInitialize(NULL);

 

// Get a ICLRMetaHost instance (from .NET 4.0)

ICLRMetaHost* pCLRMetaHost = NULL;

CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*)&pCLRMetaHost);

 

// Get an enumeration of the loaded runtimes in the target process (opened prior with OpenProcess)

IEnumUnknown* pEnumUnknown = NULL;

pCLRMetaHost->EnumerateLoadedRuntimes(hProcess, &pEnumUnknown);

 

// Use the first runtime found (Note, you can only debug one runtime at once)

IUnknown* pUnknown = NULL;

ULONG ulFetched = 0;

pEnumUnknown->Next(1, &pUnknown, &ulFetched);

 

// QueryInterface for the ICLRRuntimeInfo interface

ICLRRuntimeInfo* pCLRRuntimeInfo = NULL;

pUnknown->QueryInterface(__uuidof(ICLRRuntimeInfo), (void **)&pCLRRuntimeInfo);

 

// Get the ICorDebug interface (this allows you to debug .NET 2.0 targets with the .NET 4.0 API)

pCLRRuntimeInfo->GetInterface(CLSID_CLRDebuggingLegacy, IID_ICorDebug, (void **)&pCorDebug);

 

// Initialize the .NET 2.0 debugging interface

pCorDebug->Initialize();

 

// Allocate our ICorDebugManagedCallback2 implementation and apply it to ICorDebug

CCorDebugManagedCallback2* pCorDebugManagedCallback2 = new CCorDebugManagedCallback2();

pCorDebug->SetManagedHandler((ICorDebugManagedCallback*)pCorDebugManagedCallback2);

 

// Allocate our ICorDebugUnmanagedCallback implementation and apply it to ICorDebug

CCorDebugUnmanagedCallback* pCorDebugUnmanagedCallback = new CCorDebugUnmanagedCallback();

pCorDebug->SetUnmanagedHandler((ICorDebugUnmanagedCallback*)pCorDebugUnmanagedCallback);

 

// Start debugging the process; returns the ICorDebugProcess we’ll need in the callbacks

pCorDebug->DebugActiveProcess(nProcessId, TRUE, &pCorDebugProcess);

 

This code is pretty linear; if any call fails you are out of luck.  By the end, you have associated your own managed and unmanaged callback classes with the ICorDebug object and are attached as a debugger. The code supports a target process using any of the.NET versions (v1.0, v1.1, v2.0, v4.0). Note that .NET v3.0 and v3.5 applications are actually v2.0 applications from a debugger point-of-view as these .NET releases just contain additional class libraries.

 

My managed callback implementation supports the IUnknown, ICorDebugManagedCallback and ICorDebugManagedCallback2 interfaces. (I’m not going to discuss this code here).

 

My unmanaged callback implementation supports the IUnknown and ICorDebugUnmanagedCallback interfaces. It is in this class that I had the issue.

 

ICorDebugUnmanagedCallback Interface:

The ICorDebugUnmanagedCallback interface has just one function:

 

HRESULT DebugEvent (

    [in] LPDEBUG_EVENT  pDebugEvent,

    [in] BOOL           fOutOfBand

);

 

The function provides a DEBUG_EVENT structure in the same way that WaitForDebugEvent does. This is not surprising as under the covers, that is what the .NET 4.0 API is using – it is just passing it to us. As such, the rules for handling a DEBUG_EVENT structure apply here too.  Namely, close the handle passed with the CREATE_PROCESS_DEBUG_EVENT and LOAD_DLL_DEBUG_EVENT events.

 

Following the DebugEvent documentation, I ended up with (roughly) the code below – which hangs the target process.

 

STDMETHODIMP CCorDebugUnmanagedCallback::DebugEvent(LPDEBUG_EVENT pDebugEvent, BOOL fOutOfBand)

{

      BOOL bClear = TRUE;

      switch (pDebugEvent->dwDebugEventCode)

      {

      case EXCEPTION_DEBUG_EVENT:

            if (pDebugEvent->u.Exception.dwFirstChance != 0)

                  bClear = FALSE;

            break;

      case CREATE_PROCESS_DEBUG_EVENT:

            if (pDebugEvent->u.CreateProcessInfo.hFile)

                  CloseHandle(pDebugEvent->u.CreateProcessInfo.hFile);

            break;

      case LOAD_DLL_DEBUG_EVENT:

            if (pDebugEvent->u.LoadDll.hFile)

                  CloseHandle(pDebugEvent->u.LoadDll.hFile);

            break;

      }

      if (bClear)

            pCorDebugProcess->ClearCurrentException(pDebugEvent->dwThreadId);

 

      pCorDebugProcess->Continue(fOutOfBand);

      return S_OK;

}

 

If you know what to look for, the answer to the ‘hang’ issue is on the MSDN page:

 

You can call ICorDebugController::Continue only on a Win32 thread and only when continuing past an out-of-band event.

 

So what does this really mean?

 

What is means is that you must call ICorDebugController::Continue from any other thread than the one servicing the callback if the debug event is in-band (fOutOfBand == FALSE). The reason for this is to stop a race condition. In-band debug events can be interrupted by out-of-band debug events – that is, the DebugEvent function can be firing multiple times concurrently. By forcing the continuation on an alternate thread, the race condition is averted.

 

I’m being brief here (on purpose) as I don’t want to incorrectly dissect for you the extremely complex internals of the CLR. You just need to know that you must use another thread for the hang to be averted.

 

So what does the code look like now?  It’s something like this:

 

STDMETHODIMP CCorDebugUnmanagedCallback::DebugEvent(LPDEBUG_EVENT pDebugEvent, BOOL fOutOfBand)

{

      BOOL bClear = TRUE;

      switch (pDebugEvent->dwDebugEventCode)

      {

      case EXCEPTION_DEBUG_EVENT:

            if (pDebugEvent->u.Exception.dwFirstChance != 0)

                  bClear = FALSE;

            break;

      case CREATE_PROCESS_DEBUG_EVENT:

            if (pDebugEvent->u.CreateProcessInfo.hFile)

                  CloseHandle(pDebugEvent->u.CreateProcessInfo.hFile);

            break;

      case LOAD_DLL_DEBUG_EVENT:

            if (pDebugEvent->u.LoadDll.hFile)

                  CloseHandle(pDebugEvent->u.LoadDll.hFile);

            break;

      }

     

      if (bClear)

            pCorDebugProcess->ClearCurrentException(pDebugEvent->dwThreadId);

 

      if (fOutOfBand)

      {

            pCorDebugProcess->Continue(TRUE);

      }

      else

      {

            SetEvent(hEventContinueBegin);

            WaitForSingleEvent(hEventContinueDone, INFINITE);

      }

      return S_OK;

}

 

DWORD WINAPI CCorDebugUnmanagedCallbackThreadProc(LPVOID lpParameter)

{

      while (!bQuit)

      {

            switch (WaitForSingleObject(hEventContinueBegin, 1000))

            {

            case WAIT_OBJECT_0:

                  pCorDebugProcess->Continue(FALSE);

                  SetEvent(hEventContinueDone);

                  break;

            }

      }

      return 0;

}

 

For out-of-band debug events, nothing has changed; the ICorDebugProcess::Continue call is made locally.

For in-band debug events, an event is set to trigger the ICorDebugProcess::Continue on a dedicated thread. The dedicated thread sets an event to tell the callback thread that the Continue has been done.

 

Note that the above code is a massive simplification of what is actually required – there is a ton of code missing that passes all the interface pointers & handles around and to create & shutdown the thread at the correct time.

 

In-band vs. Out-of-band:

So what is the difference between In-band vs. Out-of-band debug events?

 

An out-of-band debug event causes all threads in the target process to suspend (it’s exactly the same as native debugger induced suspend). As such, it is not possible to use the managed debugging interfaces to gather information from the target – as the managed debugging thread is suspended.

 

An in-band debug event only causes the managed threads in the target process to suspend – the managed debugging thread is still running. As such, it is possible to use the managed debugging interfaces to gather information from the target.

 

The act of using the managed debugging thread from within an in-band debug event can cause an out-of-band debug event (the common examples being first chance exceptions).

 

Cleanup/Detach:

Just to be complete, below is the code to cleanup and (optionally) detach from the ICorDebug session. In .NET 4.0, the ICorDebugController::Detach will terminate the process if interop debugging (passing TRUE to ICorDebug::DebugActiveProcess) is used. Interop debugging is not supported in .NET 2.0 on x64 - so this is less of an issue.

 

// If the target process is still running, we need to detach.

if (bDetachNeeded)

{

      ICorDebugController* pCorDebugController = NULL;

      pCorDebugProcess->QueryInterface(__uuidof(ICorDebugController), (void**)&pCorDebugController);

      pCorDebugController->Stop(INFINITE /* Note: Value is ignored – always INFINITE */);

      pCorDebugController->Detach();

      pCorDebugController->Release();

}

pCorDebug->SetUnmanagedHandler(NULL);

pCorDebugUnmanagedCallback->Release();

 

pCorDebug->SetManagedHandler(NULL);

pCorDebugManagedCallback2->Release();

 

pCorDebug->Terminate();

 

pCorDebug->Release();

 

CoUninitialize();

 

There is still quite a big bit of code required to implement the debugger completely.

 

You’ll need an ICorDebugManagedCallback implementation that handles process exiting, attaching to an application domain (ICorDebugAppDomain::Attach), handling name changes, and continuation.

 

Plus, if you want to support .NET 2.0 debugging without .NET 4.0 installed, you’ll need to use LoadLibrary/GetProcAddress to call .NET 4.0 (optionally), and fall back to the .NET 2.0 GetVersionFromProcess and CreateDebuggingInterfaceFromVersion functions.

 

Conclusion:

The CLR Debugging API is not for the faint at heart.  There are numerous pitfalls when using the ICorDebug interface against different versions of the CLR, different versions of Windows, different architectures, and with or without interop debugging.

 

If you have any questions about the API, post a comment here and I’ll do my best to answer them for you.

My Kernel Debugger Won't Connect

$
0
0

Hello ntdebugging readers, the Debug Ninja is back again with a quick blog this holiday season.  I recently encountered a situation where the kernel debugger could not connect to a Windows Server 2008 R2 system running in a Hyper-V virtual machine.  The configuration appeared correct; however, the debugger would not connect to the VM.

 

In windbg you can use Ctrl+Alt+D to view the debugger’s internal information flow.  In KD use Ctrl+D followed by ENTER to toggle the output.  Enabling this output I could see that the debugger was unable to read from the debug port, and that it was getting timeouts.  The error "SYNCTARGET: Timeout." is a clear indication that the debug host cannot communicate with the debug target, especially when this error appears after a “Send Break in” message.

SYNCTARGET: Timeout

 

Because I was using a named pipe on a Hyper-V VM I knew that I didn't have a bad cable, although this is a common cause of kernel debug failures.  I also knew that the configuration of the VM was correct, and I could use the debugger for other VMs on this server.  The problem was most likely with the OS running in the VM.

 

By checking Device Manager I was able to confirm that there was a problem with the configuration of the OS running in the VM.  The bcdedit settings were configured to use COM1, and this should make COM1 unavailable in the OS, however, COM1 was present in device manager.  For some reason the debugger was not capturing COM1 on boot as it was configured to.

Device Manager

 

Examining the bcd configuration of this server I found that the bcd configuration was not correct.  In the bcd store of normal Windows 7 or Windows Server 2008 R2 OS, the Windows Boot Loader sections of bcdedit have an inherit setting.  You can view this information on your system from an elevated command prompt using the command ‘bcdedit /enum all’.  Ordinarily the Windows Boot Loader inherits the {bootloadersettings}, the {bootloadersettings} inherit the {globalsettings}, and the {globalsettings} inherit the {dbgsettings}.  Without the inherit settings, the debugger configuration will not be read by the boot loader.

 

Below are the bcd settings from the broken VM.  You can see that all of the normal inherited settings are missing.

C:\Windows\system32>bcdedit /enum all

 

Windows Boot Manager

--------------------

identifier              {bootmgr}

device                  partition=C:

path                    \bootmgr

description             Windows Boot Manager

locale                  en-US

default                 {current}

displayorder            {current}

timeout                 30

 

Windows Boot Loader

-------------------

identifier              {current}

device                  partition=C:

path                    \Windows\system32\winload.exe

description             Windows Server 2008 R2 Standard (recovered)

locale                  en-US

osdevice                partition=C:

systemroot              \Windows

resumeobject            {2ec5363f-2a92-11e1-bbe4-806e6f6e6963}

usefirmwarepcisettings  No

debug                   Yes

 

Resume from Hibernate

---------------------

identifier              {2ec5363f-2a92-11e1-bbe4-806e6f6e6963}

device                  partition=C:

path                    \Windows\system32\winresume.exe

description             Windows Server 2008 R2 Standard (recovered)

locale                  en-US

inherit                 {resumeloadersettings}

filedevice              partition=C:

filepath                \hiberfil.sys

debugoptionenabled      Yes

 

Windows Memory Tester

---------------------

identifier              {memdiag}

device                  partition=C:

path                    \boot\memtest.exe

description             Windows Memory Diagnostic

locale                  en-US

 

Debugger Settings

-----------------

identifier              {dbgsettings}

debugtype               Serial

debugport               1

baudrate                115200

 

Because my only interest in this VM was to get the debugger working, I did not add all of the missing settings to the bcd store.  I was able to force the debugger configuration to be read on boot using this command:

bcdedit /set inherit {dbgsettings}

 

I hope this helps the next time you are trying to configure a debugger and it does not work.  Remember that we don't just need the debugger to be turned on and be configured; we need the settings to be inherited as well.


Configuring a Hyper-V VM For Kernel Debugging

$
0
0

Yesterday's blog prompted some questions about how to set up a debugger for a Windows OS running in a Hyper-V VM.  I was surprised that I wasn't able to find good, publicly available, Microsoft issued documentation for this configuration.

 

The first step is to configure the Windows OS in the VM to enable a kernel debugger on COM1.  One would use these same steps if you were preparing the OS to be debugged using a null modem cable.  Hyper-V will allow us to redirect the COM port so that we don't need such a cable.

  1. Start an administrative command prompt.
  2. Turn on debugging with this command:
    bcdedit /debug on
  3. Configure the debugger to use COM1 with this command:
    bcdedit /dbgsettings SERIAL DEBUGPORT:1 BAUDRATE:115200
    Note that these are the default settings and already exist in most bcd stores.  However setting them again won't damage anything, and guards against a situation where the dbgsettings have been previously modified.
  4. Reboot so that the boot loader can read the new settings and configure the OS for debugging.

CommandPrompt

 

Next, configure Hyper-V to redirect the COM1 port to a named pipe.  We will use this pipe in place of a traditional null modem cable.

  1. Open Hyper-V Manager and browse to the settings page of the VM you configured to debug.
  2. Under the Hardware list choose COM 1.
  3. Change the Attachment to 'Named pipe:' and provide a pipe name.
    1. Note that the Hyper-V Manager provides the complete path to your named pipe.  Make a note of this path as you will need it in the next step.

Settings2

 

After the OS and the VM are configured for debugging, we need to connect a debugger.

  1. On the Hyper-V parent partition download and install the Debugging Tools for Windows from http://msdn.microsoft.com/en-us/windows/hardware/gg463009.
  2. After installing the debugging tools you will have a ‘Debugging Tools for Windows’ entry in your start menu.
    1. From this folder right click ‘WinDbg’ and choose ‘Run as administrator’.  Windbg needs administrative rights to connect to the pipe.
  3. In windbg open the File menu and choose ‘Kernel Debug’.
  4. Enter a Baud Rate of 115200, to match the settings made in the VM.
  5. Enter the Port that you configured in the VM settings page.
    1. To connect to the pipe remotely, substitute the '.' in the path with the Hyper-V server name.
  6. Ensure that the Pipe and Reconnect boxes are checked.
  7. Set Resets to 0.
  8. Click OK to start debugging.
  9. Windbg should display the string ' Waiting to reconnect...'

image004

 

To test the debugger connection in windbg, from the ‘Debug’ menu choose ‘Break’.  This should cause the server to break into the debugger and display a kd> prompt.  Please note that breaking into the debugger will cause the OS running in the VM to halt until you tell the debugger to go, the OS will appear to be hung during this time.  The command 'g' followed by Enter will tell the debugger to ‘go’ causing the VM to resume operation.

 

Windbg

Stop 0x19 in a Large Pool Allocation

$
0
0

Hello all, Scott Olson here again to share another interesting issue I recently debugged with pool corruption and found that using special pool does not work with large pool allocations (pool allocations greater than a PAGE_SIZE).

 

Here is an example of a valid large page allocation. Notice the size is 0x1fb0 and a PAGE_SIZE is 0x1000 or 4kb.

 

0: kd> !pool fffffa80`0dba6fa0

Pool page fffffa800dba6fa0 region is Nonpaged pool

*fffffa800dba5000 : large page allocation, Tag is Io  , size is 0x1fb0 bytes

                Pooltag Io   : general IO allocations, Binary : nt!io

 

In Windows 7, at the end of the large pool allocation it will have an allocation tag of “Frag” then a “Free” tag with the rest of the page size and is stored on the free pool list for allocation less than a page in size.

 

0: kd> dc fffffa800dba5000 fffffa800dba5000+0x1fb0-4

fffffa80`0dba5000  00558001 32373242 00000000 00000000  ..U.B272........

fffffa80`0dba5010  55555555 55555555 98764321 01b75f55  UUUUUUUU!Cv.U_..

fffffa80`0dba5020  00000001 00000001 704e6ff0 fffff981  .........oNp....

…<cut>

fffffa80`0dba6f80  55555555 55555555 55555555 55555555  UUUUUUUUUUUUUUUU

fffffa80`0dba6f90  55555555 55555555 55555555 55555555  UUUUUUUUUUUUUUUU

fffffa80`0dba6fa0  55555555 55555555 00001fb0 00000000  UUUUUUUU........

0: kd> dc

fffffa80`0dba6fb0  02010100 67617246 55555555 55555555  ....FragUUUUUUUU

fffffa80`0dba6fc0  00040101 65657246 55555555 55555555  ....FreeUUUUUUUU

fffffa80`0dba6fd0  00802170 fffff880 0e49cf70 fffffa80  p!......p.I.....

fffffa80`0dba6fe0  15cc8fe8 fffff981 3b9c50a7 00000005  .........P.;....


Displayed with the !pool command:

0: kd> !pool fffffa80`0dba6fb0

Pool page fffffa800dba6fb0 region is Nonpaged pool

*fffffa800dba6fb0 size:   10 previous size:    0  (Allocated) *Frag

                Owning component : Unknown (update pooltag.txt)

 fffffa800dba6fc0 size:   40 previous size:   10  (Free)       Free

 

The example above demonstrates how this normally works.  The downside to this architecture is that if a driver were to overrun its pool allocation then special pool would not be useful because the large pool allocation has to be page-aligned. Special pool detects pool overruns by putting the data at the end of the page, which would not be feasible with a large pool allocation.

 

In Windows 7 there is a check while freeing the pool memory that will determine if this allocation had written past the end of its allocation, and if so will bug check the machine with a Stop 0x19 BAD_POOL_HEADER with the first parameter being a 0x21.  Here is the definition along with what each parameter means:

 

BAD_POOL_HEADER (19)

The pool is already corrupt at the time of the current request.

This may or may not be due to the caller.

The internal pool links must be walked to figure out a possible cause of

the problem, and then special pool applied to the suspect tags or the driver

verifier to a suspect driver.

Arguments:

Arg1: 0000000000000021, the data following the pool block being freed is corrupt.  Typically this means the consumer (call stack ) has overrun the block.

Arg2: fffffa800dc57000, The pool pointer being freed.

Arg3: 0000000000002180, The number of bytes allocated for the pool block.

Arg4: 006b0072006f0077, The corrupted value found following the pool block.

 

Here is an example of what this corruption looks like compared to the above valid large pool allocation:

0: kd> !pool fffffa800dc57000

Pool page fffffa800dc57000 region is Nonpaged pool

fffffa800dc57000 is not a valid large pool allocation, checking large session pool...

fffffa800dc57000 is freed (or corrupt) pool

Bad allocation size @fffffa800dc57000, zero is invalid

 

***

*** An error (or corruption) in the pool was detected;

*** Attempting to diagnose the problem.

***

*** Use !poolval fffffa800dc57000 for more details.

 

 

Pool page [ fffffa800dc57000 ] is __inVALID.

 

Analyzing linked list...

[ fffffa800dc57000 ]: invalid previous size [ 0x38 ] should be [ 0x0 ]

 

 

Scanning for single bit errors...

 

None found

 

Next, I dump the allocation from the start to the end.  Notice the size of the allocation is stored in the bugcheck code as argument 3.

 

0: kd> dc fffffa800dc57000 fffffa800dc57000+2180-4

fffffa80`0dc57000  00000038 0000000e 00000000 00000000  8...............

fffffa80`0dc57010  a24da497 01ccc5d6 c827993c 41946d1f  ..M.....<.'..m.A

fffffa80`0dc57020  c0d75c9b b7cff1a5 00000000 00000020  .\.......... ...

fffffa80`0dc57030  000021e0 00000006 0000006c 00000110  .!......l.......

fffffa80`0dc57040  00000208 000003b8 00000208 00000660  ............`...

fffffa80`0dc57050  00000208 00000910 00000208 00000bb0  ................

<cut>

fffffa80`0dc59150  002d0033 00300031 0063002e 006d006f  3.-.1.0...c.o.m.

fffffa80`0dc59160  006c002e 00660065 00680074 006e0061  ..l.e.f.t.h.a.n.

fffffa80`0dc59170  006e0064 00740065 006f0077 006b0072  d.n.e.t.w.o.r.k.

 

This should be the end of the allocation.  The next thing we see should be the “Frag” and “Free” tags.

 

0: kd> dc

fffffa80`0dc59180  003a0073 0061006d 0061006e 00650067  s.:.m.a.n.a.g.e.

fffffa80`0dc59190  0065006d 0074006e 0038003a 00390036  m.e.n.t.:.8.6.9.

fffffa80`0dc591a0  0062003a 00670069 0075006c 00790063  :.b.i.g.l.u.c.y.

fffffa80`0dc591b0  0064002d 00740061 002d0061 006e0069  -.d.a.t.a.-.i.n.

fffffa80`0dc591c0  00650064 00650078 002d0073 00740063  d.e.x.e.s.-.c.t.

fffffa80`0dc591d0  006c0072 0031005f 00000031 00000000  r.l._.1.1.......

 

We clearly see that the Frag and Free tag have been overwritten with some string value which is causing the corruption.  At this point, you would need to look at the current stack to determine which driver had allocated the memory, and review the code to investigate when this corruption could have occurred.

Identifying Global Atom Table Leaks

$
0
0

Hi, it's the Debug Ninja back again with another debugging adventure.  Recently I have encountered several instances where processes fail to initialize, and a review of available resources showed that there was no obvious resource exhaustion.  A more in depth review found that there were no available string atoms in the global atom table.

 

Global atoms are organized on a per-session basis.  If atoms cannot be allocated in session 0, services may fail to start or processes launched by various services may fail to start.  However, a user logged in to a different session will not experience any such failures.

 

String atoms are numbered from 0xC000 through 0xFFFF, providing a maximum of 0x4000 atoms per session.  For more information on atoms, and atom tables, see http://technet.microsoft.com/en-us/query/ms649053.

 

When there are no more string atoms available, calls to APIs that allocate string atoms will fail.  Because atoms are often allocated at process or dll init time, the most common symptom is that processes fail to initialize.  The process may cleanly exit without an error.  You are likely experiencing this problem if you debug your application and find that the failure originates from an API that allocates string atoms such as RegisterClass, RegisterClassEx, GlobalAddAtom, or AddAtom.

 

To determine if the global string atom table is full you will need to perform a kernel debug.  This can be a live debug or a post-mortem debug using a dump.

 

First identify the session where the failures have occurred and set the process context to a process in this session.  In my example, w3wp.exe was launching a process and this process failed to initialize.

 

2: kd> !process 0 0 w3wp.exe

PROCESS fffffa8005083060

    SessionId: 0  Cid: 1668    Peb: fffdf000  ParentCid: 08ec

    DirBase: 8a2df000  ObjectTable: fffff8a0128bbe40  HandleCount: 441.

    Image: w3wp.exe

2: kd> .process /p /r fffffa8005083060

Implicit process is now fffffa80`05083060

Loading User Symbols

.....

 

Next we need to analyze the global atom table.  The pointer to the table is stored in the UserAtomTableHandle global.

 

2: kd> dq win32k!UserAtomTableHandle l1

fffff960`003bf7a8  fffff8a0`05e5bc70

 

The UserAtomTableHandle has a pointer to a handle table at offset 0x10 in 64-bit, and offset 0x8 in 32-bit.  Note that although the atom table is defined as a _RTL_ATOM_TABLE, the format shown by dt is for user mode and does not apply to the UserAtomTableHandle in kernel mode.

 

2: kd> dq fffff8a0`05e5bc70+10 l1

fffff8a0`05e5bc80  fffff8a0`05db7740

2: kd> dt nt!_HANDLE_TABLE fffff8a0`05db7740

   +0x000 TableCode        : 0xfffff8a0`109c8001

   +0x008 QuotaProcess     : (null)

   +0x010 UniqueProcessId  : 0x00000000`00000184 Void

   +0x018 HandleLock       : _EX_PUSH_LOCK

   +0x020 HandleTableList  : _LIST_ENTRY [ 0xfffff8a0`05db7760 - 0xfffff8a0`05db7760 ]

   +0x030 HandleContentionEvent : _EX_PUSH_LOCK

   +0x038 DebugInfo        : (null)

   +0x040 ExtraInfoPages   : 0n0

   +0x044 Flags            : 0

   +0x044 StrictFIFO       : 0y0

   +0x048 FirstFreeHandle  : 0x10004

   +0x050 LastFreeHandleEntry : 0xfffff8a0`10ca4ff0 _HANDLE_TABLE_ENTRY

   +0x058 HandleCount      : 0x3fc0

   +0x05c NextHandleNeedingPool : 0x10400

   +0x060 HandleCountHighWatermark : 0x3fc1

 

The FirstFreeHandle contains the handle number that will be given to the next handle allocated from this table.  This value is encoded, to get the next handle number we need to right shift the FirstFreeHandle by 2 bits.

 

2: kd> ?00010004>>2

Evaluate expression: 16385 = 00000000`00004001

 

The result from above, 0x4001, is greater than the number of possible string atoms.  As I mentioned earlier, there is a limit of 0x4000 string atoms.  Now we know that the session is out of string atoms.

 

The next step is to dump the string atoms to identify whether there is an observable pattern in the leaked strings.  The !atom command only works in user mode, so we need to dump the kernel mode strings manually.  An atom table is comprised of multiple buckets.   Each bucket is the head of a list of atoms.  The buckets start at offset 0x20 in the atom table in 64-bit, and offset 0x10 in 32-bit.

 

2: kd> dq fffff8a0`05e5bc70+20

fffff8a0`05e5bc90  fffff8a0`05e5ba60 fffff8a0`05db7be0

fffff8a0`05e5bca0  fffff8a0`08cf1770 fffff8a0`05e5b3d0

fffff8a0`05e5bcb0  fffff8a0`05ea9020 fffff8a0`05e5b8e0

fffff8a0`05e5bcc0  fffff8a0`05ea9b10 fffff8a0`05ea9910

fffff8a0`05e5bcd0  fffff8a0`05ea9f00 fffff8a0`05e5b650

fffff8a0`05e5bce0  fffff8a0`05cda290 fffff8a0`05ea9e80

fffff8a0`05e5bcf0  fffff8a0`05e5b200 fffff8a0`05ea9e30

fffff8a0`05e5bd00  fffff8a0`05e5b7e0 fffff8a0`06c56210

2: kd> dq

fffff8a0`05e5bd10  fffff8a0`06d6b5a0 fffff8a0`05ea9d50

fffff8a0`05e5bd20  fffff8a0`05e5b790 fffff8a0`05e5b9d0

fffff8a0`05e5bd30  fffff8a0`06bd9bc0 fffff8a0`05ea9c90

fffff8a0`05e5bd40  fffff8a0`05e5b0c0 fffff8a0`06ae2020

fffff8a0`05e5bd50  fffff8a0`05e5b930 fffff8a0`04d2af40

fffff8a0`05e5bd60  fffff8a0`05e5b690 fffff8a0`05e5b980

fffff8a0`05e5bd70  fffff8a0`05e5b490 fffff8a0`05e5b410

fffff8a0`05e5bd80  fffff8a0`05e5ba20 fffff8a0`05e5b4f0

2: kd> dq

fffff8a0`05e5bd90  fffff8a0`05e5baa0 fffff8a0`05e5b390

fffff8a0`05e5bda0  fffff8a0`05e5b840 fffff8a0`05ea9c50

fffff8a0`05e5bdb0  fffff8a0`05e5b250 00000000`00000000

fffff8a0`05e5bdc0  00000000`00000000 00000000`00000000

fffff8a0`05e5bdd0  00000000`00000000 00000000`00000000

fffff8a0`05e5bde0  00000000`00000000 00000000`00000000

fffff8a0`05e5bdf0  00000000`00000000 00000000`00000000

fffff8a0`05e5be00  00000000`00000000 00000000`00000000

 

The quick and dirty way to dump the buckets is with !list.  I am sure that some will say it is tedious to dump each bucket list by hand and that there are easier ways to accomplish this.  To prevent this article from becoming a lesson on debugger scripting, I am leaving that as an exercise to the reader.

 

2: kd> !list "-t nt!_RTL_ATOM_TABLE_ENTRY.HashLink -e -x \"du @$extret+10\" fffff8a0`05e5ba60"

du @$extret+10

fffff8a0`05e5ba70  "Native"

 

<snip strings that don't match a pattern>

 

du @$extret+10

fffff8a0`0838a120  "ControlOfs0210000000000700"

 

du @$extret+10

fffff8a0`0f7ff430  "ControlOfs021A000000000C30"

 

du @$extret+10

fffff8a0`162168c0  "ControlOfs020E000000001774"

 

du @$extret+10

fffff8a0`08c33870  "ControlOfs01F70000000007F4"

 

du @$extret+10

fffff8a0`07c46910  "ControlOfs0202000000000BF8"

 

du @$extret+10

fffff8a0`062aab50  "ControlOfs01F5000000001274"

 

du @$extret+10

fffff8a0`0777b150  "ControlOfs0202000000000C80"

 

du @$extret+10

fffff8a0`07dd3410  "ControlOfs0207000000000F00"

 

du @$extret+10

fffff8a0`0f01d190  "ControlOfs0214000000000DAC"

 

Dumping the atoms I found that there is a continuous pattern of the string ControlOfs followed by 16 hexadecimal numbers.  Some time spent with your favorite search engine should find other reports of atom leaks involving the string ControlOfs, and that these leaks have been identified as a problem in some specific software.  In this instance the programmer using that software needs to change their application to avoid the problem.

What Should Never Happen... Did

$
0
0

Hi, this is Bob Golding; I wanted to write a blog about an interesting hardware issue I ran into. Hardware problems can be tricky to isolate. I recently came across one that I thought was interesting and gave an example of how to trace code execution.  The machine executed the filler “int 3” instructions generated by the compiler.  Execution should never reach these filler instructions, so we needed to determine how the instruction pointer got there.

 

What was the issue?

The issue was a bug check 8E (unhandled exception).  The exception was a debug exception (80000003), because a filler INT 3 instruction was executed.

 

1: kd> .bugcheck

Bugcheck code 0000008E

Arguments 80000003 8082e3e0 f78aec38 00000000

 

Below is the trap frame (the trap frame is the third argument in the bugcheck code). Note that the actual trapping instruction is 8082e3e0, the instruction pointer is incremented before the INT 3 generates a trap. The correct EIP is reported in the bug check values.

 

1: kd> .trap f78aec38

ErrCode = 00000000

eax=0e364a01 ebx=f78aed50 ecx=f78aecb8 edx=000000e1 esi=f7727ec0 edi=f75d9ca2

eip=8082e3e1 esp=f78aecac ebp=f78aecc0 iopl=0         nv up ei pl nz na po nc

cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00000202

nt!KiCheckForKernelApcDelivery+0x37:

8082e3e1 cc              int     3

 

The machine executed code at the end of a function that should never be executed.  This should never happen.

 

1: kd> ub 8082e3e1

nt!KiCheckForKernelApcDelivery+0x1c:

8082e3c6 32c9            xor     cl,cl

8082e3c8 ff2508118080    jmp     dword ptr [nt!_imp_KfLowerIrql (80801108)]

8082e3ce 64a124010000    mov     eax,dword ptr fs:[00000124h]

8082e3d4 c6403d01        mov     byte ptr [eax+3Dh],1

8082e3d8 ff2598108080    jmp     dword ptr [nt!_imp_HalRequestSoftwareInterrupt (80801098)]

8082e3de cc              int     3 <<< This is after the function.

8082e3df cc              int     3

8082e3e0 cc              int     3

 

OK, So Now What?

Now, we need to find the execution path that caused the machine to execute this INT 3. There are places to look to find clues that will tell us. The first place to start looking is the stack. If a “call” instruction was made, the return will be pushed on the stack. This way we can try to determine if we arrived at this bad instruction pointer from a call or a ret instruction.

 

Using the value from esp in the above trap frame, let’s dump the stack.

 

1: kd> dps f78aecac

f78aecac  80a5d8fc hal!HalpIpiHandler+0xcc <<< Interesting?

f78aecb0  f78aecc0

f78aecb4  00000000

f78aecb8  00000002

f78aecbc  000000e1

f78aecc0  f78aed50

f78aecc4  f75d9ca2

f78aecc8  badb0d00

f78aeccc  000086a8

 

In looking at the stack dump, we see that there may have been a call from HalpIpiHandler.  Let’s dump the code leading up to hal!HalpIpiHandler+0xcc to see what it did.

 

1: kd> ub 80a5d8fc

hal!HalpIpiHandler+0xb1:

80a5d8e1 e8d82b0000      call    hal!HalBeginSystemInterrupt (80a604be)

80a5d8e6 0ac0            or      al,al

80a5d8e8 7509            jne     hal!HalpIpiHandler+0xc3 (80a5d8f3)

80a5d8ea 83c408          add     esp,8

80a5d8ed ff2510b0a580    jmp     dword ptr [hal!_imp_Kei386EoiHelper (80a5b010)]

80a5d8f3 6a00            push    0

80a5d8f5 55              push    ebp

80a5d8f6 ff1520b0a580    call    dword ptr [hal!_imp__KiIpiServiceRoutine (80a5b020)]

 

In the above assembly, we can see that there is a call made using a pointer in the import table.  Now, let’s have a look at that pointer.

 

1: kd> dd 80a5b020 l 1

80a5b020 8082e3e4

 

The pointer is very close to the instruction we trapped on.  Is this a coincidence?

 

It looks like due to an effective address calculation failure, the machine starting executing at 8082e3e0 instead of 8082e3e4.  Somewhere in the data path the processor executing this instruction stream dropped bit three, turning a 4 into a 0.

 

1: kd> ?0y0100

Evaluate expression: 4 = 00000000`00000004

1: kd> ?0y0000

Evaluate expression: 0 = 00000000`00000000

 

What does all of this mean?

There is some circumstantial evidence here that the machine was in the IPI handler.  The IPI Handler is used in multiprocessor systems so that one processor may interrupt another.  So, how can we further prove this is where we were?  Let’s try to match the trap frame registers to the assembly from HalpIpiHandler before it calls KiIpiServiceRoutine.

 

1: kd> .trap f78aec38

ErrCode = 00000000

eax=0e364a01 ebx=f78aed50 ecx=f78aecb8 edx=000000e1 esi=f7727ec0 edi=f75d9ca2

eip=8082e3e1 esp=f78aecac ebp=f78aecc0 iopl=0         nv up ei pl nz na po nc

cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00000202

nt!KiCheckForKernelApcDelivery+0x37:

8082e3e1 cc              int     3

1: kd> ub 80a5d8fc l10

hal!HalpIpiHandler+0x94:

80a5d8c4 c74508000ddbba  mov     dword ptr [ebp+8],0BADB0D00h <<< EBP+ 8 does equal badb0d00

80a5d8cb 895d00          mov     dword ptr [ebp],ebx <<< ebp does equal ebx

 

80a5d8ce 897d04          mov     dword ptr [ebp+4],edi <<< ebp+4 does equal edi

80a5d8d1 68e1000000      push    0E1h

80a5d8d6 83ec04          sub     esp,4

80a5d8d9 54              push    esp

80a5d8da 68e1000000      push    0E1h

80a5d8df 6a1d            push    1Dh

80a5d8e1 e8d82b0000      call    hal!HalBeginSystemInterrupt (80a604be)

80a5d8e6 0ac0            or      al,al <<< al is equal to 1

80a5d8e8 7509            jne     hal!HalpIpiHandler+0xc3 (80a5d8f3)

80a5d8ea 83c408          add     esp,8

80a5d8ed ff2510b0a580    jmp     dword ptr [hal!_imp_Kei386EoiHelper (80a5b010)]

80a5d8f3 6a00            push    0

80a5d8f5 55              push    ebp

80a5d8f6 ff1520b0a580    call    dword ptr [hal!_imp__KiIpiServiceRoutine (80a5b020)]

 

Below is the stack data that the instructions above produced (borrowed from the dps output above). It matches, so the machine did execute the above instructions.

 

f78aecac  80a5d8fc  hal!HalpIpiHandler+0xcc <<< Return Pushed by call instruction

f78aecb0  f78aecc0 <<< Pushed EBP

f78aecb4  00000000 <<< Pushed a 0

 

Finally, the last bit of evidence:

Let's view the IPI state using the debugger command !ipi.  From this output we can see that processor 1 is the receiver of a cross interrupt from Processor 0.  This is consistent with the data we found on the stack.

 

1: kd> !ipi

IPI State for Processor 0

 

    As a sender, awaiting packet completion from processor 1.

 

    TargetSet             2  PacketBarrier         e  IpiFrozen      2 [Frozen]

    IpiFrame       8089a570  SignalDone     00000000  RequestSummary 0

    Packet State     Active                           WorkerRoutine  nt!KiFlushTargetMultipleTb

    Parameter[0]   00000000  Parameter[1]   80899f10  Parameter[2]   80899f04

 

IPI State for Processor 1

 

    As a receiver, the following unhandled requests are pending: [Packet] [DPC]

 

    TargetSet             0  PacketBarrier         0  IpiFrozen      0 [Running]

    IpiFrame       b5ad7be8  SignalDone     ffdff120  RequestSummary 2 [DPC]

    Packet State      Stale                           WorkerRoutine  nt!KiFlushTargetMultipleTb

    Parameter[0]   00000000  Parameter[1]   b5ad7950  Parameter[2]   b5ad7948

 

What went wrong?

Based on the evidence in this dump it appears that call instruction transferred execution to the wrong address.  The machine ended up executing at address 8082e3e0 instead of 8082e3e4, a single bit difference.  This same bit was flipped in several crashes from this machine, so all the evidence pointed to a faulty processor.  After replacing the processor we were running on when we bugchecked, the issue did not occur again.

 

Hardware can sometimes cause pretty specific failures, such as the flipped bit we see here.  To determine that this failure was a hardware issue, we had to reconstruct the execution path and trace how we ended up at the failing instruction.  We were able to match the register contents to what they would have been before the call to KiIpiServiceRoutine.  This demonstrated that the call should have been made to KiIpiServiceRoutine, but it unexpectedly went to the wrong address.

Debugging Backwards: Proving root cause

$
0
0

Matt Burrough here again.  On rare occasions when debugging, we'll actually know (or strongly suspect) what the root cause of a problem is at the beginning of our analysis - but we still need to investigate to confirm our assertion.  The following is a case study for an issue I worked on recently where the print spooler was hanging.

 

This customer had recently upgraded their print servers from Windows 2003 to Windows 2008 R2.  After the upgrade, the spooler would frequently go unresponsive, and no jobs could be printed.  Rebooting the server provided some relief, but the problem would reoccur as the jobs started coming in again.

 

One of my peers had completed an initial analysis of a user mode memory dump of the spooler process, and found that spooler seemed to be blocked waiting on PrintIsolationHost.exe.  For those not familiar with recent changes to the print spooler's design - Print Isolation Host was added in Windows 7/2008 R2 as a way to isolate print drivers from each other and from the spooler process so a crash in one driver doesn't take down the entire printing environment.  Given the large number of print drivers found on some print servers, this can be a great help for stability and availability of the spooler.  See http://blogs.technet.com/b/askperf/archive/2009/10/08/windows-7-windows-server-2008-r2-print-driver-isolation.aspx if you would like more details on Print Isolation Host.

 

Unfortunately for my team mate, the data collected did not include dumps of Print Isolation Host, so he requested that the next time the problem happened that both spooler and PrintIsolationHost dumps would be gathered.  The customer had configured his server for the "Isolated" Print Isolation Host option during troubleshooting, which places each driver in its own process.  (The default is shared which places all drivers in one PrintIsolationHost.exe instance.  Driver isolation is configured using the Print Management Console.)

 

The next morning, the newly requested data came in, and since the problem was urgent, I began looking at the new dumps immediately.  This dataset included both a spoolsv.exe dump, as well as nearly two dozen PrintIsolationHost dumps!  I knew from the past analysis that I should start with the PrintIsolationHost data - but where to begin?  In order to triage the dumps, I wrote a small batch file to open each dump (luckily, they were sequentially numbered), write the call stacks in each thread to a file, and close the log.  On every iteration, the script created a new cmd.txt file, which contained a set of commands that were passed to the debugger.  This allowed me to name the debugger output files sequentially with names that matched their dump (e.g. PrintIsolationHost1.txt contained output from PrintIsolationHost1.dmp).

 

set x=1

:Top

echo .reload > c:\data\cmd.txt

echo .logopen c:\data\PrintIsolationHost%x%.txt  >> c:\data\cmd.txt

echo ~*kc >> c:\data\cmd.txt

echo .logclose >> c:\data\cmd.txt

echo qq >> c:\data\cmd.txt

c:\debuggers\kd.exe -cf c:\data\cmd.txt -z "c:\data\PrintIsolationHost%x%.DMP"

set /A x+=1

IF %x% LEQ 23 GOTO Top

 

Now that I had a directory full of text files, I used my favorite differencing tool to compare the stacks in each text file.  I used the first PrintIsolationHost file as a reference.  It only had four stacks, and these were common to all of the other files:

 

.  0  Id: 40c.1c28 Suspend: 0 Teb: 000007ff`fffde000 Unfrozen

Call Site

user32!ZwUserGetMessage

user32!GetMessageW

PrintIsolationHost!ATL::CAtlExeModuleT<Host>::Run

PrintIsolationHost!Host::RunUsingConfiguration

PrintIsolationHost!wWinMain

PrintIsolationHost!__wmainCRTStartup

kernel32!BaseThreadInitThunk

ntdll!RtlUserThreadStart

 

   1  Id: 40c.23c0 Suspend: 0 Teb: 000007ff`fffdb000 Unfrozen

Call Site

ntdll!ZwWaitForMultipleObjects

ntdll!TppWaiterpThread

kernel32!BaseThreadInitThunk

ntdll!RtlUserThreadStart

 

   2  Id: 40c.2598 Suspend: 0 Teb: 000007ff`fffd3000 Unfrozen

Call Site

ntdll!ZwWaitForSingleObject

KERNELBASE!WaitForSingleObjectEx

PrintIsolationHost!Host::MonitorShutdown

PrintIsolationHost!Host::MonitorProc

kernel32!BaseThreadInitThunk

ntdll!RtlUserThreadStart

 

   3  Id: 40c.820 Suspend: 0 Teb: 000007ff`fffd9000 Unfrozen

Call Site

ntdll!ZwWaitForWorkViaWorkerFactory

ntdll!TppWorkerThread

kernel32!BaseThreadInitThunk

ntdll!RtlUserThreadStart

 

I was able to rule out a number of other PrintIsolationHost instances that were either identical to this one (aside from the process/thread IDs and Tebs), or which just had one or two additional idle worker threads (like thread 3 above).

 

Things got interesting when I looked at two of the PrintIsolationHost dumps.  Both had these two stacks not found in any other the other dumps (note that I do not have symbols for the 3rd-party print processor ProseWarePrintProcessor):

 

   2  Id: 20a4.1328 Suspend: 0 Teb: 000007ff`fffda000 Unfrozen

Call Site

ntdll!ZwWaitForSingleObject

ntdll!RtlpWaitOnCriticalSection

ntdll!RtlEnterCriticalSection

ntdll!LdrLockLoaderLock

*** ERROR: Symbol file could not be found.  Defaulted to export symbols for ProseWarePrintProcessor.dll -

KERNELBASE!GetModuleFileNameW

ProseWarePrintProcessor

ProseWarePrintProcessor

ProseWarePrintProcessor!ControlPrintProcessor

PrintIsolationProxy!sandbox::PrintProcessor::ControlPrintProcessor

rpcrt4!Invoke

rpcrt4!Ndr64StubWorker

rpcrt4!NdrStubCall3

ole32!CStdStubBuffer_Invoke

ole32!SyncStubInvoke

ole32!StubInvoke

ole32!CCtxComChnl::ContextInvoke

ole32!AppInvoke

ole32!ComInvokeWithLockAndIPID

ole32!ThreadInvoke

rpcrt4!DispatchToStubInCNoAvrf

rpcrt4!RPC_INTERFACE::DispatchToStubWorker

rpcrt4!RPC_INTERFACE::DispatchToStub

rpcrt4!RPC_INTERFACE::DispatchToStubWithObject

rpcrt4!LRPC_SCALL::DispatchRequest

rpcrt4!LRPC_SCALL::HandleRequest

rpcrt4!LRPC_ADDRESS::ProcessIO

rpcrt4!LrpcIoComplete

ntdll!TppAlpcpExecuteCallback

ntdll!TppWorkerThread

kernel32!BaseThreadInitThunk

ntdll!RtlUserThreadStart

 

   6  Id: 20a4.1668 Suspend: 0 Teb: 000007ff`fffac000 Unfrozen

Call Site

ntdll!ZwWaitForSingleObject

ntdll!RtlpWaitOnCriticalSection

ntdll!RtlEnterCriticalSection

ProseWarePrintProcessor

ProseWarePrintProcessor

ProseWarePrintProcessor

ntdll!LdrpInitializeThread

ntdll!_LdrpInitialize

ntdll!LdrInitializeThunk

 

Interesting.  Thread 6 is running the DllMain code for the ProseWarePrintProcessor DLL, which holds the loader lock. It is waiting on a critical section.  Meanwhile, thread 2 is waiting on the loader lock.  So who holds the critical section that thread 6 wants?  First, let's find what lock thread 6 wants:

 

0:006> kn

 # Child-SP          RetAddr           Call Site

00 00000000`0104eb18 00000000`777fe4e8 ntdll!ZwWaitForSingleObject+0xa

01 00000000`0104eb20 00000000`777fe3db ntdll!RtlpWaitOnCriticalSection+0xe8

02 00000000`0104ebd0 00000000`750c5d6b ntdll!RtlEnterCriticalSection+0xd1

03 00000000`0104ec00 00000000`750c6256 ProseWarePrintProcessor+0xabf

04 00000000`0104ec30 00000000`750c7015 ProseWarePrintProcessor+0xfaa

05 00000000`0104f090 00000000`777dc76c ProseWarePrintProcessor+0x1d69

06 00000000`0104f1f0 00000000`777dc42f ntdll!LdrpInitializeThread+0x17c

07 00000000`0104f2f0 00000000`777dc32e ntdll!LdrpInitialize+0x9f

08 00000000`0104f360 00000000`00000000 ntdll!LdrInitializeThunk+0xe

0:006> .frame /c /r 2

02 00000000`0104ebd0 00000000`750c5d6b ntdll!RtlEnterCriticalSection+0xd1

rax=0000000300d1001a rbx=00000000750ca330 rcx=00000000001d0000

rdx=0000000000000040 rsi=0000000000000001 rdi=0000000000000004

rip=00000000777fe3db rsp=000000000104ebd0 rbp=00000000ff000000

 r8=00000000002c6a00  r9=00000000002c6a10 r10=0000000000000073

r11=0000000000000001 r12=000007fffffd6000 r13=00000000778e2660

r14=0000000000000001 r15=000000007780a280

iopl=0         nv up ei pl zr na po nc

cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246

ntdll!RtlEnterCriticalSection+0xd1:

00000000`777fe3db 83f801          cmp     eax,1

0:006> ub 00000000`750c5d6b      <<<< Let's see what gets passed to RtlEnterCriticalSection

ProseWarePrintProcessor+0xaa2:

00000000`750c5d4e f9              stc

00000000`750c5d4f ff743f33        push    qword ptr [rdi+rdi+33h]

00000000`750c5d53 d2ff            sar     bh,cl

00000000`750c5d55 15aeb4ffff      adc     eax,0FFFFB4AEh

00000000`750c5d5a 85c0            test    eax,eax

00000000`750c5d5c 7533            jne     ProseWarePrintProcessor+0xae5 (00000000`750c5d91)

00000000`750c5d5e 488d0dcb450000  lea     rcx,[ProseWarePrintProcessor+0x5084 (00000000`750ca330)] <<<< Critical section is in rcx

00000000`750c5d65 ff15bdb3ffff    call    qword ptr [ProseWarePrintProcessor+0x1128 (00000000`750c1128)]

0:006> u ntdll!RtlEnterCriticalSection

ntdll!RtlEnterCriticalSection:

00000000`77802fc0 fff3            push    rbx

00000000`77802fc2 4883ec20        sub     rsp,20h

00000000`77802fc6 f00fba710800    lock btr dword ptr [rcx+8],0

00000000`77802fcc 488bd9          mov     rbx,rcx   <<<< Critical section is saved in RBX.  RBX isn't modified between here and our current position

00000000`77802fcf 0f83e9b1ffff    jae     ntdll!RtlEnterCriticalSection+0x31 (00000000`777fe1be)

00000000`77802fd5 65488b042530000000 mov   rax,qword ptr gs:[30h]

00000000`77802fde 488b4848        mov     rcx,qword ptr [rax+48h]

00000000`77802fe2 c7430c01000000  mov     dword ptr [rbx+0Ch],1

0:006> r rbx

Last set context:

rbx=00000000750ca330  <<<< This is the address of the critical section thread 6 is waiting for

 

Now let's look at the threads in this process, and the held locks:

 

0:002> ~

#  0  Id: 20a4.180c Suspend: 0 Teb: 000007ff`fffde000 Unfrozen

   1  Id: 20a4.2294 Suspend: 0 Teb: 000007ff`fffdc000 Unfrozen

.  2  Id: 20a4.1328 Suspend: 0 Teb: 000007ff`fffda000 Unfrozen

   3  Id: 20a4.1c34 Suspend: 0 Teb: 000007ff`fffd8000 Unfrozen

   4  Id: 20a4.5bc Suspend: 0 Teb: 000007ff`fffd4000 Unfrozen

   5  Id: 20a4.3c4 Suspend: 0 Teb: 000007ff`fffae000 Unfrozen

   6  Id: 20a4.1668 Suspend: 0 Teb: 000007ff`fffac000 Unfrozen

0:002> !locks

 

CritSec ntdll!LdrpLoaderLock+0 at 00000000778e7490

WaiterWoken        No

LockCount          2

RecursionCount     1

OwningThread       1668

EntryCount         0

ContentionCount    2

*** Locked

 

CritSec ProseWarePrintProcessor+5084 at 00000000750ca330

WaiterWoken        No

LockCount          1

RecursionCount     2

OwningThread       1328

EntryCount         0

ContentionCount    1

*** Locked

 

Scanned 201 critical sections

 

That's not good!  We can see that thread 6 indeed owns the loader lock, which thread 2 is waiting for.  But thread 2 owns the ProseWarePrintProcessor lock - and thread 6 is waiting for it!  This is a classic deadlock.  In fact, Raymond Chen even described this on his blog.  More information about LdrInitialize can be found here.

image002

 

So we know that there is a deadlock in the Print Isolation Host, but why exactly does this cause spooler to hang?  Here's where we work backwards.  We know that thread 6 is handling DLL initialization, but what is thread 2 doing?  From the stack we can see it is handling an RPC request that called into ProseWarePrintProcessor.  Let's determine who called into this thread.

 

0:002> kn

 # Child-SP          RetAddr           Call Site

00 00000000`0085dd88 00000000`777fe4e8 ntdll!ZwWaitForSingleObject+0xa

01 00000000`0085dd90 00000000`777fe3db ntdll!RtlpWaitOnCriticalSection+0xe8

02 00000000`0085de40 00000000`777db9e7 ntdll!RtlEnterCriticalSection+0xd1

03 00000000`0085de70 000007fe`fd963706 ntdll!LdrLockLoaderLock+0x6d

04 00000000`0085deb0 00000000`750c58f3 KERNELBASE!GetModuleFileNameW+0x96

05 00000000`0085df10 00000000`750c5d77 ProseWarePrintProcessor!InstallPrintProcessor+0x647

06 00000000`0085e380 00000000`750c51d9 ProseWarePrintProcessor!InstallPrintProcessor+0xacb

07 00000000`0085e3b0 000007fe`f96766b4 ProseWarePrintProcessor!ControlPrintProcessor+0x25

08 00000000`0085e3e0 000007fe`fe2f23d5 PrintIsolationProxy!sandbox::PrintProcessor::ControlPrintProcessor+0x34

09 00000000`0085e420 000007fe`fe39b68e rpcrt4!Invoke+0x65

0a 00000000`0085e470 000007fe`fe2f48d6 rpcrt4!Ndr64StubWorker+0x61b

0b 00000000`0085ea30 000007fe`fdd50883 rpcrt4!NdrStubCall3+0xb5

0c 00000000`0085ea90 000007fe`fdd50ccd ole32!CStdStubBuffer_Invoke+0x5b

0d 00000000`0085eac0 000007fe`fdd50c43 ole32!SyncStubInvoke+0x5d

0e 00000000`0085eb30 000007fe`fdc0a4f0 ole32!StubInvoke+0xdb

0f 00000000`0085ebe0 000007fe`fdd514d6 ole32!CCtxComChnl::ContextInvoke+0x190

10 00000000`0085ed70 000007fe`fdd5122b ole32!AppInvoke+0xc2

11 00000000`0085ede0 000007fe`fdd4fd6d ole32!ComInvokeWithLockAndIPID+0x52b

12 00000000`0085ef70 000007fe`fe2e50f4 ole32!ThreadInvoke+0x30d

13 00000000`0085f010 000007fe`fe2e4f56 rpcrt4!DispatchToStubInCNoAvrf+0x14

14 00000000`0085f040 000007fe`fe2e775b rpcrt4!RPC_INTERFACE::DispatchToStubWorker+0x146

15 00000000`0085f160 000007fe`fe2e769b rpcrt4!RPC_INTERFACE::DispatchToStub+0x9b

16 00000000`0085f1a0 000007fe`fe2e7632 rpcrt4!RPC_INTERFACE::DispatchToStubWithObject+0x5b

17 00000000`0085f220 000007fe`fe2e532d rpcrt4!LRPC_SCALL::DispatchRequest+0x422

18 00000000`0085f300 000007fe`fe302e7f rpcrt4!LRPC_SCALL::HandleRequest+0x20d

19 00000000`0085f430 000007fe`fe302a35 rpcrt4!LRPC_ADDRESS::ProcessIO+0x3bf

1a 00000000`0085f570 00000000`777cb68b rpcrt4!LrpcIoComplete+0xa5

1b 00000000`0085f600 00000000`777cfeff ntdll!TppAlpcpExecuteCallback+0x26b

1c 00000000`0085f690 00000000`776a652d ntdll!TppWorkerThread+0x3f8

1d 00000000`0085f990 00000000`777dc521 kernel32!BaseThreadInitThunk+0xd

1e 00000000`0085f9c0 00000000`00000000 ntdll!RtlUserThreadStart+0x1d

 

I know that the code in frame 19 deals with processing the RPC and has a record of the calling process' PID and TID.  In fact, from a bit of code review, I know that at this portion of the code, we get back a value that contains a ntdll!_CLIENT_ID structure at offset 8:

 

rpcrt4!LRPC_ADDRESS::ProcessIO+0xe6:

000007fe`fe302ba6 ff151c050a00    call    qword ptr [rpcrt4!_imp_AlpcGetMessageFromCompletionList (000007fe`fe3a30c8)]

000007fe`fe302bac 4885c0          test    rax,rax

000007fe`fe302baf 0f84d1020000    je      rpcrt4!LRPC_ADDRESS::ProcessIO+0x3c6 (000007fe`fe302e86)

000007fe`fe302bb5 4c8bac2488000000 mov     r13,qword ptr [rsp+88h]

000007fe`fe302bbd 41bc01000000    mov     r12d,1

000007fe`fe302bc3 33d2            xor     edx,edx

000007fe`fe302bc5 488bf8          mov     rdi,rax

000007fe`fe302bc8 41bfff000000    mov     r15d,0FFh

 

Reviewing the assembly, from ProcessIO+0xe6 to where we are now (ProcessIO+0x3bf), we don't modify rdi again - and rdi is nonvolatile - so if we switch to that frame and check out rdi+8, we'll know who called this thread!

 

0:002> .frame /c /r 19

19 00000000`0085f430 000007fe`fe302a35 rpcrt4!LRPC_ADDRESS::ProcessIO+0x3bf

rax=0000e5a47e7646ad rbx=0000000000000001 rcx=00000000750ca330

rdx=0000000000000000 rsi=00000000002b8aa0 rdi=00000000002c4a80

rip=000007fefe302e7f rsp=000000000085f430 rbp=0000000000ecff90

 r8=000000000085e2d8  r9=0000000000000002 r10=0000000000000000

r11=0000000000000246 r12=0000000000ec8bf0 r13=0000000000000000

r14=0000000000000000 r15=0000000000ec8080

iopl=0         nv up ei pl zr na po nc

cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246

rpcrt4!LRPC_ADDRESS::ProcessIO+0x3bf:

000007fe`fe302e7f 4c8d357ad1fbff  lea     r14,[rpcrt4!COMMON_ResubmitListen <PERF> (rpcrt4+0x0) (000007fe`fe2c0000)]

0:002> dt _CLIENT_ID rdi+8

ntdll!_CLIENT_ID

   +0x000 UniqueProcess    : 0x00000000`00002694 Void

   +0x008 UniqueThread     : 0x00000000`000005e8 Void

0:002> ? 0x00000000`00002694

Evaluate expression: 9876 = 00000000`00002694

0:002> ? 0x00000000`000005e8

Evaluate expression: 1512 = 00000000`000005e8

 

So now we know that the caller was process 0x2694 and thread 0x5e8, or 9876 and 1512 in decimal, respectively.  Our current process (PrintIsolationHost.exe) is PID 20a4 (see above ~ output), or 8356 decimal.  So who is 9876?  I happen to have a process listing from the data collection:

 

[0]  0 64 9876 spoolsv.exe     Svcs:  Spooler

     Command Line: C:\Windows\System32\spoolsv.exe

[0]  0 64 8356 PrintIsolationHost.exe

     Command Line: C:\Windows\system32\PrintIsolationHost.exe -Embedding

 

Okay, so I know the caller is thread 5e8 in spooler.  Loading up the spooler dump, what is that thread doing?

 

0:000> ~~[5e8]s

ntdll!NtAlpcSendWaitReceivePort+0xa:

00000000`77801b6a c3              ret

# Child-SP          RetAddr           Call Site

00 00000000`0649d898 000007fe`fe2fa776 ntdll!NtAlpcSendWaitReceivePort+0xa

01 00000000`0649d8a0 000007fe`fe2f4e42 rpcrt4!LRPC_CCALL::SendReceive+0x156

02 00000000`0649d960 000007fe`fdd528c0 rpcrt4!I_RpcSendReceive+0x42

03 00000000`0649d990 000007fe`fdd5282f ole32!ThreadSendReceive+0x40

04 00000000`0649d9e0 000007fe`fdd5265b ole32!CRpcChannelBuffer::SwitchAptAndDispatchCall+0xa3

05 00000000`0649da80 000007fe`fdc0daaa ole32!CRpcChannelBuffer::SendReceive2+0x11b

06 00000000`0649dc40 000007fe`fdc0da0c ole32!CAptRpcChnl::SendReceive+0x52

07 00000000`0649dd10 000007fe`fdd5205d ole32!CCtxComChnl::SendReceive+0x68

08 00000000`0649ddc0 000007fe`fe39b949 ole32!NdrExtpProxySendReceive+0x45

09 00000000`0649ddf0 000007fe`fdd521d0 rpcrt4!NdrpClientCall3+0x2e2

0a 00000000`0649e0b0 000007fe`fdc0d8a2 ole32!ObjectStublessClient+0x11d

0b 00000000`0649e440 000007fe`fb059070 ole32!ObjectStubless+0x42

0c 00000000`0649e490 000007fe`fb057967 localspl!sandbox::PrintProcessorExecuteObserver::ControlPrintProcessor+0x10

0d 00000000`0649e4c0 000007fe`fb055e27 localspl!sandbox::PrintProcessorAdapterImpl::ControlPrintProcessor+0x27

0e 00000000`0649e4f0 000007fe`faff7392 localspl!sandbox::PrintProcessorAdapter::ControlPrintProcessor+0x1b

0f 00000000`0649e520 000007fe`faff8a0a localspl!DeleteJob+0x1ce

10 00000000`0649eae0 00000000`ff41fe25 localspl!SplSetJob+0x49e

11 00000000`0649eb80 000007fe`f9683603 spoolsv!SetJobW+0x25

12 00000000`0649ebc0 00000000`61001ce1 spoolss!SetJobW+0x1f

13 00000000`0649ec00 00000000`61001d7d Contoso!InitializePrintMonitor+0x781

14 00000000`0649ec40 000007fe`faffa674 Contoso!InitializePrintMonitor+0x81d

15 00000000`0649ec70 00000000`ff41c9c7 localspl!SplEndDocPrinter+0x214

16 00000000`0649ecd0 00000000`ff403ba6 spoolsv!EndDocPrinter+0x1f

17 00000000`0649ed00 00000000`ff3fe772 spoolsv!YEndDocPrinter+0x22

18 00000000`0649ed30 000007fe`fe2f23d5 spoolsv!RpcEndDocPrinter+0x3e

19 00000000`0649ed60 000007fe`fe39b68e rpcrt4!Invoke+0x65

1a 00000000`0649edb0 000007fe`fe2dac40 rpcrt4!Ndr64StubWorker+0x61b

1b 00000000`0649f370 000007fe`fe2e50f4 rpcrt4!NdrServerCallAll+0x40

1c 00000000`0649f3c0 000007fe`fe2e4f56 rpcrt4!DispatchToStubInCNoAvrf+0x14

1d 00000000`0649f3f0 000007fe`fe2e5679 rpcrt4!RPC_INTERFACE::DispatchToStubWorker+0x146

1e 00000000`0649f510 000007fe`fe2e532d rpcrt4!LRPC_SCALL::DispatchRequest+0x149

1f 00000000`0649f5f0 000007fe`fe302e7f rpcrt4!LRPC_SCALL::HandleRequest+0x20d

20 00000000`0649f720 000007fe`fe302a35 rpcrt4!LRPC_ADDRESS::ProcessIO+0x3bf

21 00000000`0649f860 00000000`777cb68b rpcrt4!LrpcIoComplete+0xa5

22 00000000`0649f8f0 00000000`777cfeff ntdll!TppAlpcpExecuteCallback+0x26b

23 00000000`0649f980 00000000`776a652d ntdll!TppWorkerThread+0x3f8

24 00000000`0649fc80 00000000`777dc521 kernel32!BaseThreadInitThunk+0xd

25 00000000`0649fcb0 00000000`00000000 ntdll!RtlUserThreadStart+0x1d

 

It's calling into the print isolation host as we expect.  It looks like it is doing that to end a print job, based on an RPC call it received.  Using our same method as last time, let's pull out the PID and TID he is responding to:

 

0:048> .frame /c /r 20

20 00000000`0649f720 000007fe`fe302a35 rpcrt4!LRPC_ADDRESS::ProcessIO+0x3bf

rax=000000000622986c rbx=0000000000000000 rcx=000000000622985c

rdx=000007fefe3a3c40 rsi=00000000027e5150 rdi=0000000008e6abd0

rip=000007fefe302e7f rsp=000000000649f720 rbp=0000000008e43480

 r8=0000000000000010  r9=0000000000000000 r10=000007fefe2c0000

r11=0000000000000002 r12=000000000512d8c0 r13=0000000000000000

r14=0000000000000000 r15=00000000102e3710

iopl=0         nv up ei pl zr na po nc

cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246

rpcrt4!LRPC_ADDRESS::ProcessIO+0x3bf:

000007fe`fe302e7f 4c8d357ad1fbff  lea     r14,[rpcrt4!COMMON_ResubmitListen <PERF> (rpcrt4+0x0) (000007fe`fe2c0000)]

0:048> dt _CLIENT_ID rdi+8

ntdll!_CLIENT_ID

   +0x000 UniqueProcess    : 0x00000000`000020a4 Void

   +0x008 UniqueThread     : 0x00000000`00001c34 Void

 

Look at that, it's doing work for a different thread back in our Print Isolation Host.  Switching back to that dump, what is thread 1c34 doing?

 

0:002> ~~[1c34]s

ntdll!NtAlpcSendWaitReceivePort+0xa:

00000000`77801b6a c3 

0:003> kn

 # Child-SP          RetAddr           Call Site

00 00000000`00ebc098 000007fe`fe2fa776 ntdll!NtAlpcSendWaitReceivePort+0xa

01 00000000`00ebc0a0 000007fe`fe39cc74 rpcrt4!LRPC_CCALL::SendReceive+0x156

02 00000000`00ebc160 000007fe`fe39cf25 rpcrt4!NdrpClientCall3+0x244

03 00000000`00ebc420 000007fe`f9bfd878 rpcrt4!NdrClientCall3+0xf2

04 00000000`00ebc7b0 000007fe`f96845bf winspool!EndDocPrinter+0x15c

05 00000000`00ebc7f0 00000000`750c4102 spoolss!EndDocPrinter+0x2f

06 00000000`00ebc820 00000000`750c5013 ProseWarePrintProcessor+0x4102

07 00000000`00ebe620 000007fe`f9676be2 ProseWarePrintProcessor!PrintDocumentOnPrintProcessor+0xb3

08 00000000`00ebe650 000007fe`fe2f23d5 PrintIsolationProxy!sandbox::PrintProcessor::PrintDocThroughPrintProcessor+0x82

09 00000000`00ebe6b0 000007fe`fe39b68e rpcrt4!Invoke+0x65

0a 00000000`00ebe710 000007fe`fe2f48d6 rpcrt4!Ndr64StubWorker+0x61b

0b 00000000`00ebecd0 000007fe`fdd50883 rpcrt4!NdrStubCall3+0xb5

0c 00000000`00ebed30 000007fe`fdd50ccd ole32!CStdStubBuffer_Invoke+0x5b

0d 00000000`00ebed60 000007fe`fdd50c43 ole32!SyncStubInvoke+0x5d

0e 00000000`00ebedd0 000007fe`fdc0a4f0 ole32!StubInvoke+0xdb

0f 00000000`00ebee80 000007fe`fdd514d6 ole32!CCtxComChnl::ContextInvoke+0x190

10 00000000`00ebf010 000007fe`fdd5122b ole32!AppInvoke+0xc2

11 00000000`00ebf080 000007fe`fdd4fd6d ole32!ComInvokeWithLockAndIPID+0x52b

12 00000000`00ebf210 000007fe`fe2e50f4 ole32!ThreadInvoke+0x30d

13 00000000`00ebf2b0 000007fe`fe2e4f56 rpcrt4!DispatchToStubInCNoAvrf+0x14

14 00000000`00ebf2e0 000007fe`fe2e775b rpcrt4!RPC_INTERFACE::DispatchToStubWorker+0x146

15 00000000`00ebf400 000007fe`fe2e769b rpcrt4!RPC_INTERFACE::DispatchToStub+0x9b

16 00000000`00ebf440 000007fe`fe2e7632 rpcrt4!RPC_INTERFACE::DispatchToStubWithObject+0x5b

17 00000000`00ebf4c0 000007fe`fe2e532d rpcrt4!LRPC_SCALL::DispatchRequest+0x422

18 00000000`00ebf5a0 000007fe`fe302e7f rpcrt4!LRPC_SCALL::HandleRequest+0x20d

19 00000000`00ebf6d0 000007fe`fe302a35 rpcrt4!LRPC_ADDRESS::ProcessIO+0x3bf

1a 00000000`00ebf810 00000000`777cb68b rpcrt4!LrpcIoComplete+0xa5

1b 00000000`00ebf8a0 00000000`777cfeff ntdll!TppAlpcpExecuteCallback+0x26b

1c 00000000`00ebf930 00000000`776a652d ntdll!TppWorkerThread+0x3f8

1d 00000000`00ebfc30 00000000`777dc521 kernel32!BaseThreadInitThunk+0xd

1e 00000000`00ebfc60 00000000`00000000 ntdll!RtlUserThreadStart+0x1d

 

This thread called into spooler to end a document based on yet another RPC!  To be clear, this is what we're looking at so far:

image004

 

Again, who called into this thread?

 

0:003> .frame /c /r 19

19 00000000`00ebf6d0 000007fe`fe302a35 rpcrt4!LRPC_ADDRESS::ProcessIO+0x3bf

rax=57633d3cd2c9c145 rbx=0000000000000000 rcx=0000000000861ff0

rdx=ffffffffff88ffe0 rsi=00000000002b8aa0 rdi=0000000000ec6bc0

rip=000007fefe302e7f rsp=0000000000ebf6d0 rbp=00000000002cdef0

 r8=000000000003dbf0  r9=00000000000000fe r10=6dc0575d3d3cc4c8

r11=0000000000860020 r12=0000000000ec82b0 r13=0000000000000000

r14=0000000000000000 r15=0000000000ec8080

iopl=0         nv up ei pl zr na po nc

cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246

rpcrt4!LRPC_ADDRESS::ProcessIO+0x3bf:

000007fe`fe302e7f 4c8d357ad1fbff  lea     r14,[rpcrt4!COMMON_ResubmitListen <PERF> (rpcrt4+0x0) (000007fe`fe2c0000)]

0:003> dt _CLIENT_ID rdi+8

ntdll!_CLIENT_ID

   +0x000 UniqueProcess    : 0x00000000`00002694 Void

   +0x008 UniqueThread     : 0x00000000`000014cc Void

 

So another call from spooler (note the same PID as earlier) - let's go back to spoolsv.dmp.  Here is this thread:

0:048> ~~[14cc]s

ntdll!NtAlpcSendWaitReceivePort+0xa:

00000000`77801b6a c3              ret

0:049> k

Child-SP          RetAddr           Call Site

00000000`0351e538 000007fe`fe2fa776 ntdll!NtAlpcSendWaitReceivePort+0xa

00000000`0351e540 000007fe`fe2f4e42 rpcrt4!LRPC_CCALL::SendReceive+0x156

00000000`0351e600 000007fe`fdd528c0 rpcrt4!I_RpcSendReceive+0x42

00000000`0351e630 000007fe`fdd5282f ole32!ThreadSendReceive+0x40

00000000`0351e680 000007fe`fdd5265b ole32!CRpcChannelBuffer::SwitchAptAndDispatchCall+0xa3

00000000`0351e720 000007fe`fdc0daaa ole32!CRpcChannelBuffer::SendReceive2+0x11b

00000000`0351e8e0 000007fe`fdc0da0c ole32!CAptRpcChnl::SendReceive+0x52

00000000`0351e9b0 000007fe`fdd5205d ole32!CCtxComChnl::SendReceive+0x68

00000000`0351ea60 000007fe`fe39b949 ole32!NdrExtpProxySendReceive+0x45

00000000`0351ea90 000007fe`fdd521d0 rpcrt4!NdrpClientCall3+0x2e2

00000000`0351ed50 000007fe`fdc0d8a2 ole32!ObjectStublessClient+0x11d

00000000`0351f0e0 000007fe`fb0591ac ole32!ObjectStubless+0x42

00000000`0351f130 000007fe`fb057882 localspl!sandbox::PrintProcessorExecuteObserver::PrintDocThroughPrintProcessor+0x124

00000000`0351f1f0 000007fe`fb05601d localspl!sandbox::PrintProcessorAdapterImpl::PrintDocumentOnPrintProcessor+0x3a

00000000`0351f220 000007fe`fb013b70 localspl!sandbox::PrintProcessorAdapter::PrintDocumentOnPrintProcessor+0x9d

00000000`0351f270 000007fe`fb014c7c localspl!PrintDocumentThruPrintProcessor+0x46c

00000000`0351fa70 00000000`776a652d localspl!PortThread+0x4d0

00000000`0351fd80 00000000`777dc521 kernel32!BaseThreadInitThunk+0xd

00000000`0351fdb0 00000000`00000000 ntdll!RtlUserThreadStart+0x1d

 

Perfect.  So now we know that localspl was printing a document, which resulted in all of these RPC calls between spooler and Print Isolation Host, and ultimately the deadlock in Print Isolation Host is holding up this thread.  Just out of curiosity, are there any other threads blocked on this wait chain?

 

0:049> !locks

CritSec +5a00060 at 0000000005a00060

WaiterWoken        No

LockCount          105

RecursionCount     1

OwningThread       5e8

EntryCount         0

ContentionCount    84

*** Locked

 

Scanned 14148 critical sections

 

Yes there are.  Thread 5e8, which we looked at earlier, is holding a lock that 104 other threads are waiting for!  This dump had 177 threads, so we know now that thread 5e8, 14cc, and 104 others in spooler are all hung on this deadlock. With about 60% of all the threads in spooler hung, the deadlock in ProseWarePrintProcessor is clearly the cause of our issue.  Here's the final wait chain diagram:

image006

 

To resolve the issue, ProseWarePrintProcessor needs to avoid calling GetModuleFileName while its DllMain routine is still running, since the former requires and the latter holds the Loader Lock.

How the Clipboard Works, Part 1

$
0
0

Recently I had the opportunity to debug the clipboard in Windows, and I thought I’d share some of the things I learned.  The clipboard is one of those parts of Windows that many of us use dozens (hundreds?) of times a day and don’t really think about. Before working on this case, I had never even considered how it works under the hood.  It turns out that there’s more to it than you might think. In this first article, I’ll describe how applications store different types of data to the clipboard and how it is retrieved.  My next post will describe how applications can hook the clipboard to monitor it for changes.  In both, I’ll include debug notes to show you how to access the data from the debugger.

 

Let’s start by discussing clipboard formats.  A clipboard format is used to describe what type of data is placed on the clipboard.  There are a number of predefined standard formats that an application can use, such as bitmap, ANSI text, Unicode text, and TIFF.  Windows also allows an application to specify its own formats. For example, a word processor may want to register a format that includes text, formatting, and images.  Of course, this leads to one problem, what happens if you want to copy from your word processor and paste into Notepad, which doesn’t understand all of the formatting and pictures?

 

The answer is to allow multiple formats to be stored in the clipboard at one time.  When I used to think of the clipboard I thought of it as a single object (“my text” or “my image”), but in reality the clipboard usually has my data in several different forms.  The destination program gets a format it can use when I paste.

 

So how does this data end up on the clipboard?  Simple, an application first takes ownership of the clipboard via the OpenClipboard function.   Once it has done that, it can empty the clipboard with EmptyClipboard.  Finally, it is ready to place data on the clipboard using SetClipboardData.  SetClipboardData takes two parameters; the first is the identifier of one of the clipboard formats we discussed above.  The second is a handle to the memory containing the data in that format. The application can continue to call SetClipboardData for each of the various formats it wishes to provide going from best to worst (since the destination application will select the first format in the list it recognizes).  To make things easier for the developer, Windows will automatically provide converted formats for some clipboard format types.  Once the program is done, it calls CloseClipboard.

 

When a user hits paste, the destination application will call OpenClipboard and one of these functions to determine what data format(s) are available: IsClipboardFormatAvailable, GetPriorityClipboardFormat, or EnumClipboardFormats. Assuming the application finds a format it can use, it will then call GetClipboardData with the desired format identifier as a parameter to get a handle to the data.  Finally, it will call CloseClipboard.

 

Now let’s take a look at how you can find what data being written to the clipboard using the debugger. (Note that all of my notes are taken from a Win7/2008 R2 system – so things might vary slightly in different versions of the OS.)   Since the clipboard is part of Win32k.sys, you’ll need to use a kernel debugger.  I like to use win32k!InternalSetClipboardData+0xe4 as a breakpoint.  The nice thing about this offset is that it is right after we’ve populated the RDI register with data from SetClipboardData in a structure known as tagCLIP.

 

kd> u win32k!InternalSetClipboardData+0xe4-c L5

win32k!InternalSetClipboardData+0xd8:

fffff960`0011e278 894360          mov     dword ptr [rbx+60h],eax

fffff960`0011e27b 8937            mov     dword ptr [rdi],esi

fffff960`0011e27d 4c896708        mov     qword ptr [rdi+8],r12

fffff960`0011e281 896f10          mov     dword ptr [rdi+10h],ebp

fffff960`0011e284 ff15667e1900    call    qword ptr[win32k!_imp_PsGetCurrentProcessWin32Process (fffff960`002b60f0)]

 

kd> dt win32k!tagCLIP

   +0x000 fmt              : Uint4B

   +0x008 hData            : Ptr64 Void

   +0x010fGlobalHandle     : Int4B

 

Here’s what a call to SetClipboardData from Notepad looks like:

kd> k

Child-SP          RetAddr           Call Site

fffff880`0513a940 fffff960`0011e14f win32k!InternalSetClipboardData+0xe4

fffff880`0513ab90 fffff960`000e9312 win32k!SetClipboardData+0x57

fffff880`0513abd0 fffff800`01482ed3 win32k!NtUserSetClipboardData+0x9e

fffff880`0513ac20 00000000`7792e30ant!KiSystemServiceCopyEnd+0x13

00000000`001dfad8 00000000`7792e494 USER32!ZwUserSetClipboardData+0xa

00000000`001dfae0 000007fe`fc5b892b USER32!SetClipboardData+0xdf

00000000`001dfb20 000007fe`fc5ba625 COMCTL32!Edit_Copy+0xdf

00000000`001dfb60 00000000`77929bd1 COMCTL32!Edit_WndProc+0xec9

00000000`001dfc00 00000000`779298da USER32!UserCallWinProcCheckWow+0x1ad

00000000`001dfcc0 00000000`ff5110bc USER32!DispatchMessageWorker+0x3b5

00000000`001dfd40 00000000`ff51133c notepad!WinMain+0x16f

00000000`001dfdc0 00000000`77a2652d notepad!DisplayNonGenuineDlgWorker+0x2da

00000000`001dfe80 00000000`77b5c521 kernel32!BaseThreadInitThunk+0xd

00000000`001dfeb0 00000000`00000000ntdll!RtlUserThreadStart+0x1d

 

So here, we can dt RDI as a tagCLIP to see what was written:

kd> dt win32k!tagCLIP @rdi

   +0x000 fmt              : 0xd

   +0x008 hData            : 0x00000000`00270235 Void

   +0x010fGlobalHandle     : 0n1

 

Fmt is the clipboard format. 0xd is 13, which indicates this data is Unicode text.  We can’t just ‘du’ the value in hData, however, because this is a handle, not a direct pointer to the data.  So now we need to look up the handle.  To do that, we need to look at a win32k global structure – gSharedInfo:

kd> ?win32k!gSharedInfo

Evaluate expression: -7284261440224 = fffff960`002f3520

kd> dt win32k!tagSHAREDINFO fffff960`002f3520

   +0x000 psi              : 0xfffff900`c0980a70 tagSERVERINFO

   +0x008 aheList          : 0xfffff900`c0800000 _HANDLEENTRY

   +0x010 HeEntrySize      : 0x18

   +0x018 pDispInfo        : 0xfffff900`c0981e50 tagDISPLAYINFO

   +0x020ulSharedDelta     : 0

   +0x028 awmControl       : [31] _WNDMSG

   +0x218DefWindowMsgs     : _WNDMSG

   +0x228DefWindowSpecMsgs : _WNDMSG

 

aheList in gSharedInfo contains an array of handle entries, and the last 2 bytes of hData multiplied by the size of a handle entry the address of our handle entry:

kd> ?0x00000000`00270235 & FFFF

Evaluate expression: 565 = 00000000`00000235

kd> ??sizeof(win32k!_HANDLEENTRY)

unsigned int64 0x18

kd> ? 0xfffff900`c0800000 + (0x235*0x18)

Evaluate expression: -7693351766792 = fffff900`c08034f8

 

kd> dt win32k!_HANDLEENTRY fffff900`c08034f8

   +0x000 phead            : 0xfffff900`c0de0fb0 _HEAD

   +0x008 pOwner           : (null)

   +0x010 bType            : 0x6 ''

   +0x011 bFlags           : 0 ''

   +0x012 wUniq            : 0x27

 

If we look in phead at offset 14, we’ll get our data (this offset may vary on different platforms):

kd> du fffff900`c0de0fb0 + 0x14

fffff900`c0de0fc4 "Hi NTDebugging readers!"

 

Let’s consider one other scenario.  I copied some text out of Wordpad, and a number of SetClipboardData calls were made to accommodate different formats. The Unicode format entry looks like this:

Breakpoint 0 hit

win32k!InternalSetClipboardData+0xe4:

fffff960`0011e284 ff15667e1900   call    qword ptr[win32k!_imp_PsGetCurrentProcessWin32Process (fffff960`002b60f0)]

kd> dt win32k!tagCLIP @rdi

   +0x000 fmt              : 0xd

   +0x008 hData            : (null)

   +0x010fGlobalHandle    : 0n0

 

hData is null!  Why is that?  It turns out that the clipboard allows an application to pass in null to SetClipboardData for a given format.  This indicates that the application can provide the data in that format, but is deferring doing so until it is actually needed.  Sure enough, if I paste the text into Notepad, which needs the text in Unicode, Windows sends a WM_RENDERFORMAT message to the WordPad window, and WordPad provides the data in the new format.  Of course, if the application exits before populating all of its formats, Windows needs all of the formats rendered.  In this case, Windows will send the WM_RENDERALLFORMATS message so other applications can use the clipboard data after the source application has exited.

 

That’s all for now.  Next time we’ll look at how applications can monitor the clipboard for changes using two hooks.  If you want to know more about using the clipboard in your code, this is a great reference.

 

-Matt Burrough

 

Part 2 of this article can be found here:

http://blogs.msdn.com/b/ntdebugging/archive/2012/03/29/how-the-clipboard-works-part-2.aspx

Our Team in Bangalore is Hiring - Windows Server Escalation Engineer

$
0
0

Would you like to join the world’s best and most elite debuggers to enable the success of Microsoft solutions?

 

As a trusted advisor to our top customers you will be working with to the most experienced IT professionals and developers in the industry. You will influence our product teams in sustained engineering efforts to drive improvements in our products.

  

This role involves deep analysis of product source code and debugging to solve problems in multi-million dollar configurations and will give you an opportunity to stretch your critical thinking skills. During the course of debugging, you will uncover opportunities to improve the customer experience while influencing the current and future design of our products.

  

In addition to providing support to customers while being the primary interface to our sustained engineering teams, you will also have the opportunity to work with new technologies and unreleased software. Through our continuous investment in depth training and hands-on experience with tough customer challenges you will become the world’s best in this area. Expect to partner with many various roles at Microsoft launching a very successful career!

  

This position is located is at the Microsoft Global Technical Support Center in Bangalore, India.

  

Learn more about what an Escalation Engineer does at:

Profile: Ron Stock, CTS Escalation Engineer - Microsoft Customer Service & Support - What is CSS?

Microsoft JobsBlog JobCast with Escalation Engineer Jeff Dailey

Microsoft JobsBlog JobCast with Escalation Engineer Scott Oseychik

  

Apply here:

https://careers.microsoft.com/jobdetails.aspx?ss=&pg=0&so=&rw=1&jid=77975&jlang=EN


How the Clipboard Works, Part 2

$
0
0

Last time, we discussed how applications place data on the clipboard, and how to access that data using the debugger. Today, we'll take a look at how an application can monitor the clipboard for changes.  Understanding this is important because it is a place where Windows allows 3rd-party code to "hook" into the system.  If you experience unexpected behavior with copying and pasting, a program using these hooks may be misbehaving.  We’ll start by covering the hooking mechanisms for clipboard, and then review how to identify what applications, if any, are using these hooks in the debugger.

 

There are three ways to monitor the clipboard for changes - clipboard viewers, clipboard format listeners, and querying the clipboard sequence number.  We will focus on the first two as these allow an application to register for notifications whenever the clipboard is updated.  The third method simply allows an application to check and see if a change has occurred, and should not be used in a polling loop.

 

The Clipboard Viewer functionality has been around since Windows 2000, if not earlier.  The way it works is pretty simple - an application interested in receiving clipboard change notifications calls SetClipboardViewer and passes a handle to its window.  Windows then stores that handle in a per-session win32k global, and anytime the clipboard is changed Windows sends a WM_DRAWCLIPBOARD message to the registered window.

 

Of course, multiple applications are allowed to register their windows as clipboard viewers - so how does Windows handle that?  Well, if an application calls SetClipboardViewer and another clipboard viewer was already registered, Windows returns the handle value of the previous viewer's window to the new viewer.  It is then the responsibility of the new viewer to call SendMessage every time it receives a WM_DRAWCLIPBOARD to notify the next viewer in the chain. Each clipboard viewer also needs to handle the WM_CHANGECBCHAIN message, which notifies  all viewers when one of the viewers in the chain is removed, and specifies what the next viewer in the chain is.  This allows the chain to be maintained.

 

An obvious problem with this design is it relies on each clipboard viewer application to behave correctly, not to terminate unexpectedly, and to generally be a good citizen.  If any viewer decided not to be friendly, it could simply skip notifying the next viewer in line about an update, rendering the next viewer and all subsequent viewers impotent.

 

To address these problems, the Clipboard Format Listener mechanism was added in Windows Vista. This works in much the same way as the clipboard viewer functionality except in this case Windows maintains the list of listeners, instead of depending on each application to preserve a chain.

 

If an application wishes to become a clipboard format listener,it calls the AddClipboardFormatListener function and passes in a handle to its window. After that, its window message handler will receive WM_CLIPBOARDUPDATE messages.  When the application is ready to exit or no longer wishes to receive notifications, it can call RemoveClipboardFormatListener.

 

Now that we've covered the ways to register a viewer/listener, let's take a look at how to identify them using the debugger.  First, you'll need to identify a process in the session you are interested in checking for clipboard monitors.  It can be any win32 process in that session -we just need to use it to locate a pointer to the Window Station.  In this case, I'll use the Notepad window I used in part 1: 

kd> !process 0 0 notepad.exe

PROCESS fffff980366ecb30

    SessionId: 1  Cid: 0374   Peb: 7fffffd8000  ParentCid: 0814

    DirBase: 1867e000  ObjectTable: fffff9803d28ef90  HandleCount: 52.

    Image: notepad.exe

 

If you are doing this in a live kernel debug, you'll need to change context into the process interactively (using .process /I< address> then hit g and wait for the debugger to break back in).  Now DT the process address as an _EPROCESS and look for the Win32Process field: 

kd> dt _EPROCESS fffff980366ecb30 Win32Process

nt!_EPROCESS

   +0x258 Win32Process : 0xfffff900`c18c0ce0 Void

 

Now DT the Win32Process address as a win32k!tagPROCESSINFO and identify the rpwinsta value: 

kd> dt win32k!tagPROCESSINFO 0xfffff900`c18c0ce0 rpwinsta

   +0x258 rpwinsta : 0xfffff980`0be2af60 tagWINDOWSTATION

 

This is our Window Station. Dump it using dt: 

kd> dt 0xfffff980`0be2af60 tagWINDOWSTATION

win32k!tagWINDOWSTATION

   +0x000 dwSessionId      : 1

   +0x008 rpwinstaNext     : (null)

   +0x010 rpdeskList       : 0xfffff980`0c5e2f20 tagDESKTOP

   +0x018 pTerm            : 0xfffff960`002f5560 tagTERMINAL

   +0x020 dwWSF_Flags      : 0

   +0x028 spklList         : 0xfffff900`c192cf80 tagKL

   +0x030 ptiClipLock      : (null)

   +0x038 ptiDrawingClipboard: (null)

   +0x040 spwndClipOpen    : (null)

   +0x048 spwndClipViewer  : 0xfffff900`c1a4ca70 tagWND

   +0x050 spwndClipOwner   : 0xfffff900`c1a3ef70 tagWND

   +0x058 pClipBase        : 0xfffff900`c5512fa0 tagCLIP

   +0x060 cNumClipFormats  : 4

   +0x064 iClipSerialNumber : 0x16

   +0x068 iClipSequenceNumber : 0xc1

   +0x070 spwndClipboardListener : 0xfffff900`c1a53440 tagWND

   +0x078 pGlobalAtomTable: 0xfffff980`0bd56c70 Void

   +0x080 luidEndSession   : _LUID

   +0x088 luidUser         : _LUID

   +0x090 psidUser         : 0xfffff900`c402afe0 Void

 

Note the spwndClipViewer, spwndClipboardListener, and spwndClipOwnerfields.  spwndClipViewer is the most-recently-registered window in the clipboard viewer chain.  Similarly, spwndClipboardListener is the most recent listener in our Clipboard Format Listener list.  spwndClipOwner is the window that set the content in the clipboard.

 

Given the window, it is just a few steps to determine the process.  This would work forspwndClipViewer, spwndClipboardListener, and spwndClipOwner.  First, dt the value as a tagWND.  We'll use the spwndClipViewer for this demonstration: 

kd> dt 0xfffff900`c1a4ca70 tagWND

win32k!tagWND

   +0x000 head             : _THRDESKHEAD

   +0x028 state            : 0x40020008

   +0x028 bHasMeun         : 0y0

   +0x028 bHasVerticalScrollbar : 0y0

 

We only care about the head - so since it is at offset 0, dt the same address as a _THRDESKHEAD: 

kd> dt 0xfffff900`c1a4ca70 _THRDESKHEAD

win32k!_THRDESKHEAD

   +0x000 h                : 0x00000000`000102ae Void

   +0x008 cLockObj         : 6

   +0x010 pti              : 0xfffff900`c4f26c20tagTHREADINFO

   +0x018 rpdesk           : 0xfffff980`0c5e2f20 tagDESKTOP

   +0x020 pSelf            : 0xfffff900`c1a4ca70  "???"

 

Now, dt the address in pti as a tagTHREADINFO: 

kd> dt 0xfffff900`c4f26c20 tagTHREADINFO

win32k!tagTHREADINFO

   +0x000 pEThread         : 0xfffff980`0ef6cb10 _ETHREAD

   +0x008 RefCount         : 1

   +0x010 ptlW32           : (null)

   +0x018 pgdiDcattr       : 0x00000000`000f0d00 Void

 

Here, we only care about the value of pEThread, which we can pass to !thread: 

kd> !thread 0xfffff980`0ef6cb10 e

THREAD fffff9800ef6cb10 Cid 087c.07ec  Teb: 000007fffffde000 Win32Thread: fffff900c4f26c20 WAIT: (WrUserRequest) UserModeNon-Alertable

    fffff9801c01efe0  SynchronizationEvent

Not impersonating

DeviceMap                 fffff980278a0fc0

Owning Process            fffff98032e18b30       Image:         viewer02.exe

Attached Process          N/A            Image:         N/A

Wait Start TickCount     5435847        Ticks: 33 (0:00:00:00.515)

Context Switch Count     809            IdealProcessor: 0                 LargeStack

UserTime                  00:00:00.000

KernelTime                00:00:00.062

Win32 Start Address 0x000000013f203044

Stack Init fffff880050acdb0 Current fffff880050ac6f0

Base fffff880050ad000 Limit fffff880050a3000 Call 0

Priority 11 BasePriority 8 UnusualBoost 0 ForegroundBoost 2IoPriority 2 PagePriority 5

Child-SP          RetAddr           Call Site

fffff880`050ac730 fffff800`01488f32 nt!KiSwapContext+0x7a

fffff880`050ac870 fffff800`0148b74f nt!KiCommitThreadWait+0x1d2

fffff880`050ac900 fffff960`000dc8e7 nt!KeWaitForSingleObject+0x19f

fffff880`050ac9a0 fffff960`000dc989 win32k!xxxRealSleepThread+0x257

fffff880`050aca40 fffff960`000dafc0 win32k!xxxSleepThread+0x59

fffff880`050aca70 fffff960`000db0c5 win32k!xxxRealInternalGetMessage+0x7dc

fffff880`050acb50 fffff960`000dcab5 win32k!xxxInternalGetMessage+0x35

fffff880`050acb90 fffff800`01482ed3 win32k!NtUserGetMessage+0x75

fffff880`050acc20 00000000`77929e6a nt!KiSystemServiceCopyEnd+0x13 (TrapFrame @ fffff880`050acc20)

00000000`002ffb18 00000000`00000000 0x77929e6a

 

As you can see, we have a clipboard viewer registered from process viewer02.exe.  Because of viewer's process-maintained chain architecture, it isn't easy to see the next process in the chain.  However, we can do this for clipboard listeners.  Let's look back at our window station: 

kd> dt 0xfffff980`0be2af60 tagWINDOWSTATION

win32k!tagWINDOWSTATION

   +0x000 dwSessionId      : 1

   +0x008 rpwinstaNext     : (null)

   +0x010 rpdeskList       : 0xfffff980`0c5e2f20 tagDESKTOP

   +0x018 pTerm            : 0xfffff960`002f5560 tagTERMINAL

   +0x020 dwWSF_Flags      : 0

   +0x028 spklList         : 0xfffff900`c192cf80 tagKL

   +0x030 ptiClipLock      : (null)

   +0x038 ptiDrawingClipboard : (null)

   +0x040 spwndClipOpen    : (null)

   +0x048 spwndClipViewer  : 0xfffff900`c1a4ca70tagWND

   +0x050 spwndClipOwner   : 0xfffff900`c1a3ef70tagWND

   +0x058 pClipBase        : 0xfffff900`c5512fa0 tagCLIP

   +0x060 cNumClipFormats  : 4

   +0x064 iClipSerialNumber : 0x16

   +0x068 iClipSequenceNumber : 0xc1

   +0x070 spwndClipboardListener: 0xfffff900`c1a53440 tagWND

   +0x078 pGlobalAtomTable: 0xfffff980`0bd56c70 Void

   +0x080 luidEndSession   : _LUID

   +0x088 luidUser         : _LUID

   +0x090 psidUser         : 0xfffff900`c402afe0 Void

 

If we dt the spwndClipboardListener, there is a field that shows the next listener named spwndClipboardListenerNext: 

kd> dt 0xfffff900`c1a53440 tagWND spwndClipboardListenerNext

win32k!tagWND

   +0x118 spwndClipboardListenerNext : 0xfffff900`c1a50080 tagWND

 

When you reach the last clipboard format listener's tagWND, its spwndClipboardListenerNext value will be null: 

kd> dt 0xfffff900`c1a50080 tagWND spwndClipboardListenerNext

win32k!tagWND

   +0x118 spwndClipboardListenerNext : (null)

 

Using this window address, we can go through the same steps as above to identify this listener's process name. As mentioned earlier, since tagWND is a kernel structure, the OS is maintaining these spwndClipboardListener/spwndClipboardListenerNext pointers, so they aren't susceptible to the chain problems of clipboard viewers.

 

That wraps up our clipboard coverage. I hope you found it informative.  Want to learn more about monitoring the clipboard?  This MSDN article is a good resource.

 

-Matt Burrough

Troubleshooting Memory Leaks With Just a Dump

$
0
0

Hello debuggers, the debug ninja is back again.  Sometimes we have a scenario where a process is using a lot of memory, and the only data we are able to get at the moment is a user dump.  Ordinarily data from tools such as umdh or xperf would be preferable because they provide memory usage data over a period of time and can include call stack information. However, umdh requires restarting the process (which loses the state of high memory usage), and xperf requires the installation of the Windows Performance Toolkit which may not always be an immediate option.

 

When we have such a dump we may not be able to specifically identify what piece of code is generating the high memory usage, but we may be able to narrow the scope of troubleshooting to a specific dll.

 

The first thing we need to do is identify what type of memory is using most of the address space.  The debugger command !address –summary allows us to do this: 

0:000> !address -summary

 

--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal

Free                                    489      7fe`6ff5a000 (   7.994 Tb)           99.92%

Heap                                   9094        1`75ed1000 (   5.843 Gb)  93.47%    0.07%

<unknown>                               275        0`12e41000 ( 302.254 Mb)  4.72%    0.00%

Image                                   937        0`05a6a000 (  90.414 Mb)  1.41%    0.00%

Stack                                   138        0`01700000 (  23.000 Mb)  0.36%    0.00%

Other                                    14        0`001bd000 (   1.738 Mb)  0.03%    0.00%

TEB                                      46        0`0005c000 ( 368.000 kb)   0.01%   0.00%

PEB                                       1        0`00001000 (   4.000 kb)  0.00%    0.00%

 

From this example we can see that most of the memory is used by heap.  A process will usually have multiple heaps, each created by a call to HeapCreate.  We can examine the size of each of these heaps with !heap –s: 

0:000> !heap -s

LFH Key                   : 0x0000006c1104d280

Termination on corruption : ENABLED

          Heap     Flags  Reserv  Commit  Virt  Free  List   UCR Virt  Lock  Fast

                            (k)     (k)   (k)     (k) length      blocks cont. heap

-------------------------------------------------------------------------------------

0000000000100000 00000002  16384  12824  16384  1180   254     5   0      3   LFH

0000000000010000 00008000     64      4     64     1     1     1   0      0     

00000000003d0000 00001002   1088    708   1088   121    20     2   0      0   LFH

0000000003080000 00001002   1536    700   1536     4     4     2   0      0   LFH

00000000033a0000 00001002 5229696 1377584 5229696 414244  4039  3059   0     2c   LFH

    External fragmentation  30 % (4039 free blocks)

    Virtual address fragmentation  73 % (3059 uncommited ranges)

0000000003380000 00001002     64      8     64     3     1     1   0      0     

0000000003600000 00001002    512     56    512     3     1     1   0      0     

0000000003c20000 00001002    512      8    512     3     1     1   0      0     

0000000003220000 00001002    512      8    512     3     1     1   0      0     

0000000003e50000 00001002    512      8    512     3     1     1   0      0     

0000000003d00000 00001002    512    148    512     5     3     1   0      0   LFH

 

From the above output we can see that most of the memory is being used by heap 00000000033a0000.

 

At this point we need to try to identify what this heap is used for.  A brute force method to do this is to search memory with the ‘s’ command. 

0:000> s -q 0 l?7fffffffffffffff 00000000033a0000

<snip>

000007fe`f21810a0  00000000`033a0000 00000000`00000001

 

The output of the ‘s’ command may be verbose.  You will need to manually examine the addresses where ‘s’ finds hits.  Most of these addresses will probably be in heap memory, we are looking for an address that matches a module.  I snipped the above output to just show the relevant hit, an address inside of a loaded module.

 

The search of memory revealed that the heap 00000000033a0000 is used by the module useheap.dll, specifically it is part of the global ‘Blob’ class. 

0:000> ln 000007fe`f21810a0

(000007fe`f21810a0)  useheap!Blob::m_Heap

 

At this point we don’t know specifically what code in useheap.dll has allocated a lot of heap, however we have significantly narrowed the scope of the problem.  We can now determine if there is a known issue with heap usage in useheap.dll that is addressed in a later version.  We may also know from experience that this module uses a lot of memory under specific circumstances, such as a high volume of work sent to this service.

 

I hope this example helps the next time you have high memory usage and only have a user dump to troubleshoot with.  Good luck!

Updated Archive of the NtDebugging Twitter Debug Tips

$
0
0

Every Wednesday (usually) we 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. Periodically we post an updated blog with the current archive. Follow us on twitter if you want to see the new tips as we post them.

The goal of these tips is to share debug commands, and forms of commands (parameters, flags, etc) that we in Platforms Global Escalation Services find useful. I hope you can add these commands to your toolkit and they will help you debug more efficiently.

Tips:

!thread/!process [address] e - on x64 will not show you the meaningless Args to Child information.

.frame /c [FrameNumber] - sets context to specificied stack frame. On x64 provides more reliable register information than .trap.

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://bit.ly/dik4OR for vol/nonvol regs.

k=rbp rip FrameCount - Dumps call stack starting at rbp/rip on x64. Useful when the stack is corrupt.

.process/.thread /p /r [address] - sets new process context, sets .cache forcedecodeuser, and reloads user symbols.

!process [address] 17 - Sets the context for this command, avoids the need for .process to see user stacks. Try !process 0 17

~~[ThreadID]s - Changes threads in user mode. Use Thread ID number from output such as !locks. Ex: ~~[1bd4]s

runas /netonly /u:<account> windbg.exe - Launch windbg with domain account. Use when dbg computer isn't in domain and symbol server is.

!heap -p -a <address> - Shows information about the heap block containing <address>, even if you aren't using pageheap.

ub - Unassembles starting at a location prior to your address. Accepts l<number> to specify how many instructions to go back. ub . l20

!stacks 2 [FilterString] - Finds kernel mode call stacks that contain the FilterString in a symbol.

!thread [address] 17 (or 1e on x64) - Sets context for this command, avoids the need for .thread/.process for user stacks.

.hh [Text] - Opens the debugger help. [Text] is the topic to lookup in the index. Example: .hh !pte

?? can dump structs using C++ style expressions. Ex: ??((nt!_KTHREAD*)(0xfffffa800ea43bb0))->ApcState

bp /t EThread - Sets a kernel mode breakpoint that only triggers when hit in the context of this thread.

bp /p EProcess - Sets a kernel mode breakpoint that only triggers when hit in the context of this process.

gc - If you run 'p' and hit a breakpoint, gc takes you where p would have gone if you had not hit the bp.

gu - Go until the current function returns. Effectively this unwinds one stack frame. #windbg

pc - Steps through until the next 'call' instruction. Combine with other commands to find who returned your error> pc;p;r eax

pt - Steps through until the next 'ret' instruction. Similar to gu, but pt stops on the ret and gu stops after the ret.

.ignore_missing_pages 1 - supresses the error: "Page 2a49 not present in the dump file. Type ".hh dbgerr004" for details"

.exr -1 shows the most recent exception. Useful in user dumps of crashes, especially for no execute crashes (NX/DEP).

wt - Trace calls until they return to the current address. More useful with -or to get return values. Use -l for depth.

.thread /w - Changes to the WOW64 32-bit context from 64-bit kernel mode. Wow64exts doesn't work in kernel mode.

??sizeof(structure) - Gets the size of a structure, it's easier than counting.

sxe ld:module.dll - Enables an exception which will break into the debugger when module.dll is loaded.

vertarget - Shows OS version of the debug target. Also shows machine name, uptime, and session time (when the dump was taken).

!vm 1 - In a kernel debugger, shows basic information about memory usage. Available, committed, pagefile, pool, sysptes, etc.

.time - Shows session time (when dump was taken) and system uptime. In user mode shows process uptime, kernel/user time.

ba w size [address] - Break on write access only. Replace size with the num bytes you want to watch. Ex: ba w 4 005d5f10

.process -i <address> - Make the process active and break into the debugger. Use in live kernel debugs to get into process context.

.reload /f /o - Overwrites cached files in your downstream symbol store. Useful when your store has corrupt pdbs.

->* - 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".

.unload [DllName] - Unloads the debug extension you didn't intend to load. Omit DllName to unload the last dll loaded.

!exqueue dumps the executive worker queues. Use flags 7f to dump the worker threads and the queues.

lmvm <module> - Dumps information about the module. Remember to use <module> and not <module.dll>.

!thread -t TID - Dump a thread using thread ID rather than thread address. Useful when working with a critical section.

!list - Walks a linked list and displays informatino for each element in a list. See blog later today for an example.

.time -h # - Shows the debug session time using the timezone offset of #. Ex: .time -h 0 shows when a dump was taken in UTC.

!session - Lists all of the user session IDs. A quick way to list the active sessions from a dump of a terminal server.

!session -s SessionID - Changes the current session context to SessionID. Useful when looking at GDI, or other per session data.

| ProcNum s - Switches to process number ProcNum. Use when debugging multiple dumps, or processes, in one windbg.

!! - Launches a shell process and redirects its output to the debugger. The same as .shell, but "bang bang" sounds cooler.

uf Function - Dumps the assembly for Function (name or address). Useful for optimized code that is not contiguous in memory.

uf /c Function - Shows all of the calls made by Function (can be function name or address).

!wow64exts.sw - switches between x64 and x86 contexts. Often used to reverse .thread /w context switch.

Debugging a Crash, Found a Trojan

$
0
0

Hi, I'm Manish from Global Escalation Services. I would like to present a multiple random bug check issue, which was caused by malicious code (trojan) running on the machine. This is the walkthrough of how we found the virus on the server.


In this particular dump, the machine crashed with Bugcheck 0xA (IRQL_NOT_LESS_OR_EQUAL) because we got a Page Fault at dispatch level, also known as DPC Level (IRQL 2). Windows system architecture governs that we cannot have a page fault at dispatch level because paging requires I/O, I/O requires a wait, and we cannot wait while the IRQL is above dispatch level.  So when this anomaly happens Windows will intentionally crash the machine.

We trapped in CcMapData because we touched the address c226d800 which was not valid because it’s paged out. If we look at implementation of CcMapData on MSDN http://msdn.microsoft.com/en-us/library/windows/hardware/ff539155(v=vs.85).aspx we see that this function can only be called below Dispatch Level, but the current CPU IRQL is 2.


So how did this happen?  The most likely possibility is that some driver on the stack raised the IRQL by calling KeRaiseIrql and then forgot to lower it by calling KeLowerIrql.  There are many drivers on this stack and anyone could be the culprit. It is difficult to track when one of these may have done it as it’s long gone.


We could have enabled IRQL Checking using Driver Verifier to find the culprit but we have to enable it on all drivers listed in the stack (and possibly others), which could cause some performance issues.  Also this was not the only crash we were seeing; there were various stop codes.

So before going the verifier route I decided to dig more into this dump. I noticed there is an address on the stack for which the module name is not getting resolved. This looked odd (suspicious) as most of the time the debugger does a great job of finding the module.

I started investigating what is this module.  It turned out to be a Trojan “TrojanDropper:Win32/Sirefef.B”.
http://www.microsoft.com/security/portal/Threat/Encyclopedia/Entry.aspx?Name=TrojanDropper%3AWin32%2FSirefef.B

 

0: kd> !analyze -v

*******************************************************************************

*                                                                             *

*                        Bugcheck Analysis                                    *

*                                                                             *

*******************************************************************************

IRQL_NOT_LESS_OR_EQUAL (a)

An attempt was made to access a pageable (or completely invalid) address at an

interrupt request level (IRQL) that is too high.  This is usually

caused by drivers using improper addresses.

If a kernel debugger is available get the stack backtrace.

Arguments:

Arg1: c226d800, memory referenced

Arg2: d0000002, IRQL

Arg3: 00000000, bitfield :

      bit 0 : value 0 = read operation, 1 = write operation

      bit 3 : value 0 = not an execute operation, 1 = execute operation (only on chips which support this level of status)

Arg4: 808b64a6, address which referenced memory

 

0: kd> kv

ChildEBP RetAddr  Args to Child             

f78ae41c 808b64a6 badb0d00 00000000 00000001 nt!KiTrap0E+0x2a7 (FPO: [0,0] TrapFrame @ f78ae41c)

f78ae4cc f71a6f2d 8b22d520 f78ae4fc 00000400 nt!CcMapData+0x8c (FPO: [Non-Fpo])

f78ae4ec f71a4494 f78ae7ec 8b64c150 01c6d800 Ntfs!NtfsMapStream+0x4b (FPO: [Non-Fpo])

f78ae560 f71a6df0 f78ae7ec 8b3a7100 e7c50ce0 Ntfs!NtfsReadMftRecord+0x86 (FPO: [Non-Fpo])

f78ae598 f71a6fac f78ae7ec 8b3a7100 e7c50ce0 Ntfs!NtfsReadFileRecord+0x7a (FPO: [Non-Fpo])

f78ae5d0 f718e46d f78ae7ec e7c50cd8 e7c50ce0 Ntfs!NtfsLookupInFileRecord+0x37 (FPO: [Non-Fpo])

f78ae67c f718e541 f78ae7ec e7c50cd8 f718e2cb Ntfs!NtfsWalkUpTree+0xbe (FPO: [Non-Fpo])

f78ae6d8 f718e263 f78ae7ec e7c50cd8 00000000 Ntfs!NtfsBuildNormalizedName+0x44 (FPO: [Non-Fpo])

f78ae704 f7196c2e f78ae7ec 8801d600 e7c50da0 Ntfs!NtfsQueryNameInfo+0x4b (FPO: [Non-Fpo])

f78ae774 f71a0ff6 f78ae7ec 88081cb0 8b5f9260 Ntfs!NtfsCommonQueryInformation+0x291 (FPO: [Non-Fpo])

f78ae7d8 f71a102f f78ae7ec 88081cb0 00000001 Ntfs!NtfsFsdDispatchSwitch+0x12a (FPO: [Non-Fpo])

f78ae8f4 8081df85 8b3a7020 88081cb0 88081cb0 Ntfs!NtfsFsdDispatchWait+0x1c (FPO: [Non-Fpo])

f78ae908 f721fd28 8b2fd220 8b60d308 8b54b020 nt!IofCallDriver+0x45 (FPO: [Non-Fpo])

f78ae934 8081df85 8b5f9260 88081cb0 88081cb0 fltMgr!FltpDispatch+0x152 (FPO: [Non-Fpo])

f78ae948 f721fd28 88081ed0 8b60d308 c000000d nt!IofCallDriver+0x45 (FPO: [Non-Fpo])

f78ae974 8081df85 8b54b020 88081cb0 88081cb0 fltMgr!FltpDispatch+0x152 (FPO: [Non-Fpo])

f78ae988 f5c62bec 88081cb0 8b54a980 88081cb0 nt!IofCallDriver+0x45 (FPO: [Non-Fpo])

WARNING: Stack unwind information not available. Following frames may be wrong.

f78ae99c f5c5e3ee 8b03b690 00000000 f78ae9c0 CtxSbx+0x5bec

f78ae9ac 8081df85 8b03b690 88081cb0 8b0528e0 CtxSbx+0x13ee

f78ae9c0 f61074e1 8b0528e0 8801d600 f78aea00nt!IofCallDriver+0x45 (FPO: [Non-Fpo])

f78ae9e4 f61075d0 8b0a9b80 00081cb0 f78aea08 CtxAltStr+0x44e1

f78ae9f4 8081df85 8b0a9b80 88081cb0 88081ed0 CtxAltStr+0x45d0

f78aea08 8b5cfc89 00000000 87ef0000 87ef9002 nt!IofCallDriver+0x45 (FPO: [Non-Fpo])

f78aec90 8081df85 8b5ef610 8b08c110 8b08c2a8 0x8b5cfc89 <------------ This looks odd what is this module?

f78aeca4 f7241607 8b08c2a8 00000000 f78aece8nt!IofCallDriver+0x45 (FPO: [Non-Fpo])

f78aecb4 f72412b2 8b08c2a8 8b392b70 87f11974 CLASSPNP!SubmitTransferPacket+0xbb (FPO: [Non-Fpo])

f78aece8 f7241533 00000000 00000e00 87f11808 CLASSPNP!ServiceTransferRequest+0x1e4 (FPO: [Non-Fpo])

f78aed0c 8081df85 8b392ab8 00000000 8b2a2670 CLASSPNP!ClassReadWrite+0x159 (FPO: [Non-Fpo])

f78aed20 f74c80cf 8b2b7d80 87f11998 f78aed44 nt!IofCallDriver+0x45 (FPO: [Non-Fpo])

f78aed30 8081df85 8b6e4020 87f11808 87f119bc PartMgr!PmReadWrite+0x95 (FPO: [Non-Fpo])

f78aed44 f7317053 87f119d8 8b6e8148 882b9888 nt!IofCallDriver+0x45 (FPO: [Non-Fpo])

f78aed60 8081df85 8b2b7cc8 87f11808 87f119fc ftdisk!FtDiskReadWrite+0x1a9 (FPO: [Non-Fpo])

f78aed74 f72cf4f5 885360cc 88536098 87f26810 nt!IofCallDriver+0x45 (FPO: [Non-Fpo])

f78aed90 f72d1517 87f11808 885360cc 8b60c470 volsnap!VspDecrementIrpRefCount+0x14f (FPO: [Non-Fpo])

f78aeda8 f72c0398 87f26810 87f26810 88536098 volsnap!VspWriteVolumePhase22+0x3d (FPO: [Non-Fpo])

f78aee30 f72d15d3 00f26810 885360cc 00000000 volsnap!VspAcquireNonPagedResource+0xc6 (FPO: [Non-Fpo])

f78aee50 8081e123 00000000 87f215e8 88536098 volsnap!VspWriteVolumePhase2+0x59 (FPO: [Non-Fpo])

f78aee80 f7241829 f78aeeb0 f72413ec 8b392ab8 nt!IopfCompleteRequest+0xcd (FPO: [Non-Fpo])

f78aee88 f72413ec 8b392ab8 87f215e8 00000001 CLASSPNP!ClassCompleteRequest+0x11 (FPO: [Non-Fpo])

f78aeeb0 8081e123 00000000 8b08c428 8b08c5c0 CLASSPNP!TransferPktComplete+0x1fd (FPO: [Non-Fpo])

f78aeee0 f7266545 8b6e30e8 8b08c428 f78aef24 nt!IopfCompleteRequest+0xcd (FPO: [Non-Fpo])

f78aeef0 f7265a8a 87f856a8 00000001 00000000 SCSIPORT!SpCompleteRequest+0x5e (FPO: [Non-Fpo])

f78aef24 f7265130 8b6e30e8 87f856a8 f78aef9b SCSIPORT!SpProcessCompletedRequest+0x6a7 (FPO: [Non-Fpo])

f78aef9c 8083211c 8b6e30a4 8b6e3030 00000000 SCSIPORT!ScsiPortCompletionDpc+0x2bd (FPO: [Non-Fpo])

f78aeff4 8088dba7 f3f4f92c 00000000 00000000 nt!KiRetireDpcList+0xca (FPO: [Non-Fpo])

f3f4f948 80a603d9 ffdffa02 f3f4f980 f3f4f980 nt!KiDispatchInterrupt+0x37 (FPO: [Uses EBP] [0,0,1])

f3f4f964 80a60577 8b6c226c f3f4f980 8088d91d hal!HalpCheckForSoftwareInterrupt+0x81 (FPO: [Non-Fpo])

f3f4f970 8088d91d 8b304c00 000001a3 f3f4fa04 hal!HalEndSystemInterrupt+0x67 (FPO: [Non-Fpo])

f3f4f970 809395e7 8b304c00 000001a3 f3f4fa04 nt!KiInterruptDispatch+0x5d (FPO: [0,2] TrapFrame @ f3f4f980)

f3f4fa04 80939c37 88139178 88028301 00000008 nt!ObpAllocateObject+0x199 (FPO: [Non-Fpo])

f3f4fa38 808f8d28 00000000 8b76aad0 f3f4fa70 nt!ObCreateObject+0x129 (FPO: [Non-Fpo])

f3f4fb44 80937a40 8b6afd10 00000000 88028398 nt!IopParseDevice+0x710 (FPO: [Non-Fpo])

f3f4fbc4 80933b74 00000000 f3f4fc04 00000042 nt!ObpLookupObjectName+0x5b0 (FPO: [Non-Fpo])

f3f4fc18 808eaee7 00000000 00000000 dffa7c01 nt!ObOpenObjectByName+0xea (FPO: [Non-Fpo])

f3f4fc94 808ec181 077ef198 c0140000 077ef14c nt!IopCreateFile+0x447 (FPO: [Non-Fpo])

f3f4fcf0 808eec10 077ef198 c0140000 077ef14c nt!IoCreateFile+0xa3 (FPO: [Non-Fpo])

f3f4fd30 808897ec 077ef198 c0140000 077ef14c nt!NtCreateFile+0x30 (FPO: [Non-Fpo])

f3f4fd30 7c82847c 077ef198 c0140000 077ef14c nt!KiFastCallEntry+0xfc (FPO: [0,0] TrapFrame @ f3f4fd64)

077ef22c 00000000 00000000 00000000 00000000 0x7c82847c

   

The current CPU IRQL is 2, at which pagefaults cannot be serviced, hence windows crashed the machine.

0: kd> !irql

Debugger saved IRQL for processor 0x0 -- 2 (DISPATCH_LEVEL)

 

Un-assembling from the return address of this unknown driver, to try to confirm that this is really driver code and that it really does belong in this call stack:

0: kd> ub 8b5cfc89

8b5cfc62 8bd7            mov     edx,edi

8b5cfc64 c60605          mov     byte ptr [esi],5

8b5cfc67 895e18          mov     dword ptr [esi+18h],ebx

8b5cfc6a c7460400020000  mov     dword ptr [esi+4],200h

8b5cfc71 c7460809000000  mov     dword ptr [esi+8],9

8b5cfc78 c7461c70fb5c8b  mov     dword ptr [esi+1Ch],8B5CFB70h

8b5cfc7f c64603e0        mov     byte ptr [esi+3],0E0h

8b5cfc83 ff158c805d8b    call    dword ptr ds:[8B5D808Ch]


Sure does call IofCallDriver

0: kd> dps 8B5D808Ch l1

8b5d808c  8081df40 nt!IofCallDriver

 

So what is this driver? In Windows every image starts with a PE header which starts with letters “MZ”. So I started looking for PE header. I took the offset of the module on the stack and went back to its page boundary, then I started moving 1 page back at a time looking for the PE header.

0: kd> dc 8b5cf000

8b5cf000  0010b9f2 f02b0000 3b063c8b 83137538  ......+..<.;8u..

8b5cf010  c08304e9 04f98304 b85fee73 00000001  ........s._.....

8b5cf020  9cb8c35e b98b5d86 00000010 ff8bd02b  ^....]......+...

8b5cf030  3b02348b 83137530 c08304e9 04f98304  .4.;0u..........

8b5cf040  b85fee73 00000001 335fc35e ccc35ec0  s._.....^._3.^..

8b5cf050  83ec8b55 78a04cec 538b5da4 c0b60f56  U....L.x.].SV...

8b5cf060  827ae857 ff330000 33f46589 f845c7db  W.z...3..e.3..E.

8b5cf070  00000400 8b084d8b 52510c55 50b4458d  .....M..U.QR.E.P

 

0: kd> dc 8b5cf000-1000

8b5ce000  01c73024 00000000 c70cc483 00800002  $0..............

8b5ce010  845e5f00 b10874db 0815ff01 8b8b5d80  ._^..t.......]..

8b5ce020  6a0c2444 15ff5000 8b5d8028 14c25b5d  D$.j.P..(.].][..

8b5ce030  cccccc00 cccccccc cccccccc cccccccc  ................

8b5ce040  6a306a56 2415ff00 8b8b5d80 74f685f0  Vj0j...$.].....t

8b5ce050  6a006a53 68026a01 8b5d0892 5d08ae68  Sj.j.j.h..].h..]

8b5ce060  ff006a8b 5d80ac15 ff56508b 5d80a815  .j.....].PV....]

8b5ce070  000d8b8b ff8b5da2 5d80b015 6a006a8b  .....].....].j.j


0: kd> dc 8b5cf000-1000*2

8b5cd000  0689c033 89044689 46890846 1046890c  3....F..F..F..F.

8b5cd010  89144689 ec831846 1c46891c 57204689  .F..F.....F..F W

8b5cd020  85244689 8d0574f6 02eb284e c033c933  .F$..t..N(..3.3.

8b5cd030  41890189 08418904 8b34468b 4e891048  ...A..A..F4.H..N

8b5cd040  14508b10 8b145689 4e891848 1c508b18  ..P..V..H..N..P.

8b5cd050  8b1c5689 4e891848 1c508b20 2424448d  .V..H..N .P..D$$

8b5cd060  244c8d50 186a510c 8b245689 6a302454  P.L$.Qj..V$.T$0j

8b5cd070  46c65205 46c70005 00000008 0c46c700  .R.F...F......F.

 

Finally I found the PE header for this image.

0: kd> dc 8b5cf000-1000*3

8b5cc000  00905a4d 00000003 00000004 0000ffff  MZ..............

8b5cc010  000000b8 00000000 00000040 00000000  ........@.......

8b5cc020  00000000 00000000 00000000 00000000  ................

8b5cc030  00000000 00000000 00000000 000000d0  ................

8b5cc040  0eba1f0e cd09b400 4c01b821 685421cd  ........!..L.!Th

8b5cc050  70207369 72676f72 63206d61 6f6e6e61  is program canno

8b5cc060  65622074 6e757220 206e6920 20534f44  t be run in DOS

8b5cc070  65646f6d 0a0d0d2e 00000024 00000000  mode....$.......

 

Using the built-in debugger extension !dh I dumped the header of this image to find the name.  Unfortunately there is no name for this image. This address 8b5cc000 is not in the loaded module list, which raised further suspicion. Either it is hiding its load address or this driver was not loaded by standard loading mechanism. This module date shows it’s pretty recent build.

0: kd> !dh 8b5cc000

 

File Type: DLL

FILE HEADER VALUES

     14C machine (i386)

       4 number of sections

4EA3461E time date stamp Sun Oct 23 04:09:26 2011

 

       0 file pointer to symbol table

       0 number of symbols

      E0 size of optional header

    2102 characteristics

            Executable

            32 bit word machine

            DLL

 

OPTIONAL HEADER VALUES

     10B magic #

    9.00 linker version

    A400 size of code

    2000 size of initialized data

       0 size of uninitialized data

    46C0 address of entry point

    1000 base of code

         ----- new -----

10000000 image base

    1000 section alignment

     200 file alignment

       1 subsystem (Native)

    5.00 operating system version

    0.00 image version

    5.00 subsystem version

   10000 size of image

     400 size of headers

    DF1F checksum

00100000 size of stack reserve

00001000 size of stack commit

00100000 size of heap reserve

00001000 size of heap commit

       0  DLL characteristics

       0 [       0] address [size] of Export Directory

    C91C [      3C] address [size] of Import Directory

       0 [       0] address [size] of Resource Directory

       0 [       0] address [size] of Exception Directory

       0 [       0] address [size] of Security Directory

    F000 [     3FC] address [size] of Base Relocation Directory

       0 [       0] address [size] of Debug Directory

       0 [       0] address [size] of Description Directory

       0 [       0] address [size] of Special Directory

       0 [       0] address [size] of Thread Storage Directory

       0 [       0] address [size] of Load Configuration Directory

       0 [       0] address [size] of Bound Import Directory

    C000 [     1A8] address [size] of Import Address Table Directory

       0 [       0] address [size] of Delay Import Directory

       0 [       0] address [size] of COR20 Header Directory

       0 [       0] address [size] of Reserved Directory

 

 

SECTION HEADER #1

   .text name

    A354 virtual size

    1000 virtual address

    A400 size of raw data

     400 file pointer to raw data

       0 file pointer to relocation table

       0 file pointer to line numbers

       0 number of relocations

       0 number of line numbers

60000020 flags

         Code

         (no align specified)

         Execute Read

 

SECTION HEADER #2

  .rdata name

    13AC virtual size

    C000 virtual address

    1400 size of raw data

    A800 file pointer to raw data

       0 file pointer to relocation table

       0 file pointer to line numbers

       0 number of relocations

       0 number of line numbers

40000040 flags

         Initialized Data

         (no align specified)

         Read Only

 

SECTION HEADER #3

   .data name

     4B0 virtual size

    E000 virtual address

     200 size of raw data

    BC00 file pointer to raw data

       0 file pointer to relocation table

       0 file pointer to line numbers

       0 number of relocations

       0 number of line numbers

C0000040 flags

         Initialized Data

         (no align specified)

         Read Write

 

SECTION HEADER #4

  .reloc name

     576 virtual size

    F000 virtual address

     600 size of raw data

    BE00 file pointer to raw data

       0 file pointer to relocation table

       0 file pointer to line numbers

       0 number of relocations

       0 number of line numbers

42000040 flags

         Initialized Data

         Discardable

         (no align specified)

         Read Only


0: kd> !lmi 8b5cc000 

Loaded Module Info: [8b5cc000]

ffffffff8b5cc000 is not a valid address

 

0: kd> lmvm 8b5cc000 

start    end        module name


Checking the import table it does have calls to raise and lower the IRQL. We get to the import table by taking the base address plus the offset to the Import Address Table Directory.  The below output is just a snippet of the whole table.

0: kd> dps 8b5cc000+c000

8b5d8000  80a603f4 hal!KfLowerIrql

8b5d8004  80a5ff00 hal!KeGetCurrentIrql

8b5d8008  80a600b4 hal!KfRaiseIrql

 

Dumping the entire image contents in memory to find more clues about this driver.  Again, I am only showing snippets of the whole output.

0: kd> dc 8b5cc000  L?10000/4

8b5cc000  00905a4d 00000003 00000004 0000ffff  MZ..............

8b5cc010  000000b8 00000000 00000040 00000000  ........@.......

8b5cc020  00000000 00000000 00000000 00000000  ................

8b5cc030  00000000 00000000 00000000 000000d0  ................

8b5cc040  0eba1f0e cd09b400 4c01b821 685421cd  ........!..L.!Th

8b5cc050  70207369 72676f72 63206d61 6f6e6e61  is program canno

8b5cc060  65622074 6e757220 206e6920 20534f44  t be run in DOS

8b5cc070  65646f6d 0a0d0d2e 00000024 00000000  mode....$.......

8b5d5e20  76000000 66697265 77252079 00000a5a  ...verify %wZ...

8b5d5e30  31000000 35343332 39383736 33323130  ...1234567890123

8b5d5e40  37363534 31303938 35343332 39383736  4567890123456789

8b5d5e50  33323130 37363534 31303938 35343332  0123456789012345

8b5d5e60  39383736 33323130 37363534 31303938  6789012345678901

8b5d5e70  00343332 66000000 646e756f 67697320  234....found sig

8b5d5e80  7574616e 3d206572 0a752520 b4000000  nature = %u.....   

 

Interestingly this image has other images (modules) embedded in it.  We can see other PE headers, which again have no name.  This looks highly suspicious and resembles behavior used by malicious software.

8b5d0970  f775c085 4848c78b 4dc35f5e 0300905a  ..u...HH^_.MZ...

8b5d0980  04000000 ff000000 b80000ff 00000000  ................

8b5d0990  40000000 00000000 00000000 00000000  ...@............

8b5d09a0  00000000 00000000 00000000 00000000  ................

8b5d09b0  00000000 c8000000 0e000000 000eba1f  ................

8b5d09c0  21cd09b4 cd4c01b8 69685421 72702073  ...!..L.!This pr

8b5d09d0  6172676f 6163206d 746f6e6e 20656220  ogram cannot be

8b5d09e0  206e7572 44206e69 6d20534f 2e65646f  run in DOS mode.

8b5d2200  4d00004e 0300905a 04000000 ff000000  N..MZ...........

8b5d2210  b80000ff 00000000 40000000 00000000  ...........@....

8b5d2220  00000000 00000000 00000000 00000000  ................

8b5d2230  00000000 00000000 00000000 f0000000  ................

8b5d2240  0e000000 000eba1f 21cd09b4 cd4c01b8  ...........!..L.

8b5d2250  69685421 72702073 6172676f 6163206d  !This program ca

8b5d2260  746f6e6e 20656220 206e7572 44206e69  nnot be run in D

8b5d2270  6d20534f 2e65646f 240a0d0d 00000000  OS mode....$....

 

After ensuring my AV definitions were up to date, I decided to dump this memory contents into a file onto my machine. The moment contents were written my AV Microsoft Forefront Endpoint Protection caught a Trojan in this file.

0: kd> .writemem c:\temp\drv.sys 8b5cc000   L?10000
Writing 10000 bytes................................

   
image001

 

image003


Before any further trouble shooting we asked the customer to clean the Trojan. Once we did that bug checks disappeared.


Hope you enjoyed reading this post.

Hotfix to Enable Mini-Filter Performance Diagnostics With XPerf for Windows Server 2008R2

$
0
0

Greetings ntdebugging community, Bob here again and today I would like to let everyone know about a new feature implemented in Windows Server 2008 R2’s kernel and filter manager binaries released in knowledge base article 2666390.

 

Beginning with this update, a minifilter that is adversely affecting system performance can be identified in Windows 2008 R2. These measurements are taken within the filter manager and measure the execution time of the minifilter’s pre and post operations.  On every file access file system minifilters are given the chance to perform pre and post modifications to IO operations within the driver stack.

 

To start recording the operations and to view the results, the latest version of the Windows Performance Toolkit from the Windows 8 beta ADK needs to be installed on the system. How to install the package can be found in the KB article.

 

The command to start recording and to record stack traces is (note that these commands may wrap depending on your screen resolution, they are intended to be typed in one line):

xperf -on  PROC_THREAD+LOADER+FLT_IO_INIT+FLT_IO+FLT_FASTIO+FLT_IO_FAILURE+FILENAME -stackwalk MiniFilterPreOpInit+MiniFilterPostOpInit

 

The command above starts a trace with most of the buffering done in memory. The command below specifies a file used as a backing store to use less memory:

xperf -start -f <traceFile> -on  PROC_THREAD+LOADER+FLT_IO_INIT+FLT_IO+FLT_FASTIO+FLT_IO_FAILURE+FILENAME -stackwalk MiniFilterPreOpInit+MiniFilterPostOpInit

 

Note: <traceFile> is the path of the file to store the backing file (i.e. trace.etl).  It is recommended that the backing file be placed on a drive other than the one experiencing the problem, to avoid xperf operations interfering with or contributing to the scenario you are measuring.  You can add the +FILENAME switch if you want to log names of the files that the minifilter is operating on.

 

You may not know if a minifilter is causing the problem until after you begin data analysis, so you can add the filter manager tracing to the Driver Delays command presented in my previous blog, Using Xperf to investigate slow I/O issues.

xperf -on PROC_THREAD+LOADER+CSWITCH+FILENAME+FILE_IO+FILE_IO_INIT+DRIVERS+FLT_IO_INIT+FLT_IO+FLT_FASTIO+FLT_IO_FAILURE -f kernel.etl -stackwalk CSwitch+DiskReadInit+DiskWriteInit+DiskFlushInit+FileCreate+FileCleanup+FileClose+FileRead+FileWrite+MiniFilterPreOpInit+MiniFilterPostOpInit -BufferSize 1024 -MaxBuffers 1024 -MaxFile 1024 -FileMode Circular

 

To stop the trace and flush data to the trace file, type the following command:

xperf –stop –d mymergedtrace.etl

 

The file specified must be a different file name than the one provided with the –f switch if that option was used when starting the trace.

 

Before we begin looking at the trace, let’s talk a little about what we are tracing. We are tracing fltmgr calling into the minifilter.  The fltmgr manages minifilters.  When a file operation is done, each minifilter has a chance to process and optionally modify an operation on a file before and after the call goes into the file system.  For example, in this case we are going to see results of the time it took for the minifilter to do a “pre create” operation. That is the function that is called before the “create file” function is sent to the file system.  This tracing can also track the time it took for the minifilter to do a “post create” operation.  That is the minifilter function that gets called after the create file is sent to the file system.  So the minifilter is called before and after every file system operation. The minifilter may not have functions to process before and after, however it has the option.

 

Once the problem is traced, the data can be viewed by clicking on the merged etl file and you will see a similar presentation as below.

 image001 

 

The Mini-Filter Delays are in the Storage display. So click on the triangle on the left side of Storage and you will see:

image002 

 

To get more detail, right click on the “Mini-Filter Delays” section and select “Add To New Analysis View”and that will bring up minifilter delays detail as below.

Image3

 

The Duration is the time in micro seconds of the total time delay in the minifilter. So in the example above the scanner minifilter did 4,068 requests and it took 30 seconds total. 

 

We can expand the display by clicking on the triangle next to scanner.sys to get a more detailed view, I chose to investigate scanner.sys because it has the longest Duration.  I dragged the “Major Function Name (Count)” and “File Name” columns to the left of the yellow bar to get detail and used the triangles to expand the ProcessID with the longest Duration, and then I expanded the File Name with the longest Duration.   To get more usable space on your monitor, you can click the X to close the Graph Explorer (to get it back, from the Window menu choose Graph Explorer).

Image4

 

On the display above it shows the scanner.sys filter was operating on the file “C:\users\Administrator\Desktop\scanner\test2.txt”   The green 6 is the total operations. So there were three cleanups and three creates.  So the display is telling us that the filter did 3 “create” operations that took close to 15 seconds and 3 cleanup operations that took 5 milliseconds on the file.

 

If you prefer operating from a command line rather than a GUI, this same data can be extracted using the below command.  Note that -min specifies the time in ns, so this command will show delays longer than 100 ms.

xperf -i mymergedtrace.etl -a minifilterdelay -min 100000

 

This will give you output similar to the below information:

[1/2]    100.0%

[2/2]    100.0%

There are 3 MiniFilter Delay completion events in given time range.

   Process Name,     Module Name,       Call (us),     Return (us),  Time (us), Major Func Name,                       Filename

   explorer.exe,     scanner.sys,        18649145,        23645502,    4996357,          CREATE, C:\Users\Administrator\Desktop\scanner\test2.txt

   explorer.exe,     scanner.sys,        23646355,        28644638,    4998282,          CREATE, C:\Users\Administrator\Desktop\scanner\test2.txt

   explorer.exe,     scanner.sys,        28645194,        33645158,    4999964,          CREATE, C:\Users\Administrator\Desktop\scanner\test2.txt

 

It seems there is a problem in the create file handling of the scanner.sys minifilter.  For some reason the create file function in the minifiltertook 5 seconds for each operation.  The vendor of this filter would need to do additional investigation to identify why the filter has this delay.

Viewing all 98 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>