Instructions en virgule flottante en x86-64

Salut ce forum est dédié à comprendre les entrailles de l'ordinateur au plus bas niveau bienvenue dans le monde du reverse et de l'assembleur

Moderator: Rick

Post Reply
Hydraxx
Site Admin
Posts: 114
Joined: Mon Jan 12, 2026 4:04 pm
Location: France
Contact:

Instructions en virgule flottante en x86-64

Post by Hydraxx »

Instructions en virgule flottante en x86-64

Objectif du chapitre

Ce chapitre présente les principales instructions utilisées en assembleur x86-64 pour manipuler des nombres à virgule flottante.

Dans les programmes modernes, les calculs sur les

Code: Select all

float
et les

Code: Select all

double
passent très souvent par les registres SIMD de la famille XMM/YMM.

En reverse engineering, reconnaître ces instructions permet notamment d'identifier :
  • des calculs sur des coordonnées ;
  • des calculs de distances ;
  • des calculs graphiques ;
  • des transformations de vecteurs ;
  • des conversions entier ↔ flottant ;
  • des comparaisons de valeurs flottantes ;
  • des calculs scientifiques ou physiques.
1. Historique : x87, SSE et AVX

Les premiers processeurs x86 utilisaient un coprocesseur spécialisé appelé

Code: Select all

8087
pour effectuer les calculs en virgule flottante.

Par la suite, les instructions x87 ont été intégrées directement aux processeurs.

Les processeurs modernes utilisent cependant principalement : SSE introduit notamment les registres

Code: Select all

XMM
.

AVX étend ensuite leur taille avec les registres

Code: Select all

YMM
.

Idée importante

Les anciennes instructions x87 existent toujours, mais dans du code C/C++ moderne en x86-64, on rencontre très souvent les registres XMM et les instructions SSE/AVX.

2. Les registres XMM

En x86-64, on dispose généralement des registres :

Code: Select all

xmm0
xmm1
xmm2
...
xmm15
Un registre XMM fait

Code: Select all

128 bits
.

Il peut contenir plusieurs types de données.

Par exemple :

Code: Select all

128 bits = 16 octets
Donc un registre XMM peut contenir :
  • 4 nombres

    Code: Select all

    float
    de 32 bits ;
  • 2 nombres

    Code: Select all

    double
    de 64 bits ;
  • plusieurs entiers de différentes tailles.
Exemple conceptuel :

Code: Select all

xmm0 = [ 1.0 | 2.0 | 3.0 | 4.0 ]
Ici,

Code: Select all

xmm0
contient quatre

Code: Select all

float
.

Avec deux

Code: Select all

double
:

Code: Select all

xmm0 = [ 10.5 | 25.75 ]
Scalaire vs packed

Une instruction scalaire travaille sur une seule valeur du registre.

Une instruction packed travaille sur plusieurs valeurs en parallèle.

Exemple :

Code: Select all

addss xmm0, xmm1
additionne seulement un

Code: Select all

float
.

Alors que :

Code: Select all

addps xmm0, xmm1
peut additionner quatre

Code: Select all

float
simultanément.

3. Les registres YMM et AVX

AVX introduit les registres

Code: Select all

YMM
de 256 bits.

Un registre YMM peut donc contenir :
Exemple :

Code: Select all

ymm0 = [f0 | f1 | f2 | f3 | f4 | f5 | f6 | f7]
Les 128 bits inférieurs d'un registre YMM correspondent au registre XMM associé.

Conceptuellement :

Code: Select all

ymm0
 ├── partie basse : xmm0
 └── partie haute : 128 bits supplémentaires
Les instructions AVX commencent souvent par

Code: Select all

v
.

Exemple :

Code: Select all

vaddps ymm0, ymm1, ymm2
Cela signifie :

Code: Select all

ymm0 = ymm1 + ymm2
4. Déplacer des nombres flottants

Les instructions les plus importantes à reconnaître sont :

Code: Select all

