Трудно отследить ошибку сегментации SIGSEGV в большой программе

Прошу прощения за то, что разместил вопрос, который задавали много раз (я только что прочитал их 10 страниц), но я не могу найти решение.

Я работаю над многопоточной графической / звуковой программой, используя OpenGL и Portaudio соответственно. Аудиопоток использует созданную мной библиотеку для объектов обработки звука. SIGSEGV происходит примерно в 20% случаев (гораздо меньше при отладке) и происходит при сбросе множества аудиообъектов с новой информацией о потоке (частота дискретизации, размер вектора и т. Д.). Code :: blocks Debugger заявляет, что сбой возникает из разных мест каждый раз, когда возникает сбой.

Это цикл обработки звука:

while(true){
    stream->tick();
    menuAudio.tick();
    {
        boost::mutex::scoped_lock lock(*mutex);
        if(channel->AuSwitch.resetAudio){
            uStreamInfo newStream(channel->AuSwitch.newSrate, 
                 channel->AuSwitch.newVSize, channel->AuSwitch.newChans);
            menuAudio.resetStream(&newStream);
            (*stream) = newStream;
            menuAudio.resetStream(stream);
            channel->AuSwitch.resetAudio = false;
        }
    }
}

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

void uPatch::resetStream(uStreamInfo* newStream)
{
    for(unsigned i = 0; i < numObjects; ++i){
/*This is where it reports this error: Program received signal SIGSEGV,  
Segmentation fault. Variables: i = 38, numObjects = 43 */
        objects[i]->resetStream(newStream); 
    }
}  

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

Так как объектов так много, я не буду публиковать их код сброса, но в качестве примера:

void uSamplerBuffer::resetStream(uStreamInfo* newStream)
{
    audio.set(newStream, false);
    control.set(newStream, true);
    stream = newStream;
    incr = (double)buffer->sampleRate / (double)stream->sampleRate;
    index = 0;
}

Где код audio.set:

void uVector::set(uStreamInfo* newStream, bool controlVector)
{
    if(vector != NULL){
        for(unsigned i = 0; i < stream->channels; ++i)
            delete[] vector[i];
        delete vector;
    }
    if(controlVector)
        channels = 1;
    else
        channels = newStream->channels;
    vector = new float*[channels];
    for(unsigned i = 0; i < channels; ++i)
        vector[i] = new float[newStream->vectorSize];
    stream = newStream;
    this->flush();
}

Я могу предположить, что это проблема с переполнением стека, поскольку это действительно происходит только с большим количеством объектов, и каждый из них работает нормально индивидуально. Тем не менее, сам аудиопоток работает нормально и работает аналогичным образом. Кроме того, цикл objects[i]->resetStream(newStream); должен выталкивать стек после каждой функции-члена, поэтому я не могу понять, почему это будет SIGSEGV.

Любые наблюдения / рекомендации?

РЕДАКТИРОВАТЬ:

Это была неправильно удаленная проблема с памятью. Application Verifier указывал на ошибку в момент возникновения ошибки, а не на случайные ошибки, которые были определены как происходящие из других мест. Проблема заключалась в функции настройки потока uVector, поскольку цель класса - для аудио векторов, использующих многомерные массивы с использованием stream->channels, с возможностью использования одномерных массивов для управляющих сигналов. При удалении для перераспределения памяти я случайно установил все uVectors независимо от типа для удаления с помощью stream-> channels.

if(vector != NULL){
    for(unsigned i = 0; i < stream->channels; ++i)
        delete[] vector[i];
    delete vector;
}

Где это должно было быть:

if(vector != NULL){
    for(unsigned i = 0; i < this->channels; ++i)
        delete[] vector[i];
    delete vector;
}

Таким образом, он удалял память, к которой не должен иметь доступа, что приводило к повреждению кучи. Я удивлен, что segfault не происходил более регулярно, так как это кажется серьезной проблемой.


person Utopia    schedule 26.04.2012    source источник


Ответы (2)


Если вы сэкономите память, вы можете попробовать такой инструмент, как Electric Fence (или DUMA, его дочерний элемент), чтобы убедиться, что вы выполняете запись за пределами допустимого диапазона. Обычно эти типы ошибок сегментации (непостоянные, только иногда возникающие) являются следствием предыдущего где-то переполнения буфера.

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

Кроме того, попробуйте проверить, какое значение имеет неверный адрес, к которому вы обращаетесь, когда это происходит: выглядит ли он действительным? Иногда значение может быть очень информативным для ошибки, с которой вы сталкиваетесь (обычно: попытка доступа к памяти по адресу 0x12, где 0X12 - счетчик в цикле :)).

Для переполнения стека ... Я бы посоветовал попытаться увеличить размер стека инкриминируемого потока, посмотреть, воспроизводится ли ошибка. Если не после нескольких хороших попыток, вы нашли проблему.

Что касается окон:

person Gui13    schedule 26.04.2012
comment
Очень информативно, спасибо. На данный момент это в первую очередь проект Windows, и я видел, как Valgrind много рекомендовал для решения такого рода проблем. Знаете ли вы о каких-либо свободно доступных альтернативах для Windows? Я попытался увеличить размер стека, чтобы добиться реального эффекта (на воспроизведение ошибки уходит много времени, но обычно это так). - person Utopia; 26.04.2012
comment
Хорошо, так что, скорее всего, ваш указатель либо больше не действителен (удален другим потоком?), Либо вы написали что-то вне пределов заранее, и он отображается только там. Что касается инструментов, у SO есть несколько ответов, которые я перечислю в своем сообщении :) - person Gui13; 26.04.2012
comment
Спасибо за ссылки. Все заработало! Я попробовал средство проверки приложения, и вместо того, чтобы в разное время приводить к ошибке, он каждый раз давал сбой на первой итерации resetStream, поэтому отследить проблему было намного проще. Я обновлю свой пост с проблемой / решением. :) - person Utopia; 27.04.2012

Я думаю, вы только что сделали это проблемой переполнения стека. :)

Если серьезно, то подобные ошибки обычно являются результатом доступа к объектам в тех местах памяти, где они больше не существуют. В вашем первом блоке кода я вижу, что вы создаете newStream в стеке с областью действия, ограниченной оператором if, частью которого он является. Затем вы копируете его в разыменованный указатель (*stream). Определено ли безопасное и правильное назначение для класса uStreamInfo? Если не определено явно, компилятор незаметно предоставит поэлементную копию для назначения объекта, что нормально для простых примитивов, таких как int и double, но не обязательно для динамически выделяемых объектов. *stream может остаться с указателем на память, выделенную newStream, но с тех пор он был освобожден, когда newStream вышел за пределы области видимости. Теперь данные в этой ОЗУ все еще там, и какое-то время будут выглядеть правильно, но, будучи освобожденной памятью, они могут быть повреждены в любой момент, например, непосредственно перед сбоем. :)

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

person Randall Cook    schedule 26.04.2012
comment
Спасибо за ответ. В структуре uStreamInfo нет динамически выделяемой памяти, поэтому я предположил, что поэлементная копия подойдет. По общему признанию, несколько resetStream () с использованием & newStream, а затем обратно в поток - это плохой код, но он все равно должен быть действительным и запускаться. - person Utopia; 26.04.2012