Debugger für atrun verwenden

pchris

Well-Known Member
Ich möchte atrun unter der Kontrolle eines Debuggers starten und den Programmablauf beobachten. Nachdem ich soetwas unter FreeBSD noch nie gemacht habe, frage ich einmal euch, ob ihr mir da zumindest in Stichworten beschreiben könnt, was ich alles tun muss bzw. brauche.
 
Wie vor einiger Zeit in einem anderen Thread beschrieben, habe ich ein unerklärliches Verhalten. Meiner Meinung werden trotz hoher Last neue Batch-Jobs gestartet. Und das will ich mir halt einmal mit Debugger anschauen, wo da ein falscher Wert herkommen könnte.
 
Welchen Schwellwert hast du denn angeben und bei welcher Last werden dennoch Jobs abgearbeitet? Ich würde das Problem einfach mit eigenen Analysestatements lösen:
Code:
--- atrun.c.orig	2016-01-26 15:47:45.797035000 +0100
+++ atrun.c	2016-01-26 15:51:49.999724000 +0100
@@ -564,6 +564,7 @@
     }
     /* run the single batch file, if any
     */
+    fprintf(stderr, "thresh load_avg: %.2f, current: %.2f\n",load_avg,gloadavg());
     if (run_batch && (gloadavg() < load_avg))
 	run_file(batch_name, batch_uid, batch_gid);

Dann at/atrun neu bauen und einfach ausführen.

Rob
 
Welchen Schwellwert hast du denn angeben und bei welcher Last werden dennoch Jobs abgearbeitet?
Schwellwert war damals der Defaultwert, also 1,5 * Kernanzahl. Trotzdem wurde noch bei einer Auslastung von 9,52 ein wartender Job ausgeführt.

Dann at/atrun neu bauen und einfach ausführen.
Und hier brauch ich ein wenig Nachhilfe: Zum Neubauen brauch ich wohl die Sourcen. Soll ich mir da die kompletten Sourcen für meine Version holen oder reicht eine Untermenge? Und dann? Komplieren mit dem Kommando cc?
 
Ich würde den ganzen Baum laden, aber nur ein make in /usr/src/libexec/atrun ausführen (nach dem Patchen/Editieren von atrun.c). Das Binary liegt dann unter /usr/obj/usr/src/libexec/atrun/.

Rob
 
Vielen Dank schon einmal für deine Starthilfe. Ich hab mir die kompletten base-Sourcen (von releng/10.2) geholt und konnte mit make dann auch erfolgreich bauen. Einzig das Binary lag dann direkt im Source-Verzeichnis (und nicht unter /usr/obj/...). Hat aber auch nicht weiter gestört.

Ich habe dann auch ins Makefile den -g Parameter für den Compiler hinzugfügt. So konnte ich dann auch den gdb sinnvoll nutzen.

Mit deiner ergänzten Codezeile bekomme ich folgenden Output:
Code:
root@baustelle:/usr/src/libexec/atrun # ./atrun
thresh load_avg: 51539607553.50, current: 0.08

Kurze Erläuterung:
In Zeile 462 wird die Variable für die Anzahl der Prozessoren (ncpu) sowie die Bufferlänge (ncpusz) als Datentyp size_t definiert.
Code:
size_t ncpu, ncpusz;
Ab Zeile 505 werden dann die Informationen vom System geholt. Laut der manpage von sysctlbyname wird für "hw.ncpu" ein Integer zurück gegeben.
Code:
ncpusz = sizeof(size_t);
if (sysctlbyname("hw.ncpu", &ncpu, &ncpusz, NULL, 0) < 0)
    ncpu = 1;
load_avg = LOADAVG_MX * ncpu;

Laut Debugger besitzt die Variable ncpu nach Aufruf von sysctlbyname den Wert 34359738369. Und das wird dann halt noch mit 1,5 multipliziert.

Nun meine neue Frage: Sollte hier nicht ein Integer Datentyp verwendet werden?

Ich habe testhalber int ncpu; definiert. Da kam dann auch das von mir erwartete zurück. Da sah der Output dann so aus:
Code:
root@baustelle:/usr/src/libexec/atrun # ./atrun
thresh load_avg: 1.50, current: 0.19
 
Laut Debugger besitzt die Variable ncpu nach Aufruf von sysctlbyname den Wert 34359738369. Und das wird dann halt noch mit 1,5 multipliziert.

Nun meine neue Frage: Sollte hier nicht ein Integer Datentyp verwendet werden?

Ich habe testhalber int ncpu; definiert. Da kam dann auch das von mir erwartete zurück.

Und das löst das Problem wirklich? size_t ist ja auch nur ein unsigned integer, sofern also kein negativer Wert zurückgeliefert wird müsste das also auch mit einem size_t funktionieren. Wenn ein kleinerer Datentyp (was int vermutlich ist) in einen Größeren umgewandelt wird, dann sollte der größere Datentyp trotzdem den korrekten Wert enthalten. Die einzige Möglichkeit, die ich mir vorstellen kann ist, dass in sysctlbyname ein memcpy o.ä. nur einen Teil des (uninitialisierten) Objektes überschreibt, aber genau dafür ist doch der ncpusz Parameter?
 
Ich sage, dass wir hier klassisches undefiniertes Verhalten sehen. Oben in der Funktion wird 'size_t ncpu' definiert, aber nicht initiiert. Da steht also Schrott drin. Später wird dann per sysctlbyname() der Wert hineingeschrieben. Die Längenangabe in Form von &ncpusz sagt aber nur, dass nicht sysctlbyname() nicht mehr als diese Anzahl Bytes schreiben wird. Es sagt nicht aus, dass diese Menge definitiv geschrieben wird. Es kann also durchaus sein, dass anschließend in ncpu der gewollte Wert und restlicher Schrott steht. Kurz gesagt: 'size_t ncpu = 0' müsste es lösen.
 
Ich hab das mal bei mir getestet und das ist wirklich das Problem. Entweder muss ncpu vorher auf 0 gesetzt werden, oder es muss ein int sein.
 
IMHO sollte man es size_t lassen (Die Anzahl der CPUs kann nie kleiner 0 sein) und korrekt initialisieren, wie Yamagi es vorschlug.

Rob
 
Schön das ihr das bestätigt habt. Schließlich war da einiges an Neuland für mich dabei. Ich habe nun auch einen Bugreport angelegt.
 
Zurück
Oben