windbg - 查看调用栈

前言-显示调用栈 

 

在分析崩溃时候,经常会查看调用栈,正确理解调用中的各字段的含义对于排查问题至关重要,所以本篇重点介绍下,如何查看调用栈。

查看调用栈,kb 如下图

调用栈命令,可以观看官方文档 :https://docs.microsoft.com/zh-cn/windows-hardware/drivers/debugger/k--kb--kc--kd--kp--kp--kv--display-stack-backtrace-

||2:2:196> kb
  *** Stack trace for last set context - .thread/.cxr resets it
 # ChildEBP RetAddr      Args to Child              
00 00cf8dec 002da5ca     96b6b062 3951c6b0 40f28218 lec_teacher!GoodsPreviewWidget::getShowMode [d:\shendun_lec_teacher\origin\version\1.5.7\src\plugin\activity\goods\goodspreviewwidget.cpp @ 22] 
01 00cf8e38 66a0c9ba     00000001 3951c6b0 1e7cb010 lec_teacher!GraphicsToolManager::onCountDownAccepted+0x7a [d:\shendun_lec_teacher\origin\version\1.5.7\src\tool\graphics\manager\graphicstoolmanager.cpp @ 739] 
02 (Inline) --------     -------- -------- -------- Qt5Core!QtPrivate::QSlotObjectBase::call+0x17 [c:\users\qt\work\qt\qtbase\src\corelib\kernel\qobjectdefs_impl.h @ 394] 
03 00cf8ed0 66a0cc8e     40f26bc8 66d48844 66d48844 Qt5Core!QMetaObject::activate+0x40a [c:\users\qt\work\qt\qtbase\src\corelib\kernel\qobject.cpp @ 3774] 
04 00cf8ee4 644c8218     40f26bc8 646197f4 00000001 Qt5Core!QMetaObject::activate+0x1e [c:\users\qt\work\qt\qtbase\src\corelib\kernel\qobject.cpp @ 3646] 
05 (Inline) --------     -------- -------- -------- Qt5Widgets!QDialog::rejected+0xb [c:\users\qt\work\qt\qtbase\src\widgets\.moc\release\moc_qdialog.cpp @ 241] 
06 (Inline) --------     -------- -------- -------- Qt5Widgets!QDialogPrivate::finalize+0x23 [c:\users\qt\work\qt\qtbase\src\widgets\dialogs\qdialog.cpp @ 178] 
07 00cf8f0c 644843da     00000001 002e5485 96b6b10a Qt5Widgets!QDialog::done+0x38 [c:\users\qt\work\qt\qtbase\src\widgets\dialogs\qdialog.cpp @ 632] 
08 00cf8f14 002e5485     96b6b10a 00000000 00000002 Qt5Widgets!QAbstractSpinBox::stepUp+0xa [c:\users\qt\work\qt\qtbase\src\widgets\widgets\qabstractspinbox.cpp @ 617] 
09 00cf8f50 003518b2     40f26bc8 00000000 00000002 lec_teacher!CountDownDialog::on_okBtn_clicked+0x165 [d:\shendun_lec_teacher\origin\version\1.5.7\src\tool\graphics\widgets\countdowndialog.cpp @ 204] 
0a 00cf8f74 669f5808     00000000 0000002b 00cf9054 lec_teacher!CountDownDialog::qt_metacall+0x32 [d:\shendun_lec_teacher\origin\version\1.5.7\release\moc_countdowndialog.cpp @ 180] 
0b 00cf8f84 66a0cacf     40f26bc8 00000000 0000002b Qt5Core!QMetaObject::metacall+0x28 [c:\users\qt\work\qt\qtbase\src\corelib\kernel\qmetaobject.cpp @ 304] 
0c 00cf9018 66a0cc8e     32be0c10 66d4871c 66d4871c Qt5Core!QMetaObject::activate+0x51f [c:\users\qt\work\qt\qtbase\src\corelib\kernel\qobject.cpp @ 3813] 
0d 00cf902c 643fdd80     32be0c10 6460966c 00000002 Qt5Core!QMetaObject::activate+0x1e [c:\users\qt\work\qt\qtbase\src\corelib\kernel\qobject.cpp @ 3646] 

在打印的调用栈中,基本展示了调用栈的层级,并且指明了一些参数和具体的代码文件、函数、行数。

内容解析

首先解释下,打印的信息内容

  1. ChildEBP: a pointer to a memory location which stores the address of the previous function on the stack ("stack frame").
  2. RetAddr: The "return address" where processing will resume once this function returns (finishes what it had to do).

ChildEBP:表示是每个函数的ebp,也就是基地址,如果是内联函数没有基地址

RetAddr:返回值地址,通过下图可以清晰的看到RetAddr的含义,可以理解为函数运行后的下一条指令。也就是执行的上层调用者的调用函数的下一条指令。

例如下面的示例:

QDialog::done的RetAddr是644843da 指向的就是 setUp的调用done的下一条指令。

由于编译程序会对代码进行优化,所以不是内联函数的一些代码会被优化成内联函数,所以上面的2个字段内容就无效了,例如在栈帧05,06帧

调用栈参数传递

在推导调用栈时候,一些基础知识是需要知道的,例如:栈帧是如何存储,参数是如何传递。

我先来看一下参数传递的几种方式,调用参数的顺序。

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

 __cdecl 是c++ 默认的调用方式,

stdcall是Win32中绝大多数 API函数的约定方式,也有少部分使用__cdcel约定方式。

例如:int add(int a, int b)

__cdecl ,调用者处理栈,调整

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

stdcall,在函数内部把栈顶调整

push b     ;参数按从右到左传递
push a
call add

