l'assembleur  [livre #4602]

par  pierre maurette



DOS & debug
premiers programmes

Dans ce chapitre, nous allons tester nos premiers programmes en assembleur ou, plus modestement, nos premières suites d'instructions, et observer le microprocesseur en action.

Nous allons nous limiter ici à l'exploitation des ressources présentes sur nos machines. Nous utilisons une version de Microsoft Windows, 9X à XP en passant par NT et 2000. Tous ces systèmes, et d'autres, proposent l'utilitaire DEBUG, que nous utiliserons quasi exclusivement dans ce chapitre.

Si cet outil est tout à fait adapté à une première prise de contact, il n'est pas conçu pour l'écriture de véritables programmes. Néanmoins, la minuscule application que nous développerons en exemple, qui nous permettra de connaître les deux codes (ASCII et scancode) délivrés par chaque touche du clavier, générera un véritable exécutable.

Les conseils généraux concernant la configuration de nos machines et de DOS seront utiles pour la suite de l'apprentissage. Il est encore, à l'heure de la rédaction de cet ouvrage, pratiquement impossible de tout ignorer de ces antiquités si nous voulons nous lancer efficacement dans l'étude de l'assembleur.

Certains lecteurs penseront que ce chapitre est "surexpliqué". Ils auront parfaitement raison. Qu'ils songent simplement aux autres lecteurs qui le trouveront "sous-expliqué". Il est certain qu'un programmeur un peu aguerri pourra coder l'exemple kb_codes.com à la volée, surtout s'il dispose d'outils plus performants que DEBUG. Mais il sera un jour content d’arriver presque mécaniquement à un résultat en utilisant ces méthodes.

3.1 MS-DOS dans le PC sous Windows

Avant d'aborder l'utilitaire DEBUG, nous allons examiner notre machine et la configurer pour la rendre plus agréable dans le cadre d'utilisation que nous envisageons. Nous considérons que le lecteur intéressé par la programmation assembleur sait comment, d'une façon ou d'une autre, initier une session MS-DOS, ou "Invite de commandes". Nous allons cependant décrire deux façons de travailler, une voie normale et une voie sécurisée.

Conseil

Optimisez la configuration de Windows

Si vous utilisez l'Explorateur Windows :

*           Dans l'Explorateur (menu Outils/Options des dossiers/Affichage ), vérifiez que tous les fichiers – y compris les fichiers cachés – sont bien affichés.

*           Vérifiez également que l'option Masquer les extensions des fichiers dont le type est connu n'est pas cochée.

Parler d'une version de Windows, c'est tenir compte de la version initiale, mais également des mises à jour et des logiciels Microsoft de premier rang installés ultérieurement, particulièrement Internet Explorer.

Allez dans : Démarrer/Paramètres/Panneau de configuration , module Ajout/Suppression de programmes , Dans Installation de Windows , vérifiez les composants installés.

Calculatrice , Aperçu rapide ( Accessoires ) par exemple prennent peu de place sur le disque et sont fort pratiques.

Pour tester Aperçu rapide , cliquez du bouton droit sur un fichier .exe et choisissez l'option Aperçu rapide du menu contextuel. Ce choix n'étant proposé que pour certaines extensions, il peut être bon de créer un raccourci vers C:\WINDOWS\SYSTEM\VIEWERS\QUIKVIEW.EXE et de le déposer dans le dossier C:\WINDOWS\SendTo . Vous pourrez ainsi appliquer quikview à tous les fichiers, par l’option Envoyer vers du menu contextuel.

Si vous portez de l'intérêt au graphisme, testez la fonction Loupe dans Accessibilité .

3.1.1 Pour lancer des sessions DOS sous Windows

Selon les systèmes d'exploitation, une session DOS se lance par un raccourci Commandes MS‑DOS ou Invite de commandes . Il est possible d'éditer les propriétés de ces raccourcis, pour modifier le répertoire actif au lancement, le mode Fenêtre ou Plein écran, le nombre de lignes, etc.

Paramétrage d'une session DOS dans A:
figure 3.01 Paramétrage d'une session DOS dans A:

Comme sur cette capture d'écran, vous pouvez créer plusieurs raccourcis et éditer leurs propriétés en fonction de vos goûts et besoins. Vous pourrez ainsi ouvrir une fenêtre DOS en fenêtre 25 lignes, directement sur la disquette A:, par un raccourci, une autre en Plein écran dans C:\essais , etc. Nous avons vu, au chapitre précédent, que chacune de ces fenêtres représente une session complète en mode V86, Virtuel 8086. Il s’agit donc en gros d’un IBM PC indépendant, mais contrôlé en mode Protégé par le système d'exploitation. Vous pouvez, une fois dans la fenêtre dite DOS, utiliser la commande  MEM pour connaître la mémoire à la disposition de la session. Sous Windows 98SE, vous obtenez :

Type de mémoire     Totale   Utilisée      Libre
----------------  --------   --------   --------
Conventionnelle       640K        64K       576K
Supérieure              0K         0K         0K
Réservée                0K         0K         0K
étendue (XMS)      65 535K          ?   391 928K
----------------  --------   --------   --------
Mémoire totale     66 175K          ?   392 504K
 
Total inférieur       640K        64K       576K
 
Totale Paginée (EMS)              64M (67 108 864 octets)  
Mémoire libre paginée (EMS)       16M (16 777 216 octets)  
 
Taille maximale du programme exécutable      576K (590 064 octets) 
Taille max. de la mémoire supérieure libre     0K      (0 octets)  
MS-DOS réside en mémoire haute (HMA).

Remarquez qu'un gestionnaire EMS est installé. Ce qui n'est pas le cas pour ce résultat, obtenu dans une fenêtre Invite de commandes sous Windows 2000 :

    655360 octets de mémoire conventionnelle
    655360 octets disponibles pour MS-DOS
    626352 taille maximale du programme exécutable
 
   1048576 octets de mémoire étendue contiguë
         0 octets disponibles de mémoire étendue contiguë
    941056 octets disponibles de mémoire XMS
           MS-DOS réside en mémoire haute (HMA)

En plus de ces fenêtres ouvertes au milieu d'une session Windows, vous pouvez utiliser la possibilité de redémarrer en mode MS-DOS, jusqu'à Windows 98SE. Dans ce cas, la commande MEM donne :

Type de mémoire     Totale   Utilisée      Libre
----------------  --------   --------   --------
Conventionnelle       640K        61K       579K
Supérieure              0K         0K         0K
Réservée                0K         0K         0K
étendue (XMS)     392 128K        68K   392 060K
----------------  --------   --------   --------
Mémoire totale    392 768K       129K   392 639K
 
Total inférieur       640K        61K       579K
 
Taille maximale du programme exécutable      578K (592 368 octets) 
Taille max. de la mémoire supérieure libre     0K      (0 octets)  
MS-DOS réside en mémoire haute (HMA).

 

N'hésitez pas à ouvrir plusieurs fenêtres simultanément sur le même répertoire, pour modifier un programme, le sauvegarder (commande  w comme nous le verrons) puis le tester dans une autre fenêtre. C'est d'ailleurs la seule façon de vraiment tester un programme. Autre avantage : une fenêtre DOS est une tâche contrôlée par Windows ; en cas de blocage, il suffira de "tuer" cette fenêtre puis de la redémarrer. Nous verrons cela plus en détail avec la prise en main de DEBUG.

Terminons cette présentation par une astuce, permettant d’ouvrir une fenêtre DOS directement dans un répertoire. Vous pouvez, pour cela, créer un raccourci et éditer son répertoire de travail ; mais si vous en changez souvent, c'est fastidieux.

Le but de cette manipulation consiste à créer un élément dans le menu contextuel (clic droit) attaché à un répertoire ou un lecteur de disque.

Ouverture d'une session DOS dans A:
figure 3.02 Ouverture d'une session DOS dans A:

Pour cela, il suffit d’ajouter une clé dans la base de registre :

HKEY_CLASSES_ROOT\Directory\shell\Fenêtre MS-DOS\command = command.com

et, pour la même chose à la racine des partitions :

HKEY_CLASSES_ROOT\Drive\shell\Fenêtre MS-DOS\command = command.com

Sous Windows 2000 (et XP), il faut changer command.com en CMD.EXE  :

HKEY_CLASSES_ROOT\Directory\shell\Fenêtre MS-DOS\command = command.com

Et :

HKEY_CLASSES_ROOT\Drive\shell\Fenêtre MS-DOS\command = command.com 

Ces paramétrages sont contenus dans les fichiers DOS_PROMPT98.reg et DOS_PROMPT2000.reg qui se trouvent sur le CD-Rom. Double-cliquer sur ces fichiers installe la modification. Testé sous Windows 98SE et Windows 2000 Pro. Certains utilitaires du type "tweaker" se chargent de cette tâche. L'idéal, et ce devrait devenir un réflexe, est d'étudier le fichier  .reg , de le critiquer, de le modifier et de procéder manuellement à la modification de la base de registre. Il est généralement, à juste titre, déconseillé de double-cliquer sur un  .reg que vous n'avez pas écrit.

Si au lancement de la session, les touches  Flèche haut  et  Flèche bas  ne répètent pas les dernières commandes, pensez à taper doskey (sous Windows 9x).

Sous Windows 2000, cmd /? affichera une aide précieuse et cmd /? >cmd.txt sauvera cette aide dans un fichier texte.

Dans le même ordre d'idées, il semble indispensable de pouvoir envoyer n'importe quel fichier, quelle que soit son extension, vers par exemple un éditeur, idéalement un éditeur de texte et un éditeur hexadécimal. Au minimum, le Bloc-notes de Windows. Certains éditeurs, comme UltraEdit32, installent un raccourci dans le menu contextuel (clic droit) de l'Explorateur. De plus, cet éditeur affiche également en vidage hexadécimal. Si vous n'avez rien de tout cela, cherchez sur votre disque système un dossier nommé SendTo  : C:\WINDOWS\SendTo ou D:\Documents and Settings\Votre_Nom\SendTo . Copiez dans ce dossier un raccourci vers le ou les éditeurs choisis. Vous aurez ainsi accès à ce ou ces outils par le menu contextuel, option Envoyer vers .

Cette façon de travailler est de loin la plus agréable : multi-fenêtrage, utilisation simultanée de divers utilitaires comme un éditeur hexadécimal, la Calculatrice ou le Bloc-notes, et accès au CD-Rom, voire à internet.

3.1.2 Pour lancer DOS au boot

Si vous voulez absolument, sur une machine familiale ou professionnelle par exemple, expérimenter en ne prenant aucun risque, vous pourrez booter directement à partir d'une disquette. Il sera même possible d‘inhiber les disques durs, soit physiquement soit à partir du Setup du BIOS. Si cette phrase vous semble obscure, faites-vous aider. Si elle est limpide pour vous, vous n'avez sans doute pas besoin de ce luxe de précautions.

C'est une approche réaliste dans le cas, par exemple, d'un micro-ordinateur multi-utilisateur, pour permettre à un adolescent de bricoler comme il l'entend en préservant la sérénité familiale. Les disques durs sur tiroir ("en rack") sont alors un must.

