Программа вылетает только при выпуске сборки - как отлаживать? - PullRequest
86 голосов
/ 09 октября 2008

У меня здесь проблема типа «Шредингерский кот» - моя программа (на самом деле набор тестов для моей программы, но, тем не менее, программы) аварийно завершает свою работу, но только при сборке в режиме выпуска и только при запуске из командная строка. С помощью отладки пещерного человека (т. Е. Неприятных сообщений printf () повсюду) я определил метод тестирования, в котором происходит сбой кода, хотя, к сожалению, в некоторых деструкторах, по-видимому, происходит сбой, так как последние сообщения трассировки, которые я вижу другие деструкторы, которые выполняются чисто.

Когда я пытаюсь запустить эту программу внутри Visual Studio, она не падает. То же самое происходит при запуске из WinDbg.exe. Сбой происходит только при запуске из командной строки. Это происходит в Windows Vista, кстати, и, к сожалению, сейчас у меня нет доступа к машине с XP для тестирования.

Было бы здорово, если бы я мог заставить Windows распечатать трассировку стека, или что-то , кроме простого завершения программы, как если бы она вышла полностью. У кого-нибудь есть какие-либо советы относительно того, как я мог бы получить здесь более значимую информацию и, надеюсь, исправить эту ошибку?

Редактировать: Проблема действительно была вызвана массивом вне пределов , который я опишу больше в этом посте . Спасибо всем за помощь в поиске этой проблемы!

Ответы [ 27 ]

119 голосов
/ 09 октября 2008

В 100% случаев, которые я видел или слышал, когда программа на C или C ++ нормально работает в отладчике, но не работает при запуске вне ее, причиной была запись за концом локального массива функции. (Отладчик помещает больше в стек, поэтому у вас меньше шансов перезаписать что-то важное.)

49 голосов
/ 09 октября 2008

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

int* p;
....
if (p == 0) { // do stuff }

В режиме отладки код в if не выполняется, но в режиме выпуска p содержит неопределенное значение, которое вряд ли будет равно 0, поэтому код выполняется часто, вызывая сбой.

Я бы проверил ваш код на наличие неинициализированных переменных. Это также может относиться к содержимому массивов.

20 голосов
/ 29 августа 2013

Пока нет ответа, я попытался дать серьезный обзор доступных методов отладки релизных приложений:

  1. Сборки выпуска и отладки ведут себя по-разному по многим причинам. Вот отличный обзор. Каждое из этих отличий может привести к ошибке в сборке выпуска, которая не существует в сборке отладки.

  2. Наличие отладчика также может изменить поведение программы , как для выпуска, так и для отладки. См. Этот ответ. Короче говоря, по крайней мере, отладчик Visual Studio автоматически использует кучу отладки при подключении к программе. Вы можете отключить кучу отладки, используя переменную окружения _NO_DEBUG_HEAP. Вы можете указать это либо в свойствах вашего компьютера, либо в настройках проекта в Visual Studio. Это может сделать аварийное воспроизведение с подключенным отладчиком.

    Подробнее об отладке повреждения кучи здесь.

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

  4. Вы можете улучшить свой код обработки исключений, и если это производственное приложение, вам следует:

    а. Установите пользовательский обработчик завершения, используя std::set_terminate

    Если вы хотите отладить эту проблему локально, вы можете запустить бесконечный цикл внутри обработчика завершения и вывести некоторый текст на консоль, чтобы уведомить вас о том, что std::terminate был вызван. Затем подключите отладчик и проверьте стек вызовов. Или вы печатаете трассировку стека, как описано в этом ответе.

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

    б. Используйте механизм обработки структурированных исключений Microsoft , который позволяет вам перехватывать исключения как аппаратного, так и программного обеспечения. См. MSDN . Вы можете защитить части своего кода с помощью SEH и использовать тот же подход, что и в а) для устранения проблемы. SEH предоставляет дополнительную информацию об исключении, которое вы можете использовать при отправке отчета об ошибке из производственного приложения.

15 голосов
/ 09 октября 2008

На что обратить внимание:

Переполнения массива - отладчик Visual Studio вставляет отступы, которые могут остановить сбои.

Условия гонки - если в этом случае участвуют несколько потоков, многие из них появляются только при непосредственном выполнении приложения.

Linking - ваша сборка релиза, загружающая правильные библиотеки.

Что попробовать:

Minidump - действительно простой в использовании (просто посмотрите в msdn) даст вам полный аварийный дамп для каждого потока. Вы просто загружаете вывод в визуальную студию, и это как если бы вы отлаживали во время сбоя.

12 голосов
/ 09 октября 2008

Вы можете установить WinDbg в качестве посмертного отладчика. Это запустит отладчик и присоединит его к процессу, когда произойдет сбой. Чтобы установить WinDbg для отладки после смерти, используйте параметр / I (обратите внимание, что это с заглавной буквы ):

windbg /I

Подробнее здесь .

Что касается причины, то, скорее всего, это унифицированная переменная, как показывают другие ответы.

9 голосов
/ 09 октября 2008

