Low Level Programming

A simple operating system is simulator system calls, called syscall. It tells the ... général un overflow flag, chargé dans les processeurs x86 par les instructions.
235KB taille 0 téléchargements 332 vues
Low Level Programming Notes du cours de Lin Jensen, septembre-décembre 2006 I.

Quelques rappels de calcul

Les nombres en hexadécimal sont précédés de 0x pour éviter une confusion dans les bases. Quand on veut multiplier 0x3FCB par 2, il suffit de le convertir en binaire et d’opérer un décalage : 0111 1111 1001 0110, soit le résultat 0x7F96. On rappelle que chaque nombre s’écrit sur 4 bits. Pour soustraite lorsqu’on a des nombres signés, on boucle. Ainsi 0000 moins 1 nous donne 1111. La règle du complément à deux consiste à inverser les bits et à rajouter 1 : 0101 = 5 → 1010 (inversion) → 1011 (ajout du 1) : -5 0110 = 6 → 1001 → 1010 = - 6 Il y a donc un seul circuit pour toute l’arithmétique, puisque les nombres négatifs sont convertis grâce à la règle du complément à deux. De même, il n’y a pas de circuit soustracteur mais juste un additionneur ; on utilise la règle du complément à deux en cas de soustraction. Le résultat doit être complémenté à nouveau pour l’exprimer en notation décimale. Par exemple si l’on obtient 111110, en le complémentant on obtient 000010 soit +2 ; donc le nombre obtenu était -2.

II.

Eléments d’architecture d’un ordinateur MIPS

Control Unit

Instruction Adress Decode (IAD) instruction

Address bus

ADD MEMORY 1 5 6

$1 $2 $3

Par exemple un 0 dans l’IAD va faire un ADD.

Philippe Giabbanelli

Data bus

1 – Fetch instruction 2 - Decode 3 – Increment program count 4 – Execute instructions

↑ OLD MIPS MIPS 1 ← MIPS Notes sur l’assembleur

Le Program Count (PC) nous donne l’instruction à exécuter. Si on passe d’une instruction à l’autre linéairement, alors on passe de la 0 à la 4, i.e. on avance de 4 bytes. On notera qu’il y a différents langages d’assembleur. Par exemple, il y a différentes syntaxes pour les processeurs Intel. En plus des syntaxes, les noms des commandes et des registres ne sont pas les mêmes. En assembleur x86 on trouve par exemple les registres eax et ebx, tandis qu’en MIPS on a des $t0, etc. Les opinions sur la syntaxe sont également divisés dans le sens de lecture des commandes : de gauche à droite, ou de droite à gauche. Est-il mieux d’écrire ADD $3 $2 $1 pour $3 ← $2+$1 ou $3+$2 → $1.

Processeur 6502, Atari III.

Quand on dépasse des bornes lors d’un calcul, on parle d’arithmetic overflow, cela entraîne des erreurs. Certains processeurs le détecte et font un trap ou interruption request, d’autres ne réagissent pas ; ils utilisent en général un overflow flag, chargé dans les processeurs x86 par les instructions arithmétiques et de comparaison (équivalent à une soustraction sans stockage de résultat), et non utilisé par les opérations logiques. Ne pas confondre : la division par 0 n’est pas un arithmetic overflow ; la division par 0 est mathématique indéfinie : ce n’est pas que sa valeur est trop large, mais plutôt qu’elle n’a pas de valeur !

Opérations arithmétiques et premiers programmes MIPS

EDIT

ASSEMBLE

LINK TO THE LIBRARIES

EXECUTE

SYNTAX ERROR We will use SPIM or XSPIM to show which line of the program code is going to be executed. We will also use bank of test data, i.e. tests units, with MIPS MARK (command mipsmark file.a). At the end, we use submit116 file.a. In MIPS, the results always go back to register.

## Text segment .text .globl __start __start: lw $t1, mynum li $t2, 5 add $t3, $t1, $t2 sw $t3, mynum

# execution starts here # load word mynum into temporary register 1 # load immediate (for a constant) 5 # add the numbers and put them into t3 # store word from register to memory (location of mysum)

.data mynum: .word 1 mysum: .word 0

# will be changed to 6 at the end

If we want to move something from a register to a data, we use instruction store. If we want to take something, then we use load.

