Atualizado: 18/10/2025

Este conteúdo é original e não foi gerado por inteligência artificial.

Frame de Pilha e Variáveis Locais em Assembly NASM

Quando uma função é chamada, ela pode precisar armazenar dados temporários — como variáveis locais, parâmetros e endereços de retorno. Essas informações são organizadas em uma estrutura chamada frame de pilha (stack frame), que representa a área da pilha reservada exclusivamente para a função em execução.

Na arquitetura x86-64, o registrador RBP (base pointer) é usado como referência para acessar o frame da função atual. Esse registrador marca o ponto de base da pilha, a partir do qual os parâmetros, variáveis locais e endereços podem ser acessados por deslocamento.


Estrutura do Frame de Pilha

A criação do frame começa no código que realiza a chamada da função:

  • A instrução call empilha o endereço de retorno.
  • Dentro da função chamada, o restante do frame é configurado, incluindo a área para variáveis locais.

Embora o Assembly não tenha o conceito de variáveis locais como nas linguagens de alto nível, é possível reservar espaço na pilha para dados que existirão apenas durante a execução da função. Quando a função termina, essa área é liberada automaticamente.

Exemplo Linux:

global _start

section .text
_start:
    mov rdi, 11
    call sum         ; após a chamada, RAX contém o resultado
    mov rdi, rax
    mov rax, 60
    syscall

sum:
    push 5           ; cria uma variável local (valor 5)
    mov rax, rdi
    add rax, [rsp]   ; soma RDI + 5
    add rsp, 8       ; libera o espaço da pilha
    ret

Aqui, o valor 5 é tratado como uma variável local criada dentro da função sum. A instrução push empilha o valor, ocupando 8 bytes, e o registrador RSP passa a apontar para esse endereço. O endereço de retorno fica em [rsp+8].

Essa variável local também pode ser alterada dentro da função:

sum:
    push 5
    add byte [rsp], 2   ; aumenta a variável local de 5 para 7
    mov rax, rdi
    add rax, [rsp]
    add rsp, 8
    ret

Aqui usamos add byte [rsp], 2, pois o valor cabe em um único byte.

Armazenando Parâmetros como Variáveis Locais

É comum salvar parâmetros recebidos em registradores dentro da pilha, liberando os registradores para outros cálculos.

Exemplo em Linux:

global _start

section .text
_start:
    mov rdi, 11
    call sum
    mov rdi, rax
    mov rax, 60
    syscall

sum:
    sub rsp, 8                ; reserva 8 bytes (duas variáveis locais)
    mov dword [rsp+4], 5      ; primeira variável = 5
    mov dword [rsp], edi      ; segunda variável = valor de EDI (parâmetro)

    mov eax, [rsp+4]
    add eax, [rsp]

    add rsp, 8
    ret

As duas variáveis ocupam 8 bytes no total (4 bytes cada). Elas são acessadas por meio dos deslocamentos [rsp] e [rsp+4].

Versão equivalente em Windows:

global _start

section .text
_start:
    mov rcx, 11
    call sum
    ret

sum:
    sub rsp, 8
    mov dword [rsp+4], 5
    mov dword [rsp], ecx
    mov eax, [rsp+4]
    add eax, [rsp]
    add rsp, 8
    ret

Nomeando Variáveis Locais

Para tornar o código mais legível e evitar confusão com deslocamentos numéricos, é possível usar constantes simbólicas:

global _start

_a equ 4
_b equ 0

section .text
_start:
    mov rdi, 12
    call sum
    mov rdi, rax
    mov rax, 60
    syscall

sum:
    sub rsp, 8
    mov dword [rsp+_a], 5
    mov dword [rsp+_b], edi
    mov eax, [rsp+_a]
    add eax, [rsp+_b]
    add rsp, 8
    ret

As constantes _a e _b representam os deslocamentos dentro da pilha, substituindo diretamente os valores 4 e 0. Essa abordagem facilita a manutenção e reduz erros.

