Cours gratuits » Cours informatique » Cours programmation » Cours Assembleur » Apprendre l'assembleur INTEL DOS 16 bits en PDF

Apprendre l'assembleur INTEL DOS 16 bits en PDF


Télécharger



★★★★★★★★★★3.5 étoiles sur 5 basé sur 1 votes.
Votez ce document:

Programmation Assembleur NASM

                                                                                                                                                                      Resum´ e´

1

Contents

1    Les bases de programmation en NASM X86          3

1.1    Ce dont vous avez besoin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .           3

1.1.1     Tel´ echarger le compilateur´              . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .               3

1.1.2     Compiler sous Linux

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .   3

1.1.3     Compiler sous Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .                3

1.2    Structure d’une programme NASM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .     4

1.2.1     Une ligne de code NASM     . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .             4

1.2.2     Partitionnement du programme . . . . . . . . . . . . . . . . . . . . . . . . . . . . .          4

1.2.3     Definir un point de d´           epart et sortir du programme (Linux)´            . . . . . . . . . . . . . .   4

1.3    Definir des donn´      ees´        . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .              5

1.3.1     Les tailles des donnees´       . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .           5

1.3.2     Tout est une adresse (ou presque) . . . . . . . . . . . . . . . . . . . . . . . . . . .          5

1.3.3     Les variables initialises´        . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .           5

1.3.4     Les variables non-initialisees´             . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

1.3.5     Les constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .      6

1.4    Les registres               . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .     7

1.4.1     Les registres gen´  eraux´    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .           7

1.4.2     Les registres de controleˆ    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .             8

1.4.3     Les registres de segment     . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .             8

1.5    Precisions sur les instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .´       9

1.5.1     Toujours utiliser au moins UN registre             . . . . . . . . . . . . . . . . . . . . . . . . .            9

1.5.2     La division entiere (`             div,idiv) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

1.5.3     La multiplication entiere (`  mul,imul)               . . . . . . . . . . . . . . . . . . . . . . . . . .          9

1.5.4     Le fonctionnement de la pile              . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

1.6    Les instructions de controle de flotˆ      . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .               11

1.6.1     Les labels et les sauts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .   11

1.6.2     Le bloc de boucle label loop label . . . . . . . . . . . . . . . . . . . . . .  12

1.6.3     Le bloc de procedure´          label ret . . . . . . . . . . . . . . . . . . . . . . . . .       12

1.6.4     Le bloc d’execution conditionnel´      cmp jcc labelSi . . . . . . . . . . . . . . . . . .               12

1.7    Les fonctions et procedures en NASM . . . . . . . . . . . . . . . . . . . . . . . . . . . . .´   13

1.7.1     L’effet d’une fonction sur la pile        . . . . . . . . . . . . . . . . . . . . . . . . . . . . .   13

1.7.2     Le passage de parametre sans sauvegarde de pile . . . . . . . . . . . . . . . . . .` 13

1.7.3     Le passage de parametre avec sauvegarde de pile . . . . . . . . . . . . . . . . . .` 14

1           Les bases de programmation en NASM X86

NASM est un compilateur et un langage assembleur X86 libre et modulaire supportant une grande variet´ e de formats d’objets.´ Les parties suivantes resument bri´ evement les bases de la program-` mation en langage NASM et sont extraites du manuel de NASM ( ) que je vous incite a lire pour plus de d` etails.´

1.1        Ce dont vous avez besoin

Pour creer un programme assembleur NASM, il vous faut :´

•    Le compilateur NASM

•    Un editeur de liens´

Le compilateur cree´ a partir de votre fichier un fichier objet (monProgramme.o)` similairement aux compilateurs C et C++. L’editeur de liens peut ensuite cr´ eer ensuite un ex´ ecutable´ pour le systeme voulu (en g` en´ eral le syst´ eme sur lequel il est ex` ecut´ e) en interfac¸ant votre programme´ avec le systeme.`

1.1.1       Tel´ echarger le compilateur´

De nombreuses distributions Linux proposent NASM directement dans leur gestionnaire de paquets. Pour les autres systemes, vous pouvez t` el´ echarger le compilateur sur le site´ , dans la section ”download”. Apres installation ou copie des fichiers, vous pouvez compiler via un` terminal ( pour Windows). Si nasm n’est pas trouve par d´ efaut, v´ erifiez que votre variable´

d’environnement PATH contient le chemin absolu du dossier contenant l’executable nasm.´ Pour compiler un fichier source .asm, NASM s’utilise de la fac¸on suivante :

nasm -f <format> [-g]

