Как мне отлаживать сложный сбой при сбое без полезного стека вызовов?

Я сталкиваюсь с необычным сбоем в нашем программном обеспечении, и у меня много проблем, отлаживая его, и поэтому я ищу 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".