movss
movsd
movaps
movups
movapd
movupd
4.1 movss

Code: Select all

movss
signifie généralement :

Code: Select all

move scalar single precision
Il sert à déplacer un

Code: Select all

float
de 32 bits.

Exemple :

Code: Select all

movss xmm0, [rax]
Interprétation :

Code: Select all

xmm0 = float situé à l'adresse rax
Autre exemple :

Code: Select all

movss [rbx], xmm0
Interprétation :

Code: Select all

stocke le float de xmm0 à l'adresse rbx
4.2 movsd

Code: Select all

movsd
sert ici à manipuler un

Code: Select all

double
de 64 bits.

Exemple :

Code: Select all

movsd xmm0, [rax]
Cela charge un

Code: Select all

double
depuis la mémoire vers

Code: Select all

xmm0
.

4.3 Données packed

Pour plusieurs valeurs en parallèle, on rencontre :

Code: Select all

movaps
movups
movapd
movupd
Le suffixe indique le type :
Les lettres

Code: Select all

a
et

Code: Select all

u
indiquent historiquement :
Exemples :

Code: Select all

movaps xmm0, [rax]
movups xmm1, [rbx]
movapd xmm2, [rcx]
movupd xmm3, [rdx]
Alignement

Une adresse alignée sur 16 octets respecte typiquement :

Code: Select all

adresse % 16 == 0
Dans du code moderne, les versions unaligned sont très fréquentes et les performances peuvent être excellentes.

5. Comprendre les suffixes

Les suffixes SSE sont extrêmement utiles en reverse engineering.

Code: Select all

ss = Scalar Single
sd = Scalar Double
ps = Packed Single
pd = Packed Double
Donc :

Code: Select all

addss
= addition d'un

Code: Select all

float
.

Code: Select all

addsd
= addition d'un

Code: Select all

double
.

Code: Select all

addps
= addition de plusieurs

Code: Select all

float
.

Code: Select all

addpd
= addition de plusieurs

Code: Select all

double
.

Raccourci mental

Quand tu vois :

Code: Select all

ss
pense :

Code: Select all

1 float
Quand tu vois :

Code: Select all

sd
pense :

Code: Select all

1 double
Quand tu vois :

Code: Select all

ps
pense :

Code: Select all

plusieurs float
Quand tu vois :

Code: Select all

pd
pense :

Code: Select all

plusieurs double
6. Addition

Les principales instructions sont :

Code: Select all

addss
addsd
addps
addpd
Exemple scalaire :

Code: Select all

movss xmm0, [a]
addss xmm0, [b]
Équivalent conceptuel :

Code: Select all

xmm0 = a + b
Pour un

Code: Select all

double
:

Code: Select all

movsd xmm0, [a]
addsd xmm0, [b]
Pour quatre

Code: Select all

float
:

Code: Select all

movups xmm0, [a]
addps xmm0, [b]
Si :

Code: Select all

a = [1, 2, 3, 4]
b = [10, 20, 30, 40]
alors :

Code: Select all

xmm0 = [11, 22, 33, 44]
Le calcul est effectué en parallèle.

7. Soustraction

Les instructions correspondantes sont :

Code: Select all

subss
subsd
subps
subpd
Exemple :

Code: Select all

movss xmm0, [a]
subss xmm0, [b]
Équivalent :

Code: Select all

xmm0 = a - b
Attention à l'ordre.

Code: Select all

subss xmm0, xmm1
signifie :

Code: Select all

xmm0 = xmm0 - xmm1
et non l'inverse.

8. Multiplication

Les instructions principales sont :

Code: Select all

mulss
mulsd
mulps
mulpd
Exemple :

Code: Select all

movss xmm0, [a]
mulss xmm0, [b]
Équivalent :

Code: Select all

xmm0 = a * b
Avec plusieurs valeurs :

Code: Select all

mulps xmm0, xmm1
Les quatre

Code: Select all

float
de

Code: Select all

xmm0
sont multipliés par ceux de

