8 марта 2020 г.

Как узнать, почему программа внезапно закрывается?

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

Когда программа может закрыться?

Приложение может завершаться:
  1. Штатно: приложение закрывается его собственный кодом, либо приложение закрывается внешним процессом;
  2. Не штатно: код приложения вызывает фатальную ошибку, приложение закрывает система.
В не столь отдалённой статье мы кратко обрисовали возможные случаи:
  • Приложение само явно вызвало TerminateProcess или ExitProcess (прямо или опосредованно - например, через Halt).
  • Приложение явно завершило все свои потоки (вышло из процедур потоков или же явно вызвало TerminateThread или ExitThread). Это не бывает в Delphi, поскольку компилятор Delphi вставляет неявный вызов Halt в конец главного потока (т.е. всегда вызывает ExitProcess в конце работы главного потока), но это может случиться, если внешний процесс уничтожит главный поток в вашей программе.
  • Какой-то внешний процесс закрыл или уничтожил или ваш процесс или все потоки в нём.
  • В вашей программе произошло необработанное (фатальное) исключение, но в Доктор Ватсон / WER отключен диалог об ошибках.
  • В вашей программе произошло необработанное (фатальное) исключение, в системе зарегистрирован сторонний посмертный отладчик с автоматическим запуском, который не показал сообщения об ошибке.
  • В вашей программе произошло необработанное (фатальное) исключение, которое настолько серьёзно, что система даже не смогла показать сообщение, а посмертный отладчик не зарегистрирован.


Как диагностировать выход из приложения?

Диагностику можно проводить внутри процесса, установив хуки на ключевые функции:
  • Плюсы: можно отдать программу клиенту, он её запустит как обычно, программа сделает диагностику. Т.е. клиенту не нужно ничего дополнительно делать. Иными словами, клиент может не сотрудничать с вами для диагностики;
  • Минусы: не любой выход можно диагностировать внутри самого процесса. Иногда всё настолько плохо, что система закроет процесс, у кода процесса не будет шансов на выполнение.

Диагностику можно проводить из внешнего процесса:
  • Плюсы: получится диагностировать любые выходы из приложения;
  • Минусы: программу нужно запускать под внешним процессом (отладчиком), клиента придётся проинструктировать, он должен сотрудничать с вами.

Вы можете использовать EurekaLog для диагностики как из, так и извне процесса. Сделать вы это можете даже не покупая лицензию. Для диагностики изнутри процесса вы можете использовать редакцию Trial, а для диагностики извне процесса - бесплатный EurekaLog Tools Pack.

Диагностика внутри процесса

  1. Установите EurekaLog;
  2. Включите EurekaLog для своего проекта;
  3. В настройках EurekaLog перейдите на вкладку Features / Restart&Recovery и включите опцию Log application's exits;
  4. Запустите приложение и позвольте ему закрыться;
  5. Откройте папку с отчётами (из Windows: Пуск / Программы / EurekaLog / EurekaLog Bug Reports; или откройте папку %APPDATA%\Neos Eureka S.r.l\EurekaLog\Bug Reports\). Если вы меняли путь для папки с отчётами - открывайте его;
  6. В папке найдите отчёт от вашего приложения. Например: Bug Reports\Project1.exe\Project1_ExitLog.el.
  7. Вы можете добавить проверку существования этого файла при запуске приложения и автоматически отправлять его вам;
  8. Или же вы можете добавить имя этого файла к опции Additional files, чтобы он автоматически был добавлен в следующий отчёт об ошибке.
Несколько замечаний:
  • Эта функция не будет создавать отчёт о выходе через Halt (штатный выход);
  • Функция работает только для приложений, не для DLL;
  • Функция не сможет перехватить выход, инициированный внешним процессом;
  • Отчёты о выходе могут быть ложно-положительными. Например, если приложение выходит через TerminateProcess, но логически это - штатно. Например, при перезапуске по исключению. Поэтому будьте осторожны, если вы хотите показывать какой-то диалог при старте приложения при обнаружении отчёта о выходе;
  • Технически функция сделана как обёртка для RtlReportSilentProcessExit (только Vista+), либо (Windows XP и ранее) хуками на TerminateProcess и TerminateThread;
  • Когда вы включаете EurekaLog для своего проекта, EurekaLog будет вызываться автоматически при фатальных исключениях. Поэтому вместо обычного тихого выхода вы можете получить отчёт об ошибке от EurekaLog.

Когда вы получили отчёт - открываете его как обычно, в EurekaLog Viewer. В отчёте будет стек вызова в момент выхода из приложения, например:
  • ExceptionLog7.ProcessExitHandler 2244[17]
  • EInject.RtlReportSilentProcessExitHook 1166[12]
  • kernel32.TerminateProcess
  • Unit175.TForm1.Button1Click 43[1]
  • Controls.TControl.Click
  • ...


Диагностика внешним отладчиком

Вместе с EurekaLog, а также с EurekaLog Tools Pack устанавливается утилита Threads Snapshot, которая предназначена для захвата стеков всех потоков приложения в определённый момент времени.

Вы можете зарегистрировать утилиту Threads Snapshot в качестве внешнего отладчика для мониторинга выхода из процесса:
  1. Откройте консоль в папке C:\Program Files (x86)\Neos Eureka S.r.l\EurekaLog 7\Bin\ (или Bin64, если у вас - 64-разрядное приложение) под учётной записью администратора;
  2. Запустите:
    threadssnapshot.exe "/watch=Project1.exe"
    Где Project1.exe - имя вашей программы. Это может быть просто имя файла или полный путь к файлу. Эта команда зарегистрирует утилиту Threads Snapshot для мониторинга выхода из указанного процесса. Не закрывайте консоль, она ещё пригодится;
  3. Запустите приложение и позвольте ему закрыться;
  4. Вне выходе будет запущена утилита Threads Snapshot, которая соберёт информацию о выходе, а после подготовки отчёта - попросит его сохранить в файл;
  5. Запустите:
    threadssnapshot.exe "/unwatch=Project1.exe"
    Где Project1.exe - в точности тот же параметр, который вы указывали в п2. Эта команда отменит регистрацию мониторинга.
