initiation `a l’assembleur x86...
TRANSCRIPT
Initiation a l’assembleur x86 (AT&T)
J.-L. Bienvenu
Ecole Nationale Superieure d’Electronique, Informatique et Radiocommunications de Bordeaux
1
Table des matieres
1 Introduction 3
1.1 Qu’est-ce que l’assembleur ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 Mais alors, pourquoi l’assembleur ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.3 Avant de commencer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.4 Avertissement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2 Les instructions 4
2.1 Le processeur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.2 Types d’instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.3 Registre d’etat et instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
3 Premier exemple 6
4 Les interruptions 7
5 Un peu de calcul 8
5.1 Operateurs arithmetiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
5.2 Operateurs bit a bit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
6 Le choix 10
7 La repetition 11
8 Donnees 14
9 Adressage 16
9.1 Adressage indirect . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
9.2 Adressage indirect par registre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2
9.3 Adressage indirect indexe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
9.4 Exemples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
10 Instructions particulieres d’adressage 19
11 Pile et passage de parametres 21
11.1 La pile pour l’adresse de retour . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
11.2 La pile pour sauvegarder des registres . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
11.3 La pile pour les variables locales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
11.4 La pile pour le passage des parametres . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
12 Passage des parametres 23
13 Cas ou la pile est indispensable 25
13.1 Nombre quelconque de parametres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
13.2 Fonctions recursives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
14 Fichiers standard 28
14.1 Ecriture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
14.2 Lecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
15 Fichiers quelconques 33
15.1 Lecture et ecriture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
15.2 Ouverture et fermeture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
15.3 Programme d’illustration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
A Utilisation du debogueur gdb 40
A.1 Deroulement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
A.2 Affichage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
3
B Creer une bibliotheque 41
4
1 Introduction
1.1 Qu’est-ce que l’assembleur ?
L’assembleur (ou plus exactement le langage d’assemblage) est le langage de programmation de plusbas niveau. Chaque instruction (egalement appelee mnemonique) correspond exactement a un codecompris par la machine. La consequence est qu’il n’existe pas un seul langage, mais autant de langagesque de types de processeur. De plus, il n’y a aucune normalisation : dans le cas qui nous interesse(famille des processeurs Intel 32bits), il y a deux syntaxes dites “Intel” et “AT&T”.Pour les processeurs Motorola, il faut apprendre un autre langage, meme si les instructions presententdes similitudes. Ces processeurs font partie de la famille CISC ( complex instuction set computer).L’autre famille (RISC : reduced instruction set computer ) possede elle aussi des langages (pas plussimples pour autant)
1.2 Mais alors, pourquoi l’assembleur ?
Reponse : pour les cas ou un programme doit gerer un dispositif particulier (peripherique particulierou systeme autre qu’un ordinateur possedant un micro-processeur) et qu’un langage de haut niveau(comme le C) est difficile (voire imposible) a utiliser.Il se dit aussi que le code obtenu est plus compact (c’est vrai) et plus efficace (cela peut etre vrai,mais pas toujours). Ces arguments pouvaient etre decisifs dans un temps ou les machines avaient defaibles performances en memoire et en vitesse, mais ce n’est plus le cas.Autre reponse : La pratique de l’assembleur permet de comprendre plus intimement le fonctionnementd’un micro-processeur.
1.3 Avant de commencer
On supposera connue la pratique de la programmation dans un langage quelconque (si possible C ouC++). Il faudra disposer une machine a processeur Intel (posterieur au 80386) muni d’un sysemed’exploitation Linux.
1.4 Avertissement
Dans tout ce qui suit, les exemples de programmes ne realiseront que des operations simples quipourraient etre realisees par quelques lignes de C. (ce qui contredit les affirmations precedentes). Lebut n’est pas de programmer un systeme particulier, mais de comprendre les concepts mis en jeu. Cesexemples n’ont donc qu’un but purement pedagogique et presentent l’avantage de ne necessiter qu’unordinateur. De plus, il ne s’agit que d’une initiation : seules les instructions les plus importantesseront utilises et decrites de facon minimale. La description du jeu d’instructions complet avec latotalite des specifications devra etre cherchee dans un ouvrage de reference. A defaut, on peut trouverl’information a l’adresse :http://docs.sun.com/app/docs/doc/806-3773/6jct9o0cb?l=fr&a=view
5
2 Les instructions
2.1 Le processeur
Avant de parler des instruction, il est necessaire de savoir a quoi elles s’appliquent. Il s’agit prin-cipalement de la memoire et des registres. La memoire vive (ou RAM) peut etre vue comme unesuite d’octets numerotes. Les registres sont des “cases” memoire situees dans le processeur. Elles sontd’acces plus rapide que la memoire et ont une taille de 4 octets (=32 bits) dans le cas qui nous interesse.
Ces registres portent un nom permettant de les adresser. Ces registres sont
• Les registres de donnees : EAX, EBX, ECX, EDX , dans lesquels on peut adresser les 32 bits,mais aussi les 16 bits de poids faible (notes respectivement AX, BX, CX et DX) et ces demi-registres peuvent a leur tour etre decomposes en leurs 8 bits de poids fort (AH,BH,CH et DH)et 8 bits de poids faible (Al, BL, CL et DL)
• Les registres d’adresse :ESI et EDI (il existe aussi SI et DI)
• Les registres de pile : ESP et EBP
Ces registres sont les seuls qui seront manipules explicitement. Il existe aussi:
• Le compteur d’instructions (ou compteur ordinal) qui contient a chaque instant l’adresse del’instruction decodee par le processeur.
• le registre d’etat (status register). debordement, retenue . . . ).
• Des registres de segment ( qu’on a pas besoin de manipuler si on se contente de 4 Go de memoire!)
• et bien d’autres encore . . .
Il est a noter que rien n’interdit a un registre d’adresse de contenir une donnee (et vice-versa), maiscertaines instructions ne peuvent utiliser qu’un registre particulier (ex: la multiplication place sonresulat dans EDX et EAX).
2.2 Types d’instructions
Comme dans la plupart des processeurs, l’ensemble des instructions permet les operations suivantes
• deplacement
chargement d’une valeur dans un registre
transfert d’un registre vers la memoire (et reciproquement)
6
• calcul
les 4 operations arithmetiques
les operations bit a bit (or, and ,xor, not, decalages)
• les comparaisons entre registres ou entre registres et memoire
• les ruptures
saut a un autre emplacement du programme.
appel de fonction (qui est aussi un saut avec memorisation de l’adresse de l’instructionsuivante)
On remarquera que les noms des instructions se terminent souvent par la lettre l. Celle ci indique quel’instruction s’applique a une valeur sur 32 bits. Pour les versions 16 bits de processeur, l’instructionde transfert, par exemple s’appelait mov ; elle s’appelle maintenant movl et le transfert de 16 bitsutilise l’instruction movw.
2.3 Registre d’etat et instructions
Chaque bit de ce registre est un indicateur detat. Parmi les bits les plus importants, on trouve :
- ZF (zero flag) : positionne par une operation arithmetique ou une comparaison en cas d’egalite(mais pas par un deplacement).
- CF (carry flag) : positionne par une operation arithmetique lorsque le resultat depasse la taillede registre concerne.exemple : si on ajoute 2 registres de 8 bits, contenant 0xFF et 1, le resultat (en theorie 0x100)vaut 0 donc CF est positionne (ZF aussi)
- SF (sign flag) : positionne par une operation arithmetique dont le resultat est negatif ou unecomparaison dans le cas ou le eroperande est le plus grand (mais pas par un deplacement).exemple : si on soustrait 2 registres de 8 bits, contenant 0x1F et 0x20, le resultat ( -1) vautOxFF donc SF est positionne.
• OF (overflow flag) indique qu’un calcul a deborde sur le bit de signe. exemple : si on ajoute2 registres de 8 bits, contenant 0x7A et 0x21, le resultat vaut OxFF (donc -1) ; OF est alorspositionne. (SF aussi). Si on considere que les nombres sont signes, OF indique bien un probleme,si on les considere non signes, OF est inutile.
7
3 Premier exemple
#exit.s version 1
.section .text
.globl _start
_start:
movl $101, %ebx # nombre a retourner au shell
movl $1, %eax # appel a la fonction 1 du noyau
int $0x80 # = terminer programme
Ce programme contient 3 instructions en assembleur : movl $101, %ebx , movl $1, %eax et int$0x80.A quoi sert le reste ?Les indications suivant la caractere # sont des commentaires. Il faut absolument mettre autant decommentaires que necessaire a la comprehension du programme (le langage en lui-meme est peu ex-pressif)La directive .section text indique a l’assembleur qu’il s’agit d’une zone de programme. Cettedirective est valable jusqu’a la prochaine directive .section.La directive .globl indique au compilateur qu’un symbole est global. Le mot start est une etiquette(ou label) qui represente une adresse. Cette adresse peut selon les cas, etre l’adresse d’une donnee,d’un saut, d’une fonction . . .Dans le cas present, start est une fonction. C’est meme la seule fonction dont la presence estobligatoire si on veut produire un programme executable (et le symbole start doit, dans ce cas, etreglobal).La directive .type start,@function indique au compilateur qu’il doit traiter l’etiquette comme unefonction.
L’instruction movl $101, %ebx place la valeur 101 dans le registre EBX, et movl $1, %eax place 1dans EAX. La notation exige qu’une constante soit prefixee par le symbole $. Les constantes en base16 commencent par 0x et les constantes en binaire par 0b
int $0x80 est ce qu’on nomme un appel systeme. Le systeme d’exploitation met a la disposition duprogrammeur un ensemble de routines pour que chacun ne soit pas oblige de les re-ecrire a chaquefois. La valeur placee dans EAX determine le choix de la routine utilisee. Ici la valeur 1 provoquel’appel de la routine de retour au systeme d’exploitation, le code retour du programme etant la valeurplacee dans EBX.
On peut alors assembler avec la commande as exit.s -o exit.o puis linker avec ld exit.o -o
exit puis executer exit et verifier le code retour avec la commande echo $?
On pourrait remarquer que gcc est capable de faire l’assemblage, mais si on tape gcc exit.s
-o exit, on obtient un message contenant In function ‘ start’ : multiple definition of
8
‘ start’ et In function ‘ start’ : undefined reference to ‘main’. Le code est correct, maisle linker ne s’y retrouve pas. En fait, gcc cree sa propre fonction start (ce qui en fait 2) qui appellela fonction main (qui n’est pas definie).Un solution serait de renommer la fonction start en main (ca marche), mais on ne pourrait plusassembler avec as . La bonne solution est d’utiliser la commande gcc exit.s -nostdlib -o exit
pour que gcc ne prenne plus d’initatives.
Comme dans tout langage de programmation, il faut decouper le programme en fonctions. Dans lecas present, la sortie du programme devient une fonction. Pour l’utiliser, on place la valeur de retourdans EBX, puis on appelle exit avec l’instruction call .
# exit.s version2
.section .text
.globl _start
#---------------------------------------------------------------------------
exit:
.type exit,@function
movl %eax, %ebx # recuperation parametre = code retour
movl $1, %eax # appel a la fonction 1 du noyau
int $0x80 # = terminer programme
#---------------------------------------------------------------------------
_start:
.type _start,@function
movl $102,%eax # nombre a retourner au shell
call exit
4 Les interruptions
N.B. Ce paragraphe n’est pas indispensable pour la suite.
Dans le programme precedent, on a utilise une instruction int , qui correspond a une interruption.Un exemple permettra d’illustrer le mecanisme d’interruption. Pendant l’execution d’un programme,l’utilisateur appuie sur une touche du clavier. Or, il se peut que le programme n’ait pas besoin ducaractere frappe. Pour prendre en compte cet evenement, le clavier declenche une interruption, qui vaprovoquer l’execution d’une routine. Celle-ci va sauvegarder les registres (y compris le registre d’etat),s’executer, et restaurer les registres pour reprendre le programme a l’endroit ou il avait ete suspendu.Les adresses des routines correspondant a toutes les interruptions sont stockees dans une zone de lamemoire dite “table des vecteurs d’interruption”. Chaque type d’interruption est associe a une casede cette table qui contient simplement l’adresse de la routine a executer.Une interruption peut aussi etre provoquee par le processeur lui-meme (par exemple : en cas dedivision par 0, l’interruption affiche un message d’erreur et arrete le programme).
9
Le systeme d’exploitation profite de cette table en se reservant une adresse de la table pour les appelssysteme, . L’instruction int $0x80 va chercher dans la table l’adresse de la routine correspondante.Dans ce cas, l’execution est provoquee par le programme et peut etre comparee a un appel indirectde fonction.
5 Un peu de calcul
5.1 Operateurs arithmetiques
Les operations d’incrementation et de decrementation s’appellent respectivement incl et decl ;l’operation de changement de signe est negl.les operations d’addition et de soustraction s’appellent respectivement addl et subl , la multiplica-tion et la division imull et idivl pour les operations signees, mull et divl pour les operationsnon signees.La syntaxe de incl et decl est :
incl <destination> et decl <destination>
ou <destination> ne peut etre qu’un registre ou une case memoire (idem pour negl).la syntaxe de addl est :
addl <operande> <destination>
ou <operande> peut etre une valeur immediate (i.e. une constante), un registre ou une case memoire,<destinaton> peut etre qu’un registre ou une case memoire, mais les deux ne peuvent pas etresimultanement une case memoire. (idem pour subl )Pour les multiplications, les choses se compliquent. D’abord parce que le produit de 2 nombres de 32bits occupe 64 bits. Dans tous les cas, les 32 bits de poids fort seront places dans EDX. Ensuite lamultiplication peut etre signee ou non signee.Pour la multiplication non signee, la syntaxe est:
mull <operande>
L’operande est multiplie par EAX et le resultat est place dans EDX:EAXPour la multiplication signee, il y a deux syntaxes:
imull <operande> ou imull <operande>, <destination>
dans le 2eme cas, on peut preciser quel registre contiendra le produit (ou plutot sa partie faible)
Pour la division, on ne precise que le diviseur, le dividende etant necessairement le double motEDX:EAX (si EDX est nul et EAX negatif, il faut etendre son signe a EDX avec l’instruction cltd.
10
Le resultat est place dans EDX pour le reste et EAX pour le quotient. Si le diviseur est nul ou si lequotient est trop grand, une exception survient et le programme est arrete.
Pour experimenter les operations aritthmetiques, nous allons ecrire une fonction qui calcule les valeursde x2/4 − 5 ∗ x+ 7 pour une valeur donnee de x.
.section .text
.globl _start, exit
#-------------------------------------------------------------------------------
exit:
........
#-------------------------------------------------------------------------------
# fontion calculant x^2/4-5*x+7
calcul:
.type _start,@function
# entree : %eax contient la valeur
# sortie : %eax
# utilisation des registres pendant le calcul
# %eax et %edx sont utilises pour les operations * et /
# %ebx contient le resultat partiel
# %ecx conserve une copie de x
movl %eax, %ecx
imull %eax # multiplication de %eax par lui-meme
movl $0, %edx # important : le dividende est %edx:%eax, si
# on n’utilise que %eax, il faut : %edx=0
movl $4, %ebx
idivl %ebx
movl %eax, %ebx # %ebx=x^2/4
movl $5,%eax
imull %ecx # calcul de 5*x
subl %eax, %ebx # %ebx=x^2/4-5*x
addl $7, %ebx # %ebx=(x/4-5)*x+7
movl %ebx, %eax # valeur retournee dans %eax
ret
#-------------------------------------------------------------------------------
_start:
.type _start,@function
movl $10, %eax
call calcul # resultat dans %eax (transmis a exit)
call exit
11
La transmission des parametres se fait par registre (ici un seul suffit) et le retour se fait par l’intermediairede EAX. Le langage C utilise la meme convention. Cette methode sera utilisee dans toute la suite.En ce qui concerne l’utilisation des registres, le programme ne fait pas tout a fait ce qui est annonce.Lorsque x2 a ete calcule, il est stocke dans EAX. Il faut alors le diviser par 4. Mais EDX doit etremis a 0 pour la division, ECX contient x pour la suite des calculs. Il ne reste que EBX pour placer lavaleur 4 ( le mode immediat n’existe pas pour la division). EBX est cense contenir le resultat partiel,mais rien n’a encore ete calcule . . .Il vaut mieux eviter ce genre d’astuce qui risque de rendre le programme incomprehensible (on auraitpu utiliser ESI par exemple). On verra par la suite qu’on pourra memoriser des donnees dans des“variables locales”On peut aussi remarquer que la fonction calcul modifie des registres autres que EAX, ces registrespouvaient contenir des resultats et etre detruits lors de l’appel a calcul. Ce probleme sera lui aussiresolu ulterieurement.
5.2 Operateurs bit a bit
• les operateurs logiques : andl, orl, xorl et not : effectuent respectivement les operations et, ou,ou exclusif et non avec les memes specifications que addl (pour les trois premieres) et negl pourla derniere)Il existe aussi l’operateur test qui effectue un et entre ses operandes sans les modifier (seuls lesflags sont positionnes).
• Les decalages : vers la gauche (shll, sall) vers ou la droite (shrl, sarl). leur syntaxe est : (parexemple pour shll
shll <operande> <destination>
ou<destination> peut etre un registre ou une case memoire et<operande> une valeur immediateou le registre CL.Les deux premieres instructions sont identiques : tous les bits sont decales vers la gauche et lebit de poids faible est mis a 0.shrl et sarl decalent les bits vers la droite, mais dans le cas de sarl, le bit de poids fort resteinchange, alors qu’il est mis a 0 par shrl.
• Les rotations roll et rorl (memes specifications que pour les decalages) decalent egalement lesbits, mais cette fois, le bit sortant est replace a l’autre extremite.
6 Le choix
Il est possible de comparer des valeurs et de faire des choix en fonction du resultat de la comparaison.Ce choix consiste en general a faire un saut vers un zone de code. L’instruction explicite de comparaisonest cmpl cette instruction positionne certains bits du registre d’etat qui vont permettre de prendreune decision.les sauts conditionnels les plus courants sont :
je (jump if equal) jne (jump if not equal)
12
jg (ou jnle ) (jump if greater ( ou if not lower equal) ),jge (ou jnl ), jl (ou jnge ), jne(ou jng ) pour les comparaisons entre nombres signes
ja ( ou jnbe ) (jump if above (ou if not below or equal)) jae ( ou jnb ), jb ( ou jnae ), jbe( ou jna ) pour les comparaisons entre nombres non signes.les operations arithmetiques positionnant egalement le registre d’etat (mais pas les transferts) ,il est egalement possible d’utiliser ensuite les sauts conditionnels; jz (jump if zero) est iden-tique a je (de meme pour jnz et jne ).Il existe aussi d’autres tests concernant le debordement, la retenue, la parite . . .
L’exemple suivant montre l’utilisation de la comparaison associee a un saut pour calculer le maximumde deux nombres.
.section .text
.globl _start
......
max2:
.type max2,@function
# entree : X dans %eax
# Y dans %ebx
#sortie : max(X,Y) dans %eax
# et acessoirement min(X,Y) dans %ebx
cmpl %eax, %ebx
jl fin_max2 # if %ebx < %eax goto fin_max2
xchgl %ebx, %eax
fin_max2:
ret
#--------------------------------------------------------------------------------
_start:
.type _start,@function
movl $192,%eax
movl $217, %ebx
call max2
call exit
7 La repetition
Pour repeter des operations, il suffit d’utiliser les instructions vues precedemment, et le saut incondi-tionnel jmp.Dans l’exemple suivant, on calcule de facon classique le pgcd a 2 entiers
13
.section .text
.globl _start
#--------------------------------------------------------------------------------
pgcd:
.type pgcd, @function
# calcule iterativement pgcd(a,b)
# a dans %eax
# b dans %ebx
boucle_pgcd:
cmpl $0, %ebx
je fin_pgcd
mov $0, %edx
idivl %ebx, %eax
movl %ebx, %eax # a = b
movl %edx, %ebx # b = a%b
jmp boucle_pgcd
fin_pgcd:
ret
#--------------------------------------------------------------------------------
_start:
.type _start,@function
movl $192,%eax
movl $111,%ebx
call pgcd
call exit
Lorsque le nombre de repetitions est connu, on peut utiliser le registre ECX pour le compteur, etl’instruction particuliere jexcz qui lui est associee. cette instruction effectue un saut si ce reg-istre a pour valeur 0. L’exemple suivant utilise cette instruction pour le calcul de la puissance.
14
puissance:
.type puissance,@function
# entree : X dans %eax
# n dans %ebx
# sortie : X^n dans %eax
movl %ebx,%ecx # %ecx = compteur
movl %eax, %ebx # liberation de %eax qui sera utilise par
# la multiplication
movl $1,%eax # valeur initiale 1 dans %eax
prod_suiv:
jecxz fin_puissance
imull %ebx,%eax
decl %ecx
jmp prod_suiv
fin_puissance:
ret
#--------------------------------------------------------------------------------
_start:
.type _start,@function
movl $2,%eax
movl $7, %ebx
call puissance
call exit
La decrementation etant toujours faite dans de type de boucle, il y a meme une instruction quidecremente ECX et qui effectue le saut s’il est non nul : loop. Il y a encore plus sophistique :l’intruction loopz effectue le saut si ECX est non nul et si le drapeau ZF est mis (idem pour loopnzlorsque ZF n’est pas mis)
L’exemple suivant calcule la factorielle en utilsant loop
15
....
factorielle:
# calcul de n! (on suppose n>=0)
movl %eax,%ecx # n dans %ecx
movl $1,%eax # resultat dans %eax
prod_suiv:
imull %ecx
loop prod_suiv
ret
#--------------------------------------------------------------------------------
_start:
.type _start,@function
movl $5,%eax # le resultat doit etre <256 pour le shell
call factorielle
call exit
8 Donnees
Pour declarer des donnees, il faut definir une nouvelle section. Si ces donnees sont initialisees , le nomde la section sera .data (ou .rodata = lecture seule ), sinon, ce sera .bss. Comme pour le code, chaquedonnee sera designee par une etiquette. S’il s’agit d’une donnee initialisee, elle devra etre preceded’un mot-cle precisant sa nature (donc sa taille). Ce mot-cle est .byte pour les octets, .long pour lesentiers (32bits), .short (ou .hword ) pour les mots de 16 bits, .string (ou .asciz ) pour les chaınesterminees par un 0, .ascii pour les chaınes non terminees par un 0. ( on trouve aussi : .quad pourles entiers sur 64 bits (les long long du C), .float (ou .single ) pour les reels en simple precision (lesfloats du C) et .double pour les reels en double precision (les double du C)S’il s’agit d’une donnee non initialisee, elle devra etre precede du mot-cle lcomm et specifiee par dataille en octets.
Exemple :
16
....
.section .data
a: .long 12345
parfaits: .long 6,28,496,8128
premiers: .byte 2,3,5,7,11,13,17,19
separateurs: .byte ’ ’,’\t’,’\n’,’;’
erreur: .string "Valeur incorrecte\n"
.section .bss
buffer: .lcomm 1000
Remarque : les donnees initialisees sont integrees au programme executable et chargees en memoirelors de l’execution, alors que les donnees non initialisees n’occupent aucune place dans l’executable;leur place est seulement reservee lors de l’execution.
Exemple d’utilisation:
......
_start:
.type _start,@function
movl nombre1, %eax
movl nombre2, %ebx
call max2
call exit
#--------------------------------------------------------------------------------
.section .data
nombre1:
.long 192
nombre2:
.long 217
On remarque ici que les symboles sont traites comme les valeurs qu’ils designent, ce qui ne permetd’acceder qu’a la premiere valeur d’une liste. Pour obtenir l’adresse, il faudra utiliser une autresyntaxe.
17
9 Adressage
9.1 Adressage indirect
Les seuls modes d’adressage examines jusqu’ici sont l’adressage immediat (placer une valeur constante)et l’adressage de registre. Il est aussi possible d’utiliser l’adressage indirect, c’est a dire utiliser uneadresse pour y stocker une valeur (constante ou registre).Exemple :
_start:
.type _start,@function
movl entier, %eax
incl %eax
movl %eax, entier
call exit
#--------------------------------------------------------------------------------
.section .data
entier:
.long 100
On constate ici que entier equivaut a la constante 100 dans l’instruction movl entier, %eax , maisne peut etre considere de la meme facon dans movl %eax, entier ( impossible de copier une valeurdans une constante ).
9.2 Adressage indirect par registre
L’adressage peut aussi se faire grace a une adresse contenue dans un registre. Dans ce cas , il fautparentheser le registres pour indiquer qu’il s’agit d’un adressage indirect .Exemples :
movl (%esi), %eax copie dans EAX la valeur pointee par ESI.
movb %bl, (%edi) copie l’octet BL a l’adresse contenue dans EDI.
On peut combiner adresse constante et adressage par registre en prefixant le registre par une constante.
Exemples :
18
movb tableau(%esi), %al copie dans AL l’octet pointe par l’adresse tableau augmentee de lavaleur de ESI. (utile pour parcourir un tableau)
movl 8(%ebp), %eax copie dans EAX la valeur situee a l’adresse contenue dans EBP. ( souventutilise pour acceder aux parametres dans la pile (voir plus loin)
9.3 Adressage indirect indexe
On peut aussi utiliser un adressant une donnee (tableau par exemple), on ne peut pas chargerson adresse dans un registre (EDI par exemple) avec l’instruction movl tableau, %edi puisque celaeffectue la copie de la valeur et pas de l’adresse. L’assembleur autorise, pour le faire, la syntaxe : movl$tableau, %edi. Cependant, cette methode est deconseillee a cause du risque de confusion. Il estpreferable d’utiliser l’instruction leal (load effective adress), qui est plus explicite et permet en outred’utiliser toutes les formes d’adressage.Exemple : leal tableau(%ecx,4) %esi
9.4 Exemples
On va utiliser ces notions pour realiser deux operations simples :
1/ calcul de la longueur d’une chaıne de caracteres.
19
longueur:
.type longueur, @function
# calcule la longueur d’une chaine
# entree : adresse de la chaine dans %eax
# sortie : longueur dans %eax
movl %eax, %esi # adresse chaine dans %esi
movl $0, %eax # longueur dans %eax (a calculer)
undeplus:
cmpb $0, (%esi,%eax)
je fin_longueur
incl %eax
jmp undeplus
fin_longueur:
ret
#--------------------------------------------------------------------------------
_start:
.type _start,@function
leal message, %eax
call longueur
call exit
#--------------------------------------------------------------------------------
.section .data
message:
.string "Hello World !\n","AAA"
2/ determination du maximum d’une liste d’entiers.
20
max:
.type max, @function
# calcule le maximum d’une liste de
# nombres positifs termin\’ee par 0
# entree : adresse le la liste dans %eax
# sortie : maximum dans %eax
movl %eax, %esi
movl (%esi), %ebx # valeur courante dans %ebx
movl %ebx, %eax # maximum dans %eax
max_suiv:
cmpl $0, %ebx # fin de liste ?
je fin_max
addl $4, %esi # adresse valeur suivante
movl (%esi), %ebx
cmpl %eax, %ebx # comparaison avec max
jle max_suiv # pas mieux
movl %ebx, %eax # remplacement de max
jmp max_suiv
fin_max:
ret
#-------------------------------------------------------------------------------
_start:
.type start, @function
# sortie : maximum dans %eax
leal liste, %eax
call max
call exit
#-------------------------------------------------------------------------------
.section .data
liste:
.long 31,8,55,21,59,71,13,129,64,9,25,102,87,0
10 Instructions particulieres d’adressage
N.B. Ce paragraphe traite de certaines instructions particulieres aux processeurs x86 et n’est pas in-dispensable pour comprendre la suite.
Les tableaux etant frequemment utilises, le jeu d’instructions offre quelques operations dediees a leurtraitement (recherche, consultation, modification, copie, comparaison). les instructions correspon-dantes s’appellent respectivement scasl, lodsl, stosl, movsl et cmpsl s’il s’agit de tableaux de nombres
21
de 32 bits. Pour 16 bits, il faut remplacer le l final par w et pour 8 bits par b. Une particularite deces instructions est de ne pas necessiter d’operande. Les adresses de tableau sont, selon les cas, dansESI ou EDI (ou les deux pour la copie). Il faut aussi noter que chacune de ces instructions modifiecelui (ou ceux) de ces registres d’adresse en vue de la prochaine operation (donc: decalage de 4 pourl, de 2 pour w et de 1 pour b. De plus, le decalage peut etre positif ou negatif selon la valeur du bitde direction du registre d’etat. L’instruction cld annule ce bit pour que le decalage soit positif (et stdle positionne pour qu’il soit negatif)
• scas{lwb} Compare l’element d’adresse EDI avec un EAX (ou AX ou AL) et modifie le registred’etat, puis decale EDI.Exemple : calcul de la longueur d’une chaıne terminee par 0.
# %edi et %esi contiennent l’adresse de la chaine
# on cherche le 0 avec scasb ; la longueur est la
# difference %edi - %esi -1
....
movb $0, %al
cherche0:
scasb
jne cherche0
subl %esi, %edi
decl %edi
....
• lods{lwb} Charge l’element d’adresse ESI dans EAX (ou AX ou AL), puis decale ESI.
• stos{lwb} Place EAX (ou AX ou AL) a l’adresse EDI, puis decale EDI
• cmps{lwb} Compare les elements d’adresse ESI et EDI, puis decale ces registres.
• movs{lwb} Copie l’element d’adresse ESI vers l’adresse EDI, puis decale ces registres. C’est laseule instruction qui realise un dplacement de memoire a memoire.
Pour appliquer ces operations a l’ensemble du tableau, ces instructions elementaires sont utiliseesconjointement avec les operations de repetition rep, repe (ou repz) et repne (ou repnz). Ces troisinstructions decrementent ECX jusqu’a ce qui celui-ci atteigne la valeur 0. repz et (respectivementrepnz) interrompt la repetition avant que ECX soit nul si ZF est positionne (respectivement annule).
Exemples :
1/ Copie d’une zone memoire de 1000 octets vers une autre
22
# %esi contient l’adresse de la zone source
# %edi, celle de la destination
movl, $1000, %ecx
rep movsb
....
2/ Copie d’un tableau d’entiers de 16 bits termine par un 0
# %esi contient l’adresse de la zone source
# %edi, celle de la destination
copie: lodsw
stosw
cmpw $0, %ax
jne copie
....
11 Pile et passage de parametres
11.1 La pile pour l’adresse de retour
On a vu qu’il existe un registre de pile (ESP), mais il n’a jamais ete utilise pour l’instant, du moinsen apparence. La valeur de ce registre est fixee au demarrage par le systeme d’exploitation. Il ne doitdonc jamais etre initialise. Chaque fois qu’une fonction est appelee, l’instruction call place sur la pilel’adresse de la prochaine instruction a executer et soustrait 4 a ESP (les adresses dans la pile croissentvers le bas !) . l’instruction ret terminant la fonction ajoute 4 a ESP. Il est clair que la valeur deESP doit toujours etre correcte, sans quoi l’instruction ret replace une adresse erronee sur le registred’instruction et le programme devient imprevisible . . .
11.2 La pile pour sauvegarder des registres
Dans une fonction comme pgcd, on utilise le registre EDX pendant le calcul, alors qu’il pouvait conteniravant l’appel une valeur utile. Pour preserver les contenus d’un registre, il suffit de les placer sur lapile et de la depiler (en general dans le registre d’origine, mais ce n’est pas obligatoire) lorsque on ena besoin. Les instructions pour empiler et depiler sont respectivement pushl et popl . pushl prendune valeur en argument ( immediate, registre ou indirecte) et popl un registre.Pour sauvegarder les registres, il y a deux options :- empiler les registres avant l’appel et les depiler apres.- empiler dans la fonction les registres qui vont etre detruits et les depiler avant le retourL’avantage de la premiere methode est de ne sauvegarder que ce qui est nessaire, la deuxieme est de
23
ne plus prendre le risque d’oublier cette sauvegarde.
11.3 La pile pour les variables locales
Si les registres ne suffisent pas pour un calcul donne, il est possible de reserver de la place dans la pile.Il suffit de diminuer le pointeur de pile du nombre d’octets suffisant.Exemple : les registres EAX,EBX et ECX contiennent des valeurs a utiliser ulterieurement et vontetre utilises pour d’autres calculs.
.....
subl $8, %esp
movl %eax , (%esp)
movl %ebx, 4(%esp)
# on utilise les registres pour un calcul; une fois fini,
# on retablit les valeurs des registres, puis %esp
movl (%esp), %eax
movl 4(%esp), %ebx
addl $8, %esp
11.4 La pile pour le passage des parametres
Dans les exemples precedents, les parametres ont ete transmis aux fonctions par les registres. Cettemethode est couramment utilisee dans la programmation sur processeur RISC car ils disposent engeneral d’un plus grand nombre de registres. On utilisera de preference la pile pour transmettre lesparametres, d’autant que c’est la methode utilisee par le langage C. Pour illustrer ce passage, on vatransmettre une chaıne de caracteres a la fonction puts de la bibliotheque C pour qu’elle l’affiche (onne sait pas encore le faire)
24
.section .text
.globl _start
.extern puts
......
_start:
.type _start,@function
pushl $message
call puts
addl $4, %esp
call exit
#-------------------------------------------------------------------------------
.section .data
message:
.string "Ce message est affiche par puts"
On peut alors assembler avec la commande as affiche.s -o affiche.o puis linker (c’est plus com-plique) avec : ld -dynamic-linker /lib/ld-linux.so.2 -lc -o affiche affiche.o.Cet exemple montre qu’on peut utiliser une fonction du C dans un programme en assembleur. Dansla pratique, on ecrit plutot (si l’environnement s’y prete) un programme en C contenant des appels ades fonctions en assembleur.
12 Passage des parametres
Le passage des parametres a une fonction se fait simplement en empilant ceux-ci avant l’appel. Dansle cas des fonctions du C, ces parametres sont empiles en commencant par le dernier, ce qui estjudicieux, puisque l’ajout d’un parametre (a la fin de la liste) ne modifie pas la position des autres (qui se trouvent plus “haut” dans la pile ).Pour la recuperation de ces parametres a l’interieur de la fonction, on peut utiliser le pointeur de pileESP, mais celui-ci peut varier (par exemple parce qu’on reserve de la place pour les variables locales).On prefere utiliser le pointeur de pile auxiliaire EBP de la facon suivante :Des l’entree dans la fonction :
- On empile EBP (pour sauvegarder sa valeur)
- On recopie ESP dans EBP, qui sera le seul utilise pour acceder aux parametres (et aux variableslocales)
Avant de sortir de la fonction :
- On recopie EBP dans ESP
25
- On depile EBP parametres
Dans le tableau qui suit, on suppose etre dans le contexte d’une fonction appelee avec n parametreset reservant la place pour p variables locales. (pour simplifier, on suppose qu’ils ont tous une taille de4 octets, si ce n’est pas le cas, il faudra en tenir compte dans le calcul des adresses). On supposeraaussi que les registres EBX, ECX et EDX ont ete empiles. La representation de la pile est :
. . . . . .
parametre n 4+4*n(%ebp)
. . . . . .
parametre 3 16(%ebp)
parametre 2 12(%ebp)
parametre 1 8(%ebp)
adresse de retour 4(%ebp)
sauvegarde de EBP (%ebp)
sauvegarde de EAX -4(%ebp)
sauvegarde de EBX -8(%ebp)
sauvegarde de ECX -12(%ebp)
variable locale 1 -16(%ebp)
variable locale 2 -20(%ebp)
. . . . . .
variable locale p -12-4*p(%ebp) ( ou (%esp) )
. . . . . .
Exemple : un nouvelle version de la fonction max2
max2:
.type max2,@function
# entree : X et Y dans la pile
#sortie : max(X,Y) dans %eax
pushl %ebp
movl %esp, %ebp
push %ebx
movl 8(%ebp), %eax
movl 12(%ebp), %ebx
cmpl %eax, %ebx
jl fin_max2 # if %ebx < %eax goto fin_max2
movl %ebx, %eax
fin_max2:
popl %ebx
movl %ebp, %esp
popl %ebp
ret
26
Exemple d’appel de cette fonction :
....
_start:
.type _start,@function
pushl $217
pushl $192
call max2
add $8, %esp
call exit
On peut aussi integrer cette fonction dans un programme en C (afficheMax.c)
#include <stdio.h>
main()
{
printf("%d\n",max2(192,217));
}
il faut ajouter dans le fichier en assembleur : .globl max2 , assembler le fichier : as max2.s -o
max2.o, puis gcc afficheMax.c max2.o -o afficheMax
Attention, le fichier assembleur ne doit pas contenir de fonction start !
13 Cas ou la pile est indispensable
Dans la plupart des cas, s’il n’est pas besoin d’interfacer le C (ou autre) avec l’assembleur, on peutchoisir de passer les parametres par registre ou par la pile, mais dans certains cas, seule la pile estutilisable
13.1 Nombre quelconque de parametres
Si le nombre de parametres passes a une fonction varie selon les appels, les registres risquent d’etreinsuffisants et de plus, ils ne se pretent pas a un parcours indexe. Il y a principalement 2 methodespour passer des parametres en nombre quelconque:
• la methode ”argc/argv” : tous les parametres sont de meme type et le 1erargument indique lanombre de valeurs a prendre en compte (variante : on ne donne pas le nombre, mais on ajouteapres le dernier parametre une valeur speciale de meme type (ex: une suite d’entiers se terminantpar 0 )
27
• la methode ”printf” : les parametres ne sont pas tous de meme type. On ajoute un 1erparametrequi decrit les parametres qui suivent.
Un exemple trivial de la 1ere methode est justement le traitement des arguments de la ligne de com-mande. Le shell memorise les mot sur la ligne de commande et place les adresses sur la pile enterminant par 0 (le nombre d’arguments n’est pas transmis)
.section .text
.globl _start
.extern puts
........
_start:
.type _start, @function
push %ebp
movl %esp, %ebp
movl $0, %ecx # %ecx compte les arguments
arg_suiv:
movl 8(%ebp,%ecx,4),%esi
# adressage indexe dans la pile
cmpl $0,%esi # si l’adresse est 0
je args_exit # c’est fini
pushl %ecx # puts peut utiliser %ecx
pushl %esi # parametre pour puts
call puts
popl %esi
popl %ecx
incl %ecx
jmp arg_suiv
args_exit:
movl %ebp, %esp
pop %ebp
movl %ecx, %eax # retourne le nombre d’arguments trouves
call exit
Remarque : le parcours de la pile s’arrete lorque une adresse 0 est trouvee. Si on continue apres avoir”saute” cette adresse , on trouve les chaınes d’environnement du shell.
28
13.2 Fonctions recursives
(Juste pour le plaisir)
Le PGCD :
.section .text
.globl pgcd
pgcd:
# calcule recursivement pgcd(a,b)
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax # a dans %eax
movl 12(%ebp), %ebx # b dans %ebx
next:
cmpl $0, %ebx # if (b==0) return a ;
je terminal
movl $0, %edx # else
idivl %ebx, %eax # return pgcd(b, a%b)
pushl %edx
pushl %ebx
call pgcd
terminal:
movl %ebp, %esp
popl %ebp
ret
et la factorielle :
29
.section .text
.globl fact
#--------------------------------------------------------------------------------
fact:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %ebx # n dans %ebx
# resultat dans %eax
cmpl $0, %ebx # if (n<=0)
jle terminaison # return 1
decl %ebx # else
pushl %ebx # return fact(n-1)
call fact # ...
popl %ebx # ...
incl %ebx # ...
mull %ebx # * n
jmp fini
terminaison:
movl $1, %eax
fini:
movl %ebp,%esp
popl %ebp
ret
14 Fichiers standard
Pour acceder aux fichiers, il faudra utiliser des appels au systeme par l’intermediaire le l’interruption0x80. pour le systeme, un fichier est un entier (dit descripteur de fichier), dont le numero est obtenulors de l’operation d’ouverture, a l’exception de certains fichiers qui sont ouverts par defaut (dont :l’entree standard (0), la sortie standard (1) et la sortie d’erreur standard (2)). Le plus simple est decommencer par ceux-ci, ce qui permettra deja de faire des saisies et des affichages.
14.1 Ecriture
Pour ecrire, il faut utiliser la fonction 4 de l’interruption. Les specifications d’utilisation sont les suiv-antes :
30
nom EAX EBX ECX EDX
write 4 descripteur adresse taille
La fonction d’ecriture d’une chaıne de caracteres sur la sortie standard ne presente aucune diffculte :il suffit de calculer la longueur et faire l’appel systeme (on se limitera a l’ecriture sur la sortie standard).
print:
.type print, @function
# affiche une chaine de caracteres
pushl %ebp
movl %esp, %ebp
cld
movl 8(%ebp), %edi
movl %edi, %ecx # duplique l’adresse dans %ecx pour le
# calcul de la longueur
movb $0,%al
len:
scasb
jne len
subl %ecx, %edi
decl %edi
movl %edi, %edx # longueur dans %edx
# adresse chaine dans %ecx
movl $1, %ebx # descripteur 1 = sortie standard
movl $4, %eax # fontion write
int $0x80
movl %ebp, %esp
popl %ebp
ret
#--------------------------------------------------------------------------------
_start:
.type _start,@function
pushl $message
call print
addl $4, %esp
call exit
#--------------------------------------------------------------------------------
.section .data
message:
.string "Hello World !\n"
Ce programme a pour l’equivalent en C :
31
#include <stdio.h>
main()
{
puts("Hello World !\n");
}
la compilation de ce programme donne un executable de 4847 octets. Le programme en assembleur(construit avec as et ld ne fait que 707 octets. On peut meme le faire ”maigrir” en lui appliquant lacommande strip qui supprime les symbole (on obtient alors 380 octets).
14.2 Lecture
Pour lire, il faut utiliser la fonction 4 de l’interruption. Les specifications sont identiques a celles del’appel write :
nom EAX EBX ECX EDX
read 3 descripteur adresse taille
La encore, on se limitera a la lecture sur l’entree standard, mais tout se complique, puisqu’il fautpreciser la taille du buffer d’entree, alors que les donnees ne sont pas encore entrees . . . .Le plus simple est de commencer par ecrire une fonction getchar qui ne preleve qu’un caractere a lafoisIl reste un petit probeme : lorque l’utilisateur tape control d pour indiquer la fin des entrees, aucuncaractere n’est depose par read. Pour resoudre ce probleme, on placera la valeur -1 (0xFF) avantl’appel et c’est cette valeur qui sera retournee par getchar si rien n’est depose.
32
getchar:
.type getchar, @function
pushl %ebx
pushl %ecx
pushl %edx
subl $1, %esp # buffer de taille 1 dans la pile
movl $3, %eax # appel systeme read
movl $0, %ebx # descripteur 0 = entree standard
movl %esp, %ecx # adresse buffer
movb $0xFF, (%ecx)
movl $1, %edx # taille buffer
int $0x80
movb (%ecx), %al
addl $1,%esp
popl %edx
popl %ecx
popl %ebx
ret
Pour saisir une chaıne, on cree une fonction gets a laquelle on transmet les informations necessaires al’appel read ( la taille du buffer et son adresse). Cette fonction appelle getchar jusqu’a ce que celle-cirenvoie ’\ n’ ou -1 (qui ne seront pas ajoutes). chaque caractere autre est ajoute a condition que lataille limite ne soit pas atteinte. (on choisira de ne pas arreter les appels a getchar pour consommertous les caracteres excedentaires).
33
get:
.type get, @function
pushl %ebp
movl %esp, %ebp
pushl %ebx
pushl %ecx
pushl %edx
pushl %edi
movl 8(%ebp), %edi # adresse chaine dans %edi
movl 12(%ebp), %edx # taille maxi dans %edx
decl %edx # garder une place pour le 0 terminal
movl $0, %ecx # taille reelle dans %ecx
get_suiv:
call getchar
cmpb $0xFF, %al
jne autre
movl $-1, %eax # EOF ?
jmp fin_get # oui : fini
autre:
cmpb $10, %al # ’\n’ ?
je fin_chn # oui : fini
cmpl %ecx, %edx # taille limite atteinte ?
je get_suiv # oui : on passe a la lecture suivante
stosb # non : on stocke
incl %ecx
jmp get_suiv
fin_chn: # ajout du 0 terminal
movb $0,(%edi)
fin_get:
popl %edi
popl %edx
popl %ecx
popl %ebx
movl %ebp, %esp
popl %ebp
ret
34
15 Fichiers quelconques
15.1 Lecture et ecriture
Si le descripteur est connu, il suffit de proceder comme precedemment en le passant comme argumentsupplementaire, ce qui donne, pour la lecture :
read:
.type read, @function
pushl %ebp
movl %esp, %ebp
pushl %ebx
pushl %ecx
pushl %edx
movl $3, %eax # appel systeme read
movl 8(%ebp), %ebx # descripteur
movl 12(%ebp), %ecx # adresse buffer
movl 16(%ebp), %edx # taille buffer
int $0x80
# le nombre d’octets lus est place dans %eax
popl %edx
popl %ecx
popl %ebx
movl %ebp, %esp
popl %ebp
ret
On peut remarquer que cette fonction est similaire a la fonction read du C : int read(int fd, const void*buf, size t count); .Pour la fonction write, on procede de meme.
15.2 Ouverture et fermeture
Pour les fichiers nommes (comme les fichiers sur disque), il faut obtenir un descripteur avec l’appelsysteme open
nom EAX EBX ECX EDX
open 5 nom mode permissions
close 6 descripteur
Le mode d’ouverture est 0 (lecture seule), 1 (ecriture seule) ou 2 (lecture et ecriture). Pour les
35
ouvertures autorisant l’ecriture, on peut combiner la valeur (1 ou 2) avec
• 0100 : creer le fichier s’il n’existe pas (O CREAT en C)
• 0200 : ne pas creer le fichier s’il existe (O EXCL en C)
• 01000 : ramener le fichier a une taille nulle s’il existe (O TRUNC en C)
• 02000 : ecrire seulement a la fin du fichier (O APPEND en C)
Les constantes sont en octal (prefixe 0). Dans le cas ou il peut y avoir creation, le 3eme parametre fixeles droits du fichier (classiquement 0644). Pour eviter de memoriser ces valeurs, il est possible de lesdeclarer de la facon suivante :
.equ O_RDONLY, 0
.equ O_WRONLY, 1
.equ O_RDWR, 2
.equ O_CREAT, 0100
.equ O_EXCL, 0200
.equ O_TRUNC, 01000
.equ O_APPEND, 02000
.equ DROITS, 0644
La fonction open peut s’ecrire ainsi :
open:
.type open, @function
# ouvre un fichier
# 1er argument : nom
# 2e argument : mode d’ouverture
# retourne : descripteur dans %eax
# ou valeur n\’egative si esrreur
pushl %ebp
movl %esp, %ebp
pushl %ebx
pushl %ecx
pushl %edx
movl $5, %eax
movl 8(%ebp), %ebx # nom
movl 12(%ebp), %ecx # mode
movl $DROITS, %edx # droits
int $0x80
popl %edx
popl %ecx
popl %ebx
movl %ebp,%esp
popl %ebp
ret
36
L’ecriture de la fonction close ne presente aucune difficulte . . .
15.3 Programme d’illustration
Pour illustrer l’utilisation des fichiers, voici un petit programme qui va copier un fichier vers un autreen mettant son contenu en majuscules. En plus des fonctions deja citees, il suffit d’ecrire une fonctioncopie maj prenant en argument deux descripteurs de fichier, l’un en lecture, l’autre en ecriture. Pourles operations de lecture et d’ecriture, on definit, dans la zone des donnees non initialisees, un bufferpour le transfert des donnees.
37
copie_maj:
.type copie_maj, @function
pushl %ebp
movl %esp, %ebp
pushl %ebx
pushl %ecx
pushl %edx
pushl %esi
pushl %edi
copie_suiv:
# chargement d’un bloc de donnees
pushl $MAX # taille
pushl $buffer
movl 8(%ebp), %eax # descripteur source
pushl %eax
call read
addl $12, %esp
cmpl $0, %eax # echec lecture ?
je copie_fin # on sort
leal buffer, %esi
movl %esi, %edi
movl %eax, %ecx # nombre d’octets lus dans %ecx
movl %eax, %ebx # et memorises dans %ebx
maj:
lodsb
cmpb $’a’, %al
jl range
cmpb $’z’,%al
jg range
andb $0b11011111, %al # passage en majuscule
range:
stosb
loop maj
# ecriture du buffer
pushl %ebx # taille
pushl $buffer
movl 12(%ebp), %eax # descripteur destination
pushl %eax
call write
add $12, %esp
jmp copie_suiv
copie_fin:
popl %edi
popl %esi
popl %edx
popl %ecx
popl %ebx
movl %ebp, %esp
popl %ebp
ret
#-------------------------------------------------------------------------------
.section .bss
.equ MAX, 4000
.lcomm buffer, MAX
38
Il reste a ecrire la fonction principale. Celle-ci va verifier que deux arguments ont ete transmis par leshell, ouvrir le 1eren lecture, le 2eme en ecriture et afficher les messages d’erreur en cas de probleme.
39
.globl _start
_start:
.type _start, @function
push %ebp
movl %esp, %ebp
movl 12(%ebp), %esi # 1er argument ?
cmpl $0, %esi # si absent , exit
je err_arg
pushl $O_RDONLY # ouverture fichier source
pushl %esi
call open
addl $8, %esp
cmpl $0, %eax # resultat <0 : on sort
jle err_r_open
movl %eax, %ecx # %ecx : descripteur source
movl 16(%ebp), %esi # 2e argument ?
cmpl $0, %esi # si absent , exit
je err_arg
pushl $O_WRONLY|O_EXCL|O_CREAT # ouverture fichier destination
pushl %esi
call open
addl $8, %esp
cmpl $0, %eax # resultat <0 : on sort
jle err_w_open
movl %eax, %edx % edx : descripteur destination
pushl %edx
pushl %ecx
call copie_maj # appel a la fonction de copie
addl $8, %esp
pushl %ecx # fermetures
call close
pushl %edx
call close
addl $8, %esp
jmp fini
err_r_open:
pushl %esi # message : erreur ouverture source
call print
pushl $msg_r_open
call print
addl $8, %esp
jmp fini
err_w_open:
pushl %esi # message : erreur ouverture destination
call print
pushl $msg_w_open
call print
addl $8, %esp
jmp fini
40
err_arg: # message : pas assez d’arguments
pushl $usage
call print
movl 8(%ebp), %esi
pushl %esi
call print
pushl $msg_args
call print
addl $12, %esp
fini:
movl %ebp, %esp
pop %ebp
call exit
#--------------------------------------------------------------------------------
.section .data
msg_r_open:
.string " : lecture impossible\n"
msg_w_open:
.string " : ecriture impossible\n"
usage:
.string "Usage : "
msg_args:
.string " <source> <destination>\n"
41
A Utilisation du debogueur gdb
gdb permet de controler de deroulement d’un programme et de visuliser l’etat des registres et desdonnees. Pour pouvoir utiliser gdb, il faut d’abord assembler avec l’option –gstabs ( ou compiler com-piler avec l’option -ggdb de gcc.
~ > as --gstabs truc.s -o truc.o; ld truc.o -o truc
~ > gdb truc
Il est egalement possible d’utiliser gdb depuis Emacs avec la commande Esc x gdb
A.1 Deroulement
La premiere chose a faire pour examiner le deroulement du programme par gdb est de le demarreravec la commande r (run). il est possible de preciser les arguments de la ligne de commande ex: rdonnees.txt 5Dans ce cas, le programme s’execute d’une traite. Pour l’executer pas a pas, il faut poser des pointsd’arret (breakpoints) avec la commande b (break) en precisant le nom de la fonction ou le numero dela ligne. Dans le cas d’un programme multifichiers, il faut eventuellement preciser en plus le nom dufichier avec la sytaxe b <fichier>:<fonction> ou b <fichier>:<ligne>
Lorsque le programme demarre, il s’execute jusqu’au premier point d’arret. Il est alors possibled’executer les instructions une a une en tapant n (next). Dans ce cas, un appel de fonction est comptecomme une instruction. Pour entrer dans la fonction, il faut taper s (step).
Un numero est attribue a chaque point d’arret. Le point d’arret n peut etre desactive avec dis n(disable) et reactive avec ena n (enable). Pour voir la liste des points d’arret, taper info breakpointsenfin, pour supprimer un point d’arret, utiliser cl (clear) en precisant le nom de la fonction ou lenumero de la ligne et pour tous les supprimer, taper del (delete).
Pour reprendre l’execution jusqu’au prochain point d’arret, taper c (continue).
Il est egalement possible de sauter a la fin d’une boucle avec u (until) ou la fin d’une fonction avec f(finish)
On peut definir une suite de commandes <C1>, <C2>, .. <Ck> au point d’arret <n> de la faconsuivante :
commands <n>
<C1>
<C2>
..
<Ck>
end
42
Pour quitter gdb, taper q (quit)
A.2 Affichage
On peut afficher un registre (en prefixant en nom par $), ou une donnee avec la commande p (print).On peut imposer un format en le prefixant par /. Ce format peut etre : d (decimal), x (hexadecimal),t (binaire), c (caractere), a (adresse), f (float). Par exemple :p/t $eax affiche eax en base 2 et p /c
liste affiche le contenu de l’adresse liste sous forme de caractere. Pour avoir les 10 premiers elementsde liste, il suffit de taper p /c liste @10 .Pour un symbole X, on peut obtenir son adresse avec p &(X). Pour un registre contenant une adresse(esi par exemple, on peut visualiser ce qui se trouve a cette adresse avec p *($esi) @20
Si on veut connaıtre une (ou des) valeur(s)a tout moment, on peut utiliser disp (display) a la placede print. Dans ce cas, l’affichage se fera a chaque arret de l’execution (et donc a chaque instructionsi on utilise step ou next). Comme pour les points d’arret, on peut desactiver, reactiver ou annuler lacommande display avec les commandes disable, display, enable, display et undisplay.
Il est egalement possible d’afficher la pile d’appel avec la commande bt (backtrace)
Pour les donnees en memoire, on peut aussi utiliser x (examine) avec la syntaxe x /nft ou n estle nombre d’elements, f le format et t la taille. Le format peut etre : d=decimal, u=decimal nonsigne, x=hexa, t=binaire, f=float, a=adresse, s=chaıne (pas de taille a preciser). La taille peut etre :b=octet, h=demi-mot, w=mot, g=double mot (exemple : x /12uh &liste.
Pour afficher tous les registres, il suffit d’utiliser info registers
B Creer une bibliotheque
Il est possible de regrouper tous les fichiers objet dans une bibliotheque. Pour cela, il suffit d’utiliserla commande ar (archive) avec la syntaxe.
ar cr <fichier bibliotheque> <fichier objet> ...
Exemple : ar cr libperso.a *.o. (Si on veut utiliser une bibliotheque nommee xxx, il faudra ap-peler le fichier libxxx.a)Pour que le linker trouve plus facilement les symboles (toutes les etiquettes placees dans les fichierssource), on utilise la commande ranlib (ranlib <fichier bibliotheque>) qui ajoute un fichier d’index
On peut connaıtre les symboles d’un fichier objet ou d’une bibliotheque avec la commande nm nm<fichier>). Chaque symbole est marque par une lettre : T pour les fonctions globales, U pour les
43
symboles externes, t pour les etiquettes locales, B,D ou R pour les donnees globales (des sections bss,data et rodata), b,d ou r pour les donnees locales.Remarque : pour ne visualiser que les fonctions globales ou externes referencees, il suffit de filtrer avecnm | cut -c 10- | grep ^[TU]
Pour utiliser la bibliotheque libperso.a ( supposee situee dans le repertoire ~/Lib86) pour linkeravec un fichier objet prog.o par exemple), il suffit de taper la commande :ld -L~/Lib86 prog.o -lperso (l’option -l doit etre a la fin)
44