Tutoriel sur la compréhension de la machine virtuelle Java

Image non disponible


précédentsommairesuivant

VII. Manipulation de la pile

Dans cette partie, nous allons étudier les instructions permettant de manipuler la pile. Comme nous le verrons, elles sont légèrement plus difficiles à appréhender que les précédentes.

Le code est disponible sur Github (tag et branche).

VII-A. Catégories des types

La correspondance entre les types utilisés par la JVM et les types Java est présentée dans le tableau ci-dessous.

Certaines instructions de la JVM telles que pop et swap opèrent sur la pile sans prendre en compte le type des valeurs manipulées, contrairement à toutes les instructions que nous avons vues jusqu'à présent, telles que iadd, dload ou lshr. Cependant, ces instructions ne peuvent être utilisées que sur des valeurs d'une certaine catégorie.

Il existe deux catégories permettant de distinguer les types nécessitant une entrée dans les variables locales ou la pile, de ceux en nécessitant deux.

Bien qu'une référence puisse être représentée sur 32 ou 64 bits en fonction de la JVM, elle sera toujours de catégorie 1, et par conséquent, prendra toujours une entrée dans les variables locales ou la pile.

Type Java

Type JVM

Catégorie

byte

int

1

short

int

1

char

int

1

int

int

1

float

float

1

référence

référence

1

adresse de retour *

adresse de retour *

1

long

long

2

double

double

2

* utilisée par l'instruction ret.

VII-B. Représentation de la pile

La JVM étant basée sur le modèle de la pile, il est essentiel de connaître quel est l'impact des instructions. Pour représenter l'état avant/après l'exécution d'une instruction, nous allons reprendre le format utilisé par la JVMS et qui est le suivant :

…, valeur1, valeur2 → …, résultat, où les valeurs les plus à droite sont au sommet de la pile. valeur1 et valeur2 étant les deux valeurs utilisées pour le calcul et résultat le résultat.

Il est important de noter que dans cette représentation, le long et le double sont considérés comme une seule valeur. Par conséquent, lorsque nécessaire nous présenterons les différents cas d'utilisation d'une instruction en utilisant plusieurs formes.

VII-C. Opérations sur la pile

Il est parfois utile de manipuler les données au sommet de la pile sans avoir à faire intervenir les variables locales. C'est ce que permettent les instructions suivantes :

Hex

Mnémonique

Description

0x57

pop

Dépile le premier élément.

0x58

pop2

Dépile les deux premiers éléments.

0x59

dup

Duplique le premier élément et l'empile.

0x5a

dup_x1

Duplique le premier élément et l'ajoute sous le deuxième.

0x5b

dup_x2

Duplique le premier élément et l'ajoute sous le troisième.

0x5c

dup2

