一次虐心的调试-dump分析的基本流程

本文通过一个具体的案例,详细解析了如何使用Windbg调试客户端崩溃产生的DMP文件,特别是针对虚表指针错误导致的问题进行深入分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

同事客户端崩溃,索dmp,上windbg:

0:000> .ecxr

eax=00000000 ebx=19eeddb0 ecx=19eeddb0 edx=2b7e011c esi=05beaf80 edi=0167d4c0  eip=004d7bd8  esp=0018f304 ebp=0018f310 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00200202
game!CRideBase::GetCurPassengerNum+0x8:
004d7bd8 cc int 3

根据加粗指令可知,竟然执行到了int 3指令,也就是eip不对了,eip不对情况有很多,我前面有篇文章介绍了,有兴趣的去看看.下面我们看看下为什么eip的值不对了:

0:000> kb 1
ChildEBP RetAddr Args to Child 
0018f310 1000be66 19eeddb0 05beaf80 00000004 game!CRideBase::GetCurPassengerNum+0x8

查看调用栈,根据返回地址看看是哪里调用到了int 3,加粗的地址为RetAddr


0:000> ub 1000be66 L1
1000be60 ff15d4480410 call dword ptr [Script!CallThisCallFunctionQWord (100448d4)]

上面这条指令是call [100448d4],看看100448d4里的地址是多少

0:000> dd 100448d4 L1
100448d4 1000ba10

100448d4地址里的值是1000ba10,然后看看这个函数的汇编,

0:000> u 1000ba10 L12
1000ba10 55 push ebp
1000ba11 8bec mov ebp,esp
1000ba13 51 push ecx
1000ba14 dbe3 fninit
1000ba16 8b4d10 mov ecx,dword ptr [ebp+10h]
1000ba19 8b450c mov eax,dword ptr [ebp+0Ch]
1000ba1c 03c1 add eax,ecx
1000ba1e 83f900 cmp ecx,0
1000ba21 740a je Script!CallThisCallFunction+0x1d (1000ba2d)
1000ba23 83e804 sub eax,4
1000ba26 ff30 push dword ptr [eax]
1000ba28 83e904 sub ecx,4
1000ba2b 75f6 jne Script!CallThisCallFunction+0x13 (1000ba23)
1000ba2d 8b4d08 mov ecx,dword ptr [ebp+8]
1000ba30 ff5514 call dword ptr [ebp+14h]

1000ba33 59 pop ecx
1000ba34 5d pop ebp
1000ba35 c3 ret

只有加粗的地方才有call,根据寄存器里的值看看call到哪里去了,

0:000> r ecx
Last set context:
ecx=19eeddb0
0:000> dd ebp+8 L1
0018f318 19eeddb0

根据指令mov ecx,dword ptr [ebp+8],可知,如果ebp的值还没有被改变,那么ecx里的值应该等于ebp+8里的值,所以指向两条指令检测一下,证明ebp很有可能还没有被改变,接下来看下面指令,
0:000> dd ebp+14 L1
0018f324 0167d4c0
这是call dword ptr [ebp+14h]调用的地址,看看这个函数里干了什么:
0:000> u 0167d4c0 L10

gui!gui::control::visible
0167d4c0 8a5161 mov dl,byte ptr [ecx+61h]
0167d4c3 8b442404 mov eax,dword ptr [esp+4]
0167d4c7 c0ea03 shr dl,3
0167d4ca 80e201 and dl,1
0167d4cd 3ad0 cmp dl,al
0167d4cf 7409 je gui!gui::control::visible+0x1a (0167d4da)
0167d4d1 8b11 mov edx,dword ptr [ecx]
0167d4d3 89442404 mov dword ptr [esp+4],eax
0167d4d7 ff6244 jmp dword ptr [edx+44h]
0167d4da c20400 ret 4

根据加粗可知是调用的visible函数, 指令 mov edx,dword ptr [ecx]把虚表指针赋值给edx,然后跳转到虚表里偏移44h那个函数去执行,这也是一种虚函数调用的优化方式,不用call,就不要压栈返回地址了,我们知道ecx里的值是19eeddb0,所以看看虚表指针是多少,

0:000> dd 19eeddb0 L1
19eeddb0 2b7e011c

然后去虚表2b7e011c里找到偏移44h的地址,
0:000> dd 2b7e011c + 44 L1
2b7e0160 004d7bd8
0:000> r eip
Last set context:
eip=004d7bd8

经过对比,发现虚表里偏移44的地址正是eip的值,而这个eip是错的,那就可能是虚表地址就不对,

根据gui!gui::control::visible可知,调用visible的对象是gui!gui::control,那看看control的虚表,

0:000> x gui!gui::control::*vftable*
01770374 gui!gui::control::`vftable' = <no type information>
0:000> dds 01770374 + 44 L1
017703b8 0167d710 gui!gui::control::on_visible 

恰好是源码中要调用的on_visible,

void control::visible(bool v)
{
    if(m_visible == v)
    {
        return;
    }
    on_visible(v);
}

根据源码和指令可知,正确的虚表地址应该是01770374, 因此可以断定虚表指针的值是错的,那为什么会错呢?

那特么可能就多了,有可能是this指针就是错的,有可能this是对的,但是虚表指针被写坏了,还有可能是对象已经被释放了,反正各种可能导致虚表指针错了,我是分析不出来了.我最讨厌这样的bug了,分析半天最后发现离找到根本原因还很远,整个人都不好了,所以就写这些吧,如果你能看懂,那么恭喜你,你已经掌握了调试dmp的最常用的一种思路了.

ps:最后你应该发现了,其实出现在调用栈里的GetCurPassengerNum根本就没人调用它,只不过因为虚表指针出问题了,导致eip错了,执行到哪就会显示哪个符号,试想一下,如果eip恰好是一系列可以运行的指令,那就给我们的分析增加难度了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值