Code: Select all

xmm1
, élément par élément.

Exemple :

Code: Select all

xmm0 = [2, 3, 4, 5]
xmm1 = [10, 20, 30, 40]
Résultat :

Code: Select all

xmm0 = [20, 60, 120, 200]
9. Division

Les variantes sont :

Code: Select all

divss
divsd
divps
divpd
Exemple :

Code: Select all

movss xmm0, [a]
divss xmm0, [b]
Équivalent :

Code: Select all

xmm0 = a / b
Comme pour

Code: Select all

sub
, l'ordre est important.

10. Tableau récapitulatif des opérations

Code: Select all

addss   addition scalaire float
addsd   addition scalaire double
addps   addition packed float
addpd   addition packed double

subss   soustraction scalaire float
subsd   soustraction scalaire double
subps   soustraction packed float
subpd   soustraction packed double

mulss   multiplication scalaire float
mulsd   multiplication scalaire double
mulps   multiplication packed float
mulpd   multiplication packed double

divss   division scalaire float
divsd   division scalaire double
divps   division packed float
divpd   division packed double
11. Conversion float ↔ double

Il est fréquent de convertir un type flottant vers un autre.

Par exemple :

Code: Select all

cvtss2sd xmm0, xmm1
Convertit un

Code: Select all

float
en

Code: Select all

double
.

Conceptuellement :

Code: Select all

double d = (double)f;
L'instruction inverse est :

Code: Select all

cvtsd2ss xmm0, xmm1
Conceptuellement :

Code: Select all

float f = (float)d;
12. Conversion entier ↔ flottant

Les programmes font également beaucoup de conversions entre entiers et nombres flottants.

Exemples courants :

Code: Select all

cvtsi2ss
cvtsi2sd
cvtss2si
cvtsd2si
cvttss2si
cvttsd2si
12.1 Entier vers float

Exemple :

Code: Select all

cvtsi2ss xmm0, eax
Si :

Code: Select all

eax = 10
alors :

Code: Select all

xmm0 = 10.0f
12.2 Entier vers double

Code: Select all

cvtsi2sd xmm0, rax
Convertit l'entier contenu dans

Code: Select all

rax
en

Code: Select all

double
.

12.3 Float vers entier

Code: Select all

cvtss2si eax, xmm0
Convertit le

Code: Select all

float
contenu dans

Code: Select all

xmm0
vers un entier.

12.4 Troncature

Les instructions commençant par

Code: Select all

cvtt
effectuent une conversion avec troncature.

Exemple :

Code: Select all

cvttss2si eax, xmm0
Si :

Code: Select all

xmm0 = 12.9
le résultat est typiquement :

Code: Select all

eax = 12
La partie décimale est supprimée.

13. Comparaison des nombres flottants

On ne compare pas toujours les nombres flottants avec un simple

Code: Select all

cmp
.

Les instructions SSE importantes sont notamment :

Code: Select all

comiss
ucomiss
comisd
ucomisd
Exemple :

Code: Select all

ucomiss xmm0, xmm1
Cela compare deux

Code: Select all

float
.

Ensuite, le programme utilise souvent un saut conditionnel.

Exemple :

Code: Select all

ucomiss xmm0, xmm1
ja greater
ou :

Code: Select all

ucomiss xmm0, xmm1
jbe less_or_equal
Flags utilisés

Les comparaisons flottantes peuvent modifier notamment :

Code: Select all

ZF
PF
CF
Les sauts les plus fréquents après une comparaison flottante sont :

Code: Select all

ja
jae
jb
jbe
je
jne
Exemple conceptuel :

Code: Select all

ucomiss xmm0, xmm1
ja bigger
peut correspondre à :

Code: Select all

if (a > b)
    ...
Important en reverse

Quand tu vois :

Code: Select all

ucomiss
comiss
ucomisd
comisd
regarde immédiatement l'instruction de saut juste après.

C'est généralement elle qui révèle la condition C/C++.

