Программа вылетает только при сборке релиза - как отлаживать?

98

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

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

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

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

Ник Рейман
источник
Можете ли вы привести образец этого метода тестирования?
akalenuk
Нет, извините, код слишком сложен, чтобы его можно было легко вставить здесь, и, как я уже упоминал, это происходит не в самом методе тестирования, а в деструкторе впоследствии. Однако в этом методе нет неинициализированных указателей или чего-либо подобного.
Ник Рейман
3
Большинство ответов - не более чем предположения. Существует несколько распространенных методов анализа сбойных сборок выпуска без подключения отладчика: stackoverflow.com/a/18513077/214777?stw=2
Себастьян
Может, это не твоя вина: опасен ли уровень оптимизации -O3 в g ++?
Брент

Ответы:

130

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

Джеймс Карран
источник
32
Кто-нибудь, дайте этому человеку сигару! В моем случае я передавал StringBuilder, у которого не было достаточно большой емкости, для функции P / Invoke. Я думаю, это похоже на то, как кто-то пишет на вашем лице волшебным маркером, когда вы спите: под отладчиком они в конечном итоге рисуют вам на лбу, так что вы не замечаете, но без отладчика они в конечном итоге наносят вам удар в глаз ... что-то в этом роде. Спасибо за этот совет!
Николас Пясецкий
1
В моем случае это оказалось проблемой выравнивания на процессоре ARM, использующем Obj-C.
Almo
1
11 лет спустя, и это все еще звучит правдоподобно ... не забудьте зарезервировать свои векторы.
Дав
1
Хорошо, тогда как можно изменить поведение режима отладки, чтобы можно было выполнять отладку.
Пол Чайлдс
1
«Теперь , зная , где искать» , но как это все работает в отладке сказать вам , где проблема. Хотя я думаю, что в большинстве случаев ваш ответ верен, и знание того, что искать, - хорошее начало, просмотр большой базы кода для точного определения того, где находится проблема, может оказаться непомерно дорогостоящим.
Пол Чайлдс
56

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

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

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

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

Дэвид Диббен
источник
Типичные случаи - это забывают поместить переменную-член в (один из) список инициализации члена конструктора. Имеет тот же эффект, но его труднее найти, если вы не знаете, что вам также следует искать правильную инициализацию члена.
steffenj
1
В режиме отладки переменные обычно инициализируются некоторой «константой, определенной компилятором», которую можно использовать при отладке, чтобы указать, в каком состоянии находится переменная. Например: указатели NULL или 0xDeadBeef популярны.
Мартин Йорк,
Среда выполнения отладки обычно инициализирует память некоторым ненулевым значением, в частности, чтобы тесты указателя NULL заставляли код действовать так, как если бы указатель был ненулевым. В противном случае у вас есть код, который работает правильно в режиме отладки, который вызывает сбой в режиме выпуска.
Майкл Берр,
1
Нет, переменные вообще не инициализируются, и UB по-прежнему "использовать" их, пока они не назначены. Однако базовое содержимое памяти часто предварительно заполняется 0x0000000 или 0xDEADBEEF или другими распознаваемыми шаблонами.
Гонки легкости на орбите
27

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

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

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

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

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

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

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

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

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

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

Себастьян
источник
16

На что следует обратить внимание:

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

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

Связывание - ваша сборка выпуска использует правильные библиотеки.

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

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

Morechilli
источник
1
Привет! Я проголосовал против этого ответа анонимно. Я хочу понять почему?
Morechilli
12

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

windbg /I

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

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

Франси Пенов
источник
2
И не забывайте, что вы можете заставить компилятор генерировать файлы PDB даже для сборок выпуска, хотя это не по умолчанию.
Майкл Берр,
Единственный реальный ответ на вопрос действительно.
Себастьян
10

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

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

Это ошибка заборного столба (ошибка с разницей в одну), которая была исправлена:

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

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

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

Ник Рейман
источник
8
Сегодня я отлаживал аналогичную проблему, и _CrtCheckMemory () всегда возвращал 1. Но потом я понял, почему: в режиме Release _CrtCheckMemory # определяется как ((int) 1).
Брайан Морарти,
7

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

  1. Запустите EXE и перед сбоем выполните команду «Присоединить к процессу» (меню «Инструменты» в Visual Studio).
  2. После сбоя выберите вариант запуска отладчика.

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

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

NB: здесь я подразумеваю среду Windows / Visual Studio.

Грег Уитфилд
источник
3

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

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

Cruachan
источник
3

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

  1. Сгенерируйте файлы pdb для своего кода.
  2. Вы выполняете переустановку, чтобы ваши exe и dll загружались по одному и тому же адресу.
  3. Включите посмертный отладчик, например « Доктор Ватсон»
  4. Проверьте адрес сбоев при сбое с помощью такого инструмента, как поиск сбоев .

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

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

