Странные SEGFAULTS с использованием fprintf

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

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

void tskProcessTenMinuteTables(void *input)
{
    /* Check the minute as soon as we start.  If we're started on a ten min
     * boundary, sleep for one minute.
     */
    time_t now;
    time_t wakeup;
    struct tm *next_tick_ptr;

    now = time(NULL);
    next_tick_ptr = localtime(&now);

    /* returns a time struct populated w/ next ten min boundary */
    GetNextTenMinBoundary(next_tick_ptr); 
    wakeup = mktime(next_tick_ptr);

    sleep(2); /* Without this sleep, the following if() was always true. */ 


    if(next_tick_ptr->tm_min % 10 == 0)   
    {
     fprintf(stderr, "On tenmin boundary on initialization.. task sleeping for 60 seconds.\n");

        /*  debug statements to test the cause of segfault.  */ 
     fprintf(stderr, "NOM NOM NOM\n"); 
     printf( "Test%d\n", 1);
     fprintf(stderr, "Test%d\n", 2);  /* <~~~ This statement is the guilty party */

        sleep(60);
    }

    /*  Main loop.  Every loop besides the tick itself will consist only 
    *   of a call to time and a comparison of current stamp with wakeup.
    *   this should be pretty light on the processing side.
    *
    *   Re-implement this as a sleep/awake with a signal in the future.
    */
    while(1)
    {
        now = time(NULL);

        if( now >= wakeup )
        {
            fprintf(stderr, "Triggered 1.\n");
            fprintf(stderr, "Triggered 2.\n");  

            char statement[150];

            fprintf(stderr, "Triggered 3.\n");      
            sprintf(statement, "SELECT ten_min_end(%d::int2)",GetTenMinPeriodNumber());
            fprintf(stderr, "Triggered 4.\n");
            DBCallStoredProcedure(statement);
            fprintf(stderr, "Triggered 5.\n");
    }

}

Причина в попытке использовать fprintf с вариативными (?) аргументами. Вызов его без чего-либо, кроме шаблона, работает. Функции Printf с аргументами или без них.

fprintf(stderr, "Hi #%d.\n", 1); <~~ segfault
fprintf(stderr, "Hi #1.\n"); <~~ works
printf("Hi #%d.\n", 1); <~~ works
printf("Hi #1.\n"); <~~ works

При запуске в gdb я получаю следующий выброс до того, как gdb перестанет отвечать. Для завершения требуется kill -9.

$gdb ir_client
(gdb) r
Starting program: /home/ziop/Experimental_IR_Clients/ir-10-20/IR_Client/obj-linux-x86/ir_client 
[Thread debugging using libthread_db enabled]
[New Thread 0xb7fe5b70 (LWP 32269)]
[New Thread 0xb7fc4b70 (LWP 32270)]
(032266 - -1208067216) 20-Oct-2010 10:56:19.59 - IR_Client_ConnectCmdPort - Socket connected.
[New Thread 0xb7ffdb70 (LWP 32272)]
(032266 - main thread) 20-Oct-2010 10:56:19.59 - sl_exit - Exiting thread with code 0.
On tenmin boundary on initialization.. task sleeping for 60 seconds.
NOM NOM NOM 
Test1

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

Заранее спасибо.


person Zypsy    schedule 20.10.2010    source источник
comment
Введите команду «назад» после Test1 — и sigsegv — напечатает (я предполагаю, что вы получите приглашение, а не зависание). Это даст обратную трассировку, которая должна сказать вам, что не удается   -  person KevinDTimm    schedule 20.10.2010
comment
Скорее всего, это не причина вашей проблемы (остального кода я не видел, но звучит совершенно по-другому), НО -- если вы запускаете потоки для запуска этой функции, я думаю, у вас возникнет проблема с localtime . Он использует одну и ту же память для структуры tm для каждого потока. Вы должны защитить его с взаимным исключением, скопировать его или использовать, а также выпустить, если хотите избежать гонок.   -  person slezica    schedule 20.10.2010
comment
@ KevinDTimm - Нет, подсказки нет. GDB не отвечает ни на какие команды. @Сантьяго - Полезно знать! В этом случае нет двух потоков, выполняющих один и тот же цикл/код, и беглый взгляд не показывает повторяющихся имен переменных в других циклах потоков.   -  person Zypsy    schedule 20.10.2010
comment
После просмотра связанных вопросов и услышав упоминание Валгринда, я попробовал. Он сообщает, что не удается расширить стек до 0x402a420 во время доставки сигнала для потока 4. Доступ за пределами сопоставленной области в буфере_vfprintf (vfprintf.c:2221). Увеличение размера основного стека не дало никакого эффекта.   -  person Zypsy    schedule 20.10.2010
comment
Попробуйте добавить printf("thread stk = %p\n", &input); и flush(stdout); в начало функции потока и зафиксируйте вывод (надеюсь, это ничего не сломает). Затем снова запустите Valgrind и посмотрите, до чего вы пытаетесь расширить стек, и как это соотносится со значением в моем операторе печати. Материал cat /proc есть на случай, если вам понадобится дальнейшее расследование.   -  person nategoose    schedule 21.10.2010
comment
@nategoose - Интересно. Исходный стек = 402d320; Расширить до = 402a4d0. Доступ к buffered_vfprintf(vfprintf.c) = 40c2e4e. Ошибка платформы?   -  person Zypsy    schedule 21.10.2010
comment
@ Сантьяго: есть еще localtime_r.   -  person ninjalj    schedule 21.10.2010
comment
@ user482025: потоки имеют ограниченный размер стека, установленный pthread_attr_setstacksize() или соответствующим ограничением ресурсов. Может быть, это слишком низко?   -  person ninjalj    schedule 21.10.2010
comment
@ user482025: Извините за последнее предложение в моем предыдущем комментарии. Изначально у меня была еще одна строка кода, которую вы могли добавить, но я удалил ее, потому что понял, что в ней есть ошибка, и чтобы сделать ее правильно, потребовалось бы много дополнительного кода. Этот код был предназначен для cat /proc/self/maps в середине вашего потока, чтобы увидеть, что, если адреса в данных об ошибках вообще имеют смысл.   -  person nategoose    schedule 21.10.2010
comment
@ user482025: Не могли бы вы опубликовать код pthread_create? Вы создали его с какими-либо атрибутами (второй аргумент)?   -  person nategoose    schedule 21.10.2010
comment
@nategoose: я закину это на pastebin. Имейте в виду, что этот код не был затронут мной. Он был предоставлен с библиотеками.   -  person Zypsy    schedule 21.10.2010
comment
Код на pastebin устанавливает несколько атрибутов для потока. Среди них атрибуты стека (размер и расположение) на основе аргументов функции job_create. Если стек предварительно выделен вызывающей стороной job_create, убедитесь, что он выделен правильно и соответствует размеру. Я думаю, что должно быть много стека потоков, если в системе постоянно не хватает памяти.   -  person nategoose    schedule 21.10.2010
comment
@nategoose: Гах, ты абсолютно прав. В job_create было определено 8192 как STACKSIZE_MIN, если не указано иное. Вызов в другом месте, который работал, имел размер 32768. Чтение этой функции было очень информативным. Я не знал и не рассматривал каждый поток, имеющий свою собственную конфигурацию размера. Пришло время прочитать больше документов на pthread. Спасибо за всю вашу помощь в этом. Решено.   -  person Zypsy    schedule 21.10.2010


Ответы (2)


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

Кроме того, если вы используете gcc, вы должны скомпилировать с помощью:

-fstack-protector-all -Wstack-protector -fno-omit-frame-pointer

в дополнение к вашим обычным флагам (по крайней мере, пока вы не найдете проблему). Это поможет при отладке и, возможно, выдаст больше предупреждений во время компиляции. Я предполагаю, что вы знаете, как флаги -O могут повлиять на возможность отладки и функциональность (особенно если вы уже делаете что-то неправильное или не определенное в коде C).

Когда вы находитесь в GDB и все выглядит так, как будто они заблокированы или программе требуется много времени, чтобы что-то сделать, вы обычно можете нажать CTRL Z, чтобы вернуться к (gdb), не убивая программу. Это выдает сигнал остановки программе и позволяет вам снова взаимодействовать с GDB, чтобы вы могли узнать, что на самом деле делает программа.

редактировать

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

Беглый взгляд на код не выявил проблемы, которая привела бы к ошибке сегментации (незаконному доступу к памяти), и Zypsy (ОП) сказал мне, что функция работает нормально при вызове непосредственно из основного, а не через отдельный нить.

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

Я попросил Zypsy (ОП) вставить некоторый код, который распечатывал бы адрес чего-то, что известно как низкое в стеке потоков (printf("thread stk = %p\n", &input);), чтобы это значение можно было сравнить с адресом, указанным в сообщении об ошибке. Из этого я мог получить предположение о размере стека. Это не говорит о том, что между началом функции потока и ее сбоем было израсходовано очень много места в стеке, но пространство также не казалось слишком маленьким для кода в вопросе (хотя, видимо, оно оказалось слишком маленьким).

Поскольку функция pthread_create позволяет вам либо принять настройки для атрибутов потока (передать NULL), либо передать аргумент, определяющий различные настройки для потока, я спросил, можно ли опубликовать код, вызывающий pthread_create, чтобы я мог видеть, есть ли были какие-либо подозрительные настройки.

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

person nategoose    schedule 20.10.2010
comment
Никаких проблем при запуске функции в основном потоке. Решив, что у меня возникла проблема с потоком, я очистил функцию. Это снег. - person Zypsy; 21.10.2010
comment
Гах... Никаких проблем при запуске функции в основном потоке. Решив, что у меня проблема с потоком, я очистил функцию, чтобы она содержала только fprintf(stderr, Test%d\n, 1); и возвращение. Теперь GDB обрабатывает эту функцию. При запуске как отдельный поток выдает загадочный 0x001b4e4e в buffered_vfprintf (s=0x2c9580, format=0x8058ef1 Test\d.\n, args=0xb7ffd308 \004) по адресу vfprintf.c:2221. Я бы предположил, что stderr небезопасен для использования в потоках, но существующая функция ведения журнала, использующая мьютекс, демонстрирует такое же поведение. Valgrind выдает такое же сообщение «Невозможно расширить стек». - person Zypsy; 21.10.2010

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

Попробуйте запустить свою программу с помощью такого инструмента, как valgrind, вы гарантированно увидите некоторые незаконные обращения к памяти. Исправьте их, и я подозреваю, что все будет работать.

person Yuval Adam    schedule 20.10.2010