CRuby-Code lesen (3): Ausgabe der Ausführungszeile rb_bug

Dieses Thema

Dies ist ein Artikel, in dem ich die CRuby-Implementierung lesen werde. Dies ist das dritte Mal.

Ich bin heute mit der Arbeit beschäftigt (ich bin um 23:30 Uhr in meinem Haus angekommen ...) und mein Posten hat sich verzögert. Das letzte Mal war relativ schwer, und ich werde an einen Ort gehen, an dem der Inhalt aufgehellt werden kann.

Lesen wir also den rb_bug, mit dem die häufig angezeigten Fehlerprotokolle ausgespuckt werden. Das Ziel ist zu lesen: "Wie erhalten Sie die Anzahl der Zeilen in der Quelldatei, wenn ein Fehler ausgegeben wird?"

rb_bug

rb_bug ist in error.c definiert.

void
rb_bug(const char *fmt, ...)
{
    const char *file = NULL;
    int line = 0;

    if (GET_THREAD()) {
	file = rb_sourcefile();
	line = rb_sourceline();
    }

    report_bug(file, line, fmt, NULL);

    die();
}

Es ist ein sehr einfacher Code. Ich habe das letzte Mal "GET_THREAD ()" gelesen, aber es sollte das aktuelle Thread-Objekt erhalten.

  1. Erfassen Sie das aktuelle Thread-Objekt
  2. Holen Sie sich die laufende Quelldatei und die Anzahl der Zeilen.
  3. Ausgabe mit der Funktion report_bug
  4. Beenden Sie das Verarbeitungssystem zwangsweise (die ())

Sie können es lesen, als wäre es der Code.

Es gibt zwei Hauptfragen.

  1. Wie erhalten rb_sourcefile () und rb_sourceline () die Anzahl der ausgeführten Zeilen? ――Warum wird das Thread-Objekt, an das GET_THREAD () übergeben wurde, nicht als Argument übergeben?
  2. Wie unterscheidet sich report_bug von fprintf (stderr, ...)?

Letzteres hat nichts mit diesem Zweck zu tun, deshalb werde ich es bald lesen und auf Eis legen.

Lesen wir das erstere.

rb_sourcefile () und rb_sourceline ()

Die Definitionen für "rb_sourcefile ()" und "rb_sourceline ()" waren in vm.c.

const char *
rb_sourcefile(void)
{
    rb_thread_t *th = GET_THREAD();
    rb_control_frame_t *cfp = rb_vm_get_ruby_level_next_cfp(th, th->cfp);

    if (cfp) {
	return RSTRING_PTR(cfp->iseq->location.path);
    }
    else {
	return 0;
    }
}

int
rb_sourceline(void)
{
    rb_thread_t *th = GET_THREAD();
    rb_control_frame_t *cfp = rb_vm_get_ruby_level_next_cfp(th, th->cfp);

    if (cfp) {
	return rb_vm_get_sourceline(cfp);
    }
    else {
	return 0;
    }
}

Derzeit fällt auf, dass beide am Anfang GET_THREAD () sind. Es scheint, dass der Grund, warum ich "GET_THREAD ()" nicht übergeben habe, darin bestand, dass ich es im Inneren wiedererlangt habe. War es ein Urteil, dass es nicht normal ist, "rb_sourcefile ()" für etwas anderes als den aktuellen Thread zu verwenden?

Und wieder beide von rb_vm_get_ruby_level_next_cfp () Ich erhalte den Zeiger von rb_control_frame_t als cfp.

Und wenn "cfp" existiert, scheint es den tatsächlichen Dateinamen und die Anzahl der Zeilen aus dieser Struktur zu erhalten. Aus dem Namen rb_control_frame_t können wir irgendwie verstehen, dass cfp eine Struktur mit Ausführungskontextinformationen ist. Diese Konstruktion von Kontextinformationen wird nicht verstanden, ohne die gesamte VM zu lesen. Vorerst kann ich mir vorstellen, dass der Ausführungskontext Informationen enthält, mit denen die Ausführungsdatei und die Ausführungszeile berechnet werden können. Überspringen Sie sie also, ohne sie zu lesen.

