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 kernelmov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernelsection .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 gracefullysection .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 kernelmov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernelsection .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 kernelsection .data
msg1 db 'Hello, programmers!',0xA,0xD
len1 equ $ - msg1msg2 db 'Welcome to the world of,', 0xA,0xD
len2 equ $ - msg2msg3 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 1segment .data
msg1 db "Enter a digit ", 0xA,0xD
len1 equ $- msg1msg2 db "Please enter a second digit", 0xA,0xD
len2 equ $- msg2msg3 db "The sum is: "
len3 equ $- msg3segment .bss
num1 resb 2
num2 resb 2
res resb 1section .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 0x80mov eax, SYS_READ
mov ebx, STDIN
mov ecx, num1
mov edx, 2
int 0x80mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg2
mov edx, len2
int 0x80mov eax, SYS_READ
mov ebx, STDIN
mov ecx, num2
mov edx, 2
int 0x80mov 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 0x80exit:
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 outprogevnn:
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 kerneloutprog:
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernelsection .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
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
Bel final article sur langage d'assembly!
Quelle partie de cet apprentissage était votre favori d'apprendre ? Et voulez-vous couvrir sa version moderne ?