Поделиться через


Устранение ошибок StackOverflow

При переполнении стека выполнения возникает ошибка StackOverflowException, так как в нём содержится слишком много вложенных вызовов методов. Часто это происходит, так как методы вызывает друг друга рекурсивно.

Например, предположим, что у вас есть приложение, как показано ниже.

using System;

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

Метод Main будет рекурсивно вызывать себя, пока не закончится свободное место в стеке. После отсутствия дополнительного пространства стека выполнение не может продолжаться, поэтому он вызовет 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>

Когда вы видите, что программа завершает работу с таким выводом, вы можете найти исходный код повторяющихся методов и исследовать логику, которая приводит к большому количеству вызовов.

Использование отладчика

Часто бывает достаточно только увидеть вышеупомянутый стек вызовов на консоли, чтобы определить проблемный код. Однако если проблема по-прежнему неясна, вы можете продолжить отладку.

В этом примере создается основной дамп при возникновении ошибки StackOverflowException, а затем загружается дамп в lldb (общий отладчик командной строки Linux) и выполняет его отладку.

  1. Запустите приложение, настроенное для сбора дампа при сбое.

    > 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
    

    Заметка

    .NET 6 стандартизирует префикс DOTNET_ вместо COMPlus_ переменных среды, которые настраивают поведение во время выполнения .NET. Однако префикс COMPlus_ продолжит работать. Если вы используете предыдущую версию среды выполнения .NET, необходимо по-прежнему использовать префикс COMPlus_ для переменных среды.

  2. Установите расширение SOS с помощью dotnet-sos.

    dotnet-sos install
    
  3. Откройте дамп в lldb и используйте команду bt (backtrace) для отображения стека.

    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. Верхний кадр 0x00007f59b40900cc повторяется несколько раз. Используйте команду SOS ip2md, чтобы узнать, какой управляемый метод расположен в адресе 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. Перейдите к указанному методу temp.Program.Main(System.String[]) и проверьте исходник "/temp/Program.cs @ 9", чтобы понять, что код делает неправильно. Если требуется дополнительная информация, для проверки процесса можно использовать дополнительные команды отладчика или SOS.