Duplique les deux premiers éléments et les empile (en gardant l'ordre).

0x5d

dup2_x1

Duplique les deux premiers éléments et les ajoute sous le troisième (en gardant l'ordre)

0x5e

dup2_x2

Duplique les deux premiers éléments et les ajoute sous le quatrième élément (en gardant l'ordre).

0x5f

swap

Échange les deux premiers éléments.

Une chose importante à ne pas oublier est que les long et double prennent deux cases dans la pile et doivent être considérés comme deux éléments.

VII-C-1. SWAP

L'instruction swap permet d'inverser les deux premiers éléments au sommet de la pile quel que soit leur type à condition qu'il soit de catégorie 1.

Note : elle ne peut pas être utilisée avec des long et des double.

État de la pile avant → après exécution : …, valeur1, valeur2 → …, valeur2, valeur1, où valeur1 et valeur2 sont de catégorie 1.

Code PJB :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
.class org/isk/jvmhardcore/bytecode/partsix/Swap
  .method swap()I
    iconst_1
    iconst_2
    swap
    ireturn
  .methodend
.classend

Source

Test unitaire :

 
Sélectionnez
1.
2.
3.
4.
5.
public void swap() {
  final int result = Swap.swap();
 
  Assert.assertEquals(1, result);
}

Source

VII-C-2. POP

L'instruction pop dépile l'élément au sommet de la pile.

Note : elle ne peut pas être utilisée avec des long et des double.

État de la pile avant → après exécution : …, valeur1 → …, où valeur1 est de catégorie 1.

Code PJB :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
.class org/isk/jvmhardcore/bytecode/partsix/Pop
  .method pop()D
    dconst_0
    iconst_1
    pop
    dreturn
  .methodend
.classend

Source

Test unitaire :

 
Sélectionnez
1.
2.
3.
4.
5.
public void pop() {
  final double result = Pop.pop();
 
  Assert.assertEquals(0.0, result, 0.0001);
}

Source

VII-C-3. POP2

L'instruction pop2 dépile les deux premiers éléments au sommet de la pile.

VII-C-3-a. POP2 - Forme 1

État de la pile avant → après exécution : …, valeur1, valeur2 → …, où valeur1 et valeur2 sont de catégorie 1.

Code PJB :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
.class org/isk/jvmhardcore/bytecode/partsix/Pop2_Form1
  .method pop2()I
    iconst_0
    iconst_1
    iconst_1
    pop2
    ireturn
  .methodend
.classend

Source

Test unitaire :

 
Sélectionnez
1.
2.
3.
4.
5.
public void pop2_form1() {
  final int result = Pop2_Form1.pop2();
 
  Assert.assertEquals(0, result);
}

Source

VII-C-3-b. POP2 - Forme 2

État de la pile avant → après exécution : …, valeur1 → …, où valeur1 est de catégorie 2.

Code PJB :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
.class org/isk/jvmhardcore/bytecode/partsix/Pop2_Form2
  .method pop2()I
    iconst_2
    dconst_1
    pop2
    ireturn
  .methodend
.classend

Source

Test unitaire :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
Test
public void pop2_form2() {
  final int result = Pop2_Form2.pop2();
 
  Assert.assertEquals(2, result);
}

Source

VII-C-4. DUP

L'instruction dup duplique le premier élément et l'empile.

Note : elle ne peut pas être utilisée avec des long et des double.

État de la pile avant → après exécution : …, valeur1 → …, valeur1, valeur1, où valeur1 est de catégorie 1.

Code PJB :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
.class org/isk/jvmhardcore/bytecode/partsix/Dup
  .method dup()I
    iconst_2
    iconst_1
    dup
    pop2
    ireturn
  .methodend
.classend

Source

Test unitaire :

 
Sélectionnez
1.
2.
3.
4.
5.
public void dup() {
  final int result = Dup.dup();
 
  Assert.assertEquals(2, result);
}

Source

VII-C-5. DUP_X1

L'instruction dup_x1 duplique le premier élément et l'ajoute sous le deuxième.

Note : elle ne peut pas être utilisée avec des long et des double.

État de la pile avant → après exécution : …, valeur1, valeur2 → …, valeur1, valeur2, valeur1, où valeur1 et valeur2 sont de catégorie 1.

Code PJB :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
.class org/isk/jvmhardcore/bytecode/partsix/Dup_X1
  .method dup_x1()I
    iconst_2
    iconst_1
    dup_x1
    pop2
    ireturn
  .methodend
.classend

Source

Test unitaire :

 
Sélectionnez
1.
2.
3.
4.
5.
public void dup_x1() {
  final int result = Dup_X1.dup_x1();
 
  Assert.assertEquals(1, result);
}

Source

VII-C-6. DUP_X2

L'instruction dup_x2 duplique le premier élément et l'ajoute sous le troisième.

Note : elle ne peut pas être utilisée avec des long et des double.

VII-C-6-a. DUP_X2 - Forme 1

État de la pile avant → après exécution : …, valeur1, valeur2, valeur3 → …, valeur3, valeur1, valeur2, valeur3, où valeur1, valeur2 et valeur3 sont de catégorie 1.

Code PJB :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
.class org/isk/jvmhardcore/bytecode/partsix/Dup_X2_Form1
  .method dup_x2()I
    iconst_3
    iconst_2
    iconst_1
    dup_x2
    pop2
    pop
    ireturn
  .methodend
.classend

Source

Test unitaire :

 
Sélectionnez
1.
2.
3.
4.
5.
public void dup_x2_form1() {
  final int result = Dup_X2_Form1.dup_x2();
 
  Assert.assertEquals(1, result);
}

Source

VII-C-6-b. DUP_X2 - Forme 2

État de la pile avant → après exécution : …, valeur1, valeur2 → …, valeur2, valeur1, valeur2, où valeur1 est de catégorie 2 et valeur2 est de catégorie 1.

Code PJB :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
.class org/isk/jvmhardcore/bytecode/partsix/Dup_X2_Form2
  .method dup_x2()I
    dconst_1
    iconst_2
    dup_x2
    pop
    pop2
    ireturn
  .methodend
.classend

Source

Test unitaire :

 
Sélectionnez
1.
2.
3.
4.
5.
public void dup_x2_form2() {
  final int result = Dup_X2_Form2.dup_x2();
 
  Assert.assertEquals(2, result);
}

Source

VII-C-7. DUP2

L'instruction dup2 duplique les deux premiers éléments et les empile (en gardant l'ordre).

VII-C-7-a. DUP2 - Forme 1

État de la pile avant → après exécution : …, valeur1, valeur2 → …, valeur1, valeur2, valeur1, valeur2, où valeur1, valeur2 et valeur3 sont de catégorie 1.

Code PJB :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
.class org/isk/jvmhardcore/bytecode/partsix/Dup2_Form1
  .method dup2()I
    iconst_2  # [empty] -> 2
    iconst_1  # 2 -> 2, 1
    dup2      # 2, 1 -> 2, 1, 2, 1
    @--------- Start packing
    bipush 8  # 2, 1, 2, 1 -> 2, 1, 2, 1, 8
    ishl      # 2, 1, 2, 1, 8 -> 2, 1, 2, 1 << 8
    ior       # 2, 1, 2, 1 << 8 -> 2, 1, 2 | 1 << 8
    bipush 8  # 2, 1, 2 | 1 << 8 -> 2, 1, 2 | 1 << 8, 8
    ishl      # 2, 1, 2 | 1 << 8, 8 -> 2, 1, (2 | 1 << 8) << 8
    ior       # 2, 1, (2 | 1 << 8) << 8 -> 2, 1 | (2 | 1 << 8) << 8
    bipush 8  # etc.
    ishl
    ior
    ireturn   # 0000 0001 0000 0010 0000 0001 0000 0010
  .methodend
.classend

Source

Nous souhaitons connaître l'état de la pile - les valeurs présentes et leur ordre - après l'exécution des trois premières instructions (celles se trouvant avant le commentaire Start packing) pour vérifier le fonctionnement de l'instruction dup2. Or, avec les instructions que nous avons étudiées jusqu'à présent, la seule solution est d'empaqueter les quatre valeurs présentes dans la pile (2, 1, 2, 1). En prenant le type int, l'empaquetage consistera à découper la valeur retournée en quatre blocs de 8 bits (). Le bloc le plus à gauche contiendra la valeur au sommet de la pile, le suivant la valeur juste au-dessous, et ainsi de suite jusqu'au bas la pile, pour obtenir la valeur 0x1020102. Pour ce faire, nous utilisons les instructions d'opération bit à bit que nous avons vues dans la partie précédente.

Test unitaire :

 
Sélectionnez
1.
2.
3.
4.
5.
public void dup2_form1() {
  final int result = Dup2_Form1.dup2();
 
  Assert.assertEquals(0x01020102, result);
}

Source

VII-C-7-b. DUP2 - Forme 2

État de la pile avant → après exécution : …, valeur1 → …, valeur1, valeur1, où valeur1 est de catégorie 2.

Code PJB :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
.class org/isk/jvmhardcore/bytecode/partsix/Dup2_Form2
  .method dup2()D
    dconst_1  # [empty] -> 1.0
    dup2      # 1.0 -> 1.0, 1.0
    dadd      # 1.0, 1.0 -> 2.0
    dreturn
  .methodend
.classend

Source

Test unitaire :

 
Sélectionnez
1.
2.
3.
4.
5.
public void dup2_form2() {
  final double result = Dup2_Form2.dup2();
 
  Assert.assertEquals(2.0, result, 0.0001);
}

Source

VII-C-8. DUP2_X1

L'instruction dup2_x1 duplique les deux premiers éléments et les ajoute sous le troisième (en gardant l'ordre).

VII-C-8-a. DUP2_X1 - Forme 1

État de la pile avant → après exécution : …, valeur1, valeur2, valeur3 → …, valeur2, valeur3, valeur1, valeur2, valeur3, où valeur1, valeur2 et valeur3 sont de catégorie 1.

Code PJB :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
.class org/isk/jvmhardcore/bytecode/partsix/Dup2_X1_Form1
  .method dup2_x1()I
    iconst_3  # [empty] -> 3
    iconst_2  # 3 -> 3, 2
    iconst_1  # 3, 2 -> 3, 2, 1
    dup2_x1   # 3, 2, 1 -> 2, 1, 3, 2, 1
    @--------- Start packing
    bipush 4
    ishl
    ior
    bipush 4
    ishl
    ior
    bipush 4
    ishl
    ior
    bipush 4
    ishl
    ior
    ireturn  # 0001 0010 0011 0001 0010
  .methodend
.classend

Source

Test unitaire :

 
Sélectionnez
1.
2.
3.
4.
5.
public void dup2_x1_form1() {
  final int result = Dup2_X1_Form1.dup2_x1();
 
  Assert.assertEquals(0x12312, result);
}

Source

VII-C-8-b. DUP2_X1 - Forme 2

État de la pile avant → après exécution : …, valeur1, valeur2 → …, valeur2, valeur1, valeur2, où valeur1 est de catégorie 1 et valeur2 est de catégorie 2.

Code PJB :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
.class org/isk/jvmhardcore/bytecode/partsix/Dup2_X1_Form2
  .method dup2_x1()D
    iconst_3  # [empty] -> 3
    dconst_1  # 3 -> 3, 1.0
    dup2_x1   # 3, 1.0 -> 1.0, 3, 1.0
    pop2      # 1.0, 3, 1.0 -> 1.0, 3
    pop       # 1.0, 3 -> 1.0
    dreturn
  .methodend
.classend

Source

Test unitaire :

 
Sélectionnez
1.
2.
3.
4.
5.
public void dup2_x1_form2() {
  final double result = Dup2_X1_Form2.dup2_x1();
 
  Assert.assertEquals(1.0, result, 0.0001);
}

Source

VII-C-9. DUP2_X2

L'instruction dup2_x2 duplique les deux premiers éléments et les ajoute sous le quatrième élément (en gardant l'ordre).

