Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
codaz:asm:l_assembleur_pour_lutins_presses_2 [2007/09/07 09:05]
jfg
codaz:asm:l_assembleur_pour_lutins_presses_2 [2010/01/12 13:29] (current)
Line 1: Line 1:
 +====== L'ASM efficace pour lutins pressés =======
  
 +===== PART II =====
 +
 +
 +Dans ce second opus nous allons mettre la barre légèrement plus haut
 +et tenter de faire des choses un peu plus utiles.
 +
 +N'​hésitez pas à vous servir du tuto précédent si certains éléments
 +vous échappent.
 +
 +
 +==== I - Le code mystérieux et magique ====
 +
 +Le programme suivant se contente de prendre les arguments qu'on lui
 +passe et de les afficher sur la sortie standard.
 +La suite du turoriel permettra de le déchiffrer.
 +
 +<​code>​
 +1      section .text
 +2         ​global _start
 +
 +4      section .rodata
 +5         ​newline:​ db 0x0a
 +
 +7      _start:
 +8         add esp, 4
 +9     
 +10     ​encore:​
 +11        add esp, 4
 +12        cmp dword [esp], 0
 +13        jz fin_encore
 +14        call affiche_string
 +15        jmp encore
 +16
 +17     ​fin_encore:​
 +18        mov eax, 1
 +19        xor ebx, ebx
 +20        int 80h
 +21
 +22     ​affiche_string:​
 +23        push dword [esp + 4]
 +24        call asm_strlen
 +25        add esp, 4
 +26        mov edx, eax
 +27        mov ecx, [esp + 4]
 +28        mov eax, 4
 +29        mov ebx, 1
 +30        int 80h
 +31
 +32        mov eax, 4
 +33        mov ebx, 1
 +34        mov ecx, newline
 +35        mov edx, 1
 +36        int 80h
 +37
 +38        ret
 +39
 +40     ​asm_strlen:​
 +41        mov eax, [esp + 4]
 +42        xor ecx, ecx
 +43     
 +44     ​count_chars:​
 +45        inc ecx
 +46        cmp byte [eax+ecx-1],​ 0
 +47        jnz count_chars
 +48        dec ecx
 +49        mov eax, ecx
 +50        ret
 +</​code>​
 +
 +Aaahhh rien de tel qu'un bon petit code source incompréhensible...
 +
 +
 +==== II - LigneParLignajation... Terriblement efficace... ====
 +
 +Première ligne, bon là je l'ai déjà expliqué.
 +
 +__Ligne 5__, on déclare une constante, newline. Chaque fois que nasm
 +rencontrera dans notre code le mot "​newline"​ il le remplacera par
 +0x0A qui est la valeur ascii du fameux "​\n"​.
 +
 +Nous voilà lancé, et déjà première question: ça veut dire quoi
 +"add esp, 4" en __ligne 8__?
 +Et bien c'est très simple, vous savez tous ce que signifient argc et
 +argv dans la ligne "int main(int argc, char **argv)"​ ?
 +
 +En fait lorsqu'​on lance un programme, tout en haut de la stack se trouve
 +argc, puis vient ensuite argv[0], puis argv[1], etc... soit :
 +
 +<​code>​
 +     ESP = argc
 +     ESP+4 = argv[0]
 +     ESP+8 = argv[1]
 +     ...
 +     ​ESP+4+4*argc = argv[argc] = NULL
 +</​code>​
 +
 +Attention ceci n'est pas toujours vrai, nous verrons l'​autre configuration
 +possible dans le prochain tutoriel. Cette configuration est valable
 +uniquement lorsqu'​on utilise "​_start"​ et donc qu'on link avec ld.
 +
 +Que fait donc notre "add esp, 4" ? Il se contente "​d'​éliminer"​ en quelque
 +sorte argc de notre "champ de vision"​ ou, pour parler en termes techniques
 +, de notre stack frame (délimitée par esp et ebp).
 +
 +Après cette commande notre stack qui était structurée comme ci dessus
 +deviens :
 +
 +<​code>​
 +     ESP = argv[0]
 +     ESP+4 = argv[1]
 +     ​etc...
 +</​code>​
 +
 +__Ligne 10__, notre premier label nommé "​encore"​. Un label permet aux
 +instructions de saut (jmp, jnz, etc...) d'​accéder à une partie du
 +code.
 +
 +__Ligne 11__, si vous avez bien compris ce que j'ai tenté de vous expliquer
 +plus haut, add esp, 4 aura pour effet d'​enlever argv[0] de la stack frame.
 +Pourquoi ? Car argv[0] c'est le nom du programme et on s'en fout.
 +
 +__Ligne 12__, cette ligne permet de voir si esp pointe pas sur NULL, ce
 +qui signifierais qu'il n'y a pas ou plus d'​arguments passés au
 +programme. NULL = 0, d'où la comparaison avec 0.
 +
 +__Ligne 13__, jz signifie Jump If equal Zero. Et donc si ZF est à 0, ce
 +qui signifie que le résultat de CMP n'est pas 0, sinon cela signifierai
 +qu'il n'y a plus d'​arguments passés au programme et qu'il peut se
 +terminer, d'où le label "​fin_encore",​ qui, devinez quoi, appelle exit.
 +
 +__Ligne 14__, vous découvrez l'​instruction call, qui appelle la "​procedure"​
 +affiche string. Nous y reviendrons.
 +
 +__Ligne 15__, une fois la procedure affiche_string terminée, le cpu
 +retourne dans la partie "​encore",​ et l'​instruction jmp se contente
 +de boucler à nouveau, pour vérifier s'il y a encore des arguments
 +à afficher.
 +
 +__Ligne 22__, "​déclaration"​ de la procédure. C'est un label tout simplement
 +mais vous remarquerez l'​instruction "​ret"​ qui indique donc qu'il
 +s'agit d'une procédure ou fonction, et non pas d'un simple label.
 +
 +__Ligne 23__, Push ! c'est quoi ce machin bidule?
 +
 +''​PUSH ELEMENT''​\\
 +Cette instruction permet d'​ajouter un élément sur la pile.
 +
 +''​POP REGISTRE''​\\
 +Cette instruction permet d'​enlever un élément de la pile et de le 
 +placer dans REGISTRE ou une variable au préalable allouée.
 +
 +Lorsqu'​on utilise l'​instruction CALL, celle-ci va PUSHer sur la stack
 +l'​adresse de retour. En gros, le CPU a besoin de savoir où il va devoir
 +reprendre une fois la fonction terminée, du coup il met l'​adresse
 +sur la pile.
 +
 +Ceci implique qu'il ne faut jamais écraser cette valeur!
 +(enfin si vous utilisez ret, sinon on s'en fout, mais c'est pas propre).
 +Il faut impérativement la conserver, sinon vous allez avoir droit
 +a un comportement totalement indéfini. Le processeur va prendre
 +le premier truc sur la pile et exécuter ce qu'il trouve à cette
 +adresse, si ça n'a aucun sens, la plupart du temps ce sera un SIGSEGV.
 +
 +Revenons à notre __ligne 23__. Le mot clé dword indique simplement
 +que ce que nous allons PUSHer est un double mot, sur 32 bits donc.
 +Lorsqu'​on utilise [ et ] en syntaxe intel cela signifie que l'on
 +fait référence à la mémoire.
 +
 +Traduction de "push dword [esp + 4]" : \\
 +Mettre en haut de la stack ce qui se trouve à l'​adresse ESP+4, de
 +taille 32 bits.
 +Bon, la traduction est pas super, mais vous aurez compris quoi.
 +
 +En fait on ne fait que copier argv[1] en haut de la stack :
 +
 +<​code>​
 +     ESP= argv[1]
 +     ​ESP+4= return_addr
 +     ​ESP+8= argv[1]
 +     ​etc...
 +</​code>​
 +
 +Pourquoi est-ce qu'on fait ça ?
 +Car avant d'​utiliser CALL, ce qu'on PUSHe sur la stack ce sont
 +les arguments que l'on passe à la fonction.
 +
 +__Ligne 24__, ici on appelle la fonction asm_strlen, qui comme vous
 +vous en doutez est une implémentation d'​strlen en assembleur.
 +Si vous vous souvenez bien, strlen est définie comme ça :
 +"​size_t strlen(const char *s);"
 +
 +Et bien notre implementation sera identique et prendra en argument
 +un pointeur sur un char, et retournera un size_t, un entier quoi.
 +
 +NOTE: Nous allons le voir plus bas, mais sachez qu'une fonction met
 +sa valeur de retour dans le registre EAX. Et donc, pour le formuler
 +autrement, après un CALL, eax contiendra la valeur de retour de
 +la fonction appellée.
 +
 +__Ligne 25__, on enlève l'​élément PUSHé sur la stack en ligne 23.
 +Il faut TOUJOURS enlever les arguments que vous avez mis sur la pile.
 +Soit par un add, soit pas un pop.
 +
 +__Ligne 26__, on met la valeur de retour, et donc la lenght de notre
 +cher argv[1] dans edx. Oui puisque le but du programme et d'​afficher
 +les arguments qu'on lui a passé, on va forcément faire appel à write
 +et donc comme on l'a vu dans la PART I, pour appeller write on a 
 +besoin de 4 registres. EAX qui contiendra le chiffre de l'apel systeme,
 +ici 4, ebx qui contiendra le descripteur de fichier sur lequel
 +écrire, ici 1 pour stdout, ecx qui contiendra un char *, et edx
 +qui contiendra le nombre de caractères à écrire, et donc la len de notre
 +string calculée par asm_strlen, dont la valeur se trouve dans eax après
 +le call et que l'on met bien évidemment dans edx.
 +
 +Si vous me dites que vous avez besoin que j'​explique encore une fois
 +je vous met mon poing sur la figure en toute amicalité aucune.
 +
 +__Ligne 27__, notre fameux argv[1] est mis dans ecx.
 +
 +__Ligne 28__, 4 pour write dans eax.
 +
 +__Ligne 29__, 1 pour stdout dans ebx.
 +
 +__Ligne 30__, appel du kernel, allez hop, bosses un peu fénéant.
 +
 +Les lignes qui suivent son facilement déchifrables suite aux
 +explication précédentes. Mais au cas où certains se poseraient des
 +questions, on appelle write pour afficher un "​\n"​...
 +
 +__Ligne 41__, ici on met le paramètre passé à notre fonction dans eax.
 +Ici cet argument cera notre argv[1]... il persiste lui...
 +
 +''​XOR''​\\
 +Cette instruction effectue un OU exclusif des deux opérandes
 +et sotcke le résultat dans celle de gauche.
 +
 +__Ligne 42__, lorsqu'​on a ce genre de lignes "xor eax, eax", et donc
 +que les deux opérandes sont le même registre, cela signifie qu'on
 +le met à 0. Ici donc ecx est mis à 0.
 +
 +__Ligne 44__, un petit label qui va nous permettre de compter le nombres
 +de caractères de la string passée en paramètre.
 +
 +__Ligne 45__, on incrémente ecx, qui va contenir le nombres de caractères
 +de la string.
 +
 +J'ai choisi ECX, mais j'​aurais pu en choisir un autre hein..
 +
 +__Ligne 46__, ici on vérifie si string[ecx-1] != \0, le fameux \0 de fin
 +de chaine. EAX contient l'​adresse du premier caractère de la string,
 +ECX est un peu comme un i en C.. vous savez les i++ qui trainent
 +dans tous les codes sources.. Ben ici c'est pareil, sauf que i c'est
 +ECX. Le ++ se situe en ligne 45 avec l'​instruction inc...
 +
 +__Ligne 47__, si donc ZF est à 0, cela signifie qu'il y a encore des
 +caractères et qu'on peut continuer à incrémenter. Du coup loop sur
 +count_chars.
 +
 +__Ligne 48__, on décrément ECX, que nous avions incrémenté injustement
 +en __ligne 45__, pour avoir le nombre exact de caractères.
 +
 +On met ce fameux résultat dans EAX, oui car comme je l'ai dis plus
 +haut, une fonction met la valeur de retour dans EAX.
 +
 +Puis on utilise RET pour que le CPU retourne au contexte d'​appel.
 +C'est à dire qu'on sort de la fonction asm_strlen.
 +
 +Bon, c'​était pas si dur ?
 +Si vous avez des zones d'​ombres,​ relisez tout bien, et essayez de
 +comprendre le code par vous même. Vous pouvez aussi essayer de 
 +rajouter quelques fonctionalités.
 +
 +Dans la troisième et sans doute dernière partie nous étudierons
 +la conception d'un serveur socket en Assembleur, ce qui j'​espère
 +vous excite un peu quand même :)
 +
 +Tous les liens sympas que j'ai sur l'​assembleur je les mettrai
 +en fin de la troisième partie.
 +
 + --- //​[[jf.guchens@gmail.com|jfg]] 2007/09/21 00:29//
codaz/asm/l_assembleur_pour_lutins_presses_2.txt · Last modified: 2010/01/12 13:29 (external edit)