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一样) kPp参数一样,显示传递给堆栈回溯中的每个函数的所有参数。但是,使用 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 @ 119] 
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     ebx
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 = 
77d2fb6b USER32!SendMessageTimeoutA = 
77d6b88f USER32!SendOpenStatusNotify = 
77d6b49e USER32!SendIMEMessageExA = 
77d2d64f USER32!SendNotifyMessageW = 
77d2cdaa USER32!SendMessageTimeoutW = 
77d65b26 USER32!SendHelpMessage = 
77d6b823 USER32!SendMessageToUI = 
77d6b48d USER32!SendIMEMessageExW = 
77d2cd08 USER32!SendMessageTimeoutWorker = 
77d203fc USER32!SendRegisterMessageToClass = 
77d3c2e7 USER32!SendDlgItemMessageA = 
77d2d6db USER32!SendMessageCallbackW = 
77d6b129 USER32!SendMessageCallbackA = 
77d273cc USER32!SendDlgItemMessageW = 
77d61930 USER32!SendWinHelpMessage = 
77d291b3 USER32!SendMessageWorker = 
77d2929a USER32!SendMessageW = 
77d2f3c2 USER32!SendMessageA = 

所以,这个可以用来定位函数,

这里介绍下字符串通配符语法

一个星号(*)表示零个或多个字符。这个前面的例子用到了,

一个问号(?)表示任意单个字符,如下例:

0:002> x user32!sendMessage?
77d2929a USER32!SendMessageW = 
77d2f3c2 USER32!SendMessageA = 

一个井号(#)匹配零个或多个前一个字符。例如,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  02sample!wmain (unsigned long, wchar_t **)
prv func   01002176             a  02sample!wmainCRTStartup (void)


 

 


 

 

 


 


 

 

 


 

你可能感兴趣的:(windbg)