Hello World en 15 lignes et plus (pt4)

Par aliu, 24 mars, 2024

Salutations à tous,

Bienvenue à la dernière partie de mon introduction à comment écrire "Hello World" en 15 lignes. Aujourd'hui, j'essayerais de finaliser pour vous les connaissances de base nécessaire au langage X86.

Nous aborderons les variables, directives, les instructions arithmétiques et les instructions logiques.

Bons, commençons!

Variables

Allouer de l'espace pour les données initialisées

Voyons premièrement la syntaxe utilisée pour allouer du stockage à des données initialisées:

[variable-name]    define-directive    initial-value   [initial-value]...

Dans ce cas ici [variable-name] est l'identifiant pour chaque espace de stockage.
L'assembleur va associer une valeur décalée (offset value) pour chaque nom de variable défini dans le segment de donnée.

Voici les cinq formes basiques de directive définie (define directive):


DB     Define Byte         allocates 1 byte
DW     Define Word         allocates 2 bytes
DD     Define Doubleword     allocates 4 bytes
DQ     Define Quadword     allocates 8 bytes
DT     Define Ten Bytes     allocates 10 bytes

Et voici des exemples qui utilisent ces formes:

choice        DB    'y'
number        DW    12345
neg_number    DW    -12345
big_number    DQ    123456789
real_number1    DD    1.234
real_number2    DQ    123.456

Et un exemple de code:

section .text
  global _start          ;must be declared for linker (gcc)
    
_start:                   ;tell linker entry point
  mov    edx,1          ;message length
  mov    ecx,choice        ;message to write
  mov    ebx,1          ;file descriptor (stdout)
  mov    eax,4          ;system call number (sys_write)
  int    0x80          ;call kernel

  mov    eax,1          ;system call number (sys_exit)
  int    0x80          ;call kernel

section .data
choice DB 'y'

Quand le code en haut est compilé et exécuté, voici le résultat qui sort:

y

Allouer de l'espace pour les données non initialisées

Les directives de réserve sont utilisées pour réserver de l'espace pour les données non initialisées. Ceux-ci prennent une seul opérande qui spécifie le nombre d'unité d'espace à être réservé.

Voici les cinq formes basiques de directive de réserve:

RESB     Reserve a Byte
RESW     Reserve a Word
RESD     Reserve a Doubleword
RESQ     Reserve a Quadword
REST     Reserve a Ten Bytes

Voici un exemple qui utilise une de ces formes:

section .text
    global _start
_start:
    mov eax, 0x4            ;4 is the unique system call number of the write system call
    mov ebx, 1                ;1 is the file descriptor for the stdout stream
    mov ecx, fir_message    ;message is the string that needs to be printed on the console
    mov edx, fir_length        ;length of message
    int 0x80                ;interrupt to run the kernel
        
    mov eax, 0x4            ;same steps as above to print sec_message        
    mov ebx, 1
    mov ecx, sec_message
    mov edx, sec_length
    int 0x80
    
    mov edx, "A"            ;sets value of edx to "A"
    mov [uninit], edx        ;copies value in edx and moves it into the uninitialised variable
    
    mov eax, 0x4            ;prints current value of uninit
    mov ecx, uninit
    mov edx, 1
    int 0x80
        

    mov eax, 0x1            ;1 is the unique system call number of the exit system call
    mov ebx, 0                ;argument to exit system call
    int 0x80                ;interrupt to run the kernel and exit gracefully

section .data
    fir_message db "Welcome to Edpresso!" , 0xA    
    fir_length equ $-fir_message                    
    sec_message db "The variable in the bss section is now initialised to: "
    sec_length equ $-sec_message
section .bss
    uninit resb 1            ;declares uninitialised variable

Et voilà ce qui en ressort:

Welcome to Edpresso!
The variable in the bss section is now initialised to: A

Définitions multiples et et les initialisations multiples

Il est possible d'avoir des multiples déclarations de définitions de données dans un programme, par exemple:

choice      DB     'Y'          ;ASCII of y = 79H
number1      DW     12345      ;12345D = 3039H
number2    DD  12345679  ;123456789D = 75BCD15H

Et il est aussi possible d'initialiser plusieurs fois une même valeur, comme cet exemple le fait, avec la directive TIMES:

marks  TIMES  9  DW  0 ; un array nommé marks de taille 9, qui peut être initialisé à zéro

La directive TIMES est souvent utilisée pour créer des tables et des arrays.


Voici un exemple:

section    .text
  global _start        ;must be declared for linker (ld)
    