A simple operating system is simulator system calls, called syscall. It tells the O/S that we need help. Register $v0 has to contain the service number, i.e. what we want syscall to do for us. Another registers will be use by the service. $a0 stands for argument 0 for any kind of a call.. Service Call code Arguments (input) Results print integer

1

$a0 = integer

print string

4

$a0 = address of string Prints every character until the terminating ‘\0’

read integer

5

(none)

$v0 holds integer that was entered

read string

8

$a0=address to store $a1= length limit

characters are stored

exit

10

(none)

Ends the program

Philippe Giabbanelli

signed decimal integer printed in console window

2

Notes sur l’assembleur MIPS

# the goal of the program is to add 2 + 3 and print “5 oranges” .text .globl __start __start: lw $t0, mynum # t0 ← 2 (mynum) li $t1, 3 # t1 ← 3 add $a0, $t0, $t1 # a0 ← mynum + 3 li $v0, 1 # ready to print an int syscall # ask the OS to run service 1 la $a0, str # load the address of str li $v0, 4 # ready to print a string syscall # print the string li $v0, 10 # ready to end syscall # exit

Il y a des pseudo-instructions qui sont ensuite expansées, comme neg $t3, $t0 → sub $t0, $0, $t0 Où $0 contient toujours 0. La forme normale est : Rdest, Rsrc1, src2 Où src2 est un registre ou une constante. Parfois, addi est utilisé pour la forme à 2 registres et une constante. Il y aussi un raccourci : add $t0, 1 → add $t0, $t0, 1 Cela marche de la même manière avec la soustraction, mais il n’y a pas de subi. C’est plutôt : addi $t5, $t0, -5 sub $t5, $t5, 4 ≡ addi $t5, $t5, -4

.data mynum: .word 2 # a word is 32 bits. mynum is a label str: .asciiz “oranges\n” # asciiz ends the string with \0

Avec ‘mul Rdest, Rsrc1, src2’, on fait une multiplication de nombres signés et on stocke le résultat sur 32 bits. Or, multiplier 32 bits par 32 bits peut donner un résultat sur 64 bits, et alors ça ne loge pas, il y a une arithmetic overflow. La pseudo-instruction mulo regarde s’il y a overflow, et arrête proprement la chose ; c’est implémenté par plusieurs instructions, pour disposer de la vérification. L’instruction div marche de même manière que mul : li $t0, 20 div $a0, $t0, 3 li $v0, 1 syscall # affiche 6, à savoir le résultat de la division entière Avec rem Rdest, Rsrc1, src2 on obtient le reste (remainder) de la division. Il y a aussi une version nonsignée de la multiplication et de la division, en mettant un ‘u’ à la fin (exemple : mulou). L’instruction réelle de la machine pour la multiplication est mult Rsrc1, Rsrc2. Comme le résultat fait 64 bits, il est stocké dans deux registres : S’ajoutent aux Register hi Register lo 32 registres de l’ordinateur En contrôlant l’allure de hi et de lo, on sait s’il y a eu overflow ou non. C’est le même principe avec la division, où on met le quotient en lo et le reste en hi. lw $t0, apples div $t1, $t0, 8 rem $t2, $t0, 8

# apples / student # remainder

Philippe Giabbanelli

li $1, 8 div $t0, $1 mflo $t1 li $1, 8 div $t0, $1 mfhi $t2

expansion

3

Optimisation (compilateur)

li $1, 8 div $t1, $1 mflo $t2 mflo $t3

Notes sur l’assembleur MIPS

# The problem is to find a point on a line, given by the equation: y = slope*x + intercept for some value # x, calculate and print the corresponding y value that is (nearly) on the line. Since we are doing integer # arithmetic, slope will be given as two numbers, dy and dx, see figure. You will need to multiply before # dividing, and the answer will be only approximate, simply ignore any remainder. .text .globl __start __start:

# execution starts here

la $a0, hi li $v0, 4 syscall

# we print a welcome message `cause we are polite # be ready to print the message # ok the message has been print

lw $t0,x lw $t1,dy mult $t0,$t1 mflo $t2 lw $t1,dx div $t2,$t1 mflo $t1 lw $t0, intercept add $t3,$t0,$t1 sw $t3, y

# let us have the variable we want to compute... x # and here we get dy # we multiply the number, it goes in Lo # the result is now in register t2 `cause we know it is a little number

li $v0, 4 la $a0,ans syscall lw $a0,y li $v0, 1 syscall li $v0,4 la $a0,endl syscall li $v0,10 syscall