例如:看一下实例

X86 体系结构具有多个不同的调用约定。 幸运的是,它们都遵循相同的寄存器保留和函数返回规则:

  • 函数必须保留所有寄存器, eax、 ecx 和 edx 除外,可以在函数调用中更改这些寄存器 ,并且必须 根据调用约定对其进行更新。

  • 如果结果为32位或更小,则 eax 寄存器接收函数返回值。 如果结果为64位,则结果存储在 edx: eax 对中。

下面是用于 x86 体系结构的调用约定的列表:

  • Win32 (_ _ stdcall)

    函数参数在堆栈上传递,从右到左推送,被调用方清理堆栈。

  • 本机 c + + 方法调用 (也称为 thiscall)

    函数参数将在堆栈上传递,从右到左推送,在 ecx 寄存器中传递 "this" 指针,被调用方清理堆栈

  • C + + 方法调用的 COM (_ _ stdcall)

    函数参数在堆栈上传递,从右到左,然后将 "this" 指针推送到堆栈上,然后调用函数。 被调用方清理堆栈。

  • __fastcall

    在 ecx 和 edx 寄存器中传递前两个 DWORD 或更小的参数。 剩余的参数在堆栈上传递(从右到左)。 被调用方清理堆栈。

  • __cdecl

    函数参数在堆栈上传递,从右到左推送,并且调用方清理堆栈。 _ _ Cdecl 调用约定用于具有可变长度参数的所有函数。

分析反编译

由于编译器会对代码进行优化,将一些简单函数直接去掉,例如:accepted 和 rejected 由于最终调用activate 只是后2个参数不同,所以生成的汇编直接调用了activate

void QDialog::done(int r)
{
    Q_D(QDialog);
    d->hide(r);
    d->finalize(r, r);
}

void QDialogPrivate::finalize(int resultCode, int dialogCode)
{
    Q_Q(QDialog);

    if (dialogCode == QDialog::Accepted)
        emit q->accepted(); // 调用QMetaObject::activate
    else if (dialogCode == QDialog::Rejected)
        emit q->rejected(); // 调用QMetaObject::activate

    emit q->finished(resultCode);
}

void QMetaObject::activate(QObject *sender, const QMetaObject *m, int local_signal_index,
                           void **argv)
{
    activate(sender, QMetaObjectPrivate::signalOffset(m), local_signal_index, argv);
}

void QDialog::done(int r) 反汇编解析

 Qt5Widgets!QDialog::done:
644c81e0 83ec08               sub     esp, 8  //1个变量,需要减8
644c81e3 53                   push    ebx     //保存当前ebx 
644c81e4 8b5c2410             mov     ebx, dword ptr [esp+10h] //获取参数大小是8字节,1
644c81e8 56                   push    esi     //保存栈顶
644c81e9 8b7104               mov     esi, dword ptr [ecx+4] //重置栈顶
644c81ec 8bce                 mov     ecx, esi //ecx 保存栈顶
644c81ee 57                   push    edi  //保存edi
644c81ef 53                   push    ebx  //保存ebx 避免函数调用被覆盖
644c81f0 e8eb020000           call    Qt5Widgets!QDialogPrivate::hide (644c84e0)
644c81f5 8b7604               mov     esi, dword ptr [esi+4]
644c81f8 8b3de0655f64         mov     edi, dword ptr [Qt5Widgets!_imp_?activateQMetaObjectSAXPAVQObjectPBU1HPAPAXZ (645f65e0)]


//判断是否等于1,accepted
644c81fe 83fb01               cmp     ebx, 1      // 比较ebx 是否为1
644c8201 7506                 jne     Qt5Widgets!QDialog::done+0x29 (644c8209) //如果不相等跳转到644c8209
644c8203 6a00                 push    0 // 右侧第一参数, argv = 0
644c8205 6a01                 push    1 // 右侧第二个参数,local_signal_index = 1
644c8207 eb07                 jmp     Qt5Widgets!QDialog::done+0x30 (644c8210)

//不相等跳转到此位置,也就是else if的位置rejected
644c8209 85db                 test    ebx, ebx //使用test指令判断ebx是否为0,但不保存结果
644c820b 750e                 jne     Qt5Widgets!QDialog::done+0x3b (644c821b)
644c820d 53                   push    ebx //右侧第一个参数, argv = ebx
644c820e 6a02                 push    2   //右侧第二个参数,local_signal_index = 2 也就是第三个个信号

//添加从右侧开始3、4的参数,QMetaObject *m 和 QObject *sender
644c8210 68f4976164           push    offset Qt5Widgets!QDialog::staticMetaObject (646197f4)
644c8215 56                   push    esi
644c8216 ffd7                 call    edi   //调用edi的指向的函数,就是644c81f8 那行的activate
644c8218 83c410               add     esp, 10h


//最后调用finished
644c821b 8d442418             lea     eax, [esp+18h]
644c821f 895c2418             mov     dword ptr [esp+18h], ebx
644c8223 89442410             mov     dword ptr [esp+10h], eax
644c8227 8d44240c             lea     eax, [esp+0Ch]
644c822b 50                   push    eax
644c822c 6a00                 push    0
644c822e 68f4976164           push    offset Qt5Widgets!QDialog::staticMetaObject (646197f4)
644c8233 56                   push    esi
644c8234 c744241c00000000     mov     dword ptr [esp+1Ch], 0
644c823c ffd7                 call    edi
644c823e 83c410               add     esp, 10h
644c8241 5f                   pop     edi

小结

1、使用kv显示调用栈

2、认识了调用的字段含义

3、大概讲解了汇编指令中参数是如何传递的

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值