_start:                 ;tell linker entry point
  mov    edx,9        ;message length
  mov    ecx, stars    ;message to write
  mov    ebx,1        ;file descriptor (stdout)
  mov    eax,4        ;system call number (sys_write)
  int    0x80        ;call kernel

  mov    eax,1        ;system call number (sys_exit)
  int    0x80        ;call kernel

section    .data
stars   times 9 db '*'

Qui affiche finalement:

*********

 

Directives

Bougeons maintenant aux directives.

Avec NASM, plusieurs directives peuvent être utilisées pour définir des constantes. Nous avons déjà utiisé la directive EQU dans les tutoriels précédents, alors pour ce tutoriel, j'irais plus en détail sur celui-ci et deux autres appelés %assign et %define.

EQU

La directive EQU est utilisée généralement pour définir des constantes. Voici la syntaxe de son utilisation:

NOM_CONSTANTE EQU expression -- TOTAL_STUDENTS equ 50

Et cette constante peut ensuite être utilisée dans d'autres lignes de code comme:

mov  ecx,  TOTAL_STUDENTS 
cmp  eax,  TOTAL_STUDENTS

L'opérande d'une déclaration EQU peut devenir une expression comme par exemple:

LENGTH equ 20
WIDTH  equ 10
AREA   equ length * width

Ce segment de code définit l'aire comme 200

Voici un exemple qui utilise la directive EQU:

SYS_EXIT  equ 1
SYS_WRITE equ 4
STDIN     equ 0
STDOUT    equ 1
section     .text
  global _start    ;must be declared for using gcc
    
_start:             ;tell linker entry point
  mov eax, SYS_WRITE         
  mov ebx, STDOUT         
  mov ecx, msg1         
  mov edx, len1 
  int 0x80                
    
  mov eax, SYS_WRITE         
  mov ebx, STDOUT         
  mov ecx, msg2         
  mov edx, len2 
  int 0x80 
    
  mov eax, SYS_WRITE         
  mov ebx, STDOUT         
  mov ecx, msg3         
  mov edx, len3 
  int 0x80
  
  mov eax,SYS_EXIT    ;system call number (sys_exit)
  int 0x80            ;call kernel

section     .data
msg1 db    'Hello, programmers!',0xA,0xD     
len1 equ $ - msg1            

msg2 db 'Welcome to the world of,', 0xA,0xD 
len2 equ $ - msg2

msg3 db 'Linux assembly programming! '
len3 equ $- msg3

Et ça affiche cela:

Hello, programmers!
Welcome to the world of,
Linux assembly programming!

%assign

Cette directive est utilisée pour définir des constantes numériques comme EQU, mais elle permet aussi la redéfinition.

Par exemple:

%assign TOTAL 10

Cette ligne peut plus tard être redéfinie comme:

%assign  TOTAL  20

Cette directive est sensible à la case.

%define

La directive %define permet la définition de constantes numériques et string. Elle est similaire à #define en C.

Une exemple d'utilisation serait:
    
%define PTR [EBP+4] ;remplace PTR avec [EBP+4]

Cette directive permet aussi la redéfinition et est sensible à la case.

Instructions arithmétiques

Quand je parle d'instructions, ce sera, dans ce contexte, les instructions INC, DEC, ADD et SUB

INC

Premièrement, l'instruction INC. Celui-ci est utilisé pour l'incrémentation d'un opérande d’une unité. Ça marche sur une seule opérande qui peut soit être dans la mémoire ou dans le registre.

La syntaxe ressemble à:

INC destination

Et un exemple serait:

INC EBX         ; Increments 32-bit register
INC DL       ; Increments 8-bit register
INC [count]  ; Increments the count variable

DEC

Au contraire de INC, DEC décrémente un opérande d’une unité.

La syntaxe ressemble à:

DEC destination

Et voici un exemple:

segment .data
  count dw  0
  value db  15
    
segment .text
  inc [count]
  dec [value]
    
  mov ebx, count
  inc word [ebx]
    
  mov esi, value
  dec byte [esi]

ADD et SUB

Ces deux instructions sont utilisées pour faire des additions et soustractions simples de données binaires.

La syntaxe ressemble à:

ADD/SUB    destination, source

Et ces instructions peuvent prendre place entre:

  • Registre à registre
  • De la mémoire à un registre
  • D'un registre à la mémoire
  • D'un registre à des données constantes
  • De la mémoire à des données constantes

Voici un exemple qui mettra en stockage deux nombres, respectivement dans les registres EAX et EBX, les additionnera, et mettra le résultat en stockage dans un emplacement mémoire appelé 'res'. Finalement, le résultat sera affiché.

