Percursos
/
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

O percurso de x86-64 no Exercism usa o NASM (The Netwide Assembler) como assembler. No NASM, os comentários são precedidos de 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

Em NASM, é possível definir uma constante em tempo de assemblagem com equ. Por exemplo, isto define uma constante chamada UNIVERSE com o valor 42:

UNIVERSE equ 42

As constantes são avaliadas uma só vez, no momento em que 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 guardar valores. Um registo da CPU é um pedaço de memória rápida dentro do processador do computador. A maior parte dos cálculos é feita nos registos da CPU.

Alguns dos registos são usados para diversos tipos de cálculos e chamam-se registos de uso geral (GPRs). Outros têm fins especiais ou dedicados.

Em x86-64, existem 16 registos de uso geral (GPRs) de 64 bits, que também podem ser acedidos 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

Quando se usam menos de 64 bits, os bits acedidos são normalmente os 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 ao longo das chamadas de funções: rbp, rsp, rbx, r12, r13, r14 e r15. Não os preservar pode provocar um erro ou 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

Normalmente, os registos são preservados na pilha com uma instrução push. Depois são restaurados antes do fim da função com uma instrução pop.

Instruções

As instruções são partes de cálculos que a CPU pode executar. Têm normalmente 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, uma 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, se o operando de origem tiver 16 bits, o operando de destino também tem de ter 16 bits.

Por exemplo, para guardar 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.

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

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, por isso mov eax, 42 é o mesmo que mov rax, 42. Isto não se aplica aos registos de 16 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 organizam-se em funções. Todas as funções ficam na secção .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.

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

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

Um valor inteiro é devolvido pela 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
Editar via GitHub A ligação abre numa nova janela ou separador

Aprende Noções Básicas