Auteur

Auteur: Faiseur

samedi 10 juillet 2010

Développer en langage assembleur: Tutoriel 2

TUTORIEL 2
PAR FAISEUR



UTILISATION DE MACROS EVOLUEES

Une partie de la force de Masm en tant qu'assembleur est l'intégration d'un système de macros puissantes au niveau du pré-processeur. Elles facilitent le travail du programmeur lorsqu'il met en place un projet en lui permettant d'utiliser du code de haut niveau tout en préservant un code compilé pratiquement sans compromis. Le projet MASM32 a progressivement développé un large éventail de macros depuis maintenant deux décennies.

Pour se servir des macros il est nécessaire d'ajouter le fichier "macros.asm" dans les définitions de votre programme. Il vous suffit d'ajouter, dans le squelette de votre programme, la ligne suivante...

Code:
include \masm32\macros\macros.asm


...dans la partie où vous placez les liens des librairies. C'est tout !

Lors de la compilation Masm32 va se servir de ce fichier pour récupérer toutes les macros que vous pourriez utiliser dans votre code.

Notez qu'il ne faut pas utiliser la fonction invoke avec des macros, c'est pourquoi la forme d'appel est directe.

Voici une sélection de macros parmi les plus usitées. Nous allons les découvrir à travers notre incontournable "minimum.asm".



M2M


En assembleur il n'est pas possible de placer la valeur d'une variable directement dans une autre variable.

FAUX:

Code:
mov variable1, variable2



JUSTE:

Code:
push variable1 <-- on place la valeur de variable1 dans la pile
pop variable2 <-- récupération de la valeur placée dans la pile dans variable2


ou encore:

Code:
mov eax, variable2
mov variable1, eax



La macro "m2m" permet de procéder à cette action en une ligne.


Usage:

m2m ,

Exemple:

Code:
m2m variable1, variable2





Switch - Case - Default (optionnel) - Endsw

Cette macro utilise le même principe que le bloc .IF - .ENDIF.

Switch == place la variable de comparaison

Case == .If

Default (optionnel) == .Else (optionnel)

Endsw == .Endif



Dans ce cas à quoi bon changer ? Le bloc .IF - .ENDIF est simple et pratique à utiliser. Cela dit il n'est pas possible, en assembleur, de comparer directement deux variables. Il vous faut placer au moins une des variables dans un registre afin de pouvoir la comparer.

Explication par l'exemple:

FAUX:

.
Code:
if variable1 == variable2

.endif


Vous ne pourrez pas compiler ce code ! Placez une des variables dans un registre pour effectuer la comparaison:

JUSTE:

Code:
mov eax, variable1
.if eax == variable2

.endif


De cette manière votre comparaison fonctionnera. Voici les limites d'utilisation du bloc .if .endif - et du langage assembleur sur ce sujet - comparé à certains langages de haut niveau. Petite digression: cela nous apprend donc que les langages de haut niveau ajoutent des procédures dans le code d'un exécutable pour permettre ce type de comparaison directe.

Nous pouvons faire de même. Si vous souhaitez atteindre une comparaison ne nécessitant pas de manipulation de registre vous pouvez vous servir des macros Switch - Case - Default (optionnel) - Endsw

Voici comment procéder: vous déclarez la variable de comparaison avec Switch; cela équivaut à la placer dans un registre virtuel. La macro fera ce travail pour vous. Ainsi il devient possible d'écrire une comparaison directe telle que:

Code:
Switch variable1
Case variable2 ; si variable1 == variable2

Case variable3 ; si variable1 == variable3

Case variable4 ; si variable1 == variable4

Endws



Lorsque vous devez effectuer plusieurs comparaisons de ce type cette macro peut vous simplifier le code. Il n'y a qu'une limitation d'importance: la comparaison ne peut se faire que pour vérifier une égalité. C'est pourquoi cette macro est surtout utilisée dans le cas d'applications utilisant l'interface Windows (GUI) que nous verrons dans un autre tutoriel.

Le bloc .if .endif permet, par contre, de vérifier une différence, un nombre inférieur ou supérieur, etc. En voici l'essentiel:


Egalité:

Code:
.if eax == 0 ; si eax est égal à 0