SYS_EXIT  equ 1
SYS_READ  equ 3
SYS_WRITE equ 4
STDIN     equ 0
STDOUT    equ 1

segment .data

  msg1 db "Enter a digit ", 0xA,0xD 
  len1 equ $- msg1

  msg2 db "Please enter a second digit", 0xA,0xD 
  len2 equ $- msg2

  msg3 db "The sum is: "
  len3 equ $- msg3

segment .bss

  num1 resb 2 
  num2 resb 2 
  res resb 1    

section    .text
  global _start    ;must be declared for using gcc
    
_start:             ;tell linker entry point
  mov eax, SYS_WRITE         
  mov ebx, STDOUT         
  mov ecx, msg1         
  mov edx, len1 
  int 0x80                

  mov eax, SYS_READ 
  mov ebx, STDIN  
  mov ecx, num1 
  mov edx, 2
  int 0x80            

  mov eax, SYS_WRITE        
  mov ebx, STDOUT         
  mov ecx, msg2          
  mov edx, len2         
  int 0x80

  mov eax, SYS_READ  
  mov ebx, STDIN  
  mov ecx, num2 
  mov edx, 2
  int 0x80        

  mov eax, SYS_WRITE         
  mov ebx, STDOUT         
  mov ecx, msg3          
  mov edx, len3         
  int 0x80

  ; moving the first number to eax register and second number to ebx
  ; and subtracting ascii '0' to convert it into a decimal number
    
  mov eax, [num1]
  sub eax, '0'
    
  mov ebx, [num2]
  sub ebx, '0'

  ; add eax and ebx
  add eax, ebx
  ; add '0' to to convert the sum from decimal to ASCII
  add eax, '0'

  ; storing the sum in memory location res
  mov [res], eax

  ; print the sum 
  mov eax, SYS_WRITE        
  mov ebx, STDOUT
  mov ecx, res         
  mov edx, 1        
  int 0x80

exit:    
  
  mov eax, SYS_EXIT   
  xor ebx, ebx 
  int 0x80

Un exemple de résultat qui sort de ce code est:

Enter a digit:
3
Please enter a second digit:
4
The sum is:
7

Voici le même exemple, mais avec les données codées en dur:

section    .text
  global _start    ;must be declared for using gcc
    
_start:             ;tell linker entry point
  mov    eax,'3'
  sub     eax, '0'
    
  mov     ebx, '4'
  sub     ebx, '0'
  add     eax, ebx
  add    eax, '0'
    
  mov     [sum], eax
  mov    ecx,msg    
  mov    edx, len
  mov    ebx,1    ;file descriptor (stdout)
  mov    eax,4    ;system call number (sys_write)
  int    0x80    ;call kernel
    
  mov    ecx,sum
  mov    edx, 1
  mov    ebx,1    ;file descriptor (stdout)
  mov    eax,4    ;system call number (sys_write)
  int    0x80    ;call kernel
    
  mov    eax,1    ;system call number (sys_exit)
  int    0x80    ;call kernel
    
section .data
  msg db "The sum is:", 0xA,0xD 
  len equ $ - msg   
  segment .bss
  sum resb 1

Et le résultat est:

The sum is:
7

Instructions logiques

Les instructions logiques finalement, qui sont fournis par le processeur, sont:

Sr.No.         Instruction         Format

1         AND             AND operand1, operand2
2         OR             OR operand1, operand2
3         XOR             XOR operand1, operand2
4         TEST             TEST operand1, operand2
5         NOT             NOT operand1

AND

AND est utilisé pour supporter des expressions logiques en faisant des opérations AND au niveau bit.

Cette opération retourne 1 si les bits correspondants des deux opérandes sont aussi 1. Sinon, elle renvoie 0.

Exemple:

            Operand1:     0101
            Operand2:     0011
----------------------------
After AND -> Operand1:    0001

Et voici un exemple de code qui vérifie si un chiffre donné est pair ou impair:

(Structure)

AND    AL, 01H     ; ANDing with 0000 0001
JZ    EVEN_NUMBER

(Code)

section .text
  global _start            ;must be declared for using gcc
    
_start:                     ;tell linker entry point
  mov   ax,   8h           ;getting 8 in the ax 
  and   ax, 1              ;and ax with 1
  jz    evnn
  mov   eax, 4             ;system call number (sys_write)
  mov   ebx, 1             ;file descriptor (stdout)
  mov   ecx, odd_msg       ;message to write
  mov   edx, len2          ;length of message
  int   0x80               ;call kernel
  jmp   outprog

