TP & TD Assembleur 8086/8088 en Doc
![TP & TD Assembleur 8086/8088 en Doc](/images/remos_downloads/previews/defprev.jpg)
MicroInformatique
Informatique Industrielle
Assembleur 8086/8088
TP n°1
I. BUT DU TP
Se familiariser avec les registres du 8086 et certaines instructions de base
II. Présentation de DEBUG
DEBUG est un programme fournit avec MSDOS. Dans un premier temps, nous allons utiliser DEBUG pour consulter/modifier les registres, pour afficher/modifier le contenu de la mémoire et enfin pour éditer/exécuter des programmes élémentaires.
a)Chargement de DEBUG
- Si vous êtes déjà sous DOS, taper DEBUG + Retour Chariot (CR)
- Si vous êtes sous WINDOWS, Relancer la machine sous DOS ou ouvrir une fenêtre DOS. Taper alors DEBUG + Retour Chariot (CR).
- Le prompt suivant apparaît: -
b)Consultation des registres du processeur
DEBUG permet de consulter les registres du microprocesseur. Pour cela taper la commande R et observer l'affichage. Noter les registres qui apparaissent et leur contenu.
CS et IP représentent l'adresse du programme dans la mémoire.
Que vaut la valeur des segments CS et IP ? Calculer l'adresse réelle (en décimal et en hexadécimal) que représente ce couple segment:déplacement.
Donner 5 autres couples Segment:Déplacement permettant d'atteindre la même adresse réelle (justifier vos propositions).
Taper la commande R reg où reg est un registre (Ax par exemple). Le contenu du registre spécifié apparaît. Editer à la suite un nombre de 16 bits en hexadécimal (ex: ABFD). Relancez la commande R reg et vérifier que le registre contient bien le nombre.
c)Edition de lignes de programme
Remettre 0000 dans le registre Ax. Taper la commande A 100 pour commencer à entrer un programme à la position 100 du segment définit dans CS.
Editez les lignes suivantes:
mov ax,a0a1 (CR)
mov bx,b0b1 (CR)
mov cx,c0c1 (CR)
mov dx,d0d1 (CR)
(CR)
d)Désassembler la mémoire
Vérifier que vos lignes sont bien en mémoire en tapant la commande u 100. DEBUG analyse le code machine (40 octets) qui se situe à partir de l'adresse cs:100 et déduit les instructions en langage source 8086.
e)Exécuter n lignes de programme
Pour exécuter n lignes de programme à partir de l'adresse cs:100, il faut exécuter la commande t=100 n. Dans notre application, il y a 4 lignes donc taper t=100 4.
Observer l'évolution de l'exécution de votre programme et notamment l'évolution du contenu des registre ax, bx, cx et dx.
Que vaut IP à l'issue de l'exécution du programme ?
Conclusion sur le rôle du registre IP.
Remettre les registres ax, bx, cx et dx à 0000 et lancer votre programme en utilisant la commande G=100 10C(signifie lancer le programme qui se trouve à l'adresse cs:100 et placer un point d'arrêt à l'adresse cs:10C).
f)Scrutation de la mémoire
Le commande D permet de scruter en hexadécimal la mémoire. Exemple D 100 permet de scruter 128 octets à partir de l'adresse cs:100.
Vérifier que vos 4 lignes de programme sont toujours présentes à partir de l'adresse cs:100. Lancer ensuite la commande D 100. En vous aidant de l'annexe A, montrer que les 12 premiers octets correspondent au code de vos lignes de programme. Conclusion concernant l'ordre de stockage des mots de 16 bits.
g)Exercices
1) Ecrire le programme suivant: (l'instruction PUSH reg place dans la pile le registre reg. L'adresse de la pile est définie par le couple SS:SP)
mov ax,35
mov bx,41
mov cx,30
mov dx,6
push ax
push bx
push cx
push dx
add ax,bx
add cx,dx
int $20
Exécuter le programme et vérifier son bon fonctionnement.
Retrouver les valeurs de ax, bx, cx et dx qui ont été sauvés dans la pile.
Conclusion concernant l'ordre de stockage dans la pile et l'ordre de stockage des parties hautes et basses des mots de 16 bits.
2) Soit le programme suivant:
mov al,17
mov cl,14
add cl,al
int $20
Transcrire le programme en hexadécimal en s'aidant de l'annexe A. Pour cela on utilisera la commande E qui permet d'éditer du code hexadécimal en mémoire. (Exemple: E 100, modifier l'octet se trouvant en cs:100 puis espace pour éditer l'octet suivant ou CR pour quitter la commande).
3) L'interruption 21 permet dans une de ses variantes d'afficher un caractère à l'écran. Pour cela il faut mettre 02 dans ah et le code ASCII du caractère dans dl. Ensuite on appelle l'interruption par l'instruction int $21. Ecrire un programme qui écrit BONJOUR à l'écran. (Le code ASCII du A est 41h).
4) L'interruption 21 permet quand ah = 08 d'attendre le premier caractère tapé au clavier. Le caractère se trouve alors dans al. Ecrire un programme qui lit le premier caractère tapé au clavier et qui l'affiche à l'écran.
Les commandes de DEBUG.
Lancer debug. |
Sous dos, on peut lancer Debug de plusieurs façons:
debug
lance debug sans charger de programme; permet d'assembler une ligne pour avoir le code.
debug <programme avec extension>
lance debug en chargeant un programme. C'est la forme la plus courante, qui permet de l'analyser. On peut lancer debug sans rien et charger un programme après coup, mais c'est beaucoup plus facile ainsi.
debug <programme avec extension> <paramètres>
lance debug en chargeant un programme et en donnant des paramètres au programme. Si un programme se lance en faisant gcc -g -c etape.c -o etape.o, si on veut le débugger, on fait debug -g -c etape.c -o etape.o (notez l'extension qui est rajoutée).
On peut en plus rediriger l'entrée, c'est à dire donner les commandes dans un fichier plutôt que par le clavier. Mettre alors le symbole inférieur suivi du nom du fichier. debug < <fichier> lance debug en prenant les commandes dans un fichier. Attention ce fichier doit contenir une commande q, sinon vous ne pourrez plus sortir de debug. Exemple debug < a.txt b.txt ou debug b.txt < a.txt lance debug en chargeant la mémoire avec le programme ou le fichier b.txt et prend les commandes dans a.txt. Ceci permet d'assembler un programme, si on n'a pas compris que mon site vous en propose un mieux (et gratuit!).
On peut aussi rediriger la sortie, c'est à dire donner le résultat dans un fichier plutôt qu'à l'écran. Mettre alors le symbole supérieur suivi du nom du fichier. debug > <fichier> lance debug en envoyant les résultats dans un fichier. Exemple debug > a.txt b.txt ou debug b.txt > a.txt lance debug en chargeant la mémoire avec le programme ou le fichier b.txt et écrit les résultats dans a.txt. Ceci permet de désassembler dans un fichier (pour analyse). On n'a pas d'écho à l'écran, on a donc tout intérêt à appeler debug sans redirection, de tout noter, et de rediriger la sortie. On peut aussi utiliser un fichier d'entrée.
debug < a.txt > b.txt c.exe lance debug pour analyser le programme c.exe, en prenant les commandes dans a.txt et en mettant les sorties dans b.txt.
La commande de redirection de sortie efface le fichier si il existait. Pour écrire à la suite sans effacer le début, mettre deux symboles supérieurs au lieu d'un. Pour information, ceci n'est pas spécifique à debug, c'est du dos!
Sous windows, je conseille de faire un raccourci et de mettre derrière le nom du programme un point d'interrogation. Ainsi si vous cliquez sur le raccourci, windows demande ce qu'il doit mettre derrière debug.
Une fois lancé, debug nous donne un tiret d'invite. Les commandes ont toutes une lettre avec une suite de paramètres. Il n'y a pas de différences entre les majuscules et les minuscules. Toutes les valeurs numériques sont en hexadécimal.
Les paramètres des commandes. |
<adresse>
L'adresse peut s'écrire sous un seul nombre <offset> ou sous un couple de nombres <segment>:<offset>. Il est possible de donner comme segment le mot DS ou CS. Si on ne précise pas de segment, c'est DS qui est pris par défaut, sauf pour les commandes Go, Load, Trace, Unassemble et Write (ce qui concerne le code en fait). Quelques adresses valides: 40:17 A000:0000 1000 CS:1000
<bloc>
C'est un ensembles d'adresses dans un même segment. Il y a deux formes:
<première adresse> <dernière adresse> ou <première adresse> L <longueur en octet>. La première adresse peut avoir un segment. La deuxième non.
<port>
C'est une adresse d'un port d'entrée sortie. Par exemple 378 pour les données de l'imprimante.
<octet>
C'est une donnée sur 1 ou 2 chiffre hexa.
<liste d'octets>
C'est une suite d'octets séparés par des espaces (ou des virgules).
<mot>
Nombre de 1 à 4 chiffres hexa.
<nom de fichier>
Donne au moins le nom et l'extension d'un fichier. On peut y mettre le chemin.
<chaîne>
Une chaîne est délimitée par des apostrophes ou des guillemets. Pour mettre un apostrophe dans une chaîne, utilisez les guillemets comme délimiteurs. Si vous voulez les deux types, doublez le délimiteur comme en C ou en Pascal.
<registre> Nom d'un registre.
<disque>
0 représente le lecteur A:, 1 le lecteur B:, 2 le disque C: et ainsi de suite.
<secteur>
Si on a nb_tetes têtes et nb_secteurs secteurs par cylindres, pour le secteur sect du cylindre cyln de la tête tet porte le numéro sect+nb_secteur*(tet+nb_tetes*cyln). Le premier secteur porte le numéro 0.
<nombre de secteurs>
Nombre entre 1 et 80 (on ne peut pas lire ou écrire en une fois plus de 80 secteurs. Je rappelle que les secteurs sont de 512 octets.
Commande A (assemble). |
Syntaxe: A <adresse>
debug reçoit ligne par ligne les instructions à assembler. On finit en donnant un retour chariot supplémentaire. On ne peut pas donner d'étiquettes.
MOVSB s'applique à des octets, MOVSW à de mots.
RET est un retour court, RETF un long
BYTE PTR et WORD PTR permettent de spécifier la taille dans certains cas: INC BYTE PTR [100]
DB et DW sont supportées.
Commande C (compare). |
Syntaxe C <bloc> <adresse>
Compare le bloc défini par <bloc> avec le bloc qui débute à <adresse> (de la même taille). Il n'y a pas de message si ils sont identiques.
Exemple C 100 110 200 ou C 100 L10 200 compare les blocs (:110) avec (:210).
Commande D (dump soit vider). |
Syntaxe D ou D <adresse> ou D <bloc>
Donne la suite d'octets du bloc défini. Si on spécifie une adresse seule, le bloc fera 128 octets. D sans paramètres "dump" les 128 octets suivants.
Commande E (enter). |
Syntaxe E <adresse> ou E <adresse> <liste d'octets>
Si on donne une liste d'octets, il sont mis en mémoire à partir de <adresse>. Sinon, on peut donner les différents octets (l'ancien est affiché). Une valeur donnée remplace l'ancienne, un espace conserve l'ancienne valeur, retour chariot termine la commande et le symbole moins permet de revenir en arrière.
Commande F (fill). |
Syntaxe F <bloc> <liste d'octets>
Remplit le bloc avec la suite d'octets. La suite est répétée pour tout remplir.
Exemple: F 100 200 1 2 3 donne 100: 1 2 3 1 2 3 1 2 3 1 2 3 jusqu'en 200 inclus.
Commande G (go). |
Syntaxe G [=<adresse1>] [<adresse2> ]
Exécute le programme à partir de l'adresse 1 si elle est spécifiée (elle est facultative) ou à partir de CS:IP si elle n'est pas indiquée. Il y a des points d'arrêt aux adresses <adresses2> (on peut mettre entre = et 10 points d'arrêt qui doivent être le premier octet d'une instruction).
Exemples:
G -> lance à partir de CS:IP
G = 100 -> lance à partir de CS:100
G 100 -> lance à partir de CS:IP et s'arrête si on tombe sur l'adresse 100
G = 100 200 300 400 -> lance à partir de 100 et s'arrête si on passe par 200 300 ou 400.
Commande H (hexa). |
Syntaxe: H <mot1> <mot2>
Affiche <mot1>+<mot2> et <mot1>-<mot2>
Commande I (input). |
Syntaxe I <port>
Affiche le contenu de l'adresse d'entrée sortie <port>
Commande L (load). |
Syntaxe L ou L <adresse> ou L <adresse> <disque> <secteur> <nombre de secteurs>
L utilisée seul charge le programme (on peut donner le nom par la commande N). Le programme est chargé à partir de <adresse> si elle est donnée (ignorée pour les .exe). par défaut, un .com est chargé en 100h. BX:CX donnent la taille du fichier chargé. Si on donne le disque, le secteur et le nombre de secteur, on charge alors des secteurs d'un disque.
Commande M (move). |
Syntaxe M <bloc> <adresse>
Déplace le bloc à partir de l'adresse spécifiée.
Commande N (name). |
Syntaxe N <nom de fichier>
La dernière commande entrée donne le nom du fichier qui peut être lue ou écrite par les commandes L ou W. La commande N permet aussi de donner les paramètres, mais je déconseille son usage.
Commande O (output). |
Syntaxe O <port> <octet>
Écrit à l'adresse entrée sortie spécifiée un octet.
Commande P (proceed). |
Syntaxe P [=<adresse>] [<mot>]
Exécute <mot> instructions (1 si mot n'est pas spécifié), en commençant à <adresse> ou en CS:IP si <adresse> n'y est pas. Cette commande ressemble à T (trace), sauf qu'elle considère INT CALL et les LOOP comme si c'était une seule instruction. Ceci permet de "passer" sur les INT 21.
Commande Q (quit). |
Syntaxe Q
Termine la cession.
Commande R (registre). |
Syntaxe R [<registre>]
Affiche et permet de modifier le contenu d'un registre ou affiche tous les registres si aucun nom n'est spécifié. Le registre des flags s'appelle F. Si on donne RF, l'état des flags s'affiche et on peut changer les flags individuellement: OV, DN, EI, NG, ZR, AC, PE et CY permettent de mettre à un les flags Overflow, Direction, Interruption, Signe, Zéro, Retenue auxiliaire, Parité et Retenue. NV, UP, DI, PL, NZ, NA, PO et NC permettent de les remettre à zéro.
Commande S (search). |
Syntaxe S <bloc> <liste d'octets>
Recherche les occurrences de la liste d'octets dans le bloc. Aucun message n'est affiché si debug ne trouve rien.
Commande T (trace). |
Syntaxe T [=<adresse>] [<mot>]
Exécute <mot> instructions (1 si mot n'est pas spécifié), en commençant à <adresse> ou en CS:IP si <adresse> n'y est pas. Cette commande ressemble à P (proceed), sauf qu'elle entre dans les INT CALL et traite les LOOP en plusieurs fois. Ceci permet de tracer l'intérieur des fonctions.
Commande U (unassemble). |
Syntaxe U ou U <adresse> ou U <bloc>
Désassemble un bloc. Si on spécifie une adresse seule, le bloc fera 20 octets. Si on ne spécifie rien, on désassemble les 20 octets suivants.
Commande W (write). |
Syntaxe W ou W <adresse> ou W <adresse> <disque> <secteur> <nombre de secteurs>
Sauve sur disque BX:CX octets à partir de <adresse> si elle est donnée ou à partir de 100 sinon. Le nom du programme est celui qui à été chargé ou le dernier nom donné par la commande N. Si on donne le disque, le secteur et le nombre de secteur, on écrit alors des secteurs sur un disque. Je ne répond de rien dans ce cas, c'est une commande très dangereuse sur disque dur.