Re: Suche Debughilfe: child never returns from fork?

From: Peter Much <pmc(at)citylink.dinoex.sub.org>
Date: Tue, 8 Apr 2008 19:47:38 GMT

<ticso(at)cicely.de> aka Bernd Walter schrieb
mit Datum Tue, 8 Apr 2008 00:32:24 +0200 in m2n.de.fbsd.questions:

|On Mon, Apr 07, 2008 at 08:03:24PM +0000, Peter Much wrote:
|>
|> Moin Leute,
|>
|> ich hab hier ein ziemlich widerliches Problem.
|>
|> Der Code sieht so aus:
|>
|> > switch (bpipe->worker_pid = fork()) {
|> > case -1: /* error */
|> ... etc. etc. ...
|> > case 0: /* child */
|> > close(readp[0]); /* Close unused child fds */
|> > dup2(readp[1], 1); /* dup our read to his stdout */
|> > dup2(readp[1], 2); /* and his stderr */
|> ... etc. etc. und irgendwann dann execvp()
|> > default: /* parent */
|> > break;
|> > }
|> ... etc. etc. ...
|>
|>
|> Passieren tut dabei folgendes:
|> - der fork() wird ausgeführt, ich hab also zweimal dasselbe Programm
|> mit unterschiedlicher PID in der Prozessliste.
|> - der Parent läuft normal weiter und macht seine Arbeit wie
|> vorgesehen.
|> - das Child steht im Status "Running" und braucht 100% system-cpu,
|> und ist dementsprechend nur mit -9 killbar.
|> - wenn ich mit lsof die Filehandles anschaue, dann sehe ich dass
|> der close() noch nicht ausgeführt wurde.
|
|D.h. es ist noch das originale Programm und nicht das neue per execvp?

Laut sichtbarem argv ist es dasselbe Programm, aber eine neue
PID - so wie fork() das machen sollte.

|Wenn der tatsächlich beim close hängt, dann stellt sich mir zwangsläufig
|auch die Frage was das für Filehandles sind.
|Das Problem mag ja durchaus am Medium der Files liegen.

Gute Frage. Die wurden unmittelbar vorher mit pipe() beschafft.

Und, ich hab mal das close() durch ein open() ersetzt - das taucht
auch nicht auf - also entweder bleibt er auch im Eingang von dem
open() hängen, oder er kommt doch schon aus dem fork() nicht raus.

|> Jetzt kommt die Widerlichkeit: das Zeug läuft 500 mal korrekt ab, und
|> dann bleibt es -unvorhersagbar- irgendwann einmal hängen. Aber das auch
|> immer wieder mal - d.h. alle paar Tage. Und auf einer anderen,
|> installationsgleichen aber deutlich schnelleren (und weniger
|> vollgepackten) Maschine ist es nur einmal passiert.
|
|Na gut - solche Programmfehler wären nichts neues.

Aye. Ich such auch nicht nach nem Schuldigen. Nur nach nem
akzeptablen Fix, und vor allem nach Verstehen.

|> Der Fehler ist plötzlich aufgetaucht nachdem die Maschinen auf
|> RELEASE 6.3 upgraded wurden (zuvor: RELEASE 5.5, mit identischen
|> Ports).
|
|Auch das wäre nichts neues.

Hm. Leider. :-/
Muss ich sagen, dass ich von 6.3 nicht übermäßig begeistert bin?
(Ich bau seit vier Monaten Bugs raus. Aus *allem* - Ports, User,
Kernel, Scripts, aus meinem Kopf;), ...)
Das fällt aber unter P.P.; braucht nicht als Kritik verstanden zu
werden. Ich weiss ehrlich gesagt nicht worans liegt - vielleicht werd
ich einfach langsam alt...

|> Ich kann natürlich nicht sagen, ob der Prozess aus dem fork() nicht
|> raus- oder in den close() nicht reinkommt. Mir ist auch klar, dass
|> der fork() derweil milliardenmal richtig ausgeführt wird...
|
|Du hast beim case -1 auch sauber abgefangen, dass der nicht in den
|case 0 durchfällt?

Das sieht so aus, ja.

|Und so Klassiker, wie sigchld hast du auch sauber behandelt?

Ich nix wissen. Ich habs nicht geschrieben. Es heißt Bacula
(in diesem Fall der Client), und ist konzeptionell die beste
Backup-Strategie die mir in Freeware bislang untergekommen ist
 - in der Ausgestaltung muss halt noch ein Stück Arbeit reingesteckt
werden.
Aber das find ich besser als andersrum... hat so die Qualität
eines sendmail: man kann beinahe alles biegen, aber oft nicht
ganz so wie man sichs vorstellt...

Allerdings passieren mit dieser Software noch andere komische
Sachen, die ich auch nicht so ganz verstehe...

Z.B. wenn ich den Server (korrekt) mit SigTERM beenden will,
dann geht er geht in eine Endlosschleife und beendet sich erst
mit einem weiteren SigTERM. Ich hab in der Userbase gefragt wem
es noch so geht, aber keiner von den (weitestgehend Pinguin-)Usern
konnte es reproduzieren.