Uso do Registrador RBP

O registrador RBP atua como uma âncora para o frame da função. Ele mantém o endereço base da pilha, permitindo acessar facilmente parâmetros e variáveis locais por deslocamento.

A sequência típica para criar e destruir um frame é:

push rbp        ; salva o RBP anterior
mov rbp, rsp    ; define o novo frame base
sub rsp, N      ; reserva N bytes para variáveis locais

E antes de retornar:

mov rsp, rbp
pop rbp
ret
  • Parâmetros enviados pela pilha são acessados com deslocamentos positivos ([rbp+16], [rbp+24], etc.).
  • Variáveis locais usam deslocamentos negativos ([rbp-8], [rbp-16], etc.).

Exemplo Linux:

global _start

section .text
_start:
    mov rdi, 11
    call sum
    mov rdi, rax
    mov rax, 60
    syscall

sum:
    push rbp
    mov rbp, rsp
    sub rsp, 16              ; duas variáveis locais (8 bytes cada)

    mov qword [rbp-8], 7
    mov qword [rbp-16], rdi

    mov rax, [rbp-8]
    add rax, [rbp-16]

    mov rsp, rbp
    pop rbp
    ret

Visualização do frame:

[rsp]     - segunda variável local  [rbp-16]
[rsp+8]   - primeira variável local [rbp-8]
[rbp]     - valor anterior de RBP
[rbp+8]   - endereço de retorno

Exemplo com Constantes Nomeadas

global _start

_a equ -8
_b equ -16

section .text
_start:
    mov rdi, 11
    call sum
    mov rdi, rax
    mov rax, 60
    syscall

sum:
    push rbp
    mov rbp, rsp
    sub rsp, 16

    mov qword [rbp+_a], 7
    mov qword [rbp+_b], rdi

    mov rax, [rbp+_a]
    add rax, [rbp+_b]

    mov rsp, rbp
    pop rbp
    ret

Versão Windows:

global _start

_a equ -8
_b equ -16

section .text
_start:
    mov rcx, 11
    call sum
    ret

sum:
    push rbp
    mov rbp, rsp
    sub rsp, 16

    mov qword [rbp+_a], 7
    mov qword [rbp+_b], rcx

    mov rax, [rbp+_a]
    add rax, [rbp+_b]

    mov rsp, rbp
    pop rbp
    ret

Instruções enter e leave

Como a configuração do frame com rbp é muito comum, o NASM fornece as instruções enter e leave para simplificar o processo.

Substitui-se:

push rbp
mov rbp, rsp
sub rsp, N

por:

enter N, 0

E, no final da função:

mov rsp, rbp
pop rbp

por:

leave

Exemplo com enter e leave:

global _start

_a equ -8
_b equ -16

section .text
_start:
    mov rdi, 12
    call sum
    mov rdi, rax
    mov rax, 60
    syscall

sum:
    enter 16, 0       ; cria o frame e reserva 16 bytes

    mov qword [rbp+_a], 7
    mov qword [rbp+_b], rdi

    mov rax, [rbp+_a]
    add rax, [rbp+_b]

    leave              ; restaura RSP e RBP
    ret

A instrução enter é funcionalmente prática, mas costuma ser mais lenta que sua sequência equivalente. Por isso, é raramente usada em código otimizado. Já leave é mais eficiente e amplamente utilizada.


Resumo

  • Cada função possui seu próprio frame de pilha, com variáveis locais, parâmetros e endereço de retorno.
  • O registrador RBP atua como referência base do frame.
  • Variáveis locais usam deslocamentos negativos, enquanto parâmetros usam positivos.
  • push e sub rsp reservam espaço na pilha; add rsp ou leave liberam-no.
  • As instruções enter e leave simplificam o gerenciamento do frame, sendo leave a mais comum na prática.
Política de Privacidade

Copyright © www.programicio.com Todos os direitos reservados

É proibida a reprodução do conteúdo desta página sem autorização prévia do autor.

Contato: programicio@gmail.com