14. NaN

IEEE 754 définit des valeurs particulières appelées :

Code: Select all

NaN
pour :

Code: Select all

Not a Number
Un NaN peut apparaître lorsqu'un calcul n'a pas de résultat numérique classique.

Exemples :
  • certaines opérations invalides ;
  • résultats indéfinis ;
  • propagation d'une valeur NaN existante.
Il existe notamment :

Code: Select all

QNaN
SNaN
Les comparaisons avec NaN nécessitent une gestion particulière.

C'est notamment pour cela que certaines comparaisons flottantes utilisent également le flag

Code: Select all

PF
.

15. Minimum et maximum

SSE fournit des instructions permettant d'obtenir directement un minimum ou un maximum.

Exemples :

Code: Select all

minss
maxss
minsd
maxsd
minps
maxps
minpd
maxpd
Exemple :

Code: Select all

maxss xmm0, xmm1
Conceptuellement :

Code: Select all

xmm0 = max(xmm0, xmm1)
Exemple packed :

Code: Select all

xmm0 = [1, 50, 3, 80]
xmm1 = [10, 20, 30, 40]

maxps xmm0, xmm1
Résultat :

Code: Select all

xmm0 = [10, 50, 30, 80]
Chaque paire de valeurs est comparée indépendamment.

16. Arrondis

Certaines instructions SSE permettent d'arrondir des valeurs flottantes.

Exemples :

Code: Select all

roundss
roundsd
roundps
roundpd
Les modes d'arrondi peuvent inclure :
  • arrondi au plus proche ;
  • arrondi vers le haut ;
  • arrondi vers le bas ;
  • troncature vers zéro.
Exemple conceptuel :

Code: Select all

1.5  -> 2
1.45 -> 1
1.99 -> 2
selon le mode utilisé.

17. Racine carrée

SSE possède également des instructions de racine carrée.

Par exemple :

Code: Select all

sqrtss
sqrtsd
sqrtps
sqrtpd
Exemple :

Code: Select all

sqrtss xmm0, xmm1
Conceptuellement :

Code: Select all

xmm0 = sqrt(xmm1)
Cette instruction apparaît souvent dans les calculs de distance.

18. Exemple : distance entre deux points 3D

Pour deux points :

Code: Select all

A = (x1, y1, z1)
B = (x2, y2, z2)
La distance est :

Code: Select all

distance = sqrt(
    (x2 - x1)^2 +
    (y2 - y1)^2 +
    (z2 - z1)^2
)
En assembleur, on peut donc retrouver une séquence de ce type :

Code: Select all

movss
subss
mulss

movss
subss
mulss
addss

movss
subss
mulss
addss

sqrtss
Même sans comprendre chaque adresse mémoire, cette structure est très reconnaissable.

En reverse engineering, une succession :

Code: Select all

sub
mul
add
sqrt
sur des XMM indique souvent un calcul de distance ou de norme.

19. Exemple : produit scalaire

Le produit scalaire de deux vecteurs 3D :

Code: Select all

A = (x1, y1, z1)
B = (x2, y2, z2)
est :

Code: Select all

x1*x2 + y1*y2 + z1*z2
En assembleur SSE, on peut rencontrer plusieurs :

Code: Select all

mulss
addss
Conceptuellement :

Code: Select all

result =
    ax * bx +
    ay * by +
    az * bz;
Le produit scalaire apparaît énormément dans :
  • jeux vidéo ;
  • moteurs 3D ;
  • physique ;
  • calculs géométriques ;
  • IA ;
  • traitement du signal.
20. Exemple : évaluation d'un polynôme

Un polynôme :

Code: Select all

P(x) = a*x² + b*x + c
peut être calculé avec des multiplications et additions.

Une méthode très fréquente est la méthode de Horner :

Code: Select all

P(x) = (a*x + b)*x + c
En assembleur, cela produit souvent une succession :

Code: Select all

mulss
addss
mulss
addss
ou les variantes

