Endereçamento Indireto em Assembly NASM
O endereçamento indireto é utilizado para acessar dados armazenados em um endereço específico da memória. O NASM permite especificar endereços com base em expressões e cálculos, o que oferece grande flexibilidade na manipulação de dados.
Uma variável em Assembly representa seu endereço na memória, e não o valor armazenado nela. Para acessar o conteúdo desse endereço, o nome da variável deve ser colocado entre colchetes:
[number] ; valor da variável number
A seguir, um exemplo em Linux que carrega o endereço de uma variável e obtém o valor armazenado nela:
global _start
section .data
num dq 12
section .text
_start:
mov rax, [num] ; carrega o valor de num em RAX
add rax, 3
mov [num], rax ; grava o novo valor de RAX em num
mov rdi, [num] ; RDI = 15
mov rax, 60
syscallPrimeiro, o valor 12 é carregado em RAX, incrementado em 3 e armazenado de volta na variável num.
O registrador RDI recebe então o novo valor (15).
Em Windows, a lógica é idêntica, mas o acesso à variável deve ser feito com o operador rel:
global _start
section .data
num dq 12
section .text
_start:
mov rdi, [rel num]
add rdi, 3
mov [rel num], rdi
mov rax, [rel num]
retO acesso pelo nome da variável é apenas um caso particular de endereçamento em NASM. A seguir, veremos todos os modos de endereçamento disponíveis.
Modos de Endereçamento no NASM
O acesso a um endereço em NASM segue a forma geral:
[base + (index * scale) + offset]
Componentes da fórmula
- base: registrador base, que contém um endereço (qualquer registrador geral de 32 ou 64 bits, ou RSP).
- index: registrador de índice, que contém um deslocamento relativo ao endereço base (também pode ser um registrador geral de 32 ou 64 bits).
- scale: fator de multiplicação aplicado ao índice. Pode ser 1, 2, 4 ou 8.
- offset: valor de deslocamento, que pode ser um número (constante de 32 bits) ou o nome de uma variável.
Nem todos os componentes precisam ser usados simultaneamente. Alguns exemplos:
| Forma de Endereçamento | Exemplo | Descrição |
|---|---|---|
[base] | [rdx] | Apenas o registrador base |
[offset] | [0F3h] ou [variavel] | Endereço absoluto ou variável |
[base + offset] | [rcx + 33h] | Base + deslocamento |
[base + index] | [rax + ecx] | Base + índice |
[index * scale] | [rbx * 4] | Índice multiplicado |
[index * scale + offset] | [rbx * 4 + 65] | Índice multiplicado com deslocamento |
[base + index * scale] | [rsp + rbx * 4] | Base + índice multiplicado |
[base + index * scale + offset] | [rsp + rbx * 4 + 65] | Base + índice multiplicado + deslocamento |
Deslocamentos
O acesso direto a uma variável, como [num], é um exemplo de endereçamento por deslocamento, em que o deslocamento é o endereço da variável.
mov rax, [num]Podemos somar um deslocamento numérico:
global _start
section .data
nums dq 12, 13, 14, 15, 16
section .text
_start:
mov rdi, [nums + 16] ; RDI = 14
mov rax, 60
syscallAqui, [nums + 16] representa o endereço 16 bytes após o início de nums.
Como cada elemento ocupa 8 bytes, esse endereço corresponde ao terceiro elemento, ou seja, 14.
Deslocamentos também podem ser negativos:
global _start
section .data
number dq 33
nums dq 12, 13, 14, 15, 16
section .text
_start:
mov rdi, [nums - 8] ; RDI = 33
mov rax, 60
syscallNesse caso, [nums - 8] acessa o valor armazenado 8 bytes antes do início de nums, isto é, a variável number.
O deslocamento máximo permitido é de 32 bits.
Registrador Base
Um registrador base armazena um endereço. Podemos carregar o endereço de uma variável e acessá-la indiretamente.
Exemplo (Linux):
global _start
section .data
nums dq 12, 13, 14, 15, 16
section .text
_start:
mov rbx, nums ; RBX = endereço de nums
mov rdi, [rbx] ; RDI = 12
mov rax, 60
syscallNo Windows, o tipo de dado deve ser explicitado:
global _start
section .data
nums dq 12, 13, 14, 15, 16
section .text
_start:
mov rbx, nums
mov rax, qword [rbx]
retRegistrador Base com Deslocamento
É possível somar deslocamentos ao endereço armazenado no registrador base:
global _start
section .data
nums dq 12, 13, 14, 15, 16
section .text
_start:
mov rbx, nums
mov rdi, [rbx + 8] ; RDI = 13
mov rax, 60
syscallTambém é possível somar valores de registradores:
global _start
section .data
nums dq 12, 13, 14, 15, 16
section .text
_start:
mov rbx, 16
mov rdi, [nums + rbx] ; RDI = 14
mov rax, 60
syscallO resultado é o mesmo: o terceiro elemento (14) é acessado. Também é permitido somar um valor adicional:
mov rdi, [nums + rbx + 8] ; RDI = 15⚠️ Em Windows, quando há uma variável no deslocamento, é obrigatório o uso de rel:
mov rdi, [rel nums]Entretanto, não é permitido combinar rel com um registrador base:
mov rax, qword [rel nums + rbx] ; InválidoSe isso for necessário, deve-se carregar o endereço em um registrador base antes, ou ajustar os parâmetros do vinculador:
- ld (GCC):
--default-image-base-low - link.exe (Microsoft):
/LARGEADDRESSAWARE:NO
Registrador de Índice
O registrador de índice especifica um elemento dentro de uma estrutura de dados, como um array.
Exemplo (Linux):
global _start
section .data
nums db 12, 13, 14, 15, 16
section .text
_start:
mov rbx, nums
mov rsi, 2
movzx rdi, byte [rbx + rsi] ; RDI = 14
mov rax, 60
syscallO registrador RBX contém o endereço base e RSI contém o índice do elemento.
[rbx + rsi] soma o índice ao endereço base.
Como cada elemento ocupa 1 byte, rsi = 2 acessa o terceiro elemento (14).
Em Windows:
global _start
section .data
nums db 12, 13, 14, 15, 16
section .text
_start:
mov rbx, nums
mov rsi, 2
movzx rax, byte [rbx + rsi]
retPara tipos maiores, o índice deve ser multiplicado pelo tamanho de cada elemento, usando o fator de escala (scale):
global _start
section .data
nums dq 112, 113, 114, 115, 116
section .text
_start:
mov rbx, nums
mov rsi, 2
mov rdi, qword [rbx + rsi * 8] ; RDI = 114
mov rax, 60
syscallTambém é possível incluir um deslocamento adicional:
mov rdi, qword [rbx + rsi * 8 + 16] ; RDI = 116Ou somar o índice diretamente à variável:
mov rsi, 1
mov rdi, qword [nums + rsi * 8] ; RDI = 113Instrução LEA
A instrução LEA (Load Effective Address) é usada para carregar um endereço em um registrador.
Ela permite calcular endereços com a mesma fórmula [base + index * scale + offset].
Exemplo (Linux):
global _start
section .data
nums dq 112, 113, 114, 115, 116
section .text
_start:
lea rbx, [nums]
mov rdi, qword [rbx] ; RDI = 112
mov rax, 60
syscallEm Windows:
global _start
section .data
nums dq 112, 113, 114, 115, 116
section .text
_start:
lea rbx, [rel nums]
mov rax, qword [rbx]
retAlém de carregar endereços de variáveis, LEA pode ser usada para cálculos aritméticos:
global _start
section .text
_start:
mov rdi, 8
mov rcx, 5
lea rdi, [rdi + rcx * 2] ; RDI = 8 + 5*2 = 18
mov rax, 60
syscallResumo
- O nome de uma variável representa seu endereço.
- Colchetes
[ ]acessam o valor armazenado no endereço. - A forma geral de endereçamento é
[base + index * scale + offset]. base: registrador base;index: registrador de índice;scale: fator de multiplicação;offset: deslocamento.rel: acesso relativo ao registrador RIP (necessário no Windows 64 bits).LEA: carrega endereços e realiza cálculos de forma eficiente.