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.
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.
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.
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.
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
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:
:.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
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.
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 |
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
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
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
Defina a função elapsed_time_in_minutes, que recebe dois parâmetros, nesta ordem:
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
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.