L’option -f permet de specifier le format de fichier compil´ e et´ -g permet de gen´ erer les symboles de´ debug. Le fichier gen´ er´ e peut ensuite´ etre utilisˆ e dans un´ editeur de liens.´

1.1.2         Compiler sous Linux

Pour creer un programme NASM pour les syst´ emes Linux r` ecents, vous pouvez compiler votre pro-´ gramme au format ELF (”Executable and Linkable Format”) et utiliser l’editeur de liens´ ld :

nasm -f elf -g

ld [-m elf_i386] monProgramme.o -o monExecutable

Pour executer les exemples pr´ esent dans ce document (´ ecrits en assembleur X86), il est n´ ecessaire´ de passer l’option -m elf i386 a ld si vous travaillez sur un systeme 64 bits et que vous compiler en` format ELF (qui par defaut est en 32 bits). Vous pouvez compiler votre programme au format´           elf64, mais attention : la creation d’un programme 64bits (X86´ 64) est differente de celle d’un programme´ 32bits (X86).

Si vous desirez utiliser des fonctions issus du langage C tel que´        printf, vous devez utiliser gcc comme editeur de liens et respecter certaines conventions d’appels pour le passage de param´      etres :` gcc monProgramme.o -o monExecutable

1.1.3         Compiler sous Windows

Pour creer un programme sous Windows, si vous poss´ edez Visual Studio, vous pouvez utiliser le linker´ fournis par microsoft . Par exemple, pour creer le programme HelloWorld´ a partir du fichier` compiler , vous pouvez utiliser la ligne de commande suivante sous Windows :

nasm -f win32

link /SUBSYSTEM:CONSOLE /ENTRY:_start Si vous utilisez minGW ou cygwin, vous pouvez utiliser l’executable ld au lieu de link.´

1.2        Structure d’une programme NASM

1.2.1         Une ligne de code NASM

label: instruction                       operandes ; commentaires´

Le symbole ; correspond aux // en Langage C, pour signaler le debut d’une ligne de commentaires.´ La presence d’un label (autrement appel´ e´ etiquette), est facultative devant une instruction, et sera´ principalement consider´ e dans ce document :´

•    comme debut d’un bloc de code (´       nomBloc:)

•    comme nom de variable (nomVariable[:] <type> <donnée>).

1.2.2         Partitionnement du programme

Un programme assembleur est en gen´ eral s´ epar´ e en plusieurs parties (appel´ ees segments).´ Trois segments sont consider´ es comme standard en assembleur X86 :´ .data, .bss et .text.

•    Le segment .data contient les definitions des variables initialis´ ees´ a une ou plusieurs valeurs` specifi´ ees (instructions´ db, dw, dd ).

•    Le segment .bss contient les definitions des variables non-initialis´ ees, c’est´ a dire uniquement` allouees en m´ emoire. (instructions´ resb, resw, resd ).

•    Le segment .text contient le code execut´          e par votre programme.´

En plus de ces parties, le programmeur a acc` es via des instructions` push et pop a une pile m` emoire li´ e´ au programme (Stack en anglais), souvent utilisee pour le passage de param´ etres` a des fonctions ou` sauvegarder des donnees de registres avant leur utilisations (section 1.5.4).´

1.2.3       Definir un point de d´      epart et sortir du programme (Linux)´

Le point d’entree de votre programme ne d´ epend actuellement pas de NASM, mais de l’´ editeur de liens´ (ou ”Linker”) utilise pour transformer le code compil´ e en ex´ ecutable. Pour beaucoup de linker, le point´ d’entree par d´ efaut est une fonction´ start defini comme un symbole´ global : ce symbole sera exporte par votre programme pour´ etre utilisable par le systˆ eme ou par d’autres fichiers compil` es lors de´ l’edition.´

Si une fonction (ou une variable) identifiee par un label est´ egalement d´ eclar´ e comme global dans le´ fichier ou elle est d` efinie, cette fonction (ou variable) peut´ etre appelˆ ee dans un autre fichier,´ a la condi-` tion que son label soit egalement d´ eclar´ e en´ extern dans le fichier voulant l’utiliser.

La programme suivant presente la structure´ el´ ementaire d’un programme NASM, dans laquelle sont´ incluses les instructions pour quitter un programme proprement :

global _start ; declaration de _start en global´

                                               ; => export du point d’entree pour cr´           eer le programme´

segment .data

; creation de variables initialis´                ees´ segment .bss

; creation de variables non-initialis´        ees´ segment .text

; creation de vos proc´ edures/fonctions entre le segment et le point d’entr´              ee´ _start:

; instructions de votre programme

