Trilhas
/
x86-64 Assembly
x86-64 Assembly
/
Programa
/
Noções básicas
No

Noções básicas em x86-64 Assembly

1 exercício

Sobre Noções básicas

Comentários

A trilha x86-64 do Exercism usa o NASM (The Netwide Assembler) como montador. 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 montador.

Constantes

Uma constante em tempo de montagem pode ser definida no NASM 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, quando são definidas, e não podem ser redefinidas depois.

Registradores de Uso Geral (GPRs)

Em assembly, em vez de variáveis, normalmente usamos registradores para armazenar valores. Um registrador de CPU é um pedaço de memória rápida dentro do processador de um computador. A maior parte das computações é realizada nos registradores da CPU.

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

No x86-64, existem 16 registradores de uso geral (GPRs) de 64 bits, que também podem ser acessados como 32 bits, 16 bits 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 parte 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 devem 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 um comportamento indefinido.

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

Note

Os registradores geralmente são preservados na pilha usando a instrução push. Eles são então restaurados antes do final da função usando a instrução pop.

Instruções

As instruções são partes de computações que uma CPU pode realizar. 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 devem ter o mesmo tamanho, a menos que indicado de outra forma. Por exemplo, o operando de destino também deve 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 bits 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.

Uma declaração de 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.

Existem convenções específicas para como os argumentos são passados para uma função e retornados dela. As duas principais convenções de chamada são a System V AMD64 ABI e a Microsoft x64.

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 da 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, a diretiva global deve ser usada.

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
Editar via GitHub O link abre em uma nova janela ou aba

Aprenda Noções básicas