вторник, 2 ноября 2010 г.

Анализ дампов Delphi-приложений. CPU spike (Episode #2)

   Еще один случай гиперактивности программы, когда в один прекрасный момент приложение начало очень активно использовать процессор.


   Не мудрствуя лукаво, получаем дамп этого приложения ([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

Комментариев нет:

Отправить комментарий