在跟进一些windbg教程时,我注意到一些使用k
命令的调用堆栈采用这种格式,特别是我的
Child-SP RetAddr Call Site
而像CodeProject这样的其他在线资源有k
命令以这种格式吐出信息
Child-EBP RetAddr Call Site
我对为什么我的输出和他们的输出之间存在差异以及这真正意味着什么感到困惑。
在跟进一些windbg教程时,我注意到一些使用k
命令的调用堆栈采用这种格式,特别是我的
Child-SP RetAddr Call Site
而像CodeProject这样的其他在线资源有k
命令以这种格式吐出信息
Child-EBP RetAddr Call Site
我对为什么我的输出和他们的输出之间存在差异以及这真正意味着什么感到困惑。
这取决于正在使用什么调用约定。一些函数分配局部变量和参数,它将传递给它使用基指针调用的函数,例如 cdecl,但 Windows x64 调用约定使用rsp
.
Child-SP 是该帧的堆栈指针的值,它将是除帧 0 之外的所有帧的返回地址之前的字节,帧 0 可能在序言之前或期间有一个断点。如果断点在第一条指令上,那么rsp
在前一帧的 Child-SP 上只会减少 8;该rsp
值是从陷阱帧中读取的。如果您不认为被调用者的返回地址是帧的一部分,则 Child-SP 是帧号处的帧地址(这在这种情况下是有意义的,因为帧的返回地址列显示返回前一帧的地址成为该帧的一部分)。
ChildEBP 是该ebp
帧中 when 的值(不是ebp
被推送到帧的值,而是 的新值ebp
)
RetAddr 是属于该帧的返回地址,所以是它会返回的地址
调用站点是被调用的结束帧的调用指令之后的指令地址(基本上就是被调用者返回地址 -- call instruction + call instruction length
),或者是断点的地址或导致它闯入调试器的其他异常(注意:异常的地址,而不是它之后的指令,所以这将rip
在陷阱帧中)在第 0 帧(堆栈顶部的帧)的情况下。只要该函数不包含标签,这将指示拥有该行上的框架的函数的名称,并且子项的参数是传递给它的参数。实际上,调用点是它调用的框架(它上面的框架)的返回地址。
子参数是传递给拥有当前行堆栈帧的函数的参数,而不是传递给它在序言分配的空间中调用的被调用函数的参数。这在 x64 上几乎是不准确的,它显示了 homespace 的前 3 个四字,因为前 4 个参数可以在寄存器中传递,如果是,被调用函数可能不会将这些参数归位(将它们保存在 homespace 中)-O0
或不是 varargs 函数,或者可能将其他东西完全放在 homespace 中。'Args to child' 和 'Child-SP' 中的 'Child' 是一个错误且具有误导性的名称,它暗示了框架调用的函数,但它实际上指的是当前函数。
这个站点上有一个示例堆栈跟踪,它显示了一个断点notepad!ShowOpenSaveDialog
,这将是第一条指令。
0:011> bp notepad!ShowOpenSaveDialog
0:011> g
Breakpoint 0 hit
notepad!ShowOpenSaveDialog:
00007ff7`0307182c 48895c2408 mov qword ptr [rsp+8],
rbx ss:00000073`74d2f310=0000000000000000
0:000> k
# Child-SP RetAddr Call Site
00 00000073`74d2f308 00007ff7`03071aeb notepad!ShowOpenSaveDialog
01 00000073`74d2f310 00007ff7`030721fa notepad!InvokeOpenDialog+0x14f
02 00000073`74d2f370 00007ff7`030738d6 notepad!NPCommand+0x4a2
03 00000073`74d2f6f0 00007fff`664b6d41 notepad!NPWndProc+0x726
04 00000073`74d2f9f0 00007fff`664b6713 USER32!UserCallWinProcCheckWow+0x2c1
05 00000073`74d2fb80 00007ff7`03073bdb USER32!DispatchMessageWorker+0x1c3
06 00000073`74d2fc10 00007ff7`03089333 notepad!WinMain+0x27f
07 00000073`74d2fd10 00007fff`68ea3034 notepad!__mainCRTStartup+0x19f
08 00000073`74d2fdd0 00007fff`69073691 KERNEL32!BaseThreadInitThunk+0x14
09 00000073`74d2fe00 00000000`00000000 ntdll!RtlUserThreadStart+0x21
0:000> r @rcx=0
0:000> r
rax=0000000000000000 rbx=0000000000000000 rcx=0000000000000000
rdx=00000213f5020968 rsi=000000499cb1f338 rdi=00000213f5021c20
rip=00007ff70307182c rsp=000000499cb1f288 rbp=000000499cb1f2d0
r8=00000213f4ffe9d6 r9=000000499cb1f338 r10=00000ffee060e246
r11=0000000000014140 r12=0000000000000000 r13=0000000000000001
r14=000000000004094e r15=00000000ffffffff
iopl=0 nv up ei pl zr na po nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
notepad!ShowOpenSaveDialog:
00007ff7`0307182c 48895c2408 mov qword ptr [rsp+8],
rbx ss:00000049`9cb1f290=0000000000000000
0:000> ub notepad!InvokeOpenDialog+0x14f
notepad!InvokeOpenDialog+0x133:
00007ff7`03071acf 8bd8 mov ebx,eax
00007ff7`03071ad1 85c0 test eax,eax
00007ff7`03071ad3 782c js notepad!InvokeOpenDialog+0x165 (00007ff7`03071b01)
00007ff7`03071ad5 4c8b05fc080200 mov r8,qword ptr [notepad!szOpenCaption (00007ff7`030923d8)]
00007ff7`03071adc 4c8bce mov r9,rsi
00007ff7`03071adf 488b5538 mov rdx,qword ptr [rbp+38h]
00007ff7`03071ae3 498bce mov rcx,r14
00007ff7`03071ae6 e841fdffff call notepad!ShowOpenSaveDialog (00007ff7`0307182c)
创建的陷阱帧nt!KiBreakpointTrap
不是堆栈跟踪的一部分,因为陷阱帧被推送到线程的内核堆栈而不是用户堆栈。
如果您正在调试内核,那么如果存在陷阱帧并且进程地址空间可访问,您将看到用户和内核堆栈的融合:
lkd> .process /P fffffa80723296f0
lkd> .reload
lkd> ld *
lkd> !process 3490
Searching for Process with Cid == 3490
Cid handle table at fffff8a00195f000 with 4126 entries in use
PROCESS fffffa80723296f0
SessionId: 1 Cid: 3490 Peb: 7fffffdf000 ParentCid: 1470
DirBase: 403874000 ObjectTable: fffff8a02b3a2ce0 HandleCount: 293.
Image: chrome.exe
VadRoot fffffa805fc816e0 Vads 295 Clone 0 Private 16586. Modified 1536. Locked 0.
DeviceMap fffff8a00208d0b0
Token fffff8a03466d9e0
ElapsedTime 00:23:43.376
UserTime 00:00:00.717
KernelTime 00:00:00.000
QuotaPoolUsage[PagedPool] 0
QuotaPoolUsage[NonPagedPool] 0
Working Set Sizes (now,min,max) (27282, 50, 345) (109128KB, 200KB, 1380KB)
PeakWorkingSetSize 33917
VirtualSize 870 Mb
PeakVirtualSize 891 Mb
PageFaultCount 81452
MemoryPriority BACKGROUND
BasePriority 4
CommitCharge 19316
Job fffffa805f9f46d0
THREAD fffffa802e890b50 Cid 3490.469c Teb: 000007fffffdd000 Win32Thread: fffff900c53a48c0 WAIT: (UserRequest) UserMode Non-Alertable
fffffa8062daf060 SynchronizationEvent
Not impersonating
DeviceMap fffff8a00208d0b0
Owning Process fffffa80723296f0 Image: chrome.exe
Attached Process N/A Image: N/A
Wait Start TickCount 45844297 Ticks: 42 (0:00:00:00.655)
Context Switch Count 21234 LargeStack
UserTime 00:00:07.300
KernelTime 00:00:00.234
Win32 Start Address chrome!IsSandboxedProcess (0x000000013fbcbe90)
Stack Init fffff8802d5bec70 Current fffff8802d5be7c0
Base fffff8802d5bf000 Limit fffff8802d5b7000 Call 0
Priority 4 BasePriority 4 UnusualBoost 0 ForegroundBoost 0 IoPriority 2 PagePriority 5
Child-SP RetAddr Call Site
fffff880`2d5be800 fffff800`0367ec32 nt!KiSwapContext+0x7a
fffff880`2d5be940 fffff800`0368145f nt!KiCommitThreadWait+0x1d2
fffff880`2d5be9d0 fffff800`0397602e nt!KeWaitForSingleObject+0x19f
fffff880`2d5bea70 fffff800`03678c13 nt!NtWaitForSingleObject+0xde
fffff880`2d5beae0 00000000`7782bd7a nt!KiSystemServiceCopyEnd+0x13 (TrapFrame @ fffff880`2d5beae0)
00000000`002eec08 000007fe`fd6110ac ntdll!NtWaitForSingleObject+0xa
00000000`002eec10 000007fe`d97c2107 KERNELBASE!WaitForSingleObjectEx+0x79
00000000`002eecb0 000007fe`d858aeab chrome_child!GetHandleVerifier+0x15d8417
00000000`002eed40 000007fe`d823004d chrome_child!GetHandleVerifier+0x3a11bb
00000000`002eedb0 000007fe`d822fc91 chrome_child!GetHandleVerifier+0x4635d
00000000`002eee10 000007fe`d8217c59 chrome_child!GetHandleVerifier+0x45fa1
00000000`002eee40 000007fe`d82176de chrome_child!GetHandleVerifier+0x2df69
00000000`002ef040 000007fe`d82105d1 chrome_child!GetHandleVerifier+0x2d9ee
00000000`002ef1b0 000007fe`d81e518b chrome_child!GetHandleVerifier+0x268e1
00000000`002ef250 000007fe`d81e4c58 chrome_child!ChromeMain+0x377d
00000000`002ef570 000007fe`d81e1b2e chrome_child!ChromeMain+0x324a
00000000`002ef600 00000001`3faf354c chrome_child!ChromeMain+0x120
00000000`002ef6d0 00000001`3faf1699 chrome+0x354c
00000000`002ef7c0 00000001`3fbcbe33 chrome+0x1699
00000000`002efba0 00000000`775d59cd chrome!IsSandboxedProcess+0x61483
00000000`002efbe0 00000000`7780a561 kernel32!BaseThreadInitThunk+0xd
00000000`002efc10 00000000`00000000 ntdll!RtlUserThreadStart+0x1d
陷阱帧由 制作nt!KiSystemCall64
,其中有一个名为 的标签nt!KiSystemServiceCopyEnd
,在这种情况下,它会调用系统服务nt!NtWaitForSingleObject
。陷阱帧是第一个堆栈帧,因此是内核堆栈上rsp
的函数,从函数的 (Child-SP)nt!KiSystemCall64
开始,并从它为下一个函数分配的主空间开始。的第一条指令nt!KiSystemCall64
是和inswapgs
交换,然后存入KPCR 中的字段,再存入KPRCB中的字段,这明显是用户与内核的交换gs
gs
IA32_KERNEL_GS_BASE
rsp
gs:10h
UserRsp
gs:1A8h
rsp
RspBase
rsp
rsp
对于线程,该线程缓存在当前运行该线程的 KPRCB 的超线程中。然后它开始构建陷阱帧,根据定义,rsp
当前指向0x190
陷阱帧中的偏移量(帧结束后的字节),从 push 开始,因为当您使用而不是SS
时 CPU 不会执行此操作。syscall
int 0x2e
当使用基指针时,陷阱帧将比ebp
它上面的帧少 8 (减去ebp
和返回地址,每个 4 个字节),所以帧nt!NtWaitForSingleObject
异常有一个异常帧,它使用 来存储非易失性寄存器nt!KiExceptionDispatch
,nt!KiBreakpointTrap
例如在创建陷阱帧(以及由于处理器将和SS:RSP
RFLAGS
推送到堆栈以用于用户模式异常和到堆栈以获取内核模式异常,并且不从 KPCR / KPRCB 保存/加载任何内容)。创建一个异常帧,从堆栈跟踪中的下一个开始,紧接着在堆栈上,总组合大小为,因此您会看到堆栈指针之间的差异和CS:RIP
ErrorCode
RFLAGS
CS:RIP
ErrorCode
rsp
nt!KiExceptionDispatch
KEXCEPTION_FRAME
rsp
EXCEPTION_RECORD
1D8
1E0
nt!KiExceptionDispatch
nt!KiBreakpointTrap
当您将退货地址包含在nt!KiBreakpointTrap
.
陷阱帧存储在内核中调用的函数数组期间内核可以破坏的所有寄存器,称为易失性寄存器,因为易失性寄存器保证在系统调用或异常期间不会更改。它不保存非易失性寄存器,因为在内核中调用的函数会保存它们使用的任何非易失性寄存器,因此当它返回此函数执行 asysret
时,所有非易失性寄存器将是它们之前的值. 您需要一个用于异常的异常框架,因为它需要在执行堆栈展开的函数中知道异常发生时非易失性寄存器包含的内容,以便可以展开堆栈。在堆栈上创建一个CONTEXT
结构nt!KiDispatchException
表示异常发生时的完整上下文,它是非易失性和易失性寄存器状态的组合。
在交换线程上下文时,您还需要另一个异常帧,以便在线程切换出时保存非易失性寄存器的状态,因此这将是状态 in nt!KiSwapContext
,因此可以在线程切换回时恢复。不需要保存易失性状态,因为假设调用SwapContext
丢弃所有易失性寄存器。首先进入内核模式时创建的陷阱帧将用于在线程返回用户模式时将易失性寄存器上下文恢复到线程。
有趣的事实:对于断点异常INT 3
,CPU 在断点指令结束后推送rip
开始,而不是断点指令,这意味着 Windows 需要递减地址,因此堆栈调用点的顶部rip
位于陷阱帧中,而不是CPU推送的返回地址。