evnn:   
 
  mov   ah,  09h
  mov   eax, 4             ;system call number (sys_write)
  mov   ebx, 1             ;file descriptor (stdout)
  mov   ecx, even_msg      ;message to write
  mov   edx, len1          ;length of message
  int   0x80               ;call kernel

outprog:

  mov   eax,1              ;system call number (sys_exit)
  int   0x80               ;call kernel

section   .data
even_msg  db  'Even Number!' ;message showing even number
len1  equ  $ - even_msg 
  
odd_msg db  'Odd Number!'    ;message showing odd number
len2  equ  $ - odd_msg

Et voici ce que ça donne:

Even Number!

Si on change la valeur dans le registre ax avec un nombre impair, alors on reçoit cela:

mov  ax, 9h                  ; getting 9 in the ax
...
Odd Number!

(Pour le registre en entier, vous pouvez faire AND avec OOH)

OR

L'opérateur au niveau bit OR retourne 1 si les bits correspondants sont soit 1-0, ou 1-1. Ça retourne 0 si les deux bits sont 0 (0-0).

Exemple:

            Operand1:     0101
            Operand2:     0011
----------------------------
After OR -> Operand1:    0111

Un autre exemple en code, qui mettra en stockage les valeurs 5 et 3 dans les registres AL et BL (OR AL, BL).

section .text
  global _start            ;must be declared for using gcc
    
_start:                     ;tell linker entry point
  mov    al, 5             ;getting 5 in the al
  mov    bl, 3             ;getting 3 in the bl
  or     al, bl            ;or al and bl registers, result should be 7
  add    al, byte '0'      ;converting decimal to ascii
    
  mov    [result],  al
  mov    eax, 4
  mov    ebx, 1
  mov    ecx, result
  mov    edx, 1 
  int    0x80
   
outprog:
  mov    eax,1             ;system call number (sys_exit)
  int    0x80              ;call kernel
    
section    .bss
result resb 1

Quand le code sera compilé, alors le résultat qui en sort sera:

7

XOR

Cette instruction retourne 1 seulement si les bits des opérandes sont différentes. Si elles sont le même (0-0, 1-1), alors ça retourne 0.

Exemple:

            Operand1:     0101
            Operand2:     0011
----------------------------
After XOR -> Operand1:    0110

Voici la structure:

XOR     EAX, EAX

TEST

Cette instruction marche de la même manière que AND, mais elle ne change pas le premier opérande. Donc si on veut vérifier si un chiffre dans un registre est pair ou impair, on peut l'utiliser sans changer le nombre original.

Exemple:

TEST    AL, 01H
JZ      EVEN_NUMBER

NOT

L'instruction NOT permet d'effectuer l'opération NOT, qui inverse les bits dans un opérande. L'opérande peut être soit dans un registre ou dans la mémoire.

Exemple:

Operand1:    0101 0011
After NOT -> Operand1:    1010 1100

Donc je pense avoir fait le vague tour de comment X86 marche, mais si vous voulez en apprendre plus en profondeur, je vous suggère ces liens:

http://www.cs.cmu.edu/afs/cs/academic/class/15213-f02/www/R03/section_b/x86notes.pdf

http://www.nlpir.org/wordpress/wp-content/uploads/2019/03/Assembly.Language.For_.x86.Processors.Kip_.R..Irvine..6ed.Prentice.Hall_.2011www.xuexi111.com_.pdf

https://docs.oracle.com/cd/E26502_01/html/E28388/eoiyg.html

https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html

Je vous remercie d'avoir lu jusqu'à ici et de m'avoir accompagné sur cet apprentissage intéressant des langages assembleurs, si vous avez des quelconques questions, n'hésiter pas à les demander!

À plus tard!

 

 

 

«Assembly - Basic Syntax», TutorialsPoint,  https://www.tutorialspoint.com/assembly_programming/assembly_basic_syntax.htm (Page consultée le 21 mars 2024)

«What are variables in assembly language?», Educative, https://www.educative.io/answers/what-are-variables-in-assembly-language (Page consultée le 22 mars 2024)

«Assembler Directives», Oracle, x86 Assembly Language Reference Manual,  https://docs.oracle.com/cd/E26502_01/html/E28388/eoiyg.html (Page consultée le 22 mars 2024)

«Intel x86 Assembly Language Programming», Bower Tim, CIS 450 - Computer Organization and Architecture, http://www.cs.cmu.edu/afs/cs/academic/class/15213-f02/www/R03/section_b/x86notes.pdf (Page consultée le 22 mars 2024)

Commentaires1