Tatsächlich scheint die Seite "rb_sourcefile ()" "iseq-> location.path" in der Struktur zu lesen und als Ruby-Zeichenfolge zurückzugeben. Daher habe ich festgestellt, dass "iseq-> location.path" als laufende Datei in der VM gespeichert ist. Wenn Sie diese erfassen, kann die Fehlermeldung den entsprechenden Dateipfad ausgeben.

Der Rest ist also "rb_vm_get_sourceline ()", mit dem Zeileninformationen aus "cfp" auf der Seite "rb_sourceline ()" extrahiert werden ...!

rb_vm_get_sourceline()

Diese Definition existiert in vm_backtrace.c.

inline static int
calc_lineno(const rb_iseq_t *iseq, const VALUE *pc)
{
    return rb_iseq_line_no(iseq, pc - iseq->iseq_encoded);
}

int
rb_vm_get_sourceline(const rb_control_frame_t *cfp)
{
    int lineno = 0;
    const rb_iseq_t *iseq = cfp->iseq;

    if (RUBY_VM_NORMAL_ISEQ_P(iseq)) {
	lineno = calc_lineno(cfp->iseq, cfp->pc);
    }
    return lineno;
}

RUBY_VM_NORMAL_ISEQ_P ist in vm_core.h, Nun, ich habe es übersprungen, weil es einige Bedingungen für einen Fehler bei der Erfassung geben kann.

calc_lineno () subtrahiert iseq-> iseq_encoded vom Zeiger von cfp-> pc.

Wenn Sie in der Quelle der VM "iseq" sagen, bedeutet dies wahrscheinlich zunächst "Instruction Sequence". Und wenn Sie sich ansehen, wie man "iseq_encoded" verwendet

#define REG_PC  (REG_CFP->pc)
#define GET_PC_COUNT()     (REG_PC - GET_ISEQ()->iseq_encoded)

Ich habe auch so etwas gefunden.

Durch Subtrahieren von "iseq-> iseq_encoded" vom Zeiger von "pc" wird PC_COUNT. "pc" ist die tatsächliche Ausführungsposition im Speicherbereich, und "iseq-> iseq_encoded" kann als Startzeiger des Arrays angesehen werden, das die Anweisungen enthält.

Und es wird tiefer und tiefer fliegen, Diese Funktion wird weiter auf "rb_iseq_line_no ()" verschoben. Es gibt eine Definition in iseq.c.

unsigned int
rb_iseq_line_no(const rb_iseq_t *iseq, size_t pos)
{
    if (pos == 0) {
	return find_line_no(iseq, pos);
    }
    else {
	return find_line_no(iseq, pos - 1);
    }
}

Wenn pos 0 ist, ist es so wie es ist, andernfalls find_line_no () für die Anweisung an der vorherigen Position. Dies versucht wahrscheinlich, die Zeile zu finden, in der der Fehler tatsächlich aufgetreten ist, indem die Ausführungsposition um eine Stelle zurück verschoben wird.

Dann ist find_line_no () wie folgt.

static unsigned int
find_line_no(const rb_iseq_t *iseq, size_t pos)
{
    struct iseq_line_info_entry *entry = get_line_info(iseq, pos);
    if (entry) {
	return entry->line_no;
    }
    else {
	return 0;
    }
}

Wenn "iseq_line_info_entry" gefunden wird, kann gelesen werden, dass das dort geschriebene "line_no" die Ausführungszeile ist. Die Definition von "iseq_line_info_entry" war wie folgt.

struct iseq_line_info_entry {
    unsigned int position;
    unsigned int line_no;
};

Es ist eine einfache Form von "position" und "line_no". Ist es nicht eine Struktur, die ausdrückt, welcher Zeile eine bestimmte Befehlsposition entspricht? Es gilt als.

Dann ist get_line_info () ...

static struct iseq_line_info_entry *
get_line_info(const rb_iseq_t *iseq, size_t pos)
{
<Unterlassung>
  :
    return &table[i-1];
}

Ich bin endlich angekommen. Dies scheint die wahre Identität zu sein. Von oben schauen ...

<Unterlassung>
  :
    size_t i = 0, size = iseq->line_info_size;
    struct iseq_line_info_entry *table = iseq->line_info_table;
  :
<Unterlassung>

