Annonce

Réduire
Aucune annonce.

Programmer en Assembleur (ASM) avec nasm

Réduire
X
 
  • Filtre
  • Heure
  • Afficher
Tout nettoyer
nouveaux messages

  • Tutoriel Programmer en Assembleur (ASM) avec nasm

    Ceci est un premier jet de moi même et de necromoine, qui recherche des avis quand à la qualité de la rédaction (mais pas de l'orthographe qui sera corrigé ultérieurement) et des explications techniques.


    Programmer en Assembleur

    1) - Introduction


    Le langage d'assemblage, abrégé ASM (soit assembleur, par un abus de langage fortuit que je me permettrait lors ce tutoriel), est le langage le plus bas niveau compréhensible par l'homme. Du temps du pron en pixel art, des bonne grosses disquettes noires, plus brièvement du temps où on se la pétait grave avec quelques Mo de ram, ce langage était l'un des plus utilisés. Mais aujourd'hui avec l'avènement des langages de haut niveau, facilitant la dure tâche du programmeur, une part d'ombre s'est installée. Si bien que les pauvre petits hardcoders dans l'âme se sont réfugiés comme ils le pouvaient pour faire perdurer leur espèce, quitte à forniquer seulement entre eux, créant ainsi une pseudo-élite.

    Comme déclaré plus haut, affirmer que l'on programme en assembleur est une faute impardonnable, qui mériterai flagellation à coups d'orties et de ronces. En réalité l'assembleur est le programme qui réalise la coordination entre les mnémoniques et le binaire correspondant, produisant donc le code machine. Le langage d'assemblage quant à lui est le code produit avant l'étape de l'assembleur, c'est à dire les mnémoniques compréhensibles par les idiots que nous sommes.
    Je l'ai également dit plus haut, je me permettrai de parler d'assembleur, effectuant ainsi l'indigne et inexcusable erreur. Non pas que j'aime me faire fouetter avec des ronces et des orties, mais c'est une formule plus commode et surtout beaucoup plus utilisée.

    Dans ce tutoriel, j'aborderai, ou plutôt je vous ferai assimilez de force, les notions principales de la programmation en langage d'assemblage, en utilisant l'assembleur NASM. Qui exploite la syntaxe Intel.

    La totalité de cet écrit se situera sur un système GNU/Linux, pour ainsi combler le manque de documentation française à ce sujet.




    2) – les outils



    Durant toute notre fulgurante ascension de niveau en assembleur, nous utiliserons différent outils indispensables. Pas seulement un IDE (je vous laisserai relativement libre quant à ce choix), mais aussi et surtout un assembleur, un linker, et bien évidemment un debugger. Ce dernier étant très important. Vous verrez vous même ; vous y passerez plus de temps que sur votre IDE.

    2.1) L'assembleur

    Souvenez-vous mes chères, on en a déjà parlé. Dans cet incroyable tutoriel nous utiliserons l'assembleur nasm (the netwide assembler).
    http://nasm.us/


    2.2 ) Le linker

    Intéressons nous maintenant à un point plus pointu dans la création d'exécutable, le 'linkage' (ce terme n'est pas traduisible en français, j'utiliserais donc ce barbarisme à chaque fois).
    Le linkage est l'étape située après l'assemblage du programme, cette étape consiste à lier les fichiers nécessaire à l'exécutable entre eux, l'exemple le plus simple sont les bibliothèques nécessaire au bon fonctionnement du programme (interface graphique par exemple).

    Il existe deux type de linkage, le premier, s'intitule le linkage statique, qui permet d'intégrer au code de l'application, celui de la bibliothèque, ainsi, votre programme de dépendra d'aucun autre fichier et sera plus facile à distribuer. En contre partie, votre exécutable
    sera plus lourd car il contiendra forcément plus de donnée.

    L'autre type de linkage est, comme vous pouvez le deviner, le linkage dynamique, il fournira un code et un exécutable plus léger, mais il sera nécessaire de distribuer les fichiers de la bibliothèque en même temps que votre programme, le meilleur exemple pour l'illustrer ce linkage sont les DLL de windows, sans lequel les programmes ne fonctionnent pas.

    Ainsi dans le cas d'un linkage statique, lors du lancement de l'application cette dernière est certaines de trouver les fonctions nécessaires à son exécution, tandis que sur un linkage dynamique, l'application doit vérifier la présence des fichiers contenant le code de la bibliothèque nécessaire à son fonctionnement.
    http://www.tenouk.com/ModuleW_files/...rlinker001.png


    Pour linker notre fichier (plus besoin d'expliciter le terme j'espère), nous utiliserons ld, de la famille des GNU binutils, en version 2.22. Soit la dernière à la future lointaine époque d'écriture de ce papier. Si votre version, pour X raison (distrib' pourries mal mise à jour), est obsolète, ne partez pas tout de suite, la différence sera normalement infime, pour l'utilisation que nous en faisons.




    2. 3) Le debugger


    Une légère parenthèse s'impose. Qu'est-ce qu'un debugger ?

    Enfaîte, le concept est assez simple ; un debugger permet, à chaque instant, de voir ce qui ce passe à l'intérieur d'un programme (état des registres, de la mémoire du programme). Littéralement, debugger signifie « qui enlève les bugs »
    Celui-ci vous permettra de résoudre vos erreurs. Et croyez-moi, vous en ferez tout un tas.

    Nous nous servirons du célèbre et réputé gdb, Le GNU DeBugger, qui doit sûrement être de base installé sur votre distribution. Le cas échéant il se trouve inévitablement dans vos dépôts.




    3) – Généralités ennuyeuses


    Certains d'entre vous ont probablement bondis sur leur chaise en lisant le terme mnémoniques dans l'introduction et c'est pour cette raison que nous allons nous attarder sur la signification de ce mot.

    D'après notre cher Wikipédia, ce terme désigne : 'l’ensemble des méthodes permettant de mémoriser par association d’idées' ce que nous résumerons dans notre domaine spécifique par la traduction de données binaires incompréhensibles par l'homme en 'mot clé' qui nous sont plus familier. En assembleur, on retrouve des termes tels que MOV, JNE, JMP qui désignent des actions effectuées par le programme lors de son exécution.

    Une ligne basique d'assembleur en syntaxe Intel, pourrait être résumé comme ceci :

    instruction destination, source

    L'ensemble des instructions disponible de votre microprocesseur est appelé le jeu d'instruction.
    Une instruction pourra être une opération mathématique, un déplacement de valeur (d'une variable à une autre...), etc...

    Imaginons une addition ;

    add eax, 2

    add est l'instruction
    eax est la destination
    2 est la source

    Cette instruction ajoutera donc 2 au registre eax.



    3.1) Les registres




    Il vient sûrement à votre génial cerveau une digne question ; qu'est-ce qu'un registre ?

    Il vient au mien une réponse :
    Un registre est une zone de mémoire interne de votre micro-processeur très rapide. Son coût élevé de fabrication nous empêche d'en posséder à profusion.
    Normalement, votre processeur en contient un petite dizaine.
    Dans ce tutoriel, nous utiliserons des registres 32 bits (soit 4 octets). Dont les principaux : eax, ebx, ecx, edx.
    Tout ces registres sont des registres étendues (le e signifie extented), leurs « équivalent » 16bits, seraient ax, bx, cx, dx.
    Et chacun de ces registre 16 bits peut être subdivisé en 2 registres 8 bits. Al et ah, bl et bh, cl et ch, dl et dh.

    Pour être clair et schématique, ce sont des variables accessibles rapidement, qui vont nous être bien utiles.


    3.2) Les systèmes de numération

    En informatique, votre ordinateur très simple d'esprit, ne comprend que les chiffres, ainsi lorsque vous tapez un texte, celui-ci est compris par la machine sous forme de numéro, que l'on retrouve dans la table ASCII par exemple (http://www.asciitable.com/).

    En nous basant sur cette même table, nous allons étudier les différents systèmes de numération qu'il est nécessaire de connaître avant de continuer la lecture de ce dossier (bien que rien ne vous en empêche, c'est un conseil).

    Intéressons nous tout d'abord à la notation décimal, celle que nous utilisons tous les jours, celle de la base 10. La base 10, qu'est ce que c'est ? On appelle base, le nom de caractères différents qui constituent un 'alphabet', c'est à dire qu'a partir des caractères qui composent la base, vous pouvez obtenir n'importe quel caractère. La base 10 s'appelle ainsi car comme vous pouvez le deviner, elle est composée de 10 nombre principaux, c'est à dire de 0 à 9 (et oui, 10 est composé d'un 1 et d'un 0 ). Je ne détaillerais pas plus cette base car si vous avez les compétences pour lire ces lignes, alors vous maîtrisez probablement la base 10.

    Le second système de numération dont nous aurons besoin est celui de la base 16 qui correspond au système hexadécimal, composé de 16 caractères, qui sont les suivants (dans l'ordre croissant): 1,2,3,4,5,7,8,9,A,B,C,D,E,F, ainsi la valeur correspondante à 10 en système décimal s'écrirait A en hexadécimal, que l'on retrouve dans la numérotation des offsets par exemple (que nous étudierons plus loin).

    Le troisième et dernier système de numérotation que nous aborderons ici est celui du binaire, celui de la base 2 composé uniquement de deux nombres : le 0 et le 1. Ce système de numérotation est celui utilisé par votre ordinateur lorsque qu'il lit les instructions, je ne détaille pas plus ces systèmes de numérotation, car nous aurons l'occasion d'en reparler au fur et à mesure de notre dossier.

    Pour résumer, je pense que ce tableau est très pratique :
    http://www.positron-libre.com/cours/...exadecimal.gif

    Vous utiliserez ces bases à profusion, autant bien les maîtriser.

    3.3)- Les sections


    Tout fichier binaire est divisé en un certain nombre de sections. Pouvant contenir différents éléments comme du code, des données initialisées ou non-initialisées...
    La langage d'assemblage étant très proche du langage binaire, la subdivision sera donc visible et décrite dans votre code.
    Voyons les principales :

    La section data dans laquelle vous pourrez mettre les variables dont la valeur est connue avant la compilation (des constantes par exemple).

    La section bss qui vous permettra de réserver de l'espace mémoire pour une variable que vous utiliserez ultérieurement.

    La section text qui contiendra votre code.

    Ce qui nous permet de dégager une armature typique d'un programme asm :

    section .data
    ; vos données initialisées

    section .bss
    ; vos données non-initialisées

    section .code
    ; votre code


    Le « ; » représente les commentaires (et non la fin d'une instruction).




    3.4)- Les syscalls



    Un syscall (abréviation de system call), est, comme son nom l'indique, un appel au système, afin de lui faire effectuer une tâche définie. Cette tâche peut être de lire du texte, d'en écrire, de lire un fichier, d'exécuter un programme, etc...
    En bref une panoplie de tâches rudimentaires, qui constituent la base de toute – ou quasiment toute – les tâches du système.

    Si vous avez déjà des connaissances basiques en programmation vous connaissez le principe des fonctions. Si vous n'avez aucune connaissance en programmation, je pense qu'il serai préférable de s'atteler à un langage un peu moins agressif que celui-ci, histoire de se familiariser avec les notions fondamentales de la programmation.

    Rappelons qu'une fonction s'utilise afin d'effectuer une tâche précise, en lui donnant parfois des arguments, soit des données, qui lui serviront pour l'effectuer.

    Un syscall est donc une fonction, banale dans son utilisation, un peu différente dans sa structure, qui utilise différent arguments.

    Le numéro de la tâche à effectuer se met dans le registre eax.
    Le premier argument dans ebx, le second dans ecx, le troisième dans edx, etc...
    Puis on appelle le kernel, via le « kernel interrupt », int 80h

    L'instruction qui servira à entrer une valeur dans un registre est l'instruction mov

    On fera donc :

    mov eax, [numéro du syscall]
    mov ebx [premier argument]
    […]
    int 80h

    La valeur de retour du syscall se trouvera dans eax.


    La correspondance entre numéro de syscall et fonction peut être retrouvée ici :
    http://bluemaster.iu.hio.no/edu/dark.../syscalls.html
    Ou dans le fichier /usr/include/asm/unistd.h


    Prenons un exemple simple ; écrire du texte.
    Le numéro du syscall write est 4.
    Le numéro de la sortie standard (votre écran) est 1, et il doit être le premier argument.
    La chaîne à écrire doit être le second argument.
    La taille de la chaîne donc être le troisième.

    Avec ce que nous avons déjà vu, vous devriez être capable d'esquisser le code de ce syscall :

    mov eax, 4
    mov ebx, 1
    mov ecx, chaine
    mov edx, TailleChaine
    int 80h


    Second exemple, que vous devrez utiliser dans ton vos programmes, le syscall exit, pour terminer le programme.
    Son numéro est le 1
    un seul argument ; le code d'erreur. 0 signifie qu'il n'y a pas d'erreurs.

    Soit ;
    mov eax, 1
    mov ebx, 0
    int 80h

    Allez, finis le théorique, passons à la pratique.


    4) - Hello World

    Une fois n'est pas coutume, commençons notre périple par une humble salutation internationale.


    Nous avons déjà vu la décomposition des deux syscalls utile pour ce que nous voulons faire (et oui, les exemples n'étaient pas pris au hasard), Il nous suffira de déclarer la chaîne et sa taille dans la section data, et d'utiliser ces syscalls.

    section .data
    chaine: db "hello world !", 10, 13
    tailleChaine: equ $-chaine

    section .text
    global _start

    _start:
    mov eax, 4
    mov ebx, 1
    mov ecx, chaine
    mov edx, tailleChaine
    int 80h

    mov eax, 1
    mov ebx, 0
    int 80h


    3 nouvelles choses :
    Pour déclarer une donnée, on utilise son nom, suivi de deux points, de son « type », et de sa valeur.
    chaine: db "hello world !", 10, 13
    Ici nous déclarons variable nommée chaîne, de valeur "hello world !", 10, 13
    10 et 13 sont les valeurs de CR (Carriage Return) et LF (Line Feed), dans la table ascii, qui représentent un saut de ligne.

    pour déclarer la taille de la chaîne, nous utilisons une petite astuce , plutôt que de l'écrire en dur. Nous utilisons le symbole $, qui signifie le début du programme, au quel on soustrait la variable chaîne (son adresse), ce qui nous renvoi sa taille.

    le global _start représente le point d'entrée du programme (l'oep, Original Entry Point). Là ou il va commencer, et donc le code commence après le _start :


    Pour compiler ce programme il faut d'abord l'assembler avec nasm :

    $ nasm -f elf hello.asm

    Puis le linker avec ld :

    $ ld -o hello hello.o -melf_i386

    Vous pouvez ensuite le lancer :
    $ ./hello
    hello world !

    C'est fait ! Votre premier programme en langage d'assemblage a été exécuté avec succès.
    Dernière modification par kallimero, 12 décembre 2011, 21h47.

  • #2
    Génial. Si y'a bien une chose où j'suis vraiment nul (pire encore^^), c'est en ASM !

    Merci !
    sigpic

    Cyprium Download Link

    Plus j'étudie plus j'me rends compte que je n'sais rien.

    †|

    Commentaire


    • #3
      Très bien, mais en hexadecimal, comme son nom l'indique si bien, il y a 16 chiffres, et de même que pour la base 10, il y a donc le 0 au début !

      Sinon, très bon article, claire, facile à comprendre. Ca pète !

      Encore une choses, mes connaissances sont limitées, mais tu parles de jeu d'instruction au début. J'imagine que ça fait référence au jeu d'instruction du processeur (MOV, NOP, JNE...). Mais ce jeu est différent entre l'architecture x86 (processeurs 8086) et l'architecture x64 (merci AMD ).

      Donc, si je voulais compiler ce programme en compatibilité x86/x64 ou même uniquement pour du x64, comment devrais-je faire ?

      L'assembleur serait-il le même ? Si non, quel devrait-il être ?
      Dernière modification par comaX, 13 décembre 2011, 23h36.

      Commentaire


      • #4
        Je fait effectivement bien référence au jeu d'instruction du processeur.
        de l'assembleur 64 bit ne changera pas radicalement du 32 bits. Le plus gros pas en avant est surtout la mise à disposition de plus de registres généraux, et de l'extension de ceux-ci (en 64 bits, donc. Soit rax, rbx, rcx, rdx...).

        Un proco 64bits supportera du 32bits, mais pas l'inverse. Pour compiler avec nasm du 64bits, il suffit de faire nasm -f elf64 fichier.asm, et de linker normalement.
        En revanche si tu veux compiler du 64bits sur une archi 32bits, c'est au linkage que tu auras des problèmes.

        J'espère avoir répondu à tes questions.
        Dernière modification par kallimero, 14 décembre 2011, 19h10.

        Commentaire


        • #5
          Ouip. Mais ceci dit les elf sont les exécutables linux, comment ferait-on pour windows ? nasm -f exe64 ? (Monsieur pifomètre bonjour).

          Sinon, petit problème : je n'ai pas trouvé d'assembleur 64 bits tout court. Je veux dire par là qu'il a été compilé pour du 32, sans compatibilité, donc il tourne pas (message d'erreur me disant grosso-merdo ça). Une idée ?

          Commentaire


          • #6
            Si tu souhaites t'initier à nasm sous système windows, je te conseille ce tutoriel : http://esauvage.developpez.com/tutor...tel-avec-nasm/
            Il y a aura pas mal de différences avec l'assembleur sous GNU/Linux.

            C'est à l'assemblage que tu as ce problème, ou au linkage ?

            Commentaire


            • #7
              C'est au lancement de nasm. Je le lance dans l'invite de commande, et ça m'affiche un joli message comme quoi il est pas fait pour les version 64bits de Windows...

              Merci pour le tuto, je vais lire ça !

              Commentaire


              • #8
                Envoyé par comaX Voir le message
                Ouip. Mais ceci dit les elf sont les exécutables linux, comment ferait-on pour windows ? nasm -f exe64 ? (Monsieur pifomètre bonjour).

                Sinon, petit problème : je n'ai pas trouvé d'assembleur 64 bits tout court. Je veux dire par là qu'il a été compilé pour du 32, sans compatibilité, donc il tourne pas (message d'erreur me disant grosso-merdo ça). Une idée ?
                On ferait plutôt:
                Code:
                nasm -f win64 fichier.asm [...]
                Et puis, linkage.

                Commentaire


                • #9
                  Merci. Je vais le lire tranquillement et m'y remettre..

                  Commentaire

                  Chargement...
                  X