14.windbg-k、u、x(堆栈查看、汇编查看、函数查)k
k*命令显⽰给定线程的调⽤堆栈,以及其他相关信息
~0 k表⽰打印0号线程的调⽤堆栈,直接⽤k表⽰打印当前线程的调⽤堆栈
0:002> ~0k
ChildEBP RetAddr
0007fddc 77d191be ntdll!KiFastSystemCallRet
0007fdfc 010021b0 USER32!NtUserGetMessage+0xc
0007ff1c 010125e9 calc!WinMain+0x25f
0007ffc0 7c817077 calc!WinMainCRTStartup+0x174
0007fff0 00000000 kernel32!BaseProcessStart+0x23
0:002> k
ChildEBP RetAddr
00bfffc8 7c972119 ntdll!DbgBreakPoint
00bffff4 00000000 ntdll!DbgUiRemoteBreakin+0x2d
0:002> ~2k
ChildEBP RetAddr
00bfffc8 7c972119 ntdll!DbgBreakPoint
00bffff4 00000000 ntdll!DbgUiRemoteBreakin+0x2d
我们注意到2号线程的堆栈,这是windbg创建⼀个远程线程来执⾏DbgUiRemoteBreakin函数,它内部会调⽤DbgBreakPoint执⾏断点指令,以触发断点异常,强制把程序断了下来,所以windbg打印出来的线程总多了⼀条,所以不要惊讶为什么线程多了点。
其实我想弄清楚那个ChildEBP/RetAddr分别具体指什么:先K看下堆栈:
0:000> k
ChildEBP RetAddr
0012fb1c 7c95e612 ntdll!DbgBreakPoint
0012fc94 7c94108f ntdll!LdrpInitializeProcess+0xffa
0012fd1c 7c92e437 ntdll!_LdrpInitialize+0x183
00000000 00000000 ntdll!KiUserApcDispatcher+0x7
再打开反汇编窗⼝:
ntdll!DbgBreakPoint:
7c92120e cc              int    3
当前运⾏到这⼀⾏:再⽤r ebp查看下值:
0:000> r ebp
ebp=0012fc94
这个值是LdrpInitializeProcess前⾯的ChildEBP,F10单步调试到ret(也就是7c92120f)
ntdll!DbgBreakPoint:
7c92120e cc              int    3
7c92120f c3              ret
再F10调试⼀步,退回到LdrpInitializeProcess中(7c95e612):
7c95e60d e8fc2bfcff      call    ntdll!DbgBreakPoint (7c92120e)
7c95e612 8b4368          mov    eax,dword ptr [ebx+68h] ds:0023:7ffd3068=00000070
我们发现这个7c95e612就是DbgBreakPoint的返回地址,也就是返回地址应该是指函数退出后下个EIP的值,我以前还⼀直以为是那个
ret/leave对应的地⽅,原来是ret运⾏后的值.
结论:
ChildEBP指的是当前堆栈运⾏时的ebp值
RetAddr指当前堆栈中函数退出时的下个EIP的值
kb 显⽰传递给堆栈回溯中的每个函数的前三个参数 kp 显⽰传递给堆栈回溯中的每个函数的所有参数。参数列表包含参数的数据类型、名字和值。 p命令是区分⼤⼩写的。 使⽤该参数需要完整符号信息。 (事实上我看到的结果和k⼀样) kP 和 p参数⼀样,显⽰传递给堆栈回溯中的每个函数的所有参数。但是,使⽤ P ,函数参数在第⼆⾏中显⽰,⽽不是作为数据的结尾在⾏末显⽰。 (事实上我看到的结果和k⼀样)
int Add(int a,int b,int c,int d,int e,int f)
{
return a+b;
}
int _tmain(int argc, _TCHAR* argv[])
{
int c = Add(0x12,0x34,0x56,0x78,0x9a,0xbc);
return 0;
}
显⽰的堆栈如下:
0:000:x86> kb
ChildEBP RetAddr  Args to Child
0015fce4 010e1415 00000012 00000034 00000056 test1!Add+0x1e [f:\test1\test1\test1.cpp @ 7]
0:000:x86> kp
ChildEBP RetAddr
0015fce4 010e1415 test1!Add(int a = 0n18, int b = 0n52, int c = 0n86, int d = 0n120, int e = 0n154, int f = 0n188)+0x1e [f:\test1\test1\test1.cpp @ 7]
0:000:x86> kP
ChildEBP RetAddr
0015fce4 010e1415 test1!Add(
int a = 0n18,
int b = 0n52,
int c = 0n86,
int d = 0n120,
int e = 0n154,
int f = 0n188)+0x1e [f:\test1\test1\test1.cpp @ 7]
0:000:x86> kv
ChildEBP RetAddr  Args to Child
0015fce4 010e1415 00000012 00000034 00000056 test1!Add+0x1e (FPO: [Non-Fpo]) (CONV: cdecl) [f:\test1\test1\test1.cpp @ 7]
在某些情况下,只有⼀部分栈是可⽤的,此时调试器的k命令⽆法解析栈,这时因为当前的栈基指针ebp和栈顶指针esp所指向的地址是不可访问的,在这种情况下,可以使⽤命令K的⼀种变化形式,在这种形式中可以接受栈基指针、栈顶指针以及指令指针做为参数。
在⼿动重新构造栈的过程中,最困难的任务就是从内存中出两个值来表⽰调⽤栈中正确的栈帧。出这两个值的⽅法之⼀就是识别出⼀系列的值,这些值表⽰当前栈的某个地址,并且在这些值之后是⼀个可执⾏的地址,每个地址都可能是⼀个栈帧,此时需要通过命令k来进⾏验证,将这个操作重复应⽤于其他可能的栈帧,直到将栈重构出来并且在执⾏命令K时能够显⽰⼀个正确的栈,如下所⽰:
0:000> dc esp
000dfc94  010017eb 00000001 00000000 000dfcb4  ................
000dfca4  01001810 00000000 00000001 00000002  ................
000dfcb4  000dfcc8 01001802 00000002 00000001  ................
000dfcc4  00000003 000dfcdc 01001802 00000003  ................
000dfcd4  00000001 00000004 000dfcf0 01001802  ................
000dfce4  00000004 00000001 00000005 000dfd04  ................
000dfcf4  01001802 00000005 00000001 00000006  ................
000dfd04  000dfd18 01001802 00000006 00000001  ................
0:000> *使⽤被保存的ebp,存储ebp的地址以及返回值
0:000> k=000dfcc8 000dfcb4 01001802
# ChildEBP RetAddr
00 000dfcc8 01001802 02sample!KBTest::Fibonacci_stdcall+0x42 [c:\awd\chapter2\sample.cpp @ 119]
01 000dfcdc 01001802 02sample!KBTest::Fibonacci_stdcall+0x42 [c:\awd\chapter2\sample.cpp @ 119]
02 000dfcf0 01001802 02sample!KBTest::Fibonacci_stdcall+0x42 [c:\awd\chapter2\sample.cpp @ 119]
03 000dfd04 01001802 02sample!KBTest::Fibonacci_stdcall+0x42 [c:\awd\chapter2\sample.cpp @ 11
9]
04 000dfd18 01001802 02sample!KBTest::Fibonacci_stdcall+0x42 [c:\awd\chapter2\sample.cpp @ 119]
05 000dfd2c 01001802 02sample!KBTest::Fibonacci_stdcall+0x42 [c:\awd\chapter2\sample.cpp @ 119]
06 000dfd40 01001802 02sample!KBTest::Fibonacci_stdcall+0x42 [c:\awd\chapter2\sample.cpp @ 119]
07 000dfd54 01001802 02sample!KBTest::Fibonacci_stdcall+0x42 [c:\awd\chapter2\sample.cpp @ 119]
08 000dfd68 01001802 02sample!KBTest::Fibonacci_stdcall+0x42 [c:\awd\chapter2\sample.cpp @ 119]
因为栈是⾃⾼向低增长,⽽返回值是⼀个可执⾏的地址,所以猜01001802是⼀个函数地址,做为返回值,低位接着就是EPB了,
u
u 命令显⽰指定的内存中的程序代码的反汇编。
如果要反汇编某⼀个地址,直接⽤u 命令加地址
0:002> u 77d2929a
USER32!SendMessageW:
77d2929a 8bff            mov    edi,edi
77d2929c 55              push    ebp
77d2929d 8bec            mov    ebp,esp
77d2929f 56              push    esi
77d292a0 8b750c          mov    esi,dword ptr [ebp+0Ch]
77d292a3 f7c60000feff    test    esi,0FFFE0000h
77d292a9 0f85be800100    jne    USER32!SendMessageW+0x11 (77d4136d)
77d292af 8b4d08          mov    ecx,dword ptr [ebp+8]
如果存在符号⽂件,也可以这样直接加函数名:
0:002> u user32!SendMessagew
USER32!SendMessageW:
77d2929a 8bff            mov    edi,edi
77d2929c 55              push    ebp
77d2929d 8bec            mov    ebp,esp
77d2929f 56              push    esi
77d292a0 8b750c          mov    esi,dword ptr [ebp+0Ch]
77d292a3 f7c60000feff    test    esi,0FFFE0000h
77d292a9 0f85be800100    jne    USER32!SendMessageW+0x11 (77d4136d)
77d292af 8b4d08          mov    ecx,dword ptr [ebp+8]
注意的是,函数只⽀持全名,你要是写成u user32!SendMessage,windbg是认不出来的,当然你可以按TAB来让windbg⾃动匹配
ub 指⽰要反汇编的区域是向后计算的。如果使⽤了 ub Address ,反汇编区域是以 Address结束的8或9条指令。如果⽤ ub Address L Length语法指定区域,则反汇编以 Address结尾的指定长度的内容。
0:002> ub USER32!SendMessageW
USER32!SendMessageWorker+0x4ed:
77d29290 5b              pop    ebxsendmessage返回值
77d29291 c9              leave
77d29292 c21400          ret    14h
77d29295 90              nop
77d29296 90              nop
77d29297 90              nop
77d29298 90              nop
77d29299 90              nop
我们可以发现ub的结束后⼀条刚好是u的开始
同样如果存在符号⽂件,我们可以⽤uf来反汇编整个函数:
uf 命令显⽰内存中指定函数的反汇编代码。
x
x命令显⽰所有上下⽂中匹配指定模板的符号。可⽤字符通配符
x user32!send*
77d53948 USER32!SendNotifyMessageA = <no type information>
77d2fb6b USER32!SendMessageTimeoutA = <no type information>
77d6b88f USER32!SendOpenStatusNotify = <no type information>
77d6b49e USER32!SendIMEMessageExA = <no type information>
77d2d64f USER32!SendNotifyMessageW = <no type information>
77d2cdaa USER32!SendMessageTimeoutW = <no type information>
77d65b26 USER32!SendHelpMessage = <no type information>
77d6b823 USER32!SendMessageToUI = <no type information>
77d6b48d USER32!SendIMEMessageExW = <no type information>
77d2cd08 USER32!SendMessageTimeoutWorker = <no type information>
77d203fc USER32!SendRegisterMessageToClass = <no type information>
77d3c2e7 USER32!SendDlgItemMessageA = <no type information>
77d2d6db USER32!SendMessageCallbackW = <no type information>
77d6b129 USER32!SendMessageCallbackA = <no type information>
77d273cc USER32!SendDlgItemMessageW = <no type information>
77d61930 USER32!SendWinHelpMessage = <no type information>
77d291b3 USER32!SendMessageWorker = <no type information>
77d2929a USER32!SendMessageW = <no type information>
77d2f3c2 USER32!SendMessageA = <no type information></span>
所以,这个可以⽤来定位函数,
这⾥介绍下字符串通配符语法
⼀个星号(*)表⽰零个或多个字符。这个前⾯的例⼦⽤到了,
⼀个问号(?)表⽰任意单个字符,如下例:
<span >0:002> x user32!sendMessage?
77d2929a USER32!SendMessageW = <no type information>
77d2f3c2 USER32!SendMessageA = <no type information></span>
⼀个井号(#)匹配零个或多个前⼀个字符。例如,Lo#p 将匹配 "Lp", "Lop", "Loop", "Looop" 等等
⼀个加号(+)匹配⼀个或多个前⼀个字符
如果你需要使⽤ #、 ?、 [, ]、*、+ 字符本⾝,必须在这些字符前⾯加⼀个反斜杠(\)。
x还有个作⽤,在函数断下来后输⼊x,会⾃动打印出当前的局部变量,可以配合.frame使⽤
0:000:x86> kn
# ChildEBP RetAddr
00 0039fd18 010e19a8 test1!wmain+0x1e [f:\test1\test1\test1.cpp @ 12]
01 0039fd68 010e17ef test1!__tmainCRTStartup+0x1a8 [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 583]
0:000:x86> x
0039fd20 argc = 0n1
0039fd24 argv = 0x00033438
0039fd10 c = 0n-858993460
/v 命令可以帮助你更好理解⼆进制⽂件的内容,它将按照对象或函数所占的字节数以升序来列出序号的类型和⼤⼩
0:000> x /v /t  02sample!w*
prv func  01001c60            21 <function> 02sample!wmain (unsigned long, wchar_t **)
prv func  01002176            a <function> 02sample!wmainCRTStartup (void)

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。