Udostępnij za pośrednictwem


Debugowanie błędów StackOverflow

StackOverflowException jest zgłaszany, gdy stos wykonywania przepełnia się, ponieważ zawiera zbyt wiele zagnieżdżonych wywołań metod. Bardzo często dzieje się tak, ponieważ metody wywołuje się rekursywnie.

Załóżmy na przykład, że masz aplikację w następujący sposób:

using System;

namespace temp
{
    class Program
    {
        static void Main(string[] args)
        {
            Main(args); // Oops, this recursion won't stop.
        }
    }
}

Metoda Main będzie stale wywoływać sama siebie, dopóki nie będzie więcej miejsca na stosie. Gdy nie ma więcej miejsca na stosie, nie można kontynuować wykonywania, więc zostanie wyrzucony błąd StackOverflowException.

> dotnet run
Stack overflow.
   at temp.Program.Main(System.String[])
   at temp.Program.Main(System.String[])
   at temp.Program.Main(System.String[])
   at temp.Program.Main(System.String[])
   at temp.Program.Main(System.String[])
   at temp.Program.Main(System.String[])
   <this output repeats many more times>

Gdy zobaczysz, że program zakończy działanie z danymi wyjściowymi w następujący sposób, możesz znaleźć kod źródłowy dla powtarzających się metod i zbadać logikę, która powoduje dużą liczbę wywołań.

Korzystanie z debugera

Często wystarczy zobaczyć ten stos wywołań w powyższej konsoli, aby zidentyfikować problematyczny kod. Jeśli jednak problem nadal nie jest jasny, możesz go debugować dalej.

W tym przykładzie tworzony jest zrzut podstawowy, gdy wystąpi wyjątek StackOverflowException, a następnie ładuje zrzut do lldb (typowy debuger wiersza polecenia systemu Linux) i debuguje go.

  1. Uruchom aplikację, która została skonfigurowana do zbierania zrzutu w przypadku awarii.

    > export DOTNET_DbgEnableMiniDump=1
    > dotnet run
    Stack overflow.
    Writing minidump with heap to file /tmp/coredump.6412
    Written 58191872 bytes (14207 pages) to core file
    

    Notatka

    .NET 6 standaryzuje prefiks DOTNET_ zamiast COMPlus_ dla zmiennych środowiskowych, które konfigurują zachowanie środowiska uruchomieniowego .NET. Jednak prefiks COMPlus_ będzie nadal działać. Jeśli używasz poprzedniej wersji środowiska uruchomieniowego platformy .NET, nadal należy użyć prefiksu COMPlus_ dla zmiennych środowiskowych.

  2. Zainstaluj rozszerzenie SOS przy użyciu dotnet-sos.

    dotnet-sos install
    
  3. Otwórz zrzut w bazie danych lldb i użyj polecenia bt (backtrace), aby wyświetlić stos.

    lldb --core /temp/coredump.6412
    (lldb) bt
    ...
        frame #261930: 0x00007f59b40900cc
        frame #261931: 0x00007f59b40900cc
        frame #261932: 0x00007f59b40900cc
        frame #261933: 0x00007f59b40900cc
        frame #261934: 0x00007f59b40900cc
        frame #261935: 0x00007f5a2d4a080f libcoreclr.so`CallDescrWorkerInternal at unixasmmacrosamd64.inc:867
        frame #261936: 0x00007f5a2d3cc4c3 libcoreclr.so`MethodDescCallSite::CallTargetWorker(unsigned long const*, unsigned long*, int) at callhelpers.cpp:70
        frame #261937: 0x00007f5a2d3cc468 libcoreclr.so`MethodDescCallSite::CallTargetWorker(this=<unavailable>, pArguments=0x00007ffe8222e7b0, pReturnValue=0x0000000000000000, cbReturnValue=0) at callhelpers.cpp:604
        frame #261938: 0x00007f5a2d4b6182 libcoreclr.so`RunMain(MethodDesc*, short, int*, PtrArray**) [inlined] MethodDescCallSite::Call(this=<unavailable>, pArguments=<unavailable>) at callhelpers.h:468
    ...
    
  4. Górna ramka 0x00007f59b40900cc jest powtarzana wielokrotnie. Użyj polecenia SOSip2md, aby dowiedzieć się, jaka metoda zarządzana znajduje się pod adresem 0x00007f59b40900cc.

    (lldb) ip2md 0x00007f59b40900cc
    MethodDesc:   00007f59b413ffa8
    Method Name:          temp.Program.Main(System.String[])
    Class:                00007f59b4181d40
    MethodTable:          00007f59b4190020
    mdToken:              0000000006000001
    Module:               00007f59b413dbf8
    IsJitted:             yes
    Current CodeAddr:     00007f59b40900a0
    Version History:
      ILCodeVersion:      0000000000000000
      ReJIT ID:           0
      IL Addr:            0000000000000000
         CodeAddr:           00007f59b40900a0  (MinOptJitted)
         NativeCodeVersion:  0000000000000000
    Source file:  /temp/Program.cs @ 9
    
  5. Przejdź do wskazanej metody temp. Program.Main(System.String[]) i źródło "/temp/Program.cs @ 9", aby sprawdzić, czy możesz dowiedzieć się, co kod robi źle. Jeśli potrzebne są dodatkowe informacje, możesz użyć dalszych poleceń debugera lub SOS w celu sprawdzenia procesu.