Tutorial de Assembly
Transcript of Tutorial de Assembly
Tutorial de Assembly
Aprenda Assembly (e não assembler) em 1 hora (pelo menos é o tempo que leva para ler este tutorial, ou 15 min. Com leitura dinâmica).
Introdução:
Assembly é uma linguagem que dá medo em muita gente, pois as instruções são completamente estranhas e diferentes de uma linguagem de alto nível, e mais ainda de uma linguagem visual. Nela você tem poucas funções básicas: mover, somar, subtrair, chamar interrupções etc.
Neste tutorial, eu estou assumindo que você já saiba códigos binários e um pouco de eletrônica digital; eu irei abordar temas como programação VGA e um pouco de interface com as portas seriais e paralelas.
Como este é o primeiro tutorial que estou escrevendo, será um texto para completos iniciantes no assunto, e depois, quem sabe eu escreva textos mais avançados. Mande-me e-mails com sugestões do que eu devo abordar nos próximos textos.
Vamos ao que interessa:
Em primeiro lugar, eu gostaria de esclarecer algumas coisas: Assembler é o compilador, montador, aquele que traduz para o código de máquina o seu código-fonte; Assembly é a linguagem de programação que todos vocês querem estudar.
Assembly é uma linguagem muito eficiente, veloz e que gera executáveis menores que as outras linguagens, pois nela você fala diretamente com a máquina, as instruções usadas são as do processador, por isso é sempre importante conseguir o manual do processador, ou algum texto com todas as instruções e comandos dele, isso você pode encontrar geralmente no site do fabricante.
Comparando com as linguagens de alto nível, no assembly você terá que digitar muito mais para resolver certos problemas e não terá a portabilidade que certas linguagens oferecem. Para resolver este último problema, sugiro sempre ter em mãos o manual de todos os processadores com o qual você quer que seu programa rode, assim como o dos Sistemas Operacionais, para converte-los.
Mas, com tanta dificuldade, porque aprender assembly então? Simples, as empresas de Hardware preferem profissionais que saibam assembly, pois eles terão uma visão melhor da máquina em si e de como programá-la; algumas empresas de Software, como as de jogos, levam isso como bônus, pois algumas soluções de problemas só podem ser feitos no assembly, e estes programadores terão uma maior lógica computacional.
Chega de papo furado: Que compilador eu uso?
Neste tutorial eu irei utilizar o TASM, pois é mais conhecido e mais usado, mas sugiro que aprenda também a usar o NASM (não o MASM!!!) pois é de graça e também muito bom.
Afinal, como funciona o Assembly?
O Assembly simplesmente se comunica diretamente com a memória e as portas do computador usando registradores. Registradores são nada mais que flip-flops usados para armazenar os dados, e passá-los à memória, basicamente os registradores são:
AX – accumulator (acumulador): utilizado em operações aritméticas e de I/O (entrada e saída).
BX – base (base): utilizado como base ou ponteiro.
CX – counter (contador): utilizado como contador, e em loops de repetição.
DX – displacement (repositor): igual ao BX, é utilizado para propósitos gerais
Estes segmentos podem ser usados sem restrições pelo programador, são os chamados de propósitos gerais, eles tem 16 bits que podem ser acessados também em duas partes de 8 bits, a parte alta (H) e a baixa (L). Ex.: AH e AL.
À partir dos 386 estes segmentos passaram a ser de 32 bits, sendo acessados com EAX, EBX, ECX, EDX (o E é de Extended, estendido).
Existem também os registradores de segmentos, eles são:
CS – code segment (segmento de código): ele contém o endereço do código que está atualmente sendo executado (não mexa nele).
DS – data segment (segmento de data): contém o endereço dos dados do programa (não mexa nele).
ES – extra segment (segmento extra): este segmento aponta para onde você quer que ele aponte, use-o como você quiser.
SS – stack segment (segmento de pilha, e não o exército alemão): nele está o endereço da pilha, que contém valores retornados de instruções.
Destes segmentos, você só poderá modificar realmente o ES, pois ele não aponta para nada importante para a execução do programa. Existe também o FS, que é parecido com o ES, e dizem ser mais rápido.
Agora eu vos apresento os registradores de ponteiros:
SI e DI (source index e destiny index): eles são usados em comandos que movem blocos de bytes de um lugar(SI) para outro(DI), são a fonte e o destino.
BP – base pointer (ponteiro base): usado geralmente em conjunto com SS, ele nos servirá para, por exemplo, passar argumentos para uma função de um modo bem rápido (e complicado), quando fizer operações com pilhas, use-o, pois dificilmente você fará besteira com ele.
SP – stack pointer (ponteiro da pilha): Ele aponta para o topo da pilha, não mexa com ele, à não ser que queira que o seu programa trave.
Mas me diga....o que são pilhas?
Considere pilhas do jeito que o próprio nome diz, pilha!!! K ? Imagine uma pilha de livros, você começando a empilha-los, primeiro você põe o livro de Eletrônica Digital no chão, depois você põe o livro de Algoritmos em cima dele, e então põe o de cálculo, e em seguida o de Eletromagnetismo, no dia seguinte o professor de Algoritmos pede para trazer o livro na próxima aula, bem, você terá que tirar os livros de Eletromagnetismo e de Cálculo para poder pegá-lo.....em Assembly a pilha funciona desse jeito também, ou seja, o primeiro que entra é o último que sai (neste caso vale aquele ditado: Os últimos serão os primeiros). Os comandos para pôr e tirar um registrador da pilha são respectivamente:
PUSH reg
POP reg
Ex.:1) PUSH AX ;salva o valor de AX
...códigos, e mais códigos....
POP AX ;retorna o valor de AX
2) PUSH AX
PUSH BX
...mais códigos...
POP BX ;primeiro retorna o valor de BX e então...
POP AX ;...o de AX
Oh sim! Quase me esqueço, para fazer algum comentário use o ";" .
Segmentos de Memória, que diabo é isso?
A memória do computador é divida em segmentos para facilitar (?) o seu acesso, isso vem dos XTs que tinham pouca memória, tinham na verdade uma vaga lembrança....
A memória foi divida em segmentos e os segmentos em offsets.
4BC2 : 378F
seg. off.
Os registradores usados para representar os segmentos e offsets foram ditos acima. Ex.: DS:DI.
Você entenderá isso melhor ao longo do curso.
Pare com essa coisa chata, vamos partir logo para a parte legal (?), os comandos.
Bom, vamos começar com alguns comandos básicos, e ao longo deste texto eu irei mostrando novos comandos.
MOV dest, origem
Move um valor para um registrador. Pode-se fazer os seguintes tipos de MOVs:
registrador à registrador
registrador à memória
registrador à registrador de segmentos
valor à registrador
memória à registrador
OBS.: registrador são os registradores AX, BX, CX e DX.
ADD dest, origem
Soma a origem com o destino e põe o resultado no destino. Segue a mesma regra do MOV, citado acima.
ADC dest, origem
Mesma coisa que ADD, só que com "vai um", modificando o "Carry Flag".
INT interrupção
Chama uma interrupção de propósitos gerais.
JUMP label
Pula a execução do programa para a posição do código que se encontra label.
Label: pode ser qualquer palavra, desde que não seja reservada pelo compilador (algum comando, etc.) seguido ":".
Os números em assembly seguem as seguintes notações:
decimal: apenas escreva o número naturalmente;
binário, octal, hexadecimal: ponha uma letra b, o, h no final do número.
OBS.: no hexadecimal, se o número começar com A, B, C, D, E ou F, ponha um zero na frente, assim A000h vira 0A000h.
Bom, vamos ao código...
Como primeiro programa, vamos fazer o tradicional programa de iniciação em todas as linguagens.....o famoso: HELLO WORLD!!!!
Digite o texto abaixo em um editor (e não processador) de texto, como o EDIT ou o Bloco de Notas, e rode a seguinte linha de programa: TASM <nome-do-arquivo>, depois digite TLINK <nome-do-arquivo>. E execute-o.
Hellow.asm
.MODEL SMALL ;modelo de memória
.STACK 200h ;aloca espaço na pilha
.DATA ;aqui começam os dados e declaração de
;variáveis
HelloWorld DB "Hello World$" ;string sempre termina com $
.CODE ;aqui começa o código do programa
START: ;e aqui a função principal
MOV DX, OFFSET HelloWorld ;movendo o OFFSET de HelloWorld
MOV AX, SEG HelloWorld ;movendo o segmento de HelloWorld
MOV DS, AX ;e pondo ele no DS
MOV AH, 9h ;número da função do MS-DOS
INT 21h ;chamada de função
MOV AX, 4c00h ;função de saída do programa
INT 21h
END START ;fim do programa
Explicação:
.MODEL declara o modelo de memória usado no programa, ele pode ser:
TINY: código e dados cabem em 64kb de memória.
SMALL: código e dados são menores que 64kb, mas estão em segmentos diferentes.
MEDIUM: código é maior que 64kb mas os dados são menores.
COMPACT: código é menor que 64kb e os dados são maiores.
LARGE: códigos e dados são maiores que 64kb, mas arrays não são.
HUGE: tudo pode ser maior que 64kb.
.STACK aloca espaço na pilha.
.DATA indica o começo do bloco de dados.
.CODE indica o começo do bloco de código.
START e END START indica onde começa e termina o código principal.
Interrupção 21h: são as mais usadas, pois são as chamadas de funções do MS-DOS.
Função 9h do INT 21h: esta função escreve na tela um string apontada por DS:DX, perceba que a string sempre termina com $, e veja também como foi passado os valores do segmento e offset da string, e por último perceba que não podemos passar diretamente o segmento para DS, como foi explicado acima.
OBS.: Podemos mover o segmento DATA da seguinte forma: MOV AX, @DATA.
Função 4c00h do INT 21h: esta função retorna para o MS-DOS.
Repare que as funções dos INTs são escritas no registrador AX.
Na declaração da String, você reparou naquela palavra DB? Pois ela significa que estamos declarando um variável BYTE (8 bits), apesar do tamanho dela ser 1 BYTE, no caso das strings, ela amplia seu valor até o código $ subdividindo a string.
Também podemos declarar variáveis DW (Word – 16 bits), DD (Double Word – 32 bits).
O que são ‘flags’
Flags são quase como registradores, mas que servem para indicar certos acontecimentos, na verdade ele é um registrador de 8 bits onde cada bit indica uma coisa.
SF – Signal Flag: 0 – positivo, 1 – negativo;
ZF – Zero Flag: 0 – não zero, 1 – zero;
AF – Auxiliary Flag: carry (vai um) no fim do reg. ? 0 – não 1 – sim;
PF – Parity Flag: 0 – ímpar, 1 – par;
CF – Carry Flag: vai um;
OF – Overflow Flag: ocorreu overflow;
DF – Direction Flag: direção nos comandos de bloco, 0 – normal, 1 – reverso;
IF – Interrupt Flag: 0 – não executa interrupções, 1 – executa interrupções;
TF – Trap Flag: usado para debulhação (debug) do programa.
Como e para que eu uso isso?
Flags são importantes para verificar erros nos dados, estouro de pilha, e comandos parecidos com "se..então".
Eles são usados com os comandos CLx e STx, onde x é a inicial do flag que você usará.
Ou com comandos derivados do JUMP:
Jx ou JNx , onde x é a inicial do flag (Ex.: JC, pula se o carry flag está ativado). São usados depois de operações como ADD e MOV.
JUMPS condicionais:
Substituem o "se então" de outras linguagens. São usados geralmente após o comando CMP.
CMP reg1, reg2
Compara dois registradores, em relação ao seus valores.
Os Jumps condicionais são os seguintes:
Jumps levando em conta o sinal:
JG – pula se reg1 for maior que reg2;
JGE – pula se reg1 for maior ou igual à reg2;
JNG – pula se reg1 não for maior à reg2;
JL – pula se reg1 for menor que reg2;
JLE – pula se reg1 for menor que reg2;
JNL – pula se reg1 for menor ou igual à reg2;
JE – pula se reg1 for igual à reg2;
JNE – pula se reg1 não for igual à reg2;
OBS.: quando os registradores forem iguais o ZF será ativado, portanto dizer JE e JZ é a mesma coisa.
Jumps condicionais sem levar o sinal em consideração:
JA – pula se reg1 for maior que reg2;
JAE – pula se reg1 for maior ou igual à reg2;
JNA – pula se reg1 não for maior à reg2;
JB – pula se reg1 for menor que reg2;
JBE – pula se reg1 for menor que reg2;
JNB – pula se reg1 for menor ou igual à reg2;
JE – pula se reg1 for igual à reg2;
JNE – pula se reg1 não for igual à reg2;
Vamos agora exemplificar tudo isso com um programa que faz uma pergunta e pega a reposta do teclado.
Pergunte.asm
.MODEL SMALL
.STACK 200h
.DATA
Pergunta DB "Voce gosta de estudar Eletromagnetismo?$"
Sim DB "Isso e bom!$"
Nao DB "Deveria...$"
.CODE
START:
MOV AX, @DATA
MOV DS, AX ;primeiro façamos com que DS aponte para os dados
DeNovo:
MOV DX, OFFSET Pergunta
MOV AH, 9h
INT 21h
MOV AH, 0h ;função 0 do INT 16h: pega um caractere do teclado
INT 16h ;e coloca em AX.
CMP AX, "S" ;vê se sim foi pressionado
JNE MauAluno ; se não for, vai para o label "MauAluno"
MOV AH, 9h
MOV DX, OFFSET Sim
INT 21h
MOV AX, 4c00h
INT 21h
MauAluno:
MOV AH, 9h
MOV DX, OFFSET Nao
INT 21h
JUMP DeNovo ;repete ate dizer sim
END START
Espero que tenha entendido este exemplo, pois a coisa vai começar a pegar agora que vamos falar de......
PROGRAMAÇÃO DA PLACA VGA
MODO GRÁFICO: 320x200
Primeiramente iremos trabalhar com o modo gráfico 320x200, e possivelmente eu escreverei tutoriais sobre o famoso modo X e, quem sabe, sobre programação SVGA e VESA.
Como eu mudo do modo de texto para o modo VGA?
Simples, usa-se interrupções. A interrupção de funções VGA é a 10h.
Para passar para modo 320x200:
INT 10h
Função 00h – mudar modo de vídeo
13h – modo 320x200
00h – modo texto
Ex.:
MOV AH, 00h
MOV AL, 13h
INT 10h ;muda para 320x200
MOV AL, 00h
INT 10h ;muda para modo texto
Podemos simplesmente escrever:
MOV AX, 13h
INT 10h
Já que MOV AX, 13h vai mover 00 para AH e 13h para AL.
Legal, agora eu estou em 320x200, mas o que eu faço com isso?
Calma! Um passo de cada vez...vamos aprender a plotar um pixel.
Lembre-se que o modo VGA permite utilizar 256 cores de uma vez (0 – 255), ou seja, utilizaremos apenas 8bits de um registrador para colocar a cor.
Poderíamos simplesmente utilizar-se de funções do INT 10h para isso, mas isso deixaria o programa realmente muito lento se tivéssemos que pintar a tela inteira (precisaríamos repetir 64000 vezes).
Existe um jeito mais fácil de faze-lo: acessando a memória de vídeo diretamente.
O segmento de memória do VGA está no endereço 0A000h, mas e daí? E daí que assim nós podemos escrever um pixel na tela apenas usando comando MOV, isso torna o programa muito mais rápido, no final deste texto eu colocarei todas as instruções do assembler com seus respectivos ‘ticks’ (cada período do clock).
Para plotar um pixel faremos assim:
MOV AX, 0A000h ; o segmento do VGA vai para um registrador
MOV ES, AX ; de segmento (ES)
MOV BX, 32160 ; plota na posição (159,99)
MOV DI, BX
MOV AL, 54 ;cor a ser plotada
MOV ES:[DI], AL ;[ ] significa que estamos movendo para o
;local de memória indicado por DI, e não
;para o registrador DI.
Mas como eu sei que 32160 é a posição (159,99)?
A memória VGA é tratada linearmente (veja a figura abaixo), portanto o offset 0 representa (0,0) o 1 (1,0) o 319 (319,0) e o 320 (0,1), lembrando que se começa a contar do 0,0.
Como então calcular onde plotar? Basta usar esta simples fórmula: x + (320 * y), fácil não?
Então façamos a nossa função PlotPixel:
PlotPixel PROC
ARG x:WORD, y:WORD, cor:BYTE
MOV AX, 0A000h
MOV ES, AX
MOV BX, [x]
MOV DI, BX
MOV AX, [y]
MUL 320
ADD DI, AX
MOV AL, [cor]
MOV ES:[DI], AL
RET
PlotPixel ENDP
Basicamente, é isso, como PlotPixel é um procedimento e não a função principal ela é definida por:
<nome-da-função> PROC
ARG argumento1:tipo, argumento2:tipo, ...
...
RET
<nome-da-função> ENDP
ARG é usado para declarar os argumentos, RET diz para voltar à função principal, tipo pode ser DBYTE, DWORD, DDOUBLE (8 bits, 16 bits, 32 bits).
Antes de prosseguir vamos dar uma palavra sobre otimização. Bem essa função é bastante rápida, e se você tem um computador acima de um 486 você vai achar isso realmente muito rápido, mas quando você está trabalhando com animações, ou atualização de tela constante, isso se tornará excessivamente lento, então o que devemos fazer?
Em primeiro lugar, vamos lembrar que o processador trata tudo como número binário, ou seja 0s e 1s, nesse caso o processo de multiplicação se torna realmente lento, mas existe duas funções que podem nos ajudar no processo de multiplicação e divisão, os chamados ‘shifts’. Os shifts simplesmente arrastam os bits de um registrador para direita ou para a esquerda. Esse processo é realmente rápido para o computador, uma vez que ele apenas move os bits. Mas mover os bits de um número binário para a esquerda é a mesma coisa que multiplicar por uma potência de dois, e para a direita é dividir por um potência de dois:
SHL reg, num
Move os bits para a esquerda num casas.
SHR reg, num
Move os bits para a direita num casas.
Ex.: SHR AX, 1 (move os bits uma casa à direita = dividir por 2)
SHL AX, 3 (move os bits 3 casas à esquerda = multiplicar por 23 = 8)
Você irá notar que 320 não é uma potência de 2, certo, mas 256 e 64 são. Sim, e daí? E daí que 256 + 64 = 320....ou seja, 256 * y + 64 * y = 320 * y.
Agora o código ficará assim:
PlotPixel PROC
ARG x:WORD, y:WORD, cor:BYTE
MOV AX, 0A000h
MOV ES, AX
MOV BX, [y]
MOV DI, BX
SHL BX, 8
SHL DI, 6
ADD DI, BX
MOV DX, [x]
ADD DI, DX
MOV AL, [cor]
MOV ES:[DI], AL
RET
PlotPixel ENDP
Assim ele já está bom o suficiente, existe outros métodos para deixá-lo ainda mais rápido, mas isso eu deixarei para vocês, por enquanto...
Retas Horizontais, Verticais e Diagonais
Para desenhar um reta horizontal é bem simples, basta repetirmos um plot varias vezes sempre indo para a próxima coluna até o final da linha.
PlotHorizontal PROC
ARG x1:WORD, x2:WORD, y:WORD, cor:BYTE
MOV AX, 0A000h
MOV ES, AX
MOV AX, [x1]
MOV CX, [x2]
CMP AX, CX
JNA Desenha
MOV BX, CX
MOV CX, AX
MOV AX, BX
Desenha:
SUB CX, AX
MOV BX, [y]
MOV DI, BX
SHL BX, 8
SHL DI, 6
ADD DI, BX
ADD DI, AX
MOV AX, [cor]
REP STOSB
RET
PlotHorizontal ENDP
Oba! Mais duas funções novas à serem consideradas...
REP função
Repete a função o número de vezes indicado no registrador CX, no caso da nossa função ele vai repetir a diferença x2-x1, que é o comprimento da linha.
STOSB
Copia o conteúdo de AL para o segmento de memória apontado por ES:DI.
STOSW
Copia o conteúdo de AX para o segmento de memória apontado por ES:DI
MOVSB
Move um byte do conteúdo apontado por DS:SI para o conteúdo apontado em ES:DI.
MOVSW
Move uma word do conteúdo apontado por DS:SI para o conteúdo apontado em ES:DI.
Para desenhar um linha vertical, é fácil, mas tem-se que considerar certas coisas, tente fazer este por si só e também uma função geral para as linhas. Eu deixarei as respostas com os devidos comentários nas mesmas páginas que se encontra este tutorial. A resposta não é tão diferente do procedimento descrito acima, apenas lembre-se de que a cada STOSB o registrador DI é acrescido de mais 1.
Eu também deixarei um pequeno programa de demonstração, que mostra todas estas funções em uso.
Esta parte de VGA irá se encerrar aqui, existem bons materiais espalhados pela net sobre o assunto, eu também escreverei um tutorial mais específico sobre o assunto.
Vamos agora com a comunicação serial e paralela.
O processador se comunica com os diversos periféricos através de portas de comunicação, um PC tem 65535 portas, comunicar-se com estas portas é uma tarefa simples, o difícil é saber o que faz cada uma dessas portas.
Os comandos para entrada e saída de comunicação são:
IN AL, porta ou IN AL, DX ou IN AX, DX
Este comando obtém dados da porta e coloca-os em AL ou AX, dependendo se você quer bytes ou words, geralmente usa-se o registrador DX para dizer qual o no da porta, embora possa-se dizer diretamente, se o no da porta for até 255.
OUT DX, AL ou OUT DX, AX
Este comando envia dados para a porta especificada por DX.
Esta é a base do interfaceamento, dependendo do que você quer fazer, ou do que a máquina que você estará ligando ao computador foi feita, você usará esses comandos do jeito mais apropriado.
As portas seriais são:
Porta: COM1 COM2 COM3 COM4
Endereço: 3F8h 3E8h 2F8h 2E8h
A porta paralela geralmente é 378h (LPT1).
Apêndice I: Instruções Assembly
Aqui eu colocarei os comandos mais importantes da linguagem Assembly com uma breve explicação e quantos ticks (ciclos de máquina) ele leva (para o 486).
ADC dest, fonte
Realiza uma soma com carry (vai um).
ADD dest, fonte
Realiza uma soma.
AND dest, fonte
Realiza um "e" lógico.
CALL function/macro
Chama um procedimento ou uma macro.
CLx, onde x é o flag
Limpa o flag correspondente (zera).
CMC
Muda o valor do carry flag. Se for 1 vira 0, se for 0 vira 1.
CMP dest, fonte
Compara os dois valores mudando os flags correspondentes.
CMPSx, onde x é B (byte), W (word), ou D (double).
Compara o byte/word/double no endereço DS:[SI] com o ES:[DI].
DEC dest
Decrementa de um o registrador.
DIV num
Calcula o quociente (sem sinal) da divisão de dest por fonte. Se for uma divisão de bytes, o dividendo estará em AX e o quociente e resto em AH e AL respectivamente. Se for uma divisão de words, o dividendo estará em DX:AX e o quociente e o resto em AX e DX.
IDIV num
Calcula a divisão com sinal.
IMUL num
Calcula a multiplicação com sinal.
IN AL, porta/DX
Pega dados de alguma porta.
INC reg
Incrementa em um o registrador.
INT interrupt
Chama uma interrupção.
JUMP label
Pula para o label correspondente no código fonte.
Jx label, onde x é alguma condição descrita anteriormente no tutorial.
Pula para o label correspondente se a condição for satisfeita.
LAHF
Põe os valores dos bits mais baixos dos flags em AH.
LEA dest, fonte
Calcula o offset e põe no registrador.
LODSx, onde x é B, W ou D.
Põe o valor de DS:[SI] em Al, AX ou DX:AX, dependendo da escolha.
LOOP/LOOPE/LOOPNE label
Repete o código compreendido entre o label e a instrução o número de vezes armazenado em CX. LOOPE e LOOPNE são loops condicionais, eles repetem verificando também se o zero flag está setado ou zerado.
MOV dest, fonte
Move o valor de fonte para dest.
MOVSx, onde x é B, W, D.
Move o byte, word ou double de DS:[SI] para ES:[DI].
MUL num
Multiplica AX ou AL por num e o resultado armazenado em DX:AX ou AX.
NEG dest
Nega dest (0 – dest).
NOP
Não faz nada (no operation – sem operação).
(o Windows provavelmente tem muitas desta instrução em seu código...J ).
NOT dest
Faz um "não" lógico.
OR dest, fonte
Faz um "ou" lógico.
OUT porta/DX, AX/AL
Põe um dado na porta especificada.
POP reg
Põe o valor no topo da pilha em reg.
PUSH reg
Põe o valor de reg no topo da pilha.
POPF/PUSHF
Põe o valor da pilha no flag / Põe o valor do flag na pilha.
REP instrução
Repete a instrução CX vezes.
RET
Retorna de uma sub-rotina.
SHR / SHL dest, num
Move os bits de dest para direita / esquerda num posições.
STx, onde x é um flag.
Seta (põe o valor 1) no flag.
STOSx, onde x é B, W, D.
Põe o valor de AL, AX ou DX:AX em ES:[DI].
SUB dest, fonte
Subtrai fonte de dest e põe o valor em dest.
TEST dest, fonte
Faz um "e" lógico, só que não põe o resultado no registrador <dest>, ele apenas muda os flags.
XCHG dest, fonte
Troca os valores entre os registradores.
XOR dest, fonte
Faz um "ou exclusivo" entre os registradores.