VII-C-9-a. DUP2_X2 - Forme 1

État de la pile avant → après exécution : …, valeur1, valeur2, valeur3, valeur4 → …, valeur3, valeur4, valeur1, valeur2, valeur3, valeur4, où toutes les valeurs sont de catégorie 1.

Code PJB :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
.class org/isk/jvmhardcore/bytecode/partsix/Dup2_X2_Form1
  .method dup2_x2()I
    iconst_4  # [empty] -> 4
    iconst_3  # 4 -> 4, 3
    iconst_2  # 4, 3 -> 4, 3, 2
    iconst_1  # 4, 3, 2 -> 4, 3, 2, 1
    dup2_x2   # 3, 2, 1 -> 2, 1, 3, 2, 1
    @--------- Start packing
    bipush 4
    ishl
    ior
    bipush 4
    ishl
    ior
    bipush 4
    ishl
    ior
    bipush 4
    ishl
    ior
    bipush 4
    ishl
    ior
    ireturn   # 0001 0010 0011 0100 0001 0010
  .methodend
.classend

Source

Test unitaire :

 
Sélectionnez
1.
2.
3.
4.
5.
public void dup2_x2_form1() {
  final int result = Dup2_X2_Form1.dup2_x2();
 
  Assert.assertEquals(0x123412, result);
}

