Segmentation fault bei fopen

Lemiras

Mitglied
Hallo,
ich habe einen Server geschrieben der selber eine Datei zum loggen öffnet und im moment nach jedem logeintrag wieder schließt. (Ohne das schließen passiert das selbe) Wenn ein Client sich mit dem Server verbindet wird ein Kindprozess erstellt.

Dieser soll nun ebenfalls diese Datei öffnen und sie schließen. Jedoch lößt das fopen des Kindprozesses ein Segmentation fault aus. Ich bin im Moment ratlos warum es im Elternprozess funktioniert und beim Kind nicht.

Hier der Code: writelog() schreibt den übergebenen String in die einzelnen Logfiles. initlog() öffnet die Datei, und lößt im Kindprozess das Signal aus. Die FILE Strukte sind als globale Variablen definiert.

Gruß
Lemiras

init_logging()
Code:
static void init_logging(bool first_use){
    
    static char *tmp_debugfile;
    static char *tmp_errfile;
    static char *tmp_msgfile;
    

    
    if(seconf->debug ){
        if(first_use){    
            if((tmp_debugfile = malloc(strlen(LOG_PATH)+strlen("_debug")+strlen(seconf->logfile)+1)) == NULL){
                fprintf(stderr,MSG_ALLOC_FAIL);
                exit(EXIT_FAILURE);
            }
            sprintf(tmp_debugfile,"%s%s%s",LOG_PATH,seconf->logfile,"_debug");
        }

/*Hier tritt der Fehler auf*/
        if((debugfile = fopen(tmp_debugfile,"a")) == NULL){
            fprintf(stderr, MSG_CANT_OPEN, tmp_debugfile);
            free(tmp_debugfile);
            exit(EXIT_FAILURE);
        }
//        free(tmp_debugfile);
    }
    
    if(first_use){
        if((tmp_errfile = malloc(strlen(LOG_PATH)+strlen("_err")+strlen(seconf->logfile)+1)) == NULL){
            fprintf(stderr,MSG_ALLOC_FAIL);
            exit(EXIT_FAILURE);
        }
        sprintf(tmp_errfile,"%s%s%s",LOG_PATH,seconf->logfile,"_err");
    }
    if((errfile = fopen(tmp_errfile,"a")) == NULL){
        fprintf(stderr, MSG_CANT_OPEN, tmp_errfile);
        free(tmp_errfile);
        exit(EXIT_FAILURE);
    }
//    free(tmp_errfile);
    
    if(first_use){
        if((tmp_msgfile = malloc(strlen(LOG_PATH)+strlen("_msg")+strlen(seconf->logfile)+1)) == NULL){
            fprintf(stderr,MSG_ALLOC_FAIL);
            exit(EXIT_FAILURE);
        }
        sprintf(tmp_msgfile,"%s%s%s",LOG_PATH,seconf->logfile,"_msg");
    }
    if((msgfile = fopen(tmp_msgfile,"a")) == NULL){
        fprintf(stderr, MSG_CANT_OPEN, tmp_msgfile);
        free(tmp_msgfile);
        exit(EXIT_FAILURE);
    }
    free(tmp_msgfile);
}

Funktion writelog()
Code:
void writelog(short prio, char *name, char *format, ...){
    static bool first_start = false;
    va_list arglist;
    char *formatstr;
    char *tmptime;
    time_t ltime;
    char *X;
    
    if(prio == LOGDEBUG && seconf->debug == false){
        return;
    }
       
    if(!first_start){
        first_start = true;
//        init_logging();
    }
    debugfile = NULL;
    msgfile = NULL;
    errfile = NULL;
    init_logging(first_start);
           
    if((formatstr = malloc(sizeof(char) * (strlen(name)+ 100 + strlen(format)))) == NULL){
        exit(EXIT_FAILURE);
    }
    //Datum des Logeintrags
    ltime = time(NULL);
    tmptime =ctime(&ltime);
    tmptime[24] = '\0';
     
    sprintf(formatstr, "%s; %d; %s:\t %s",tmptime,getpid(),name,format);  
    va_start(arglist, format);
    semOp(SEM_LOCK,SEM_LOG);
    if((prio <= LOGDEBUG) && seconf->debug){
        if(vfprintf(debugfile, formatstr, arglist) < 0){
            X = strerror(errno);
        }        
    }
    if((prio <= LOGMSG)){
        if(vfprintf(msgfile, formatstr, arglist) < 0){
            X = strerror(errno);
        }
    }
    if((prio <= LOGERR)){
        if(vfprintf(errfile, formatstr, arglist) < 0){
            X = strerror(errno);
        }
    }
    if(debugfile != NULL){
        fclose(debugfile);
    }
    if(errfile){
        fclose(errfile);
    }
    if(msgfile != NULL){
        fclose(msgfile);
    }
//    fflush(NULL);
    semOp(SEM_UNLOCK,SEM_LOG);
    va_end(arglist);
    free(formatstr);
}
 
