Site Administrator, Forum General Manager, BSOD Kernel Dump Expert
- Feb 19, 2012
- New Jersey Shore
Thank you, Isaac!They are offsets from the function start. When you look at a callstack like this, it start from the bottom and goes like "this function called the function above me, which called the function above it, and so on and so forth." For each function listed, you have the three parts: the module name, the function name, and the offset, respectively, as followed:
Listed with kv or whatever, this portion of the output is the "Call Site" of the listing, which is basically just the name of the return address (RetAddr), complete with symbols to make it readable for humans to interpret what the function is and its intent. It can be read like an address on an envelope. The module name is the city, the function name is the street, and the offset is the address of the house. If you were to just say to someone asking for directions to your place "I live on such-n-such road" they have to scan down the whole road to find you. Otherwise if you add your address, the process of discovering your house is greatly alleviated.Code:
USBPORT!USBPORT_AssertSig+0x25 /\ /\ /\ module function name offset
It's important to understand what is going on here in a callstack. Read up on my basic description of code flow in BSOD Method & Tips. I'll add it here for quick reference:
Once you understand that, it's easier to get an idea what's going on in a callstack. Let's use your example. Starting with nt!KxStartSystemThread at the bottom, this function sets up the thread to start doing work. Then when necessary, it calls into the function above it (nt!PspSystemThreadStartup) to continue it. This continues till what appears to be the real work starting (usbhub!UsbhHubWorker), as the rest is just initial setup. It's then performing various and sundry USB-related tasks for some USB I/O.The flow of which all operations are going. The basics of it is you have a thread, and in that thread you have an initial function responsible for a specific task. In order to accomplish it, it will need the assistance of other functions to do so.
Much like a product being built by a company, you have it go through various hands and sometimes even various places before the finished product is made. Tack on also all the other personnel responsible for various indirect duties to supply the needs of those actually creating the product. If you had the same person/people doing all the tasks, then things slow to a crawl and you're lucky to even have a satisfactory result in the end.
That's much like what goes on in your typical code flow. It is not enough to have a "one function to rule them all", that's just daft. Rather, you start with the initial function, like say, for drawing a popup window. There's a multitude of facets to this job, so the initial thread will call one function to do something, like DirectX telling it to draw the window, which DirectX will figure "ok, but with what?" and then pass that request to another, and then that to another, and then so on and so forth. How things fragment and flow through all this process of events is the essence of code flow.
It's important to understand that each function is not really calling the function above it from the offset specified in the call site nor from the address specified in the return address (which are both the same thing). Rather, it calls it from the beginning of the function. So, usbhub!UsbhHubWorker is not calling into usbhub!UsbhHubSSH_Worker at offset 0x2d, but is calling it straight into the beginning of the function. The offsets and return addresses are rather just displaying where things continues once and if the function returns. That means the function has done its job, and the flow code operation is now returning to the function below it in the callstack.
I'm sure it's difficult to be able to interpret this process without any visuals, which I really can't provide any. The closest I can provide is to show some disassembly. Let's start with a simple idle loop from a kernel dump I have stored away:
Ignoring the first two frames (it's a little too complicated right now to explain), let's start at nt!KiIdleLoop+0x21. I'll whip out the disassembly window and copy and paste the entire name from module to offset, getting as followed:Code:
Child-SP RetAddr Call Site fffffa60`01d8ece0 fffff800`01ca8b83 intelppm+0x29ed fffffa60`01d8ed10 fffff800`01ca88a1 nt!PoIdle+0x183 fffffa60`01d8ed80 fffff800`01e75860 nt!KiIdleLoop+0x21 fffffa60`01d8edb0 00000000`fffffa60 nt!zzz_AsmCodeRange_End+0x4 fffffa60`01d6ad00 00000000`00000000 0xfffffa60
The bold is where the disassembly window highlights, telling me that nt!KiIdleLoop+0x21 points exactly to here. As you can tell, its position is 0x21 away from the beginning of the function. Now take notice of the call function right before it. That's the call to nt!PoIdle, which you can see in the callstack is the next function that was called into. What happened is nt!KiIdleLoop started, ran its course, then it eventually said "I need to call PoIdle cuz it needs to do something", so it called it as such. Now, did it call it at nt!PoIdle+0x183 like the callstack said? Nope. Rather, it says it called straight into the beginning of the PoIdle function, at address fffff800`01ca8a00. I'll verify:Code:
... nt!KiIdleLoop: fffff800`01ca8880 4883ec28 sub rsp,28h fffff800`01ca8884 65488b1c2520000000 mov rbx,qword ptr gs:[20h] fffff800`01ca888d eb20 jmp nt!KiIdleLoop+0x2f (fffff800`01ca88af) fffff800`01ca888f 33c9 xor ecx,ecx fffff800`01ca8891 440f22c1 mov cr8,rcx fffff800`01ca8895 488d8b80380000 lea rcx,[rbx+3880h] fffff800`01ca889c e85f010000 call nt!PoIdle (fffff800`01ca8a00) fffff800`01ca88a1 fb sti fffff800`01ca88a2 b902000000 mov ecx,2 fffff800`01ca88a7 440f22c1 mov cr8,rcx fffff800`01ca88ab 80630700 and byte ptr [rbx+7],0 fffff800`01ca88af 803d9c881c0000 cmp byte ptr [nt!HvlEnableIdleYield (fffff800`01e71152)],0 fffff800`01ca88b6 7402 je nt!KiIdleLoop+0x3a (fffff800`01ca88ba) fffff800`01ca88b8 f390 pause fffff800`01ca88ba fb sti fffff800`01ca88bb 90 nop fffff800`01ca88bc 90 nop fffff800`01ca88bd fa cli ...
Blammo. First line of code in the function is where it pointed too, not the offset described in the callstack. Now what's going to happen here is that PoIdle will runs its stuff, until eventually it, too, needs to call another function. Let's check nt!PoIdle+0x183 which is what the callstack gave us as the call site:Code:
... nt!PoIdle: fffff800`01ca8a00 4883ec68 sub rsp,68h fffff800`01ca8a04 f605ff09130002 test byte ptr [nt!PpmIdlePolicy+0x2 (fffff800`01dd940a)],2 fffff800`01ca8a0b 0f85b9010000 jne nt!PoIdle+0x1ca (fffff800`01ca8bca) fffff800`01ca8a11 48895c2470 mov qword ptr [rsp+70h],rbx fffff800`01ca8a16 4889742458 mov qword ptr [rsp+58h],rsi fffff800`01ca8a1b 48897c2450 mov qword ptr [rsp+50h],rdi fffff800`01ca8a20 488b39 mov rdi,qword ptr [rcx] fffff800`01ca8a23 4885ff test rdi,rdi fffff800`01ca8a26 0f841d050600 je nt! ?? ::FNODOBFM::`string'+0x31b79 (fffff800`01d08f49) fffff800`01ca8a2c 8b5f08 mov ebx,dword ptr [rdi+8] fffff800`01ca8a2f f6c302 test bl,2
Notice again the call instruction before it? In this case it's a little more convoluted in that the address isn't a static address like before but it's one made by doing some math with some registers and junk and then using the resulting memory address' contents as a reference point. However if we did all of that math n stuff, we'd most likely come up with the start for the next function listed in the callstack, which is somewhere in the module intelppm. What's going to happen, then is that is for whatever reason the function in intelppm returns, the code flow will return to nt!PoIdle+0x183, then if for whatever reason the PoIdle function returns, everything will continue at nt!KiIdleLoop+0x21, and so on.Code:
... fffff800`01ca8b68 0f8456050600 je nt! ?? ::FNODOBFM::`string'+0x31cf4 (fffff800`01d090c4) fffff800`01ca8b6e 83fb02 cmp ebx,2 fffff800`01ca8b71 0f845b050600 je nt! ?? ::FNODOBFM::`string'+0x31d02 (fffff800`01d090d2) fffff800`01ca8b77 33d2 xor edx,edx fffff800`01ca8b79 4a8b4cef28 mov rcx,qword ptr [rdi+r13*8+28h] fffff800`01ca8b7e 42ff54ef20 call qword ptr [rdi+r13*8+20h] fffff800`01ca8b83 85c0 test eax,eax fffff800`01ca8b85 0f8851050600 js nt! ?? ::FNODOBFM::`string'+0x31d0c (fffff800`01d090dc) fffff800`01ca8b8b 85db test ebx,ebx fffff800`01ca8b8d 0f859e050600 jne nt! ?? ::FNODOBFM::`string'+0x31d61 (fffff800`01d09131) fffff800`01ca8b93 33c9 xor ecx,ecx fffff800`01ca8b95 ff1505a60d00 call qword ptr [nt!_imp_KeQueryPerformanceCounter (fffff800`01d831a0)] fffff800`01ca8b9b 492bc6 sub rax,r14 ...
You're probably wondering why intelppm doesn't have a function listed. That's because in my case, I do not have access to any symbols for intelppm. Because there are no symbols, no function names and whatnot can be displayed, so it just leaves me with intelppm with a very big offset, not an offset from the beginning of a function, but from the beginning of the entire module. So it's important to have symbols whenever possible. It's not impossible to go without em, but it makes things quite more difficulty because then you have to ascertain through disassembling and reading the code just what the function is actually doing. The function names there provided by the symbols are to help you discover what each function's responsibility is.
I hope that kinda clarifies something about what's going on in a callstack. If you need more assistance I'll do what I can to help, but that should at least get things going for ya.