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 nccs=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恰好是一系列可以运行的指令,那就给我们的分析增加难度了