Definição e Chamada de Funções em Assembly NASM
As funções em Assembly permitem dividir um programa em partes menores chamadas sub-rotinas, cada uma responsável por executar um conjunto específico de instruções.
Na prática, uma função é um bloco de instruções associado a um rótulo, que serve como o nome da função. Toda função deve terminar com a instrução ret, responsável por devolver o controle para o ponto do código onde a função foi chamada.
Exemplo:
sum:
mov rdi, 7
mov rsi, 5
add rdi, rsi
retNeste caso, a função tem o nome sum. Ela apenas soma os valores armazenados nos registradores rdi e rsi, e o resultado fica em rdi.
A chamada de uma função é feita com a instrução call, seguida pelo nome da função:
call nome_da_funcaoA instrução call empilha no stack (pilha) o endereço de retorno — isto é, o endereço da próxima instrução após a chamada. Quando a função termina, a instrução ret recupera esse endereço do topo da pilha e transfere o controle de volta ao código que chamou a função.
Exemplo de Função em Linux:
global _start
section .text
_start:
call sum ; chama a função sum
mov rax, 60 ; syscall para encerrar o programa
syscall
; definição da função sum
sum:
mov rdi, 7
mov rsi, 5
add rdi, rsi
retExemplo de Função em Windows:
global _start
section .text
_start:
call sum ; chama a função sum
ret
; definição da função sum
sum:
mov rax, 7
mov rsi, 5
add rax, rsi
retFunções Chamando Outras Funções
Uma função pode chamar outras funções.
global _start
section .text
_start:
call sum
mov rax, 60
syscall
sum:
call set_rdi ; chama a função set_rdi
mov rsi, 5
add rdi, rsi
ret
set_rdi:
mov rdi, 3
retAqui, a função sum chama a função set_rdi, que define rdi com o valor 3 antes de a soma ocorrer.
A Pilha e as Funções
Ao chamar uma função, a instrução call empilha o endereço de retorno. No final, ret deve encontrar esse endereço no topo da pilha. Se a pilha for manipulada incorretamente, o programa pode apresentar comportamento indefinido ou falhar.
Veja um exemplo incorreto:
global _start
section .text
_start:
mov rdi, 3
mov rsi, 9
call sum
mov rax, 60
syscall
sum:
push rsi ; salva RSI na pilha
add rdi, rsi
retO problema é que o valor empilhado não foi removido antes da instrução ret. Assim, o endereço de retorno não estará mais no topo da pilha, resultando em erro de execução (como “Segmentation fault” no Linux).
Outro erro comum é retirar o endereço de retorno da pilha antes da instrução ret:
global _start
section .text
_start:
mov rdi, 3
mov rsi, 9
call sum
mov rax, 60
syscall
sum:
pop rsi ; remove o endereço de retorno da pilha
add rdi, rsi
retAqui, o endereço de retorno foi acidentalmente retirado com pop rsi. Assim, ret tentará retornar para um endereço inválido, resultando novamente em falha.
Portanto, uma função deve restaurar a pilha exatamente ao estado anterior à chamada, removendo apenas o que foi empilhado dentro dela.
Uso Direto do Endereço de Retorno
Também é possível acessar diretamente o endereço de retorno armazenado na pilha.
global _start
section .text
_start:
mov rdi, 5
mov rsi, 20
call sum
add rdi, 10 ; RDI = 15
mov rax, 60
syscall
sum:
jmp [rsp] ; salta para o endereço armazenado no topo da pilha
add rdi, rsi ; esta linha não será executada
retA instrução jmp [rsp] faz um salto para o endereço que está no topo da pilha, que corresponde ao ponto imediatamente após a chamada de sum (ou seja, a instrução add rdi, 10).
Resumo
- Uma função é um bloco de instruções identificado por um rótulo.
- A instrução
callchama a função e empilha o endereço de retorno. - A instrução
retrestaura o controle ao código que chamou a função. - Alterar indevidamente o conteúdo da pilha pode causar falhas.
- O endereço de retorno pode ser acessado diretamente, mas isso exige cuidado.