Юваль Пелед
источник
3

Отличный способ отладить такую ​​ошибку - включить оптимизацию для вашей отладочной сборки.

Mgill404
источник
2

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

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

длинный __stdcall MyFilter (EXCEPTION_POINTERS *)
{
    ОБРАБОТКА hEvt = :: CreateEventW (0,1,0,0);
    если (hEvt)
    {
        если (WAIT_FAILED == :: WaitForSingleObject (hEvt, INFINITE))
        {
            // ошибка журнала
        }
    }

}
// где-то в вашем wmain / WinMain:
SetUnhandledExceptionFilter (MyFilter);

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

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

.dump / ma path_to_dump_file

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

sd esp Диапазон 1003f

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

0: 000> SD ESP l1000 1003f
0012c160 0001003f 00000000 00000000 00000000? ...............

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

.cxr 0012c160

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

.exr -1

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

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

Димок
источник
2

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

Мохамад мехди Харатизаде
источник
1

Что касается ваших проблем с получением диагностической информации, пробовали ли вы использовать adplus.vbs в качестве альтернативы WinDbg.exe? Чтобы присоединиться к запущенному процессу, используйте

adplus.vbs -crash -p <process_id>

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

adplus.vbs -crash -sc your_app.exe

Полную информацию о adplus.vbs можно найти по адресу: http://support.microsoft.com/kb/286350

DocMax
источник
1

Ntdll.dll с прикрепленным отладчиком

Одно небольшое различие между запуском программы из IDE или WinDbg и запуском из командной строки / рабочего стола заключается в том, что при запуске с подключенным отладчиком (например, IDE или WinDbg) ntdll.dll использует другую реализацию кучи, которая выполняет небольшую проверку о выделении / освобождении памяти.

Вы можете прочитать некоторую важную информацию в неожиданной пользовательской точке останова в ntdll.dll . Одним из инструментов, который может помочь вам определить проблему, является PageHeap.exe .

Анализ сбоев

Вы не писали, какой у вас "сбой". Как только программа выйдет из строя и предложит вам отправить информацию об ошибке в Microsoft, вы сможете щелкнуть по технической информации и проверить хотя бы код исключения, а с некоторыми усилиями вы даже сможете выполнить посмертный анализ (см. Heisenbug : Программа WinApi вылетает на некоторых компьютерах) для инструкций)

Suma
источник
1

Vista SP1 имеет действительно хороший генератор аварийных дампов, встроенный в систему. К сожалению, по умолчанию он не включен!

См. Эту статью: http://msdn.microsoft.com/en-us/library/bb787181(VS.85).aspx

Преимущество этого подхода заключается в том, что в уязвимой системе не требуется устанавливать дополнительное программное обеспечение. Возьми его и разорви, детка!


источник
1

По моему опыту, это большинство проблем с повреждением памяти.

Например :

char a[8];
memset(&a[0], 0, 16);

: /*use array a doing some thing */

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

Но в выпуске это могло бы привести к сбою.

Для меня слишком утомительно рыться там, где память не ограничена.

Использование некоторых инструментов, таких как Visual Leak Detector (windows) или valgrind (linux), является более разумным выбором.

Гайгер Чен
источник
1

Я видел много правильных ответов. Однако ничто не помогло мне. В моем случае было неправильное использование инструкций SSE с невыровненной памятью . Взгляните на свою математическую библиотеку (если вы ее используете) и попробуйте отключить поддержку SIMD, перекомпилировать и воспроизвести сбой.

Пример:

Проект включает mathfu и использует классы с вектором STL: std :: vector <mathfu :: vec2> . Такое использование, вероятно, вызовет сбой во время создания элемента mathfu :: vec2, поскольку распределитель по умолчанию STL не гарантирует требуемого 16-байтового выравнивания. В этом случае, чтобы доказать идею, можно определить #define MATHFU_COMPILE_WITHOUT_SIMD_SUPPORT 1перед каждым включением mathfu , перекомпилировать в конфигурации Release и снова проверить.

В Debug и RelWithDebInfo конфигурация работала хорошо для моего проекта, но не в релизе один. Причина такого поведения, вероятно, заключается в том, что отладчик обрабатывает запросы на выделение / освобождение и выполняет некоторую учетную запись памяти для проверки и проверки доступа к памяти.

Я испытал ситуацию в средах Visual Studio 2015 и 2017.

Влад Сергиенко
источник
0

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

Ну, по правде говоря, это была моя вина, а не gcc, поскольку я не заметил, что мой код полагался на тот факт, что эта конкретная оптимизация не была бы проведена.

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

