Lasanha

Lasanha

Exercício de aprendizagem

Introdução

Noções básicas

Comentários

O percurso x86-64 no Exercism usa o NASM (The Netwide Assembler) como o seu assembler. No NASM, os comentários são precedidos por um ponto e vírgula (;).

Os comentários podem ser colocados em qualquer parte do programa e tudo o que vem depois de um ; é ignorado pelo assembler.

Constantes

Uma constante definida em tempo de assembler pode ser definida em NASM com equ. Por exemplo, isto define uma constante chamada UNIVERSE com o valor 42:

UNIVERSE equ 42

As constantes são avaliadas uma vez, quando são definidas, e não podem ser redefinidas mais tarde.

Registos de uso geral (GPRs)

Em assembly, em vez de variáveis, usamos normalmente registos para armazenar valores. Um registo da CPU é uma pequena memória rápida dentro do processador de um computador. A maioria dos cálculos é realizada nos registos da CPU.

Alguns dos registos são usados para vários tipos diferentes de cálculos e são chamados registos de uso geral (GPRs). Outros têm propósitos especiais ou dedicados.

Em x86-64, existem 16 registos de uso geral (GPRs) de 64 bits, que também podem ser acedidos como 32 bits, 16 bits ou 8 bits. Os GPRs são descritos abaixo, onde n em rn varia de 8 a 15: r8, r9, r10, r11, r12, r13, r14 e r15.

64 bits 32 bits 16 bits 8 bits
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 se usam menos de 64 bits, os bits acedidos são normalmente da parte inferior do registo. A exceção a esta regra são ah, bh, ch e dh, que acedem aos 8 bits superiores da parte de 16 bits do registo.

Ilustração de como os bits são acedidos no registo rax:

+--------+---------------------------------------+
| 64-bit |                  rax                  |
+--------+-------------------+-------------------+
| 32-bit |                   |        eax        |
+--------+-------------------+---------+---------+
| 16-bit |                             |    ax   |
+--------+-----------------------------+----+----+
| 8-bit  |                             | ah | al |
+--------+-----------------------------+----+----+

Alguns desses registos têm de ser preservados entre chamadas de funções: rbp, rsp, rbx, r12, r13, r14 e r15. Não os preservar pode dar origem a um erro ou a comportamento indefinido.

Os outros não são preservados e podem ser usados livremente: rax, rcx, rdx, rdi, rsi, r8, r9, r10 e r11.

Caution

Mais à frente no programa, vais aprender a preservar registos. Para já, usa apenas o segundo tipo de registos: rax, rcx, rdx, rdi, rsi, r8, r9, r10 e r11.

Instruções

As instruções são operações que uma CPU pode realizar. Normalmente têm a seguinte forma:

name destination, source

Assim, o nome da instrução vem primeiro, depois pelo menos um espaço em branco, seguido do operando de destino, uma vírgula (,) e, por fim, um operando de origem. Normalmente, a instrução não modifica o operando de origem, apenas o operando de destino.

Ambos os operandos têm de ter o mesmo tamanho, salvo indicação em contrário. Por exemplo, o operando de destino também tem de ter 16 bits se o operando de origem tiver 16 bits.

Por exemplo, para armazenar um valor num registo, podemos usar a instrução mov:

mov rax, rdx  ; rax = rdx
              ; mov is the name, rax is the destination operand and rdx is the source operand

O excerto acima copia o conteúdo dos 64 bits de rdx, o operando de origem, para os 64 bits de rax, que é o operando de destino da instrução.

É possível usar números inteiros constantes como operandos de origem (mas não como operandos de destino) na maioria das instruções:

mov rcx, 42 ; rcx = 42
            ; rcx is the destination operand and 42 is the source operand

Repara que escrever num registo de 32 bits também limpa os bits superiores, pelo que mov eax, 42 é o mesmo que mov rax, 42. Isto não é verdade para registos de 16 bits e de 8 bits.

Para as operações aritméticas de adição, subtração e multiplicação, podemos usar as instruções add, sub e imul:

add rax, rsi ; rax = rax + rsi
imul rax, rdi ; rax = rax * rdi
sub rax, r8 ; rax = rax - r8

Funções

As instruções estão organizadas em funções. Todas as funções são colocadas na section .text do ficheiro de código-fonte.

Uma declaração de função é composta por:

  1. Uma etiqueta com o nome da função, seguida de :.
  2. As instruções que definem a função.
  3. A instrução de retorno, ret.

Este percurso usa a convenção de chamada System V AMD64 ABI e os seis primeiros argumentos inteiros são passados a uma função em registos. São passados pela seguinte ordem: rdi, rsi, rdx, rcx, r8 e r9.

Um valor inteiro é devolvido da função no registo rax.

Para chamar uma função, usamos a instrução call. Uma função pode ser chamada em qualquer parte do mesmo ficheiro de código-fonte. Para tornar uma função visível a outros ficheiros de código-fonte, é preciso usar a diretiva global.

Por exemplo, isto declara uma função 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 isto chama a nossa função sum com os argumentos 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

Instruções

Neste exercício vais escrever algum código para te ajudar a cozinhar uma lasanha fantástica do teu livro de culinária preferido.

Tens quatro tarefas, todas relacionadas com o tempo gasto a cozinhar a lasanha.

Note

Estas são as instruções mencionadas neste conceito:

Instrução Descrição
mov a, b copia o conteúdo de b para a
add a, b a = a + b
sub a, b a = a - b
imul a, b a = a * b
call a chama a função a
ret regressa de uma função

1. Define o tempo esperado no forno em minutos

Define a função expected_minutes_in_oven, que não recebe parâmetros e devolve quantos minutos a lasanha deve estar no forno. Segundo o livro de culinária, o tempo esperado no forno, em minutos, é 40:

expected_minutes_in_oven();
// => 40

2. Calcula o tempo restante no forno em minutos

Define a função remaining_minutes_in_oven, que recebe como parâmetro os minutos que a lasanha já passou no forno e devolve quantos minutos a lasanha ainda tem de permanecer no forno, com base no tempo esperado no forno em minutos da tarefa anterior.

remaining_minutes_in_oven(30);
// => 10

3. Calcula o tempo de preparação em minutos

Define a função preparation_time_in_minutes, que recebe como parâmetro o número de camadas que adicionaste à lasanha e devolve quantos minutos passaste a preparar a lasanha, assumindo que cada camada demora 2 minutos a preparar.

preparation_time_in_minutes(2);
// => 4

4. Calcula o tempo decorrido em minutos

Define a função elapsed_time_in_minutes, que recebe dois parâmetros, por esta ordem:

  1. O número de camadas que adicionaste à lasanha.
  2. O número de minutos que a lasanha já passou no forno.

A função deve devolver quantos minutos trabalhaste a cozinhar a lasanha. Isto é a soma do tempo de preparação em minutos e do tempo, em minutos, que a lasanha passou no forno até ao momento.

elapsed_time_in_minutes(3, 20);
// => 26
Editar via GitHub A ligação abre numa nova janela ou separador
x86-64 Assembly Exercism

Estás pronto para começar Lasanha?

Inscreve-te no Exercism para aprenderes e dominares x86-64 Assembly com 22 conceitos130 exercícios, e mentoria humana real, tudo grátis.