Страницы

Поиск по вопросам

среда, 27 ноября 2019 г.

Определение точки зависания C#-программы

#c_sharp #отладка


Есть программа, которая достаточно редко зависает. Выполнение программы происходит
на удалённых машинах, и возможности запустить отладчик не имеется. Запуск внешнего
профайлера более реален, но тоже сопряжён с огромными сложностями. Как можно, не прибегая
к профайлеру, определить точку зависания программы? 

Вариант "подробное логирование работы программы в файловую систему" плохо подходит.
Программа состоит из около 20 тысяч строк кода, а виснет редко.

Пробовал Process Explorer, но он очень странно работает (либо я не разобрался). Если
успеть "поймать" поток, вошедший в бесконечный цикл, то его стек посмотреть вполне
возможно. Но этот поток достаточно быстро исчезает (то ли в РЕ, то ли его действительно
убивает среда).

Вариант создания ещё одного приложения, приложения-монитора, вполне приемлем. Если
можно как-то создать дамп процесса или получить информацию о потоках этого процесса,
то было бы здорово. Если есть какой-то готовый инструмент, то ещё лучше.
    


Ответы

Ответ 1



Я пользовался такой идеей: Выясняем, произошло ли зависание. Для этого время создаётся фоновый поток, который время от времени пытается выполнить пустой колбэк в главном потоке, и если выполнение занимает подозрительно много, запускается реакция: async Task LoopChecks(CancellationToken ct) { try { // исключить реакцию, если мы побывали в Sleep/Hibernate SystemEvents.PowerModeChanged += OnPowerModeChanged; while (true) { // ожидание следующей проверки await Task.Delay(checkTimeout, ct); PowerStateChanged = false; var pingSuccess = await Check(ct); if (!pingSuccess && !PowerStateChanged) { OnHang(); // колбэк, вызывается когда приложение зависло await Unhung(ct); OnUnhang(); // колбэк, вызывается когда приложение отвисло } } } finally { SystemEvents.PowerModeChanged -= OnPowerModeChanged; } } async Task Check(CancellationToken ct) { var ping = PingMain(ct); var delay = Task.Delay(pingTimeout, ct); var succeeded = (await Task.WhenAny(ping, delay)) == ping; ct.ThrowIfCancellationRequested(); return succeeded; } Task PingMain(CancellationToken ct) { return Task.Factory.StartNew( () => { /* nothing */ }, ct, TaskCreationOptions.PreferFairness, mainTaskScheduler); } void OnPowerModeChanged(object sender, PowerModeChangedEventArgs e) { PowerStateChanged = true; } Свойство PowerStateChanged должно быть, разумеется, защищено внутри lock'ом, поскольку будет изменяться из непонятно какого потока внутри OnPowerModeChanged. В случае, если зависание сдетектировано, происходило много вещей. Пользователю показывался (в новом UI-потоке, разумеется) диалог о зависании программы (который автоматически скрывался при отвисании). Запускалось внешнее приложение, которое снимало скриншот и запускало отладчик командной строки mdbg со скриптом, который присоединялся к процессу, снимал список загруженных модулей, список потоков и их стеков, и отсоединялся: !a {0} !l -v modules !w -v all !block !fo p !de !quit Результаты потом можно было упаковать и отправить «домой» основной программой.

Ответ 2



Вы можете создать дамп процесса и затем исследовать его с помощью WinDbg. Создать дамп можно: с помощью Process Explorer (в контекстном меню процесса выберите Create Dump -> Create Full Dump) с помощью собственно WinDbg. Для этого откройте WinDbg, выберите команду меню File -> Attach to а Process, и выберите ваш процесс из списка. В открывшемся окне внизу будет строка для ввода команд. Выполните .dump /ma D:\Dump_file_name.dmp, затем .detach с помощью стандартного диспетчера задач Windows. В контекстном меню процесса есть команда создания дампа, которая сохраняет дамп в директорию Temp и сообщает путь к файлу дампа Последний способ конечно оптимальный, потому что можно довольно просто проинструктировать юзеров как создать дамп, но честно говоря не уверен, что он создает дамп с достаточным количеством информации. Поэтому пробуйте, но если третий способ не подойдет, используйте первые два. Чтобы исследовать дамп вам потребуется: Windbg Библиотека sos.dll, которая учит WinDbg работать с управляемым кодом. Эту библиотеку не надо устанавливать отдельно, она уже есть на вашей машине с Visual Studio, причем в нескольких вариантах. Пример пути к 32-битной версии для .Net 4 - C:\Windows\Microsoft.NET\Framework\v4.0.30319\sos.dll sosex.dll - библиотека расширений для sos.dll, которая добавляет дополнительные команды. Вот здесь, например, версия для .Net 4. Когда будете работать со всем этим следите за разрядностью. Если у вас 32-битное приложение, вам надо использовать 32-битные версии WinDbg, sos.dll и sosex.dll. Кроме того при выборе версий sos.dll и sosex.dll надо следить за версией .NET фреймворка. Ваша сессия работы с дампом памяти будет начинаться так. В WinDbg откройте дамп командой меню File -> Open Crash Dump. Затем выполните следующие команды: .load C:\Windows\Microsoft.NET\Framework\v4.0.30319\sos.dll // подгружаем sos.dll .symfix C:\Путь_к_исходникам_проекта // подгружаем символы .load C:\Tools\sosex_32\sosex.dll // подгружаем sosex !bhi /* sosex попросит вас выполнить эту команду, команда создаст индекс объектов кучи для ускорения работы команд */ С этого момента ваша рабочая среда готова и вы можете исследовать дамп с помощью команд из sos.dll (официальная справка здесь) и из sosex.dll (краткую справку по камандам найдете в файле readme, в загруженном архиве с sosex). Конкретные рекомендации по поиску проблемы дать сложно. Я таким способом искал дедлоки в игре. В моем случае работа начиналась с вызова команды !dlk, которая очень часто сразу показывает где находится дедлок. Если эта команда не давала результатов, я сам искал проблемные блокировки с помощью команд !mlocks (показывает все залоченные объекты) и !mwaits (показывает все потоки, находящиеся в режиме ожидания). Если в вашем приложении есть какая-то многопоточность, возможно, вам тоже стоит начать с этого. Либо просто начните с просмотра потоков (!threads) и стека (!clrstack) и по мере продвижения решайте какие еще команды вам могут понадобиться. Будет интересно!

Ответ 3



Если зависания долговременные - просто запустите с отладкой из студии, сделайте действия приводящие к зависанию, и в момент зависания нажмите в студии на кнопку "Break All"(с иконкой "пауза") - попадете в места в коде, которые выполняются в данный момент. Если же зависания кратковременны, и поймать нужный момент не удается, то для этого в студии в меню отладки есть профайлер - с его помощью вы можете легко определить места в коде которые дольше всего выполняются, которые чаще всего выполняются, больше всего тратят ресурсы процессора, видеокарты, съедают оперативную память, нагружают сеть, требуют наибольших затрат электроэнергии и т.д.

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

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