Source

VII-C-9-b. DUP2_X2 - Forme 2

État de la pile avant → après exécution : …, valeur1, valeur2, valeur3 → …, valeur3, valeur1, valeur2, valeur3, où valeur1 et valeur2 sont de catégorie 1 et valeur3 est de catégorie 2.

Code PJB :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
.class org/isk/jvmhardcore/bytecode/partsix/Dup2_X2_Form2
  .method dup2_x2()D
    iconst_2  # [empty] -> 2
    iconst_0  # 2 -> 2, 0
    dconst_1  # 2, 0 -> 2, 0, 1.0
    dup2_x2   # 2, 0, 1.0 -> 1.0, 2, 0, 1.0
    pop2      # 1.0, 2, 0, 1.0 -> 1.0, 2, 0
    pop2      # 1.0, 2, 0 -> 1.0
    dreturn
  .methodend
.classend

Source

Test unitaire :

 
Sélectionnez
1.
2.
3.
4.
5.
public void dup2_x2_form2() {
  final double result = Dup2_X2_Form2.dup2_x2();
 
  Assert.assertEquals(1.0, result, 0.0001);
}

Source

VII-C-9-c. DUP2_X2 - Forme 3

État de la pile avant → après exécution : …, valeur1, valeur2, valeur3 → …, valeur2, valeur3, valeur1, valeur2, valeur3, où valeur1 est de catégorie 2 et, valeur2 et valeur3 sont de catégorie 1.