После многих часов отладки я наконец-то обнаружил, что причина проблемы, которая действительно была вызвана переполнением буфера, вызвала разницу в один байт:

char *end = static_cast<char*>(attr->data) + attr->dataSize;

Это ошибка ограждения (ошибка off-by-one), исправленная с помощью:

char *end = static_cast<char*>(attr->data) + attr->dataSize - 1;

Странно было то, что я сделал несколько вызовов _CrtCheckMemory () вокруг различных частей моего кода, и они всегда возвращали 1. Я смог найти источник проблемы, поставив «return false;» вызовы в тестовом примере, а затем, в конечном итоге, методом проб и ошибок определить, где произошла ошибка.

Спасибо всем за ваши комментарии - сегодня я многое узнал о windbg.exe! :)

7 голосов
/ 09 октября 2008

Даже несмотря на то, что вы создали свой exe как релиз, вы все равно можете генерировать файлы PDB (базы данных программ), которые позволят вам отслеживать трассировку и выполнять ограниченный объем проверки переменных. В ваших настройках сборки есть возможность создавать файлы PDB. Включите это и перекомпоновайте. Затем попробуйте сначала запустить из IDE, чтобы увидеть, если вы получили сбой. Если так, то отлично - вы готовы смотреть на вещи. Если нет, то при запуске из командной строки вы можете сделать одно из двух:

  1. Запустите EXE, а перед сбоем выполните Attach To Process (меню Сервис в Visual Studio).
  2. После сбоя выберите опцию для запуска отладчика.

Когда вас попросят указать файлы PDB, перейдите к их поиску. Если PDB были помещены в ту же папку вывода, что и ваши EXE или DLL, они, вероятно, будут автоматически подхвачены.

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

Примечание: я предполагаю, что среда Windows / Visual Studio здесь.

3 голосов
/ 09 октября 2008

Чтобы получить аварийный дамп, который вы можете проанализировать:

  1. Генерация файлов pdb для вашего кода.
  2. Вы перебазируете, чтобы ваши exe и dll были загружены по одному и тому же адресу.
  3. Включить отладку после смерти, например, Dr. Уотсон
  4. Проверьте адрес сбоев, используя такой инструмент, как crash finder .

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

Надеюсь, это поможет ...

3 голосов
/ 09 октября 2008

Подобные сбои почти всегда возникают, потому что среда IDE обычно устанавливает содержимое неинициализированной переменной в нули, нуль или другое подобное «разумное» значение, тогда как при естественном запуске вы получите любой случайный мусор, который система обнаружит. .

Поэтому ваша ошибка почти наверняка состоит в том, что вы используете что-то, как вы используете указатель, прежде чем он будет должным образом инициализирован, и вам это сходит с рук в IDE, потому что он не указывает ни на что опасное - или значение обрабатывается проверкой ошибок - но в режиме выпуска это делает что-то неприятное.

2 голосов
/ 09 октября 2008

Однажды у меня возникла проблема, когда приложение вело себя так же, как ваше. Это оказалось неприятным переполнением буфера в sprintf. Естественно, он работал, когда запускался с отладчиком. Что я сделал, так это установил необработанный фильтр исключений ( SetUnhandledExceptionFilter ), в котором я просто бесконечно блокировался (используя WaitForSingleObject на фиктивном дескрипторе со значением времени ожидания INFINITE).

Так что вы могли бы что-то вроде:

long __stdcall MyFilter(EXCEPTION_POINTERS *)
{
    HANDLE hEvt=::CreateEventW(0,1,0,0);
    if(hEvt)
    {
        if(WAIT_FAILED==::WaitForSingleObject(hEvt, INFINITE))
        {
            //log failure
        }
    }

}
// somewhere in your wmain/WinMain:
SetUnhandledExceptionFilter(MyFilter);

Затем я подключил отладчик после того, как ошибка проявилась (программа GUI перестала отвечать).

Тогда вы можете взять дамп и поработать с ним позже:

.dump / ma path_to_dump_file

Или отладить прямо сейчас. Самый простой способ - отследить, где контекст процессора был сохранен механизмом обработки исключений во время выполнения:

s-d esp Диапазон 1003f

Команда будет искать адресное пространство стека для записей КОНТЕКСТА при условии продолжительности поиска. Я обычно использую что-то вроде 'l? 10000' . Обратите внимание, что не используйте неоправданно большие числа в качестве записи, которую вы ищете, обычно рядом с рамкой фильтра необработанных исключений. 1003f - это комбинация флагов (я думаю, что она соответствует CONTEXT_FULL), используемая для захвата состояния процессора. Ваш поиск будет выглядеть примерно так:

0: 000> s-d esp l1000 1003f
0012c160 0001003f 00000000 00000000 00000000? ...............

Получив результаты, используйте адрес в команде cxr:

.cxr 0012c160

Это приведет вас к этому новому КОНТЕКСТУ, точно в момент сбоя (вы получите точно трассировку стека в момент сбоя вашего приложения). Дополнительно используйте:

.exr -1

чтобы точно выяснить, какое исключение произошло.

Надеюсь, это поможет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...