La traccia x86-64 di Exercism usa NASM (The Netwide Assembler) come assembler.
In NASM, i commenti sono preceduti da un punto e virgola (;).
I commenti possono essere inseriti ovunque nel programma e tutto ciò che viene dopo un ; viene ignorato dall'assembler.
In NASM è possibile definire una costante in fase di assemblaggio usando equ.
Ad esempio, qui si definisce una costante chiamata UNIVERSE con il valore 42:
UNIVERSE equ 42
Le costanti vengono valutate una sola volta, al momento della definizione, e non possono essere ridefinite in seguito.
In assembly, invece delle variabili, di solito usiamo i registri per memorizzare i valori. Un registro della CPU è una porzione di memoria veloce all'interno del processore di un computer. La maggior parte dei calcoli viene eseguita nei registri della CPU.
Alcuni registri sono usati per svariati tipi di calcolo e sono chiamati registri di uso generale (GPR). Altri hanno scopi speciali o dedicati.
In x86-64 ci sono 16 registri di uso generale (GPR) a 64 bit, a cui si può accedere anche come 32 bit, 16 bit o 8 bit.
I GPR sono descritti di seguito, dove n in rn va da 8 a 15: r8, r9, r10, r11, r12, r13, r14 e r15.
| 64-bit | 32-bit | 16-bit | 8-bit |
|---|---|---|---|
| rax | eax | ax | ah/al |
| rbx | ebx | bx | bh/bl |
| rcx | ecx | cx | ch/cl |
| rdx | edx | dx | dh/dl |
| rsi | esi | si | sil |
| rdi | edi | di | dil |
| rbp | ebp | bp | bpl |
| rsp | esp | sp | spl |
| rn | rnd | rnw | rnb |
Quando si usano meno di 64 bit, i bit a cui si accede provengono di solito dalla parte bassa del registro.
L'eccezione a questa regola sono ah, bh, ch e dh, che accedono agli 8 bit alti della porzione a 16 bit del registro.
Illustrazione di come si accede ai bit per il registro rax:
+--------+---------------------------------------+
| 64-bit | rax |
+--------+-------------------+-------------------+
| 32-bit | | eax |
+--------+-------------------+---------+---------+
| 16-bit | | ax |
+--------+-----------------------------+----+----+
| 8-bit | | ah | al |
+--------+-----------------------------+----+----+
Alcuni di questi registri devono essere preservati durante le chiamate di funzione: rbp, rsp, rbx, r12, r13, r14 e r15.
Se non li preservi, potresti causare un errore o un comportamento indefinito.
Gli altri non sono preservati e possono essere usati liberamente: rax, rcx, rdx, rdi, rsi, r8, r9, r10 e r11.
Più avanti nel programma, imparerai come preservare i registri.
Per ora, usa solo il secondo tipo di registri: rax, rcx, rdx, rdi, rsi, r8, r9, r10 e r11.
Le istruzioni sono calcoli che una CPU può eseguire. Di solito hanno la seguente forma:
name destination, source
Quindi, prima viene il nome dell'istruzione, poi almeno uno spazio bianco, seguito dall'operando di destinazione, una virgola (,) ed infine un operando sorgente.
L'operando sorgente di solito non viene modificato da un'istruzione, solo l'operando di destinazione.
Entrambi gli operandi devono avere la stessa dimensione, salvo diversa indicazione. Per esempio, anche l'operando di destinazione deve avere 16 bit se l'operando sorgente ha 16 bit.
Per esempio, per memorizzare un valore in un registro, possiamo usare l'istruzione mov:
mov rax, rdx ; rax = rdx
; mov is the name, rax is the destination operand and rdx is the source operand
Il frammento qui sopra copia il contenuto di tutti i 64 bit di rdx, l'operando sorgente, in tutti i 64 bit di rax, che è l'operando di destinazione dell'istruzione.
Nella maggior parte delle istruzioni è possibile usare numeri interi costanti come operandi sorgente (ma non come operandi di destinazione):
mov rcx, 42 ; rcx = 42
; rcx is the destination operand and 42 is the source operand
Nota che scrivere in un registro a 32 bit azzera anche i bit alti, quindi mov eax, 42 equivale a mov rax, 42.
Questo non vale per i registri a 16 bit e 8 bit.
Per le operazioni aritmetiche di addizione, sottrazione e moltiplicazione, possiamo usare le istruzioni add, sub e imul:
add rax, rsi ; rax = rax + rsi
imul rax, rdi ; rax = rax * rdi
sub rax, r8 ; rax = rax - r8
Le istruzioni sono organizzate in funzioni. Tutte le funzioni sono inserite nella sezione .text del file sorgente.
Una dichiarazione di funzione è composta da:
:.ret.Questa traccia usa la convenzione di chiamata System V AMD64 ABI e i primi sei argomenti interi vengono passati a una funzione nei registri.
Vengono passati nel seguente ordine: rdi, rsi, rdx, rcx, r8 e r9.
La funzione restituisce un valore intero nel registro rax.
Per chiamare una funzione, usiamo l'istruzione call.
Una funzione può essere chiamata ovunque nello stesso file sorgente.
Per rendere una funzione visibile ad altri file sorgente, si deve usare la direttiva global.
Per esempio, questo dichiara una funzione sum:
section .text ; functions are placed here
global sum ; sum is visible to other source files
sum:
; first argument is passed in rdi
; second argument is passed in rsi
; return value is placed in rax
mov rax, rdi ; rax is now equal to rdi
add rax, rsi ; rax = rax + rsi
ret ; function return
E questo chiama la funzione sum con gli argomenti 3 e 5:
mov rdi, 3 ; First argument in rdi
mov rsi, 5 ; Second argument in rsi
call sum
; The rax register now contains the value 8 (3 + 5), after sum returns
In questo esercizio scriverai del codice per aiutarti a cucinare una lasagna fantastica dal tuo libro di cucina preferito.
Hai quattro compiti, tutti legati al tempo necessario per cucinare la lasagna.
Queste sono le istruzioni menzionate in questo concetto:
| Istruzione | Descrizione |
|---|---|
| mov a, b | copia il contenuto da b ad a |
| add a, b | a = a + b |
| sub a, b | a = a - b |
| imul a, b | a = a * b |
| call a | chiama la funzione a |
| ret | ritorna da una funzione |
Definisci la funzione expected_minutes_in_oven che non prende alcun parametro e restituisce per quanti minuti la lasagna deve stare in forno.
Secondo il libro di cucina, il tempo previsto in forno è di 40 minuti:
expected_minutes_in_oven();
// => 40
Definisci la funzione remaining_minutes_in_oven che prende come parametro i minuti effettivi che la lasagna ha già passato in forno e restituisce per quanti minuti la lasagna deve ancora rimanere in forno, in base al tempo previsto in forno del compito precedente.
remaining_minutes_in_oven(30);
// => 10
Definisci la funzione preparation_time_in_minutes che prende come parametro il numero di strati che hai aggiunto alla lasagna e restituisce quanti minuti hai impiegato a prepararla, ipotizzando che ogni strato richieda 2 minuti di preparazione.
preparation_time_in_minutes(2);
// => 4
Definisci la funzione elapsed_time_in_minutes che prende due parametri, in quest'ordine:
La funzione dovrebbe restituire per quanti minuti hai lavorato alla cottura della lasagna. È la somma del tempo di preparazione in minuti e del tempo, in minuti, che la lasagna ha passato in forno fino a quel momento.
elapsed_time_in_minutes(3, 20);
// => 26
Iscriviti a Exercism per imparare e padroneggiare x86-64 Assembly con 22 concetti130 esercizi e il mentoring di persone reali, tutto gratis.