Atualizado: 30/08/2025

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

A Instrução mov e a Cópia de Dados em Assembly NASM

A base de qualquer programa em Assembly é formada por instruções, que são os comandos que definem seu comportamento. A instrução mais fundamental e popular é a mov, responsável pela cópia de dados. De forma análoga ao operador de atribuição (=) em linguagens de programação de alto nível, sua função é copiar um valor de um local para outro. Estima-se que a mov componha entre 25% a 40% de todas as instruções de um programa típico.

Sua sintaxe é simples e direta:

mov destination, source

A instrução mov opera com dois operandos:

  • destination (destino): O local onde o dado será armazenado. Pode ser um registrador do processador ou um endereço na memória.
  • source (origem): A fonte do dado a ser copiado. Pode ser um registrador, um endereço de memória ou um valor imediato (um número constante).

Uma regra crucial é que destination e source não podem ser ambos endereços de memória na mesma instrução mov.

Por exemplo, para copiar o número 5 para o registrador rax:

mov rax, 5

E para copiar o valor contido em rax para o registrador rbx:

mov rax, 5
mov rbx, rax   ; Agora o valor de rbx é igual ao de rax, ou seja, 5

Verificando Valores de Forma Simples: O Código de Saída

Para quem está começando, exibir o conteúdo de um registrador na tela pode ser uma tarefa complexa que envolve chamadas de sistema. Uma alternativa mais simples para verificar se nossas operações estão corretas é usar o código de status de saída do programa. Quando um programa termina, ele retorna um número ao sistema operacional. Por convenção, 0 significa sucesso, enquanto outros valores podem indicar erros específicos. Podemos usar esse mecanismo para "exportar" o valor de um registrador e inspecioná-lo.

Exemplo em Linux

No Linux, o código de saída é passado através do registrador rdi antes de chamar a rotina de encerramento do sistema (syscall número 60).

; hello.asm para Linux
global _start           ; Torna o rótulo _start visível para o linker

section .text           ; Declaração da seção de código executável
_start:                 ; Ponto de entrada do programa
    mov rdi, 23         ; Armazena o valor 23 em rdi para ser o código de saída
    mov rax, 60         ; Número da chamada de sistema 'exit' no Linux
    syscall             ; Pede ao kernel para executar a chamada de sistema

Para compilar e verificar o resultado:

nasm -f elf64 hello.asm -o hello.o
ld -o hello hello.o
./hello
echo $?
23

O comando echo $? exibe o código de saída do último programa executado, que neste caso é 23.

Exemplo em Windows

No Windows, o código de saída é o valor contido no registrador rax no momento em que o programa termina. O encerramento pode ser feito com a instrução ret.

; hello.asm para Windows
global _start       ; Torna o rótulo _start visível para o linker

section .text       ; Declaração da seção de código executável
_start:             ; Ponto de entrada do programa
    mov rax, 23     ; Armazena o valor 23 em rax para ser o código de saída
    ret             ; Retorna o controle para o sistema operacional, finalizando o programa

Para compilar e verificar o resultado no terminal do Windows:

nasm -f win64 hello.asm -o hello.o
ld hello.o -o hello.exe
hello.exe
echo %ERRORLEVEL%
23

O comando echo %ERRORLEVEL% mostra o código de retorno, confirmando o valor que colocamos em rax.

A Regra de Ouro: O Tamanho dos Operandos

A instrução mov exige que os operandos de origem e destino tenham o mesmo tamanho. A arquitetura x86-64 oferece registradores de diferentes tamanhos (64, 32, 16 e 8 bits), e é fundamental respeitar essa correspondência.

  • 64 bits: rax, rbx, rcx, etc.
  • 32 bits: eax, ebx, ecx, etc. (correspondem à metade inferior dos registradores de 64 bits)
  • 16 bits: ax, bx, cx, etc.
  • 8 bits: al, ah, bl, bh, etc.

Operações como as abaixo são válidas, pois os tamanhos coincidem:

mov ecx, 525    ; O valor 525 cabe em um registrador de 32 bits (ecx)
mov eax, ecx    ; Válido: copia de um registrador de 32 bits para outro

