zoukankan      html  css  js  c++  java
  • Windbg查看调用堆栈(k*)

            无论是分析程序崩溃原因,还是解决程序hang问题,我们最常查看的就是程序调用堆栈。学会windbg调用堆栈命令,以及理解堆栈中的各个参数的意义就显得至关重要。

    上图就是一个典型的Windbg堆栈,如果不理解ChildEBP、RetAddr、Args to Child等参数意义,以及它们之间的来龙去脉,调试工作将很难进行下去。

    1. 函数参数

            函数的参数传递有二种方式:堆栈方式、寄存器方式。如果是堆栈方式传递的,就需要定义参数在堆栈中的传递顺序,并约定函数被调用之后,由谁来平衡堆栈;如果是寄存器方式传递的,就需要确定参数存放在哪个寄存器中。每一种方式都有其优缺点,而且与使用的编程语言有关系,不存在哪种方式好与坏。

    如Visual Studio中的C++工程,可以C++ --> 高级 --> 调用约定中进行设置:

            常用的调用约定类型有__cdecl、stdcall、PASCAL、fastcall。除了fastcall可以支持以寄存器的方式来传递函数参数外,其他的都是通过堆栈的方式来传递函数参数的。

    利用堆栈传递参数

            堆栈是一种“后进先出”的数据结构,ESP寄存器始终指向栈顶。栈中数据地址从底部到顶部依次减小,也就是说,栈底对应高地址,栈顶对应低地址。
    调用函数时,调用者依次把参数压栈,然后调用函数,函数被调用之后,在堆栈中取得参数数据。函数调用结束以后,堆栈需要恢复到函数调用之前的样子,具体由调用者来恢复还是由函数自身来恢复,根据不同的调用约定类型采用不同的方式。

    约定类型 __cdecl stdcall PASCAL fastcall
    参数传递顺序 从右到左 从右到左 从左到右 使用寄存器
    平衡堆栈者 调用者 函数自身 函数自身 函数自身

    __cdcel是C/C++/MFC程序默认的调用约定。
    stdcall是Win32中绝大多数 API函数的约定方式,也有少部分使用__cdcel约定方式,如wsprintf等。

            在windows C/C++开发中常用的就是__cdecl和stdcall这2种调用约定。
    假设调用函数int add(int a, int b), 按照不同的调用约定来调用它。从调用者的视角来看,其汇编代码分别表示如下:

    __cdecl

    push b     ;参数按从右到左传递
    push a
    call add
    add esp, 8 ;调用者在函数外部平衡堆栈
    

    stdcall

    push b     ;参数按从右到左传递
    push a
    call add   ;函数自己内部平衡堆栈
    

    在函数调用过程中,参数入栈的过程:

    上图中,EBP和函数返回地址都是地址,在32位程序中地址占4个字节。在函数的一次调用过程中EBP是不会变化的,函数调用完之后会将EBP恢复为暂存在堆栈中的原EBP值。所以,通过EBP可以获取函数各个参数的值:
    参数a = EBP + 0x8
    参数b = EBP + 0xC

    2. Windbg堆栈命令

    2.1 显示堆栈信息k*

    [~Thread] k[b|p|P|v] [c] [n] [f] [L] [M] [FrameCount]
    [~Thread] k[b|p|P|v] [c] [n] [f] [L] [M] = BasePtr [FrameCount]
    [~Thread] k[b|p|P|v] [c] [n] [f] [L] [M] = BasePtr StackPtr InstructionPtr
    [~Thread] kd [WordCount]
    

    参数:
    Thread  指定显示哪个线程的调用堆栈。如果省略该参数,则显示当前线程的调用堆栈。*显示所有线程的调用堆栈。

    b  显示每个函数的前3个参数。

    p  显示每个函数的所有参数。参数列表包括每个参数的类型、名称、值。

            如上图,可以看到函数的每个参数的类型,名称,值。但是这个需要有对应的符号文件(pdb),没有应用程序的符号文件只能显示系统API的参数信息。

    P  类似p。不同之处在于,每个参数显示在单独的行上面。

    n  显示调用堆栈中每帧的序号(一般称栈帧,如栈帧3)。

    FrameCount  指定显示调用堆栈的帧数,即调用堆栈的深度。默认为16进制格式。默认帧数为0x14=20

    2.2 切换到指定帧信息.frame

            调用堆栈显示出来之后,如果想知道调用某帧时的相关信息,可以使用.frame 来切换指定帧,然后就可以使用如dv命令显示局部变量等。

    .frame [/c] [/r] [FrameNumber] 
    

    /c

    /r  显示执行该帧时寄存器的值。

    FrameNumber  指定要切换到的帧号。

    3. 实例分析

    kbn 显示堆栈信息:

    栈帧12
    调用add函数,参数1=00000001,参数2=000000002,EBP=0015fc0c
    根据图1得知,函数返回地址=EBP+4,我们使用dw命令来验证。

    参考:
    《软件调试》张银奎 著
    《格蠹汇编》张银奎 著
    《加密与解密》第三版 段刚编著

  • 相关阅读:
    Maximum Depth of Binary Tree
    Single Number
    Merge Two Sorted Lists
    Remove Nth Node From End of List
    Remove Element
    Remove Duplicates from Sorted List
    Add Two Numbers
    编译视频直播点播平台EasyDSS数据排序使用Go 语言 slice 类型排序的实现介绍
    RTMP协议视频直播点播平台EasyDSS在Linux系统中以服务启动报错can’t evaluate field RootPath in type*struct排查
    【解决方案】5G时代RTMP推流服务器/互联网直播点播平台EasyDSS实现360°全景摄像机VR直播
  • 原文地址:https://www.cnblogs.com/jiangxueqiao/p/7418131.html
Copyright © 2011-2022 走看看