# be ready to print # "answer ="

# and now we have (dy.x)/dx # we move the result from Low because we assume it is the quotient # we make the final computing for additionning every part of the expression # we store the final result in the variable y

# print the answer # print "\n" # exit program # ciao !

## -----------------------------------------------------.data x: .word 9 # find y for this value dy: .word 10 # vertical rise dx: .word 6 # horizontal run intercept: .word -2 #line crosses y-axis here y: .word -99 #optionally use this memory... ans: .asciiz "answer = " endl: .asciiz "\n" hi: .asciiz "hi there we are gonna make some calculus\n"

Philippe Giabbanelli

4

Notes sur l’assembleur MIPS

IV.

Control Flow Instructions

On discerne trois patterns : appel de functions (call functions), sauter une partie (skip), boucle (loop).

?

T

Branch to label

F Keep going in normal way Instruction

Branch to label if

beq Rsrc1, Src2, label

Rsrc1 = Src2

bne Rsrc1, Src2, label

Rsrc1 Src2

blt Rsrc1, Src2, label

Rsrc1 < Src2

bgt Rsrc1, Src2, label

Rsrc1 > Src2

bgtu

ble Rsrc1, Rsrc1 = Src2 Src2, label

bgeu

Unsigned

bltu

On utilisera principalement l’instruction ‘j’ qui permet d’aller à un label. Par exemple, j backthere saute à au label (étiquette) backthere. # affichons un message 5 fois .text .globl –start li $t0, 5 # initialization of loop counter la $a0, mess li $v0, 4 again : beqz $t0, done # while t0 != 0… syscall # loop body sub $t0, 1 # decrement t0 j again # go to the test done: li $v0, 10 syscall .data mess: .asciiz “hello again\n”

# affiche un message # nombre de fois : ∞ .text .globl –start la $a0, mess li $v0, 4 again : syscall j again done: li $v0, 10 syscall .data mess: .asciiz “hey\n”

On utilise aussi les pseudo-instructions avec un z à la fin pour signifier « zéro ». beqz $t0, done → beq $t0, $0, done .text .globl –start --start li $v0, 4 la $a0, what # ‘what is the temperature ?’ syscall li $v0, 5 # read int, store result in $v0 syscall move $t0, $v0 blt $t0, 5, chilly # s’il fait moins de 5°C la $a0, nice # ‘it’s hot !’ li $v0, 4 syscall j endif chilly : la $a0, cold # ‘brr !’ li $v0, 4 syscall endif: li $v0, 10 syscall Philippe Giabbanelli

Ces instructions de branchement sont parmi les soucis qu’engendre l’écriture d’un code assembleur à la main. En effet, si le programme devient gros et qu’il y a plusieurs boucles, on risque de leur attribuer les mêmes labels… un compilateur utilise un générateur de label, qui asure qu’il n’a pas déjà été utilisé. {sum non-negative numbers} sum := 0 num := 0 WHILE num >= 0 DO sum := sum + num read (num) ENDWHILE

{write 10 lines} FOR countdown := 10 downto 1 DO print ("Hello again...") ENDFOR

# add $t0, $0, $0 really: # set up print arguments before loop move $t0, $0 #sum=0 la $a0, hello_again move $v0, $0 #num=0 li $v0, 4 #print string call while5: li $t0, 10 # countdown bltz $v0, endwhile5 for10: add $t0, $v0 beqz $t0, endfor10 # ---- $v0 switches role here syscall #print another line li $v0, 5 #read int call sub $t0, 1 #decr. countdown syscall j for10 j while5 endfor10: endwhile5:

5

Notes sur l’assembleur MIPS

V.

Parcours d’un tableau

T

3 +0 Soit un tableau T d’entiers {3, 6, 9, 2}. T[2] représente le contenu du mot mémoire 6 +4 adressé par T + 2 * sizeof(int), c’est-à-dire habituellement T + 2*4 = 2 + 8. 9 +8 2 +12 En MIPS, on peut charger l’adresse d’un registre, i.e. créer une sorte de pointeur. lw $t3, ($t0) # les ( ) signifient qu’on ce qui est pointé par t0, c’est de l’adressage indirect Nous allons donc transcrire nos pointeurs C en assembleur, comme suit : int arr[] = {3, 6, 9, 2} ; .globl –start int *a = arr ; --start int sum = 0; move $t2, $0 # t2 = sum = 0 while(i