; Les 3 lignes suivantes doivent etreˆ     a la fin de votre _start` mov eax,1   ; signalement de fin de programme mov ebx,0    ; code de sortie du programme int 0x80        ; interruption Linux : votre programme rend la main au systeme`

1.3      Definir des donn´ ees´

1.3.1        Les tailles des donnees´

NASM consiere les types de donn`      ee suivants :´

•    le Byte ( 1 octet = 8 bits)

•    le Word ( 2 octet = 16 bits)

•    le Double word ( 2 word = 4 octets = 32 bits)

•    le Quad word (4 word = 8 octets = 64 bits)

•    et le exTended word ( type special sur 80 octets pour les r´           eels)´

Les lettres majuscules de cette liste sont utilisees pour d´ efinir le type de la variable d´ eclar´ ee. Cette lettre´ est associee´ a un pr` efixe pour pr´ eciser au syst´ eme si la variable doit` etre initialisˆ ee (´ d pour ”define”) ou juste allouee en m´ emoire (´ res pour ”reserve”).

1.3.2          Tout est une adresse (ou presque)

Une variable NASM se comporte comme un pointeur en Langage C : la variable est une adresse memoire qui pointe vers un tableau de un ou plusieurs´             el´ ements. Chaque donn´ ee est accessible par´

der´ ef´ erencement du pointeur ou d’une adresse obtenue par d´    ecalage depuis celui-ci.´

Plus gen´ eralement, pour acc´ eder´ a la valeur d’une emplacement m` emoire point´ e par une adresse,´ on utilise l’operateur de d´ er´ ef´ erencement´ [ ]. Par exemple, pour acceder au contenu de la vari-´ able/adresse var, on utilisera la notation [var] en NASM, equivalente´ a la notation (` *var) en langage C et C++.

Toute el´ ement d´                              eclar´    e par le programmeur en assembleur est en r´           egle g`    en´ erale une adresse,´ sauf exceptions des constantes.

1.3.3         Les variables initialises´

Lors de la declaration d’une variable initialis´ ee, l’op´ erande de droite est la valeur d’initialisation, c’est´ a` dire le contenu de la variable :

var1 db 123 ; "Define Byte" [var1] = 123, sizeof([var1]) = 8 bits var2 dw 456 ; "Define Word" [var2] = 456, sizeof([var2]) = 16 bits var3 dd 789 ; "Define Double" [var3] = 789, sizeof([var3]) = 32 bits var4 dq 123 ; "Define Quad" [var4] = 123, sizeof([var4]) = 64 bits var5 dt 1.23; "Define exTended word" [var4] = 1.23, sizeof([var5]) = 80 bits

Une variable est une adresse qui ref´ erence un tableau de un´ el´ ement, ou plus si plusieurs valeurs´ (separ´ ees d’une virgule) ou une cha´ ˆ?ne de caracteres sont utilis` ees comme op´ erandes. L’instruction´ times n peut etre utilisˆ ee entre le nom de la variable et l’instruction pour r´         ep´ eter la d´ efinition d’une´ valeur particuliere dans la variable :`

var1 db 123,12,3 ; [var1] = 123, [var1+1] = 12, [var1+2] = 3 var2 db ’Hi’,0xa ; [var2] = ’H’, [var2+1] = ’i’, [var2+2] = ’\n’ = 0xa var3 dd 255,6554 ; [var3] = 255, [var3+1] = 6554 var4 times 4 db ’*’ ; var4 = "****"

Les 3 premieres lignes peuvent se traduire en langage C par :`

char var1[3] = {123,12,3}; char var1[3] = "Hi\n"; int var3[2] = {255,6554};

1.3.4         Les variables non-initialisees´

Les instructions de declarations de variables non-initialis´ ees allouent un tableau dont le nombre d’´ el´ ement´ est definis par l’op´ erande de droite :´

var1 resb 1024 ; "Reserve 1024 Bytes" var2 resw 1 ;   "Reserve 1 Word" var3 resd 12 ;       "Reserve 12 Double" var3 resq 24 ; "Reserve 24 Quad" var4 rest 2;              "Reserve 2 exTended word"

Dans cet exemple, var1 est l’adresse du premier el´ ement d’un tableau de 1024 octets.´

ATTENTION, la memoire est allou´  e mais non-initialis´            e : l’emplacement allou´    e peut contenir des´ valeurs completement al`               eatoires.´

1.3.5        Les constantes

Pour definir une constante, on utilise l’instruction´ equ (pour ”equal”) prec´ ed´ ee d’un label et suivi de la´ valeur de la constante : myConstante equ 12345

ATTENTION, une constante n’est pas une variable et, similairement aux registres, n’a pas d’adresse a proprement parler : une constante est directement interpr`      et´ ee comme une valeur.´

1.4       Les registres

Le processeur ne peut pas directement realiser une instruction entre deux variables : Les sorties de ses´ unites de calcul ne sont pas directement connect´ e´ a la m` emoire centrale dans laquelle les variables sont´ normalement stockes. Il doit alors utiliser ses propres emplacements m´ emoires avant de demander´ le transfere du r` esultat d’une instruction dans votre m´ emoire centrale. Ces emplacements m´ emoire,´ appeles ”registres”, contiennent des informations sur l’´ etat courant du processeur et sont utilis´ es par´ celui-ci pour realiser des instructions et sauvegarder le r´ esultat. Pour acc´ eder au contenu des registres,´ aucun der´ ef´ erencement n’est n´ ecessaire : le nom d’un registre sert d’alias permettant de r´              ecup´ erer ou´ modifier la valeur qu’il contient (similairement au fonctionnement des constantes). Par exemple, pour transferer le contenu du registre EAX dans une variable´ var, on peut simplement faire l’instruction : mov [var], eax

En assembleur, differents types de registres processeur sont utilisables. : les registres g´           en´ eraux,´ les registres de controle et les registres de segment.ˆ

1.4.1       Les registres gen´ eraux´

Pour la plupart des registres gen´ eraux, il est possible d’acc´ eder aux registres en mode 32bits (pr´ efixe´ E) ou 16bits. Si vous utilisez un systeme 64 bits et que vous compiler en` elf64, ces registres ont egalement un mode 64bits (pr´ efixe R). Pour les 4 registres de stockage principaux (AX, BX, CX et DX)´ il est en plus possible d’acceder directement aux parties hautes et basses des 16 bits de poids faibles´ (les 8 bits de poids fort et les 8 bits de poids faible).

La liste suivante presente les alias 64, 32 et 16 bits des registres g´ en´ eraux ainsi que leur utilit´ e par´ defaut pour le processeur. les pr´ efixe R et E et les alias [octet fort:octet faible] des 2 octets de poids´ faibles sont signales seulement si ces modes d’acc´ es sont disponibles pour le registre en question :`

•    (R,E)AX - [AH:AL] : accumulateur, stockage du retour d’une fonction ou d’un appel systeme.`

•    (R,E)BX - [BH:BL] : decalage dans le segment des donn´  ees point´              e par le registre´  DS.

•    (R,E)CX - [CH:CL] : compteur de boucle (instruction loop).

•    (R,E)DX - [DH:DL] : registre de donnees, utilis´ e lors des op´ erations d’entr´ ees/sorties et pour´ certains calculs longs (instructions div et mul).

•    (R,E)SI : Source Index : pointeur ”source” pour les operations sur des cha´ ˆ?nes de caracteres.`

•    (R,E)DI : Destination Index : pointeur ”destination” pour les operations sur des cha´               ˆ?nes de caracteres.`

•    (R,E)BP : Base Pointer : Pointeur du debut de la pile m´   emoire du programme.´

•    (R,E)SP : Stack Pointer : Pointeur de la position actuelle de la pile.

En plus de leur utilite list´ ee ci-dessus, les 4 registres´ **X sont egalement les registres de stockage´ a utiliser pour les op` erations arithm´ etiques.´ Les registres de type ”Pointer” ne contiennent pas une adresse ”absolue” en memoire, mais une adresse relative´ a un` segment du programme. Il est cependant possible de recalculer une telle adresse en utilisant les registres dit de ”segments”, qui conservent l’adresse de depart de chaque segment du programme.´

1.4.2         Les registres de controleˆ

Pour controler l’ordonnancement des instructions lors de l’exˆ                ecution, deux registres sont utilis´   es par le´ processeur :

•    (E)IP : Instruction Pointer : Registre stockant l’adresse de la prochaine instruction a ex` ecuter´ (relatif au segment de code).

•    (E)FLAGS : registre contenant des valeurs binaires (flag) utilisees dans certaines instructions.´

Ce dernier registre est decoup´ e en plusieurs bits de contr´ ole situˆ es aux positions suivantes dans le´ registre (du poids faible au poids fort) :

•    bit 0 : CF (Carry flag) : si une operation arithm´ etique ou de d´ ecalage g´ en´ ere une retenue, ce bit` correspond a la valeur de la retenue`

•    bit 2 : PF (Parity flag) : nombre de bits a 1 dans le r` esultats d’une op´ eration arithm´ etique (1 si´ nombre de bit impair).

•    bit 4 : AF (Auxiliary Carry Flag) : contient la retenue du bit 3 au bit 4 specifiquement apr´ es une` operation arithm´ etique sur un octet.´

•    bit 6 : ZF (Zero Flag) : apres une op` eration arithm´ etique ou de comparaison, ce bit est´ a 1 si le` resultat est´ egale´ a 0.`

•    bit 7 : SF (Sign Flag) : Donne le signe d’une operation arithm´ etique (0 pour positif, 1 pour n´ egatif).´ • bit 8 : TF (Trap Flag) : permet le passage du processeur en mode pas-a-pas (mode debug).`

•    bit 9 : IF (Interrupt Flag) : si ce bit est a 0, tout signal d’interruption envoyer par une entr` ee (clavier´ ou autre) est ignore.´

•    bit 10 : DF (Direction Flag) : direction du curseur pour les operation sur les cha´ ˆ?nes (si 0, de gauche a droite, sinon de droite`         a gauche).`

•    bit 11 : OF (Overflow Flag) : indique si une erreur de depassement de capacit´ e´ a eu lieu apr` es` une operation arithm´ etique sign´ ee.´

1.4.3         Les registres de segment

Certains registres present´ es dans les sections pr´ ec´ edentes servent´ a conserver des d` ecalages m´ emoire´ (adresses relatives) par rapport a des adresses conserv` ees dans d’autres registres. Ces derniers sont´ les registres de segment, dont voici la liste et leur utilite :´

•    CS : Code Segment : ce registre contient l’adresse de depart du segment de code (plus ou moins´ la partie .text du programme), contenant toutes les instructions a ex` ecuter.´

