Instruções assembly x86
Entendendo algumas instruções do Assembly x86
Até agora já foram explicados alguns dos conceitos principais da linguagem Assembly da arquitetura x86, agora que já entendemos como a base funciona precisamos nos munir de algumas instruções para poder fazer códigos mais complexos. Pensando nisso vou listar aqui algumas instruções e uma explicação bem básica de como utilizá-las.
Formato da instrução
Já expliquei a sintaxe de uma instrução no NASM mas não expliquei o formato em si da instrução no código de máquina. Para simplificar uma instrução pode ter os seguintes operandos:
- Um operando registrador 
- Um operando registrador OU operando na memória 
- Um operando imediato, que é um valor numérico que faz parte da instrução. 
Basicamente são três tipos de operandos: Um registrador, valor na memória e um valor imediato. Um exemplo de cada um para ilustrar sendo mostrado como o segundo operando de MOV:
mov eax, ebx      ; EBX   = Registrador
mov eax, [ebx]    ; [EBX] = Memória
mov eax, 65       ; 65    = Valor imediato
mov eax, "A"      ; "A"   = Valor imediato, mesmo que 65São três operandos diferentes e cada um deles é opcional, isto é, pode ou não ser utilizado pela instrução (opcional para a instrução e não para nós).
Repare que somente um dos operandos pode ser um valor na memória ou registrador, enquanto o outro é especificamente um registrador. É devido a isso que há a limitação de haver apenas um operando na memória, enquanto que o uso de dois operandos registradores é permitido.
Notação
As seguintes nomenclaturas serão utilizadas:
Nomenclatura
Significado
reg
Um operando registrador
r/m
Um operando registrador ou na memória
imm
Um operando imediato
addr
Denota um endereço, geralmente se usa um rótulo. Na prática é um valor imediato assim como o operando imediato.
Em alguns casos eu posso colocar um número junto a essa nomenclatura para especificar o tamanho do operando em bits. Por exemplo r/m16 indica um operando registrador/memória de 16 bits.
Em cada instrução irei apresentar a notação demonstrando cada combinação diferente de operandos que é possível utilizar. Lembrando que o operando destino é o mais à esquerda, enquanto que o operando fonte é o operando mais à direita.
MOV | Move
mov reg, r/m
mov reg, imm
mov r/m, reg
mov r/m, immCopia o valor do operando fonte para o operando destino.
destiny = source;ADD
add reg, r/m
add reg, imm
add r/m, reg
add r/m, immSoma o valor do operando destino com o valor do operando fonte, armazenando o resultado no próprio operando destino.
destiny = destiny + source;SUB | Subtract
sub reg, r/m
sub reg, imm
sub r/m, reg
sub r/m, immSubtrai o valor do operando destino com o valor do operando fonte.
destiny = destiny - source;
INC | Increment
inc r/mIncrementa o valor do operando destino em 1.
destiny++;DEC | Decrement
dec r/mDecrementa o valor do operando destino em 1.
destiny--;MUL | Multiplicate
mul r/mMultiplica uma parte do mapeamento de RAX pelo operando fonte passado. Com base no tamanho do operando uma parte diferente de RAX será multiplicada e o resultado armazenado em um registrador diferente.
Operando 1
Operando 2
Destino
AL
r/m8
AX
AX
r/m16
DX:AX
EAX
r/m32
EDX:EAX
RAX
r/m64
RDX:RAX
No caso por exemplo de DX:AX, os registradores de 16 bits são usados em conjunto para representar um valor de 32 bits. Onde DX armazena os 2 bytes mais significativos do valor e AX os 2 bytes menos significativos.
// Se operando de 8 bits
AX = AL * operand;
// Se operando de 16 bits
aux = AX * operand;
DX  = (aux & 0xffff0000) >> 16;
AX  = aux & 0x0000ffff;DIV | Divide
div r/mSeguindo uma premissa inversa de MUL, essa instrução faz a divisão de um valor pelo operando fonte passado e armazena o quociente e a sobra dessa divisão.
Operando 1
Operando 2
Destino quociente
Destino sobra
AX
r/m8
AL
AH
DX:AX
r/m16
AX
DX
EDX:EAX
r/m32
EAX
EDX
RDX:RAX
r/m64
RAX
RDX
// Se operando de 8 bits
AL = AX / operand;
AH = AX % operand;LEA | Load Effective Address
lea reg, memCalcula o endereço efetivo do operando fonte e armazena o resultado do cálculo no registrador destino. Ou seja, ao invés de ler o valor no endereço do operando na memória o próprio endereço resultante do cálculo de endereço será armazenado no registrador. Exemplo:
mov rbx, 5
lea rax, [rbx + 7]
; Aqui RAX teria o valor 12AND
and reg, r/m
and reg, imm
and r/m, reg
and r/m, immFaz uma operação E bit a bit nos operandos e armazena o resultado no operando destino.
destiny = destiny & source;OR
or reg, r/m
or reg, imm
or r/m, reg
or r/m, immFaz uma operação OU bit a bit nos operandos e armazena o resultado no operando destino.
destiny = destiny | source;XOR | Exclusive OR
xor reg, r/m
xor reg, imm
xor r/m, reg
xor r/m, immFaz uma operação OU Exclusivo bit a bit nos operandos e armazena o resultado no operando destino.
destiny = destiny ^ source;XCHG | Exchange
xchg reg, r/m
xchg r/m, regO operando 2 recebe o valor do operando 1 e o operando 1 recebe o valor anterior do operando 2. Fazendo assim uma troca nos valores dos dois operandos. Repare que diferente das instruções anteriores essa modifica também o valor do segundo operando.
auxiliary = destiny;
destiny   = source;
source    = auxiliary;XADD | Exchange and Add
xadd r/m, regO operando 2 recebe o valor do operando 1 e, em seguida, o operando 1 é somado com o valor anterior do operando 2. Basicamente preserva o valor anterior do operando 1 no operando 2 ao mesmo tempo que faz um ADD nele.
auxiliary = source;
source    = destiny;
destiny   = destiny + auxiliary;Essa instrução é equivalente a seguinte sequência de instruções:
xchg rax, rbx
add rax, rbxSHL | Shift Left
shl r/m
shl r/m, imm
shl r/m, CLFaz o deslocamento de bits do operando destino para a esquerda com base no número especificado no operando fonte. Se o operando fonte não é especificado então faz o shift left apenas 1 vez.
destiny = destiny << 1;       // Se: shl r/m
destiny = destiny << source; //  Nos outros casosSHR | Shift Right
shr r/m
shr r/m, imm
shr r/m, CLMesmo caso que SHL porém faz o deslocamento de bits para a direita.
destiny = destiny >> 1;       // Se: shr r/m
destiny = destiny >> source; //  Nos outros casosCMP | Compare
cmp r/m, imm
cmp r/m, reg
cmp reg, r/mCompara o valor dos dois operandos e define RFLAGS de acordo.
RFLAGS = compare(operand1, operand2);SETcc | Set byte if condition
SETcc r/m8Define o valor do operando de 8 bits para 1 ou 0 dependendo se a condição for atendida (1) ou não (0). Assim como no caso dos jumps condicionais, o 'cc' aqui denota uma sigla para uma condição. Cuja a condição pode ser uma das mesmas utilizadas nos jumps. Exemplo:
sete al
; Se RFLAGS indica um valor igual: AL = 1. Se não: AL = 0
if (verify_rflags(condition) == true)
{
  destiny = 1;
}
else
{
  destiny = 0;
}CMOVcc | Conditional Move
CMOVcc reg, r/mBasicamente uma instrução MOV condicional. Só irá definir o valor do operando destino caso a condição seja atendida.
if (verify_rflags(condition) == true)
{
  destiny = source;
}NEG | Negate
neg r/mInverte o sinal do valor numérico do operando.
destiny = -destiny;NOT
not r/mFaz uma operação NÃO bit a bit no operando.
destiny = ~destiny;MOVSB/MOVSW/MOVSD/MOVSQ | Move String
movsb  ; byte        (1 byte)
movsw  ; word        (2 bytes)
movsd  ; double word (4 bytes)
movsq  ; quad word   (8 bytes)Copia um valor do tamanho de um byte, word, double word ou quad word a partir do endereço apontado por RSI (Source Index) para o endereço apontado por RDI (Destiny Index). Depois disso incrementa o valor dos dois registradores com o tamanho em bytes do dado que foi movido.
// Se MOVSW
word [RDI] = word [RSI];
RDI        = RDI + 2;
RSI        = RSI + 2;CMPSB/CMPSW/CMPSD/CMPSQ | Compare String
cmpsb  ; byte        (1 byte)
cmpsw  ; word        (2 bytes)
cmpsd  ; double word (4 bytes)
cmpsq  ; quad word   (8 bytes)Compara os valores na memória apontados por RDI e RSI, depois incrementa os registradores com o tamanho em bytes do dado.
// CMPSW
RFLAGS = compare(word [RDI], word [RSI]);
RDI    = RDI + 2;
RSI    = RSI + 2;LODSB/LODSW/LODSD/LODSQ | Load String
lodsb  ; byte        (1 byte)
lodsw  ; word        (2 bytes)
lodsd  ; double word (4 bytes)
lodsq  ; quad word   (8 bytes)Copia o valor na memória apontado por RSI para uma parte do mapeamento de RAX equivalente ao tamanho do dado, e depois incrementa RSI com o tamanho do valor.
// LODSW
AX  = word [RSI];
RSI = RSI + 2;SCASB/SCASW/SCASD/SCASQ | Scan String
scasb  ; byte        (1 byte)
scasw  ; word        (2 bytes)
scasd  ; double word (4 bytes)
scasq  ; quad word   (8 bytes)Compara o valor em uma parte mapeada de RAX com o valor na memória apontado por RDI e depois incrementa RDI de acordo.
// SCASW
RFLAGS = compare(AX, word [RDI]);
RDI    = RDI + 2;STOSB/STOSW/STOSD/STODQ | Store String
stosb  ; byte        (1 byte)
stosw  ; word        (2 bytes)
stosd  ; double word (4 bytes)
stosq  ; quad word   (8 bytes)Copia o valor de uma parte mapeada de RAX e armazena na memória apontada por RDI, depois incrementa RDI de acordo.
// STOSW
word [RDI] = AX;
RDI        = RDI + 2;LOOP/LOOPE/LOOPNE
loop   addr8
loope  addr8
loopne addr8Essas instruções são utilizadas para gerar procedimentos de laço (loop) usando o registrador RCX como contador. Elas primeiro decrementam o valor de RCX e comparam o mesmo com o valor zero. Se RCX for diferente de zero a instrução faz um salto para o endereço passado como operando, senão o fluxo de código continua normalmente.
No caso de loope e loopne os sufixos indicam a condição de igual e não igual respectivamente. Ou seja, além da comparação do valor de RCX elas também verificam o valor de RFLAGS como uma condição extra.
// loop
RCX = RCX - 1;
if(RCX != 0)
{
  goto operand;
}
// loope
RCX = RCX - 1;
if(RCX != 0 && verify_rflags(EQUAL) == true)
{
  goto operand;
}
// loopne
RCX = RCX - 1;
if(RCX != 0 && verify_rflags(EQUAL) == false)
{
  goto operand;
}NOP | No Operation
nopNão faz nenhuma operação... Sério, não faz nada. Essa instrução normalmente é utilizada apenas como um "preenchimento" por compiladores afim de alinhar o endereço de código por motivos de otimização.
EAX = EAX;Last updated
Was this helpful?
