Saturday, September 20, 2014

Stacks, stacks, and more stacks!

If you've ever debugged a crash dump before, user-mode or kernel, you've very likely seen a stack. If you debug crash dumps quite often, you've seen and traversed thousands of stacks, maybe more! For a long time I knew the basics behind a stack and what I was looking at/through for the most part, but really beyond that I didn't know much for quite awhile until I dug deep into dozens of documents and books as time went on. With that said, let's discuss stacks and their mechanics!

The stack excerpt below is from an x86 0xF4 crash dump. The cause of the bug check and such is irrelevant, and this stack is purely for explanatory purposes. With that said, turn off your tingly BSOD senses for a moment and look at the functions and such for what they are, and not what they could have done to play a role in the crash : )

 kd> k  
 ChildEBP RetAddr   
 a1e72ccc 81a8eec4 nt!KeBugCheckEx+0x1e  
 a1e72cf0 81a1e85f nt!PspCatchCriticalBreak+0x73  
 a1e72d20 81a1e806 nt!PspTerminateAllThreads+0x2c  
 a1e72d54 8185c986 nt!NtTerminateProcess+0x1c1  
 a1e72d54 77725d14 nt!KiSystemServicePostCall  
 0028f0d0 00000000 0x77725d14  

k* will perform a stack unwind, or more specifically will display the stack backtrace. * denotes the placeholder for the various possible parameters that we can use, such as - kb, kv, kc, kd, the list goes on. Each of those all does something different, but for now we'll stick to plain old k for example purposes.

Let's break this down one at a time!

ChildEBP

ChildEBP - First we have our ChildEBP, otherwise known as the 'base pointer', which is essentially nothing more than a pointer to a stack frame that has been set up by a piece of code (a reference to a stack of memory). As an example, let's take a look at the following command:

 dd a1e72cf0 l1  

dd - This is a variation of plain old d, which simply means dump. If we however use dd, this will display/dump dword(s).

a1e72cf0 - This is a pointer address from our stack, specifically from nt!PspCatchCriticalBreak+0x73.

L1 - I've always been under the impression that this tells WinDbg to print a line based on what we're looking to do as far as the command goes. I've searched the WinDbg help doc far and wide for a conclusive answer, but I haven't found one yet. There are billions of people out there far smarter than I, and hopefully one of them reads my blog to tell me what it really does!

EDIT: Hilariously enough, someone smarter than me did happen to come along. Thanks to Blair Foster, I now understand what L1 really does.
its a size/range specifier. In the case of d* its used as size. So, dd L1 displays 1 dword, db L1 is 1 byte, etc.
Thanks, Blair!

If we go ahead and run this command, here's what we get:

 kd> dd a1e72cf0 l1  
 a1e72cf0 a1e72d20  

Look familiar? a1e72d20 is another pointer address from our stack, but more specifically the pointer from the previous function in the stack - nt!PspTerminateAllThreads+0x2c. With this known, we can conclude that the ChildEBP also stores the previous function address.

 a1e72d20 81a1e806 nt!PspTerminateAllThreads+0x2c  

