Lasagna

Lasagna

Esercizio di apprendimento

Introduzione

Le basi

I commenti

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.

Le costanti

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.

I registri di uso generale (GPR)

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.

Caution

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

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 funzioni

Le istruzioni sono organizzate in funzioni. Tutte le funzioni sono inserite nella sezione .text del file sorgente.

Una dichiarazione di funzione è composta da:

  1. Un'etichetta con il nome della funzione, seguita da un :.
  2. Le istruzioni che definiscono la funzione.
  3. L'istruzione di ritorno, 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

Istruzioni

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.

Note

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

1. Definisci il tempo previsto in forno, in minuti

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

2. Calcola il tempo rimanente in forno, in minuti

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

3. Calcola il tempo di preparazione, in minuti

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

4. Calcola il tempo trascorso, in minuti

Definisci la funzione elapsed_time_in_minutes che prende due parametri, in quest'ordine:

  1. Il numero di strati che hai aggiunto alla lasagna.
  2. Il numero di minuti che la lasagna ha passato in forno.

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
Modifica tramite GitHub Il link si apre in una nuova finestra o scheda
x86-64 Assembly Exercism

Vuoi iniziare Lasagna?

Iscriviti a Exercism per imparare e padroneggiare x86-64 Assembly con 22 concetti130 esercizi e il mentoring di persone reali, tutto gratis.