Hmm... Mais je n'aime pas traîner trop longtemps sur des choses trop simples: ça donne l'impression de piétiner, et je n'aime pas piétiner. Vous non plus, j'imagine, puisque vous avez choisi de vivre avec un ordinateur de la race RISC.
donc, nous allons aborder l'étude du Wimp. En assembleur. Rien que ça. Sacré saut, n'est-ce pas?
Pour cela, je vais simplement, et vous de même, taper un listing qui fera exactement ce faisait celui de l'article précédent (je vous renvoie donc à cet article pour les explications):
;Premiere addition en assembleur sur ARM #smile #set LMAX=1 ;1 chiffre max. #entry ;Point d'entree MOV R0,#1 ;R0=1 MOV R1,#1 ;R1=1 ADD R0,R0,R1 ;R0=R0+R1 ADR R1,RESULT ;Adresse de Result MOV R2,#LMAX ;nb max. chiffres SWI OS_BinaryToDecimal ADR R0,MOUT ;Adresse message SWI OS_Write0 MOV R0,R1 ;Adresse de Result MOV R1,R2 ;R1=R2 (nb car.) SWI OS_WriteN SWI OS_Exit .MOUT DCB "Resultat: ",0 ;A afficher .RESULT DBB LMAX ;Res. Lmax octets #end ;Fin d'assemblage
Je tape donc ce listing, j'appelle le fichier, par exemple ``PremAdd'', et je drague son icone sur celui de extASM, et, à moins que j'en aie décidé autrement dans les options, un fichier ``Object'' est créé dans le dossier d'origine, que je peux exécuter en double-cliquant dessus. Tout va bien.
Un coup d'oeil aux deux listings met immédiatement en lumière les principales différences entre les deux assembleurs (l'ancien et le nouveau):
Ah, j'allais oublier: la directive "#smile" au début du programme ordonne à l'assembleur de m'afficher un smiley si la compilation s'est déroulée sans erreur... C'est plus utile que ça en a l'air.
En fait, nous allons nous contenter d'assez peu, comme nos cousins qui apprennent le Wimp en BASIC ou en C, au début. Mais rassurez-vous: nous accélèrerons très vite (enfin, cela dépendra de vos capacités d'assimilation, et de vos remarques, que j'espère voir arriver nombreuses au bureau de l'ARMada!).
Je passe sur les grandes introductions à la gloire du multi-tâche, qu'à présent tous les systêmes permettent plus ou moins, même si, à part Unix (encore que de façon moins complète), la communication entre applications est plus ou moins disponible... Pour ça, n'importe quel bouquin d'initiation au Wimp fera l'affaire.
Par contre, nous allons faire ensemble les exemples de base. Le premier de ceux-ci sera évidemment un machin qui affichera "Bonjour à tous!" avant de nous quitter...
;Premier programme Wimp en assembleur ;(C)1996 A. VIDOVIC pour l'ARMada #smile #set lenpile=1024 ;ceci devrait suffire pour une pile (en mots) ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #entry ADR R0,pile STR R13,[R0] MOV R13,R0 ;on crée une pile DB (ED) ;-------Initialisation du Wimp MOV R0,#200 ;derniere version connue de Wimp*100 MOV R1,#&4B534154 ;"TASK" ADR R2,nom_prog ;short description for Task Mgr SWI Wimp_Initialise ADR R0,task_handle STR R1,[R0] ;sauvegarder le task_handle ;-------Polling loop .je_polle MOV R0,#0 ;masque (Ici tout retourner) ADR R1,user_buf SWI Wimp_Poll ;=>R0: reason code... CMP R0,#0 ;pas de raison particulière BEQ affichage ;dans ce cas afficher le message B je_polle ;sinon continuer à attendre ;-------Affichage du message .affichage ADR R0,user_buf MOV R1,#255 STR R1,[R0],#4 ;premier mot de user_buf: code d'erreur ADR R1,message ;on va recopier le message dans user_buf .copier_mes LDRB R2,[R1],#1 ;prendre le prochain caractère STRB R2,[R0],#1 ;le stocker dans user_buf CMP R2,#0 ;dernier octet (le nul) copié? BNE copier_mes ;non: continuer à copier ADR R0,user_buf ;on va faire afficher le message MOV R1,#1 ;code "Ok" ADR R2,nom_prog ;message provenant de notre programme SWI Wimp_ReportError ;affiché par la sortie "Erreurs" du Wimp ;-------Fermeture su slot Wimp et sortie du programme ADR R0,task_handle ;récupérer le task handle LDR R0,[R0] MOV R1,#&4B534154 ;"TASK" SWI Wimp_CloseDown ADR R0,pile LDR R13,[R0] ;on restaure l'ancienne pile MOV R0,#0 SWI OS_Exit ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - DBD lenpile,0 ;espace pour la pile programme .pile DBD 1,0 ;sauvegarde ancien pointeur de pile .task_handle ;espace pour contenir le handle DBD 1,0 .user_buf ;un bloc de 256 octets (nuls) de long DBB 256,0 .nom_prog ;nom à donner au Wimp, au début DCB "MyFirst",0 .message ;le message (thaïlandais) DCB "Bonjour à tous!",0 #end
Une fois ce source tapé, sauvez-le dans un fichier appelé, mettons "Wimp1", et compilez-le. Si vous n'avez pas fait de faute de frappe, le smiley vous sourit bêtement, et vous pouvez alors démarrer (Oups! Le mot malheureux!) votre exécutable, qui alors dêclenchera l'ouverture d'une fenêtre impérative, contenant un panneau "Danger particulier", intitulé "Message from Myfirst", affichant "Bonjour à tous", et vous offrant d'appuyer sur le bouton "OK"...
Amusez-vous une trentaine de fois avec, comme si c'était la première fois de votre vie que vous voyiez une boîte de message d'erreur...
Mais nous n'allons pas tout relire. Seulement ce qui est nouveau. Toutefois, j'insiste: Si ce n'était pas suffisant, si vous avez la moindre hésitation, si quelque chose ne vous semble pas parfaitement clair, il faut alors que vous en fassiez la remarque au bureau de l'ARMada, qui me transmettra, ce qui me permettra d'apporter tous les éclaircissements nécessaires dans le prochain numéro!
DBD lenpile,0 ;espace pour la pile programme .pile DBD 1,0 ;sauvegarde ancien pointeur de pileLa directive DBD x,0 permet de réserver, dans le corps du programme exécutable, une zone de x mots de long, initialisés à la valeur 0. Vous pourriez lire cette directive "Define Block DoubleWord x, value 0" (définir un bloc de mots de longueur x, de valeur 0).
Remarque: DoubleWord, c'est un mot de 32bits, car historiquement les mots faisaient 16bits, mais de nos jours, c'est 32bits.
Le label ".pile" sert à stocker l'ancienne adresse de la pile système, qui est normalement dans R13, et sert de point de départ pour la nouvelle, que je définis uniquement pour ce programme, ce qui est conseillé, car c'est toujours plus propre d'avoir sa propre pile.
La pile, c'est une zone où seront empilées des valeurs utilisées entre autres par le système... Comme nous ne nous en servons pas ici, nous y reviendrons ultérieurement. Mais vous avez noté quele registre R13 a donc une utilisation spéciale, et que vous ne pourrez pas l'utiliser sans précautions dans vos programmes!
ADR R0,task_handle STR R1,[R0] ;sauvegarder le task_handleLa pseudo-instruction "ADR" permet de mettre dans le registre la valeur de l'adresse d'un label. Nous en avons parlé dans l'acte 1. Ce qui est nouveau, c'est le "STR" qui suit. Vous pouvez traduire "STR" par "Store Register", ou "Ranger le Registre". Ici, le contenu du registre R1 est recopié en mémoire, à l'adresse spécifiée dans le registre R0.
Relisez bien la phrase qui précède jusqu'à ce que vous ayez compris. C'est hyper-important.
Un peu plus loin, vous pouvez lire:
ADR R0,task_handle ;récupérer le task handle LDR R0,[R0]Ici, c'est l'opération inverse: "LDR", que vous pouvez traduire par "Load Register", ou "Charger le Registre". Il s'agit ici de lire en mémoire, à l'adresse spécifiée dans R0, un mot, et de le stocker dans... Et bien, comme vous le voyez, ici c'est dans R0, ce qui effacera l'ancienne valeur de ce registre, du coup.
En résumé, vous avez vu que rien de ce qui est dans la mémoire n'a été à un moment ou à un autre dans le microprocesseur, et nous avons vu les deux instructions:
CMP R0,#0 ;pas de raison particulièreC'est l'instruction "CMP", comme "Compare", qui permet cette opération. Ici, on compare le contenu du registre de 32bits R0 à la valeur 0. Mais ce n'est pas la comparaison elle-même qui décide de ce qui va suivre: elle ne fait que préparer le processeur à réagir quand il rencontrera un code de condition...
.je_polle (...) BEQ affichage ;dans ce cas afficher le message B je_polle ;sinon continuer à attendre .affichage (...)Il y a des labels, à certains endroits, et des instructions de branchement. En fait, il n'y a qu'une seule instruction, pour brancher simplement:
B labelC'est "B" comme "Brancher". C'est l'équivalent d'un GOTO en Basic ou en C (nous verrons l'équivalent du GOSUB plus tard).
Mais il y a des formes conditionnelles de "B", qu'on écrit simplement en ajoutant un code de condition de deux lettres "xx" après le "B". Les codes possibles sont:
EQ = MI HI GT > NE <> PL LS LE <= CS VS GE >= AL CC VC LT < NVA côté de certains d'entre eux, j'ai écrit des symboles qui donnent une idée de leur signification. Pour d'autres, cela nécessiterait de s'étendre un peu plus. Nous verrons cela ultérieurement.
Donc, nous sommes en mesure de comprendre la signification des deux lignes:
CMP R0,#0 ;pas de raison particulière BEQ affichage ;dans ce cas afficher le messageIci, donc, nous comparons la valeur de R0 avec 0. Si elle est égale, nous branchons l'exécution à partir du label ".affichage"... Autrement dit: si R0 contient une valeur nulle, nous allons à ".affichage"... Autrement dit: "IF R0=0 THEN GOTO affichage"
Par exemple: la pile, dont nous parlions plus haut... Mais nous y reviendrons plus tard. Ou encore: une chaîne de caractères, qui est un tableau de caractères, que l'on doit lire un par un. Ou encore, le tableau de données dont on transmet l'adresse au Wimp pour certaines opérations.
Pour accéder aux cellules de mémoire d'un tableau, nous pourrions faire une série d'opérations qui consisterait, à chaque fois, à mettre l'adresse de la cellule dans un registre avec "ADR", puis à accéder à la cellule... Mais il y a plus simple...
Il y a bien des moyens, mais ici, nous n'en verrons qu'un, qui s'appelle la "Post-incrémentation"... Pfff!.... Qu'est-ce que c'est que cette bête-là?
Jetez un coup d'oeil aux lignes du programme suivantes:
ADR R0,user_buf (...) STR R1,[R0],#4 ;premier mot de user_buf: code d'erreurIci, nous mettons d'abord l'adresse de la zone "user_buf" dans R0. Ensuite, nous écrivons le nombre contenu dans le registre R1 à l'adresse pointée par R0, mais ce n'est pas fini: le processeur, une fois cette écriture en mémoire effectuée, ajoutera 4 au contenu de R0, c'est-à-dire, il fera pointer R0 4 octets plus loin (4 octets, c'est la longueur d'un mot de 32 bits)!
Pourquoi? Parceque j'ai l'intention de continuer à écrire dans cette zone de mémoire, dans ce tableau que j'ai appelé "user_buf", après ce nombre, et sans écrire par dessus.
Post-incrémentation: J'ai incrémenté (augmenté, quoi!) après l'opération.
N'oubliez pas: post-incrémentation: virgule et incrément après le crochet fermé!
En fait, cette notion tient compte de tout ce que vous avez déjà vu. C'est bon signe, nous allons pouvoir commencer à travailler sans, sans cesse, avoir l'impression de débuter...
Revenons sur ces quelques lignes:
ADR R0,user_buf (...) ADR R1,message ;on va recopier le message dans user_buf .copier_mes LDRB R2,[R1],#1 ;prendre le prochain caractère STRB R2,[R0],#1 ;le stocker dans user_buf CMP R2,#0 ;dernier octet (le nul) copié? BNE copier_mes ;non: continuer à copier (...)C'est un peu long, mais vous connaissez déjà la plupart de ces instructions. Reste maintenant à comprendre leur effet groupé.
Tout d'abord, nous chargeons R0 avec une adresse (de destination), et R1 avec une adresse (de départ). Ensuite, à partir du label, nous lisons l'octet pointé par R1 dans R2, puis le stockons à l'adresse pointée par R0.
Vous avez bien sûr noté qu'après les opérations de lecture, et d'écriture, les registres R1 et R0 pointent sur l'octet suivant en mémoire, grâce à la post-incrémentation de 1.
Ensuite, nous comparons la valeur de l'octet que nous venons de recopier avec la valeur 0. Si R2 ne contient pas de 0, nous revenons au label et recommençons l'opération. Sinon, l'exécution se poursuit plus loin.
En clair: on recopie, octet par octet, la zone pointée par R1 dans celle pointée par R0, jusqu'à ce que l'octet copié soit nul.
Nous venons de réaliser une copie de chaîne de caractères "null-terminated": vous avez remarqué, dans le listing, que la chaîne "Bonjour à tous!" se termine par un octet nul:
.message ;le message (thaïlandais) DCB "Bonjour à tous!",0La directive "DCB" permet de définir une constante de valeur "octets": ici, les octets que nous incluons suivent, soit compris entre des guillemets, soit séparés par des virgules, soit les deux. Traduisez par "Define Constant Byte". (A propos, il y a aussi la directive "DBB", pour "Define Bloc Byte")
Voilà. C'était une des nombreuses sortes de boucles possibles. Ici, la boucle "UNTIL" (en anglais: "Jusqu'à").
En tous les cas, si cette façon plutôt "Bulldozer" d'avancer en assembleur vous convient, alors nous allons progresser très vite et nous pourrons plus vite que prévu sortir des programmes merveilleux de rapidité et de compacité! Et pourquoi pas des mégadémos programmées par tous les ARMadistes?
Bon, sur ce, bon courage, et à la prochaine!