Assembly x86-64 - Corrigés Complets
Table des matières
- my_strlen
- strlen_rep
- parity_check
- _start / sys_hello
- fix_strcmp
- caller_spy
- print_bigsum
- memory_args
- float / double_avg
- cat
- balanced_brackets
- rpn_eval
my_strlen
Prototype : size_t my_strlen(const char *str)
Retourne : La longueur de la chaîne (sans le '\0')
.text
.global my_strlen
my_strlen:
xor %rax, %rax
.loop:
cmpb $0, (%rdi)
je .end
inc %rax
inc %rdi
jmp .loop
.end:
ret
Ligne par ligne :
| Ligne | Instruction | Explication |
|-------|-------------|-------------|
| 1 | xor %rax, %rax | length = 0 (initialise le compteur) |
| 2 | cmpb $0, (%rdi) | Compare le caractère courant avec '\0' |
| 3 | je .end | Si c'est '\0', on a fini |
| 4 | inc %rax | length++ |
| 5 | inc %rdi | Avance au caractère suivant |
| 6 | jmp .loop | Continue la boucle |
| 7 | ret | Retourne length |
strlen_rep
Prototype : size_t strlen_rep(const char *str)
Retourne : La longueur de la chaîne en utilisant repne scasb
.text
.global strlen_rep
strlen_rep:
mov %rdi, %rcx
xor %al, %al
mov $-1, %rcx
repne scasb
not %rcx
dec %rcx
mov %rcx, %rax
ret
Ligne par ligne :
| Ligne | Instruction | Explication |
|-------|-------------|-------------|
| 1 | mov %rdi, %rcx | Sauvegarde (pas nécessaire ici, erreur corrigée ci-dessous) |
| 2 | xor %al, %al | al = 0 (on cherche le '\0') |
| 3 | mov $-1, %rcx | rcx = 0xFFFFFFFFFFFFFFFF (max iterations) |
| 4 | repne scasb | Répète tant que *rdi != al et rcx != 0 |
| 5 | not %rcx | Inverse les bits de rcx |
| 6 | dec %rcx | Enlève 1 (le '\0' ne compte pas) |
| 7 | mov %rcx, %rax | Met le résultat dans rax |
| 8 | ret | Retourne la longueur |
Version corrigée :
.text
.global strlen_rep
strlen_rep:
xor %al, %al
mov $-1, %rcx
repne scasb
not %rcx
dec %rcx
mov %rcx, %rax
ret
Explication du calcul :
rcx commence à -1 (0xFFFF...)
- À chaque
scasb, rcx--
- Si on trouve '\0' après 5 caractères,
rcx = -1 - 5 - 1 = -7
not(-7) = 6, dec(6) = 5 ✓
parity_check
Prototype : int parity_check(uint64_t n)
Retourne : 1 si nombre pair de bits à 1, 0 sinon
.text
.global parity_check
parity_check:
xor %eax, %eax
popcnt %rdi, %rcx
test $1, %cl
setz %al
ret
Ligne par ligne :
| Ligne | Instruction | Explication |
|-------|-------------|-------------|
| 1 | xor %eax, %eax | result = 0 |
| 2 | popcnt %rdi, %rcx | Compte le nombre de bits à 1 dans n |
| 3 | test $1, %cl | Teste si le compte est impair |
| 4 | setz %al | al = 1 si ZF=1 (count pair), sinon 0 |
| 5 | ret | Retourne le résultat |
Alternative sans popcnt :
.text
.global parity_check
parity_check:
xor %eax, %eax
.loop:
test %rdi, %rdi
jz .done
mov %rdi, %rcx
and $1, %ecx
xor %ecx, %eax
shr $1, %rdi
jmp .loop
.done:
xor $1, %eax
ret
_start / sys_hello
But : Afficher "Hello, World!\n" sans utiliser la libc
.section .rodata
msg: .string "Hello, World!\n"
msg_len = . - msg - 1
.text
.global _start
_start:
mov $1, %rax
mov $1, %rdi
lea msg(%rip), %rsi
mov $msg_len, %rdx
syscall
mov $60, %rax
xor %rdi, %rdi
syscall
Ligne par ligne :
| Ligne | Instruction | Explication |
|-------|-------------|-------------|
| 1 | mov $1, %rax | Syscall numéro 1 = write |
| 2 | mov $1, %rdi | File descriptor 1 = stdout |
| 3 | lea msg(%rip), %rsi | Adresse du message (PIC) |
| 4 | mov $msg_len, %rdx | Longueur du message |
| 5 | syscall | Appelle write(1, msg, len) |
| 6 | mov $60, %rax | Syscall numéro 60 = exit |
| 7 | xor %rdi, %rdi | Code de sortie = 0 |
| 8 | syscall | Appelle exit(0) |
Compilation : gcc -nostdlib -o sys_hello sys_hello.S
fix_strcmp
Prototype : int fix_strcmp(const char *s1, const char *s2)
Retourne : < 0 si s1 < s2, 0 si égaux, > 0 si s1 > s2
.text
.global fix_strcmp
fix_strcmp:
.loop:
movzbl (%rdi), %eax
movzbl (%rsi), %ecx
cmp %cl, %al
jne .diff
test %al, %al
jz .equal
inc %rdi
inc %rsi
jmp .loop
.diff:
sub %ecx, %eax
ret
.equal:
xor %eax, %eax
ret
Ligne par ligne :
| Ligne | Instruction | Explication |
|-------|-------------|-------------|
| 1 | movzbl (%rdi), %eax | Charge s1[i] avec zero-extend |
| 2 | movzbl (%rsi), %ecx | Charge s2[i] avec zero-extend |
| 3 | cmp %cl, %al | Compare les deux caractères |
| 4 | jne .diff | Si différents, calcule la différence |
| 5 | test %al, %al | Teste si on est à '\0' |
| 6 | jz .equal | Si '\0', les chaînes sont égales |
| 7-8 | inc %rdi/rsi | Avance dans les deux chaînes |
| 9 | jmp .loop | Continue |
| 10 | sub %ecx, %eax | Retourne s1[i] - s2[i] |
| 11 | xor %eax, %eax | Retourne 0 (égaux) |
caller_spy
Prototype : void *caller_spy(void)
Retourne : L'adresse de retour de la fonction appelante
.text
.global caller_spy
caller_spy:
mov (%rsp), %rax
ret
Ligne par ligne :
| Ligne | Instruction | Explication |
|-------|-------------|-------------|
| 1 | mov (%rsp), %rax | Lit l'adresse de retour sur la pile |
| 2 | ret | Retourne cette adresse |
Explication :
- Quand on appelle une fonction,
call push l'adresse de retour sur la pile
- À l'entrée de la fonction,
(%rsp) contient cette adresse
- On la retourne simplement
Version avec offset (pour récupérer l'appelant de l'appelant) :
.text
.global caller_spy
caller_spy:
mov 8(%rsp), %rax
ret
print_bigsum
Prototype : void print_bigsum(const char *a, const char *b)
But : Additionne deux grands nombres (chaînes) et affiche le résultat
.section .bss
buffer: .space 1024
.text
.global print_bigsum
print_bigsum:
push %rbx
push %r12
push %r13
push %r14
push %r15
sub $8, %rsp
mov %rdi, %r12
mov %rsi, %r13
mov %rdi, %rdi
call strlen
mov %rax, %r14
mov %r13, %rdi
call strlen
mov %rax, %r15
lea buffer(%rip), %rbx
add $1022, %rbx
movb $0, 1(%rbx)
movb $10, (%rbx)
xor %ecx, %ecx
.loop:
xor %eax, %eax
xor %edx, %edx
cmp $0, %r14
jle .skip_a
dec %r14
movzbl (%r12, %r14), %eax
sub $'0', %eax
.skip_a:
cmp $0, %r15
jle .skip_b
dec %r15
movzbl (%r13, %r15), %edx
sub $'0', %edx
.skip_b:
add %edx, %eax
add %ecx, %eax
xor %ecx, %ecx
cmp $10, %eax
jl .no_carry
sub $10, %eax
mov $1, %ecx
.no_carry:
add $'0', %eax
dec %rbx
mov %al, (%rbx)
mov %r14, %rax
or %r15, %rax
jnz .loop
test %ecx, %ecx
jz .print
dec %rbx
movb $'1', (%rbx)
.print:
mov %rbx, %rdi
call puts
add $8, %rsp
pop %r15
pop %r14
pop %r13
pop %r12
pop %rbx
ret
Ligne par ligne (parties clés) :
| Section | Explication |
|---------|-------------|
| Sauvegarde registres | %rbx, %r12-15 sont callee-saved |
| strlen des deux nombres | Obtient la longueur de chaque nombre |
| Buffer setup | Prépare le buffer de sortie (de droite à gauche) |
| Loop principale | Additionne chiffre par chiffre avec retenue |
| Retenue finale | Ajoute '1' si carry restant |
| Print | Affiche le résultat |
memory_args
Prototype : Programme _start qui affiche ses arguments
.text
.global _start
_start:
mov (%rsp), %r12
lea 8(%rsp), %r13
xor %r14, %r14
.loop:
cmp %r12, %r14
jge .exit
mov (%r13, %r14, 8), %rdi
call puts
inc %r14
jmp .loop
.exit:
mov $60, %rax
xor %rdi, %rdi
syscall
Ligne par ligne :
| Ligne | Instruction | Explication |
|-------|-------------|-------------|
| 1 | mov (%rsp), %r12 | r12 = argc |
| 2 | lea 8(%rsp), %r13 | r13 = &argv[0] |
| 3 | xor %r14, %r14 | i = 0 |
| 4 | cmp %r12, %r14 | Compare i avec argc |
| 5 | jge .exit | Si i >= argc, fin |
| 6 | mov (%r13, %r14, 8), %rdi | rdi = argv[i] |
| 7 | call puts | Affiche l'argument |
| 8 | inc %r14 | i++ |
| 9 | jmp .loop | Continue |
Structure de la pile au _start :
(%rsp) = argc
8(%rsp) = argv[0] (nom du programme)
16(%rsp) = argv[1]
24(%rsp) = argv[2]
...
float / double_avg
Prototype : double double_avg(double *arr, size_t n)
Retourne : La moyenne des éléments du tableau
.text
.global double_avg
double_avg:
test %rsi, %rsi
jz .zero
xorpd %xmm0, %xmm0
xor %rcx, %rcx
.loop:
cmp %rsi, %rcx
jge .divide
addsd (%rdi, %rcx, 8), %xmm0
inc %rcx
jmp .loop
.divide:
cvtsi2sd %rsi, %xmm1
divsd %xmm1, %xmm0
ret
.zero:
xorpd %xmm0, %xmm0
ret
Ligne par ligne :
| Ligne | Instruction | Explication |
|-------|-------------|-------------|
| 1-2 | test/jz | Si n=0, retourne 0.0 |
| 3 | xorpd %xmm0, %xmm0 | sum = 0.0 |
| 4 | xor %rcx, %rcx | i = 0 |
| 5-6 | cmp/jge | Si i >= n, calcule la moyenne |
| 7 | addsd (%rdi, %rcx, 8), %xmm0 | sum += arr[i] |
| 8 | inc %rcx | i++ |
| 9 | jmp .loop | Continue |
| 10 | cvtsi2sd %rsi, %xmm1 | Convertit n en double |
| 11 | divsd %xmm1, %xmm0 | xmm0 = sum / n |
| 12 | ret | Retourne la moyenne (dans xmm0) |
Instructions float importantes :
| Instruction | Signification |
|-------------|---------------|
| addsd | Add Scalar Double |
| subsd | Subtract Scalar Double |
| mulsd | Multiply Scalar Double |
| divsd | Divide Scalar Double |
| cvtsi2sd | Convert Signed Int to Scalar Double |
| cvtsd2si | Convert Scalar Double to Signed Int |
| xorpd | XOR Packed Double (pour mettre à 0) |
cat
Prototype : Programme qui implémente cat (lit fichiers et les affiche)
.section .bss
buffer: .space 4096
.text
.global _start
_start:
mov (%rsp), %r12
lea 8(%rsp), %r13
cmp $1, %r12
jle .read_stdin
mov $1, %r14
.file_loop:
cmp %r12, %r14
jge .exit
mov (%r13, %r14, 8), %rdi
xor %esi, %esi
xor %edx, %edx
mov $2, %rax
syscall
test %rax, %rax
js .next_file
mov %rax, %r15
.read_loop:
mov $0, %rax
mov %r15, %rdi
lea buffer(%rip), %rsi
mov $4096, %rdx
syscall
test %rax, %rax
jle .close_file
mov %rax, %rdx
mov $1, %rax
mov $1, %rdi
lea buffer(%rip), %rsi
syscall
jmp .read_loop
.close_file:
mov $3, %rax
mov %r15, %rdi
syscall
.next_file:
inc %r14
jmp .file_loop
.read_stdin:
mov $0, %r15
jmp .read_loop
.exit:
mov $60, %rax
xor %rdi, %rdi
syscall
Ligne par ligne (parties clés) :
| Section | Explication |
|---------|-------------|
| mov $2, %rax | Syscall open |
| mov $0, %rax | Syscall read |
| mov $1, %rax | Syscall write |
| mov $3, %rax | Syscall close |
| mov $60, %rax | Syscall exit |
Syscalls utilisés :
| Numéro | Nom | Arguments |
|--------|-----|-----------|
| 0 | read | fd, buf, count |
| 1 | write | fd, buf, count |
| 2 | open | path, flags, mode |
| 3 | close | fd |
| 60 | exit | code |
balanced_brackets
Prototype : int balanced_brackets(const char *str)
Retourne : 1 si les parenthèses/crochets/accolades sont équilibrés, 0 sinon
.section .bss
stack: .space 1024
.text
.global balanced_brackets
balanced_brackets:
push %rbx
push %r12
mov %rdi, %r12
lea stack(%rip), %rbx
xor %ecx, %ecx
.loop:
movzbl (%r12), %eax
test %al, %al
jz .check_empty
cmp $'(', %al
je .push
cmp $'[', %al
je .push
cmp $'{', %al
je .push
cmp $')', %al
je .check_paren
cmp $']', %al
je .check_bracket
cmp $'}', %al
je .check_brace
inc %r12
jmp .loop
.push:
mov %al, (%rbx, %rcx)
inc %rcx
inc %r12
jmp .loop
.check_paren:
test %rcx, %rcx
jz .fail
dec %rcx
cmpb $'(', (%rbx, %rcx)
jne .fail
inc %r12
jmp .loop
.check_bracket:
test %rcx, %rcx
jz .fail
dec %rcx
cmpb $'[', (%rbx, %rcx)
jne .fail
inc %r12
jmp .loop
.check_brace:
test %rcx, %rcx
jz .fail
dec %rcx
cmpb $'{', (%rbx, %rcx)
jne .fail
inc %r12
jmp .loop
.check_empty:
test %rcx, %rcx
jnz .fail
mov $1, %eax
jmp .done
.fail:
xor %eax, %eax
.done:
pop %r12
pop %rbx
ret
Ligne par ligne (parties clés) :
| Section | Explication |
|---------|-------------|
| Stack BSS | Pile pour stocker les ouvertures |
| .push | Empile le caractère ouvrant |
| .check_* | Vérifie que le fermant correspond à l'ouvrant |
| .check_empty | À la fin, la pile doit être vide |
Logique :
- Parcourt la chaîne caractère par caractère
- Si ouvrant
([{ : push sur la pile
- Si fermant
)]} : pop et vérifie la correspondance
- À la fin, pile vide = équilibré
rpn_eval
Prototype : int64_t rpn_eval(const char *expr)
Retourne : Le résultat de l'expression en notation polonaise inversée
Exemple : "3 4 + 2 *" = (3+4)*2 = 14
.section .bss
stack: .space 8192
.text
.global rpn_eval
rpn_eval:
push %rbx
push %r12
push %r13
push %r14
push %r15
mov %rdi, %r12
lea stack(%rip), %r13
xor %r14, %r14
.main_loop:
movzbl (%r12), %eax
test %al, %al
jz .end
cmp $' ', %al
je .skip
cmp $'+', %al
je .add
cmp $'-', %al
je .sub_op
cmp $'*', %al
je .mul
cmp $'/', %al
je .div_op
xor %rax, %rax
.parse_num:
movzbl (%r12), %ecx
sub $'0', %ecx
cmp $9, %ecx
ja .push_num
imul $10, %rax
add %rcx, %rax
inc %r12
jmp .parse_num
.push_num:
mov %rax, (%r13, %r14, 8)
inc %r14
jmp .main_loop
.skip:
inc %r12
jmp .main_loop
.add:
dec %r14
mov (%r13, %r14, 8), %rax
dec %r14
add (%r13, %r14, 8), %rax
mov %rax, (%r13, %r14, 8)
inc %r14
inc %r12
jmp .main_loop
.sub_op:
cmp $1, %r14
jle .parse_negative
dec %r14
mov (%r13, %r14, 8), %rbx
dec %r14
mov (%r13, %r14, 8), %rax
sub %rbx, %rax
mov %rax, (%r13, %r14, 8)
inc %r14
inc %r12
jmp .main_loop
.parse_negative:
inc %r12
xor %rax, %rax
.parse_neg_num:
movzbl (%r12), %ecx
sub $'0', %ecx
cmp $9, %ecx
ja .push_neg
imul $10, %rax
add %rcx, %rax
inc %r12
jmp .parse_neg_num
.push_neg:
neg %rax
mov %rax, (%r13, %r14, 8)
inc %r14
jmp .main_loop
.mul:
dec %r14
mov (%r13, %r14, 8), %rax
dec %r14
imul (%r13, %r14, 8), %rax
mov %rax, (%r13, %r14, 8)
inc %r14
inc %r12
jmp .main_loop
.div_op:
dec %r14
mov (%r13, %r14, 8), %rbx
dec %r14
mov (%r13, %r14, 8), %rax
cqo
idiv %rbx
mov %rax, (%r13, %r14, 8)
inc %r14
inc %r12
jmp .main_loop
.end:
dec %r14
mov (%r13, %r14, 8), %rax
pop %r15
pop %r14
pop %r13
pop %r12
pop %rbx
ret
Ligne par ligne (parties clés) :
| Section | Explication |
|---------|-------------|
| .parse_num | Parse un nombre et le push |
| .add | Pop 2 valeurs, additionne, push résultat |
| .sub_op | Pop 2 valeurs, soustrait, push résultat |
| .mul | Pop 2 valeurs, multiplie, push résultat |
| .div_op | Pop 2 valeurs, divise, push résultat |
| .end | Retourne le sommet de la pile |
Algorithme RPN :
- Si c'est un nombre → push sur la pile
- Si c'est un opérateur → pop 2 opérandes, calcule, push résultat
- À la fin → le résultat est sur le sommet de la pile
Note sur cqo :
cqo = Convert Quadword to Octword
- Étend le signe de
%rax vers %rdx:%rax
- Nécessaire avant
idiv pour la division signée
Rappels importants
Registres pour les arguments (System V AMD64 ABI)
| Argument |
Registre |
| 1er |
%rdi |
| 2ème |
%rsi |
| 3ème |
%rdx |
| 4ème |
%rcx |
| 5ème |
%r8 |
| 6ème |
%r9 |
| Retour |
%rax |
| Float args |
%xmm0-%xmm7 |
| Float return |
%xmm0 |
Registres callee-saved vs caller-saved
| Callee-saved (à préserver) |
Caller-saved (volatiles) |
| %rbx |
%rax |
| %rbp |
%rcx |
| %r12 |
%rdx |
| %r13 |
%rsi |
| %r14 |
%rdi |
| %r15 |
%r8-%r11 |
Syscalls Linux x86-64
| Numéro |
Nom |
rdi |
rsi |
rdx |
| 0 |
read |
fd |
buf |
count |
| 1 |
write |
fd |
buf |
count |
| 2 |
open |
path |
flags |
mode |
| 3 |
close |
fd |
- |
- |
| 60 |
exit |
code |
- |
- |