•    DS : Data Segment : ce registre contient l’adresse de depart du segment contenant les donn´ ees´ et les constantes (parties .bss et .data).

•    SS : Stack Segment : ce registre contient l’adresse de depart de la pile du programme (qui´ contient des donnees et´ egalement les adresses de retour des fonctions).´

•    ES, FS et GS sont des registres de segment supplementaires (ES peut par exemple´ etre utiliserˆ avec EDI (ES:EDI) pour manipuler des adresses situees plus loin dans l’espace m´ emoire).´

1.5        Precisions sur les instructions´

Cette partie a pour but d’apporter des precisions suppl´ ementaires sur certaines instructions pr´ esent´ ees´ dans la quickcard x86 pour NASM proposes par l’Universit´ e de Nantes.´

1.5.1          Toujours utiliser au moins UN registre

La majorite des instructions de votre processeur stocke le r´ esultat dans la premi´ ere op` erande (i.e.´ l’operation´ add EAX,2 stocke le resultat de l’op´ eration dans EAX) ou directement dans un registre´ precis.´ Pour des operations arithm´ etiques, le processeur ne peut pas directement lire et´  ecrire un´ meme emplacement mˆ emoire : il peut d´ eplacer des donn´ ees depuis celui-ci ou vers celui-ci (l’instruction´ mov) respectivement vers ou depuis un registre, mais il ne peut pas directement executer un calcul´ sur l’emplacement. Par extension, lorsque plusieurs operandes sont requises dans une op´ eration´ arithmetiques, la premi´ ere op` erande doit´ etre un registre (de prˆ ef´ erence un registre de stockage´ **X) :

