Sie sind auf Seite 1von 30

Structure d’un programme Entrées/Sorties Sous-programmes

Structure d’un programme


Structure d’un programme Entrées/Sorties Sous-programmes

Programme en Assembleur

programme en assembleur = fichier texte (extension .asm)


organisé en plusieurs SECTIONS (= segments)
sections différentes pour les données et le code
directives pour NASM 6= instructions pour le processeur
une seule instruction par ligne, séparateur = chgt de ligne
1 ligne de code = 4 champs (certains optionnels) :
étiquette : instruction opérandes ; commentaire
Structure d’un programme Entrées/Sorties Sous-programmes

Sections de données

B Données initialisées : SECTION .data


déclarer des données initialisées avec la directive : dX
X = b (1 octet), w (2 octets) ou d (4 octets = 1 mot).
exemples :
l1: db 0x55 ; l’octet 0x55
db 0x55,0x66,0x77 ; 3 octets successifs
dw 0x1234 ; 0x34 0x12 (little endian)
dw ’a’ ; 0x61 0x00
l2: dw ’ab’ ; 0x61 0x62 (caractères)
l3: dw ’abc’ ; 0x61 0x62 0x63 0x00 (string)
définir des constantes non modifiables avec la directive : equ
exemple : nb lettres : equ 26
Structure d’un programme Entrées/Sorties Sous-programmes

Sections de données - suite

répéter une déclaration avec la directive : times


exemples :
p0: times 100 db 0 ; 100 fois l’octet 0x00
p1: times 28 dd 0xffffffff ; 100 fois le m^
eme mot

B Données non initialisées : SECTION .bss


déclarer des données non initialisées avec la directive : resX
X = b (1 octet), w (2 octets) ou d (4 octets = 1 mot).
exemples (étiquettes obligatoires) :
input1: resb 100 ; réserve 100 octets
input2: resw 1 ; réserve 2 octets
input3: resd 1 ; réserve 1 mot (4 octets)
Structure d’un programme Entrées/Sorties Sous-programmes

Section de code

B Corps du programme : SECTION .text


commencer par déclarer global l’étiquette de début de
programme (main) pour qu’elle soit visible :
SECTION .text
global main

main:
...

fin de fichier :
mov ebx, 0 ; code de sortie, 0 = normal
mov eax, 1 ; numéro de la commande exit
int 0x80 ; interruption 80 hex, appel au noyau
Structure d’un programme Entrées/Sorties Sous-programmes

Fichier squelette
%include "asm_io.inc"
SECTION .data
; données initialisées
;

SECTION .bss
; données non initialisées
;

SECTION .text
global main ; rend l’étiquette visible de l’extérieur

main:
; programmme
;
mov ebx,0 ; code de sortie, 0 = normal
mov eax,1 ; numéro de la commande exit
int 0x80 ; interruption 80 hex, appel au noyau
Structure d’un programme Entrées/Sorties Sous-programmes

Assembler un programme
Assemblage : créer un fichier objet (transformer le programme
écrit en langage d’assemblage en instructions machine)
nasm -g -f <format> <fichier> [-o <sortie>]
Exemples :
nasm -g -f coff toto.asm
nasm -g -f elf toto.asm -o toto.o
Produire un listing des instructions machine :
nasm -g -f elf toto.asm -l toto.lst
Édition de lien :
ld -e main toto.o -o toto
En utilisant des bibliothèques C ou écrites en C (par exemple,
asm io de P. Carter, pour les E/S) :
nasm -g -f elf toto.asm
gcc toto.o asm_io.o -o toto
Structure d’un programme Entrées/Sorties Sous-programmes

Entrées/Sorties
Structure d’un programme Entrées/Sorties Sous-programmes

Interruptions

Le flot ordinaire d’un programme doit pouvoir être interrompu


pour traiter des évènements nécessitant une réponse rapide.
Mécanisme d’interruptions (ex : lorsque la souris est déplacée, le
programme en cours est interrompu pour gérer ce déplacement).
Passage du contrôle à un gestionnaire d’interruptions.
Certaine interruptions sont externes (ex : la souris).
D’autres sont soulevées par le processeur, à cause d’une erreur
(traps) ou d’une instruction spécifique (interruption logicielle).
En général, le gestionnaire d’interruptions redonne le contrôle au
programme interrompu, une fois l’interrupion traitée.
Il restaure tous les registres (sauf eax).
Le programme interrompu s’exécute comme si rien n’était arrivé.
Les traps arrêtent généralement le programme.
Structure d’un programme Entrées/Sorties Sous-programmes