Dann hab ich im Code gesucht und finde ein Konstrukt dieser Art:
        while(c = next(c)) free(c);
Das ist ja kein Problem. Blöd ist dabei nur, wenn
1. die Zeiger selber im freizugebenden Speicherbereich rumzuliegen
   scheinen (das ist zur Hälfte irgendwelches objektorientiertes C,
   und mithin nicht so genau auszumachen), und
2. man dabei das free() umdefiniert hat und ein eigenes free verwendet
   das den Space vorsichtshalber erst noch überschreibt -
   (intelligenterweise dann gleich mit 0xaaaaaaaa, sodass man
   den Müll wenigstens sieht wenn man die Adressen ausgeben läßt).

Interessant sind allerdings zwei Fragen:
1. wieso macht sowas den Pinguinen nichts aus, und
2. wieso geht im FreeBSD dabei der Prozess in eine Endlosschleife?

Nächste interessante Frage: zur Fehlerbehandlung bei Null-Pointer-
Referenzen wird folgender Code verwendet:

    if (type == M_ABORT) {
       char *p = 0;
       p[0] = 0; /* generate segmentation violation */
    }

Auch hier geht das Programm unter FreeBSD in eine Endlosschleife.
Und das scheint nun wiederum mit genau 37 verbogenen Signalhandlern
zu tun zu haben die man da zu brauchen meint...
Es scheint sich da so zu verhalten, wenn das Programm auf ein SigTERM
hin beendet wird, und derweil die SEGV erfolgt, dass dann das
Programm weder crasht noch beendet, sondern endlosschleift. Irgendwie
nicht wirklich sinnvoll - da muss es doch brauchbarere Optionen
geben...

Und dann kommt mir der leise Verdacht, dass es sich bei dem
hängenbleibenden fork zwar nicht um das gleiche (denn diese
Spielchen sind immerhin mit einem weiteren SigTERM abstellbar),
aber um etwas verwandtes handeln könnte... hm, nicht wirklich
erfreulich...

Und immer noch die Frage: warum stört sowas die Pinguine nicht?

|> Wie kann man sowas debuggen?
|
|Wenn der Prozess unkillbar ist, dann hilft der Kernel-Debugger, um dort
|zu sehen wo der Prozess gerade steckt.
|Mitunter muss man das mehrfach machen, um ein Gefühl dafür zu bekommen,
|da der ja schließlich in irgendeiner Form imme rnoch läuft.

Hmja, gute Idee. Hab ich glaub ich nur ansatzweise vor laaanger
Zeit mal gemacht, und auf die schnelle auch keine brauchbare
Anleitung gefunden. (Das geht alles nur in Richtung remote und mit
symboltabelle(*) und riesenaufwand usw. usf.)

Also: ich drücke ctr-alt-esc und gebe "ps" ein. Da sehe ich das
Freundchen, im Status "RL" aber ohne irgendwelche weitere Hinweise.
Und "bt" liefert natürlich nur informationen über die KDB-Session
selber.

Die ddb manpage ist scheinbar seit 386BSD nicht aktualisiert und
scheint wenig mit der Realität gemein zu haben.

Dann hatte ich die Idee, "trace" mit der PID des Prozesses
einzugeben. Das liefert einen wechselnden Output. Ist das das was Du
meintest?

>tracing pid 59287 tid ...
>sched_switch(...)
>mi_switch(...)
>critical_exit(...)
>intr_execute_handlers(...)
>atpic_handle_intr(1)
>Xatpic_intr1()
>--- interrupt, eip ... ---
>vm_map_growstack(...)
>vm_fault(...)
>trap_pfault(...)
>trap(...)
>calltrap()
>--- trap 0xc, eip =... ---

Was sich dabei von Unterbruch zu Unterbruch immer wieder ändert sind
die Zeilen unmittelbar unter der "--- interrupt..." Zeile.
Manchmal sind da gar keine, manchmal mehr und manchmal auch andere...

Nur, was sagt mir das?

(*) Andere Frage:
Ich versteh nicht warum alle welt meint man solle erstmal
"makeoptions DEBUG=-g" einschalten - ich hab beim Probieren
(bzgl. kern/122126) erlebt, dass das bewirkt, dass der Kernel
im falle einer panic gleich mehrere davon macht und dann nicht
mehr rebootet sondern steckenbleibt - also KDB und KDB_UNATTENDED
funktioniert dann nicht mehr.
Man kommt dann weder in den KDB (falls man on-site ist) noch kriegt
man einen Reboot und damit seine Connectivity wieder (falls man
off-site ist). Und einen dump (wenn man denn einen wollte) gibts
dann scheinbar auch nicht.
Und das finde ich so ziemlich, äh, unwünschenswert - und wirklich
nur auf Testsystemen vertretbar...

PMc

To Unsubscribe: send mail to majordomo(at)de.FreeBSD.org
with "unsubscribe de-bsd-questions" in the body of the message
Received on Tue 08 Apr 2008 - 23:13:42 CEST

search this site