What are the “Args to Child” in the kv output?

The “Args to Child” label is one of the most misleading things you’ll come across in a WinDBG session. Here’s a stack from a laptop crash I had:

0: kd> kv
  *** Stack trace for last set context - .thread/.cxr resets it
ChildEBP RetAddr  Args to Child
ba50b8c0 b9e390a8 000335e5 89bc65f4 000000fe nt!ObQueryNameString+0x9b (FPO: [Non-Fpo])
ba50b8e8 b9e2eb4e 000335e5 89bc65e0 00000144 fltmgr!FltpGetObjectName+0x24 (FPO: [2,1,4])
ba50b944 b9e2eddc 8609b718 8055ae90 89aff030 fltmgr!FltpEnumerateFileSystemVolumes+0x138 (FPO: [Non-Fpo])
ba50b998 b9e330eb 89aff030 ba50b9ac 89c4849c fltmgr!FltpAttachToFileSystemDevice+0x142 (FPO: [Non-Fpo])
ba50bac4 b9e334d4 89aff030 00000001 ba50baf0 fltmgr!FltpFsNotificationActual+0x47 (FPO: [2,70,4])
ba50bad4 80574908 89aff030 00000001 84dfe004 fltmgr!FltpFsNotification+0x1c (FPO: [2,0,0])
ba50baf0 b9e3413d 8a6f5850 b9e334b8 b9e2d8e0 nt!IoRegisterFsRegistrationChange+0xcc (FPO: [2,0,4])
ba50bb58 b9e3431e 861eabc8 861eabbc 862ecc08 fltmgr!FltpAttachFrame+0x121 (FPO: [Non-Fpo])
ba50bba4 b9e3b6e1 861eaba8 861eabc8 862ecc08 fltmgr!FltpFindFrameForFilter+0xa8 (FPO: [Non-Fpo])
ba50bc08 990cb284 862ecc08 990ce650 990ceb68 fltmgr!FltRegisterFilter+0x295 (FPO: [Non-Fpo])
WARNING: Stack unwind information not available. Following frames may be wrong.
ba50bc74 990c851f 862ecc08 e13cf65c ba50bd54 PROCMON13+0x3284
ba50bc84 80581377 862ecc08 86106000 00000000 PROCMON13+0x51f
ba50bd54 80581487 80000050 00000001 00000000 nt!IopLoadDriver+0x66d (FPO: [4,45,0])
ba50bd7c 8053877d 80000050 00000000 8a7133c8 nt!IopLoadUnloadDriver+0x45 (FPO: [1,1,4])
ba50bdac 805cff70 98d7fcf4 00000000 00000000 nt!ExpWorkerThread+0xef (FPO: [1,6,0])
ba50bddc 805460ee 8053868e 00000001 00000000 nt!PspSystemThreadStartup+0x34 (FPO: [Non-Fpo])
00000000 00000000 00000000 00000000 00000000 nt!KiThreadStartup+0x16

In this case, I don’t have private PDBs with parameter information. So how does WinDBG know what the arguments to the routines are? Also, to confuse things even more, IoRegisterFsRegistrationChange only takes two parameters:

NTSTATUS
IoRegisterFsRegistrationChange(
IN PDRIVER_OBJECT DriverObject,
IN PDRIVER_FS_NOTIFICATION DriverNotificationRoutine
);

So why does WinDBG show three args?

ba50baf0 b9e3413d 8a6f5850 b9e334b8 b9e2d8e0 nt!IoRegisterFsRegistrationChange+0xcc (FPO: [2,0,4])

The answer is that WinDBG is lying to you, it has no idea if these values are arguments to the routine or not!

On the x86, the “Args to Child” column in the kv output assumes that each routine is using an EBP frame. In an EBP frame, EBP points to the previous EBP, EBP+4 points to the return address, EBP+8 points to the first thing on the stack after the return address, and so on. If the routine isn’t using fastcall, EBP+8 will be the first parameter. If the routine is using fastcall, EBP+8 will be the third parameter to the routine (first parameter will be in ECX and the second in EDX). All “Args to Child” shows is the contents of EBP+8, EBP+C, and EBP+10.

So, in order for this output to be correct three things must be true:

1) The specified routine does indeed use an EBP frame, which can be optimized out of the binary using the /Oy compiler switch.

2) The specified routine actually takes 3 or more parameters and doesn’t use fastcall

3) The specified routine does not reuse the parameter location for some other value. It’s not uncommon for the optimizer to decide to overwrite a parameter if it is of no more use to the routine.

Therefore it is important to be careful before trusting these values in any way!

More details on EBP frames and calling conventions can be found in this great article from long ago:

http://www.microsoft.com/msj/0298/hood0298.aspx

Note that these fields also appear on the x64 platform:

0: kd> kv
  *** Stack trace for last set context - .thread/.cxr resets it
Child-SP          RetAddr           : Args to Child                                                           : Call Site
fffffade`4e4fe8c0 fffff800`01027652 : 00000000`0000000f 00000000`00000000 00000000`000001a4 fffff800`011b3180 : nt!KiSwapContext+0x85
fffffade`4e4fea40 fffff800`01024f20 : 00000000`00000000 00000000`00000001 fffffadf`f845da10 fffff800`011b3180 : nt!KiSwapThread+0x3c9
fffffade`4e4feaa0 fffff800`01282c26 : 00000000`00000000 00000000`00000001 00000000`00000000 00000000`00000000 : nt!KeRemoveQueue+0x656
fffffade`4e4feb20 fffff800`0102e43d : 00000000`00000000 fffffadf`a44e2c68 00000000`000c9f00 fffffade`4e4fecf0 : nt!NtRemoveIoCompletion+0x13c
fffffade`4e4fec00 00000000`77ef0a7a : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiSystemServiceCopyEnd+0x3 (TrapFrame @ fffffade`4e4fec70)
00000000`01dafce8 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : 0x77ef0a7a

In the case of the x64, the output is showing you the first four things on the stack after the return address. This is the start of the “spill” or “home” space for the first four arguments to the routine, so it may contain copies of the first parameters, it may contain non-volatile registers, it may contain neither,  or it may contain both! So again we have to be sure to not trust these fields implicitly.

The x64 ABI is fully documented by MS, but a short and clear description can be found here:

http://uninformed.org/?v=4&a=1

2 Responses to “What are the “Args to Child” in the kv output?”

  1. Thanks for the concise explanation!

  2. snoone says:

    Glad it helped!