Как мне отлаживать сложный сбой при сбое без полезного стека вызовов?
Я сталкиваюсь с необычным сбоем в нашем программном обеспечении, и у меня много проблем, отлаживая его, и поэтому я ищу SO советы о том, как справиться с этим.
Сбой - это нарушение доступа, считывающее указатель NULL:
Первое случайное исключение в $00CF0041. Класс исключения $C0000005 с сообщением "Нарушение доступа при 0x00cf0041: читать адреса 0x00000000 '.
Это случается только "иногда" - мне не удалось выяснить какую-либо рифму или причину, но, когда - и только в основном потоке. Когда это происходит, стек вызовов содержит одну неверную запись:
![Call stack with one line, Classes::TList::Get, address 0x00cf0041]()
Для основного потока, который это значит, он должен показать большой стек, полный других элементов.
В этот момент все остальные потоки неактивны (в основном сидят в WaitForSingleObject
или аналогичной функции.) Я только видел, что этот сбой произошел в основном потоке. Он всегда имеет один и тот же стек вызовов одной записи, в том же методе по тому же адресу. Этот метод может быть или не быть связан - мы используем VCL в нашем приложении. Моя ставка, однако, заключается в том, что что-то (возможно, совсем недавно) разрушает стек, а адрес, где он сбой, является случайным. Обратите внимание, что это был один и тот же адрес в нескольких сборках, но, вероятно, он не был случайным.
Вот что я пробовал:
- Попытка воспроизвести его надежно в определенный момент. Я не нашел ничего, что воспроизводит его каждый раз, и несколько вещей, которые иногда случаются или нет, без видимых причин. Это не "узкие" действия, чтобы сузить его до определенного раздела кода. Это может быть связано с синхронизацией, но в момент, когда IDE ломается, другие потоки обычно ничего не делают. Я не могу исключить проблему с потоками, но думаю, что это маловероятно.
- Создание дополнительных инструкций отладки (дополнительная информация об отладке, дополнительные подтверждения и т.д.) После этого авария никогда не возникает.
- Работает с Codeguard. После этого авария никогда не возникает, и Codeguard не показывает ошибок.
Мои вопросы:
1. Как узнать, какой код вызвал сбой? Как сделать эквивалент ходьбы по стопке?
2. Какой общий совет у вас есть, чтобы проследить причину этого сбоя?
Я использую Embarcadero RAD Studio 2010 (проект в основном содержит код С++ Builder и небольшие количества Delphi.)
Изменить: Я думал, что должен добавить то, что на самом деле вызвало это. Был поток, который назывался ReadDirectoryChangesW
, а затем, используя GetOverlappedResult
, ждал продолжения события и сделал что-то с изменениями. Событие также было сигнализировано, чтобы завершить поток после установки флага состояния. Проблема заключалась в том, что когда поток вышел из него, он никогда не назывался CancelIO
. В результате Windows по-прежнему отслеживала изменения и, вероятно, все еще записывала в буфер при изменении каталога, даже несмотря на то, что структура перекрытия и перекрытие буфера перестали существовать (а также контекст потока, в котором они были созданы.) Когда CancelIO
был вызван, больше не было сбоев.
Ответы
Ответ 1
Даже если трассировка стека, предоставленной IDE, не очень полная, это не означает, что в стеке еще нет полезной информации. Откройте представление процессора и проверьте панель стека; для каждого кода операции CALL обратный адрес помещается в стек. Поскольку стек растет вниз, вы найдете эти обратные адреса выше текущего местоположения стека, то есть путем прокрутки вверх в стеке стека.
Стек для основного потока будет где-то около $00120000 или $00180000 (рандомизация адресного пространства в Vista и выше сделала его более случайным). Код для основного исполняемого файла будет где-то около $00400000. Вы можете спекулятивно исследовать элементы в стеке, которые не похожи на целые данные (низкие значения) или адреса стека ($ 00120000 + range), щелкнув правой кнопкой мыши на записи стека и выбрав Follow → Near Code, что приведет к тому, что окно разборки перейдет к этому кодовому адресу. Если он выглядит как недопустимый код, он, вероятно, не является допустимой записью в трассировке стека. Если это действительный код, это может быть код ОС (часто около $77000000 и выше), и в этом случае у вас не будет значимых символов, но каждый раз вы попадаете в фактическую правильную запись в стеке.
Этот метод, хотя и несколько трудоемкий, может получить вам значимую информацию о трассировке стека, когда отладчик не сможет проследить его. Это не поможет вам, если ESP (указатель стека) был привинчен. К счастью, это довольно редко.
Ответ 2
Вот почему я сделал средство просмотра стека процессов:-)
http://code.google.com/p/asmprofiler/wiki/ProcessStackViewer
Он может отображать стек с трассировкой стека raw, поэтому он покажет полный стек, когда обычная трассировка стека невозможна.
Но будьте осторожны: необработанная трассировка стека покажет "ложные срабатывания"! Будет указан любой адрес в стеке, для которого можно найти имя функции.
Это помогло мне несколько раз, когда я столкнулся с той же проблемой, что и ваш (обычный ход стека по Delphi из-за неправильного состояния стека)
Изменить: новая версия загружена, на веб-сайте была старая версия (я сам использую новую версию)
http://asmprofiler.googlecode.com/files/AsmProfiler_Sampling%20v1.0.7.13.zip
Ответ 3
Возможно, это может быть проблема с потоками. Обычным подозреваемым являются потоки, которые используют структуры OVERLAPPED в стеке и потоки, которые отправляют указатели на объекты, находящиеся в стеке, на другие потоки.
Может быть возможно восстановить информацию о частичном стеке, если вы используете Deubgging Tools for Windows и используйте команду "dps".
Ответ 4
Я не уверен на 100%, но из предоставленного вами изображения я считаю, что где-то рядом с исполнением вы пытаетесь получить доступ к объекту в TList, который является NULL. т.е.:
AList[Index].SomeProperty/SomeMethod/etc. <-- error if (AList[Index] == NULL)
Относительно отладки и поиска фактического места, где возникает исключение, никогда не бывает легкой задачей, особенно когда информации не существует или ее трудно воспроизвести, в этом случае я обычно:
-
Переход шаг за шагом из основного исполнения формы (если исключение не существует)
-
во время поэтапного перехода, если я нахожу какой-то небезопасный код, я помещаю его между try... except и условиями для индексов (если у меня есть массивы, списки, ожидаемые значения и т.д.)
-
если выше не удается найти проблему, проверьте, не работают ли некоторые библиотеки
-
используйте журнал Eureka, он иногда терпит неудачу (очень мало раз), но он обычно указывает вам в правильном направлении
У меня были многочисленные проблемы, похожие на ваши, и я могу сказать вам, что проблема была почти очень легко исправить, однако при появлении ошибки я не получил ошибку "point near".