Сборки / MT и / MD завершаются сбоем, но только когда отладчик не подключен: как отлаживать?

У меня есть небольшое однопоточное приложение C ++, скомпилированное и связанное с использованием Visual Studio 2005, которое использует ускорение (crc, program_options и tokenizer), немного STL и различные другие системные заголовки.

(Его основная цель - прочитать файл .csv и создать собственный двоичный файл .dat и парный .h, объявляющий структуры, которые «объясняют» формат .dat.)

Инструмент дает сбой (нарушение прав доступа на NULL) при запуске вне отладчика, только в выпуске. Например. нажатие F5 не приводит к сбою инструмента, в отличие от Ctrl-F5. Когда я снова подключаю отладчик, я получаю такой стек:

ntdll.dll!_RtlAllocateHeap@12()  + 0x26916 bytes    
csv2bin.exe!malloc(unsigned int size=0x00000014)  Line 163 + 0x63 bytes C
csv2bin.exe!operator new(unsigned int size=0x00000014)  Line 59 + 0x8 bytes C++
>csv2bin.exe!Record::addField(const char * string=0x0034aac8)  Line 62 + 0x7 bytes  C++
csv2bin.exe!main(int argc=0x00000007, char * * argv=0x00343998)  Line 253   C++
csv2bin.exe!__tmainCRTStartup()  Line 327 + 0x12 bytes  C

Строка, в которой происходит сбой, выглядит довольно безобидно:

pField = new NumberField(this, static_cast<NumberFieldInfo*>(pFieldInfo));

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

Проблема исчезает при компиляции с / MTd или / MDd (время выполнения отладки) и возвращается при использовании / MT или / MD.

NULL загружается из стека, и я могу видеть его в памяти. _RtlAllocateHeap @ 12 + 0x26916 байт кажется огромным смещением, как будто был сделан неправильный переход.

Я пробовал _HAS_ITERATOR_DEBUGGING в отладочной сборке, и это не вызвало ничего подозрительного.

Удаление HeapValidate в начале и в конце Record :: addField показывает «кучу ОК» вплоть до момента ее сбоя.

Раньше это работало - я не совсем уверен, что изменилось с этого момента до последней компиляции инструмента (возможно, много лет назад, возможно, в более старой версии VS). Мы пробовали более старую версию boost (1.36 против 1.38).

Прежде чем вернуться к исследованию кода вручную или передать его в PC-Lint и просмотреть его вывод, есть ли предложения о том, как это эффективно отлаживать?

[Я буду рад дополнить вопрос дополнительной информацией, если вы запросите информацию в комментариях.]


person leander    schedule 01.05.2009    source источник
comment
Однажды я имел удовольствие полу-реверс-инжиниринг материала RtlHeap для подобной ошибки. Пусть вас не смущают огромные смещения - это нормально. В отладочных символах (которые вы, кажется, используете), по-видимому, отсутствуют некоторые частные функции (вероятно, объявленные статическими в исходном файле), а получение огромных смещений для RtlAllocateHeap или RtlAllocateHeapSlowly просто означает, что это был ближайший найденный символ.   -  person arke    schedule 14.05.2010
comment
@arke: да, я понял это вскоре после того, как разместил вопрос. (Надо было вернуться, чтобы отредактировать его.) Задолго до этого я написал инструмент поиска, который анализировал файлы xMAP, сгенерированные Codewarrior на работе, и время от времени встречал то же самое - просто не приходило мне в голову, что я бы не стал ' По какой-то причине здесь обязательно присутствуют все символы.   -  person leander    schedule 14.05.2010


Ответы (3)


Одно небольшое различие между запуском с подключенным отладчиком или без него - это куча отладки ОС (см. Также Почему мой код работает медленно, когда ко мне подключен отладчик?). Вы можете отключить отладочную кучу с помощью переменной среды _NO_DEBUG_HEAP. Вы можете указать это либо в свойствах вашего компьютера, либо в настройках проекта в Visual Studio.

Как только вы отключите отладочную кучу, вы должны увидеть такой же сбой даже с подключенным отладчиком.

Тем не менее, имейте в виду, что повреждение памяти может быть трудно отладить, так как часто реальная причина повреждения (например, переполнение буфера) может быть очень далеко от того места, где вы видите симптомы (сбой).

person Suma    schedule 01.05.2009
comment
+1: Спасибо, не знал о _NO_DEBUG_HEAP. Попробую сейчас. (У меня был забавный опыт отслеживания повреждений памяти, которые происходили только на розничном встроенном оборудовании без подключенного отладчика, поэтому я слышал, что вы, возможно, очень далеки от симптомов.) - person leander; 01.05.2009
comment
Ага, вот и вышло - вылетел отладчик. =) Пожелайте мне удачи ... - person leander; 01.05.2009
comment
Хм - куча отладки скрывает коррупцию? Вот это невезение ... - person Michael Burr; 01.05.2009
comment
@Michael: да, это было односимвольное переполнение буфера. Я предполагаю, что куча отладки не показывала его из-за другого заполнения ... - person leander; 01.05.2009

Сбой внутри new или malloc обычно является намеком на то, что (внутренняя) структура реализации malloc была повреждена. В большинстве случаев это делается путем записи после предыдущего выделения (переполнение буфера). Затем при следующем вызове new или malloc приложение вылетает, поскольку внутренняя структура теперь содержит недопустимые данные.

Проверьте, можете ли вы перезаписать ранее выделенное пространство.

Если ваше приложение переносимо, вы можете попробовать собрать его в Linux и запустить под Valgrind.

person lothar    schedule 01.05.2009
comment
Да, это тоже мое предположение. Пора раскопать электрическую заграждение или dmalloc, особенно теперь, когда _NO_DEBUG_HEAP позволяет мне вылетать внутри отладчика. - person leander; 01.05.2009
comment
Да, я раньше думал о портировании на linux только для valgrind! =) Модуль memcheck отличный, раньше я даже использовал его для отладки серверов MMORPG. Application Verifier, похоже, охватывает многие из тех же основ в Windows, к счастью, рад, что обнаружил это. - person leander; 02.05.2009

Application Verifier был превосходным -полезно для решения этой проблемы, когда у меня был _NO_DEBUG_HEAP = 1 в среде, см. принятый ответ здесь: Ищете, где последний раз была освобождена память?

Вероятно, также стоит упомянуть pageheap, которую я обнаружил, просматривая Application Verifier. Похоже, это касается аналогичных вопросов.

(К вашему сведению, это было переполнение односимвольного буфера:

m_pEnumName = (char*)malloc(strlen(data) /* missing +1 here */);
strcpy(m_pEnumName, data);

... еще один смехотворно хороший аргумент не использовать strcpy напрямую.)

person leander    schedule 01.05.2009