Chamada de Funções Assembly a partir de C/C++
Assim como o código em Assembly pode chamar funções escritas em C ou C++, também é possível fazer o inverso: chamar funções implementadas em Assembly a partir de um programa C/C++. Nesses casos, aplicam-se as mesmas convenções de passagem de parâmetros e retorno de resultados definidas pelo ABI da plataforma (System V ABI no Linux e Microsoft Windows ABI no Windows).
Linux
Considere o seguinte arquivo sum.asm, que define uma função simples para somar dois números:
global sum
section .text
; Função retorna a soma de dois números
; Recebe dois parâmetros:
; rdi - primeiro número
; rsi - segundo número
; Resultado retornado em rax
sum:
mov rax, rdi
add rax, rsi
retA função recebe dois valores inteiros pelos registradores RDI e RSI e devolve o resultado em RAX. Para gerar o arquivo objeto:
nasm -f elf64 sum.asm -o sum.o
Agora, o programa principal em C — app.c — pode utilizar essa função:
#include <stdio.h>
int sum(int, int); // protótipo da função em Assembly
int main(void) {
int result = sum(4, 2); // chamada da função Assembly
printf("result = %d\n", result);
}O protótipo int sum(int, int) deve corresponder exatamente à assinatura da função em Assembly, garantindo compatibilidade de tipos e convenções de chamada.
Para compilar e vincular os arquivos:
gcc app.c sum.o -o app
Execução:
root@programicio:~/asm# nasm -f elf64 sum.asm -o sum.o root@programicio:~/asm# gcc app.c sum.o -o app root@programicio:~/asm# ./app result = 6
Windows
A versão equivalente em Windows muda apenas os registradores usados para o envio dos parâmetros, seguindo o Microsoft Windows ABI. O código Assembly em sum.asm fica assim:
global sum
section .text
; Função retorna a soma de dois números
; Recebe dois parâmetros:
; rcx - primeiro número
; rdx - segundo número
; Resultado retornado em rax
sum:
mov rax, rcx
add rax, rdx
retOs registradores RCX e RDX substituem RDI e RSI, conforme a convenção do Windows. Geração do arquivo objeto:
nasm -f win64 sum.asm -o sum.obj
O programa C permanece idêntico à versão do Linux:
#include <stdio.h>
int sum(int, int); // protótipo da função em Assembly
int main(void) {
int result = sum(4, 2); // chamada da função Assembly
printf("result = %d\n", result);
}A compilação depende do compilador utilizado:
Com GCC (MinGW):
gcc app.c sum.obj -o app.exe
Com Microsoft Visual C++ (cl.exe):
cl app.c sum.obj
Considerações Importantes
- A convenção de chamada deve ser idêntica nos dois lados — C/C++ e Assembly. Isso inclui a ordem dos parâmetros, registradores utilizados e tipo de retorno.
- As funções Assembly podem ser declaradas como
externem C, e os arquivos devem ser vinculados juntos na etapa de linkagem. - Tanto no Linux quanto no Windows, o nome exportado pela diretiva
global(comosum) precisa coincidir exatamente com o nome usado no protótipo C. - Para projetos maiores, é recomendável declarar as funções Assembly em um cabeçalho separado (
.h) para evitar inconsistências de protótipo.
Resumo
- C e C++ podem chamar funções Assembly da mesma forma que Assembly pode chamar funções C/C++.
- No Linux, parâmetros são passados por RDI, RSI, RDX, RCX, R8, R9 (System V ABI).
- No Windows, os quatro primeiros parâmetros usam RCX, RDX, R8, R9 (Microsoft Windows ABI).
- O resultado é retornado em RAX.
- É essencial que o protótipo em C corresponda à assinatura definida em Assembly.
- A compilação e linkagem unem os arquivos
.ce.o(ou.obj) no executável final.