Lasagna

Lasagna

Exercício de aprendizagem

Introdução

Noções básicas

Comentários

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

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

Constantes

No NASM, você pode definir uma constante em tempo de assembler usando equ. Por exemplo, isto define uma constante chamada UNIVERSE com o valor 42:

UNIVERSE equ 42

As constantes são avaliadas uma única vez, no momento em que são definidas, e não podem ser redefinidas depois.

Registradores de propósito geral (GPRs)

Em assembly, em vez de variáveis, normalmente usamos registradores para armazenar valores. Um registrador da CPU é um pequeno espaço de memória rápida dentro do processador de um computador. A maioria dos cálculos é realizada nos registradores da CPU.

Alguns registradores são usados para diversos tipos diferentes de cálculos e são chamados de registradores de propósito geral (GPRs). Outros têm propósitos especiais ou dedicados.

No x86-64, existem 16 registradores de propósito geral (GPRs) de 64 bits, que também podem ser acessados como 32, 16 ou 8 bits. Os GPRs são descritos abaixo, em que n em rn vai de 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

Ao usar menos de 64 bits, os bits acessados geralmente são da parte inferior do registrador. A exceção a essa regra são ah, bh, ch e dh, que acessam os 8 bits superiores da porção de 16 bits do registrador.

Ilustração de como os bits são acessados no registrador rax:

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

Alguns desses registradores precisam ser preservados entre chamadas de função: rbp, rsp, rbx, r12, r13, r14 e r15. Não preservá-los pode levar 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 adiante no programa de estudos, você vai aprender a preservar registradores. Por enquanto, use apenas o segundo tipo de registradores: rax, rcx, rdx, rdi, rsi, r8, r9, r10 e r11.

Instruções

As instruções são trechos de computação que uma CPU pode executar. Elas geralmente 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. O operando de origem normalmente não é modificado por uma instrução, apenas o operando de destino.

Os dois operandos precisam ter o mesmo tamanho, a menos que indicado de outra forma. Por exemplo, o operando de destino também precisa ter 16 bits se o operando de origem tiver 16 bits.

Por exemplo, para armazenar um valor em um registrador, 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 trecho acima copia o conteúdo de todos os 64 bits de rdx, o operando de origem, para todos 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

Repare que escrever em um registrador de 32 bits também limpa os bits superiores, então mov eax, 42 é o mesmo que mov rax, 42. Isso não vale para registradores de 16 e 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 são organizadas em funções. Todas as funções ficam na section .text do arquivo-fonte.

A declaração de uma função consiste em:

  1. Um rótulo com o nome da função, seguido de :.
  2. As instruções que definem a função.
  3. A instrução de retorno, ret.

Esta trilha usa a convenção de chamada System V AMD64 ABI, e os seis primeiros argumentos inteiros são passados para uma função em registradores. Eles são passados na seguinte ordem: rdi, rsi, rdx, rcx, r8 e r9.

Um valor inteiro é retornado pela função no registrador rax.

Para chamar uma função, usamos a instrução call. Uma função pode ser chamada em qualquer lugar do mesmo arquivo-fonte. Para tornar uma função visível a outros arquivos-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 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, você vai escrever um pouco de código para ajudar a preparar uma lasanha incrível do seu livro de receitas favorito.

Você tem quatro tarefas, todas relacionadas ao tempo gasto no preparo da 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 retorna de uma função

1. Defina o tempo esperado no forno em minutos

Defina a função expected_minutes_in_oven, que não recebe nenhum parâmetro e retorna quantos minutos a lasanha deve ficar no forno. De acordo com o livro de receitas, o tempo esperado no forno é de 40 minutos:

expected_minutes_in_oven();
// => 40

2. Calcule o tempo restante no forno em minutos

Defina a função remaining_minutes_in_oven, que recebe como parâmetro os minutos que a lasanha já passou no forno e retorna quantos minutos a lasanha ainda precisa ficar no forno, com base no tempo esperado no forno da tarefa anterior.

remaining_minutes_in_oven(30);
// => 10

3. Calcule o tempo de preparo em minutos

Defina a função preparation_time_in_minutes, que recebe como parâmetro o número de camadas que você adicionou à lasanha e retorna quantos minutos você gastou preparando a lasanha, considerando que cada camada leva 2 minutos para preparar.

preparation_time_in_minutes(2);
// => 4

4. Calcule o tempo decorrido em minutos

Defina a função elapsed_time_in_minutes, que recebe dois parâmetros, nesta ordem:

  1. O número de camadas que você adicionou à lasanha.
  2. O número de minutos que a lasanha passou no forno.

A função deve retornar quantos minutos você trabalhou no preparo da lasanha. Esse valor é a soma do tempo de preparo em minutos com o tempo, em minutos, que a lasanha passou no forno até o momento.

elapsed_time_in_minutes(3, 20);
// => 26
Editar via GitHub O link abre em uma nova janela ou aba
x86-64 Assembly Exercism

Tudo pronto para começar Lasagna?

Crie sua conta no Exercism para aprender e dominar x86-64 Assembly com 22 conceitos130 exercícios e mentoria humana de verdade, tudo de graça.