Remo.D
источник
0

Я нашел эту статью полезной для вашего сценария. ISTR параметры компилятора были немного устаревшими. Изучите параметры проекта Visual Studio, чтобы узнать, как создавать файлы pdb для сборки выпуска и т. Д.

шипучка
источник
0

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

Ник
источник
0

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

Один из способов, по крайней мере, сузить проблему - использовать MessageBox () для отображения быстрых операторов, указывающих, в какой части программы находится ваш код («Запуск Foo ()», «Запуск Foo2 ()»); начните размещать их в верхней части функций в той области вашего кода, которую вы подозреваете (что вы делали в то время, когда он разбился?). Когда вы можете сказать, какая функция, измените окна сообщений на блоки кода или даже отдельные строки внутри этой функции, пока вы не сузите ее до нескольких строк. Затем вы можете начать распечатывать значения переменных, чтобы увидеть, в каком состоянии они находятся в момент сбоя.


источник
Он уже пробовал добавить printfs, поэтому окна сообщений не принесут ничего нового на вечеринку.
Грег Уитфилд,
0

Попробуйте использовать _CrtCheckMemory (), чтобы узнать, в каком состоянии находится выделенная память. Если все идет хорошо, _CrtCheckMemory возвращает TRUE , иначе FALSE .

Ваэрун
источник
0

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

Марчин Гил
источник
0

Сделайте так, чтобы ваша программа генерировала мини-дамп при возникновении исключения, затем откройте его в отладчике (например, в WinDbg). Ключевые функции, на которые стоит обратить внимание: MiniDumpWriteDump, SetUnhandledExceptionFilter

михайлицкий
источник
0

Вот случай, который кто-то может найти поучительным. Он разбился только в выпуске Qt Creator - не при отладке. Я использовал файлы .ini (поскольку я предпочитаю приложения, которые можно копировать на другие диски, а не те, которые теряют свои настройки в случае повреждения реестра). Это относится к любым приложениям, которые хранят свои настройки в дереве каталогов приложений. Если сборки отладки и выпуска находятся в разных каталогах, у вас также могут быть разные настройки. У меня были отмечены предпочтения в одном, но не в другом. Это оказалось причиной моего крушения. Хорошо, что нашел.

Ненавижу это говорить, но я диагностировал сбой только в MS Visual Studio Community Edition; после установки VS, позволив моему приложению вылететь в Qt Creator, и выбрав его в отладчике Visual Studio . Хотя в моем приложении Qt не было информации о символах, оказалось, что в библиотеках Qt она была. Это привело меня к обидной линии; так как я мог видеть, какой метод вызывается. (Тем не менее, я думаю, что Qt - это удобный, мощный и кроссплатформенный фреймворк LGPL.)

CodeLurker
источник
0

У меня тоже была эта пробема. В моем случае режим RELEASE содержал msvscrtd.dll в определении компоновщика. Мы удалили его, и проблема решена.

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

Паван Диттакави
источник
-3

У меня была эта ошибка, и vs вылетел даже при попытке! Clean! мой проект. Поэтому я вручную удалил файлы obj из каталога Release, и после этого все было нормально.

Chris89
источник
-6

Я согласен с Рольфом. Поскольку воспроизводимость очень важна, у вас не должно быть режима без отладки. Все ваши сборки должны быть отлаживаемыми. Наличие двух целей для отладки более чем вдвое увеличивает отладочную нагрузку. Просто отправьте версию для «режима отладки», если она не пригодна для использования. В таком случае сделайте его пригодным для использования.

шум
источник
Это может работать для 10% приложений, но, конечно, не для всех. Хотели бы вы играть в игры, выпущенные как сборки DEBUG? Отдать свой секретный код безопасности, защищенный торговой маркой, в режиме, удобном для разборки, может быть, даже вместе с PDB? Думаю, нет.
steffenj
Штеффень: Я хочу, чтобы разработчики игр находили ошибки. В идеале до отправки, но если это будет после, я хочу, чтобы они могли получить достаточно информации, чтобы воспроизвести и отследить ее. если это секретный код, товарный знак не применяется. PDB? Банк данных белков? отладчик питона?
wnoise
ИМХО, это плохая идея. Исполняемые файлы больше, они не оптимизированы и работают намного медленнее. Эти случаи действительно довольно редки; хотя особенно бесит, когда они все же случаются. Вы не должны поставлять неизменно худший продукт, беспокоясь о крайне редких случаях отладки в худшем случае. (Мой не был одним из многих, кто проголосовал против). Я занимался программированием для НАСА; и мы сказали, что как минимум каждую строку кода следует тестировать один раз. Модульное тестирование также может помочь.
CodeLurker 05