Salvando Registradores e Variáveis Durante Chamadas de Funções em Assembly NASM
Durante seus cálculos, uma função em Assembly pode mudar o conteúdo de vários registradores. Caso esses valores ainda sejam necessários no código principal, é preciso salvá-los para garantir o resultado correto do programa.
Considere o seguinte exemplo em Linux:
global _start
section .text
_start:
mov rdi, 125
mov rax, 15
call sum
add rdi, rax ; RDI = 25
mov rax, 60
syscall
; definição da função sum
sum:
mov rdi, 5
mov rax, 10
add rax, rdi
retA função sum redefine os registradores rdi e rax. Após sua execução, o valor original de rdi (125) é perdido, e o resultado final em rdi é 20 em vez de 140.
Embora seja possível escolher outros registradores, funções reais tendem a usar vários deles simultaneamente. Além disso, o programador pode não saber quais registradores o código que faz a chamada da função utiliza.
Por isso, é uma boa prática — e em muitos casos uma necessidade — salvar os registradores usados pela função na pilha antes de modificá-los e restaurá-los antes do retorno.
Salvando e Restaurando Registradores
global _start
section .text
_start:
mov rdi, 125
call sum
add rdi, rax ; RDI = 140
mov rax, 60
syscall
; definição da função sum
sum:
push rdi ; salva RDI na pilha
mov rdi, 5
mov rax, 10
add rax, rdi
pop rdi ; restaura RDI da pilha
retAqui, o valor original de rdi é preservado. No início da função sum, ele é salvo na pilha e, no final, restaurado. O registrador rax não é salvo, pois é usado para armazenar o resultado que será retornado ao código principal.
Salvando Registradores no Código que Faz a Chamada
Também é possível realizar a preservação dos registradores no código que faz a chamada, em vez de dentro da função:
global _start
section .text
_start:
mov rdi, 125
push rdi ; salva RDI
call sum
pop rdi ; restaura RDI
add rdi, rax ; RDI = 140
mov rax, 60
syscall
; definição da função sum
sum:
mov rdi, 5
mov rax, 10
add rax, rdi
retEssa abordagem funciona, mas tende a ser menos eficiente se o programa fizer várias chamadas de função. Nesse caso, salvar e restaurar registradores em cada chamada aumentaria o tamanho e a complexidade do código principal.
Por isso, a prática mais comum é a própria função ser responsável por preservar os registradores que modifica.
Salvando e Restaurando Variáveis
A mesma lógica vale para variáveis globais. Se uma função altera uma variável usada em outro ponto do programa, é recomendável salvar seu valor antes da modificação e restaurá-lo em seguida.
Exemplo em Linux:
global _start
section .data
num dq 45
section .text
_start:
call sum
mov rdi, rax ; RDI = 25
add rdi, [num] ; RDI = 25 + 15 = 40
mov rax, 60
syscall
; definição da função sum
sum:
mov qword [num], 15 ; altera num
mov rax, 10
add rax, [num] ; RAX = 10 + 15 = 25
retO valor da variável num muda de 45 para 15 após a execução da função sum, o que pode gerar resultados inesperados.
Para evitar isso, a variável pode ser salva e restaurada usando a pilha:
global _start
section .data
num dq 45
section .text
_start:
call sum
mov rdi, rax ; RDI = 25
add rdi, [num] ; RDI = 25 + 45 = 70
mov rax, 60
syscall
; definição da função sum
sum:
push qword [num] ; salva num
mov qword [num], 15 ; altera num
mov rax, 10
add rax, [num] ; RAX = 10 + 15 = 25
pop qword [num] ; restaura num
retExemplo em Windows:
global _start
section .data
num dq 45
section .text
_start:
call sum
add rax, [rel num] ; RAX = 25 + 45 = 70
ret
; definição da função sum
sum:
push qword [rel num] ; salva num
mov qword [rel num], 15 ; altera num
mov rax, 10
add rax, [rel num] ; RAX = 10 + 15 = 25
pop qword [rel num] ; restaura num
retResumo
- Funções podem modificar registradores e variáveis usados pelo código que faz a chamada.
- Para evitar erros, registradores e variáveis importantes devem ser salvos antes da modificação e restaurados depois.
- A prática mais comum é a própria função ser responsável por preservar o que modifica.
- O registrador
raxgeralmente não é salvo, pois é usado para retornar o resultado ao código principal. - O mesmo princípio se aplica a variáveis globais.