5TC OSA (27 09 21) - gforien/os-from-scratch GitHub Wiki

Question pour Kevin

4.19) Calcul binaire
    date = 0x4300000042
    msw = date >> 32
    msw = 0x4300 non ? pourquoi on me dit que c'est = 0x43 ?? dans gdb
    avec ou sans cast uint32_t, ça ne change rien
    .
    en plus ça marche mais je comprends pas pourquoi
    .
    en plus ensuite quand on fait
    msw << 32
    on obtient
    = 0x4300 0000
    c'est pour ça que ça marche, mais WTF ??

5.4) j'ai rien compris

5.8) on sauvegarde LR_user sur r4 simplement ?

*******

5TC OSA

*******

Rappels fonctionnement d'un microprocesseur Le processeur fonctionne sur un cycle Fetch → Decode → Execute En réalité, avant le Execute on vérifie qu'il n'y a pas d'interruption système, sinon on ne pourrait rien faire en parallèle. Le clavier, la souris, tous les périphériques doivent constamment interrompre le système pour pouvoir signaler les actions reçues.

Stack = variables dont l'allocation est connue à l'avance par le compilateur Heap = variables allouées dynamiquement

Registres spéciaux PC : Programm Counter x IR : Instruction Register (en MIPS, mais pas en ARM) SP : Stack Pointer (pointe vers la stack actuelle) bl : instruction branch-and-link (équivalent de l'instruction JUMP sur MIPS) (et on sauvegarde la position de retour dans $LR) LR : Link Register, il pointe vers la position de retour du dernier bl CPSR : Current Program Status Register

Rappels gdb print_sr affiche le CPSR actuel bt affiche la stack d'exécution i r (info registers) affiche tous les registres p/x affiche une variable x/x affiche une variable en la déréférencant 1 fois

Chap 2. Compiling, executing and debugging

(2.3) Quelles sont les sections du programme ? Instructions Début de text - 0000 8000 dummy - 0000 91c8 fin dummy - 0000 .... div - 0000 91d0 fin div - 0000 .... compute_v - 0000 922c fin compute_v - 0000 .... kmain - 0000 9278 fin kmain - 0000 92a4 Fin de text - 0000 92a4

Non initialized variables = heap ? Début de bss - 0000 92a8 Fin de text - 0000 92bc

Début de stack - 0000 930c Fin de stack - 0000 970c

(2.4) Quels sont les registres utilisés ? r2, r3, r4 C'est le compilateur qui décide d'utiliser ces registres

(2.5) Où sont stockées les variables ? Avant de retourner de kmain(),

         $sp    = 0x96fc
*((int*) $sp)   = 0x20b
*((int*) $sp+1) = 0x5
*((int*) $sp+2) = 0x0
*((int*) $sp+3) = 0x80a4

On voit l'initialisation de radius

mov r3, #5              (r3 ← 5)
str r3, [$sp, #4]       (sp+4 ← r3)

On affiche donc $sp+4 après cette instruction $sp+4 = 0x9700 Donc radius est à l'adresse 0x9700 De même, volume est à l'adresse 0x96fc

(2.6) Dans quelle direction augmente la stack ? La stack augmente avec des adresses décroissantes.

(2.8) Quelle instruction modifie le Link Register ? C'est le branch & link BL = LR ← PC+1 PC ← label

(2.9) Retourner correctement de la fonction dummy

__asm("bl dummy");

(2.10) Storer et loader radius dans les registres

__asm("mov r2, %0" : : "r"(radius));
__asm("mov %0, r3" : "=r"(radius));

(2.11) Déclarer une fonction naked On voit que le prologue et l'épilogue sont manquants

# prologue
push {lr}
sub sp, sp, #20

# epilogue
add sp, sp, #20
pop {pc}

(2.12) Compiler un test et vérifier qu'on passe 2 fois par bidule() OK

(2.13) Compiler un test et vérifier à la main qu'on passe 2 fois par bidule() OK

$ ./run-gdb.sh ../test/bidule-called-twice.gdb

(2.14) Utiliser le script shell fourni

$ ./tools/run-test.sh ./test/kmain-bidule.c ./test/bidule-called-twice.gdb
> test OK

Chap 3. CPU Execution modes

Rappels: on a registres de 32 bits.

  • r0-r12 = registers
  • r13 = SP
  • r14 = LR
  • r15 = PC

Également:

  • CPSR (Current Program Status Register)
  • SPSR (Saved Program Status Register)
Dec Hex Binary Execution mode
16 0x10 0b1 0000 User
17 0x11 0b1 0001 FIQ
18 0x12 0b1 0010 IRQ
19 0x13 0b1 0011 SVC (Supervisor)
23 0x17 0b1 0111 Abort
31 0x1f 0b1 1111 System

(3.1) Observe the value of CPSR at the beginning of kmain()

(gdb) p/x $cpsr
> 0x 6000 00df
        df = 1101 1111
        M[4:0] = 11111 = System
        I = 1 = désactivé
        A = 1 = désactivé

(3.2)(3.3) Passer au mode SVC au début de kmain()

p/x $cpsr           → 0x......df
p/x $sp             → 0x9744
x/x $lr             → 0x80a4 <loop_after_kmain>

__asm("cps #19");

p/x $cpsr           → 0x......d3
p/x $sp             → 0x955c
p/x $lr             → 0x0 (désactivé)

print_sr
CPSR = ...AIF[USER]

À partir du moment où on passe en mode d'exception (Supervisor, Abort, Undefined, Interrupt, Fast Interrupt), on a un SP et un LR dédié. Donc le fil d'exécution normal est interrompu, et $lr (qui pointe vers la prochaine fonction) est désactivé.

On est passé en mode SVC (Supervisor) donc pour accéder aux nouveaux SP et LR, il faut maintenant appeler R13_svc et R14_svc respectivement.

(3.4) Passer au mode User puis au mode SVC Impossible (on reste en mode User) car ce n'est pas un mode privilégié.

(3.5) Écrire dans le SPSR

mrs r0, SPSR       #(read)
msr SPSR, r1       #(write)

Impossible car le mode System n'est pas un mode d'exception. Le SPSR existe uniquement en mode d'exception.

Chap 4. System calls

(4.5) Pass the test

$ ./tools/run-test.sh ./test/kmain-reboot.c ./test/sys-reboot-does-reboot.gdb

(4.6) Implémenter une naive version de do_sys_nop() Rappels: Quand on branch dans une fonction, l'instruction bl stocke l'adresse de la prochaine instruction dans le lr. Pour y retourner, la dernière instruction est donc bx lr. Évidemment cela ne marche qu'une fois, puisque après être retourné dans la fonction supérieure on a perdu la valeur du lr supérieur.

Il y a donc deux méthodes:

  • au début/fin d'une fonction terminale bl 0x84a0 → .... ← bx lr

  • au début/fin d'une fonction intermédiaire (où on sait que lr sera effacé) bl 0xa1f9 <compute_volume> → push {lr} .... ← pop {pc}

Implémentation naïve:

void do_sys_nop() {
    __asm("nop");
}

(Évidemment ça ne marche pas). Voilà ce qu'il se passe:

  • on fait l'interruption système swi
  • donc on passe en mode SVC
  • on arrive dans asm_swi_handler
  • on arrive dans C_swi_handler()
  • c'est une fonction intermédiaire donc pas de problème on fait push {lr} et pop {pc}
  • on revient dans sys_nop()
  • sauf que entre temps tous les registres on été affectés par C_swi_handler(), notamment lr
# avant le swi
(gdb) x/x $lr
0x9320 <kmain+16>:      0xebfffc57

(gdb) si
# juste après avoir exécuté le swi, on arrive dans C_swi_handler()

(gdb) print_sr
> CPSR=0x600001d3 (nZCv...AIF[SVC])             # on est bien en mode Supervisor

(gdb) x/x $lr
> 0x8470 <sys_nop+8>:     0xe320f000            # et lr pointe sur l'instruction de retour
# ensuite on est dans C_swi_handler(), on push {lr}
# ..
# ..
# ..
# on sort de C_swi_handler(), on pop {pc}
# on revient dans sys_nop, et
(gdb) x/x $lr
0x84d4 <C_swi_handler+52>:      0xea000003

(4.9) Faut-il sauvegarder le CPSR ?

Non c'est déjà fait par l'instruction swi. La sauvegarde uniquemeent la restauration c'est le ^.

(4.10) Vérifier que ça marche

OK

(4.11) Mettre une boucle infinie autour de sys_nop()

Avec la boucle infinie on voit que sp diminue à chaque interruption système.

(gdb) c
(gdb) p/x $sp
$sp = 0x958c

(gdb) c
(gdb) p/x $sp
$sp = 0x957c

(gdb) c 7
(gdb) p/x $sp
$sp = 0x950c

Cela posera un problème quand sp rattrapera le pc. On ajoute l'attribut naked sur C_swi_handler() qui fait disparaître le prologue et l'épilogue et ça marche.

# prologue
push {lr}
sub sp, sp, #20

# epilogue
add sp, sp, #20
pop {pc}

On charge une nouvelle valeur dans le PC donc on n'exécute jamais l'épilogue

(4.12) Vérifier que les tests passent

OK

(4.13) Syscall avec paramètres

Quand on arrive dans do_sys_nop, ils sont directement en haut de la stack.

# quand on arrive dans do_sys_nop
(gdb) i r
    r0             0x3              3
    r1             0x89acbdef       -1985167889
    r2             0x1234567        19088743
    ..
    r12            0x0              0
    sp             0x9628           0x9628
    lr             0x8530           34096
    pc             0x84b8           0x84b8 <do_sys_settime>
    cpsr           0x600001d3       1610613203
    fpscr          0x0              0
    fpsid          0x410430f0       1090793712
    fpexc          0x0              0

(gdb) x/x $sp
    0x9628: 0x89acbdef
    0x962c: 0x01234567

(4.14) (4.15)

OK

(4.16) Comment accéder au paramètres ?

Solution :

  • on définit la variable globale registers_p dans syscall.c
  • on l'initialise à la valeur de sp dans C_swi_handler()
__asm("mov %0, sp" : "=r"(registers_p));
  • puis dans do_sys_settime() on peut accéder aux registres (32-bits) avec *(registers_p+1), *(registers_p+2), etc..

(4.17) (4.18)

OK

Ce qui est foireux c'est ça:

uint64_t date_ms2 = *(registers_p+1);

Alors qu'on sait que registers_p est un int* donc sur 32-bits

Solution

// Attention (!)
uint64_t date_ms = *(registers_p+2);
date_ms = date_ms << 32;

uint32_t date_ms2 = *(registers_p+1);
date_ms += (uint64_t) date_ms2;

(4.19) Syscall retournant une valeur

On implémente gettime():

  • cette fois c'est dans do_sys_gettime() qu'on manipule le pointeur vers la stack.
  • ensuite C_swi_handler() push la stack vers les registres
  • ensuite on récupère les registres dans sys_gettime(), après l'instruction swi
    // jsutement cette fois on a du code C avant l'asm du coup on ne sait plus ce qu'on a
    // dans R0 et R1
    uint64_t date = get_date_ms();
    uint32_t lsw = (uint32_t) date;
    uint32_t msw = (uint32_t) (date >> 32);

(4.20)

Chap 5. Process dispatching

(5.1) Lire le code concurrent

OK

(5.2) Écrire le struct PCB

On n'oublie pas le typedef

(5.3) Écrire sys_yieldto

On continue comme au dernier chapitre : c'est le syscall numéro 4.

(5.4) C'est le mystère complet

...

(5.5)

OK

(5.6)

(5.8)

On est tenté de faire ça dans la fonction sys_yieldto():

// save    LR_user in r4
__asm("mov r4, lr" : : : "r0", "r1");
__asm("swi #0");
__asm("mov lr, r4");
// restore LR_user from r4

Mais c'est pas une bonne solution. Ce qu'on veut c'est le stocker dans le PCB, sinon c'est juste un registre. Si on le stocke plutôt dans le PCB, on peut avoir un PCB pour chaque process.

Donc ce qu'il faut c'est le faire dans l'espace kernel donc do_sys_yieldto():

// save
__asm("cps #31");
__asm("mov %0, lr" : "=r"(current_process->LR_user));
__asm("cps #19");

// restore
__asm("cps #19");
__asm("mov lr, %0" :  : "r"(current_process->LR_user));
__asm("cps #31");

5.10

on va créer une stack pour chaque process

kalloc me renvoie un uint8_t* vers le bas de la pile (0x100000) Je dois faire deux choses:

  • additionner la taille de la pile pour me retrouver en haut
  • caster en uint32_t* pour manipuler des int
⚠️ **GitHub.com Fallback** ⚠️