Не мудрствуя лукаво, получаем дамп этого приложения ([1]) и приступаем к анализу.
Ищем поток, который потратил на себя процессорного времени больше остальных. Он особо и не скрывается:
0:000> !runaway
User Mode Time
Thread Time
0:85c 0 days 2:18:22.484
9:9e8 0 days 0:00:02.046
21:8e8 0 days 0:00:00.281
2:8d0 0 days 0:00:00.281
13:d60 0 days 0:00:00.250
6:9dc 0 days 0:00:00.171
1:8c8 0 days 0:00:00.140
38:474 0 days 0:00:00.109
5:90c 0 days 0:00:00.093
10:9ec 0 days 0:00:00.078
32:b04 0 days 0:00:00.046
29:9f8 0 days 0:00:00.046
...
Как видим - это главный поток приложения, и занимается он этим безобразием уже более двух часов.
Посмотрим, чем он там так занят:
0:000> k
ChildEBP RetAddr
0012f7c4 2143f73d libUtils!DeCAL.DRedBlackTree.insert+0x5c
0012f808 2143b9ee libKernel!uCache.TCachePool.GetItem+0xdd
0012f830 21150b9a libKernel!uCache.TBaseCache.Item+0xae
0012f8fc 2114d9ac libEvents!uManager.TItemEvent.GetItem+0x1ea
0012fcec 21457299 libgui!uBaseControls.TControl.WndProc+0xd7
0012fd24 21154ecf libgui!uPages.TCustomTabs.WndProc+0x5
0012fd78 77d48744 libgui!uControls.WndProc+0x10b
0012fda4 77d48826 user32!InternalCallWinProc+0x28
0012fe0c 77d4b4d0 user32!UserCallWinProcCheckWow+0x150
0012fe60 77d4b51c user32!DispatchClientMessage+0xa3
0012fe88 7c90eae3 user32!__fnDWORD+0x24
0012feac 77d494e2 ntdll!KiUserCallbackDispatcher+0x13
0012fef4 77d48a20 user32!NtUserDispatchMessage+0xc
0012ff04 201625ed user32!DispatchMessageW+0xf
...
Так-так-так... Во время снятия дампа поток находился в методе insert класса DRedBlackTree (библиотека DeCAL). Посмотрим на него повнимательнее:
function DRedBlackTree.insert(const pair: DPair): Boolean; var x,y: DTreeNode; cmp: Integer; comp: Boolean; j: DTreeNode; begin result := True; y := FHeader; x := FHeader.parent; comp := true; while x <> nil_node do begin y := x; cmp := FComparator(pair.first, x.pair.first); if (cmp = 0) and (not FInsertAlways) then begin _SetDObject(x.pair.second, pair.second); Exit; end; comp := cmp < 0; if comp then x := x.left else x := x.right; end; if FInsertAlways then begin RBInternalInsert(x,y,pair); Exit; end; j := y; if comp then begin if j = Fheader.left then begin RBInternalInsert(x,y,pair); Exit; end else RBDecrement(j); end; if FComparator(j.pair.first, pair.first) < 0 then RBInternalInsert(x,y,pair) else result := False end;Сам по себе метод вроде бы сложный и шанс "поймать" поток именно в нем невелик. С похожей ситуацией прежде уже приходилось сталкиваться (см. CPU spike. Episode #1). Тогда поток зациклился в DRedBlackTree.RBincrement из-за "поломанного" итератора. Так что план действий будет такой - проверим, все ли хорошо в Датском королевстве (в плане того, а не столкнулись ли мы снова с той же самой проблемой).
Для начала определим, где именно находился поток во время снятия дампа:
0:000> r
eax=0400005a ebx=030d8728 ecx=030d872c edx=0012f794 esi=030d8700 edi=0400005a
eip=20409bdc esp=0012f770 ebp=0012f7c4 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
libUtils!DeCAL.DRedBlackTree.insert+0x5c:
20409bdc 7405 je libUtils!DeCAL.DRedBlackTree.insert+0x63 (20409be3) [br=1]
И посмотрим на дизассемблерный листинг в окрестностях найденной точки:
0:000> uf libUtils!DeCAL.DRedBlackTree.insert
libUtils!DeCAL.DRedBlackTree.insert:
20409b80 53 push ebx
20409b81 56 push esi
20409b82 57 push edi
20409b83 83c4ec add esp,0FFFFFFECh
20409b86 891424 mov dword ptr [esp],edx
20409b89 8bf0 mov esi,eax ; Сохранение ссылки Self в esi
20409b8b c644240401 mov byte ptr [esp+4],1
20409b90 8b4608 mov eax,dword ptr [esi+8]
20409b93 89442408 mov dword ptr [esp+8],eax
20409b97 8b581c mov ebx,dword ptr [eax+1Ch]
20409b9a c644240c01 mov byte ptr [esp+0Ch],1
20409b9f eb45 jmp libUtils!DeCAL.DRedBlackTree.insert+0x66 (20409be6)
libUtils!DeCAL.DRedBlackTree.insert+0x21:
20409ba1 895c2408 mov dword ptr [esp+8],ebx
20409ba5 8d4b04 lea ecx,[ebx+4]
20409ba8 8b1424 mov edx,dword ptr [esp]
20409bab 8b4614 mov eax,dword ptr [esi+14h]
20409bae ff5610 call dword ptr [esi+10h]
20409bb1 8bf8 mov edi,eax
20409bb3 85ff test edi,edi
20409bb5 7519 jne libUtils!DeCAL.DRedBlackTree.insert+0x50 (20409bd0)
libUtils!DeCAL.DRedBlackTree.insert+0x37:
20409bb7 807e1c00 cmp byte ptr [esi+1Ch],0
20409bbb 7513 jne libUtils!DeCAL.DRedBlackTree.insert+0x50 (20409bd0)
libUtils!DeCAL.DRedBlackTree.insert+0x3d:
20409bbd 8b1424 mov edx,dword ptr [esp]
20409bc0 83c208 add edx,8
20409bc3 8d430c lea eax,[ebx+0Ch]
20409bc6 e815d6ffff call libUtils!DeCAL._SetDObject (204071e0)
20409bcb e99c000000 jmp libUtils!DeCAL.DRedBlackTree.insert+0xec (20409c6c)
libUtils!DeCAL.DRedBlackTree.insert+0x50:
20409bd0 85ff test edi,edi
20409bd2 0f9c44240c setl byte ptr [esp+0Ch]
20409bd7 807c240c00 cmp byte ptr [esp+0Ch],0
20409bdc 7405 je libUtils!DeCAL.DRedBlackTree.insert+0x63 (20409be3)
libUtils!DeCAL.DRedBlackTree.insert+0x5e:
20409bde 8b5b14 mov ebx,dword ptr [ebx+14h]
20409be1 eb03 jmp libUtils!DeCAL.DRedBlackTree.insert+0x66 (20409be6)
libUtils!DeCAL.DRedBlackTree.insert+0x63:
20409be3 8b5b18 mov ebx,dword ptr [ebx+18h]
libUtils!DeCAL.DRedBlackTree.insert+0x66:
20409be6 3b1d2c904520 cmp ebx,dword ptr [libUtils!DeCAL.nil_node (2045902c)]
20409bec 75b3 jne libUtils!DeCAL.DRedBlackTree.insert+0x21 (20409ba1)
Из увиденного выше можно сделать вывод, что поток, когда с него снимали дамп, был застигнут на проверке условия:
while x <> nil_node do
begin
y := x;
cmp := FComparator(pair.first, x.pair.first);
if (cmp = 0) and (not FInsertAlways) then
begin
_SetDObject(x.pair.second, pair.second);
Exit;
end;
comp := cmp < 0;
if comp then
x := x.left
else
x := x.right;
end;
Попутно замечаем, что в этот момент в регистре ebx должна находится ссылка на объект DTreeNode (переменная x), а в регистре esi - ссылка на объект DRedBlackTree. Проверим:0:000> !object ebx 030d8728 "DTreeNode" 0:000> !object esi 030d8700 "DRedBlackTree"
Теперь мысленно продолжим дальнейшее выполнение и посмотрим, куда приведет нас эта кривая дорожка. Для этого нам необходимо знать, значения полей left/right/parent объекта переменной x:
0:000> dp /c 1 ebx+14 l3 030d873c 030d8728 ; x.left (+0x14) 030d8740 030d8728 ; x.right (+0x18) 030d8744 030d8728 ; x.parent (+0x1c)И что же мы видим? А то, что все эти поля указывают на один и тот же объект - на самого себя. Ну теперь все становится на свои места - у потока нет ни единого шанса самостоятельно выбраться из этого цикла.
Еще проверим, не является ли данный "самозамкнутый" объект Header-ом красно-черного дерева (находится по смещению +0x08 относительна начала DRedBlackTree):
0:000> dp esi+8 l1 030d8708 030d8728 ; DRedBlackTree.FHeader
Подведем итоги.
Поток зациклился в методе DRedBlackTree.insert потому что вершина дерева оказалась полностью замкнута сама на себя. Интуиция подсказывает, что в процессе операций (вставка/удаление элементов) с деревом после удаления последнего элемента вершина дерева замкнулась сама на себя. И последующая попытка вставить новый элемент привела к зацикливанию потока.
Но причина возникновения такой ситуации пока остается загадкой...
Ссылки
[1] Как создать дамп процесса
[2] Process Explorer
[3] Delphi WinDbg Extension Dll
Комментариев нет:
Отправить комментарий