reverse 1


hexadecimal


Le système hexadecimal utilise une base 16. C’est à dire que chaque caractère peut prendre 16 valeurs différentes.

base 10 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
base 16 0 1 2 3 4 5 6 7 8 9 a b c d e f

On préfixe les nombres hexadécimals souvent par un Ox afin de les différencier des autres bases.

base 10 8 20 29 32 74 90 100 999 1000
base 16 0x8 0x14 0x1d 0x20 0x4a 0x5a 0x64 0x3e7 0x3e8

base 10 to base 16

…. 163 162 161 160
…. 4096 256 16 1
…. 0 3 14 7

(999)10 = 3 * 256 + 14 * 16 + 7 * 1
(999)10 = 3 * 162 + 14 * 161 + 7 * 160
(999)10 = 3 * 162 + E * 161 + 7 * 160
(999)10 = (0x3E7)16

memory space

Combien de bits pour coder un caractère en base 16 ?
Chaque caractére peut prendre 16 valeurs :

  • 1 bit -> 21 = 2 combinaisons
  • 2 bits -> 22 = 4 combinaisons
  • 3 bits -> 23 = 8 combinaisons
  • 4 bits -> 24 = 16 combinaisons <– Donc 1 caractère occupe 4 bits.

  • 0x8 -> occupe 4 bits
  • 0x4A -> occupe 8 bits -> 1 octet
  • 0x4A88 -> occupe 16 bits -> 2 octets

memory addressing


On utilise souvent la base hexadécimale pour donner des adresses mémoires. Par exemple 0x000008ff est une adresse mémoire sur une machine 32 bits.

Les machines 32 bits ont 32 bits d’adressage (ou 4 octets), c’est à dire que l’on peut localiser 232 blocs de 1 octet dans la RAM. C’est à dire 4 Go maximum.

Les processeurs 64 bits quand à eux peuvent adresser 264 blocs différents. C’est à dire BEAUCOUP !


assembler (asm)


Voici un exemple de code écrit en langage C :

/* main.cpp */
int main (int arc, char* argv[]) {
  int nb1 = 10;
  int nb2 = 100;

  if(nb1 > nb2) {
    printf("%i est plus grand que %i", nb1, nb2);
  } else {
    printf("%i est plus petit que %i", nb1, nb2);
  }

  return 0;
}

Le processeur d’un ordinateur ne peut réaliser que de simples opérations (additions, soustractions, comparaison à 0,…). Il ne peut donc pas directement interpréter notre main.cpp. C’est pour cela que l’on passe par une phase de compilation qui nous permet d’obtenir un exécutable compréhensible par notre processeur.

example

0x000008d6      83f80a         cmp eax, 0xa
0x000008d9      7513           jne 0x8ee
0x000008db      488d3d430100.  lea rdi, qword str.Gagne
0x000008e2      b800000000     mov eax, 0
0x000008e7      e834feffff     call sym.imp.printf
0x000008ec      eb11           jmp 0x8ff
0x000008ee      488d3d440100.  lea rdi, qword str.Perdu
0x000008f5      b800000000     mov eax, 0
0x000008fa      e821feffff     call sym.imp.printf
0x000008ff      b800000000     mov eax, 0
0x00000904      c9             leave
0x00000905      c3             ret

La colonne de gauche représente les adresses mémoires (on reconnait la base hexadécimale 0x...), la seconde est le contenu mémoire (données stockées) jusqu’à la prochaine adresse mémoire. Enfin, la troisième colonne est la traduction de ce contenu en langage assembleur. Par exemple, eb11 est traduit par l’instruction jmp 0x8ff.


addresses evolution


Prenons par exemple les 4 premières lignes

adresse (hexa) contenu (hexa) directive (assembleur)
0x000008d6 83f80a cmp eax, 0xa
0x000008d9 7513 jne 0x8ee
0x000008db 488d3d430100 lea rdi, qword str.Gagne
0x000008e2 b800000000 mov eax, 0

Rappel

  • un caractère hexadécimal occupe 4 bits
  • un case memoire fait 1 octet

Pourquoi passe t-on de l’adresse 0x000008d6 à 0x000008d9 ?

Tout d’abord l’adresse 0x000008d6 pointe sur une suite de données correspondant à la directive 83f80a. Cette directive est une valeur hexadécimale qu’il faut stocker. Etant donné que 83f80a est composée de 6 caractéres hexa, il faut donc 3 octets pour stocker cette directive (6*4 = 24 bits = 3 octets).

De plus si on regarde le delta de 0x000008d9 - 0x000008d6 on retouve la valeur 3. Tout s’explique.

Voiçi un tableau avec le contenu de chaque case mémoire :

adresse (hexa) directive (hexa)
0x000008d6 83
0x000008d7 f8
0x000008d8 0a
0x000008d9 75
0x000008da 13