It is assumed that the reader has a basic understanding of kernel debuggingWindows NT on Intel-based systems, as that is used as a basis forcomparison. Any debug commands listed should work in WINDBG as well as theKD debuggers (ALPHAKD, MIPSKD, PPCKD).
Part 1: The Windows NT stack on a RISC system
On an Intel-based system, a stack dump using the KB or KV command givesyou a wealth of information. This is because of three things that are donein Intel assembly when a function call occurs:
- Before the call, all arguments are pushed onto the stack (with a few exceptions).
- During the call, the return address of the function making the call is pushed on the stack.
- After the call occurs, most functions push the base pointer (EBP) onto the stack.
These three steps allow the debugger to display a stack showing the firstthree arguments passed to each function, as well as other information (suchas trap frames). On a RISC system, the frame pointers (equivalent to theEBP) are present as are the return addresses. However, arguments are passedin special argument registers and cannot be found on the stack. RISCsystems will use the stack for more permanent storage of variables (as youwill see later).
On MIPs-based and Dec Alpha-based systems, the arguments are passed inorder, first to last, in registers labeled a0, a1, a2 and so on (referredto as the argument registers). There are 4 such registers on MIPs systems(a0 - a3) and 6 on an Alpha (a0 - a5). If there are more arguments than canfit in the aX registers, both types of systems use the temporary (t0 - t7)registers. On PPC systems, the arguments are passed in order, first tolast, in the registers r3, r4, r5 - r31.
The following examples show an Intel system and an Alpha system stack(using the KB and KV command on the Intel system, the KB command only onthe alpha):
KDx86> kb FramePtr RetAddr Param1 Param2 Function Name fcbc69ac 80128bfb fcc86588 fcdb3808 NT!KiTrap0E+0x252 fcbc6a58 8013b26b 00000001 f8bc6ee0 NT!MmAccessFault+0x1cd fcbc6a58 80102af6 00000001 f8bc6ee0 NT!KiTrap0E+0xa7 fcbc6af8 801bc367 fcc83000 fa4e6000 NT!@IofCompleteRequest@8+0x15c fcbc6b04 801bd84b fcbc6c80 fcbc6c84 NTFS!NtfsCompleteRequest+0x58 fcbc6b14 801bdb7b fcdb3808 fcd75020 NTFS!NtfsCommonWrite+0xee8 fcdb3808 fcbc6d34 00000043 00000000 NTFS!NtfsCommonWrite+0x1218 KDx86> kv fcbc69ac 80128bfb NT!KiTrap0E+0x252 (FPO: [0,0] TrapFrame @ fcbc69ac) fcbc6a58 8013b26b NT!MmAccessFault+0x1cd fcbc6a58 80102af6 NT!KiTrap0E+0xa7 (FPO: [0,0] TrapFrame @ fcbc6a6c) fcbc6af8 801bc367 NT!@IofCompleteRequest@8+0x15c fcbc6b04 801bd84b NTFS!NtfsCompleteRequest+0x58 (FPO: [3,0,2]) fcbc6b14 801bdb7b NTFS!NtfsCommonWrite+0xee8 (FPO: [seh] [0,0,0]) fcdb3808 fcbc6d34 NTFS!NtfsCommonWrite+0x1218 KDalpha> kb FramePtr RetAddr Param1 Param2 Function Name f18a6f20 80554ca8 818e20a0 001901ac NT!KeBugCheckEx+0x58 f18a7220 8056c920 818e20a0 001901ac NTFS!NtfsExceptionFilter+0x118 f18a7250 800b0dc8 001901ac 001901ac NTFS!NtfsCommonFileSystemControl+0xa0 f18a7260 800d8ef0 001901ac 001901ac NT!OtsCSpecificHandler+0x78 f18a72b0 800b08cc 001901ac 001901ac NT!RtlpExecuteHandlerForException+0x10 f18a72c0 800c4360 001901ac 001901ac NT!RtlDispatchException+0xec f18a7600 800c2840 001901ac 001901ac NT!KiDispatchException+0x3f0 f18a7900 800c2980 001901ac 001901ac NT!KiExceptionDispatch+0x50 f18a79a0 80082f80 001901ac 001901ac NT!KiMemoryManagementException+0xc8 f18a7ba0 80552c94 001901ac 001901ac NT!ExFreePool+0x270 f18a7bf0 800a758c 001901ac 001901ac NTFS!NtfsFreeFcbTableEntry+0xa4
NOTE: In the above stacks, the Param3 column was removed for readability.
The first two stacks are from a STOP 0xA on an Intel system. In the KBcommand output, the first two parameters passed to each function can beseen on the stack and you would normally see the third parameter as well.In the KV command, trap frames associated with the function call also showup.
The third stack is from a STOP 0x24 occurring on a DEC Alpha system. Theframe pointers and the return addresses are all valid, however, theparameters, although they may appear to be valid addresses, are notnecessarily arguments. Generally, the parameters are values that are pushedon the stack by the functions themselves.
Part 2: Finding the trap frame on a Windows NT RISC system
On a RISC system, the KV command will not show the trap frame as it will onan Intel system; more work is required to find the trap frame. In mostcases, the frame pointer from one of the exception handling routines willbe used as a trap frame and will need to identify the correct function. Ifthat is not possible, you might also be able to identify a function whichpassed the trap frame as an argument.
Here is an example from the STOP 0x24 stack listed above:
KDalpha> kb FramePtr RetAddr Function Name f18a6f20 80554ca8 NT!KeBugCheckEx+0x58 f18a7220 8056c920 NTFS!NtfsExceptionFilter+0x118 f18a7250 800b0dc8 NTFS!NtfsCommonFileSystemControl+0xa0 f18a7260 800d8ef0 NT!OtsCSpecificHandler+0x78 f18a72b0 800b08cc NT!RtlpExecuteHandlerForException+0x10 f18a72c0 800c4360 NT!RtlDispatchException+0xec f18a7600 800c2840 NT!KiDispatchException+0x3f0 f18a7900 800c2980 NT!KiExceptionDispatch+0x50 f18a79a0 80082f80 NT!KiMemoryManagementException+0xc8 f18a7ba0 80552c94 NT!ExFreePool+0x270 f18a7bf0 800a758c NTFS!NtfsFreeFcbTableEntry+0xa4 f18a7c20 80580a80 NT!RtlDeleteElementGenericTable+0x6c
First, recognize where the exception handling code starts and ends. Thisindicates what portion of the stack is exception handling code and wherethe trap occurred. In the above stack, the first exception handling routineis NT!KiMemoryManagementException, the rest of the stack is all exceptioncode. This means that the actual line which caused the trap isNT!ExFreePool+0x270.
Next, walk through the code for each function, starting withKiMemoryManagementException until you find one that deals with the trapframe. There are two ways in this stack - first the FramePtr ofKiMemoryManagementException should point to the trap frame. Additionally,the trap frame is passed to KiDispatchException as its third parameter.
Doing a !Trap on the FramePointer for KiMemoryManagementException shows thefollowing:
KDalpha> !trap f18a79a0 Debugger extension library [kdextalp.dll] loaded v0 = 00000000 00000040 a0 = 00000000 00000000 t0 = 00000000 00000000 a1 = 00000000 00000001 t1 = 00000000 0002f89c a2 = ffffffff e1836048 t2 = 00000000 00000000 a3 = ffffffff 81918008 t3 = 00000000 00000000 a4 = ffffffff e19d69ec t4 = 00000000 00000001 a5 = 00000000 0039a014 t5 = 00000000 00000000 t8 = ffffffff e1b10f88 t6 = ffffffff c1b11008 t9 = ffffffff e188af8c t7 = 00000000 000003c0 t10 = ffffffff e188af8c t11 = ffffffff 809fcb08 ra = ffffffff 80082ed0 t12 = ffffffff 809fcb08 at = ffffffff 818e0065 gp = ffffffff 800ee088 fp = 00000000 00000004 sp = ffffffff f18a7ba0 fir= ffffffff 80082f80 ExFreePool+0x270 0x80082f80 a0e70000 ldl t6,0x0(t6)
Part 3: Tracing the function call arguments
Despite the fact that the arguments are not pushed on the stack on a RISCsystem, it is still possible to trace the arguments as they are passed fromfunction to function; although it will require some knowledge of theassembly language used to do so. The only thing that makes it possible isthe fact that in RISC assembly, most function calls initially save offseveral of the commonly used registers on to more permanent storagelocations on the stack. This means that it can be possible to trace thearguments by performing the following steps:
- Disassemble each function on the stack in two places, at the very beginning and before the call to the next function.
- Look at what is being put into the argument registers before each function call, and what happens to both the argument registers and any other registers in use after the function call.
In most cases, you will find one of two things; either the argumentregisters themselves are pushed on the stack at the beginning of the call,or the registers whose values where loaded on to the argument registerswere pushed on the stack.
Here is an example from an Alpha dump file. The portion of the stack youwish to trace is:
FramePtr RetAddr Function Name f16c7550 ec138ae8 NTFS!BinarySearchIndex+0x134 f16c7670 ec133254 NTFS!FindFirstIndexEntry+0xf8 f16c76d0 ec13ba00 NTFS!NtfsRestartIndexEnumeration+0xe4 f16c7830 ec1375f4 NTFS!NtfsQueryDirectory+0x728 f16c7a50 ec12d930 NTFS!NtfsCommonDirectoryControl+0x124 f16c7a90 8008607c NTFS!NtfsFsdDirectoryControl+0xe0 f16c7b10 ec324d40 NT!IofCallDriver+0x8c
Starting with the first function, BinarySearchIndex, the code shows 5parameters, which means that on an Alpha, registers a0 through a4 will beused to pass them.
Now disassemble the function which called BinarySearchIndex, right beforethe return address. This will reveal where the values in the a0 through a4registers came from:
NTFS!FindFirstIndexEntry+0xe4:0xec138ad4 47ea0411 bis zero,s1,a10xec138ad8 a21e0054 ldl a0,0x54(sp)0xec138adc 47eb0413 bis zero,s2,a30xec138ae0 47ec0414 bis zero,s3,a40xec138ae4 47e90412 bis zero,s0,a20xec138ae8 d3401c29 bsr ra,BinarySearchIndex
The above assembly instructions would be read as follows:
- bis zero,s1,a1: bis is the mnemonic for a logical or, zero is areference to a special register on the Alpha that always holds the valuezero. This instruction is a fast copy from one register to another, inthis case s1 to a1. The other three bis instructions accomplish the samepurpose with different registers.
- ldl a0,0x54(sp): ldl is the mnemonic for load long, which loads thelong (dword) value from the memory address in operand 2 into theregister in operand 1. 0x54(sp) is the Alpha equivalent of the Intelinstruction dword ptr [ebp+54].
- bsr ra,BinarySearchindex: bsr is a branch subroutine command, this iseffectively the same as a call on an Intel system.
Based on the above assembly, the following values are being placedin argument registers:
a0 = 0x54(SP)a1 = s1a2 = s0a3 = s2a4 = s4
Now disassemble the beginning of BinarySearchIndex to see what is donewith all of the above registers:
NTFS!BinarySearchIndex+0x0:0xec13fb90 23defee0 lda sp,-0x120(sp)0xec13fb94 b53e0000 stq s0,0x0(sp)0xec13fb98 b55e0008 stq s1,0x8(sp)0xec13fb9c b57e0010 stq s2,0x10(sp)0xec13fba0 b59e0018 stq s3,0x18(sp)0xec13fba4 b5be0020 stq s4,0x20(sp)0xec13fba8 b5de0028 stq s5,0x28(sp)0xec13fbac b5fe0030 stq fp,0x30(sp)KDalpha> uNTFS!BinarySearchIndex+0x20:0xec13fbb0 b75e0038 stq ra,0x38(sp)0xec13fbb4 47f1040a bis zero,a1,s10xec13fbb8 b21e0040 stl a0,0x40(sp)<BR/>
In the above code, you are saving off a number of the registers using theinstructions stq (store quadword) and stl (store longword)(dword). Theseinstructions work similarly to load longword(ldl) but in reverse: the valuein the register is written out to the memory address specified by thememory location. In the first seven instructions, the s0 through s5registers are written out to various locations on the stack, and later a0is also written out on to the stack. You now know the following:
a0 = 0x40(sp)a1 = s1 = 0x8(sp)a2 = s0 = 0x0(sp)a3 = s2 = 0x10(sp)a4 = s4 = 0x20(sp)
At the beginning of a function like this, the sp is equal to the FramePtrvalue given in the stack dump for this function(f16c7550), you can usethat to dump out the values for the following arguments:
KDalpha> dd f16c7550+40 l1 Argument 10xF16C7590 80da1848KDalpha> dd f16c7550+8 l1 Argument 20xF16C7558 e18d95c8KDalpha> dd f16c7550 l1 Argument 30xF16C7550 e1ce9a40KDalpha> dd f16c7550+10 l1 Argument 40xF16C7560 80e3f948KDalpha> dd f16c7550+20 l1 Argument 50xF16C7570 e1ce9a08
To verify that you have found the correct values, check the function codeto determine the variable types, and use that information to determine ifyou have the correct values. This method will work for tracing the valuesof most arguments passed from function to function, although occasionallyyou might have to follow a variable through a couple of functions beforeyou find it pushed out onto the stack in an identifiable location.