Entrées/sorties
Entrées-sorties (I/O) : échanges d’informations entre le
processeur et les périphériques.
Entrées : données envoyées par un périphérique (disque, réseau,
clavier...) à destination de l’unité centrale.
Sorties : données émises par l’unité centrale à destination d’un
périphérique (disque, réseau, écran...).

Gestion par interruptions :


permet de réagir rapidement à un changement en entrée.
le périphérique prévient le processeur par une interruption,
le processeur interrompt la tâche en cours, effectue l’action
prévue pour cette interruption et reprend l’exécution du
programme principal là où il l’avait laissée.

Gestion “haut-niveau” :
Bibliothèques standards en C pour les E/S (pas en assembleur).
MAIS, les conventions d’appels utilisées par C sont complexes...
Structure d’un programme Entrées/Sorties Sous-programmes

Affichage par interruption

SECTION .data
msg1:db "message 1",10
lg1: equ $-msg1

SECTION .text
global main

main:
...
mov edx, lg1
mov ecx, msg1
mov ebx, 1 ; stdout
mov eax, 4 ; write
int 0x80
...
Structure d’un programme Entrées/Sorties Sous-programmes

Sous-programmes
Structure d’un programme Entrées/Sorties Sous-programmes

Sous-programmes

Les sous-programmes servent à mutualiser du code (éviter les


copier-coller)
Exemple : les fonctions des langages haut niveau.
Le code appelant le sous-programme et le sous-programme
lui-même doivent se mettre d’accord sur la façon de se passer
les données (conventions d’appel).

Un saut peut être utilisé pour appeler le sous-programme, mais


le retour pose problème.
Le sous-programme peut être utilisé par différentes parties du
programme : il doit revenir là ou il a été appelé.
Donc, le retour du sous-programme ne peut pas être codé “en
dur” par un saut vers une étiquette.
L’étiquette de retour doit être un paramètre du sous-programme.
Structure d’un programme Entrées/Sorties Sous-programmes

Sans sous-programme

%include "asm_io.inc" cmp eax, 10


jl ok
SECTION .data
msg1: db "entier <10 ?",10,0 rate: mov eax, msg3
msg2: db "bravo",10,0 call print_string
msg3: db "perdu",10,0 jmp fin

SECTION .text ok: mov eax, msg2


global main call print_string

main: mov eax, msg1 fin: mov ebx, 0


call print_string mov eax, 1
int 0x80
call read_int
Structure d’un programme Entrées/Sorties Sous-programmes

Sous-programme “à la main”

%include "asm_io.inc" mov ecx, ret_sb


jmp sb
SECTION .data ret_sb: call print_string
msg1: db "entier <10 ?",10,0
msg2: db "bravo",10,0 fin: mov ebx, 0
msg3: db "perdu",10,0 mov eax, 1
int 0x80
SECTION .text
global main sb: cmp eax, 10
jl ok
main: mov eax, msg1 rate: mov eax, msg3
call print_string jmp ecx
ok: mov eax, msg2
call read_int jmp ecx
Structure d’un programme Entrées/Sorties Sous-programmes

Instructions call et ret


Problèmes :
Un peu compliqué
Besoin d’autant d’étiquettes que d’appels du sous-programme.
Solution : call et ret
L’instruction call effectue un saut inconditionnel vers un sous-
programme après avoir empilé l’adresse de l’instruction suivante
l1 : call fonction push l2
l2 : ... jmp fonction
L’instruction ret dépile une adresse et saute à cette adresse :
ret pop reg.
jmp reg.
Lors de l’utilisation de ces instructions, il est très important de
gérer la pile correctement (dépiler tout ce qu’on a empilé) afin
que l’adresse dépilée par l’instruction ret soit correcte.
Permet d’imbriquer des appels de sous-programmes facilement.
Structure d’un programme Entrées/Sorties Sous-programmes

Sous-programme avec call et ret

%include "asm_io.inc" call sb


call print_string
SECTION .data
msg1: db "entier <10 ?",10,0 fin: mov ebx, 0
msg2: db "bravo",10,0 mov eax, 1
msg3: db "perdu",10,0 int 0x80

SECTION .text sb: cmp eax, 10


global main jl ok
rate: mov eax, msg3
main: mov eax, msg1 ret
call print_string ok: mov eax, msg2
ret
call read_int
Structure d’un programme Entrées/Sorties Sous-programmes