Code: Select all

sd
pour les

Code: Select all

double
.

21. AVX : syntaxe à trois opérandes

Une différence importante entre SSE classique et AVX est la syntaxe.

SSE :

Code: Select all

addps xmm0, xmm1
signifie :

Code: Select all

xmm0 = xmm0 + xmm1
La destination est également une source.

Avec AVX :

Code: Select all

vaddps ymm0, ymm1, ymm2
signifie :

Code: Select all

ymm0 = ymm1 + ymm2
La destination peut être différente des deux sources.

Cela simplifie souvent le code.

22. Reconnaître un float dans un débogueur

Un

Code: Select all

float
fait 4 octets.

Un

Code: Select all

double
fait 8 octets.

Dans x64dbg, WinDbg ou un autre débogueur, une suite d'octets peut représenter un entier ou un flottant.

Exemple :

Code: Select all

3F 80 00 00
peut représenter :

Code: Select all

1.0f
La valeur brute n'est donc pas forcément un entier.

Quand tu vois qu'une adresse mémoire est utilisée par :

Code: Select all

movss
addss
mulss
ucomiss
c'est un très gros indice qu'elle contient un

Code: Select all

float
.

De même, si elle est utilisée par :

Code: Select all

movsd
addsd
mulsd
ucomisd
il s'agit probablement d'un

Code: Select all

double
.

23. Reconnaître des structures contenant des floats

Supposons :

Code: Select all

struct Position
{
    float x;
    float y;
    float z;
};
En mémoire :

Code: Select all

offset +0  -> x
offset +4  -> y
offset +8  -> z
Dans le désassemblage, on pourrait voir :

Code: Select all

movss xmm0, [rcx]
movss xmm1, [rcx+4]
movss xmm2, [rcx+8]
Cela peut indiquer une structure contenant trois

Code: Select all

float
consécutifs.

C'est extrêmement utile en reverse engineering.

24. Exemple concret de reverse

Supposons que tu trouves :

Code: Select all

movss xmm0, dword ptr [rcx]
subss xmm0, dword ptr [rdx]
mulss xmm0, xmm0
movss xmm1, dword ptr [rcx+4]
subss xmm1, dword ptr [rdx+4]
mulss xmm1, xmm1
addss xmm0, xmm1
sqrtss xmm0, xmm0
On peut reconstituer :

Code: Select all

float dx = *(float*)(rcx) - *(float*)(rdx);
float dy = *(float*)(rcx + 4) - *(float*)(rdx + 4);

float result = sqrt(dx*dx + dy*dy);
Donc très probablement une distance 2D.

C'est exactement le type de pattern qu'il faut apprendre à reconnaître en reverse.

25. Ce qu'il faut absolument retenir

Pour le reverse engineering, retiens surtout :
26. Résumé ultra-court pour le reverse

Code: Select all

movss  -> déplace 1 float
movsd  -> déplace 1 double

addss  -> float +
subss  -> float -
mulss  -> float *
divss  -> float /

addsd  -> double +
subsd  -> double -
mulsd  -> double *
divsd  -> double /

ucomiss -> compare des float
ucomisd -> compare des double

cvtsi2ss -> int vers float
cvtsi2sd -> int vers double
cvtss2si -> float vers int
cvtsd2si -> double vers int

sqrtss -> racine carrée float
sqrtsd -> racine carrée double
Pour analyser rapidement une fonction, regarde d'abord les suffixes des instructions.

Si tu vois beaucoup de :

Code: Select all

movss
mulss
addss
subss
la fonction travaille presque certainement avec des

Code: Select all

float
.

Si tu vois :

Code: Select all

movsd
mulsd
addsd
subsd
elle travaille probablement avec des

Code: Select all

double
.

Et si tu vois :

Code: Select all

addps
mulps
subps
le programme effectue probablement du calcul SIMD sur plusieurs

Code: Select all

float
en parallèle.

Who is online

Users browsing this forum: No registered users and 1 guest