Zuerst extrahieren wir line_info_table von iseq nach table. Diese Tabelle ist ein Zeiger auf iseq_line_info_entry, also Sie können sehen, dass es sich um ein Array handelt, das die Entsprechung zwischen dem Befehlswort und der Anzahl der Zeilen in der Originaldatei des Befehlsworts auflistet.

Lesen Sie weiter get_line_info ().

<Unterlassung>
  :
    const int debug = 0;

    if (debug) {
	printf("size: %"PRIdSIZE"\n", size);
	printf("table[%"PRIdSIZE"]: position: %d, line: %d, pos: %"PRIdSIZE"\n",
	       i, table[i].position, table[i].line_no, pos);
    }
  :
<Unterlassung>

Dienstprogramm zum Debuggen ... Es scheint, dass es die Tabellengröße ausgibt. Ich werde es überspringen.

<Unterlassung>
  :
    if (size == 0) {
	return 0;
    }
    else if (size == 1) {
	return &table[0];
    }
    else <Folgendes wird weggelassen>

Es gab ein großes Wenn, aber die ersten beiden Bedingungen. Wenn die Größe von table 0 ist, wird 0 (= NULL) zurückgegeben, und wenn es 1 ist, wird der Zeiger auf das erste Element so zurückgegeben, wie er ist. (Sollte diese 0 nicht in eine NULL-Konstante geändert werden?)

Der Hauptkörper ist in sonst.

<Unterlassung>
  :
	for (i=1; i<size; i++) {
	    if (debug) printf("table[%"PRIdSIZE"]: position: %d, line: %d, pos: %"PRIdSIZE"\n",
			      i, table[i].position, table[i].line_no, pos);

	    if (table[i].position == pos) {
		return &table[i];
	    }
	    if (table[i].position > pos) {
		return &table[i-1];
	    }
	}
    }
    return &table[i-1];
}   // get_line_info()Bisher

Es durchsucht table nach einer Position, an der der Wert position die im Argument angegebene Position pos überschreitet, und gibt einen Zeiger auf diese Position zurück. Mit anderen Worten, es ist eine lineare Suche.

Wenn Sie jedoch zu einer Position gehen, die die Position in der Reihenfolge von vorne überschreitet, wird sie dort zurückgegeben, sodass die Position monoton ansteigen sollte. Dies bedeutet, dass "iseq_line_info_entry" anscheinend anzeigt, dass die "position" -te Anweisung von "iseq" nach "line_pos" steht. Ich denke auch, dass Sie lesen können, dass "Tabelle" ein Array ist, das nach "Position" sortiert ist.

Hmm ···? Wird der Ort am häufigsten zum Debuggen verwendet? In diesem Fall ist die richtige Antwort die Verwendung eines einfachen Algorithmus.

Wenn es nicht bis zum Ende gefunden werden kann, scheint es das vor "i" außerhalb der else-Klausel zurückzugeben. Zu diesem Zeitpunkt sollte "i" mit "size" übereinstimmen, was bedeutet, dass das letzte Element zurückgegeben wird.

Jetzt wissen Sie, wie Sie die Ausführungslinie erhalten.

Zusammenfassung

So erhält rb_bug (), mit dem die Fehlermeldung generiert wird, die laufende Datei und Zeile:

Apropos

Die Tabelle schien sortiert zu sein, aber warum nicht die binäre Suche usw. verwenden? Als ich darüber nachdachte, blieben die folgenden Kommentare in der Quelle.

/* TODO: search algorithm is brute force.
         this should be binary search or so. */

Den Kommentaren zufolge ist es besser, zu einem effizienteren zu wechseln. In Anbetracht des Standorts denke ich, dass es besser wäre, es so einfach wie jetzt zu halten. Da es eine große Sache ist, denke ich, dass ich Pull Request später versuchen werde.

Andere

Diesmal hatte ich das Gefühl, dass viel spekuliert wurde. Ich denke jedoch, dass ich nicht wenig Wissen unter ihnen erworben habe. Durch langsames Lesen werde ich mein Wissen schrittweise erweitern und das Verständnis erweitern.

Recommended Posts

CRuby-Code lesen (3): Ausgabe der Ausführungszeile rb_bug
CRuby Code Reading (2): rb_newobj_of
CRuby Code Reading (1): LIKELY / UNLIKELY
Ausführung des RSpec-Testcodes
[Lernen / Ausgeben von Testcode]