Passage des paramètres

! ! Il est très important de dépiler toute donnée qui a été


empilée dans le corps du sous-programme.
Exemple :
plus2: add eax, 2
push eax
ret ; dépile la valeur de eax !!
Ce code ne reviendra pas correctement !

Autre problème : passage des paramètres par registres


limite le nombre de paramètres ;
mobilise les registres pour les conserver.
Solution : passer les paramètres par la pile (convention de C).
Structure d’un programme Entrées/Sorties Sous-programmes

Passer les paramètres par la pile


Les paramètres passés par la pile sont empilés avant le call.
Si le paramètre doit être modifié par le sous-programme, c’est
son adresse qui doit être passée.
Les paramètres sur la pile ne sont pas dépilés par le
sous-programme mais accédés depuis la pile elle-même :
Sinon, comme ils sont empilés avant le call, l’adresse de retour
devrait être dépilée avant tout (puis ré-empilée ensuite).
Si les paramètres sont utilisés à plusieurs endroits :
B ça évite de les conserver dans un registre ;
B les laisser sur la pile permet de conserver une copie de la
donnée en mémoire accessible à n’importe quel moment.
Lors d’un appel de sous-programme : la pile ressemble à
...
esp adresse de retour
esp+4 paramètre
Accès au paramètre par adressage indirect : [esp+4]
Structure d’un programme Entrées/Sorties Sous-programmes

Passer les paramètres par la pile - suite


Si la pile est également utilisée dans le sous-programme pour
stocker des données, le nombre à ajouter à esp change.
Par exemple, après un push la pile ressemble à :
...
esp donnée du sous-programme
esp+4 adresse de retour
esp+8 paramètre
Maintenant, le paramètre est en esp+8, et non plus en esp+4.
esp pour faire référence aux paramètres ⇒ erreurs possibles.

Pour résoudre ce problème, utiliser ebp (base de la pile).


La convention de C est qu’un sous-programme commence par
empiler la valeur de ebp puis affecte à ebp la valeur de esp.
Permet à esp de changer sans modifier ebp.
A la fin du programme, la valeur originale de ebp est restaurée.
Structure d’un programme Entrées/Sorties Sous-programmes

Passer les paramètres par la pile - suite

Forme générale d’un sous-programme qui suit ces conventions :


etiquette sousprogramme:
push ebp ; empile la valeur originale de ebp
mov ebp, esp ; ebp = esp
; code du sous-programme
pop ebp ; restaure l’ancienne valeur de ebp
ret
La pile au début du code du sous-programme ressemble à
...
... esp donnée
esp ebp ebp prec. esp+4 ebp ebp prec.
esp+4 ebp+4 adr. de retour esp+8 ebp+4 adr. de retour
esp+8 ebp+8 paramètre esp+12 ebp+8 paramètre

Le paramètre est accessible avec [ebp+8] depuis n’importe quel


endroit du sous-programme.
Structure d’un programme Entrées/Sorties Sous-programmes

Passer les paramètres par la pile - fin

Une fois le sous-programme terminé, les paramètres qui ont


été empilés doivent être retirés.
La convention d’appel C spécifie que c’est au code appelant
de le faire (convention différente en Pascal, par exemple).
Un sous-programme utilisant cette convention peut être
appelé comme suit :
push param ; passe un paramètre
call fnc
add esp, 4 ; retire le paramètre de la pile
La 3ème ligne retire le paramètre de la pile en manipulant
directement l’adresse du sommet de la pile.
Une instruction pop pourrait également être utilisée, mais
le paramètre n’a pas besoin d’être stocké dans un registre.
Structure d’un programme Entrées/Sorties Sous-programmes

Exemple de passage de paramètre par la pile


%include "asm_io.inc" call print_string

SECTION .data fin: mov ebx,0


msg1: db "entier <10 ?",10,0 mov eax,1
msg2: db "bravo",10,0 int 0x80
msg3: db "perdu",10,0
sb: push ebp
SECTION .text mov ebp, esp
global main cmp dword [ebp+8], 10
main: mov eax,msg1 jl ok
call print_string rate: mov eax, msg3
call read_int pop ebp
ret
push eax ok: mov eax, msg2
call sb pop ebp
add esp, 4 ret
Structure d’un programme Entrées/Sorties Sous-programmes

Rappel

Forme générale d’un sous-programme (fonction) :


