4 Stimmen

Seltsame SEGFAULTS bei Verwendung von fprintf

Ich habe große Schwierigkeiten beim Debuggen einer mehrfädigen C-Anwendung, die ich ein paar Änderungen vorgenommen habe. Ich konnte GDB nicht verwenden, um das Problem zu identifizieren(siehe untenstehenden Code für weitere Informationen).

Der folgende Code stammt von einer der Aufgaben, die in einem eigenen Thread geöffnet ist. Ich habe den größten Teil des Codes nach dem Problem entfernt.

void tskProcessTenMinuteTables(void *input)
{
    /* Überprüfen der Minute sofort nach dem Start. Wenn wir auf einer Zehnminuten-Grenze gestartet haben, schlafen wir eine Minute.
     */
    time_t now;
    time_t wakeup;
    struct tm *next_tick_ptr;

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

    /* gibt eine mit dem nächsten Zehnminutenzeitstempel gefüllte Zeitstruktur zurück */
    GetNextTenMinBoundary(next_tick_ptr); 
    wakeup = mktime(next_tick_ptr);

    sleep(2); /* Ohne diesen Schlaf war das folgende if() immer wahr. */ 

    if(next_tick_ptr->tm_min % 10 == 0)   
    {
     fprintf(stderr, "Auf Zehnminuten-Grenze bei Initialisierung.. Aufgabe schläft für 60 Sekunden.\n");

        /*  Debug-Meldungen zum Testen der Ursache des Segfaults.  */ 
     fprintf(stderr, "NOM NOM NOM\n"); 
     printf( "Test%d\n", 1);
     fprintf(stderr, "Test%d\n", 2);  /* <~~~ Diese Anweisung ist der Übeltäter */

        sleep(60);
    }

    /*  Hauptschleife. Jede Schleifenrunde außer dem Tick selbst besteht nur aus einem Aufruf von time und einem Vergleich des aktuellen Stempels mit dem Aufwachen.
    *   das sollte ziemlich leicht auf der Verarbeitungsseite sein.
    *
    *   Implementieren Sie dies neu als Schlafen/Aufwachen mit einem Signal in der Zukunft.
    */
    while(1)
    {
        now = time(NULL);

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

            char statement[150];

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

}

Die Ursache liegt im Versuch, fprintf mit variadischen(?)-Argumenten zu verwenden. Wenn man es nur mit dem Muster aufruft, funktioniert es. Printf funktioniert mit oder ohne Argumente.

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

Beim Ausführen in gdb erhalte ich die folgende Ausgabe, bevor gdb nicht mehr reagiert. Ein kill -9 ist erforderlich, um zu beenden.

$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 - Hauptthread) 20-Oct-2010 10:56:19.59 - sl_exit - Thread wird mit Code 0 beendet.
Auf Zehnminuten-Grenze bei Initialisierung.. Aufgabe schläft für 60 Sekunden.
NOM NOM NOM 
Test1

Ich bin noch ziemlich neu in C, daher kann es etwas Offensichtliches sein. Mein erster Gedanke war, dass etwas mit der nicht-gepufferten Ausgabe nicht threadsicher ist, aber fprintf gelingt immer, wenn außer dem Muster nichts übergeben wird. Pthread-Seltsamkeiten sind immer noch mein Hauptverdächtiger. Leider bin ich vorerst auf die Architektur beschränkt.

Vielen Dank im Voraus.

4voto

nategoose Punkte 11636

Schritt eins besteht darin, die Funktion zu versuchen, ohne Threads einzuführen. Schreiben Sie einfach eine .c-Datei, die ein main hat, das das Bare-Minimum tut, um den Thread startbereit zu machen, und anstatt dies zu tun, ruft es einfach die Funktion auf. Es ist viel einfacher zu debuggen, wenn Sie das Problem mit nur einem Thread reproduzieren können.

Zusätzlich sollten Sie, wenn Sie gcc verwenden, mit folgendem kompilieren:

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

