5TC OSA (27 09 21) - gforien/os-from-scratch GitHub Wiki
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 ?
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
(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
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.
(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}
etpop {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
Non c'est déjà fait par l'instruction swi
.
La sauvegarde uniquemeent la restauration c'est le ^.
OK
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
OK
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
OK
Solution :
- on définit la variable globale
registers_p
danssyscall.c
- on l'initialise à la valeur de
sp
dansC_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..
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;
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);
OK
On n'oublie pas le typedef
On continue comme au dernier chapitre : c'est le syscall numéro 4.
...
OK
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");
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