In this specific example, we don't have a good stack to show us what would happen if we went all the way back to what function spawned/began the stack. Taking a quick look through some x64 dumps (didn't have any good x86 ones), I dug up a quick stack. Try not to get overwhelmed with the differences between x86 and x64 as far as stack goes (even though there aren't really any). It's just more bits and bobs!

 3: kd> k  
 Child-SP          RetAddr             
 fffff880`033dfa58 fffff800`0302da3b nt!KeBugCheckEx  
 fffff880`033dfa60 fffff800`031f0463 hal!HalBugCheckSystem+0x1e3  
 fffff880`033dfaa0 fffff800`0302d700 nt!WheaReportHwError+0x263  
 fffff880`033dfb00 fffff800`0302d052 hal!HalpMcaReportError+0x4c  
 fffff880`033dfc50 fffff800`0302cf0d hal!HalpMceHandler+0x9e  
 fffff880`033dfc90 fffff800`03020e88 hal!HalpMceHandlerWithRendezvous+0x55  
 fffff880`033dfcc0 fffff800`030d84ac hal!HalHandleMcheck+0x40  
 fffff880`033dfcf0 fffff800`030d8313 nt!KxMcheckAbort+0x6c  
 fffff880`033dfe30 fffff800`030d07b8 nt!KiMcheckAbort+0x153  
 fffff880`09e9f638 00000000`00000000 nt!memcpy+0x208  

Just by looking at the stack we can see it begins right at nt!memcpy+0x208, but let's show it the way we've been demonstrating:

 3: kd> dd fffff880`033dfe30 l1  
 fffff880`033dfe30 00000000  

We can see at address fffff880`033dfe30, the return value was zero. This tells us it wasn't called from another prior function, etc, and is the very beginning of the stack. Given the function itself is nt!memcpy, this is understandable.

RetAddr

RetAddr - Now that we understand our base pointer, we have our RetAddr, which implies return address. This is done because quite simply the CPU without being told what to do is pretty useless. We need to let it know what address to return to after it's done successfully executing the code, because all it's concerned about is instructions and executing their code. It has no idea how to handle them without this stuff set in place, or what to do after it's done. With that said, this is why we have our return address.

If we take another look at our x64 stack excerpt:

 3: kd> k  
 Child-SP          RetAddr             
 fffff880`033dfa58 fffff800`0302da3b nt!KeBugCheckEx  
 fffff880`033dfa60 fffff800`031f0463 hal!HalBugCheckSystem+0x1e3  
 fffff880`033dfaa0 fffff800`0302d700 nt!WheaReportHwError+0x263  
 fffff880`033dfb00 fffff800`0302d052 hal!HalpMcaReportError+0x4c  
 fffff880`033dfc50 fffff800`0302cf0d hal!HalpMceHandler+0x9e  
 fffff880`033dfc90 fffff800`03020e88 hal!HalpMceHandlerWithRendezvous+0x55  
 fffff880`033dfcc0 fffff800`030d84ac hal!HalHandleMcheck+0x40  
 fffff880`033dfcf0 fffff800`030d8313 nt!KxMcheckAbort+0x6c  
 fffff880`033dfe30 fffff800`030d07b8 nt!KiMcheckAbort+0x153  
 fffff880`09e9f638 00000000`00000000 nt!memcpy+0x208  

According to the stack, HalpMceHandlerWithRendezvous was called by HalHandleMcheck. We know that when the work HalpMceHandlerWithRendezvous was doing was finished, it would return back to HalHandleMcheck. We can confirm this by taking a look at the return memory addresses and using the ln command:

 3: kd> ln fffff800`03020e88  
 (fffff800`03020e48)  hal!HalHandleMcheck+0x40  

ln - This command will display the function/routine at or near the given address.

Bingo! Execution will resume afterwards in HalHandleMcheck, specifically 64 bytes from the start of the function.

Functions/Call Site

Functions/Call Site - Last but not least, we have our third column (it's not labeled above in any of the stack excerpts). Interestingly enough, x64 stacks do label their function columns, specifically as Call Site. In the above stack x64 excerpts however, I removed them to avoid confusion! : )

Here is what a non-edited x64 stack looks like:

 3: kd> k  
 Child-SP          RetAddr           Call Site  
 fffff880`033dfa58 fffff800`0302da3b nt!KeBugCheckEx  
 fffff880`033dfa60 fffff800`031f0463 hal!HalBugCheckSystem+0x1e3  
 fffff880`033dfaa0 fffff800`0302d700 nt!WheaReportHwError+0x263  
 fffff880`033dfb00 fffff800`0302d052 hal!HalpMcaReportError+0x4c  
 fffff880`033dfc50 fffff800`0302cf0d hal!HalpMceHandler+0x9e  
 fffff880`033dfc90 fffff800`03020e88 hal!HalpMceHandlerWithRendezvous+0x55  
 fffff880`033dfcc0 fffff800`030d84ac hal!HalHandleMcheck+0x40  
 fffff880`033dfcf0 fffff800`030d8313 nt!KxMcheckAbort+0x6c  
 fffff880`033dfe30 fffff800`030d07b8 nt!KiMcheckAbort+0x153  
 fffff880`09e9f638 00000000`00000000 nt!memcpy+0x208  

As we can see, there's Call Site! In x86 crash dumps however, it's not labeled. I don't need to paste the stack excerpt we used from above as I didn't edit it, so you can just scroll back up and take a quick look.

It should be no mystery that this third and final column displays the function (and routine) names throughout the stack. Do note that they are in direct correlation with the current loaded symbols. If you don't have proper symbols, you're going to get an unresolved function (or routine), and it's going to look like junk.

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

Thanks for reading! In my next post I plan on going into the x86 registers (maybe x64 although it'll be a lot of work -- may save that for a future post).

No comments:

Post a Comment