Ce thème Il s'agit d'un article dont l'auteur lira l'implémentation de CRuby. C'est la troisième fois. Je suis occupé avec le travail aujourd'hui (je suis arrivé chez moi à 23h30 ...) et mon poste a été retardé. La dernière fois était relativement lourde, et je vais aller dans un endroit où le contenu peut être allégé. Alors, lisons le rb_bug
qui est utilisé pour cracher les journaux d'erreurs que nous voyons souvent. Le but est de lire "Comment obtenir le nombre de lignes dans le fichier source lorsqu'une erreur est générée?"
rb_bug
rb_bug
est défini dans error.c.
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();
}
C'est un code très simple.
J'ai lu GET_THREAD ()
la dernière fois, mais c'était pour obtenir l'objet thread actuel.
report_bug
die ()
)Vous pouvez le lire comme s'il s'agissait du code.
Il y a deux questions principales.
rb_sourcefile ()
et rb_sourceline ()
obtiennent le nombre de lignes exécutées?
―― Pourquoi l'objet thread qui a été GET_THREAD () n'est-il pas passé en argument?fprintf (stderr, ...
Ce dernier n'a rien à voir avec cet objectif, je vais donc le lire bientôt et le mettre en attente.
Lisons le premier.
rb_sourcefile ()
et rb_sourceline ()
Les définitions de rb_sourcefile ()
et rb_sourceline ()
se trouvaient dans 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;
}
}
Pour le moment, ce qui est notable, c'est que les deux sont GET_THREAD ()
au début.
Il semble que la raison pour laquelle je n'ai pas réussi GET_THREAD ()
était parce que j'ai fini par le réacquérir à l'intérieur.
Était-ce un jugement selon lequel ce n'est pas du bon sens de rb_sourcefile ()
pour autre chose que le thread actuel?
Et encore une fois, les deux par rb_vm_get_ruby_level_next_cfp ()
Je reçois le pointeur de rb_control_frame_t
comme cfp
.
Et si cfp
existe, il semble obtenir le nom de fichier réel et le nombre de lignes de cette structure.
A partir du nom rb_control_frame_t
, nous pouvons en quelque sorte comprendre que cfp
est une structure avec des informations de contexte d'exécution.
Cette construction d'informations contextuelles ne sera pas comprise sans la lecture de l'ensemble de la VM
Pour le moment, je peux imaginer que le contexte d'exécution contient des informations permettant de calculer le fichier d'exécution et la ligne d'exécution, alors sautez-le sans le lire.
En fait, le côté rb_sourcefile ()
semble lire ʻiseq-> location.path dans la structure et le renvoyer comme une chaîne Ruby. Par conséquent, j'ai trouvé que ʻiseq-> location.path
est conservé en tant que fichier en cours d'exécution dans la VM, et en acquérant cela, le message d'erreur peut afficher le chemin de fichier approprié.
Donc, le reste est rb_vm_get_sourceline ()
qui est utilisé pour extraire les informations de ligne de cfp
du côté rb_sourceline ()
...!
rb_vm_get_sourceline()
Cette définition existe dans 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
est dans vm_core.h
,
Eh bien, je l'ai sauté car il peut y avoir des conditions d'échec d'acquisition.
calc_lineno ()
soustrait ʻiseq-> iseq_encoded du pointeur de
cfp-> pc`.
Tout d'abord, quand vous dites ʻiseq dans la source de la VM, cela signifie probablement la séquence d'instructions. Et quand vous regardez comment utiliser ʻiseq_encoded
#define REG_PC (REG_CFP->pc)
#define GET_PC_COUNT() (REG_PC - GET_ISEQ()->iseq_encoded)
J'ai aussi trouvé quelque chose comme.
En soustrayant ʻiseq-> iseq_encoded du pointeur de
pc, il devient PC_COUNT.
pc est la position d'exécution réelle dans l'espace mémoire, et ʻiseq-> iseq_encoded
peut être considéré comme le pointeur de départ du tableau contenant les instructions.
Et il volera de plus en plus profondément,
Cette fonction est également différée à rb_iseq_line_no ()
.
Il y a une définition dans 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);
}
}
Si pos
est 0, c'est comme ça, sinon find_line_no ()
pour l'instruction à la position précédente.
Cela tente probablement de trouver la ligne où l'erreur s'est réellement produite en déplaçant la position d'exécution d'un endroit.
Alors find_line_no ()
est comme suit.
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;
}
}
Si ʻiseq_line_info_entryest trouvé, on peut lire que le
line_no écrit ici est la ligne d'exécution. La définition de ʻiseq_line_info_entry
était la suivante.
struct iseq_line_info_entry {
unsigned int position;
unsigned int line_no;
};
C'est une forme simple de «position» et de «line_no». N'est-ce pas une structure qui exprime à quelle ligne correspond une certaine position d'instruction? C'est considéré.
Alors get_line_info ()
est ...
static struct iseq_line_info_entry *
get_line_info(const rb_iseq_t *iseq, size_t pos)
{
<Omission>
:
return &table[i-1];
}
Je suis enfin arrivé. Cela semble être la véritable identité. En regardant d'en haut ...
<Omission>
:
size_t i = 0, size = iseq->line_info_size;
struct iseq_line_info_entry *table = iseq->line_info_table;
:
<Omission>
Tout d'abord, nous extrayons line_info_table
de ʻiseq vers
table. Puisque cette
table est un pointeur vers ʻiseq_line_info_entry
,
Vous pouvez voir qu'il s'agit d'un tableau qui répertorie la correspondance entre le mot de commande et le nombre de lignes dans le fichier d'origine du mot de commande.
Continuez à lire get_line_info ()
.
<Omission>
:
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);
}
:
<Omission>
Utilitaire de débogage …… Il semble qu'il affiche la taille de la table. Je vais sauter.
<Omission>
:
if (size == 0) {
return 0;
}
else if (size == 1) {
return &table[0];
}
else <Ce qui suit est omis>
Il y avait un gros si, mais les deux premières conditions.
Si la taille de table
est 0, 0 (= NULL) est retourné, et s'il est 1, le pointeur vers le premier élément est retourné tel quel.
(Au fait, ce 0 ne devrait-il pas être changé en une constante NULL?)
Le corps principal est ailleurs.
<Omission>
:
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()Jusque là
Il parcourt la «table» pour trouver une position où la valeur de «position» dépasse la «pos» donnée dans l'argument et renvoie un pointeur vers cette position. En d'autres termes, c'est une recherche linéaire.
Cependant, si vous allez à une position qui dépasse la position dans l'ordre depuis l'avant, elle y sera renvoyée, donc «position» devrait augmenter de façon monotone.
Cela signifie que ʻiseq_line_info_entry indique apparemment que la
positione instruction de ʻiseq
est après line_pos
.
Aussi, je pense que vous pouvez lire que «table» est un tableau trié par «position».
Hmm ···? L'endroit est-il le plus utilisé pour le débogage? Dans ce cas, la bonne réponse est d'utiliser un algorithme simple.
De plus, s'il ne peut pas être trouvé jusqu'à la fin, il semble en renvoyer un avant ʻi` en dehors de la clause else. À ce stade, «i» doit correspondre à «size», ce qui signifie qu'il renvoie le dernier élément.
Vous savez maintenant comment obtenir la ligne d'exécution.
Voici comment rb_bug ()
, qui est utilisé pour générer le message d'erreur, obtient le fichier et la ligne en cours d'exécution:
--Fichier d'exécution
rb_control_frame_t
)pc
) en cours d'exécution, donc indexez cette table pour obtenir la ligne d'exécution.Le tableau semblait trié, mais pourquoi ne pas utiliser la recherche binaire, etc.? Quand j'y ai pensé, les commentaires suivants sont restés dans la source.
/* TODO: search algorithm is brute force.
this should be binary search or so. */
Selon les commentaires, il vaut mieux passer à un plus efficace. Compte tenu de l'emplacement, je pense qu'il serait préférable de le garder aussi simple que maintenant. Comme c'est un gros problème, je pense que je vais essayer Pull Request plus tard.
Cette fois, j'ai senti qu'il y avait beaucoup de spéculations. Cependant, je pense que j'ai acquis pas un peu de connaissances parmi eux. En lisant lentement, j'augmenterai progressivement mes connaissances et élargirai le champ de compréhension.
Recommended Posts