No entanto, o código a seguir apresenta problemas:

mov cl, 525    ; Inválido: tenta mover 525 para um registrador de 8 bits (cl)
mov eax, cl    ; Inválido: tamanhos diferentes (32 bits e 8 bits)

Na primeira linha, o registrador cl tem 8 bits e só pode armazenar valores de 0 a 255. Tentar inserir 525 resultará em um aviso de overflow (warning: byte data exceeds bounds) durante a compilação. O NASM truncará o valor, armazenando apenas os 8 bits menos significativos (525 % 256 = 13), o que leva a um erro lógico no programa.

A segunda linha causará um erro de compilação (error: invalid combination of opcode and operands) porque os tamanhos dos operandos (eax de 32 bits e cl de 8 bits) são incompatíveis.

Superando a Limitação de Tamanho: movsx e movzx

E se for necessário mover um valor menor para um registrador maior? Para isso, existem duas instruções especiais que, além de copiar, estendem o valor para o tamanho do destino. A escolha entre elas depende se o número é tratado como um valor com ou sem sinal.

movsx (Move with Sign-Extend)

Esta instrução é usada para números com sinal. Ela copia o valor e preenche o espaço extra no destino repetindo o bit de sinal (o bit mais à esquerda) da origem. Isso preserva o valor numérico do dado (seja ele positivo ou negativo).

mov al, -1      ; Em 8 bits, -1 é 11111111 em binário. O bit de sinal é 1.
movsx rdi, al   ; Copia 'al' para 'rdi' e estende o bit de sinal (1).
                ; 'rdi' se tornará 0xFFFFFFFFFFFFFFFF, que é -1 em 64 bits.

movzx (Move with Zero-Extend)

Esta instrução é usada para números sem sinal. Ela copia o valor e preenche o espaço extra no destino com zeros.

mov al, 255     ; Em 8 bits, 255 é 11111111 em binário.
movzx rdi, al   ; Copia 'al' para 'rdi' e estende com zeros.
                ; 'rdi' se tornará 0x00000000000000FF, que é 255 em 64 bits.

Uma característica importante da arquitetura x86-64 é que qualquer operação que modifica um registrador de 32 bits (eax, ebx, etc.) automaticamente zera os 32 bits superiores do seu registrador de 64 bits correspondente (rax, rbx, etc.). Isso torna a instrução mov uma forma eficiente de fazer uma extensão com zeros de 32 para 64 bits.

mov rax, -1     ; rax agora é 0xFFFFFFFFFFFFFFFF
mov eax, eax    ; Esta operação em 'eax' zera os 32 bits superiores de 'rax'.
                ; O resultado é que 'rax' se torna 0x00000000FFFFFFFF.

Formatos Numéricos no NASM

Por padrão, o NASM interpreta números como decimais. No entanto, é possível especificar outros sistemas de numeração:

  • Binário: com o prefixo 0b ou o sufixo b.

    mov rdi, 0b1011  ; 11 em decimal
    mov rdi, 1011b   ; Também 11 em decimal
  • Hexadecimal: com o prefixo 0x ou o sufixo h.

    mov rdi, 0x1F    ; 31 em decimal
    mov rdi, 1Fh     ; Também 31 em decimal

    Importante: Se um número hexadecimal com sufixo h começa com uma letra (A-F), é obrigatório adicionar um 0 no início (ex: 0Fh) para que o assembler não o confunda com um nome de variável ou rótulo.


Resumo

  • A instrução mov é a operação fundamental para copiar dados entre registradores e memória.
  • Os operandos de mov devem sempre ter o mesmo tamanho para evitar erros de compilação.
  • Os códigos de saída do programa são uma técnica útil para inspecionar o conteúdo de registradores de forma simples.
  • Para mover dados para registradores maiores, as instruções movsx (com extensão de sinal) e movzx (com extensão de zero) são utilizadas.
  • O NASM suporta números em formato decimal, binário (0b... ou ...b) e hexadecimal (0x... ou ...h).
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