Tutoriel 7 - Instructions asm et corps Programme FASM

Pour discuter de l'assembleur
Mesa
Messages : 1092
Inscription : mer. 14/sept./2011 16:59

Re: Tutoriel 7 - Instructions asm et corps Programme FASM

Message par Mesa »

Moi, ça m’intéresse :D

Mesa.
Avatar de l’utilisateur
majikeyric
Messages : 602
Inscription : dim. 08/déc./2013 23:19
Contact :

Re: Tutoriel 7 - Instructions asm et corps Programme FASM

Message par majikeyric »

Cool quelqu'un d'intéressé ! :D

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"
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):

Code : Tout sélectionner

Param1    equ     esp + 8
Pour pouvoir ensuite faire

Code : Tout sélectionner

		mov	eax,[Param1]
au lieu de : (et qui est moins parlant)

Code : Tout sélectionner

		mov	eax,[esp+8]
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:

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

L'assembleur lui générera ça :

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
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:

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

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)

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
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) :

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

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.

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

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):

Code : Tout sélectionner

	locals
			var1 dd ?
			var2 dd ?
	endl
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.
Anonyme2
Messages : 3518
Inscription : jeu. 22/janv./2004 14:31
Localisation : Sourans

Re: Tutoriel 7 - Instructions asm et corps Programme FASM

Message par Anonyme2 »

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

Code : Tout sélectionner

  .if Resultat
      jmp  _Ok
  .endif
que l'on pourrait écrire aussi

Code : Tout sélectionner

  .if Resultat <> 0
      jmp  _Ok
  .endif
pour le code qui suit c'est le contraire :
si eax = 0 alors saute à l'étiquette (label) Erreur

Code : Tout sélectionner

  .if ~Resultat
      jmp  _Erreur
  .endif
que l'on pourrait écrire aussi

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
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.

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
Anonyme2
Messages : 3518
Inscription : jeu. 22/janv./2004 14:31
Localisation : Sourans

Re: Tutoriel 7 - Instructions asm et corps Programme FASM

Message par Anonyme2 »

En fait, il faudrait réécrire un compilateur :mrgreen:
Répondre