.endif



Différence:

.
Code:
if eax != 0 ; si eax est différent de 0

.endif



Inférieur ou égal:

.
Code:
if eax <= variable1; si eax est inférieur ou égal à variable1

.endif


Supérieur ou égal:

.
Code:
if eax >= variable1; si eax est plus grand ou égal à variable1

.endif



Pour terminer sur ce sujet reprenons, comme précédemment, notre dès à présent fameux "minimum.asm", un vrai classique :-)


Code:
.386
.model flat, stdcall
option casemap :none

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc

includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

include \masm32\macros\macros.asm

.data
szDlgTitle db "Minimum MASM",0
szMsg db "Choisissez Oui, Non ou Annuler",0
szMsgOui db "Oui",0
szMsgNon db "Non",0
szMsgCancel db "Annuler",0

.code

start:
Switch eax
invoke MessageBox,0,ADDR szMsg,ADDR szDlgTitle,MB_YESNOCANCEL

Case IDYES ; si réponse Oui
invoke MessageBox,0,ADDR szMsgOui,ADDR szDlgTitle,MB_OK
Case IDNO ; si réponse Non
invoke MessageBox,0,ADDR szMsgNon,ADDR szDlgTitle,MB_OK
Case IDCANCEL ; si réponse Annuler
invoke MessageBox,0,ADDR szMsgCancel,ADDR szDlgTitle,MB_OK
Endsw

invoke ExitProcess,0

end start


Nous avons ici utilisé notre macro et ajouté une troisième sélection à notre MessageBox: oui, non, annuler. Cette troisième sélection est ajoutée en utilisant la constante MB_YESNOCANCEL. Les trois possibilités de sélection de l'utilisateur sont ensuite analysées par notre macro avec Case.



MsgBox

La macro MsgBox simplifie la vie avec l'API MessageBox qui, il faut le dire, est couramment utilisée. L'appel de cette macro est des plus simple.

Usage:

MsgBox hWnd, lpText, lpCaption, uType

La force de cette macro tient au fait qu'elle vous permet d'insérer directement la chaîne de caractère que vous souhaitez sans avoir besoin de la spécifier dans la section .data et dans le .code.

Exemple:

Code:
MsgBox 0,"Texte","Caption",0



Nous pouvons à présent purger l'exemple précédent de sa section data:


Code:
.386
.model flat, stdcall
option casemap :none

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc

includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

include \masm32\macros\macros.asm


.code

start:
switch eax
MsgBox 0,"Choisissez Oui, Non ou Annuler","Minimum MASM",MB_YESNOCANCEL

Case IDYES ; si réponse Oui
MsgBox 0,"Oui","Minimum MASM",MB_OK
Case IDNO ; si réponse Non
MsgBox 0,"Non","Minimum MASM",MB_OK
Case IDCANCEL
MsgBox 0,"Annuler","Minimum MASM",MB_OK
Endsw

invoke ExitProcess,0

end start




Etape "purgatoire": Simple is beautiful !


Essayez donc le code suivant:


Code:
.386
.model flat, stdcall
option casemap :none

include \masm32\include\windows.inc
include \masm32\macros\macros.asm
uselib user32,kernel32

.code

start:
exit rv(MessageBox,0,"Hello !","Minimum MASM",0)
end start



Oh ! Une ligne de code pour tout faire !

Voici les macros utilisées pour arriver à cette simplification:


exit

La macro "exit" remplace simplement la fonction "ExitProcess". Vous pouvez l'utiliser seule ou lui adjoindre une valeur. Par défaut la valeur est "0", ce qui équivaut à utiliser la fonction ainsi: "invoke ExitProcess,0".

Usage:

Exit ou Exit ou encore Exit(valeur)



Uselib

Cette macro assigne les noms reçus comme fichiers .inc et .lib ! Cela vous simplifie le travail pour déclarer les dll utilisées par votre programme.

Lorsqu'une nouvelle dll est à inclure dans les définitions insérez simplement son nom (sans l'extension) en utilisant la virgule comme séparation.

Exemple:

Code:
uselib masm32,gdi32,user32,kernel32,Comctl32,comdlg32,shell32,oleaut32,msvcrt