zusätzlich zu Ihren normalen Flags (zumindest, bis Sie das Problem finden). Diese helfen beim Debuggen und geben möglicherweise mehr Warnungen zur Kompilierzeit aus. Ich gehe davon aus, dass Sie wissen, wie sich die -O-Flags auf die Debugbarkeit und Funktionalität auswirken können (insbesondere, wenn Sie bereits etwas Falsches oder Undefiniertes im C-Code tun).

Wenn Sie in GDB sind und es so aussieht, als ob sich alles aufgehängt hat oder das Programm lange dauert, um etwas zu tun, können Sie normalerweise STRG Z drücken, um zurück zu (gdb) zu gelangen, ohne das Programm zu beenden. Dies gibt dem Programm das Stoppsignal und ermöglicht es Ihnen, wieder mit GDB zu interagieren, damit Sie herausfinden können, was das Programm tatsächlich macht.

bearbeiten

Anscheinend habe ich das Problem in der Kommentar-Diskussion gelöst, deshalb werde ich hier schreiben, was das Problem war.

Ein schneller Blick auf den Code deutete nicht auf ein Problem hin, das zu einem Segmentation Fault (illegaler Speicherzugriff) führen würde, und Zypsy (der OP) sagte mir, dass die Funktion einwandfrei lief, wenn sie direkt von main aufgerufen wurde, anstatt über einen separaten Thread zu laufen.

Valgrind meldete, dass der Stack des Threads nicht erweitert werden konnte, um eine bestimmte Adresse zu erreichen. In Linux wird der Stack des Hauptthreads in die Anwendung so gemappt, dass er einfach wachsen kann, aber das wird oft nicht gemacht, wenn Speicher für Thread-Stacks allokiert wird.

Ich bat Zypsy (den OP), einige Code einzufügen, der die Adresse von etwas, das bekanntermaßen niedrig im Thread-Stack liegt, ausgeben würde (printf("thread stk = %p\n", &input);), damit dieser Wert mit der in der Fehlermeldung angegebenen Adresse verglichen werden könnte. Daraus konnte ich eine Schätzung für die Stack-Größe erhalten. Dies deutete nicht darauf hin, dass zwischen dem Beginn der Thread-Funktion und ihrem Scheitern sehr viel Stack-Speicher verbraucht wurde, aber der Speicher schien auch nicht zu klein für den Code in der Frage zu sein (es stellte sich allerdings heraus, dass er zu klein war).

Weil die Funktion pthread_create es Ihnen ermöglicht, entweder die Einstellungen für die Attribute eines Threads zu akzeptieren (geben Sie ein NULL ein) oder ein Argument zu übergeben, das verschiedene Einstellungen für den Thread angibt, fragte ich, ob der Code, der pthread_create aufrief, gepostet werden könnte, damit ich sehen könnte, ob es verdächtige Einstellungen gab.

Nachdem ich mir diesen Code angesehen hatte (eine anwendungsspezifische Wrapper-Funktion um verschiedene pthread_-Funktionen), sah ich, dass tatsächlich einige Stack-bezogene Attribute gesetzt wurden. Ich bat den OP, nach verdächtigen Dingen im Zusammenhang mit der Stack-Allokierung zu suchen (stellen Sie sicher, dass der Größenwert und die allokierte Speichergröße tatsächlich übereinstimmten). Es stellte sich heraus, dass der OP dann herausfand, dass der Thread-Stack kleiner als die Stacks anderer Threads allokiert wurde. Der Stack war doch zu klein.

0voto

Yuval Adam Punkte 155168

Normalerweise sind diese Arten von Problemen mit Speicherbeschädigungen verbunden. Symptome wie inkonsistente Segfaults in verschiedenen Zeilen, wann immer Sie den Code geringfügig ändern, sind ein wunderbares Beispiel.

Versuchen Sie, Ihr Programm mit einem Tool wie valgrind auszuführen, dann werden Sie garantiert einige illegale Speicherzugriffe sehen. Beheben Sie diese, und ich vermute, dass die Dinge funktionieren werden.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X