Mais avant d’inhiber quoi que ce soit, il nous faut préparer une disquette qui nous permettra de booter. Celle-ci nous servira également, si nous expérimentons sous Windows, en lançant nos sessions DOS dans 'A:'. Enfin, devant un programme qui ne fonctionne pas dans l'environnement Windows, il sera parfois utile de savoir si, par hasard, il ne fonctionnerait pas sous DOS seul. C'est un élément de diagnostic intéressant.

Conseil

Attention à l'état de vos disquettes !

La disquette est un média dont l'usage est devenu rare. Dans le cas fréquent de réutilisation d'anciennes disquettes, sachez qu'elles se dégradent si elles sont conservées longtemps sans être exploitées. Il est possible que cette dégradation provienne de moisissures, qui vont ensuite encrasser de façon sérieuse le lecteur. Donc, avant utilisation, procédez à un formatage complet et jetez toute disquette qui présente des secteurs défectueux, même si la capacité résiduelle est satisfaisante. Cela est particulièrement important si vous utilisez un jour ce moyen pour mettre à jour le BIOS de votre carte mère. Certains fabricants ont modifié leur protocole de mise à jour, afin que, lors de la phase "chaude" de l'opération, l’on accède non pas à la disquette mais au disque dur. Le manque de fiabilité des disquettes est certainement à l'origine de cette évolution.

Vous devrez donc formater une disquette bootable, comportant au minimum debug.exe , mais également autoexec.bat , ainsi que ce qui est nécessaire à la gestion de votre clavier français. Vous trouverez sur le CD‑Rom un dossier BOOT_DOS contenant les fichiers d'une disquette de ce type. En cas de problème, munissez-vous d'une disquette formatée (donc vérifiée) et lancez le programme faire_boot.exe . Il s’agit tout simplement du programme servant d'exemple au chapitre suivant.

Muni de la disquette de boot, il vous faut maintenant :

Éteindre votre machine.

Inhiber physiquement les disques durs, si c'est votre choix.

Remettre sous tension et entrer dans le Setup du BIOS.

Si nécessaire, inhiber les disques durs et configurer pour démarrer sur le lecteur de disquettes.

Après avoir validé le Setup, rebooter sur la disquette.

Si le disque dur est vraiment inhibé, vous pouvez maintenant faire tout ce que vous voulez sans aucun risque. Hormis celui de ne pas savoir le reconnecter.

3.2 Présentation de DEBUG

L'utilitaire DEBUG est présent dans toutes les distributions de MS-DOS, PC DOS et Windows, ainsi que OS2 ou DR‑DOS par exemple.

Outil disque utile mais parfois dangereux, piètre assembleur réduit aux instructions et mode mémoire des 8086/8088/8087, il reste un instrument pédagogique, de mise en œuvre immédiate. Et puis, même rudimentaire, DEBUG est un véritable débogueur (ou debugger), peut-être le premier que rencontrent beaucoup de programmeurs. Qu'est-ce qui, fondamentalement, caractérise un débogueur, indépendamment de ses possibilités annexes ? C'est le fait de pouvoir suivre l'exécution d'une séquence d'instructions pas à pas, en ayant à chaque pas accès aux divers registres et à la mémoire.

Après lecture du chapitre précédent, il est évident qu'il s'agit d'une forme de tromperie, à base de INT 1 et/ou INT 3. C'est en quelque sorte un simulateur, à une nuance importante près : dans le débogueur, les instructions seront réellement exécutées dans la CPU, au plus près des conditions réelles de fonctionnement, le contrôle du programme n'étant qu'une contrainte nécessaire, rendue la plus discrète possible. Un simulateur peut être autre chose, par exemple un simple programme pédagogique montrant le fonctionnement d'un processeur A sur une machine dotée d'un processeur B, voire hébergeant une machine virtuelle Java.

Attention

DEBUG est réellement dangereux

La commande  w (write), si utilisée avec plus d'un paramètre, écrit des secteurs sur une partition. Ce qui peut conduire à la perte de l'ensemble de cette partition. ABSTENEZ VOUS D'EXPÉRIMENTER ! En revanche, la commande  w utilisée seule ou avec un seul paramètre est sûre. Mais rien ne vous oblige à l'employer. Ce conseil sera volontairement répété en cours de chapitre.

Vous trouverez, sur internet ou sur le CD-Rom grdb , un clone amélioré de DEBUG, ainsi que le lien vers le site de son auteur. C'est un Freeware qui répond à la syntaxe de DEBUG, mais qui intègre quelques commandes supplémentaires, et surtout le jeu d'instructions des processeurs jusqu'aux Pentium.

Sous Windows, DEBUG se présente sous la forme d'un fichier debug.exe , d'une vingtaine de kilo-octets, dans le dossier DOS par défaut : C:\WINDOWS\COMMAND sous 9X, C:\WINNT\SYSTEM32 sous NT. Puisqu'il est dans le dossier DOS, il doit être directement accessible à partir de n'importe quelle fenêtre DOS. En cas de problème, puisque c'est un fichier autonome, il suffit de le copier sur la disquette ou le dossier de travail pour qu'il fonctionne. Attention toutefois à ce que les versions de DEBUG et du DOS soient les mêmes, ou en tout cas compatibles.

Voici une liste condensée des commandes de DEBUG. Il est lui-même considéré comme une commande du DOS ; ses commandes sont donc nommées sous-commandes ( SubCommands ) dans la documentation Microsoft officielle.

La mise entre crochets ( [paramètre] ) indique qu'un paramètre est facultatif.

DEBUG, comme en général le DOS, ne distingue pas les majuscules des minuscules en entrée, que ce soit pour les commandes, pour les paramètres ou pour les données. De même, les espaces indiqués dans la colonne syntaxe ne sont pas tous nécessaires.

Résumé des commandes de l'utilitaire DEBUG

Commande

Syntaxe

Description rapide

?

?

Affiche la liste des commandes de DEBUG.

A  (assemble)

A [adresse]

Assemble des instructions 8086/8087/8088 directement en mémoire, à partir de adresse . Sans paramètres, assemble à partir de CS:0100 ou de l'adresse où l'avait laissé la commande  A précédente.

C  (compare)

C plage adresse

Compare 2 zones de mémoire, plage et une autre de même taille commençant à adresse .

D  (dump)

D [plage]

Affiche le contenu d'une zone mémoire. Sans paramètres, affiche 128 octets à partir de CS:0100 ou de l'adresse où l'avait laissé la commande  D précédente.

E  (enter)

E adresse [liste]

Introduit des données en mémoire à partir de adresse , soit sous forme d'octets, soit sous forme de chaîne de caractères. Si liste n'existe pas, les données sont introduites en mode Interactif.

F  (fill)

F plage liste

Remplit une zone mémoire par une (ou des) valeurs.

G  (go)

G [=adresse] [stops]

Lance le programme en mémoire, à partir de adresse . Sans paramètres, le programme démarre en CS:IP. stops représente une liste de points d'arrêt.

H  (hexadécimal)

H val1 val2

Arithmétique hexadécimale 16 bits. Affiche val1 + val2 et val1 - val2 .

I  (input)

I port

Lit et affiche la valeur d'un port d'entrée, défini par son adresse port .

L  (load)

L [adresse] [lecteur] [PremierSecteur] [NbreSecteurs]

Charge en mémoire soit un fichier ( .com par exemple) soit un certain nombre de secteurs de disque.

M  (move)

M plage adresse

Copie le contenu d'un bloc mémoire plage dans un autre bloc mémoire, commençant à adresse .

N  (name)

N [chemin] [exe]

N

Gestion du nom de l'exécutable attaché à la session DEBUG en cours.

O  (output)

O port val

Envoie la valeur val (8 bits) vers le port de sortie, défini par son adresse port .

P  (proceed)

P [=adresse] [nombre]

À partir de adresse (CS:IP par défaut), effectue nombre (1 par défaut) instructions, puis affiche la valeur des registres. En fait, DEBUG place un point d'arrêt après l'instruction courante. Donc, les CALL , INT , LOOP , etc., sont exécutées entièrement (comme une seule instruction) avant de rendre la main. À cause de cette technique, P  ne peut pas être utilisé en ROM.

Q  (quit)

Q

Quitte la session DEBUG, sans sauvegarder quoi que ce soit.

R  (register)

R [registre]

Affiche la valeur du registre registre et permet de la modifier. Sans paramètre, affiche la valeur de tous les registres.

S  (search)

S plage liste

Recherche une valeur ou un motif liste dans une zone mémoire plage .

T  (trace)

T [=adresse] [nombre]

À partir de adresse (CS:IP par défaut), effectue nombre (1 par défaut) instructions, puis affiche la valeur des registres. À la différence de  P , chaque instruction est prise en compte, à l'intérieur d'une boucle, d'un sous-programme, etc. DEBUG utilise le mode Trace du 8086/8088 ; il n'y a donc pas de problème pour "tracer" en ROM.

U  (unassemble)

U [plage]

Affiche un listage désassemblé de la zone mémoire plage . Sans paramètre, la zone fait 32 (20H)octets de longueur, à partir deCS:0100 ou de l'adresse où l'avait laissé la commande U précédente. plage peut être remplacé par adresse .

W  (write)

W [adresse] [lecteur] [PremierSecteur] [NbreSecteurs]

Écrit sur le disque soit un fichier ( .com par exemple) soit un certain nombre de secteurs. C'est LA commande dangereuse, puisqu'elle permet en une ligne de rendre le disque dur inaccessible.

XA

XA [nombre]

Alloue nombre pages de mémoire EMS (si gestionnaire installé). Sans paramètre, vérifie siEMS est installé.

XD

XD [handle]

Désalloue la mémoire EMS désignée par handle . Sans paramètre, vérifie si EMS est installé.

XM

XM [lpage] [ppage] [handle]

Projette une page logique EMS lpage de handle vers la page physique ppage . Sans paramètres, vérifie si EMS est installé.

XS

XS

Affiche des informations sur l'état de la mémoire EMS.

Hormis les chaînes de caractères, saisies "chaîne" ou 'chaîne' , les données ne peuvent être saisies qu'en notation hexadécimale, sans lettre ' h ' finale. Attention, c'est le seul programme que nous utilisons dans cet ouvrage dans lequel la notation par défaut des valeurs numériques n'est pas décimale. Ce petit détail est source de quelques erreurs. Pour être clair, mov ax, 12 sous DEBUG transfère la valeur 18 ( 12h ) dans le registre AX. En revanche, la même ligne dans les assembleurs habituels comme MASM, TASM et même BASM de Delphi, transférera la valeur 12 (soit 0Ch ).

Pour les détails d'utilisation de ces commandes, reportez-vous au paragraphe suivant, et pratiquez en vous aidant du CD‑Rom.

Remarque

Une utilisation de debug.exe

DEBUG permet de sauvegarder des secteurs de disque au sein d'une procédure automatisée, dans notre cas, un fichier  .bat . Pour être plus précis, le contenu d'un ou de plusieurs secteurs est récupéré, puis sauvegardé dans un fichier. Il est également possible d'écrire un secteur sur le disque à partir de son fichier image, mais nous nous en abstiendrons pour des raisons déjà évoquées.