Cela vous enlève toutes les lignes:

Code:
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
etc...
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
etc...

Note: n'oubliez pas d'utiliser la macro "uselib" après l'ajout des fichiers windows.inc et macros.asm.



rv

La macro rv est très intéressante, elle vous permet:

- D'inclure l'appel d'une fonction API

- D'y insérer des chaînes de caractères (tout comme la macro MsgBox cette macro insérera automatiquement les chaînes de caractères dans la section .data lors de la compilation)

- De recevoir la valeur de retour de l'API dans une variable !


Usage:

mov result, rv(API,param1,param2,...)

Exemple:

Code:
mov variable1, rv(MessageBox,0,"Message Text","Title",0)


Lorsque vous vous servez de cette macro dans le code, la valeur de retour peut être traitée en tant que registre EAX. C'est pourquoi vous pouvez directement placer la valeur de retour dans une variable (pour rappel, il n'est pas possible de placer directement une variable dans une autre variable, sauf si l'une est un registre).

Autrement dit le code suivant:
Code:
MsgBox,0,"Message Text","Title",0
mov variable1,eax


Peut être remplacé par:
Code:
mov variable1, rv(MessageBox,0,"Message Text","Title",0)


Allons plus loin. La macro d'appel elle-même peut être utilisée directement en tant que registre EAX (c'est d'ailleurs ce qui est expliqué dans le fichier d'aide de cette macro). Autrement dit l'on peut effectuer une comparaison de la macro tout comme s'il s'agissait d'un registre, ce qui nous évitera la condition .IF - .ENDIF. Remplaçons-là pédagogiquement par de nouvelles instructions assembleur: CMP et JNE.
On se met dans le bain avec notre désormais immuable "minimum.asm":

Code:
.386
.model flat, stdcall
option casemap :none ; case sensitive

include \masm32\include\windows.inc
include \masm32\macros\macros.asm
uselib user32,kernel32

.code
start:
cmp rv(MessageBox,0,"Faites votre choix:","Minimum MASM",MB_YESNO),IDYES
jne @F
MsgBox 0,"Vous avez cliqué sur Oui","Message",0
jmp fin
@@:
MsgBox 0,"Vous avez cliqué sur Non","Message",0
fin:
exit
end start


Analyse du code:
Code:
cmp rv(MessageBox,0,"Hello !","Minimum MASM",0),IDYES


L'instruction CMP permet de comparer deux opérandes.
Le résultat de la fonction API MessageBox est placé dans le registre EAX en utilisant la macro "rv". Cela équivaut à écrire:
Code:
MsgBox 0,"Hello !","Minimum MASM",0
cmp eax,IDYES


JNE effectue un saut à l'étiquette @@ si la comparaison n'est pas égale. Autrement dit, si la valeur de EAX n'est pas égale à la valeur IDYES on saute. Sinon on poursuit. Le reste du code n'a pas besoin de complément, tout a déjà été vu et revu précédemment.


fn

Le meilleur pour la fin d'un certain point de vue; si vous ne souhaitez pas utiliser la valeur de retour d'une fonction, ce qui arrivera le plus souvent, et que vous devez placer une chaîne de caractère lors de l'utilisation d'une fonction, la macro "fn" est faite pour vous. Elle préserve tour à fait le code original lors de la compilation, autrement dit vous pouvez l'utiliser en remplacement d'invoke peu importe la situation.

Usage:
fn API,param1,param2,...

Exemple:

Code:
.386
.model flat, stdcall
option casemap :none ; case sensitive

include \masm32\include\windows.inc
include \masm32\macros\macros.asm
uselib user32,kernel32


.code
start:

cmp rv(MessageBox,0,"Faites votre choix:","Minimum MASM",MB_YESNO),IDYES
jne @F
fn MessageBox,0,"Vous avez clique sur Oui","Message",0
jmp fin
@@:
fn MessageBox,0,"Vous avez cliqué sur Non","Message",0
fin:
exit

end start


A la différence de la macro MsgBox, la macro fn est utilisable pour toutes les fonctions API !



Juillet 2010 - Faiseur

0 commentaires:

Enregistrer un commentaire