Zuletzt bearbeitet:
1. Es ist IMMER hilfreich, das BETRIEBSSYSTEM zu nennen unter dem man programmiert.

2. Sowas:
Code:
    tmptime =ctime(&ltime);
    tmptime[24] = '\0';

ist ein absolutes no-go. Das mag zwar unter bestimmten Umständen zufällig das gewünschte Ergebnis liefern, aber es ist lediglich ein bug der darauf warted füher oder später zuzuschlagen. Ausschnitt aus der ctime-doku:
---
The converted character string is also adjusted according to the local time zone settings. See the time, _ftime, and localtime functions for information on configuring the local time and the _tzset function for details about defining the time zone environment and global variables.
---

Du brauchst also nur einen Rechenr zu haben auf dem die settings für Zeitdarstellung anders sind, schon knallt das...

3. Aua

Code:
static void init_logging(bool first_use){

Du setzt vorraus das beim Aufruf der Funktion jemand von aussen eine bool-variable richtig benutzt um davon abhängig die internen Buffer richtig zu setzen? Einmal falsch bedient oder vergessen beim ersten Aufruf true zu setzen, und schon knallts.

Wenn Du schon mit statics arbeitest, warum nciht INNERHALB von logging eine static bool die nach dem setzen der buffer auf false gesetzt wird? dann bist Du auf der absolut sicheren Seite...

4. Zum Fehler.

Du redest von Prozessen, nciht von Threads. Das deutet an das Du unter einem Unix-derivat arbeitest. Prozesse haben dabei komplett eigene Heaps/Stacks:

---
Process memory is generally organized into code, data, heap, and stack segments, as shown in Figure 2–12 (a). The code or text segment includes instructions and read-only data. It can be marked read only so that modifying memory in the code section results in faults.4 The data segment contains initialized data, uninitialized data, static variables, and global variables. The heap
---

Ich bin da zwar jetzt nciht sicher, aber ich tippe drauf das statics nur inenrhalb eines Processes gültig sind, nicht processübergreifend (wie das z.B. bei threads der Fall wäre),

Stellt sich nebenbei die Frage, warum Du überhaupt processe verwendest.

5. Und die letzte Frage, waru schaust Du dir das nciht einfach im debugger an? wenn ich mit der Vermutung richtig liege müßte es leicht zu erkennen sein das die statics nicht initialisiert sind.
 
Hallo und danke für die Antwort,
also:

1. Es ist IMMER hilfreich, das BETRIEBSSYSTEM zu nennen unter dem man programmiert.
das Ziehlsystem ist Unix/Linux. Sorry hatte ich vergessen.

2. Sowas:
Code:
    tmptime =ctime(&ltime);
    tmptime[24] = '\0';
ist ein absolutes no-go. Das mag zwar unter bestimmten Umständen zufällig das gewünschte Ergebnis liefern, aber es ist lediglich ein bug der darauf warted füher oder später zuzuschlagen. Ausschnitt aus der ctime-doku:
[...]
Laut meiner Doku von ctime (C In a Nutshell): "Der String ist immer exakt 26Byte lang, inklusive des abschließenden Null- Zeichens." Da ich das '\n' vor dem '\0' löschen wollte dachte ich ich mache es mir einfach.... also '\0'==str[25] '\n' == str[24]. Und da ctime POSIX Standart ist sollte es allgemein Funktionieren. Model ich aber dann doch noch um wenn es da unstimmigkeiten gibt. Unschön ist die Lösung allemal...

3. Aua

Code:
static void init_logging(bool first_use){
Du setzt vorraus das beim Aufruf der Funktion jemand von aussen eine bool-variable richtig benutzt um davon abhängig die internen Buffer richtig zu setzen? Einmal falsch bedient oder vergessen beim ersten Aufruf true zu setzen, und schon knallts.

Wenn Du schon mit statics arbeitest, warum nciht INNERHALB von logging eine static bool die nach dem setzen der buffer auf false gesetzt wird? dann bist Du auf der absolut sicheren Seite...
Wird im Prinzip so gemacht die bool wird innerhalb von writelog als static gesetzt und beim ersten benutzten false.

In der ursprünglichen Fassung war init_logging eine void Funktion und wurde durch writelog nur beim ersten durchlauf benutzt.
Code:
//Writelog
if(!first_start){
    first_start = true;
    init_logging();
}

Durch den Fehler habe ich sie umgestellt. Ich wollte testen ob der Fehler dadurch zu stande kam das der Elternprozess die Datei noch geöffnet hatte und der Kindprozess sie ein zweites mal öffnet.
(quick and dirty testlösung).

4. Zum Fehler.

Du redest von Prozessen, nciht von Threads. Das deutet an das Du unter einem Unix-derivat arbeitest. Prozesse haben dabei komplett eigene Heaps/Stacks:
[...]
Ich bin da zwar jetzt nciht sicher, aber ich tippe drauf das statics nur inenrhalb eines Processes gültig sind, nicht processübergreifend (wie das z.B. bei threads der Fall wäre),
Jup das stimmt daher wird init_logging() auch beim Kind aufgerufen. Sonst würde es mit der first_start Variablen nicht funktionieren.

Stellt sich nebenbei die Frage, warum Du überhaupt processe verwendest.
Da somit jeder eingelogte Client seinen eigenen Prozess bekommt. Ähnlich zu der alten Version des Apache etc. Threats kann ich nicht verwenden da der GPIB Treiber den ich benutze nicht Threatsicher ist. Das ganze gibt dann eine Steuerung für 4 Spannungsquellen über das Lokale Netzwerk. Also der Server ist im Prinzip ein Ethernet auf GPIB Wandler grob gesagt. Bei der von mehreren Rechnern aus die Spannungsquelle n gesteuert werden können. Vorteil: jeder Client bekommt seinen eigenen Prozess die sich nicht untereinander behaken können. Viele operrationen können 'parralel' abgearbeitet werden. (Wenn ein Kind grade auf etwas wartet und im blocked Zustand ist). Der Elternprozess nimmt somit nur die TCP Verbindung vom Client entgegen und erstellt den Prozess. Threats wähen vieleicht schöner aber dafür brauche ich einen anderen GPIB Treiber.

5. Und die letzte Frage, waru schaust Du dir das nciht einfach im debugger an? wenn ich mit der Vermutung richtig liege müßte es leicht zu erkennen sein das die statics nicht initialisiert sind.
Habe ich.... allerdings ohne einen erkenntlichen Fehler (Daher weiß ich das es genau diese fopen() Zeile ist in der das Signal ausgelöst wird). fopen() bekommt den richtigen Pfad übermittelt (Beim Elternprozess funktioniert es ja mit dem gleichen Pfad). die FILE* Variable existiert auch und ist zu dem Zeitpunkt NULL. Der Zeiger wird ja von fopen als return übermittelt.

Andere Möglichkeiten sehe ich nicht die schief gehen könnten. Da, wie du schon sagtest, der Kindprozess seinen eigenen Speicher verwendet. Ich habe gestern noch getestet den file decriptor vom Eltern Prozess an den Kindprozess zu übergeben und dann mit fdopen zu öffnen dort funktioniert es. Allerdings wüsste ich schon gerne warum das fopen im Kindprozess nicht funktioiniert? Oder mag es am Debugger liegen das es nicht funktioniert?

Gruß
Lemiras
 
Zuletzt bearbeitet:
Hallo,

ich kann den Fehler aus deinem Code zwar auch nicht ersehen, mir stellt sich aber
die Frage warum du die Datei zweimal öffnen musst?
Das Speicherbild des Kindprozesses ist ja identisch mit dem des Elternprozesses nach einem fork(), sprich er besitzt auch die selben Dateideskriptoren, sprich du bräuchtest die Datei ja nur einmal im Elternprozess zu öffnen:

C:
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>

int main()
{
    FILE* fd = fopen("dat.log", "a");
    int pid = 0;
    if ((pid = fork()) > 0) {
        printf("Parent process has file descriptor: %p\n", fd);
        waitpid(pid, NULL, 0);
        fclose(fd);
    } else
        printf("Child process has file descriptor: %p\n", fd);
    return 0;
}

Gruß,
RedWing
 
Hallo,

ich kann den Fehler aus deinem Code zwar auch nicht ersehen, mir stellt sich aber
die Frage warum du die Datei zweimal öffnen musst?
Das Speicherbild des Kindprozesses ist ja identisch mit dem des Elternprozesses nach einem fork(), sprich er besitzt auch die selben Dateideskriptoren, sprich du bräuchtest die Datei ja nur einmal im Elternprozess zu öffnen:

Gruß,
RedWing

Jup das Stimmt, allerdings habe ich den Prozess mittels einer exec Funktion durch das eigentliche child Programm ersetzt.
Ich fand es eleganter, da das Kind einen Großteil des vom Elternprozess benutzten Speichers nicht braucht und somit seinen eigenen Speicher aufbauen kann ohne Altlasten.

Eine weitere Merkwürdigkeit habe ich bei der Verwendung von asprintf festgestellt. Hier wird ebenfalls ein Sekmentation fault ausgelös, wenn ich die Funktion in einem Seperaten Testprogramm laufen lasse.

Bei diesem Proramm:
Code:
#define _GNU_SOURCE
#include <stdio.h>

int main(void){
  char **tmp;
  int d;

  d =asprintf(tmp,"%d",1);
  printf("%s\n",tmp[0]);
  return 0;
}
wird kein Segmentation fault ausgelöst. Bei dem folgenden jedoch schon.

Code:
#define _GNU_SOURCE
#include <stdio.h>

int main(void){
  char **tmp;

  asprintf(tmp,"%d",1);
  printf("%s\n",tmp[0]);
  return 0;
}
Soweit ich weiß sollte sich doch nichts ändern? Ob nun der Rückgabewert einer Variablen zugewiesen wird oder nicht Implementationsfehler?

Gruß
Lemiras - verwirt
 
Zuletzt bearbeitet:
Hallo,
Eine weitere Merkwürdigkeit habe ich bei der Verwendung von asprintf festgestellt. Hier wird ebenfalls ein Sekmentation fault ausgelös, wenn ich die Funktion in einem Seperaten Testprogramm laufen lasse.

Bei diesem Proramm:
Code:
#define _GNU_SOURCE
#include <stdio.h>

int main(void){
  char **tmp;
  int d;

  d =asprintf(tmp,"%d",1);
  printf("%s\n",tmp[0]);
  return 0;
}
wird kein Segmentation fault ausgelöst. Bei dem folgenden jedoch schon.

Code:
#define _GNU_SOURCE
#include <stdio.h>

int main(void){
  char **tmp;

  asprintf(tmp,"%d",1);
  printf("%s\n",tmp[0]);
  return 0;
}
Soweit ich weiß sollte sich doch nichts ändern? Ob nun der Rückgabewert einer Variablen zugewiesen wird oder nicht Implementationsfehler?

Gruß
Lemiras - verwirt

eigtl. sollten bei allen beiden Programmen es zu einem Speicherzugriffsfehler kommen. Der Punkt ist das du asprintf nicht richtig anwendest...

Ein Beispiel sollte das verdeutlichen:

C:
int foo(char** test) 
{
    *test = (char*) malloc(3);
    strcpy(*test, "re");
    return 0;
}

Nun gibt es zwei Varianten die Funktion aufzurufen:

einmal die richtige variante:

char* word;
foo(&word);

und einmal in der falschen Variante:

char** word;
foo(word);

Der Unterschied ist der das einmal der Inhalt von &word auf dem Stack liegt. Sprich
*test in foo ist ein gültiger Speicher für ein char Zeiger. Und beim zweiten mal ist *test ungültig da hierfür kein Platz reseirviert wurde...

Gruß,
RedWing
 
Hallo,
[...]
Der Unterschied ist der das einmal der Inhalt von &word auf dem Stack liegt. Sprich
*test in foo ist ein gültiger Speicher für ein char Zeiger. Und beim zweiten mal ist *test ungültig da hierfür kein Platz reseirviert wurde...

Gruß,
RedWing
Danke,
dann verstehe ich auch den Aufruf von Zeiger auf Zeiger. Hatte es mir nur in der man page angeschaut und dort ist dann natürlich als **str definiert. Programmiere leider nicht so heufig aber es wird :-).

Gruß
Lemiras
 
Zurück