etiquette sousprogramme:
push ebp ; empile la valeur originale de ebp
mov ebp, esp ; ebp = esp
; code du sous-programme
pop ebp ; restaure l’ancienne valeur de ebp
ret
Un sous-programme peut être appelé comme suit :
push param ; passe un paramètre
call fnc ; appelle la fonction fnc
add esp, 4 ; retire le paramètre de la pile
Le dernier paramètre empilé est désigné par [ebp+8] dans fnc
L’avant dernier paramètre par [ebp+12], etc...
Structure d’un programme Entrées/Sorties Sous-programmes

Afficher des rayures


On souhaite écrire un programme qui affiche k rayures
verticales sur n lignes. Les rayures doivent être dessinées en
utilisant un caractère choisi par l’utilisateur.
Structure d’un programme Entrées/Sorties Sous-programmes

rayures.asm
%include "asm_io.inc" mov eax, msg2 ligne:
call print_string push ebp
SECTION .data call read_int mov ebp, esp
mov [nbl], eax
msg1:db "nb bandes?",10,0 push ecx
msg2:db "nb lignes?",10,0 mov eax,msg3
msg3:db "caractere?",10,0 call print_string mov ecx, [ebp+8]
blanc: db " ",0 call read_char
call read_char ; !! sv: mov eax, [ebp+12]
SECTION .bss mov ecx, [nbl] call print_char
mov eax, blanc
nbb: resd 1 push eax call print_string
nbl: resd 1 push dword [nbb] loop sv
nxt:call ligne
SECTION .text loop nxt call print_nl
global main add esp, 4
pop eax pop ecx
main:
mov eax, msg1 pop ebp
call print_string fin:mov ebx, 0 ret
call read_int mov eax, 1
mov [nbb], eax int 0x80
Structure d’un programme Entrées/Sorties Sous-programmes

Écrire une fonction récursive

B pas de différence avec une fonction “classique”


MAIS :
Bien respecter les règles de préservation de la pile
est crucial lors de l’écriture d’une fonction récursive !

B Exemple de fonction récursive simple : calculer la somme des


entiers de 1 à n.
En C :

int somme_rec (int n){


if (n = 1)
return 1;
else
return n + somme_rec(n-1);
}
Structure d’un programme Entrées/Sorties Sous-programmes

somme rec.asm

%include "asm_io.inc" push ebx somme:push ebp


call somme mov ebp, esp
SECTION .data add esp, 4
msg1: db "Un entier?",10,0 mov ebx, [ebp+8]
msg2: db 10,"Somme: ",0 call print_int cmp ebx, 1
call print_nl je stop
SECTION .text
global main fin: mov ebx, 0 dec ebx
mov eax, 1 push ebx
main: int 0x80 call somme
mov eax, msg1 add esp, 4
call print_string
call read_int add eax, [ebp+8]
jmp fin_somme
mov ebx, eax
stop: mov eax, 1
mov eax, msg2
call print_string fin_somme:
pop ebp
ret
Structure d’un programme Entrées/Sorties Sous-programmes

Évolution de la pile

s
b
retour
s 1 1
b s
b
retour retour retour
s 2 2 2 2
s b s
b b
s retour retour retour retour retour retour retour
s 3 3 3 3 3 3 3 3
b b s
### ### ### ### ### ### ### ### b ###
avant le 1er call push ebp avant le 2e avant le 3e fin de la
appel à somme_rec mov ebp, esp appel à appel à récursion
somme_rec somme_rec somme_rec 1 1+2=3 3+3=6
eax eax eax

B Une procédure non récursive aurait-elle été plus efficace ?


Structure d’un programme Entrées/Sorties Sous-programmes

Les variables locales...


La pile peut être utilisée pour stocker des variables locales.
Permet d’écrire des sous-programmes ré-entrants
⇒ pas de registres, ni d’adresse fixe.
Utiliser la pile pour les variables économise de la mémoire (par
opposition aux variables globales) et les registres.
Les variables locales sont stockées immédiatement après la
valeur de ebp sauvegardée dans la pile.
Allocation : soustraire de esp le nombre d’octets requis.
fct: push ebp ; empile la valeur originale de ebp
mov ebp, esp ; ebp = esp
sub esp, ??
...
mov esp, ebp ; désalloue les variables locales
pop ebp ; restaure la valeur de ebp
ret
On utilise ebp pour localiser les variables locales.

Das könnte Ihnen auch gefallen