Tutoriel 7 - Instructions asm et corps Programme FASM
Re: Tutoriel 7 - Instructions asm et corps Programme FASM
Moi, ça m’intéresse
Mesa.
Mesa.
- majikeyric
- Messages : 602
- Inscription : dim. 08/déc./2013 23:19
- Contact :
Re: Tutoriel 7 - Instructions asm et corps Programme FASM
Cool quelqu'un d'intéressé !
Bon, je ne vais sans doute rien apprendre à ceux qui codent déjà en ASM mais cela peut intéresser les personnes qui ont du mal à
l'appréhender.
(Les exemples sont pour du code 32 bit)
pour générer du code 64 bit, mettre en début de code:
ET ADAPTER les tailles des registres si besoin.
****************************************************************
Pour accéder aux paramètres et aux variables locales dans une fonction ASM, il n'y a pas besoin de s'embêter avec les choses
du style :
(pour accéder au paramètre Param1 de la fonction, sur la pile):
Pour pouvoir ensuite faire
au lieu de : (et qui est moins parlant)
FASM (comme d'autres assembleurs) propose des macros qui facilitent l'accès aux
paramètres et variables locales d'une fonction en assembleur (à son cadre de pile).
Ici ce sont les macros : proc , endp ou local (page 110 du manuel de FASM)
On n'a pas besoin de jongler avec le pointeur de pile (ESP(32 bit) ou RSP(64 bit)) et des déplacements
pour pouvoir accéder aux variables . C'est l'assembleur qui fait ces calculs pour nous.
D'autant plus que calculer le déplacement par rapport au pointeur de pile est fastidieux.
De plus utiliser le registre ESP (ou RSP) pour accéder au cadre de pile n'est pas une
vraiment bonne méthode, car si dans la fonction on modifie le pointeur de pile par empilement
d'une valeur par exemple pour la sauvegarder temporairement
tous nos accès ensuite aux paramètres et variables locales ne sont plus valides puisque ESP a changé.
(Mais c'est une manière de faire, c'est d'ailleurs la manière dont sont générées les fonctions PureBasic).
Le registre EBP (RBP en 64 bit) a été crée pour ça, c'est le "registre de base".
pour écrire une fonction ASM : 'MaSomme' qui accepte, par exemple, 2 paramètres (longs) en entrée et en retourne la somme
vous pouvez le faire directement comme cela:
L'assembleur lui générera ça :
Pour avoir accès aux macros proc et endp, il faut inclure un des "headers" proposés par FASM,
ici par exemple "win32a.inc" pour 32 bit Ascii.
Donc notre code complet ASM sera:
et c'est tout!
Vous le compilez avec FASM pour générer le fichier objet : .obj
Vous créer le fichier .desc associé :
La convention d'appel de la fonction est : StdCall,
(la manière dont les paramètres sont passés à la fonction puis libérés)
Donc ne pas oublier de le spécifier ! (à la fin en attribut de la valeur de retour)
Et vous passez le tout à LibraryMaker dans le répertoire SDK de PureBasic,
dans le readme.txt de ce répertoire, il y aussi plein d'infos intéressantes.
Vous pouvez aussi déclarer des variables locales dans une fonction ASM (page 111 du manuel de FASM) :
Vous pouvez aussi spécifier (à l'aide de "USES"), si votre fonction utilisent certains registres qui ne doivent pas être modifiés,
l'assembleur génère automatiquement les instructions de sauvegarde et restauration de ces registres
en début et fin de procédure.
On peut aussi déclarer les variables locales à une fonction comme cela (pour des DWORDs on met DD (define Double), ?: sans valeur prédéfinie):
Voilà, j'ai "survolé" quelques pages du manuel de FASM, mais j'ai pensé que cela pouvait intéresser certains,
l'emploi de macros facilite grandement le développement en ASM en s'affranchissant de certaines taches fastidieuses.
Bon, je ne vais sans doute rien apprendre à ceux qui codent déjà en ASM mais cela peut intéresser les personnes qui ont du mal à
l'appréhender.
(Les exemples sont pour du code 32 bit)
pour générer du code 64 bit, mettre en début de code:
Code : Tout sélectionner
format MS64 COFF
include "Win64a.inc"
****************************************************************
Pour accéder aux paramètres et aux variables locales dans une fonction ASM, il n'y a pas besoin de s'embêter avec les choses
du style :
(pour accéder au paramètre Param1 de la fonction, sur la pile):
Code : Tout sélectionner
Param1 equ esp + 8
Code : Tout sélectionner
mov eax,[Param1]
Code : Tout sélectionner
mov eax,[esp+8]
paramètres et variables locales d'une fonction en assembleur (à son cadre de pile).
Ici ce sont les macros : proc , endp ou local (page 110 du manuel de FASM)
On n'a pas besoin de jongler avec le pointeur de pile (ESP(32 bit) ou RSP(64 bit)) et des déplacements
pour pouvoir accéder aux variables . C'est l'assembleur qui fait ces calculs pour nous.
D'autant plus que calculer le déplacement par rapport au pointeur de pile est fastidieux.
De plus utiliser le registre ESP (ou RSP) pour accéder au cadre de pile n'est pas une
vraiment bonne méthode, car si dans la fonction on modifie le pointeur de pile par empilement
d'une valeur par exemple pour la sauvegarder temporairement
tous nos accès ensuite aux paramètres et variables locales ne sont plus valides puisque ESP a changé.
(Mais c'est une manière de faire, c'est d'ailleurs la manière dont sont générées les fonctions PureBasic).
Le registre EBP (RBP en 64 bit) a été crée pour ça, c'est le "registre de base".
pour écrire une fonction ASM : 'MaSomme' qui accepte, par exemple, 2 paramètres (longs) en entrée et en retourne la somme
vous pouvez le faire directement comme cela:
Code : Tout sélectionner
proc PB_MaSomme param1:DWORD, param2:DWORD
mov eax,[param1] ; eax=param1
add eax,[param2] ; eax=eax+param2
ret ; le resultat est dans eax
endp
Code : Tout sélectionner
param1 equ ebp+8
param2 equ ebp+12
push ebp ; on sauve ebp
mov ebp,esp ; ebp=esp
sub esp,0 ; place réservée sur la pile pour les variables locales, ici on en a pas, donc : 0
; votre code
mov eax,[param1] ; eax=param1
add eax,[param2] ; eax=eax+param2
;-
mov esp,ebp ; on restaure esp
pop ebp ; on restaure ebp
ret 8 ; on sort de la fonction et libère la place réservée pour les paramètres sur la pile
ici par exemple "win32a.inc" pour 32 bit Ascii.
Donc notre code complet ASM sera:
Code : Tout sélectionner
format MS COFF
include "Win32a.inc"
public PB_MaSomme
section '.text' code readable executable
proc PB_MaSomme param1:DWORD, param2:DWORD
mov eax,[param1] ; eax=param1
add eax,[param2] ; eax=eax+param2
ret
endp
Vous le compilez avec FASM pour générer le fichier objet : .obj
Vous créer le fichier .desc associé :
La convention d'appel de la fonction est : StdCall,
(la manière dont les paramètres sont passés à la fonction puis libérés)
Donc ne pas oublier de le spécifier ! (à la fin en attribut de la valeur de retour)
Code : Tout sélectionner
; Langage utilisé pour coder la librairie ASM ou C
ASM
; Nombre de Dll windows utilisées par la lib
0
; Type de librairie (OBj,LIB)
OBJ
; Librairies PureBasic & utilisateur utilisées par la librairie
0
; Nom du fichier d'aide de notre librairie, OBLIGATOIRE !
test1.chm
; Enumération des nouvelles fonctions crées
MaSomme, Long, Long (param1, param2)
Long | StdCall
dans le readme.txt de ce répertoire, il y aussi plein d'infos intéressantes.
Vous pouvez aussi déclarer des variables locales dans une fonction ASM (page 111 du manuel de FASM) :
Code : Tout sélectionner
proc PB_MaSomme param1:DWORD, param2:DWORD
local var1:DWORD
mov dword [var1],123
mov eax,[param1] ; eax=param1
add eax,[param2] ; eax=eax+param2
add eax,[var1] ; eax=eax+var1
ret
endp
l'assembleur génère automatiquement les instructions de sauvegarde et restauration de ces registres
en début et fin de procédure.
Code : Tout sélectionner
proc PB_MaSomme uses ebx esi,param1:DWORD, param2:DWORD
local var1:DWORD
mov ebx,98
mov esi,11212
mov dword [var1],123
mov eax,[param1] ; eax=param1
add eax,[param2] ; eax=eax+param2
add eax,[var1] ; eax=eax+var1
add eax,ebx ; eax=eax+ebx
add eax,esi ; eax=eax+esi
ret
endp
Code : Tout sélectionner
locals
var1 dd ?
var2 dd ?
endl
l'emploi de macros facilite grandement le développement en ASM en s'affranchissant de certaines taches fastidieuses.
Re: Tutoriel 7 - Instructions asm et corps Programme FASM
La convention d'appel classique en 32/64 StdCall
Si on omet StdCall dans le fichier descripteur pour le code 32 bit, le 1er paramètre sera passé par eax.
J'utilise les macro FASM du fichier IF.INC qui permettent de créer des if/elseif/else/endif et des boucles .while/.endw et .repeat/.until.
J'utilisais ces Macros avec MASM il y a déjà quelques années.
Les tests se font sur les registres.
par exemple pour utiliser la fonction PB IsGadget en supposant que la fonction n'a qu'un paramètre en stardCall (passage du paramètre par la pile).
Voila en 32 bit, le test se fait sur eax ici je le fait en non signé car si eax vaut 0, la fonction a échoué, on teste sur la valeur 0 et c'est comme PB on utilise l'inversion avec ~
Pour faire le test en signé, il faut utiliser le mot signed avant l'expression.
A noter que j'ai déclaré le mot Resultat comme étant équivalent au registre eax.
FASM va remplacer Resultat par eax à la compilation.
Cela apporte un peu de clarté au code.
le code suivant veut dire :
si eax est différent de 0 alors saute à l'étiquette (label) _Ok
que l'on pourrait écrire aussi
pour le code qui suit c'est le contraire :
si eax = 0 alors saute à l'étiquette (label) Erreur
que l'on pourrait écrire aussi
je n'utilise pas toujours les .if
exemple d'une boucle while extraite d'une de mes fonctions en 64 bit.
J'ai mis la fonction complète avec les déclarations.
A noter, la macro ne modifie pas les registres, il faut donc faire évoluer le/les registres qui permettent de sortir de la boucle.
Le test peut être une expression avec plusieurs registres.
En 64 bits, Il faut aligner chaque variable locales sur 8 bit ou sur un multiple de 8 sinon on crash.
Idem pour les variables globales que j'utilise dans cette fonction (sinon crash ?).
La marco alignvar est déclarée et utilisée entre chaque déclaration dans la section bss.
Je n'ai initialisé aucune variable à 0 ni locale ni globale.
Si on omet StdCall dans le fichier descripteur pour le code 32 bit, le 1er paramètre sera passé par eax.
J'utilise les macro FASM du fichier IF.INC qui permettent de créer des if/elseif/else/endif et des boucles .while/.endw et .repeat/.until.
J'utilisais ces Macros avec MASM il y a déjà quelques années.
Les tests se font sur les registres.
par exemple pour utiliser la fonction PB IsGadget en supposant que la fonction n'a qu'un paramètre en stardCall (passage du paramètre par la pile).
Voila en 32 bit, le test se fait sur eax ici je le fait en non signé car si eax vaut 0, la fonction a échoué, on teste sur la valeur 0 et c'est comme PB on utilise l'inversion avec ~
Pour faire le test en signé, il faut utiliser le mot signed avant l'expression.
A noter que j'ai déclaré le mot Resultat comme étant équivalent au registre eax.
FASM va remplacer Resultat par eax à la compilation.
Cela apporte un peu de clarté au code.
le code suivant veut dire :
si eax est différent de 0 alors saute à l'étiquette (label) _Ok
Code : Tout sélectionner
.if Resultat
jmp _Ok
.endif
Code : Tout sélectionner
.if Resultat <> 0
jmp _Ok
.endif
si eax = 0 alors saute à l'étiquette (label) Erreur
Code : Tout sélectionner
.if ~Resultat
jmp _Erreur
.endif
Code : Tout sélectionner
.if Resultat = 0
jmp _Erreur
.endif
Code : Tout sélectionner
;----------------------------------------------
; Commandes PureBasic
;----------------------------------------------
extrn _PB_IsGadget@4
PB_IsGadget equ _PB_IsGadget@4
;----------------------------------------------
; équivalences
;----------------------------------------------
Resultat equ eax
GadgetID equ eax
;----------------------------------------------
; Paramètre d'entrée
;----------------------------------------------
; Gadget (Integer)
Gadget equ esp+4
; Appel de la fonction PB IsGadget()
PUSH dword [Gadget]
CALL PB_IsGadget
.if ~Resultat
jmp _Erreur
.endif
exemple d'une boucle while extraite d'une de mes fonctions en 64 bit.
J'ai mis la fonction complète avec les déclarations.
A noter, la macro ne modifie pas les registres, il faut donc faire évoluer le/les registres qui permettent de sortir de la boucle.
Le test peut être une expression avec plusieurs registres.
En 64 bits, Il faut aligner chaque variable locales sur 8 bit ou sur un multiple de 8 sinon on crash.
Idem pour les variables globales que j'utilise dans cette fonction (sinon crash ?).
La marco alignvar est déclarée et utilisée entre chaque déclaration dans la section bss.
Je n'ai initialisé aucune variable à 0 ni locale ni globale.
Code : Tout sélectionner
; Fichier généré le 01/03/2016 à 11h44
format MS64 COFF
public PB_SwapRows_F
;**********************************************
; X64 ASCII
;**********************************************
;----------------------------------------------
; Include
;----------------------------------------------
include '..\IF.INC'
; macro d'alignement des variables globales sur 32 bits
macro alignvar value {rb (value-1) - ($-_SectionVariables + value-1) mod value}
;----------------------------------------------
; Constantes
;----------------------------------------------
Erreur equ -1
False equ 0
True equ 1
HDM_GETITEMCOUNT equ 4608
LVIF_COLFMT equ 65536
LVIF_COLUMNS equ 512
LVIF_DI_SETITEM equ 4096
LVIF_GROUPID equ 256
LVIF_IMAGE equ 2
LVIF_INDENT equ 16
LVIF_NORECOMPUTE equ 2048
LVIF_PARAM equ 4
LVIF_STATE equ 8
LVIF_TEXT equ 1
LVM_GETHEADER equ 4127
LVM_GETITEM equ 4101
LVM_GETITEMCOUNT equ 4100
LVM_SETITEM equ 4102
MaskLecture equ LVIF_COLFMT or LVIF_COLUMNS or LVIF_DI_SETITEM or LVIF_GROUPID or LVIF_IMAGE
MaskLecture equ MaskLecture or LVIF_INDENT or LVIF_NORECOMPUTE or LVIF_PARAM or LVIF_STATE or LVIF_TEXT
StateLecture equ MaskLecture
MaskEcriture_Item equ MaskLecture
MaskEcriture_SubItem equ LVIF_TEXT or LVIF_IMAGE
;----------------------------------------------
; Commandes PureBasic
;----------------------------------------------
extrn PB_GadgetID
;----------------------------------------------
; API Windows
;----------------------------------------------
extrn SendMessageA
SendMessage equ SendMessageA
extrn UpdateWindow
;----------------------------------------------
; équivalences
;----------------------------------------------
Decalage = (Fin_Decalage-Debut_Decalage)*8
; nombre d'octets réservés sur la pile
ShadowSpace = 40
LocalVar = 176 + ShadowSpace
Colonne equ esi
Nb_Colonnes equ edi
Colonne_x64 equ rsi
Nb_Colonnes_x64 equ rdi
NB_Elements equ rax
HeaderID equ rax
;----------------------------------------------
; Offset des structures
;----------------------------------------------
; Structure Lvitem (Taille : 84 octets)
lvitem.mask equ 0 ; long
lvitem.iItem equ 4 ; long
lvitem.iSubItem equ 8 ; long
lvitem.state equ 12 ; long
lvitem.stateMask equ 16 ; long
lvitem.PB_Alignment1_0 equ 20 ; byte x 4
lvitem.PB_Alignment1_1 equ 21 ; byte
lvitem.PB_Alignment1_2 equ 22 ; byte
lvitem.PB_Alignment1_3 equ 23 ; byte
lvitem.pszText equ 24 ; pointeur
lvitem.cchTextMax equ 32 ; long
lvitem.iImage equ 36 ; long
lvitem.lParam equ 40 ; integer
lvitem.iIndent equ 48 ; long
lvitem.iGroupId equ 52 ; long
lvitem.cColumns equ 56 ; long
lvitem.PB_Alignment2_0 equ 60 ; byte x 4
lvitem.PB_Alignment2_1 equ 61 ; byte
lvitem.PB_Alignment2_2 equ 62 ; byte
lvitem.PB_Alignment2_3 equ 63 ; byte
lvitem.puColumns equ 64 ; integer
lvitem.piColFmt equ 72 ; integer
lvitem.iGroup equ 80 ; long
;----------------------------------------------
; Paramètres d'entrée
;----------------------------------------------
; GadgetId (Integer)
GadgetId equ rsp+Decalage+LocalVar+8
; Item1 (Integer)
Item1 equ rsp+Decalage+LocalVar+16
; Item2 (Integer)
Item2 equ rsp+Decalage+LocalVar+24
;----------------------------------------------
; Variables locales
;----------------------------------------------
; Element1.Lvitem (taille de la structure : 84)
Element1 equ qword [rsp+ShadowSpace]
; Element2.Lvitem (taille de la structure : 84)
Element2 equ qword [rsp+ShadowSpace+88]
;----------------------------------------------
; Variables globales
;----------------------------------------------
; Text1.Buffer (Structure Taille : 101)
Text1 equ v_Text1
; Text2.Buffer (Structure Taille : 101)
Text2 equ v_Text2
;----------------------------------------------
; Section code
;----------------------------------------------
; les registres volatiles PB sont : rax, rcx, rdx, r8, r9, xmm0, xmm1, xmm2 et xmm3
section '.text' code readable executable
PB_SwapRows_F:
; sauvegarde des paramètres sur la pile
MOV qword [rsp+08], rcx
MOV qword [rsp+16], rdx
MOV qword [rsp+24], r8
Debut_Decalage:
PUSH rsi
PUSH rdi
Fin_Decalage:
; réserve la place sur la pile (cadre de pile)
SUB rsp, LocalVar
XOR r9, r9
MOV rdx, LVM_GETITEMCOUNT
MOV r8, r9
CALL SendMessage
CMP NB_Elements, 2
jl _Erreur
MOV rcx, qword [Item1]
NOP
TEST rcx, rcx
jl _Erreur
MOV rdx, qword [Item2]
NOP
TEST rdx, rdx
jl _Erreur
CMP rcx, NB_Elements
JGE _Erreur
CMP rdx, NB_Elements
JGE _Erreur
CMP rcx, rdx
JE _Erreur
XOR r9, r9
MOV rdx, LVM_GETHEADER
MOV r8, r9
MOV rcx, qword [GadgetId]
CALL SendMessage
OR rax, rax
JZ _Erreur
XOR r9, r9
MOV rdx, HDM_GETITEMCOUNT
MOV r8, r9
MOV rcx, HeaderID
CALL SendMessage
CMP rax, -1
JE _Retour
PUSH rax
; initialisation compteur de boucle
MOV Colonne_x64, 0
POP Nb_Colonnes_x64
.while Colonne < Nb_Colonnes
LEA r9, Element1
MOV rcx, qword [Item1]
; Element1\mask = #maskLecture
MOV dword [r9+lvitem.mask], MaskLecture
; Element1\iItem = Item1
NOP
MOV dword [r9+lvitem.iItem], ecx
; Element1\iSubItem = Colonne
NOP
MOV dword [r9+lvitem.iSubItem], Colonne
; Element1\state = #StateLecture
NOP
MOV dword [r9+lvitem.state], StateLecture
; Element1\stateMask = -1
NOP
MOV dword [r9+lvitem.stateMask], -1
; Element1\pszText = *pszText1
MOV r8, Text1
MOV qword [r9+lvitem.pszText], r8
; Element1\cchTextMax = SizeOf(Text1)-SizeOf(character)
NOP
MOV dword [r9+lvitem.cchTextMax], 101
; If SendMessage_(GadgetID, #LVM_GETITEM, 0, @Element1) = #False
XOR r8, r8
MOV rdx, LVM_GETITEM
MOV rcx, qword [GadgetId]
CALL SendMessage
OR rax, rax
JZ _Erreur
; Element2\mask = #maskLecture
LEA r9, Element2
MOV rcx, qword [Item2]
MOV dword [r9+lvitem.mask], MaskLecture
; Element2\iItem = Item2
NOP
MOV dword [r9+lvitem.iItem], ecx
; Element2\iSubItem = Colonne
NOP
MOV dword [r9+lvitem.iSubItem], Colonne
; Element2\state = #StateLecture
NOP
MOV dword [r9+lvitem.state], StateLecture
; Element2\stateMask = -1
NOP
MOV dword [r9+lvitem.stateMask], -1
; Element2\pszText = *pszText2
MOV r8, Text2
MOV qword [r9+lvitem.pszText], r8
; Element2\cchTextMax = SizeOf(Text1)-SizeOf(character)
NOP
MOV dword [r9+lvitem.cchTextMax], 101
; If SendMessage_(GadgetID, #LVM_GETITEM, 0, @Element2) = #False
XOR r8, r8
MOV rdx, LVM_GETITEM
MOV rcx, qword [GadgetId]
CALL SendMessage
OR rax, rax
JZ _Erreur
;// on réécrit l'élément Item2 à l'emplacement Item1
LEA r9, Element2
OR Colonne, Colonne
; Element2\mask = #maskEcriture_SubItem
MOV dword [r9+lvitem.mask], MaskEcriture_SubItem
JNZ _Element2_iItem_Element2
; Element2\mask = #maskEcriture_Item
MOV dword [r9+lvitem.mask], MaskEcriture_Item
_Element2_iItem_Element2:
; Element2\iItem = Item1
PUSH qword [Item1]
POP rcx
NOP
MOV dword [r9+lvitem.iItem], ecx
; Element2\iSubItem = Colonne
NOP
MOV dword [r9+lvitem.iSubItem], Colonne
; If SendMessage_(GadgetID, #LVM_SETITEM, 0, @Element2) = #False
XOR r8, r8
MOV rdx, LVM_SETITEM
MOV rcx, qword [GadgetId]
CALL SendMessage
OR rax, rax
JZ _Erreur
;// on réécrit l'élément Item1 à l'emplacement Item2
LEA r9, Element1
OR Colonne, Colonne
; Element1\mask = #maskEcriture_SubItem
MOV dword [r9+lvitem.mask], MaskEcriture_SubItem
JNZ _Element2_iItem_Element1
_MaskEcriture_Item_Element1:
; Element1\mask = #maskEcriture_Item
MOV dword [r9+lvitem.mask], MaskEcriture_Item
_Element2_iItem_Element1:
; Element1\iItem = Item2
PUSH qword [Item2]
POP rcx
NOP
MOV dword [r9+lvitem.iItem], ecx
; Element1\iSubItem = Colonne
NOP
MOV dword [r9+lvitem.iSubItem], Colonne
; If SendMessage_(GadgetID, #LVM_SETITEM, 0, @Element1) = #False
XOR r8, r8
MOV rdx, LVM_SETITEM
MOV rcx, qword [GadgetId]
CALL SendMessage
OR rax, rax
JZ _Erreur
INC Colonne
.endw
MOV rcx, qword [GadgetId] ; rcx = GadgetId(Gadget)
CALL UpdateWindow
.if rax
MOV rax, True ; c'est Ok, on retourne #True
_Retour:
ADD rsp, LocalVar
POP rdi
POP rsi
RET
.endif
_Erreur:
MOV rax, Erreur
JMP _Retour
;----------------------------------------------
; Déclaration des variables globales
;----------------------------------------------
section '.bss' readable writeable
_SectionVariables:
; Text1.Buffer (Structure Taille : 101)
alignvar 8
v_Text1 rb 101
; Text2.Buffer (Structure Taille : 101)
alignvar 8
v_Text2 rb 101
Re: Tutoriel 7 - Instructions asm et corps Programme FASM
En fait, il faudrait réécrire un compilateur