Éditons, sauvons sur disquette et exécutons le fichier batch saveboot.bat suivant (pour effectuer cette manipulation efficacement, il est sans doute préférable d'y revenir après la prise en main de DEBUG) :

@echo off
echo Sauvegarde du secteur de boot dans petrus.sav
REM creation d'un fichier de commandes debug
echo l 100 0 0 1 >cmd.txt
echo n petrus.sav >>cmd.txt
echo r cx >>cmd.txt
echo 200 >>cmd.txt
echo w >>cmd.txt
echo q >>cmd.txt
debug <cmd.txt
echo Sauvegarde effectuee

Selon les utilitaires de lecture hexadécimale dont vous disposez (WinHex est idéal), vous pouvez constater l'identité entre les 512 octets du fichier petrus.sav et ceux du premier secteur de la disquette d'essai.

Un fichier intermédiaire est créé : le fichier de commandes cmd.txt .

Dans le cadre d'une installation de plusieurs systèmes d'exploitation en multiboot, ce procédé est utilisable pour sauvegarder certains secteurs du disque dur, puis pour les restaurer en cas de problème, après avoir démarré la machine à partir du lecteur de disquettes.

 

3.3 Prise en main de DEBUG

Remarque

Conventions d'écriture propre à ce chapitre

DEBUG ne distinguant pas les majuscules des minuscules, les commandes seront souvent saisies en minuscules, ce qui permettra de distinguer les entrées clavier des réponses de DEBUG. Nous représenterons donc tout ce qui concerne les saisies au clavier en minuscules. En revanche, DEBUG répond (à la commande  u par exemple) en majuscules. Nous nous y conformerons. Si nous nous référons à une instruction, registre, etc., en général, nous utiliserons les majuscules comme d'habitude.

Il en sera de même pour le format des données. L'hexadécimal est implicite sous DEBUG. Dans le reste du texte, cette base est spécifiée par la lettre suffixe h. Nous parlerons en général de INT 21h , mais nous saisirons int 21 , et DEBUG désassemblera en INT 21 .

Enfin, nous avons pris la liberté d’ajouter des commentaires directement aux captures, précédés du caractère " ; ". C'est bien entendu impossible sous DEBUG.

Pour exemple, cette capture "améliorée" :

-a 100
1D2B:0100 mov ax, 1234
1D2B:0103 mov bx, 1
1D2B:0106 mov cl, 2
1D2B:0108 int 21
1D2B:010A
-u 100 109
1D2B:0100 B83412        MOV     AX,1234    ;WORD
1D2B:0103 BB0100        MOV     BX,0001    ;WORD
1D2B:0106 B102          MOV     CL,02    ;BYTE
1D2B:0108 CD21          INT     21

Nous avons utilisé les majuscules dans le tableau récapitulatif des commandes pour des raisons de lisibilité.

Partons donc à la découverte de DEBUG, de la mémoire, du microprocesseur et enfin du code assembleur, sans autre logique que de parcourir rapidement l'essentiel des commandes. Nous les testerons toutes, à l'exception toutefois des commandes de gestion de la mémoire EMS ( xa , xd , xm , xs ), des commandes d'entrée/sortie ( i , o ) et de la commande  w avec paramètres, classée Seveso 2.

Au cours de cette prise en main, ne seront rapportées que les commandes saisies au clavier et quelques réponses. À titre d'exercice, les réponses du DOS et de DEBUG seront lues sur votre écran. Une solution alternative est proposée sur le CD-Rom.

CD-ROM

Pour suivre ce chapitre sans lancer DEBUG

Cliquez sur le lien Prise en main de DEBUG –  Sorties écran dans Chapitre 3 pour consulter les sorties écran complètes de ce chapitre. Des repères CD-ROM Repère xx vous aident à vous synchroniser. Vous tirerez avantage à utiliser ce lien conjointement à une expérimentation réelle, la couleur jaune indiquant ce qui doit être saisi au clavier. Le fichier PemDEBUG.txt contient ces sorties pour impression éventuelle (pour étudier dans le train !).

Si vous vous interrogez sur l’opportunité d’effectuer une action quelconque, allez-y ! Les conséquences d'un blocage dans une fenêtre DOS concernant autre chose que cette fenêtre sont théoriquement nulles. C'est en partie à cela que sert Windows. Si vous avez démarré directement à l'aide de la disquette, il vous faudra éventuellement redémarrer votre système. N'hésitez pas à quitter DEBUG, puis à le relancer pour redémarrer "sur du propre". Une manipulation conduisant à un blocage est proposée dans ce chapitre. La seule opération réellement dangereuse utilise la commande  w avec des paramètres, qui conduit à écrire des secteurs sur le disque dur. C'est un peu comme les lamelles sous le chapeau à la cueillette aux champignons. Évitez toute utilisation de  w avec paramètres, à moins de vous sentir tout à fait compétent quant à cette commande.

Nous sommes donc, soit devant un écran DOS, soit dans une fenêtre DOS ou Invite de commandes , donc devant une émulation du PC "historique" sous DOS. Le processeur est donc un 8086, ou sa version à bus externe 8 bits 8088. Un coprocesseur 8087 est installé. Nous devrons y penser lorsque nous serons confrontés à l'impossibilité de saisir telle ou telle instruction. Dans le chapitre suivant, nous verrons le cas de l'instruction de décalage  SHR , dont certaines syntaxes n'existaient pas, sans qu'une explication évidente puisse être donnée à ce fait.

Pour la suite de ces expérimentations, nous considérons que nous sommes sur une disquette "A:", fabriquée comme indiqué précédemment, et sur laquelle sont copiés à partir du CD‑Rom les fichiers utiles.

CD-Rom Repère 01

Saisissez :

A:\>dir
 
 Le volume dans le lecteur A est ESSAI
 Le numéro de série du volume est 13E6-2357
 Répertoire de A:\
 
KEYBOARD SYS        34 566  05/05/99  22:22 KEYBOARD.SYS
COMMAND  COM        95 874  05/05/99  22:22 COMMAND.COM
DEBUG    EXE        21 178  05/05/99  22:22 DEBUG.EXE
DOSKEY   COM        15 799  05/05/99  22:22 DOSKEY.COM
KEYB     COM        20 135  05/05/99  22:22 KEYB.COM
AUTOEXEC BAT            15  09/05/02   3:03 autoexec.bat
SAVEBOOT BAT           285  19/04/02   6:16 SAVEBOOT.BAT
CMD      TXT            50  09/05/02   3:19 cmd.txt
PETRUS   SAV           512  09/05/02   3:19 PETRUS.SAV
TRY1     COM            64  10/05/02  15:22 TRY1.COM
GRDB     EXE        40 176  28/04/02  18:13 GRDB.EXE
EDIT     COM        71 102  05/05/99  22:22 EDIT.COM
        12 fichier(s)            299 756 octets
         0 répertoire(s)         931 840 octets libres

Vérifiez la présence des fichiers autoexec.bat et command.com et notez leur taille exacte.

Astuce

Obtenir de l'aide sous MS-DOS

Saisissez simplement le nom de la commande ou du programme, suivi de l'argument  /? .

Vous constatez que cette aide vous oriente vers la commande  ? , une fois que vous avez lancé DEBUG. À partir de là, vous pourriez découvrir l'utilitaire petit à petit.

Si  /? ne fonctionne pas, testez  /h ou  h . Parfois, ne pas fournir de paramètres alors que certains sont requis suffit pour obtenir l'aide.

Ces méthodes vous fourniront en général la syntaxe et la liste des options pour les assembleurs tasm32.exe , ml.exe par exemple, ainsi que tous les programmes qui gravitent autour, lieurs et autres.

De plus, vous pouvez récupérer cette aide dans un fichier :

*           debug /? > fichier.txt redirige la sortie en remplaçant le contenu de fichier.txt .

*           debug /? >> fichier.txt redirige la sortie en l'ajoutant au contenu de fichier.txt .

*           debug /? > prn envoie la sortie vers l'imprimante, mais il est préférable sous Windows de passer par un fichier et un éditeur.

Obtenir de l'aide sur une commande DOS
figure 3.03 Obtenir de l'aide sur une commande DOS
A:\>debug
-?

Vous obtenez une aide succincte sur chaque commande de DEBUG.

CD-Rom Repère 02

Observez la valeur initiale des registres grâce à la commande  r  :

-r
AX=0000  BX=0000  CX=0000  DX=0000  SP=FFEE  ....
DS=1D2B  ES=1D2B  SS=1D2B  CS=1D2B  IP=0100  ....

Vous constatez que :

  Les registres AX, BX, CX, DX, BP, DI, SI sont à 0000h .

  Les registres de segment ont une valeur quelconque, mais la même pour tous. C'est le segment qui définit la zone de 64 Ko que gère DEBUG.

  Le registre IP pointe vers 0100h . CS:0100 est effectivement l'adresse du début du programme.

  Le registre SP pointe vers FFEE , c'est-à-dire "presque en haut" du segment.

3 syntaxes pour la commande  d (display ou dump), qui affiche le contenu d'une zone mémoire :

-d

Affiche 128 octets, en 8 lignes de 16 octets. Pour chaque ligne, vous pouvez lire l'adresse du premier octet de la ligne :

1D2B:0100

Puis les 16 valeurs hexadécimales des 16 octets,

00 41 80 3E DC E2 00 74-05 B8 41 71 33 F6 BA 26

Puis enfin cette ligne en représentation texte :

.A.>...t..Aq3..&

Si l'octet ne correspond pas à un code ASCII imprimable, un point est affiché.

-d 0 ff

Affiche les FFh (256) octets de la zone mémoire comprise entre DS:0000 et CS:00FF .

-d 100 l 10

Affiche la zone commençant en DS:0100 et de longueur 10h , donc 16 octets. Attention, saisissez bien la lettre  l ou  L , et non le chiffre  1 .

-d 100 10f

Vous obtenez exactement le même résultat que la commande précédente.

Vous avez saisi une adresse simplifiée, sans mention du segment. DEBUG la complète. Pour l'instant, nous ne travaillons que sur un seul segment. Si les segments CS (code) et DS (données) sont différents, DEBUG utilise par défaut le segment dicté par la logique : en général, DS, sauf pour les commandes  a et  u pour lesquelles le CS est évident. Rappelons que le rôle initial des segments fut d'écrire dans deux registres 16 bits les composantes d'une adresse physique de 20 bits, étant entendu que le registre de segment change "peu souvent". Profitez-en pour vous tester sur l'arithmétique des segments.

CD-Rom Repère 03

Sachant que vous obtenez l'adresse physique en ajoutant un 0 à droite de la valeur du segment, et en additionnant le résultat au déplacement, le tout en hexadécimal, trouvez diverses expressions de :

-d 100 l 10

qui donnent le même résultat. Nous avons bien entendu :

-d ds:0100 l 10
-d 1d2b:0100 l 10

Les couples suivants fonctionnent également :

-d 1d3b:0 l 10
-d 1d00:03b0 l 10
-d 1c00:13b0 l 10

Vous pouvez en trouver d'autres. Si vous expérimentez sur votre machine, le DS peut ne pas avoir la même valeur ; mais le raisonnement reste le même.

 

CD-Rom Repère 04

Testez maintenant les commandes  f (fill), m (move), e (enter) et c (compare), dont l'objet est de remplir une zone mémoire, déplacer son contenu, déposer des valeurs en mémoire, et comparer deux zones mémoires. Arbitrairement, décidons que la zone 01XX est destinée à recevoir du code, 04XX une zone de données numériques, et 05XX une zone de texte.

-f 100 1ff 90
-d 100 1ff

Remplissez la zone DS:0100 à DS;01FF par l'octet  90h (il s'agit en fait du code de l'instruction  NOP , instruction qui ne fait rien), puis vérifiez.

-m 100 l 100 200
-d 200 2ff

Copiez les données de la zone DS:0100 , d'une longueur de 100h (256) octets, vers une zone de même longueur commençant en DS:0200 , puis vérifiez.

-c 100 1ff 200

Comparez le contenu de la zone DS:0100 à DS:01FF avec la zone de même longueur débutant en DS:0200 . DEBUG ne répond rien, il n'y a pas de différences.

-e 200 01 02 03
-d 200 20f

Les 3 octets 01h , 02h et 03h sont placés en mémoire aux adresses DS:0200 et suivantes, puis vérification.

-c 100 l 100 200
1D2B:0100  90  01  1D2B:0200
1D2B:0101  90  02  1D2B:0201
1D2B:0102  90  03  1D2B:0202
-m 100 10f 200
-c 100 l 100 200
-d 100 2ff

Comparaison de la zone DS:0100 de longueur 100h (256) octets avec la zone modifiée de longueur égale. DEBUG affiche les différences. Copie à nouveau de DS:0100 à DS:010F vers DS:0200 , puis vérification par  c de l'identité des zones (DEBUG ne répond rien à la commande  c ).

Vérifiez enfin que la zone complète DS:0100 à DS:02FF est bien conforme à vos attentes, par la commande  d .

CD-Rom Repère 05

Il existe plusieurs façons, dont une interactive, d'utiliser la commande  e (enter). Essayez-les :

-d 600 60f
-e 600 "petrus "
-d 600 60F
-e 600 'pierre '
-d 600 60f
-e 600 9 a b c d
-d 600 60f
-e 600 
[touche Entrée]
1D5E:0600  09.1 
[touche Entrée]
-e 600 
[touche Entrée]
1D5E:0600  01.01 
[barre d'espace]
 0A.02 
[touche Entrée]

Vous pouvez appuyer autant de fois sur la [barre d'espace] que vous le souhaitez. Expérimentez.

Tapez  u (unassemble) pour désassembler une zone mémoire :

-u 100 100
1D2B:0100 90            NOP
-u
1D2B:0101 90            NOP
.......

Vous constatez :

  Que le code 90h se désassemble bien en l'instruction  NOP .

  Que DEBUG gère un index qui fait, par la commande  u sans paramètre, démarrer le désassemblage à l'adresse suivant la dernière ligne désassemblée par la commande  u .

 

CD-Rom Repère 06

Essayez maintenant les commandes suivantes, données sans commentaire :

-f 0400 04ff 00
-d 400 l 100

pour remplir la zone de données numériques par des 00h .

-f 500 5ff 'pierre '
-d 500 5ff
-f 500 l 100 "Petrus "
-d 500 5ff

Puis enfin :

-f 500 5ff 20
-d 500 5ff

20h (32) étant le code du caractère espace.

Nous avons donc maintenant schématiquement le contenu mémoire suivant (les segments du code CS et des données DS sont les mêmes).

Carte mémoire de notre squelette
figure 3.04 Carte mémoire de notre squelette [the .swf]

Vous souhaitez sauvegarder cette configuration en tant que point de départ de vos travaux futurs ?

CD-Rom Repère 07

Saisissez :

-n
-n squelet.com
-n
-w
Erreur lors de la création du fichier
-n squelet.com
-w
Écriture de 00000 octets

Sur la disquette A:, vous constatez bien la présence d'un fichier squelet.com , de taille nulle.

Vous en concluez que :

  La commande  n sans argument réinitialise la gestion du nom et ne permet pas d'afficher ce nom, comme nous pourrions nous y attendre.

  La taille du fichier généré par la commande  w n'est pas satisfaisante. DEBUG utilise les registres BX et CX pour déterminer cette taille.

Saisissez :

-r cx
CX 0000
:500
-w
Écriture de 00500 octets

Cela a pour effet de placer la valeur 500h (1280) dans CX, puis de sauver ces données sous le nom par défaut. La zone mémoire CS:0000 à CS:000FF n'est pas prise en compte par la sauvegarde, la taille donnée ne doit pas en tenir compte. C'est le DOS qui se chargera de cette zone.

Continuez à explorer les commandes  n , w , l (load) :

-n
-l
Fichier introuvable
-n squelet.com
-l
-f 100 5ff 55
-d 200 20f
-l
-d 200 20f
-d 100 10f
-d 500 50f

Cette séquence doit maintenant vous paraître évidente. Le fichier portant le nom spécifié par la commande  n a été chargé en mémoire à partir de l'adresse CS:0100 , sur sa longueur propre. DEBUG charge de la même façon n'importe quel fichier.

Testez par exemple :

-n autoexec.bat
-l
-d 100 11F
-r

Le registre CX vaut bien Fh , ce qui correspond aux 15 octets donnés par la commande  dir . DEBUG a chargé brutalement les 15 octets de autoexec.bat à partir de CS:0100 , sans rien modifier du reste de la mémoire à ce moment-là. Nous retrouvons donc des octets 90h .

 

CD-Rom Repère 08

 

Après avoir quitté puis relancé DEBUG pour travailler "sur du propre", saisissez :

-a 100
1D2B:0100 mov ax, 3
1D2B:0103 dec ax
1D2B:0104 nop
1D2B:0105 jnz 103
1D2B:0107 int 20
1D2B:0109

Vérifiez en désassemblant :

-u 100 107
1D2B:0100 B80300        MOV     AX,0003
1D2B:0103 48            DEC     AX
1D2B:0104 90            NOP
1D2B:0105 75FC          JNZ     0103
1D2B:0107 CD20          INT     20

Analysons notre création :

  MOV AX,0003 transfère la valeur 3h dans le registre AX (16 bits, c'est pour cela que DEBUG affiche 0003 ).

  DEC AX  : décrémente le registre AX d'une unité.

  NOP  : ne fait rien. Symbolise l'emplacement du code utile.

  JNZ 0103  : saute en CS:0103 si "Non Zero". L'instruction  DEC précédente positionne un certain nombre d'indicateurs, appelés flags, dans le registre FLAGS (EFLAGS sur les versions 32 bits). Dans ce registre, le bit 6 est le ZF (Zero Flag). Il est à 1 quand et seulement quand le résultat de l'opération qui le positionne (ici DEC AX ) est 0. Il y aura donc un saut à l'adresse CS:0103 tant que AX ne sera pas nul. Nous avons donc créé une boucle.

  INT 20  : retour au DOS en fin de programme.

Nous pouvons prévoir que l'instruction  NOP sera "traversée" 3 fois, AX ayant pour valeur 2, 1 puis enfin 0. Puisque NOP ne fait rien, notre premier programme pourrait s'appeler trois_fois_rien.com .

Cette analyse est représentée par des ordinogrammes, sous forme graphique et textuelle.

Ordinogrammes de notre premier programme
figure 3.05 Ordinogrammes de notre premier programme [the .swf]

Remarque

Les déplacements dans les sauts

L'instruction JNZ 0103 est codée 75 FC . 75 représente JNZ  et FC  l'adresse cible du saut. C'est la valeur qu'il faut ajouter à IP pour obtenir l'adresse cible. Or, au moment du traitement du JNZ , IP pointe sur l'instruction suivante : IP = 0107h . Donc, la séquence 75 00 se désassemble en JNZ 0107 . En arithmétique signée, FCh vaut -4 ; donc le saut éventuel s'effectuera bien en CS:0103 .

Il est possible intuitivement d'en arriver au même résultat. Si une mémoire chargée par 00h est décrémentée, elle passe à FFh , et vice versa (incrémentée, elle passe de FFh à 00h ), un peu comme un compteur de voiture passe de 999 999 km à 000 000 km. Donc, en partant de 00h et en "reculant" de 4 positions dans la mémoire, nous obtenons successivement : 00h , FFh , FEh , FCh . Pas plus compliqué ! C'est DEBUG qui fait les calculs pour les commandes  a et  u . Vous pouvez obtenir la valeur FCh en utilisant la commande  h  :

-h 0 4
0004  FFFC

Cette commande affiche la somme et la différence de deux opérandes, sur 16 bits. C'est pourquoi le résultat de 0h - 4h est FFFCh . Quand vous maîtriserez l'arithmétique signée, vous saurez que le bit de signe est "recopié", et que le résultat sur 8 bits est bien FCh . Il serait FFFFFFFCh sur 32 bits. Vous pouvez effectuer quelques tests supplémentaires avec cette commande.

Une conséquence sympathique :

Notre programme étant toujours à l'adresse CS:0100 , saisissons :

-m 100 10F 200
-u 200 207
1D2B:0200 B80300        MOV     AX,0003
1D2B:0203 48            DEC     AX
1D2B:0204 90            NOP
1D2B:0205 75FC          JNZ     0203
1D2B:0207 CD20          INT     20

Le code reste opérationnel à la nouvelle adresse (il continuera à accomplir trois fois rien), alors que la commande de déplacement  m (move) ne sait pas s'il s'agit de code ou de données : elle déplace des octets. Cette suite d'octets peut être déposée n'importe où, telle quelle ; elle fonctionnera de la même façon, grâce au codage des sauts par le déplacement.

À l'inverse, le déplacement d'un bloc qui comporte des sauts vers des zones non déplacées posera problème. Il existe des méthodes pour rendre un code source indépendant de l'endroit où il est déposé, DEBUG atteint ici ses limites, les assembleurs évolués nous seront d'une aide précieuse. L'instruction INT participe à cette souplesse ; nous y reviendrons.

Nous pouvons lancer notre programme par la commande  g sans paramètre. C'est possible parce que rien n'a été exécuté depuis le début de la session DEBUG. Dans d'autres cas, il faudra vérifier et modifier la valeur des registres.

-g
 
Le programme s'est terminé normalement

Vous pouvez, sur le programme tel qu'il est actuellement, tester par vous-même les commandes  t (trace) et  p (proceed). Aidez-vous au besoin du CD-Rom. Vous ne constaterez de différence entre les deux commandes qu'à l'attaque de l'instruction  INT 20 . Si vous utilisez  t dans INT 20 , vous constaterez un changement de CS, ce qui est normal puisque nous savons qu'un vecteur d'interruption est une adresse effective complète, segment et offset. Vous risquez d'être amené à quitter/relancer DEBUG. Vous aurez également à saisir à nouveau votre programme, pour continuer les tests, si vous aviez négligé de le sauvegarder. Mais habituellement, après quelques commandes  t , il vous suffit de taper  g pour que le programme se termine normalement.

Tout semble parfait. Et pourtant... Ce premier programme comporte au moins une faute et demie.

La grosse erreur : remplaçons  NOP par INC CX (incrémente le registre CX d'une unité). Nous espérons ainsi incrémenter CX de 3. Que nenni ! CX positionne en effet le flag ZF. Si nous supposons que CX contient 0h en début d'exécution, le programme va boucler en incrémentant CX (et en décrémentant AX). La boucle se terminera lorsque CX, ayant atteint FFFFh , sera incrémenté et passera à 0000h . La boucle sera donc effectuée 65 536 ( 10000h ) fois. CX sera revenu à sa valeur initiale après avoir fait "un tour complet". Il en est de même pour AX, qui doit présenter à nouveau la valeur 0003h en fin de boucle. Vérifiez-le.

CD-Rom Repère 09

Pour cela, quittez et relancez DEBUG, puis saisissez :

-a 100
1D2B:0100 mov ax, 3
1D2B:0103 dec ax
1D2B:0104 inc cx
1D2B:0105 jnz 103
1D2B:0107 int 20
1D2B:0109
-g 107
 
AX=0003  BX=0000  CX=0000  DX=0000  SP=FFEE ....
DS=1D2B  ES=1D2B  SS=1D2B  CS=1D2B  IP=0107 ....
1D2B:0107 CD20          INT     20

La commande g 107 exécute le programme à partir de l'adresse CS:IP avec un point d'arrêt en CS:0107 . Pour forcer une adresse de démarrage différent de CS:IP , avec ou sans point d'arrêt, il faut placer le signe = avant l'adresse démarrage. Nous constatons que, comme prévu, AX et CX ont "fait le tour" (ils ne peuvent pas ne pas avoir bougé, puisqu'ils sont modifiés une fois avant tout branchement conditionnel).

Pour terminer l'exécution, saisissez la commande  g sans paramètre :

-g
 
Le programme s'est terminé normalement

La demi-faute est une "faute de style" : le registre CX (ECX en 32 bits) est spécialisé dans les tâches de compteur. Sauf bonne raison contraire, par exemple plusieurs compteurs actifs simultanément, il est très souhaitable de s'y conformer.

Nous allons maintenant récrire ce morceau de code en tenant compte de ces deux remarques. Disons que nous voulons obtenir une séquence qui additionne 41h à AX, en fatiguant un peu quelqu'un qui tenterait de comprendre le fonctionnement de notre œuvre par désassemblage, à l'aide de DEBUG ou d'autres outils. Ce peut être dans un module de vérification de mot de passe par exemple. C'est certainement très insuffisant, mais en appliquant en "sapin de Noël" cette méthode à INC AX , DEC CX , etc., nous pouvons espérer quelque chose d'assez jovial à suivre. Et à maintenir également ! Cela ne nous interdit pas de mettre en œuvre d'autres moyens de protection. De plus, nous introduisons un bogue (d'inattention), pas subtil du tout, dans la saisie.

Avant de continuer, récapitulons au sujet du développement d'un  .com  avec DEBUG : la zone de 100h (256) octets allant de CS:0000 à CS:00FF est nommée en anglais PSP. À la saisie, il est géré par DEBUG. À l'exécution, ou si vous rechargez votre programme, il est fourni par le DOS. Il comporte en particulier les paramètres passés au programme dans la ligne de commande. Notez que, sous DOS, vous devez quitter une étape pour passer à la suivante. Sous Windows, vous pouvez, vous devriez même, sauver et exécuter sans fermer la fenêtre d'édition.

Édition, sauvegarde et test d'un .com sous debug
figure 3.06 Édition, sauvegarde et test d'un .com sous debug [the .swf]

Puisque le programme va planter, si vous travaillez sous Windows :

Ouvrez tout de suite deux fenêtres.

Dans l’une d'elles, saisissez le programme (sans corriger le bogue !) et sauvez-le sous le nom de try1.com , avec une taille (arbitraire) de 256 ( 100h ) octets.

Aidez-vous de la capture d'écran un peu plus loin et/ou du CD-Rom, pour cette opération.

Dans la seconde fenêtre, lancez le programme  try1 . Plus rien ne peut être saisi dans la fenêtre de test. Le reste de Windows fonctionne parfaitement. Le gestionnaire de tâches (accès par la combinaison  Ctrl  +  Alt  +  Suppr  ) ne signale rien de suspect. Donc, nous sommes certainement en présence d'un programme qui boucle et qui, pour Windows, fonctionne normalement.

Tuez la tâche, c'est-à-dire fermez la fenêtre, soit directement par sa "croix" de fermeture, soit dans le gestionnaire de tâches. Vous devriez avoir un écran tel que figuré sur l’illustration.

Suites d'un blocage
figure 3.07 Suites d'un blocage

Cliquez sur Oui pour tuer la fenêtre.

Rouvrez-en une autre et passez dans celle où est ouvert DEBUG. Vous allez tracer le programme pour trouver l'erreur. Si vous ne disposez pas de cette fenêtre, tapez simplement, au prompt du DOS :

debug try1.com

Tapez  t et observez en comparant à ce que vous attendez.

CD-Rom Repère 10

Observez :

-t

0100 dans AX, tout va bien.

-t

0041 dans CX, c'est tout bon.

-t
-t

0101 dans AX, 0040 dans CX, après INC AX puis DEC CX , tout est nominal. De plus, DEBUG indique bien NZ ( ZF à 0), le programme va donc sauter (le JNZ suit).

-t

Tout à l'air d'aller bien, seul IP a changé. Mais... l'instruction suivante est MOV CX, 0041  ! Donc, si nous remettons CX à 41h à chaque itération de la boucle, aucune chance d'en sortir. Il fallait sauter en CS:0106 . Arrêtez le traçage et corrigez puisque la cause de l'erreur est évidente.

-a 108
1D5D:0108 jnz 106
1D5D:010A
-u 100 10A
1D5D:0100 B80001        MOV     AX,0100
1D5D:0103 B94100        MOV     CX,0041
1D5D:0106 40            INC     AX
1D5D:0107 49            DEC     CX
1D5D:0108 75FC          JNZ     0106
1D5D:010A CD20          INT     20
-w
Écriture de 00040 octets

Tiens, 40h (64 en décimal) octets écrits. Effectivement, CX a changé et vaut bien 40h . DEBUG ne va pas plus loin, il utilise les valeurs actuelles de BX et CX pour déterminer la taille de la zone à sauvegarder. Mais c'est largement suffisant pour notre programme, dont la taille n’est que de 12 octets.

Testez try1 dans l'autre fenêtre DOS. Tout va bien, pas de blocage, mais ce n'est pas très visuel. Vous allez utiliser DEBUG pour tester le résultat.

Vous pourriez quitter/relancer ; mais modifiez plutôt les registres. AX et CX seront initialisés par le programme, donc seul IP importe.

Repositionnez-le à 0100 et lancez par  g avec un point d'arrêt en CS:010A (juste avant d'exécuter le retour au DOS par INT 20 ), et terminez par un  g sans paramètre :

CD-Rom Repère 11
-r ip
IP 0103
:100
-g 10a
 
AX=0141  BX=0000  CX=0000  DX=0000  SP=FFFE  ....
DS=1D5D  ES=1D5D  SS=1D5D  CS=1D5D  IP=010A  ....
1D5D:010A CD20          INT     20
-g
 
Le programme s'est terminé normalement

 

Les résultats sont satisfaisants : AX passe de 0100h à 0141h  ; il lui a donc bien été ajouté la valeur 41h . CX vaut 0h en fin de boucle, ce qui était attendu.

Quittez DEBUG. Vous allez maintenant tester la commande  s (search) et, pour cela, lancer DEBUG sur le programme try1.com , avec une chaîne de caractères en paramètre.

Saisissez :

-q
 
A:\>debug try1.com chaine_argument
-s 0 ffff "try1"
-s 0 ffff "chaine_argument"
1D5F:0082
-d 0 ff

La recherche sur try1 , le nom du programme, ne donne rien. Ce nom n'est donc pas systématiquement présent dans la zone mémoire gérée par DEBUG. En revanche, le paramètre chaine_argument est bien trouvé, dans la zone de 256 octets, de CS:0000 à CS:00FF , gérée par DOS, appelée PSP. L'octet 10h en CS:0080 indique la longueur de ce paramètre, en incluant l'espace initial. C'est donc là qu'il faudra aller chercher le paramètre de la ligne de commande, si le programme en gère. Les caractères sont recopiés à partir de CS:0081 , jusqu'au caractère 0Dh (la touche  Entrée  ).

Récapitulons :

  S'il n'y a pas de paramètre, vous allez trouver 00h en CS:0080 , puis immédiatement après le 0Dh final.

  S'il y a des paramètres, vous trouverez la longueur de la chaîne paramètres en CS:0080 , puis un espace ( 20h ) nécessaire, puis votre chaîne suivie du caractère 0Dh . Il appartiendra au programme de séparer les paramètres s'il y en a plusieurs.

  Si vous saisissez plusieurs espaces finaux inutiles, ils seront traités comme des paramètres.

 

Vous pouvez tester ces différents cas de figure, en saisissant :

A:\>debug try1.com
-d 80 8f

Puis :

A:\>debug try1.com param1 param2
-d 80 9f

Et enfin :

A:\>debug try1.com [+ 8 espaces]
-d 80 8f
 

 

CD-Rom Repère 12

Terminez cette prise en main par un dernier exercice :

A:\>debug command.com
-r
AX=0000  BX=0001  CX=7682  DX=0000  ....
DS=1D5E  ES=1D5E  SS=1D6E  CS=1D6E  ....
-u 100
1D6E:0100 06            PUSH    ES
1D6E:0101 17            POP     SS
1D6E:0102 BE1B02        MOV     SI,021B
1D6E:0105 BF1B01        MOV     DI,011B
1D6E:0108 8BCE          MOV     CX,SI
1D6E:010A F7D9          NEG     CX
1D6E:010C FC            CLD
1D6E:010D B81B01        MOV     AX,011B
1D6E:0110 06            PUSH    ES
1D6E:0111 50            PUSH    AX
1D6E:0112 06            PUSH    ES
1D6E:0113 B81801        MOV     AX,0118
1D6E:0116 50            PUSH    AX
1D6E:0117 CB            RETF
1D6E:0118 F3            REPZ
1D6E:0119 A4            MOVSB
1D6E:011A CB            RETF
1D6E:011B E93221        JMP     2250
1D6E:011E 7AC9          JPE     00E9

Si nous concaténons les registres BX et CX (de deux registres 16 bits, nous faisons un nombre de 32 bits), nous obtenons 00017682h , ce qui, converti en décimal, donne 95874 ; c'est bien la longueur du fichier command.com donnée par la commande  dir .

Cette longueur nous met la puce à l'oreille : command.com n'est pas un  .com mais un .exe  ! Nous pouvons confirmer cette particularité en observant les deux premiers octets du fichier : 4D 5A en hexa, soit les caractères "magiques" MZ. C'est l'identification des  .exe , prioritaire sur l'extension.

Le programme débutera, comme pour un  .com , en CS:0100 . Simplement, nous n'avons pas à cet endroit le début du fichier command.com . Pour information, le morceau de code débutant en CS:0100 se trouve dans le fichier command.com à l'offset 00004b00h .

À titre d'exercice, vous pouvez suivre le début de l'exécution à l'aide des commandes  t et  p . Vous vérifierez que le RETF en CS:0117 n'est qu'un JMP avec modification du CS, que la séquence  PUSH ES/POP SS ne fait que transférer ES dans SS. Vous pourrez étudier le fonctionnement de REPZ/MOVSB , instruction de transfert de chaîne. Pensez à utiliser  p à la place de  t quand vous aurez vu le fonctionnement de ce couple d'instructions. Quand vous en saurez assez, tapez  g . L'interpréteur de commandes command.com est effectivement lancé. Vous en sortez par la commande DOS exit , et retombez dans DEBUG !

Nous arrêtons là cette découverte. Pour la suite de votre apprentissage, tester rapidement une instruction, étudier ses effets sur les flags, etc., vous pouvez conserver DEBUG (ou grdb ) "sous le coude". grdb , évoqué en tête de ce chapitre, semble plus alléchant. À vous de le tester si vous le souhaitez. Il n'est pas à priori maladroit de conserver une fenêtre DOS ouverte en permanence sur dans un coin du Bureau Windows, avec DEBUG ou grdb lancé.

3.4 Écrire et tester kb_codes.com

Afin de mettre en pratique l'utilitaire DEBUG, nous allons développer une (toute) petite application réelle. Nous disons réelle dans la mesure où nous allons la coder entièrement avec DEBUG, et rien d'autre, la tester puis sauvegarder un fichier kb_codes.com , qui sera exploitable comme n'importe quelle commande. "Réelle" n’est donc pas à prendre au sens de son utilité !

3.4.1 Généralités sur les interruptions du BIOS et du DOS

Pour mener à bien ce travail, nous devons connaître un minimum de certaines particularités du BIOS et du DOS. Après une rapide introduction, seules seront explicitées les fonctionnalités mises en œuvre dans notre exemple. Le lecteur pourra trouver de la documentation supplémentaire sur le sujet, ainsi que des liens internet, sur le CD-Rom accompagnant cet ouvrage.

Nous avons vu précédemment que, même en l'absence de tout autre programme, une carte mère munie d'un clavier et d'une carte graphique standard avec écran est capable de dialoguer avec l'utilisateur. C'est à ce niveau que celui-ci peut, par une séquence de touches appropriée, lancer un programme de mise au point, couramment appelé le "Setup du BIOS". Elle est également en mesure de charger le système d'exploitation, à partir d'un périphérique donné, en général le lecteur de disquettes ou le disque dur. Cela fait déjà beaucoup de fonctionnalités, et ce ne sont pas les seules, prises en charge par le BIOS (Basic Input/Output System). Ce BIOS est nécessairement en ROM au démarrage, pour au moins une partie.

Le BIOS met à la disposition du programmeur un ensemble de fonctions, appelées également (par un certain abus de langage) "interruptions du BIOS". En effet, le programmeur sollicite ces fonctions par l'usage d'une interruption logicielle (instruction INT n ), précédée par le positionnement d'un ou de plusieurs registres.

Remarque

Rappels sur les interruptions

Une interruption est le processus par lequel un programme est dérouté de son cheminement normal pour sauter dans une sous-routine, généralement par des événements externes, par l'intermédiaire des circuits d'interface, ainsi que par des événements imprévisibles, comme une division par 0 ou un dépassement de limites dans l'utilisation de la mémoire.

Leur traitement tient donc compte du fait qu'elles peuvent survenir à n'importe quel endroit du programme.

Pour les processeurs Intel et compatibles :

*           Les interruptions sont vectorisées, c'est-à-dire qu'il existe une table de 256 adresses mémoires, ou vecteurs, pointant sur autant de routines de traitement possibles.

*           L'instruction INT n , ou n  est un entier non signé codé sur 8 bits, permet de déclencher directement chacune des 256 interruptions. Nous parlons d'interruption logicielle. Déclencher de cette façon sur un vecteur correspondant à une interruption matérielle n'a généralement pas de sens.

Les avantages de cette technique, par rapport à l'utilisation de CALL ou de JMP , sont nombreux. L'appel est plus performant mais, surtout, il est facile de modifier la routine par changement du vecteur, sans aucune modification du code compilé (ou assemblé). Les informations à communiquer à l'utilisateur de ces ressources sont minimales.

En général, le numéro de l'interruption définit une famille de fonctions, comme le fameux 21h qui correspond aux fonctions du DOS et la valeur d'un registre, AH, détermine la fonction exacte, comme 09h pour la sortie d'une chaîne sur l'écran (par défaut).

Par l'intermédiaire de ces fonctions, nous pouvons effectuer des opérations primitives (saisie d'un caractère au clavier, affichage d'un point sur un écran, lecture ou écriture d'un secteur sur une disquette, etc.), facilement et indépendamment de la structure réelle de la machine. C'est la base de la notion de "compatible PC". Une machine devrait pouvoir, par exemple, ne posséder ni clavier ni écran, mais une simple liaison vers un terminal clavier-écran, voire un autre PC remplissant cet office.

Les fonctions du BIOS sont, bien entendu, disponibles quel que soit le système d'exploitation installé sur notre compatible, système qui doit lui-même les reconnaître.

À côté des "interruptions du BIOS", il existe les "interruptions du DOS". Le mécanisme est strictement le même. Plus précisément, les fonctions purement DOS sont accessibles au travers de l'interruption 21h . Pour se fixer les idées, l'écriture d'un secteur sur la disquette est du ressort du BIOS, alors que l'écriture d'un fichier sur un disque est une fonctionnalité du DOS. Nous pouvons considérer que les "interruptions du DOS" sont un sur-ensemble des "interruptions du BIOS", les fonctions BIOS non surchargées devenant des éléments du bouquet DOS. Les fonctions spécifiquement DOS utilisent les fonctions BIOS. Cette organisation est tout à fait en accord avec la structure logicielle "en couches" vue au chapitre précédent.

À titre d'exemple, extraites d'une documentation que vous trouverez sur le CD-Rom, voici quelques lignes de la liste des exceptions :

INT 00h         Divide error (division by zero etc.)
INT 01h         Single-step
INT 02h         NMI (Non-maskable interrupt)
INT 03h         1-byte interrupt/Breakpoint
INT 04h         Overflow (internal interrupt)
INT 05h         Print-screen key
.....
INT 10h         Video
INT 11h         Equipment determination
INT 12h         Hardware memory size
INT 13h         Disk
INT 14h         Serial I/O
INT 15h         Cassette and extended services
INT 16h         Keyboard
INT 17h         Printer
INT 18h         Transfer to ROM BASIC
INT 19h         Disk BOOT
INT 1Ah         Time of day
INT 1Bh         Ctrl-Break key
INT 1Ch         User timer tick
INT 1Dh         6845 video init tables
INT 1Eh         Diskette params (base table)
INT 1Fh         Graphics set 2
INT 20h         Program termination
INT 21h         DOS functions (and Extensions.)
INT 22h         DOS terminate address
INT 23h         DOS Control "C" exit address
INT 24h         DOS Critical error handler address

Si nous nous intéressons spécialement à la fameuse 21h , voici un autre extrait, des sous-fonctions de cette interruption :

INT 21h, 00h    Program termination
INT 21h, 01h    Keyboard input
INT 21h, 02h    Display output
INT 21h, 03h    Aux input
INT 21h, 04h    Aux output
INT 21h, 05h    Printer output
INT 21h, 06h    Direct console I/O character
INT 21h, 07h    Direct console input, no echo
INT 21h, 08h    Stdin input, no echo
INT 21h, 09h    Print string
INT 21h, 0Ah    Buffered keyboard input
INT 21h, 0Bh    Check standard input status
INT 21h, 0Ch    Clear keyboard buffer
INT 21h, 0Dh    Disk reset
INT 21h, 0Eh    Select disk
INT 21h, 0Fh    Open disk file
INT 21h, 10h    Close disk file
INT 21h, 11h    Search first using FCB
INT 21h, 12h    Search next using FCB
INT 21h, 13h    Delete file via FCB
INT 21h, 14h    Sequential disk file read
INT 21h, 15h    Sequential disk record write
INT 21h, 16h    Create a disk file

Si c'est la sortie d'une chaîne qui nous importe, nous obtenons :

Print string (INT 21h, 09h)
 
Sends a character string to stdout (and checks for Ctrl-Break).
Note:  Break checked, and INT 23h called if pressed
 
Entry:
AH    = 09h
DS:DX = address of string terminated by "$"

Nous revenons sur cette fonction juste un peu plus loin dans ce chapitre. Nous sommes désolés pour la langue anglaise, mais sur ce sujet, le mieux est de s'y habituer. Les sources traduites existent certainement, mais rien n'en garantit la fiabilité.

Remarquons que l'assembleur n'est pas indispensable, loin de là, pour utiliser ces fonctions. Diverses méthodes existent à partir de nombreux langages de haut niveau comme le C ou le Pascal. Par exemple, les fonctions BIOS du C (simples encapsulations), dont les prototypes se trouvent dans le fichier bios.h . L'API Win32 fournit VWIN32.VXD qui permet d'invoquer ou de simuler certaines fonctions du DOS.

Voici quelques exemples de fonctions, parmi celles que nous allons utiliser ici.

Terminer un programme, et rendre la main au DOS

Nous avons déjà utilisé l'interruption 20h . Sous DEBUG, la séquence d'appel se réduit à :

int 20 

Nous pouvons également utiliser la fonction 00h de l'interruption 21h . Sous DEBUG, la séquence d'appel est :

mov ah, 0
int 21

La seule contrainte (pour les deux formes) est que le registre CS contienne le segment du PSP (Program Segment Prefix). Dans notre cas (programme  .com contenu dans un seul segment), cette contrainte est automatiquement réalisée.

Afficher une chaîne de caractères

Sur le périphérique de sortie standard, l'écran dans notre cas. Nous allons utiliser la fonction DOS 09h , donc l'interruption 21h avec le code 09h dans AH. De plus DS:DX doit pointer vers la chaîne. Cette chaîne doit se terminer par le caractère  $ , qui ne sera pas affiché. Cette fonction ne renvoie rien. Sous DEBUG, la séquence d'appel est :

mov ah, 9
mov dx, 160
int 21

Si la chaîne à afficher suivie de $ est en DS:0160 et suivants.

Afficher un caractère

Sur le périphérique de sortie standard, l'écran dans notre cas. Nous allons utiliser la fonction DOS 02h , donc l'interruption 21h avec le code 02h dans AH. De plus, DL doit contenir le code ASCII du caractère à afficher. Cette fonction ne renvoie rien. Sous DEBUG, la séquence d'appel est :

mov ah, 2
mov dl, 7A
int 21

Lire une touche au clavier

Pour lire le clavier, il existe de nombreuses possibilités. En effet, les saisies clavier sont dirigées vers un tampon mémoire, le programme ayant la charge de vider celui-ci en le lisant. De plus, le clavier fournit deux informations différentes : le scancode, qui correspond à la localisation de la touche sur le clavier et le code ASCII de la touche, ou 0 si la touche n'a pas de code ASCII correspondant. Nous allons utiliser l'interruption BIOS 16h . C'est une famille de services concernant le clavier, baptisée Keyboard I/O Services . Sa fonction 0 ( 00h dans AH), nommée Keyboard Read , correspond à une lecture clavier avec attente si le tampon est vide. Quand la fonction retourne, le code ASCII est dans AL et le scancode dans AH.

Sous DEBUG, la séquence d'appel est :

mov ah, 0
int 16

Au retour, il restera à isoler AH et AL et à les exploiter.

3.4.2 Cahier des charges

Invoqué sans paramètre (commande  kb_codes ), ce programme attend la frappe d'une touche au clavier. Quand cet événement survient :

Le code ASCII de la touche est affiché. Si la touche ne correspond à aucun code ASCII, le code 00h (code ASCII NUL) est affiché.

Le scancode (voir plus haut) est affiché.

Si la touche correspond à un caractère imprimable, celui-ci est affiché à l'endroit où se trouve le curseur au moment de la frappe. La frappe est qualifiée "avec écho". Les caractères sont considérés imprimables si leur code ASCII a une valeur entre 20h (32 en décimal) et FEh (254 en décimal).

Si la touche appuyée est  Echap  (Échappement,  ESC  ,  Escape  ), le programme s'arrête (retour au DOS) après que cette touche de fin a été traitée comme les autres. Dans tous les autres cas, le programme attend un appui sur une nouvelle touche, et ainsi de suite.

Il est souhaitable que chaque touche sur laquelle vous appuyez génère un affichage sur une seule nouvelle ligne de 80 caractères. Nous nous attacherons à ce que les éléments des lignes soient "joliment" alignés.

Une future version permettra de modifier la touche de fin. Par exemple, kb_codes a lancera le programme qui ne s'arrêtera que lors de la frappe de la touche   A  . Nous devrons en tenir compte afin que cette amélioration soit le plus simple possible à implémenter.

Aucune autre optimisation particulière ne sera recherchée.

3.4.3 Analyse

À l'Annexe 4, vous trouverez quelques considérations sur l'algorithmique spécifiquement appliquée à l'assembleur.

Vous pouvez suspendre votre lecture ici et coder vous-même cette petite application. Attention, la rusticité de DEBUG ne vous permettra pas d'éditer facilement votre code. Deux trucs peuvent vous aider :

  Insérer des NOP (No Operation, instruction codée sur 1 octet, code 90h , qui ne fait rien) dans le code, afin de se donner éventuellement un peu de place pour une correction.

  Utiliser des CALL à des endroits où ce n'est pas strictement justifié. Une sous-routine, même invoquée une seule fois, permet de modifier un morceau du code sans toucher le programme principal.

Vous avez tout à gagner à anticiper par des essais sur la lecture. Dans la même optique, vous trouverez sur le CD-Rom le fichier KB_INTER.COM , dans lequel la routine la plus intéressante SP2 reste à écrire. Vous pourrez donc lire rapidement au début et coder  SP2 vous-même.

Le cœur de notre programme est la fonction Keyboard Read de Keyboard I/O Services du BIOS, soit la fonction 00h de l'interruption 16h , décrite plus haut. Nous pouvons donc déjà poser un schéma général.

Les blocs de code
figure 3.08 Les blocs de code [the .swf]

Nous allons faire un inventaire des difficultés en analysant un par un les "blocs de code". Le code assembleur commenté correspondant est à consulter en fin de chapitre.

Début – Initialisations

Le point 8 du cahier des charges impose que le code ASCII indiquant au programme qu'il doit se terminer ( 1Bh ) ne soit pas directement implanté dans le programme, c'est-à-dire "codé en dur". Ce sera au contraire une case mémoire d'un octet initialisée à cette valeur. C'est exactement la notion de variable des langages de haut niveau. Cette initialisation se fera dans un sous-programme  SP1 . Ce sous-programme est destiné à évoluer en traitant la lecture de la ligne de commande. Pour l'heure, son rôle est limité à cette affectation. Attribuons un nom, MemTF , à cette variable pour référence ultérieure. Répétons que DEBUG ne connaît pas les noms, ni même les variables en tant que telles.

Attendre une touche

Cette action consiste à afficher une invite, puis à invoquer Keyboard Read du BIOS, qui ne retourne que lors de l'appui sur une touche.

Les blocs 1 et 2
figure 3.09 Les blocs 1 et 2 [the .swf]

Traiter le résultat

Ce que nous voulons obtenir, après avoir appuyé successivement sur les touches  z  ,  Z  et  F1   :

Une touche -> z Code ASCII -> 7Ah  Scancode -> 11h
Une touche -> Z Code ASCII -> 5Ah  Scancode -> 11h
Une touche ->   Code ASCII -> 00h  Scancode -> 3Bh

Nous venons d'appuyer sur la touche   z  . Nous héritons de l'étape précédente le code ASCII 7Ah dans AL et le scancode 11h dans AH. La première précaution à prendre est de sauver ces deux valeurs en mémoire. Donc, deux nouvelles variables de la taille d'un octet, MemASCII et MemScan .

Tester si 7Ah est un code ASCII imprimable et l'afficher ne pose pas de problème puisque c'est bien  z que fait afficher la valeur 7Ah dans la fonction 02h de l' int 21 .

Il faudra juste veiller à afficher un espace pour les touches non imprimables comme   F1  , pour préserver l'alignement de l'affichage.

Mais comment afficher la chaîne  7Ah  ? Notre problème est de "sortir" de la valeur 7Ah (122 en décimal) les codes ASCII des caractères ' 7 ' et ' A '. Le problème est strictement le même pour le scancode. Considérons un sous-programme  SP2 qui, recevant la valeur XYh dans AL, imprime la chaîne "XYh"  ; il sera alors utilisable pour le code ASCII et pour le scancode.

Allons plus loin, considérons ce sous-programme écrit. Cela aurait pu être le cas : commencer par l'écriture des routines stratégiques et les tester. C'est la méthode classique. Nous préférons ici réduire, dans un premier temps, le travail de ce sous-programme à l'affichage de la chaîne "**h " . Pour continuer à développer notre analyse dans la continuité.

Pour terminer le bloc de code 3, il restera à passer à la ligne suivante, ce qui se fait en envoyant à l'écran les "caractères" de code ASCII 0Dh et 0Ah .

Les blocs 3, 4 et 5
figure 3.10 Les blocs 3, 4 et 5 [the .swf]

Si touche saisie ≠ touche de fin

Nous testons l'égalité de MemTF et de MemASCII . En cas d'inégalité, le programme revient à l'affichage de l'invite, en début du bloc 2.

Fin du programme

Nous n'avons pas dit "Bonjour" en début de programme. Affichons "Au revoir..." avant de quitter.

L'implantation en mémoire est la suivante.

Carte mémoire
figure 3.11 Carte mémoire [the .swf]

Dans sa version actuelle, le programme sera sauvegardé avec une longueur de 368 ( 170h ) octets.

3.4.4 Premier codage

Voici le listing commenté, correspondant au fichier KB_INTER.COM , donc sans  SP2  :

; Appel traitement ligne de commande 
1D5F:0100 E88D00        CALL    0190
 
; Affiche "Une touche -> "
1D5F:0103 B409          MOV     AH,09        ; Début boucle
1D5F:0105 BA1002        MOV     DX,0210
1D5F:0108 CD21          INT     21
 
; Attente d'une frappe au clavier
1D5F:010A B400          MOV     AH,00
1D5F:010C CD16          INT     16
 
; Affiche du caractère ou d'un espace si non imprimable
1D5F:010E 88260002      MOV     [0200],AH
1D5F:0112 A20102        MOV     [0201],AL
1D5F:0115 B220          MOV     DL,20
1D5F:0117 803E010220    CMP     BYTE PTR [0201],20
1D5F:011C 7C04          JL      0122            
1D5F:011E 8A160102      MOV     DL,[0201]
1D5F:0122 B402          MOV     AH,02
1D5F:0124 CD21          INT     21
 
; Affiche " Code ASCII -> "
1D5F:0126 B409          MOV     AH,09
1D5F:0128 BA2002        MOV     DX,0220
1D5F:012B CD21          INT     21
 
; Affiche du code ASCII
1D5F:012D A00102        MOV     AL,[0201]
1D5F:0130 E82D00        CALL    0160
 
; Affiche " Scancode -> "
1D5F:0133 B409          MOV     AH,09
1D5F:0135 BA3002        MOV     DX,0230
1D5F:0138 CD21          INT     21
 
; Affiche du Scancode
1D5F:013A A00002        MOV     AL,[0200]
1D5F:013D E82000        CALL    0160
 
; Passage à la ligne suivante
1D5F:0140 B409          MOV     AH,09
1D5F:0142 BA4802        MOV     DX,0248
1D5F:0145 CD21          INT     21
 
; Boucle si Code ASCII # Code de fin
1D5F:0147 A00102        MOV     AL,[0201]
1D5F:014A 3A060202      CMP     AL,[0202]
1D5F:014E 75B3          JNZ     0103    ; Saut à Début boucle
 
; Affiche "Au revoir ...."
1D5F:0150 B409          MOV     AH,09
1D5F:0152 BA5002        MOV     DX,0250
1D5F:0155 CD21          INT     21
 
; Fin du programme - Retour au DOS
1D5F:0157 CD20          INT     20
1D5F:0159 90            NOP
......... ..            ...
......... ..            ... 
1D5F:015F 90            NOP
 
; Sous-programme SP2 (voir le texte)
; Affiche la valeur de AL en hexa (2 digits)
; Version non terminée
1D5F:0160 BA4002        MOV     DX,0240
1D5F:0163 B409          MOV     AH,09
1D5F:0165 CD21          INT     21
1D5F:0167 C3            RET
1D5F:0168 90            NOP
......... ..            ...
......... ..            ...
1D5F:018F 90            NOP
 
; Sous-programme SP1: Traitement ligne de commande 
; Pour cette version: touche Echap = touche de fin
1D5F:0190 C60602021B    MOV     BYTE PTR [0202],1B
1D5F:0195 C3            RET
1D5F:0196 90            NOP
......... ..            ...
......... ..            ... 
1D5F:01FF 90            NOP
 
 
; Données en mémoire
1D5F:0200             ; Variable Scancode
1D5F:0201             ; Variable Code ASCII
1D5F:0202             ; Variable Code de fin
1D5F:0203 ... 1D5F:0203     ; Variables disponibles
 
1D5F:0210  "Une touche -> $"
1D5F:0220  " Code ASCII -> $"
1D5F:0230  " Scancode -> $"
1D5F:0240  "**h $"
1D5F:0248  "..$"        ; = saut de ligne  
1D5F:0250  "Au revoir ....$"

3.4.5 Le sous-programme SP2

Ce sous-programme reçoit un octet. Oublions que cet octet a pu représenter un code ASCII. Sinon, nous sommes certains de nous embrouiller les neurones. Dans ce module, cet octet représente une valeur numérique non signée (de 0 à 255, soit de 00h à FFh ) et rien d'autre.

À chacune des valeurs de cet octet, correspond une paire de chiffres hexadécimaux, donc une paire de caractères, donc une paire d'octets : les codes ASCII de ces caractères. Si nous recevons la valeur 122, sa représentation hexadécimale est 7A , nous devons donc calculer les codes ASCII du  7 et du A .

Il est intéressant de remarquer que les 4 bits de poids fort de notre octet d'entrée représentent le premier chiffre de sa représentation hexadécimale, les 4 bits de poids faible l'autre.

Commençons donc par écrire une routine qui reçoit une valeur dans les 4 bits de poids faible de BL et la transforme en code ASCII, renvoyé dans BL.

Première opération à réaliser, nettoyer BL, c'est-à-dire forcer à 0 ses 4 bits de poids fort. Nous utilisons pour cela AND BL , 0F . Cette instruction réalise un ET bit à bit entre BL et la valeur 0Fh , et place le résultat dans BL.

Forçage par le AND
figure 3.12 Forçage par le AND [the .swf]

La valeur immédiate est parfois appelée "masque". Pour chaque bit à 1 du masque, le bit correspondant du registre conserve sa valeur. Pour chaque bit à 0 du masque, le bit correspondant du registre est mis (forcé) à 0. Nous voilà avec une valeur de BL strictement comprise entre 00h et 0Fh .

Si nous partons de 0h à 9h , les codes ASCII sont ceux des 10 chiffres habituels, leurs codes ASCII évoluent de 30h à 39h , c'est-à-dire 30h ajouté à la valeur du chiffre. De Ah à Fh , les codes ASCII sont ceux des majuscules de A à F, de 41h à 46h , c'est-à-dire 37h (soit 30h  +  07h ) ajouté à la valeur du chiffre. D'où le code :

1D5F:0180 80E30F        AND     BL,0F
1D5F:0183 80C330        ADD     BL,30
1D5F:0186 80FB39        CMP     BL,39
1D5F:0189 7E03          JLE     018E
1D5F:018B 80C307        ADD     BL,07
1D5F:018E C3            RET

Sens du  JLE  : si BL est plus petit ou égal à 39h , le programme saute directement au RET . Sinon, il ajoute encore 07h au code ASCII.

Munis de cette routine, voyons maintenant comment afficher. Le but du jeu sera de remplacer les octets à l'emplacement des  * (adresses DS:0240 et DS:0241 ) par les codes ASCII voulus, dans la chaîne "**h $" , puis de l'envoyer à la fonction BIOS idoine. Le problème qui nous reste concerne les 4 bits de poids fort. Nous allons les déplacer vers la droite de 4 bits, ils prendront ainsi la place des 4 bits de poids faible.

Pour ce déplacement, nous utilisons l'instruction de décalage de bits SHR BL, CL , après avoir effectué MOV CL, 04 . Cette instruction décale les bits vers la droite dans le registre BL, en complétant par des 0 introduits par la gauche, d'autant de positions que la valeur contenue dans CL, soit 4. Soit l'équivalent d'une division entière par 16.

Une curiosité : la version immédiate de SHL : SHL BL , 04 est plus intéressante ici. Mais il se trouve qu'elle est refusée par DEBUG. Après enquête (en fait d'enquête, une simple question postée sur news:comp.lang.asm.x86 ), confirmation du fait que le 8086 ne connaissait pas cette forme qui ne serait apparue qu'avec le 80186, voire un peu plus tard. Donc, DEBUG ne veut pas en entendre parler.

Nous pouvons maintenant écrire le code complet de  SP2  :

; Sous-programme SP2
; Affiche la valeur de AL en hexa (2 digits)
1D5F:0160 88C3          MOV     BL,AL
1D5F:0162 E81B00        CALL    0180
1D5F:0165 881E4102      MOV     [0241],BL
1D5F:0169 88C3          MOV     BL,AL
1D5F:016B B104          MOV     CL,04
1D5F:016D D2EB          SHR     BL,CL
1D5F:016F E80E00        CALL    0180
1D5F:0172 881E4002      MOV     [0240],BL
1D5F:0176 B409          MOV     AH,09
1D5F:0178 BA4002        MOV     DX,0240
1D5F:017B CD21          INT     21
1D5F:017D C3            RET
1D5F:017E 90            NOP
1D5F:017F 90            NOP
 
; Sous-programme
; Converti les 4 bits de poids faible de BL en caractère hexa 
1D5F:0180 80E30F        AND     BL,0F
1D5F:0183 80C330        ADD     BL,30
1D5F:0186 80FB39        CMP     BL,39
1D5F:0189 7E03          JLE     018E
1D5F:018B 80C307        ADD     BL,07
1D5F:018E C3            RET
1D5F:018F 90            NOP

 

Remarque

Une autre solution...

Excellente en terme de vitesse : serait de créer une zone mémoire "peuplée" par des couples d'octets, en d'autres termes une table. Nous saurons très bientôt aller chercher le bon couple en une seule instruction pour chaque valeur. Mais nous n’appliquerons pas cette solution ici, ne désirant pas encore mettre en œuvre les notions de modes d'adressage.

3.4.6 Amélioration

Ajouter le traitement d'un éventuel paramètre en ligne de commande, sans modifier l'existant. C'est-à-dire que le traitement doit commencer en CS:0190 . L'espace disponible est la zone CS:0190 à CS:01FF . Nous pourrons intégrer la zone CS:0280 à CS:02FF au programme pour écrire telle ou telle routine. Rappelons que pour augmenter la taille du programme sauvegardé, il faut modifier le registre CX ( 0200h ) avant d'effectuer une commande  w .

La commande :

kb_codes 41 (ou debug kb_codes.com 41 , pour analyser l'effet du paramètre) va lancer kb_codes , mais c'est la touche   A  (code ASCII 41h ) qui provoquera la fin de programme.

Rappel : quand un programme  .com est lancé, il se trouve lors de l'exécution (idem sous DEBUG) précédé par une zone mémoire de 256 octets, de CS:0000 à CS:00FF , nommée PSP (Program Segment Prefix).

Si l'utilisateur saisit des paramètres (une chaîne de caractères à la suite de la commande), ceux-ci vont se retrouver à partir de 81h et peuvent au maximum courir sur 127 octets, soit jusqu'à la fin du PSP. Tout y est recopié, y compris le caractère  Espace initial. La fin est marquée par le code 0D (CR, ou Carriage Return, ou Retour Chariot). À vous d'étudier cela à l'aide de DEBUG.

Cahier des charges : le paramètre, débarrassé de ses espaces initiaux et éventuellement finaux, devra être de la forme XX ou XXh , sans espace inséré. XX sera une représentation valide d'un octet en hexadécimal. Les chiffres A..F pourront être saisis en majuscules ou en minuscules.

Une solution est proposée sur le CD-Rom, dans le fichier KB_CPLUS.COM , avec quelques explications dans KB_CPLUS.TXT .

3.5 Quelques exercices

Dans ce petit chapitre vous sont proposés quatre exercices. Pour trois d'entre eux, il faudra simplement prévoir ce que va faire un programme. Le quatrième est un minuscule cahier des charges.

3.5.1 Énoncés

EXO1.COM

Que fait le code suivant ?

1D5E:0100 BA5001        MOV     DX,0150
1D5E:0103 E80000        CALL    0106
1D5E:0106 5E            POP     SI
1D5E:0107 83C60F        ADD     SI,+0F
1D5E:010A C60490        MOV     BYTE PTR [SI],90
1D5E:010D 46            INC     SI
1D5E:010E C60490        MOV     BYTE PTR [SI],90
1D5E:0111 46            INC     SI
1D5E:0112 C60490        MOV     BYTE PTR [SI],90
1D5E:0115 BA6001        MOV     DX,0160
1D5E:0118 B409          MOV     AH,09
1D5E:011A CD21          INT     21
1D5E:011C CD20          INT     20

Sachant que nous trouvons en mémoire :

1D5E:0150  normal$.........
1D5E:0160  modifie$........

Aidez-vous au besoin des commandes  t et  p .

 

EXO2.COM

Comme le précédent, avec :

1D5E:0100 BA5001        MOV     DX,0150
1D5E:0103 E80000        CALL    0106
1D5E:0106 5E            POP     SI
1D5E:0107 83C611        ADD     SI,+11
1D5E:010A C60401        MOV     BYTE PTR [SI],01
1D5E:010D 4E            DEC     SI
1D5E:010E C60470        MOV     BYTE PTR [SI],70
1D5E:0111 4E            DEC     SI
1D5E:0112 C604BA        MOV     BYTE PTR [SI],BA
1D5E:0115 C60490        MOV     BYTE PTR [SI],90
1D5E:0118 B409          MOV     AH,09
1D5E:011A CD21          INT     21
1D5E:011C CD20          INT     20

et :

1D5E:0150  normal$.........
1D5E:0160  modifie$........
1D5E:0170  bien vu$........ .

 

SEVEN.COM

Lancez DEBUG et tapez le code suivant :

1D5F:0100 31C0          XOR     AX,AX
1D5F:0102 48            DEC     AX
1D5F:0103 50            PUSH    AX
1D5F:0104 40            INC     AX
1D5F:0105 50            PUSH    AX
1D5F:0106 CB            RETF

Dans un premier temps, essayez de prédire le comportement de ce programme. Quand vous serez arrivé au  RETF , ce qui ne devrait pas poser de problème maintenant, il faudra peut-être chercher dans la documentation.

INPUT.COM

Le programme doit attendre une saisie au clavier, avec écho, puis afficher sur la ligne suivante "Nombre pair", "Nombre impair" ou "Pas un nombre", selon le cas, puis passer à la ligne et attendre une touche, etc. Jusqu'à ce que vous appuyiez sur  Echap  .

Une réponse se trouve dans le fichier INPUT.COM , sur le CD-Rom.

3.5.2 Réponses

EXO1.COM et EXO2.COM

Le point intéressant est le CALL 0106 . Le saut a lieu à l'instruction suivante. Mais il a également pour effet de mettre l'adresse de retour ( 0106 également !) au sommet de la pile. POP SI permet donc de récupérer cette adresse dans SI et équilibre le CALL par rapport à la gestion de la pile ; c'est-à-dire qu'il remplace le  RET qui n'aura jamais lieu. Ensuite, le programme utilise SI pour :

  Dans EXO1 : "atteindre" les 3 octets de l'instruction MOV DX, 0160 et la détruire en la remplaçant par 3  NOP .

  Dans EXO2 : écrire une instruction MOV DX, 0170 à la place de MOV BYTE PTR [SI],90 .

Le programme affichera donc "normal", la chaîne qui est en 0150 pour EXO1, et "bien vu" pour EXO2.

Cette façon d'écrire dans la zone de code n'est possible que parce que nous sommes en mode 8086.

SEVEN.COM

Ce programme est le résultat de la recherche du code le plus court pour faire redémarrer la machine. Son nom correspond à sa taille : 7 octets.

Sachant que XOR AX, AX met AX à 0000h , vous verrez facilement que le RETF charge FFFFh dans CS et 0000h dans IP. Il effectue donc un saut à FFFF:0000 (ou F000:FFF0 , c'est la même adresse physique). Ce sont ces mêmes valeurs de CS:IP qui sont chargées à la mise sous tension. À partir de là, se trouve donc la procédure dite de "BOOT". Précisément, le POST (Power On Self Test), puis l'exécution d'un secteur de boots, en général le chargement d'un système d'exploitation.

Dans une fenêtre sous Windows, vous avez certainement remarqué que le système garde le contrôle en laissant seulement la fenêtre DOS se tuer.

Envoyer un message à l'auteur

se connecter pour pouvoir écrire

Ce site est créé et maintenu (laborieusement) par Pierre Maurette
hébergé par 1 & 1