В результате будет создан обычный EurekaLog отчёт, в котором стек может выглядеть как-то так:
  • ntdll.NtWaitForSingleObject
  • kernel32.TerminateProcess
  • Unit175.TForm1.Button1Click 43[1]
  • Controls.TControl.Click
  • ...

Технически, эта функциональность реализована через Global Flags.


Что ещё можно сделать?

Вот список того, что вы можете попробовать сделать для дополнительной диагностики.

Примечание: в списке ниже ключ реестра Windows Error Reporting\что-то обозначает ключ HKCU\Software\Microsoft\Windows\Windows Error Reporting\что-то, а при его отсутствии - HKLM\Software\Microsoft\Windows\Windows Error Reporting\что-то, либо HKLM\Software\Wow6432Node\Microsoft\Windows\Windows Error Reporting\что-то (для 32-битных приложений на 64-битной машине).
  1. Попробуйте запустить приложение под отладчиком. Убедитесь, что в отладчике не отключены уведомления об исключениях. Если приложение под отладчиком не вылетает или подключить отладчик нет возможности - см. шаги ниже.
  2. Первым делом удалите регистрацию посмертного отладчика в ключе реестра AeDebug, либо хотя бы сбросьте параметр Auto в 0.
  3. [Vista+] Убедитесь, что "Служба регистрации ошибок Windows" ("Windows Error Reporting Service", WerSvc) не отключена (не находится в состоянии Disabled; по-умолчанию у неё тип запуска - Manual, но для надёжности вы можете её запустить вручную).
  4. Запустите Доктор Ватсон в Windows 2000, настройки отчётов в Windows XP, настройки WER в Windows Vista и позднее - и включите визуальные оповещения (Windows 2000), отчёты об ошибках (Windows XP), запрос согласия, т.е. не включайте автоматическую отправку (Windows Vista и выше).
  5. [Vista+] Проверьте настройки групповых политик WER. Убедитесь, что UI не отключен, логгинг не отключен, согласие (consent) не установлено в автоматическую отправку без запросов (DefaultConcent = 1). Не забудьте проверить как политики машины, так и пользователя.
  6. [Vista+] Убедитесь, что ключа реестра Windows Error Reporting\DebugApplications\* нет или он установлен в 1.
  7. [Vista+] Убедитесь, что ключа реестра Windows Error Reporting\DontShowUI нет или он установлен в 0.
  8. [Vista+] Убедитесь, что ключа реестра Windows Error Reporting\LoggingDisabled нет или он установлен в 0.
  9. [Vista+] Очистите все отчёты в журнале стабильности системы.
  10. Убедитесь, что вы не вызываете SetErrorMode с одним из следующих флагов: SEM_FAILCRITICALERRORS, SEM_NOGPFAULTERRORBOX, SEM_NOOPENFILEERRORBOX. Для надёжности сделайте вызов SetErrorMode(0); первым действием при запуске своего приложения.
  11. [Win7+] Убедитесь, что вы не вызываете SetThreadErrorMode с одним из следующих флагов: SEM_FAILCRITICALERRORS, SEM_NOGPFAULTERRORBOX, SEM_NOOPENFILEERRORBOX для ваших потоков. Для надёжности сделайте вызов SetThreadErrorMode(0); первым действием ваших потоков.
  12. [Vista+] Убедитесь, что ваш код не делает вызов WerSetFlags(WER_FAULT_REPORTING_NO_UI);.
  13. [Vista+] Сделайте вызов WerSetFlags(WER_FAULT_REPORTING_ALWAYS_SHOW_UI); первым действием при запуске своего приложения.
  14. Убедитесь, что глобальная переменная System.JITEnable равна 0. Для надёжности присвойте её 0 первым действием при старте приложения.
  15. Запустите ваше приложение и дайте ему вылететь. Если никакого диалога так и не появилось, то выполните нижеследующие шаги.
  16. Проверьте, есть ли записи о вылете приложения в системном логе "Приложения".
  17. Проверьте, нет ли свежих записей в "журнале стабильности системы" или логов в %APPDATA%\Microsoft\Windows\WER\ReportArchive\ / %APPDATA%\CrashDumps\.
  18. Попробуйте назначить свой глобальный обработчик необработанных исключений через системную функцию SetUnhandledExceptionFilter.
  19. Установите точки останова или хуки (в рамках вашего процесса) на TerminateProcess, ExitProcess, а если это не помогло - то и на TerminateThread и ExitThread.
  20. Установите точки останова или хуки на системную функцию kernel32.KiUserExceptionDispatcher - если эта функция будет вызвана непосредственно перед вылетом, то 99% за то, что у вас произошло крайне серьёзное необработанное исключение, при котором система даже не смогла показать сообщение.
  21. Наконец, попробуйте установить глобальный хук (все процессы) на TerminateProcess, TerminateThread, чтобы узнать, не завершает ли ваш процесс кто-то ещё.
  22. Также попробуйте пересобрать приложение под x86-64 или использовать другую версию Delphi (как более новую, так и более старую).

Читать далее: Как узнать почему зависла программа?

2 комментария:

  1. Привет, мне очень нравится твой блог! Веди его дальше!

    Есть альтернатива - madExcept, мне она ранее зашла. Попробую теперь EurekaLog

    ОтветитьУдалить

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и (опционально) ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку.

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

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

Примечание. Отправлять комментарии могут только участники этого блога.