четверг, 13 марта 2008 г.

Post mortem

Этот пост начинает серию о диагностике windows приложений. Традиционно, для того что-бы найти ошибки в работающей программе, используются лог файлы. Программа пишет себе в лог, а в случае возникновения проблем разработчик по логам может найти баг. Здесь есть несколько проблем, главная - нельзя писать много, во первых сложно анализировать, во вторых возникает оверхед, особенно, когда одновременно работает множество программ работающих с диском... Ну и конечно проблема с тем, что писать, а что нет. Значения переменных, вызовы функций, все это записывается только за тем, что-бы в случае возникновения проблем, можно бы было приблизительно восстановить состояние стека. Есть еще один путь, использование средств ОС для диагностирования программы. Самое вкусное, на мой взгляд, отладочный дамп. Для создания дампа нужно использовать средства библиотеки DbgHelp. А именно функцию MiniDumpWriteDump. Функция получает на вход структуру EXCEPTION_POINTERS, которая содержит контекст в котором произошло исключение. Эту структуру в принципе можно получить при обработке исключения, с помощю функции GetExceptionInformation (эту функцию можно вызывать только внутри filter expression(__except) обработчика исключения), а так-же при срабатывании фильтра необработанных исключений (поставить можно функцией SetUnhandledExceptionFilter). Так же функция принимает хэндл открытого для записи файла, идентификатор и хэндл процесса. В опциях проекта нужно сказать компоновщику, что нужно генерировать отладочную информацию (указать *.pdb файл). код:
LONG WINAPI UExceptFilter(_EXCEPTION_POINTERS* ExceptionInfo)
{
    char block[_MAX_PATH];
    SYSTEMTIME st;
    GetLocalTime(&st);
    _snprintf(block, _MAX_PATH, "CrashDump-%04d%02d%02d%02d%02d%02d%03d.dmp", 
        st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
    HANDLE hProc = GetCurrentProcess();
    HANDLE hFile = CreateFile(
                        block, 
                        GENERIC_READ|GENERIC_WRITE, 
                        FILE_SHARE_READ, 
                        NULL,
                        CREATE_ALWAYS, 
                        FILE_ATTRIBUTE_NORMAL, 
                        NULL
                        );
    MINIDUMP_EXCEPTION_INFORMATION eInfo;
    eInfo.ExceptionPointers = ExceptionInfo;
    eInfo.ThreadId = ::GetCurrentThreadId();
    eInfo.ClientPointers = NULL;
    BOOL ret = MiniDumpWriteDump(    
                        hProc, 
                        GetProcessId(hProc), 
                        hFile,
                        MiniDumpWithFullMemory,
                        ExceptionInfo ? &eInfo : NULL,
                        NULL,NULL
                );
    CloseHandle(hFile);
    return EXCEPTION_CONTINUE_SEARCH;
} 

int _tmain(int argc, _TCHAR* argv[])
{
    SetUnhandledExceptionFilter(&UExceptFilter);
    //.........
}
Далее, после возникновения ошибки, просим клиента отправить нам отладочный дамп, можно это дело автоматизировать, и отправлять средствами самой программы. Потом открываем его студией (OpenSolution, потом выбираем тип файла memory dump), и запускаем отладчик. Нужно что-бы в папке с дампом находился исполняемый файл, файл с отладочной информацией (*.pdb), и исходники - все это должно быть той-же версии что и экзешник записавший дамп, желательно, что-бы версии используемых библиотек так-же совпадали (хотя есть подозрение что будет работать в любом случае). После запуска можно будет в окне call stack просматривать состояние стека так, как-будто запущена программа, можно так-же смотреть значения локальных переменных. Но есть одна проблема, место возникновения исключения нельзя узнать точно из за оптимизаций компилятора, который может инлайнить многие функции. Самое сложное в этом - хранить исходники, exe и pdb файлы для каждого релиза :)

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

Alex Nekipelov комментирует...

Интересный блог, буду почитывать :-)

Статейка старая, но захотелось оставить комментарий.

Подкладывание pdb файлов в каталог с дампом очень неудобное решение. Версий может быть много и придется вручную подбирать pdb файлы... гораздо проще сделать это автоматизировав выкладывание pdb файлов для каждой сборки в единое хранилище. Можно это сделать с помощью symstore (входит в пакет Debugging Tools for Windows), но у меня это выполняется своей утилитой, которая выкладывает файлы на ftp. А для программ, использующих pdb файлы, сделать переменную окружения _NT_SYMBOL_PATH со значением вроде такого: srv*d:\symstore*http://main/pdb/store;SRV*d:\symstore*http://msdl.microsoft.com/download/symbols

В этом случае отладчик сам найдет pdb файлы для нужного релиза. Только некоторые нехорошие программы, будут делать запросы за pdb файлами не смотря на их наличие в каталоге кеша, поэтому адрес сервера можно убрать. Предварительно натравив на файл дампа symchk.exe (входит в пакет Debugging Tools for Windows), указав ключем /s путь к серверу, получим в кеш pdb файлов все, что нужно.

Ну а для анализа дампа я бы советовал использовать не Visual Studio, а WinDbg (входит в пакет Debugging Tools for Windows). Он работает гораздо быстрее, не требует наличия исходников и бинарников. Ну а встроенные команды вроде !analyze -v или !locks, частенько помогают выяснить причину падения за несколько секунд.

Прям какая-то реклама Debugging Tools for Windows получилась... :-)

Alex Nekipelov комментирует...

Интересный блог, буду почитывать :-)

Статейка старая, но захотелось оставить комментарий.

Подкладывание pdb файлов в каталог с дампом очень неудобное решение. Версий может быть много и придется вручную подбирать pdb файлы... гораздо проще сделать это автоматизировав выкладывание pdb файлов для каждой сборки в единое хранилище. Можно это сделать с помощью symstore (входит в пакет Debugging Tools for Windows), но у меня это выполняется своей утилитой, которая выкладывает файлы на ftp. А для программ, использующих pdb файлы, сделать переменную окружения _NT_SYMBOL_PATH со значением вроде такого: srv*d:\symstore*http://main/pdb/store;SRV*d:\symstore*http://msdl.microsoft.com/download/symbols

В этом случае отладчик сам найдет pdb файлы для нужного релиза. Только некоторые нехорошие программы, будут делать запросы за pdb файлами не смотря на их наличие в каталоге кеша, поэтому адрес сервера можно убрать. Предварительно натравив на файл дампа symchk.exe (входит в пакет Debugging Tools for Windows), указав ключем /s путь к серверу, получим в кеш pdb файлов все, что нужно.

Ну а для анализа дампа я бы советовал использовать не Visual Studio, а WinDbg (входит в пакет Debugging Tools for Windows). Он работает гораздо быстрее, не требует наличия исходников и бинарников. Ну а встроенные команды вроде !analyze -v или !locks, частенько помогают выяснить причину падения за несколько секунд.

Прям какая-то реклама Debugging Tools for Windows получилась... :-)