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.
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.
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.
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.
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
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:
:.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
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.
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 |
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
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
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
Define a função elapsed_time_in_minutes, que recebe dois parâmetros, por esta ordem:
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
Inscreve-te no Exercism para aprenderes e dominares x86-64 Assembly com 22 conceitos130 exercícios, e mentoria humana real, tudo grátis.