Atualizado: 18/10/2025

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

Chamada de Funções C/C++ no Windows em Assembly NASM

Ao chamar funções escritas em C ou C++ a partir de código Assembly no Windows, é necessário seguir as convenções definidas pelo Microsoft Windows ABI (Application Binary Interface). Esse conjunto de regras estabelece como os parâmetros são enviados para as funções e como os valores de retorno são tratados. Essas convenções diferem significativamente do padrão System V ABI utilizado no Linux.


Convenções do Microsoft Windows ABI

A função que faz a chamada envia os quatro primeiros parâmetros por registradores. Os parâmetros inteiros são passados nos registradores RCX, RDX, R8 e R9, nessa ordem. Quando os parâmetros são valores de ponto flutuante, eles são enviados pelos registradores XMM0, XMM1, XMM2 e XMM3, sendo também espelhados nos registradores correspondentes RCX, RDX, R8 e R9. Por exemplo, se o segundo parâmetro for um número de ponto flutuante, ele é colocado tanto em XMM1 quanto em RDX.

Os parâmetros a partir do quinto são passados pela pilha. Assim, o quinto parâmetro ocupa o endereço [RSP + 32], o sexto [RSP + 40] e assim por diante.

Quando a função possui parâmetros mistos — inteiros e de ponto flutuante —, cada um é colocado no registrador correspondente à sua posição. Por exemplo, considere a função em C/C++:

void someFunc(int a, double b, char *c, double d)

A disposição dos parâmetros será:

ParâmetroRegistrador(es) utilizados
a (int)RCX
b (double)XMM1 e RDX
c (char *)R8
d (double)XMM3 e R9

Todos os parâmetros são tratados como valores de 8 bytes.


Shadow Storage e Alinhamento da Pilha

A função que faz a chamada deve reservar 32 bytes na pilha para os parâmetros — mesmo que a função receba menos de quatro argumentos. Esse espaço é chamado de shadow storage (ou shadow space). Ele serve como área reservada que a função chamada pode usar para armazenar temporariamente os parâmetros, ou mesmo para variáveis locais.

De forma simplificada, pode-se imaginar que:

  • o primeiro parâmetro ocupa [RSP],
  • o segundo [RSP + 8],
  • o terceiro [RSP + 16],
  • o quarto [RSP + 24].

Antes de qualquer chamada, o registrador RSP (ponteiro de pilha) deve estar alinhado a 16 bytes — o que garante a conformidade com o ABI e o correto funcionamento de funções que dependem desse alinhamento.


Registros Voláteis e Não Voláteis

O Microsoft Windows ABI divide os registradores em dois grupos principais:

  • Voláteis (volatile): podem ser modificados livremente pelas funções chamadas. A função que faz a chamada não pode assumir que seus valores serão preservados. Incluem: RAX, RCX, RDX, R8, R9, R10, R11, e XMM0–XMM5 / YMM0–YMM5.
  • Não voláteis (nonvolatile): devem ser preservados entre chamadas. A função chamada deve salvar e restaurar esses registradores caso precise modificá-los. Incluem: RBX, RBP, RDI, RSI, RSP, R12–R15, e XMM6–XMM15 / YMM6–YMM16 (apenas a metade superior dos YMM6–YMM16 pode ser alterada).

O valor de retorno é colocado em RAX para números inteiros e em XMM0 para valores de ponto flutuante.


Ponto de Entrada

Assim como no Linux, o ponto de entrada do programa Assembly que usa funções C/C++ é a função main, e não o rótulo _start:

global main     ; função main - ponto de entrada
section .text
main:
    ; instruções da função main
    ret

A execução e finalização do programa ocorrem por meio da instrução ret. O código associado a _start é fornecido por bibliotecas do sistema, responsáveis por inicializar o ambiente de execução da linguagem C e chamar a função main. Após a conclusão, main retorna o controle ao código da biblioteca.


Exemplo: Chamada da Função printf no Windows

O exemplo a seguir mostra como chamar printf em um programa NASM no Windows:

global main     ; função main - ponto de entrada

extern printf   ; referência externa para printf

section .data
message db "Hello www.programicio.com", 10   ; string a ser exibida

section .text
main:
    sub rsp, 40             ; alinhamento de 16 bytes + 32 bytes de shadow space
    lea rcx, [rel message]  ; primeiro parâmetro de printf - endereço da string
    call printf
    add rsp, 40             ; restaura o valor original da pilha
    ret

Diferentemente do código no Linux, aqui o primeiro parâmetro é enviado em RCX, e não em RDI. A instrução lea com endereçamento relativo (rel) é usada para carregar o endereço da variável no registrador.

Antes da chamada de função, o código ajusta a pilha em 40 bytes — 32 bytes para o shadow storage e 8 bytes adicionais para o alinhamento de 16 bytes. Como o ponteiro da pilha (RSP) já está alinhado a 8 bytes no início da execução, o total de 40 bytes garante o alinhamento correto.


Compilação e Linkagem

Criação do arquivo objeto:

nasm -f win64 hello.asm -o hello.o

Se o GCC for utilizado como vinculador:

gcc hello.o -o hello.exe

Se for usado o link.exe do Microsoft Visual C++, é necessário incluir bibliotecas adicionais:

link hello.o legacy_stdio_definitions.lib msvcrt.lib /subsystem:console /out:hello2.exe

Essas bibliotecas fornecem as definições de entrada/saída e a implementação da printf. Outros vinculadores podem exigir parâmetros diferentes conforme a ferramenta utilizada.


Envio de Parâmetros para Funções C/C++

Exemplo com múltiplos parâmetros sendo passados para printf:

global main

extern printf

section .data
message db "Name: %s  Age: %u  Company: %s  Salary: %u", 10, 0
name db "Tom", 0
age dq 39
company db "www.programicio.com", 0
salary dq 1150

section .text
main:
    sub rsp, 40
    lea rcx, [rel message]     ; string de formatação
    lea rdx, [rel name]        ; primeiro argumento
    mov  r8,  [rel age]        ; segundo argumento
    lea r9,  [rel company]     ; terceiro argumento
    mov  r10, [rel salary]
    mov  qword [rsp+32], r10   ; quarto argumento (via pilha)
    call printf
    add rsp, 40
    ret

Nesse exemplo, printf recebe cinco parâmetros. Os quatro primeiros são passados nos registradores RCX, RDX, R8 e R9, enquanto o quinto argumento é colocado na pilha, no endereço [RSP + 32].


Resumo

  • O Microsoft Windows ABI define convenções próprias, diferentes do System V ABI do Linux.
  • Os quatro primeiros parâmetros são enviados por registradores (RCX, RDX, R8, R9).
  • O shadow space reserva 32 bytes na pilha, mesmo que a função receba menos parâmetros.
  • O ponteiro de pilha (RSP) deve estar alinhado a 16 bytes antes da chamada.
  • RAX é usado para retornos inteiros, XMM0 para valores de ponto flutuante.
  • Registradores voláteis podem ser alterados livremente; não voláteis devem ser salvos e restaurados.
  • Compilação pode ser feita com GCC ou link.exe, exigindo, neste último caso, as bibliotecas legacy_stdio_definitions.lib e msvcrt.lib.
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