Code PJB :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
.class org/isk/jvmhardcore/bytecode/partsix/Dup2_X2_Form3
  .method dup2_x2()I
    dconst_1  # [empty] -> 1.0
    iconst_1  # 1.0 -> 1.0, 1
    iconst_2  # 1.0, 1 -> 1.0, 1, 2
    dup2_x2   # 1.0, 1, 2 -> 1, 2, 1.0, 1, 2
    pop2      # 1, 2, 1.0, 1, 2 -> 1, 2, 1.0
    pop2      # 1, 2, 1.0 -> 1, 2
    @--------- Start packing
    bipush 4
    ishl
    ior
    ireturn   # 0010 0001
  .methodend
.classend

Source

Test unitaire :

 
Sélectionnez
1.
2.
3.
4.
5.
public void dup2_x2_form3() {
  final int result = Dup2_X2_Form3.dup2_x2();
 
  Assert.assertEquals(0x21, result);
}

Source

VII-C-9-d. DUP2_X2 - Forme 4

État de la pile avant → après exécution : …, valeur1, valeur2 → …, valeur2, valeur1, valeur2, où valeur1 et valeur2 sont de catégorie 2.

Code PJB :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
.class org/isk/jvmhardcore/bytecode/partsix/Dup2_X2_Form4
  .method dup2_x2()D
    dconst_0
    dconst_1
    dup2_x2
    pop2
    pop2
    dreturn
  .methodend
.classend

Source

Test unitaire :

 
Sélectionnez
1.
2.
3.
4.
5.
public void dup2_x2_form4() {
  final double result = Dup2_X2_Form4.dup2_x2();
 
  Assert.assertEquals(1.0, result, 0.0001);
}

Source

VII-D. What's next ?

Au cours des trois parties précédentes et de celle-ci, nous avons vu 136 instructions. Néanmoins, l'objectif étant aussi de comprendre le contenu d'un fichier .class, nous allons mettre de côté les 69 restantes pendant quelques parties pour nous concentrer sur l'étude de différents éléments dont la connaissance est nécessaire à la construction d'un assembleur de bytecode.

La prochaine partie sera dédiée à la première partie de la création d'un analyseur syntaxique.


précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2016 SOAT. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.