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

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

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

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

[email protected]()  + 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 в начале и конце записи:: addField показывает кучу OK вплоть до сбоя.

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

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

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

Ответ 1

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

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

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

Ответ 2

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

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

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

Ответ 3

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

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

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

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

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