add [var1], [var2] ; erreur mov eax, [var1] add eax, [var2]

mov [var1], eax ; [var1] = [var1] + [var2]

1.5.2       La division entiere (`     div,idiv)

Les instructions de divisions entieres sign` ees (´ idiv) et non-signees (´ div) divisent le contenu de EAX par une operande pass´ e en param´ etre de l’instruction. La taille de l’op` erande (le diviseur) va cependant´ definir la taille et le nombre de registre utilis´ es pour l’op´ eration et la sauvegarde du r´ esultat :´

•    Si le diviseur est declarer sur 8 bits, l’op´ eration prendra le registre AX comme dividende.´  Le resultat de la division sera conserv´ e dans AL, et son reste dans AH´

•    Si le diviseur est declarer sur 16 bits, le double registre DX:AX (DX pour les bits de poids forts´ et AX pour ceux de poids faibles) sera utilise comme dividende. Le r´ esultat de la division sera´ conserve dans AX, et le reste dans DX.´

•    Si le diviseur est declarer sur 32 bits, le double registre EDX:EAX sera utilis´               e comme dividende.´ Le resultat de la division sera conserv´ e dans EAX, et le reste dans EDX.´

La division utilisant EDX pour des tailles de donnees sup´ erieur a 8 bits, l’oubli d’une remise´ a z` ero de´ EDX avant l’operation peut mener´ a un r` esultat faux. Il est possible de pr´ eciser directement la taille de´ la division graceˆ a un sp` ecificateur de type (dbyte, dword, ddouble) :´

a: dd 12 ; (dans .data) a sur 32 bits mov eax, 15 ; initialisation du dividende sur 32 bits div byte [a] ; specificateur de taille byte :

; divise ax par le contenu de a transforme (cast) en octet´

1.5.3        La multiplication entiere (`    mul,imul)

Si une seule operande est pass´ ee en param´ etre de l’instruction` mul ou imul, cette instruction multiplie cette operande par :´

•    AL si l’operande est de type byte (ou si byte est sp´         ecifi´       e devant l’op´       erande)´

•    AX si l’operande est de type word (ou si word est sp´      ecifi´       e devant l’op´       erande)´

•    EAX si l’operande est de type double (ou si dword est sp´               ecifi´       e devant l’op´       erande)´

Le resultat est alors respectivement stock´    e dans AX, le double registre DX:AX ou le double registre´ EDX:EAX.

1.5.4         Le fonctionnement de la pile

Comme dit dans la section 1.2.2, chaque programme assembleur a acces` a une structure de pile` memoire, utilis´ ee par certaines instructions pour stocker des donn´ ees. Mais vous pouvez´ egalement´ ajouter et enlever des donnees de cette pile respectivement gr´ ace aux instructionsˆ push and pop. La pile peut etre utilisˆ ee, par exemple, pour sauvegarder la valeur d’un registre avant d’utiliser ce dernier :´

; Etat pile debut : ESP = EBP (le haut et le bas ont la m´         eme adresse)ˆ push eax ; sauvegarde de la valeur de eax en haut de la pile

; Pile : [ESP] = valeur de eax (4 octets), ESP+4 = EBP push ebx ; sauvegarde de la valeur de ebx en haut de la pile

; Pile : [ESP] = valeur de ebx, [ESP+4] = valeur de eax

;

; instructions utilisant eax et ebx

; pop ebx ; depile [ESP] dans ebx et d´            ecale ESP,´

; Etat pile : [ESP] = valeur de eax (4 octets) pop eax ; depile [ESP] dans eax et d´                ecale ESP´

; Etat pile : ESP = EBP (le haut et le bas ont la meme adresse)ˆ

Lorsque vous remplissez la pile, vous pouvez directement acceder au contenu de celle-ci en utilisant les´ registres ESP et EBP. Ces registres contiennent respectivement les adresses du haut de la pile et du bas de la pile. Dans l’exemple prec´ edent, apr´ es les 2 instructions de` push, il est possible de recup´ erer´ respectivement les valeurs de eax et ebx stockees dans la pile gr´ aceˆ a ESP :`

 

; Etat pile debut : ESP = EBP´

push eax

; sauvegarde de la valeur de eax en haut de la pile

; Pile : [ESP] = valeur de eax (4 octets), ESP+4 = EBP

push ebx

; sauvegarde de la valeur de ebx en haut de la pile

; Pile : [ESP] = valeur de ebx, [ESP+4] = valeur de eax

mov ebx,[esp+4] ; copie dans EBX de la valeur de EAX mov eax,[esp]      ; copie dans EAX de l’ancienne valeur de EBX mov ecx,[esp+4] ; copie dans ECX de l’ancienne valeur de EAX

Notez que le decalage par rapport´       a ESP (et`      egalement EBP) descend dans la pile.´

1.6        Les instructions de controle de flotˆ

1.6.1         Les labels et les sauts

Pour controler le flot d’instructions, un label peutˆ etre utilisˆ e pour identifier le d´ ebut d’un bloc d’instruction´ : la plupart des instructions de controle ont besoin d’un point de repˆ ere (un label ou une adresse)` pour se deplacer dans les instructions´ a l’aide de saut. Il existe en assembleur 2 type de sauts : le` saut inconditionnel jmp <adresse ou label> et les sauts conditionnelles. Voici un exemple de saut inconditionnel.

unLabel: ; instructions

                      jmp unLabel ; retourne a la position identifi`                ee par "unLabel"´

Les sauts conditionnelles dependent du r´ esultat de l’instruction´ cmp val1,val2 : cette instruction realise´ l’operation´ val1 - val2 et modifie le registre EFLAGS en consequence (notamment les bits ZF, SF et´ OF). Ces instructions de sauts sont nombreuses mais sont toujours de la forme j<cc> label, ou` <cc> est la condition du test et label est la position a laquelle le flot d’instructions doit reprendre.`

Ce saut conditionnel peut etre ”non-signˆ   e” :´

•    je : jump if val1 is equal to val2 (|val1| = |val2|)

•    ja : jump if val1 is above val2 (|val1| > |val2|)

•    jae : jump if val1 is above or equal to val2 (|val1| >= |val2|)

•    jb : jump if val1 is below val2 (|val1| < |val2|)

•    jbe : jump if val1 is below or equal to val2 (|val1| <= |val2|)

Ou ”signe”:´

•    jg : jump if val1 is greater than val2 (val1 > val2)

•    jge : jump if val1 is greater than or equal val2 (val1 >= val2)

•    jl : jump if val1 is lower than val2 (val1 < val2)

•    jle : jump if val1 is lower than or equal val2 (val1 <= val2)

Il existe egalement des versions pr´      evues pour tester sp´    ecifiquement certains bits de EFLAGS :´

•    jc : jump if CF = 1 (si une retenue a et´ e g´         en´          er´ ee)´

•    jo : jump if OF = 1 (s’il y a eu overflow)

•    jz : jump if ZF = 1 (si le dernier calcul a renvoye 0)´

•    js : jump if SF = 1 (si le resultat du dernier calcul est n´    egatif)´

Pour chacune de ces instructions, il existe un version inverse jncc (par exemple jne pour ”jump if not equal”).

unLabel:

mov cl, [byte1]; contenu de byte1 (1 octet) dans CL cmp cl, [byte2]; comparaison de [byte2] et [byte1] jnz unLabel        ; si cl - [byte2] ne renvoi pas 0, on saute a "unLabel"`

1.6.2                Le bloc de boucle label loop label

Pour creer une boucle similaire´ a une boucle for, il est possible d’utiliser l’instruction`     loop associee´ a` un label definissant le d´ ebut des instructions´ a r` ep´ eter. Lorsque le programme atteint cette instruction,´ il decr´ emente la valeur du registre ECX, et si cette valeur est diff´        erente de 0, le programme retourne au´ label donnee en param´ etre de l’instruction :`

mov ecx, 5 ; pour faire 5 iterations´ ma_boucle:

; mes instructions

loop ma_boucle ; ecx = ecx - 1, si ecx != 0, alors jmp ma_boucle

1.6.3        Le bloc de procedure´          label ret

En NASM, une procedure ou une fonction est simplement un ensemble d’instructions commenc¸ant par´ un label et se terminant par l’instruction ret :

ma_procedure:

; instructions sans push ni pop ret ; equivalent´   a pop eip, ce qui` a pour effet dans ce cas`    a jmp apresLeCall`

; Dans le _start call ma_procedure ; equivalente´        a push eip et jmp ma_procedure` apresLeCall : ; suite du programme

Pour appeler cette fonction/procedure, il suffit d’appeler l’instruction´         call ma procedure.

ATTENTION : si vous modifiez la pile, vous risquez de perdre le pointeur stocke par le´                         call.

Plus de details sur les fonctions sont fournis dans la section 1.7.´

1.6.4         Le bloc d’execution conditionnel´         cmp jcc labelSi

En utilisant le systeme de label et de saut conditionnel, il est possible de reproduire le comportement` d’un if{ } else { } en utilisant des sauts. Voici un exemple illustrant la structure d’un tel bloc en utilisant EAX comme registre de calcul :

mov eax, [var1] cmp eax, [var2] ; calcul de la comparaison [var1] - [var2]

j<cc> si_condition_vrai ; si [var1] <cc> [var2] est vrai, aller a si_condition_vrai

; instructions sinon jmp fin_si ; fin du sinon, aller a la fin du bloc conditionnel` si_condition_vrai:

; instruction si [var1] <cc> [var2] est vrai fin_si:

; fin du bloc

La liste des instructions de sauts conditionnels (j<cc>) et des tests qui leur correspond sont donnees´ dans la section 1.6.1.

1.7         Les fonctions et procedures en NASM´

1.7.1         L’effet d’une fonction sur la pile

L’instruction call label utilise la pile du programme pour sauvegarder le pointeur vers la prochaine instruction (stocke dans EIP) et r´ ealise un saut inconditionnelle vers le label d´ efinissant le d´ ebut du´ bloc de la procedure. L’instruction´ ret est equivalente´ a un` pop eip : le dernier el´ ement de la pile est´ transfer´ e dans le pointeur vers la prochaine instruction´ a ex` ecuter, ce qui aura un effet similaire´ a un` saut vers l’adresse anciennement contenu dans EIP si aucune modification de la pile n’a eu lieu :

ma_fonction:

; Etat de la pile : [ESP] = adresse de prochaine_instruction

; instructions

; Si pas de modification de la pile, [ESP] = adresse de prochaine_instruction ret ; = pop EIP => changement du pointeur de la prochaine instruction

_start:

; instructions

; Etat pile : ESP = EBP call ma_fonction ; push EIP ([ESP] = prochaine_instruction) et jmp ma_fonction

; au retour de la fonction : ESP = EBP prochaine_instruction

Si la pile n’a pas et´ e modifi´ e dans la fonction, le pointeur positionn´ e en haut de la pile par le´ call correspond a l’instruction suivant ce` call dans le code. Pour que cette instruction fonctionne, en cas d’utilisation de la pile dans la fonction, un nettoyage de la pile est necessaire (un´ pop pour chaque push).

1.7.2            Le passage de parametre sans sauvegarde de pile`

Le nombre de registre de stockage etant limit´ e, il est courant d’utiliser la pile en assembleur pour passer´ des parametres` a une fonction. Voici un exemple :`

ma_fonction:

; Etat suppose de la pile (du haut vers le bas de la pile) :´

; ancien EIP (32 bits = 4 octets), une valeur 32 bits, une adresse 32 bits mov ecx, [esp+8] ; acces` a l’adresse stock`       e dans la pile´ mov edx, [esp+4] ; acces` a la valeur stock`          e dans la pile´ mov eax, 4 mov ebx, 1 int 0x80 ret ; pop dans EIP

; autre version (pour que la fonction nettoie la pile) ; ret 8 => pop EIP et depile N (ici 8) octets (2*32 bits)´

_start:

; Au debut : ESP = EBP´ push msg            ; variable contenant une chaine de caracteres`

; [ESP] = msg

           push len               ; (constante egale´            a la taille de msg)`

; [ESP] = len, [ESP+4] = msg

call ma_fonction ; [ESP] = EIP, [ESP+4] = len, [ESP+8] = msg

; apres le ret : [ESP] = len, [ESP+4] = msg`

; apres le ret N : ESP = EBP`

; nettoyage de la pile si non fait dans la fonction avec ret N pop eax ; depile dans eax pour nettoyage´ pop eax

Le type de retour ret utiliser est en realit´ e d´ ependant du type de convention : certaines conventions´ reclament au module appelant la fonction de nettoyer lui m´          eme la pile (ˆ pop des arguments apres le` call, alors que d’autres specifient que la fonction appel´ ee est responsable du nettoyage des arguments´ situes dans la pile (´ ret N avec N egale´ a la taille en octets des param` etres).`

Cette approche est pratique pour des fonctions tres simples et rapide, mais la gestion de la pile` devient plus complexe si une autre fonction est appele dans la fonction.´

1.7.3            Le passage de parametre avec sauvegarde de pile`

Une maniere plus souple d’utiliser la pile consiste` a cr` eer une pile locale dans la fonction, et d’utiliser´ le pointeur de bas de pile EBP pour recup´ erer les valeurs de l’ancienne pile. En effet, ce pointeur n’est´ pas modifie par les instructions´ push et pop, ce qui permet de revenir facilement a l’ancien` etat de la´ pile comme dans l’exemple suivant :

ma_fonction:

; Etat suppose de la pile (du haut vers le bas de la pile) :´

; ancien EIP (32 bits = 4 octets), une valeur 32 bits, une adresse 32 bits

; nouveau cadre de pile push ebp           ; Sauvegarde de l’ancien bas de pile mov ebp, esp ; on remonte le bas de pile a la position du haut de la pile`

; EBP = ESP => nouveau cadre de pile

; Pour acceder au ancien param´             etre, on peut se se d`          ecaler par rapport´              a EBP` ; [EBP] = ancien EBP, [EBP+4] = EIP, [EBP+8] = len, [EBP+12] = msg mov ecx, [ebp+12] ; acces` a l’adresse stock` e dans la pile´ mov edx, [ebp+8] ; acces` a la valeur stock` e dans la pile´ mov eax, 4 mov ebx, 1 int 0x80 leave ; = mov esp, ebp et pop ebp => la pile reprend son ancien etat´ ret      ; pop dans EIP

; autre version (pour que la fonction nettoie la pile) ; ret 8 => pop EIP et depile N (ici 8) octets (2*32 bits)´

_start:

; Au debut : ESP = EBP´ push msg            ; variable contenant une chaine de caracteres`

; [ESP] = msg push len                ; (constante egale´              a la taille de msg)`

; [ESP] = len, [ESP+4] = msg call ma_fonction ; [ESP] = EIP, [ESP+4] = len, [ESP+8] = msg

; apres le ret : [ESP] = len, [ESP+4] = msg`

; apres le ret N : ESP = EBP`

; nettoyage de la pile si non fait dans la fonction avec ret N pop eax ; depile dans eax pour nettoyage´ pop eax


25