END OF FILE – EBOOK ASM X86 Autor: kodo no kami ( fb/hacker.fts315 ) discord: kodo#0010 Forum: https://eofclub.in/forum Pagina: https://www.facebook.com/forumeof Discord: https://discordapp.com/invite/cmzeMPv (convite) Greetz: hefest0, sir.rafiki, s1m0n, neofito, 0x4xh4x, gjunnior, 0racle, lend4pop, refell, fascist, jhonydk, v4p0r, angley, hchild, onezer0, melots Data: 08/01/2017 ~ 25/08/2017 (sem revisão) E ae galera eu sou o kõdo no kami (コードの神) sendo esse o meu quinto ebook de programação e nele vamos aprender a programar na linguagem assembly, sendo essa linguagem uma linguagem complexa e chata de inicio devido ser uma linguagem de baixo nível (low level), como ela é uma linguagem de baixo level não fica limitada a um sistema operacional porém é possivel ser usada para programar para um determinado sistema operacional ou ate mesmo desenvolver um sistema operacional para alguma arquitetura especifica por ela, sua limitação seria não ser portável entre arquiteturas estando limitada para aquela arquitetura para qual foi criada. devido a linguagem assembly ser complexa e trabalhosa não é muito usada para fins comerciais sendo substituida por linguagenas de mais alto nivel que ela como a própria linguagem C, sendo ela uma linguagem mais usada para fins academicos para ensinar sobre a própria arquitetura da computação (asm é ensinado em areas mais especificas como ciencia e engenharia da computação, muito dificilmente ensinada em cursos de programação embora exista alguns inclusive voltados a eletronica ou que envolva programação a baixo level), o recomendado para aprender assembly é ter uma boa base em outra linguagem de programação principalmente na linguagem C. como nos ebooks anteriores (c/c++, pascal, perl e php) esse ebook não pode ser vendido sendo a sua distribuição livre sem fins lucrativos ^^ Indice 1.0 – Introdução ao Assembly 1.1 – Baixo e Alto nível 1.2 – Arquitetura CISC e RISC 1.3 – Sintaxe INTEL e AT&T 1.4 – Agrupamento de bits 1.5 – Little endian e Big endian 1.6 – Tipos de dados 1.7 – Compiladores 1.8 – Comentario 2.0 – Familia x86 2.1 – x86: Registradores 2.2 – x86: Flags 2.3 – x86: instrução de movimento 2.4 – x86: instrução aritmetica 2.5 – x86: instrução de pulo 2.6 – x86: Subrotina 2.7 – x86: Acesso a memoria 2.8 – x86: Interrupção 2.9 – x86: IO 3.0 – x86: FPU 3.1 – x86: MMX 3.2 – x86: 3DNow

3.3 – x86: SSE 4.0 – x86: Modo Protegido 4.1 – x86: libc 4.2 – x86: asm web (cgi) 4.3 – x86: asm inline 4.4 – x86: disassembler 5.0 – EOF 1.0 – Introdução ao Assembly assembly é uma linguagem de baixo nível considerada como linguagem de 2º geração sendo ela uma linguagem de montagem e sua primeira aparição foi por volta de 1949, sua compilação do código fonte para o executável final não tem tantas conversões e o seu código na maior parte das vezes são instruções equivalentes ao próprio código na linguagem da máquina, sendo essas instruções equivalente aos códigos binários em uma forma textual sendo chamados de instrução mnemônicos ou códigos mnemônicos, um executável em assembly é centenas de vezes menor que um executável em outras linguagens de alto nível, por outro lado o código fonte de um programa em assembly é bem maior do que outras linguagens em alto nível, a linguagem assembly não é uma linguagem portável entre arquiteturas já que está limitada aos opcodes e ao assembler de uma determinada arquitetura ou plataforma especifica, além do próprio compilador que pode influenciar a forma de escrita daquele código seja naquele sistema ou naquela arquitetura, a linguagem assembly pode ser usada tanto para programar para baixo nível para uma arquitetura especifica quanto para alto nível para um sistema operacional especifico, como também é possível ser usada para programar para web no lado do servidor de forma semelhante a linguagem PHP usando o seu executável criado em assembly como CGI naquele servidor web, alguns compiladores em linguagens de alto nível permite inserir códigos assembly de forma linear na própria linguagem com isso você pode programar em assembly diretamente naquela outra linguagem de programação, alguns compiladores em outras linguagens de programação gera primeiro o código em assembly para depois gerar o codigo na linguagem da máquina um bom exemplo disso é a linguagem C usado o próprio gcc que inclusive tambem é um compilador para a linguagem assembly 1.1 – Baixo e Alto nível sempre escutamos que determinada linguagem é alto nível ou baixo nível sendo algumas consideradas ate como médio nível embora esse termo alto e baixo nivel seja muito relativo, uma linguagem muitas vezes é considerada de baixo nível quando está mais perto da linguagem da máquina no caso do binário puro, já uma linguagem de alto nivel é considerada mais longe da linguagem da máquina podendo ser uma linguagem com uma abstração muito grande, uma linguagem de alto nível pode ser compilada sobre uma linguagem de baixo nivel como a linguagem C com o compilador gcc que gera um códigos em assembly antes do executável final, uma linguagem de alto nivel pode usar recursos do sistema como funções APIs alem de chamadas de sistemas enquanto uma linguagem de baixo nivel usa recursos direto da arquitetura e processador alem do próprio hardware, as linguagens de alto nivel são portaveis entre arquiteturas e sistemas, ja linguagens de baixo nivel não são portaveis entre arquiteturas (o printf na linguagem C vai ser sempre o mesmo independente da arquitetura ou sistema, ele sempre sera uma função para imprimir algo na tela, por outro lado a interrupção de vídeo 0x10 que permite imprimir algo na tela da arquitetura x86 só vai funcionar apenas nessa arquitetura e não em outra) 1.2 – Arquitetura CISC e RISC uma arquitetura é um padrão a ser seguido na criação de um computador, processador ou chip, levando em conta tanto o seu hardware quanto o seu software como também o seu firmware, as

arquiteturas padroniza aquele determinado modelo de máquina para que todas as demais máquinas que segue a mesma arquitetura seja fisicamente iguais e tenha as mesmas possibilidades lógicas como as mesmas instruções e recursos, em uma arquitetura com instrução CISC (Complex Instruction Set Computer) temos um conjunto de instruções complexas e de tamanho variável, a quantidade de tipos de instruções são grandes, a vantagem disso que o binário gerado pelo compilador é menor porém mais lento em sua execução, a quantidade de operando em uma instrução CISC é no máximo de dois, os computadores pessoais hoje que usa arquitetura x86/64 são CISC (embora meio hibrido), além das arquiteturas baseada em CISC tambem temos as arquiteturas baseada em RISC (Redunced Instruction Set Computer), o RISC é um conjunto de instruções reduzidas de forma simples, a quantidade de bytes por opcodes no RISC são fixos, o RISC tem uma quantidade pequena de instruções e com isso o binario gerado pelo compilador fica muito maior porém sua execução mais rapida já que são instruções e operações simples, RISC tem a possibilidade de executar mais de uma instrução por ciclo, esse tipo de instrução é muito usado para microcontroladores e chips como por exemplo a arquitetura do playstation que é a arquitetura MIPS sendo ela RISC 1.3 – Sintaxe INTEL e AT&T as sintaxes é uma forma do compilador enxerga o código ou toda estrutura lógica que compõe a linguagem naquele codigo, as sintaxes de uma linguagem em boa parte das vezes é independente da arquitetura e sistema ou ate mesmo do compilador, isso tornar um codigo em uma determinada linguagem sempre igual independente da arquitetura ou do sistema, porém na linguagem assembly pode variar muito a sua sintaxe dependendo não apenas da arquitetura ou do próprio sistema como tambem do proprio compilador, isso torna o mesmo codigo as vezes incompatível entre compiladores mesmo sendo para a mesma arquitetura. cada compilador assembly pode conter uma sintaxe única embora possa gerar o mesmo código binário ou equivalente para aquela determinada arquitetura ou sistema depois de ser compilado, existem duas sintaxes que são muito usadas em compiladores para arquitetura x86 sendo elas a sintaxe Intel e a sintaxe AT&T, muitos compiladores tende a seguir um desses dois tipos de padrões, como exemplo temos o nasm que usa sintaxe Intel e o gas usa sintaxe AT&T, uma das diferenças entre as duas sintaxes que na sintaxe Intel as operações são atribuidas da direita para esquerda enquanto que na sintaxe AT&T são da esquerda para direita sintaxe Intel Destino Origem sintaxe AT&T Origem Destino outra diferença que os numeros na sintaxe Intel são representado apenas com os numeros, já na sintaxe AT&T os numeros deve conter um cifrão indicando passagem imediata sintaxe Intel 315 sintaxe AT&T $315 os registradores na sintaxe Intel são apenas os nomes deles já na sintaxe AT&T deve conter o símbolo de porcentagem antes (não precisa se prender aos registradores por enquanto vamos abordar sobre eles mais para frente isso é apenas um exemplo)

sintaxe Intel eax sintaxe AT&T %eax o acesso indexado na sintaxe Intel devemos usar o colchete e dentro o registrador junto as operações, já na sintaxe AT&T usamos o valor e depois entre parênteses usamos o registrador sintaxe Intel [esp+8] sintaxe AT&T 8(%esp) como podemos ver existem diferenças entre a sintaxe Intel e a AT&T, com isso determinado compilador que use a sintaxe Intel não vai compilar um codigo na sintaxe AT&T ou vice-versa, mesmo com essa diferença entre as sintaxes é possivel converter de uma sintaxe para outra quando se trata da mesma arquitetura, outros compiladores de outras arquiteturas diferente pode ser um pouco diferente desse padrões ou ter alguma semelhança com eles, exemplo temos a arquitetura MIPS dependendo do compilador o cifrão é usado para indicar um registrador diferente do que ocorre na sintaxe AT&T que é usado o sinal de porcentagem, compilar asm para os microcontroladores PIC com o MPLab a origem e destino é da esquerda para direita enquanto compilar asm para microcontroladores AVR é da direita para esquerda 1.4 – Agrupamento de bits a máquina é capaz de acessar uma sequencia de bits de uma única vez de uma forma paralela, essa sequencia pode ser fixa ou relativa dependendo da arquitetura, muitas vezes a gente define uma determinada arquitetura por essa quantidade de leitura e escrita por barramento, um processador de em um computador pessoal hoje contem barramentos de 32 ou 64bits (arquitetura x86 e x64), em processadores mais antigos como o 8080 era barramento de 8bits em outros mais antigos ainda como 4040 era capaz de acessar apenas 4bits por vez por ter um barramento de apenas 4bits, hoje temos placas de videos com barramento de 128bits como tambem existem registradores no próprio processador com tamanho de 128bits mesmo sendo arquitetura de 32bits ou 64bits, existem muitas arquiteturas hoje que ainda usam 8bits como vários tipos de microcontroladores e chips, na informática é usado alguns nomes para definir a quantidade de dados manipulado por vez isso tambem é conhecido como palavra, os dados de 4bits são chamado de nibble, dados de 8 bits chamado de byte, dados de 16bits é word, os de 32bits é o dword, os de 64bits sendo os qword 1.5 – Little endian e Big endian alem do agrupamento de bits existe a ordenação do byte seja na memória do computador ou ate mesmo a sua passagem pelo barramento, no dia a dia a gente usa em nosso sistema numérico os digitos menos significando para direita e o mais significativo para esquerda (como exemplo podemos imaginar o numero 159, sendo que o numero 1 vale muito mais que o 5, e o numero 5 vale mais que o numero 9), por outro lado um computador pode fazer o inverso dependendo da sua arquitetura ou ate mesmo do sistema, o numero mais significativo pode ser o da direita e não o da esquerda dependendo da arquitetura (naquele exemplo do numero 159 seria o 951, sendo o 1 ainda maior que o numero 5 e o 9), sistemas onde o numero menos significativo fica para direita são chamado de little endian enquanto que sistemas que o numero menos significativo fica para

esquerda são chamado de big endian, os computadores usa agrupamento de pelo menos 16bits ou mais para aplicar esse conceito do little e big endian, sendo que a cada 8bits é a própria separação, então em um computador que tem o barramento de 16bits se a gente transferir o numero “0315” e ele usa little endian será enviado o numero “03 15”, em um computador que use big endian sera enviado o valor “15 03” sendo nos dois casos os mesmo numeros 1.6 – Tipos de dados dependendo da arquitetura ou ate da plataforma a linguagem assembly pode não ter tipos de dados específicos como ocorre nas linguagens de alto nivel onde temos tipos especificos de dados, em uma linguagem de alto nivel fortemente tipada criamos variaveis de tipos especificos como o tipo numerico inteiro em C para armazenar valor do mesmo tipo sendo o numerico inteiro, na linguagem assembly dependendo da arquitetura apenas temos que alocar aquela quantidade especifica de espaço na memoria que seria equivalente ao tamanho daquele tipo especifico de dado, em linguagens de alto nivel a propria declaração de uma variavel seria equivalente a alocar na memoria aquele determinado espaço para ser armazenado nele sendo que isso é trabalho do compilador de uma forma mais automatica, por outro lado em assembly as vezes precisamos criar esse determinado espaço de forma manual as vezes subtraindo o próprio endereço da pilha de memoria, porem isso não é uma regra dependendo da arquitetura em alguns casos ate mesmo do próprio compilador o assembly pode ter tipos específicos de dados, já em outros casos o tipo especifico de dado em si é apenas a palavra dele sendo o agrupamento de bits 1.7 – Compiladores os compiladores transforma o código da linguagem de programação para a linguagem da maquina, isso acontece em vários passos dependendo para qual sistema ele está gerando aquele código ou para qual arquitetura, em um compilador assembly essa conversão é muito simples apenas convertendo entre as instruções mnemônica da linguagem assembly para o código binário equivalente aquela determinada instrução, alguns compiladores para a linguagem assembly são gas – https://www.gnu.org/software/binutils/ (multi) nasm – http://www.nasm.us/ (x86) yasm – http://yasm.tortall.net/ (x86) flasm – https://flatassembler.net/ (x86) goasm – http://www.godevtool.com/ (x86) tasm – https://sourceforge.net/projects/guitasm8086/ (x86, z80) masm – http://www.masm32.com/ (x86) jwasm - https://sourceforge.net/projects/jwasm/ (x86) dependendo do sistema ou ate mesmo do compilador para gerar determinado tipo de arquivo binário ou executavel especifico é necessario alem do compilador tambem de um lincador (link), os lincadores pega o codigo objeto gerado pelo compilador junta com outros arquivos do sistema e monta o executável final para aquele sistema, um exemplo são os programas para windows da aquitetura x86 onde compilamos com o gas e depois lincamos como ld (esse mesmo passo é feito pelo próprio gcc quando compilamos em C porém isso é feito por trás dos panos) ld – https://www.gnu.org/software/binutils/ tlink – https://sourceforge.net/projects/guitasm8086/ golink – http://www.godevtool.com/ link – http://www.masm32.com/ jwlink - https://sourceforge.net/projects/jwlink/

alem dos compiladores tambem existem as IDEs que são ambientes de programação permitindo criar o codigo de forma mais simples embora o uso de IDE seja opcional podendo ser usado qualquer editor de texto de sua preferencia, boa parte das IDEs são apenas editores de texto com destacamento de sintaxe, exibição de erros e debugação, opções que facilita na hora da compilação as vezes com um compilador embutido, e outras opções (inclusive existem IDEs para programação grafica usando janelas em asm como easy code) GUI Tasm Editor – https://sourceforge.net/projects/guitasm8086/ Easy Code Visual Asm – http://www.easycode.cat/ qeditor – http://www.masm32.com/ notepad++ – https://notepad-plus-plus.org/download/v7.3.2.html geany – https://www.geany.org/ nano – https://www.nano-editor.org/ vim – http://www.vim.org/ emul8086 – http://www.emu8086.com/ (8086) quando compilamos um determinado codigo para uma outra arquitetura ou plataforma diferente esse processo é chamado de cross-compile, muitas vezes é bem mais simples ou necessario compilar o codigo em outra arquitetura e jogar naquela devido a sua limitação ou ate mesmo processamento para tal coisa, alem ser possível compilar o codigo para outro tipo de arquitetura em uma arquitetura diferente, também é possível rodar o executável de arquitetura ou plataforma diferente através de maquinas virtuais (vm), emuladores ou ate simuladores. uma maquina virtual emula um computador completo a nivel de arquitetura podendo inclusive instalar sistemas operacionais diferente de outra arquitetura, um emulador por outro lado tambem emula aquela arquitetura porém são bem mais especificas e limitadas já que emula apenas uma plataforma especifica como por exemplo um emulador de super nintendo que é possível rodar jogos de super nintendo porém não vai rodar um programa para o chip 65c816 diretamente no emulador, o simulador é bem parecido com o emulador a sua diferença que ele simula aquela arquitetura (um emulador tambem pode ser considerado um simulador ou vice versa dependendo do contexto) qemu – http://www.qemu-project.org/ (x86/64, arm, 68k, mips, sparc, s390, powerpc, outros) virtualbox – https://www.virtualbox.org/ (x86/64) vmware – https://www.vmware.com/ (x86/64) emul8086 – http://www.emu8086.com/ (8086) oshonsoft – http://www.oshonsoft.com/downloadspage.php (z80, pic, 8085, avr) mars – http://courses.missouristate.edu/KenVollmar/mars/ (mips) jubin 8085 simulator – https://8085simulator.codeplex.com/ (8085) gnu8085 – https://gnusim8085.github.io/ (8085) j51 – http://www.viara.eu/en/j51/ (8051) easy68k – http://www.easy68k.com/ (68k) exifpro 6502 simulator – http://exifpro.com/utils.html (6502) hercules – http://www.hercules-390.eu/ (system370, esa390, z/achiteture) simhv – http://simh.trailing-edge.com/ (pdp, s3, altair, vax, sds940, ibm, outros) dosbox – https://www.dosbox.com/ (x86: msdos) openmsx - https://openmsx.org/ (z80: msx) yaze-ag – http://www.mathematik.uni-ulm.de/users/ag/yaze-ag/ (z80: cp/m) vice – http://vice-emu.sourceforge.net/ (6502: C64/128, PET, CBM II, VIC20, PLUS/4) pce – http://www.hampa.ch/pce/ (msdos, pcdos, cp/m, mac, minix, xenix) tambem existem os disassembly e debuggers que são usados para dissecar um executavel de forma

estatica direto no binario ou de forma dinamica em tempo de execução objdump – https://www.gnu.org/software/binutils/ gdb – https://www.gnu.org/software/gdb/ ollydbg – http://www.ollydbg.de/ radare2 – http://rada.re/r/ ida – https://www.hex-rays.com/

1.8 – Comentario em boa parte das linguagens de programação (se não todas), é possível comentar determinado trecho no codigo, quando comentamos um determinado trecho, esse mesmo trecho de codigo sera ignorado pelo compilador, servindo apenas para o programador, sendo que os comentário podem ser usado para excluir temporariamente um determinado codigo, podem ser usado para destacar um trecho no codigo, ou podem ser usados mostrar alguma informação sobre o codigo ou quem o criou aquele codigo. os comentarios na linguagem assembly podem variar dependendo do compilador ou ate da arquitetura, normalmente os comentarios em asm em muitos compiladores são representado por ponto e virgula, quando o compilador encontra o ponto e virgula ele automaticamente ignora tudo depois dele ;isso é um comentario no compilador gas é usado duas vezes o barra //isso é um comentario é sempre bom comentar no começo do codigo com algumas informações como o autor, arquitetura, compiladores e etc ;autor: kodo no kami :arquitetura: x86 ;compilador: nasm podemos usar o comentário depois da instrução para informar o que exatamente ela faz mov eax,10 ;vai mover 10 para eax tambem podemos usar o comentário para excluir um determinado trecho de codigo ;mov eax,10 2.0 – Família x86 quando nos referimos a x86 estamos nos referindo a toda família de processadores que seguem o padrão x86, deis do antigo 8086 ate os mais atuais processadores x86 ou x64 (x86_64), o 8086 surgiu em 1978 e também era chamado de iAPX86 sendo ele um processador de 16bits que endereçava no máximo 1MB de memória devido ter barramento de 20bits de memoria e barramento de 16bits de dados, o 8086 é sucessor da arquitetura 8080 e 8085 com isso muitas de suas instruções foram mantida ou são parecidas embora não seja compativel entre essas arquiteturas, depois do 386

(80386) em 1989 os processadores x86 são de 32bits podendo endereçar ate 4GB de memoria em teoria, mesmo sendo o processador de 32bits para manter uma compatibilidade com maquinas e sistema antigos de 16bits o proprio computador inicia lendo apenas 1MB de memoria isso acontece ate hoje em computadores x86 atuais, em 2003 teve uma nova geração de processadores x86 sendo eles os de 64bits como o athlon64 e Opterom 2.1 – x86: Registradores os registradores são memorias internas dos processadores sendo uma das suas diferença a velocidade de acesso a ela, um processador acessa os dados nos registradores em uma velocidade superior as memórias no computador podendo ser ate milhares de vezes mais rapida que na memoria primaria, os registradores são usados para diversos fins sendo alguns para fins especificos como operações aritmética feita pelo processador, operações logica, contador de instrução, endereço da pilha de memoria, passagem de argumento para chamadas externas como interrupções ou retorno de subrotinas, na arquitetura x86 existem 4 registradores de uso geral que são chamados de AX, BX, CX e DX, embora esses registradores seja de uso geral eles também podem ser usados para fins específicos sendo o registrado AX usado como acumulador, o registrado BX como base, o registrador CX como contador e o registrado DX como dado, esses registradores são registradores de 16bits porem podemos usar eles apenas como 8bits sendo que sera dividido em dois novos registradores cada um de 8bits totalizando um único registrador de 16bits, o registrador AX sendo usado como registrador de 8bits são os registradores AH e AL os dois juntos forma o proprio regitrador AX sendo o AH o byte mais significativo do registrador AX e o AL o menos significativo dele, o mesmo vale para os outros registradores onde o BX é um registrador de 16bits porem pode ser acessado como registrador de 8bits com o BH e BL, o registrador CX de 16bits e CH e CL de 8bits, o DX com o DH e DL, então se a gente armazenar o valor 0315 no registrador AX o numero 03 estaria armazenado no AH e o 15 no AL AX 0315

AH 03

AL 15

alem dos registradores de uso geral existem os registradores apontadores como os registradores SI e DI, eles são usados como registradores de indice de dados sendo eles usados principalmente na manipulação de strings, o registrador SI seria a fonte e o DI o destino daquele dado, tambem temos dois registradores para manipulação da pilha de memoria que são os registrador SP e BP, sendo que o registrador SP aponta para o topo da pilha de memoria e o registrador BP é usado como base para correr aquele determinado trecho da pilha, outro registrador é o IP que aponta para execução daquela determinada instrução sendo o IP importante para o próprio processador saber onde sera a próxima instrução que sera executada, alem dos registradores gerais e apontadores tambem existem os de segmento que são usados em conjuntos com outros registradores entre esses regitradores temos o CS que aponta para o segmeto do codigo ele é usando em conjunto com o registrador IP (CS:IP) e indica onde esta o offset da memoria atual da instrução, o registrador de segmento DS seria segmento de dados e é usado em conjunto com o registrador SI (DS:SI) e DI (DS:DI), o registrador SS seria o registrador de segmento da pilha de memoria ele é usado em conjuto com o registrador SP (SS:SP) e BP (SS:BP), existem outros registradores de segmentos extras de dados como o ES, FS e GS e seu uso é parecido ao registrador de segmento DS, quando estamos no modo real a maquina só pode usar instruções e registradores de 16bits, quando passamo para modo protegido a maquina já pode usar instruções e registradores de 32bits sendo que esses registradores são os mesmos registradores de 16bits com tamanho de 32bits, para indicar que estamos manipulando registradores de 32bits usamos a letra “E” na frente do registrador sendo eles o EAX, EBX, ECX, EDX, ESP, EIP, EBP, ESI e EDI, para arquitetura x86 de 64bits podemos acessar os mesmo registradores em 64bits usando o R sendo eles RAX, RBX, RCX, RDX, RSI, RDI, RSP,

RBP e RIP 64bits (longo)

32bits (protegido)

16bits

8bits (H)

8bits (L)

rax

eax

ax

ah

al

rbx

ebx

bx

bh

bl

rcx

ecx

cx

ch

cl

rdx

edx

dx

dh

dl

rsi

esi

si

rdi

edi

di

rbp

ebp

bp

rsp

esp

sp

rip

eip

ip

na arquitetura de 64bits foram colocacados alguns novos registradores denominados de R, esses registradores são representados por uma numeração que vai de 0 ate 15, sendo que os numeros de 0 a 7 são os registradores já citados (rax, rcx, rdx, rbx, rsp, rbp, rsi rdi), os registradores de 8 a 15 não tem nomes especificos e são utilizados principalmente para passagem de argumentos em chamadas de sistema de 64bits (normalmente do R6 ate o R10), tambem é possível acessar uma quantidade de bits especificos nesses novos registradores colocando a letra equivalente ao seu bit no final dele, a sua representação seria o “d” para 32bits, “w” para 16bits e “l” para 8bits 64bits

32bits

16bits

8bits

r0 (rax)

r0d (eax)

r0w (ax)

r0l (al)

r1 (rcx)

r1d (ecx)

r1w (cx)

r1l (cl)

r2 (rdx)

r2d (edx)

r2w (dx)

r2l (dl)

r3 (rbx)

r3d (ebx)

r3w (bx)

r3l (bl)

r4 (rsp)

r4d (esp)

r4w (sp)

r5 (rbp)

r5d (ebp)

r5w (bp)

r6 (rsi)

r6d (esi)

r6w (si)

r7 (rdi)

r7d (edi)

r7w (di)

r8 ~ r15

outro tipo de registrador da arquitetura x86 são as flags que sinaliza alguma alteração durante a execução de uma determinada instrução e podem ser usadas em algumas instruções especificas como por exemplo comparação e pulos, esses registradores citados são os registradores padrões da arquitetura x86 existem outros registradores como os de FPU que são registradores usados para operações com pontos flutuantes na arquitetura x86 em cima de um coprocessador o x87 (8087) ou internamente a partir do 80486, alem de outros registradores como os do MMX, SSE e de controle como o CR 2.2 – x86: Flags as flags sinaliza alguma alteração feita por uma instrução sendo que algumas flags serve para o

controle, elas são constantemente utilizadas para operações aritmeticas pelo processador, diferente dos demais registradores citados as flags são apenas um unico registrador de 16bits que cada bit dele corresponde a uma determinada flag, nem todo bit do registrador flag é uma flag sendo alguns são apenas bits reservados para usos futuros e não tem utilidade nenhuma e outras flags usadas a partir de algum processador especifico como o IOPL e NT que são flags usadas por maquinas 286+, como cada bit é uma flag então a própria flag só pode assumir dois estados sendo o numero 0 ou 1, podemos ver abaixo as flags correspondente em uma tabela e sua ordem de bit sendo o menos significativo o da direita 15bit

0bit NT

IOPL IOPL OF

DF

IF

TF

SF

ZF

AF

PF

1

CF

a arquiteturas x86 de 32bits o registrador da flag é de 32bits ele tambem é chamado de eflag e tem algumas flags novas que não tem em arquitetura x86 de 16bits, em arquitetura de 64bits o registrador das flags (rflag) é de 64bits porem não tem nenhuma flag nova comparado ao de 32bits 32bit

0bit NT

IOPL IOPL OF

DF

IF

TF

SF

ZF

AF ID

VIP

VIF

PF

1

CF

AC

VM

RF

x64 reservado

a flag CF (Carry Flag) é setada em 1 quando acontece um estouro no limite maximo de um registrador em uma determinada operação sem sinal, como já sabemos os registradores tem um tamanho maximo em bits que pode ser armazenado naquele registrador, em registradores de 16bits só podem ser armazenados numeros de 0 ate 65535 (0 ate ffff em hexadecimal) que é equivalente a 16bits (256 ^ 2), se em um registrador estiver o numero 65535 e a gente somar mais 1 a ele vai acontecer esse estouro no registradores sendo o resultado o numero 0 e sera setado a flag CF em 1 indicando esse estouro, o mesmo acontece quando o numero no registrador é 0 e subtraimos ele em 1 sendo o resultado final 65535 e a flag CF sera 1, outra flag é o PF (Parity Flag) que é utilizada para paridade em determinadas operações indicando se o resultado do numero foi par ou impar, sendo setado em 1 caso seja par ou em 0 caso seja impar, a flag AF tambem chamada de Auxiliary carry Flag é paracida com a carry flag (CF) sua diferença que ela funciona apenas nos 4 primeiros bits sendo no nibble do registrador muito em numeros do tipo BCD, a flag ZF (Zero Flag) é setada em 1 quando a operação resultar no numero 0, a flag SF (Signal Flag) sera setada em 1 quando o resultado de uma operação for numero negativo exemplo a subtração do numero 2 pelo 5, a flag OF (Overflow Flag) é parecida com a flag CF a sua diferença que ela atua em cima de numeros com sinais enquanto no CF os numeros são sem sinais, em registradores de 16bits armazenando numeros sem sinais o numero que pode ser armazenado no registrador é entre 0 a 65535 já com sinal o numero é entre -32768 ate 32767, dependendo da operação pode ser setado varias flags ao mesmo tempo 1111111111111111 (65535) 1 (1) -------------------------- + 0 (0) flags setada = CF PF AF ZF 101 (5) 111 (7)

-------------------------- – 1111111111111110 (65534) flags setada = CF PF AF SF a flag de controle DF (Directional Flag) controla a direção que sera lido e armazenado pelos registradores SI e DI, quando ela esta setada em 0 a leitura e escrita é da esquerda para direita e quando esta setada em 1 sera da direita para esquerda tanto na fonte quanto no destino, a flag TF (Trap Flag) é uma flag de controle ela é usada para executar o codigo passo a passo quando acontece uma exceção é usada para debugação, a IF (Interrupt Flag) é uma flag de controle das interrupções sendo ela usada para habilitar ou desabilitar as interrupções mascaradas, a flag IOPL (I/O Privilege Level) é usada a partir do 286 sendo ela a flag de controle de nivel de permissão tanto do modo protegido quando do modo longo, a flag NT (Nested Task) é usada para tarefas do modo protegido a partir do 286, a flag RF só é valida a partir do 386 e ela é usada para desativar temporariamente a debugação, a flag VM (Virtual 8086 Mode) só existe no 386 a diante e é usada para executar programas de forma virtual como se estivesse em modo real só que dentro do modo protegido. diferente dos demais registradores não é possível acessar diretamente uma flag porem existem instruções para modificar determinadas flags, são poucas instruções que permite modificar flag entre elas temos stc e clc que modifica o estado da flag CF, a instrução sti e cli modifica o estado da flag IF, a instrução std e cld modifica o estado da flag DF Flag afetada

Seta em 1

Seta em 0

CF

stc

clc

IF

sti

cli

DF

std

cld

outra forma de modificar o estado das flags seria usar a pilha de memoria (stack), para fazer isso jogamos a flag na pilha com a instrução pushf ou pushfd depois modificamos o valor nele e depois bastaria jogar na flag novamente com popf ou popfd 2.3 – x86: instrução de movimento vamos sair um pouco da teoria e começar digitar alguns codigos nessa bagaça ;) , as primeira instruções que temos que aprender da linguagem assembly são as instruções de movimentos que permite atribuir valores aos registradores ou ate mesmo diretamente a memoria, uma dessas instruções de movimento na linguagem assembly da arquitetura x86 é a instrução mov, para a gente usar essa instrução temos que especificar o registrador e o valor que sera atribuido a ele ambos separados por virgula, se a gente atribuir um numero diretamente ao um registrador então estamos atribuido esse numero de forma imediata sendo que muitas arquiteturas não permite essa atribuição imediata principalmente as baseada em RISC, quando estamos usando um compilador que usa a sintaxe intel como o compilador nasm o registrador deve ser especificado antes do valor (instrução destino,fonte) mov ax,10 quando estamos usando um compilador que a sintaxe é at&t como o compilador gas o valor que sera atribuido vem antes do registrador (instrução fonte, destino), outra diferença que a sintaxe at&t usa o cifrão para indicar que é um numero imediato e tambem usamos o porcentagem para indicar que é um registrador mov $10,%ax

caso o sistema ou arquitetura permita usar registradores maiores, podemos atribuir da mesma forma que o exemplo anterior um valor a um registrador de 32bits ou 64bits mov rax,10 como o valor atribuido nos exemplos foram apenas o numero 10, e o próprio valor poderia ser armazenado em um registrador de apenas 8bits, nesse caso então podemos atribuir ele para um registrador de 8bits e depois o valor poderia ser lido normalmente no registrador 16bits ainda sendo o numero 10 (embora isso não seja muito recomendado) mov al,10 se no exemplo anterior a gente armazenar ele no registrado “ah” ao inves do registrador “al” o resultado no registrador “ax” seria outro valor e não o valor 10, isso porque o registrador “ah” fica o numero mais significativo daquele registrador (sendo o valor final lido no registrador “ax” o numero 2560 e não o numero 10) mov ah,10 o problema é quando tentamos atribuir valores maiores que o proprio limite daquele registrador, sendo que alguns compiladores não aceita retornando erro na compilação outros podem tratar o valor atribuido uma pequena parte dele, exemplo seria atribuir o valor 300 ao registrador al (8bits) não vai funcionar já que registradores de 8bits só permite numeros de 0 ate 255 (nesse caso teriamos que usar o registrador ax que é 16bits ou armazenar uma parte do valor no al e a outra no ah para totalizar o valor 300) mov al,300 podemos atribuir da mesma forma valores para outros registradores gerais (a,b,c,d), sendo todos esses registradores de uso geral e podem ser usados para qualquer fim tirando algumas exceções mov dx,500 na linguagem assembly usamos uma linha do codigo para cada nova instrução, ou seja a cada nova linha do nosso codigo é uma nova instrução (linhas vazias são ignoradas pelo compilador) mov ax,80 mov cx,60 mov dx,90 mov bx,10 se a gente atribuir mais de um valor ao registrador o valor antigo que estava armazenado nele sera substituido pelo novo valor mov eax,10 mov eax,50 dependendo do compilador o numero é tratado por padrão como decimal (base10) e em outros o

numero por padrão é hexadecimal (base16) como acontece com o debug do windows, alguns compiladores como o nasm para a gente manipular o numero como hexadecimal temos que especificar 0x antes do próprio numero indicando que é um numero hexadecimal mov bx,0xf5 outros compiladores para tratar o numero como hexadecimal usamos um h no final do numero (as vezes deve ser usado um 0 antes do numero), como acontece com o emu8086 e o nasm (sim o nasm pode usar as duas formas 0x e o h para indicar numeros hexadecimais) mov ax,0f5h alguns compiladores permite tratar numeros em binario (base2) com o 0b antes do numero mov ch,0b110001 como tambem existem compiladores que usa b no final do numero indicando o binario mov ch,110001b boa parte dos compiladores tambem permite enviar caracteres ascii entre aspas, sendo ele convertido automaticamente para o tipo numerico equivalente mov dx,'k' para numeros negativos basta a gente usar o sinal antes do numero mov ax,-10 o processador pode tratar tanto o numero com sinal ou numero sem sinal equivalente a ele, o numero -10 seria nada mais nada menos que o numero 0xfff6 sem sinal mov ax,0xfff6 podemos atribuir um valor de um registrador para outro registrador, bastando especificar ele no lugar do valor (o seu valor sera mantido e ambos os registradores tera o mesmo valor) mov ax,0x30 mov bx,ax lembrando que esses mesmos exemplos tambem vale para sintaxe at&t mov $0x30,%ax mov %ax,%bx quando precisamos manipular uma quantidade de bytes exatos ou especificar a quantidade bytes enviado então devemos usar algumas palavras reservadas do compilador indicando que estamos transferido aquela determinada quantidade de bytes, compiladores baseado em sintaxe intel a palavra deve ser usada em conjunto com o valor que sera transferido, já em sintaxe at&t é usado

uma letra em conjunto com a propria instrução, para transferir apenas byte devemos especificar a palavra byte mov ah, byte 10 na sintaxe at&t devemos usar o b em conjunto como o mov indicando a transferencia do byte movb $10,%ah para indicar que estamos manipulando 16bits usamos a palavra reservada word na sintaxe intel mov ax,word 0x1050 usamos o w em conjunto com a instrução mov na sintaxe at&t para o word movw $0x1050,%ax em 32bits usamos a palavra reservada dword na sintaxe intel mov eax,dword 0x12345678 na sintaxe at&t usamos o l para indicar o dword movl $0x12345678,%eax para 64bits usamos a palavra reservada qword na sintaxe intel mov rax,qword 0x123456789012345 no at&t usamos a letra q para o qword movq $0x123456789012345,%rax na sintaxe at&t existe uma instrução extra a movabs que seria equivalente ao movq movabs $0x123456789012345,%rax quando estamos mexendo com a arquitetura de 64bits podemos manipular os registradores extras sendo eles os registradores r8 ate r15 mov r8,315 o mesmo pode ser feito na sinataxe at&t mov $315,%r8 os registradores de r0 ate r7 dependendo do compilador deve ser usado com o nome padrão, no compilador nasm podemos usar eles declarando a diretiva “%use altreg” sendo que sem essa

diretiva o nasm não reconhece o r0 como rax %use altreg mov r0,100 outra instrução que podemos usar é o xchg que troca os valores entre dois registradores mov ax,100 mov bx,200 xchg ax,bx 2.4 – x86: instrução aritmetica tambem podemos fazer operações matematica usando a linguagem assembly, uma dessas operações é a adição que pode ser feita usando a instrução add, da mesma forma que a instrução mov temos que especificar o registrador que vamos utilizar e o valor que sera adicionado a ele, então se no registrador ax tiver o valor 300 e a gente adicionar mais 15 a ele, o valor final dentro do registrador vai ser 315 mov ax,300 add ax,15 o mesmo pode ser feito na sintaxe at&t porem mudando a ordem da fonte e destino mov $300,%ax add $15,%ax podemos fazer o mesmo usando dois registradores mov ax,300 mov bx,100 add ax,bx tambem é possível fazer varias operações em sequencia mov ax,300 add ax,15 add ax,20 add ax,8 operações com registradores maiores como os de 32 e 64bits mov ecx,5000 mov edx,1000 add ecx,edx é possivel fazer operações com bases diferentes tambem mov ax,0b1111

mov bx,0x50 add ax,bx a instrução mov não afeta nenhuma flag por outro lado a instrução add sim, então se em um operação der como resultado par a flag PF sera setada, se em uma operação for numero negativo a flag SF sera setada, se em uma operação der um estouro com um resultado maior que o próprio registrador a flag CF sera setada (AF ou ate OF dependendo) mov ax,0xffff mov bx,0x1 add ax,bx

; flag setada = C Z P A

outra operação é a de subtração com a instrução sub, o seu uso é parecido com a instrução add mov ax,50 sub ax,30 o mesmo pode ser feito na sintaxe at&t mov $50,%ax sub $30,%ax podemos subtrair um registrador de outro mov ax,60 mov cx,5 sub ax,cx tambem é possível subtrair por um numero maior sendo que o resultado sera negativo (alem de setar a flag SF e outras flags) mov ax,40 mov dx,60 sub ax,dx podemos fazer operações de multiplicação com a instrução mul, sendo uma das diferenças da instrução mul que obrigatoriamente deve ser usado o registrador ax, temos que passar outro registrador que sera multiplicado pelo registrador ax, o resultado tambem sera salvo no próprio registrador ax mov ax,15 mov bx,2 mul bx outra instrução que permite fazer a multiplicação é o imul, o uso dessa instrução é parecida com add e sub permitindo escolher o registrador e o valor mov bx,10 imul bx,6

tambem podemos multiplicar valores armazenado em outro registrador sem ser de forma imediata mov bx,10 mov ax,6 imul bx,ax outra forma de usar o imul seria especificar apenas um registrador como na instrução mul, sendo usado e armazenado obrigatoriamente no registrador ax mov ax,15 mov bx,2 imul bx outra diferença entre mul e imul que a instrução mul é uma multiplicação sem sinal enquanto o imul é uma multiplicação com sinal, em ambos os casos é usado o registrador dx como segmento em conjunto com o ax (dx:ax), caso o numero seja muito maior que o registrador ax sera armazenado a parte mais significativa dele no dx (tambem sera setado a flag CF caso isso ocorra com a instrução mul ou a flag OF na instrução imul) mov ax,65535 mov bx,1000 mul bx

; dx:ax = 65535000

tambem podemos fazer a divisão usando a instrução div e o seu uso é parecido com o mul, então armazenamos um valor no registrador ax depois fazemos a divisão de um outro registrador por ele sendo que sera armazenado no próprio registrador ax mov ax,100 mov bx,5 div bx tambem existe a instrução idiv que permite a gente escolher os dois registradores que vamos dividir mov bx,100 mov cx,5 idiv bx,cx da mesma forma que o imul podemos apenas especificar um registrador no idiv que e sera dividido e armazenado obrigatoriamente o registrador ax mov ax,100 mov bx,5 idiv bx o resto de uma divisão tambem chamado de modulo sera armazenado no registrador dx mov ax,10 mov bx,3

div bx

;ax = 3, dx = 1

não é possível fazer uma divisão onde o divisor é maior que o dividendo, o resultado final sera 0 mov ax,10 mov bx,12 div bx podemos incrementar um valor em um registrador com a instrução inc, seria equivalente a adicionar o valor em mais 1 mov ax,100 inc ax tambem podemos decrementar um valor em um registrador com o dec, essas instruções subtrair o registrador em menos 1 mov ax,100 dec ax a instrução adc faz a adição em conjunto com carry, o seu diferencial da instrução add seria se a flag CF estiver setada em 1 sera incrementado o registrador destino em 1 tambem mov ax,1 mov bx,1 stc adc ax,bx tambem existe a instrução sbb que faz a subtração em conjunto com o carry, o sbb alem de fazer a subtração como a instrução sub ele tambem decrementa o registrador destino em 1 caso a flag CF estiver setada em 1 mov ax,5 mov bx,2 stc sbb ax,bx é possivel fazer operações bit a bit em assembly entre elas temos a operação and, a operação and testa bit por bit entre os dois numeros formando um terceiro numero com base nos dois bits dos dois numeros, os bits do primeiro numero é testado com os bits do segundo na sua posição equivalente, se os dois bits do primeiro e do segundo forem iguais a 1 (verdadeiro) sera gerado um bit tambem igual a 1 verdadeiro, se os dois bits forem 0 (falso) o bit gerado sera 0, se os dois forem diferentes um do outro tambem sera falso, no assembly podemos fazer a operação usando a instrução com o mesmo nome no caso o “and”, seu funcionamento é parecido com as outras instruções como add, sub e imul com o diferencial que a operação feita sera a and mov ax,153 mov bx,247 and ax,bx

existe a operação bit a bit or, nessa operação se um dos bits for verdadeiro ou seja 1, o resultado sera um bit verdadeiro, se ambos forem verdadeiros o bit tambem sera verdadeiro, sera falso apenas quando os dois bits forem falsos ou seja quando ambos bits estiver setados em 0 mov ax,153 mov bx,247 or ax,bx a operação bit a bit xor ou “e exclusive” tera o bit verdadeiro se os dois bits forem diferente um do outro ou seja se em um numero o bit for verdadeiro e no outro numero o bit for falso mov ax,153 mov bx,247 xor ax,bx o xor é usado constantemente para zerar um determinado registrador usando ele proprio mov ax,153 xor ax,ax outro uso para o xor é a troca de valores entre dois registradores sendo igual a instrução xchg mov ax,153 mov bx,247 xor ax,bx xor bx,ax xor ax,bx

;bx = 153 ;ax = 247

outra operação bit a bit é o not ou negação que tambem é chamada de complemento de um, diferente das outras operações bit a bit essa funciona em apenas um único numero sendo os bits invertido, caso o bit seja verdadero sera falso como resultado ou se era falso sera verdadeiro mov ax,153 not ax outra forma de fazer a operação not é subtrair o maior numero aceito no registrador pelo numero que sera invertido, no caso dos registradores ax o maior numero é 65535 (0xffff) já que é um registrador de 16bits mov ax,65535 mov bx,153 sub ax,bx tambem existe a operação bit a bit neg que seria o complemento de dois, na operação neg é feito a operação not e somado mais um bit ao numero menos significativo dele mov ax,153

neg ax outra forma de fazer o complemento de dois como já citado é usar a operação not e adicionar mais um bit a ele isso seria equivalente a operação neg mov ax,153 not ax add ax,1b com a shl podemos usar a operação bit a bit shift left (deslocar para esquerda), essa operação deslocas os bits a esquerda adicionando o bit 0 a direita do numero, o seu uso é bem simples bastando especificar o registrador e quantidade de deslocamento a esquerda mov ax,15 shl ax,2 tambem podemos deslocar os bits a direita com a instrução shr, no deslocamento a direita o bit menos significativo sera perdido mov ax,15 shr ax,2 para deslocamento com sinal para esquerda usamos a instrução sal mov ax,15 sal ax,2 e para o deslocamento com sinal para direita usamos a instrução sar mov ax,15 sar ax,2 as vezes estamos trantando de um numero de 8bits (byte) e precisamos tratar desse mesmo numero como 16bits (word), o problema de fazer isso é quando estamos mexendo com numeros com sinais já que os bits mais significativo é aqueles que indica se o numero é positivo ou negativo, a função cbw converte o registrador al para 16bits formando o novo valor no registrador ax, dependendo do valor que esta no registrador al o valor de ah sera 0x0 caso o numero em al for de 0 a 127 sendo o numero final positivo, se o no registrador al estiver o numero entre 128 e 255 o valor em ah sera o numero 0xff indicando negativo no ax mov al,130 cbw tambem é possível converter de 16bits para 32bits usando cwd, a diferença que na instrução cwd sera usado todo o registrador ax e sera armazenado o restante no registrador dx, caso o valor for de 0 a 32767 sera positivo ou seja o numero armazenado em dx sera igual a 0, caso o valor seja de 32768 ate 65535 o numero sera 0xffff indicando o numero negativo em dx:ax mov ax,40000

cwd o mesmo pode ser feito no registrador de 32bits para 64bits usando a instrução cdq, a instrução cdq só existe em processadores a partir do 80386 mov eax,0x123456 cdq podemos converter um numero que esteja como formato caracter ascii para tipo numerico usando a instrução aaa mov ax,'5' aaa 2.5 – x86: instrução de pulo existem instruções que permite pular para determinada parte do codigo permitindo abstrair ou criar um determinado fluxo de execução, podemos pular caso ocorra determinada condição como uma flag esta setada ou ate mesmo um valor especifico em determinado registrador para que ocorra esse pulo sobre aquela determinada condição, uma instrução que não tem grande utilidade é a instrução nop (no operation), essa instrução pula para a próxima instrução não faz nada alem disso, ela apenas é usada para gastar ciclos de maquina sendo muito usadas para criar temporizadores como o delay nop podemos usar a instrução jmp para pular para um determinado endereço de codigo na memoria de forma incondicional, a instrução jmp é uma instrução absoluta ou seja vamos pular para aquele trecho absoluto da memoria que a gente especificou nela como endereço, quando passamos apenas o endereço para a instrução jmp o segmento é mantido no caso o registrador CS não sera afetado apenas o registrado IP jmp 0x200 o mesmo pode ser feito na sintaxe at&t porem para endereços não precisamos colocar o cifrão como acontece em valores imediatos jmp 0x200 tambem pode ser feito com um registrador ao inves de um endereço mov ax,0x200 jmp ax tambem é possível dar um pulo com a instrução jmp entre segmentos o famoso far jump, para fazer isso basta especificar o segmento seguido do offset onde vamos pular, quando é o far sera alterado tanto o registrador de segmento CS como o registrador IP jmp 0x710:0x100

;cs = 0x710, ip = 0x100

para a gente pular para um trecho especifico temos que usar uma label como referencia daquele determinado trecho na memoria, isso tambem seria uma especie de pulo relativo no codigo já que não precisamos saber o real endereço para onde temos que pular apenas o nome da label, para criar uma label escrevemos qualquer nome sendo que não pode conter espaços ou caracteres especiais e deve terminar com dois pontos kodo: na instrução jmp a gente especifica o nosso label que vamos pular jmp kodo kodo: como foi dito o jmp é um pulo incondicional então no exemplo abaixo nenhuma instrução depois do jmp e antes do label sera executada jmp kami mov ax,10 add ax,50 kami: quando pulamos para tras temos que tomar cuidado para não prender o programa em um loop infinito kami: jmp kami dependendo do nosso programas as vezes precisamos prender ele em um loop infinito, como por exemplo um contador infinito que o programa nunca termina mov ax,0 kami: inc ax jmp kami outra forma de fazer um pulo relativo em alguns compilador é usando o cifrão que indica a posição atual (lembrando que o $ é usado na sintaxe at&t indicando numero imediato), se a gente der um pulo para o cifrão seria um pulo para a própria instrução jmp ficando preso nela jmp $ alguns compiladores permite somar um valor ao cifrão indicando um pulo relativo a partir da posição atual jmp $ + 5 tambem é possível subtrair o valor do cifrão para dar um pulo para antes dele porem lembrando que nesse caso vai ficar preso em um loop infinito já que vai executar o jmp novamente e ficar voltando

jmp $ - 5 uma forma simples de contornar o problema anterior de voltar sem ficar preso em um loop seria alguma coisa parecida com isso (o mesmo poderia ser feito com label tambem) jmp inicio mov ax,1 jmp $ + 4 inicio: jmp $ - 5 add ax,1

;pula para o inicio usando o label ;pula para o add de forma relativa tambem ;pula para o mov, veja que pulamos de forma relativa aqui

temos que tomar cuidado quando usar o jmp para não ir em uma parte da memoria desconhecida que a gente não tenha criado nada nela ainda já que pode conter uma instrução aleatoria naquela parte inclusive instruções valida que sera executada pelo computador, para evitar isso pule apenas para endereços conhecidos ou com label que você tenha criado e se certifique que o programa vai ser finalizado corretamente dependendo do sistema ou ficar preso em um loop infinito no final da execução evitando executar instruções aleatorias da memoria (embora boa parte dos compiladores pra determinado sistema já corrige esse pequeno problema) mov ax,10 add ax,5 fim: jmp fim tambem existem os pulos condicionais que permite pular para determinado trecho caso seja satisfeita determinada condição ou se alguma determinada flag especifica estiver setada, entre esses pulos condicionais temos a instrução je (jump if equal) que permite pular caso a condição seja igual (para definir uma condição como igual a flag ZF deve esta setada com o valor 1 ou seja basta fazer uma operação que o resultado final seja zero) mov ax,5 xor ax,ax je igual mov ax,100 igual: mov ax,200 caso a flag ZF não estiver setada em 1 seria equivalente a uma condição diferente e nesse caso não aconteceria o pulo pelo je passando para próxima instrução depois dele mov ax,5 xor ax,3 je igual mov ax,100 igual: mov ax,200 se a gente der uma analisada no codigo anterior não faz tanto sentido, já que se o valor não for igual

ele não vai pular com isso o registrador ax recebe 100 só que em seguida o mesmo registrador ax recebe 200 apagando o valor anterior, para contonar isso damos um pulo incondicional para depois da instrução para evitar a segunda atribuição a ele mov ax,5 xor ax,3 je igual mov ax,100 jmp proximo igual: mov ax,200 proximo: tambem é possível pular de forma relativa ou para um endereço absoluto, o je do próximo exemplo seria equivalente ao do anteriror pulando ambos para o “mov ax,200” caso seja igual (ZF = 1) mov ax,5 xor ax,ax je $ + 7 mov ax,100 jmp proximo mov ax,200 proximo: como podemos ver nos exemplos anteriores a operação bit a bit xor é perfeita para comparar dois valores e saber se são iguais, isso acontece na operação xor devido os bits ser os mesmos com isso zerando todos eles e dando o resultado final 0 mov ax,315 mov bx,315 xor ax,bx je pulo pulo: o problema de usar o xor que ele altera o registrado usado perdendo o valor, outra instrução semelhante e ate especifica para esse caso é a instrução cmp usado para comparar dois valores sem afetar os registradores, o seu uso normalmente é feito antes dos pulos condicionais e ele afeta apenas as flags e a comparação é feita do lado do destino para a fonte (na sintaxe intel seria da esquerda para direita e na sintaxe at&t da direita para esquerda) mov ax,315 mov bx,315 cmp ax,bx je pulo pulo: outro tipo de pulo condicional é o jne (jump if not equal), sendo que essa instrução apenas vai pular para aquele trecho se os valores forem diferentes ou seja se a flag ZF estiver setado em 0, caso o numero seja igual mão vai pular continuando a execução do codigo depois da instrução

mov ax,100 mov bx,315 cmp ax,bx jne pulo pulo: tambem podemos comparar se o numero em questão é maior que o outro usando a instrução jg (jump if greater), caso o numero seja maior ele ira pular para o endereço na instrução jg caso não seja ira continuar a execução depois dela mov ax,200 mov bx,100 cmp ax,bx jg pulo pulo: na comparação entre o igual e diferente é usado a flag ZF indicando igual caso ela esteja setada em 1 ou diferente caso ela esteja setado em 0, na comparação para o maior usamos três flags sendo a SF e OF que devem estar setadas com o mesmo valor e a flag ZF deve esta setada com o valor 0, se as duas flags SF e OF estiver setadas em 1 e a ZF em 0 sera equivalente a comparação maior, ou se todas as tres flags estiver setada em 0 tambem sera equivalente a comparação do maior mov ah,127 mov bh,2 add ah,bh jg pulo pulo: com a instrução jl (jump if lesser) comparamos se o numero é menor que o outro e pulando na instrução jl caso seja mov ax,200 mov bx,500 cmp ax,bx jl pulo pulo: na comparação do menor as flags usadas são as mesmas que a comparação maior (SF, OF e ZF), o diferencial da comparação menor são as flags SF e OF que devem ser diferentes uma da outra ou seja enquanto uma precisa esta setada em um a outra precisa esta setada em 0 mov ah,-100 mov bh,2 sub ah,bh jl pulo

pulo: tambem temos a instrução que compara se o valor é maior ou igual ao segundo sendo essa instrução o jge (jump if greader or equal), essa instrução seria equivalente ao jg e je junto, sera pulado caso o numero seja maior ou igual mov ax,500 mov bx,315 cmp ax,bx jge pulo pulo: as flags usada na comparação são SF, OF e ZF, nesse caso tanto faz se a flag SF e OF forem iguais indicando que é maior ou se a flag ZF for igual a 1 indicando que é igual xor ax,ax jge pulo pulo: da mesma forma tambem temos a instrução jle (jump if lesses or equal) que define se o valor é menor ou igual, caso seja igual sera pulado na instrução jle para aquele endereço mov ax,315 mov bx,500 cmp ax,bx jle pulo pulo: na comparação do jle as flags usadas são as mesmas que jge com o diferencial que as flags SF e OF devem ser diferentes uma da outra indicando que o numero é menor ou então a flag ZF deve esta setada em 1 indicando que é igual mov ah,-100 mov bh,2 sub ah,bh jle pulo pulo: existe a comparação sem sinal tambem, para comparar se um numero é maior do que o outro sem sinal usamos a instrução ja (jump if above) mov ax,65000 mov bx,10000 cmp ax,bx ja pulo

pulo: na instrução ja é usado as flags CF e a ZF para testa se a condição é satisfeita, então se ambas as flags estiver setadas em 0 sera considerado o numero sendo maior clc ja pulo pulo: para comparar se um numero é menor do que o outro usamos a instrução jb (jump if below), caso numero seja menor sera pulado na instrução jb mov ax,1000 mov bx,5000 cmp ax,bx jb pulo pulo: da mesma forma é usado a flag CF na instrução jb, com a diferença que se o CF estiver setado em 1 sera feito o pulo stc ja pulo pulo: tambem podemos comparar se é maior ou igual com a instrução jae (jump if above or equal) mov ax,5000 mov bx,1000 cmp ax,bx jae pulo pulo: nessa comparação é usado tanto a flag CF quanto ZF em 0 ou apenas a flag ZF em 1 para que ocorra o pulo xor ax,ax jae pulo pulo: outra instrução é o jbe (jump if below or equal) que compara se o numero é menor mov ax,1000

mov bx,5000 cmp ax,bx jbe pulo pulo: no jbe é comparado se a flag CF esta setada em 1 ou se a flag ZF esta setada em 0 mov ax,0xffff add ax,100 jae pulo pulo: tambem podemos pular se a flag CF estiver setada em 1 com a instrução jc stc jc pulo pulo: ou se a flag CF estiver com o valor 0 usando a instrução jnc clc jnc pulo pulo: é possível pular caso a flag OF estiver setada em 1 usando a instrução jo mov ah,127 add ah,10 jo pulo pulo: ou pular caso a flag OF estiver setada em 0 usando a instrução jno mov ah,100 add ah,10 jno pulo pulo: outro tipo de pulo é o jp que permite pular caso a flag PF estiver com valor 1 mov ah,9 add ah,1 jp pulo

pulo: com o jnp podemos pular caso a flag PF esteja com o valor 0 mov ah,10 add ah,1 jnp pulo pulo: com a instrução js podemos pular caso a flag SF estiver setada em 1 mov ax,100 sub ax,200 js pulo pulo: tambem podemos pular se a flag SF estiver setada em 0 usando a instrução jns mov ax,100 add ax,200 jns pulo pulo: na flag ZF usamos a instrução jz para pular caso essa flag estiver setada em 1 xor ax,ax jz pulo pulo: como tambem podemos usar o jnz para pular caso a flag ZF estiver em 0 mov ax,5 mov bx,10 add ax,bx jnz pulo pulo: esses pulos condicionais são parecidos com as estruturas condicionais em linguagens de alto nivel como o if na linguagem C, não apenas parecido já que linguagens de alto nivel quando são compiladas para a linguagem da maquina são criado esses pulos condicionais no próprio binario int var = 10; if(var == 10)

//mov ax,10 //cmp ax,10

{ } else { }

//jne kodo //kodo:

os pulos condicionais e incondicionais pode ser usados tambem para criar loops usando contadores, uma forma simples seria atribuir um numero a um registrador incrementando e comparando ate chegar a determinado valor especifico mov ax,0 mov bx,100 inicio: cmp ax,bx jge fim inc ax jmp inicio fim: não necessariamente precisa ser um contador que se incrementa, podemos criar um contado que se decrementa ou que testa qualquer outra condição ou qualquer outro tipo de flag mov ax,0xffff inicio: jz fim dec ax jmp inicio fim: o codigo anterior de repetição é bem semelhante com uma estrutura de repetição while já que vai repetir enquanto uma condição for satisfeita ou seja enquanto a codição não for 0 (falso) int x = 0xffff; while(x == 0){ x--; }

//mov ax,0xffff //inicio: //jz fim //dec ax //jmp inicio //fim:

outra forma da gente fazer uma repetição de determinado trecho do codigo em assembly seria usar instruções especificas da própria linguagem como o loop, essa instrução vai repetir um determinado trecho enquanto o registrador cx não for igual a 1, toda vez que o programa chega na instrução loop ele vai decrementar o registrador cx e pular para determinado endereço caso o cx seja diferente do valor 1 (o loop se assemelha bem a estrutura de repetição for em linguagens de alto nivel) mov cx,20

kodo: loop kodo da mesma forma podemos modificar o registrador cx dentro da repetição e cessar o loop a qualquer momento (o famoso break em linguagens de alto nivel) mov cx,20 kodo: mov cx,1 loop kodo

;break

a instrução loopz permite usar o registrador cx como contador e tambem usar a flag ZF para cessar o loop, caso a flag ZF estiver setado em 0 o loop sera cessado, só fazendo o loop enquanto ela estiver setada em 1 mov cx,20 xor ax,ax kodo: loopz kodo tambem existe a instrução loopnz que permite o contador no cx e sera cessado o loop apenas se a flag ZF estiver setada em 0 mov cx,20 mov ax,1 add ax,1 kodo: loopnz kodo 2.6 – x86: Subrotina em algumas situações precisamos executar os mesmo codigos no nosso programa varias vezes seguidas em trechos diferente do programa, para facilitar podemos criar um pequeno trecho de codigo e reaproveitar ele bastando pular para esse trecho quando precisar executar aquele determinado trecho de codigo, isso evitaria gastos desnecessario tanto para memoria quanto para tempo de criar o mesmo codigo varias vezes no nosso programa, para criar uma subrotina não bastaria apenas pular para aquele determinado trecho já que a execução depois do pulo seria dali em diante ou seja o programa ia se perder depois daquele pulo, para que uma subrotina seja criada temos que voltar para o ultimo ponto da onde pulamos no final da execução dela, a forma de fazer isso seria pegar o registrador IP antes do pulo sendo que isso é uma parte do problema já que não manipulamos o IP diretamente diferente de outros registradores que podemos mover o valor, em alguns compiladores é possivel de forma bem simples já que podemos pegar o endereço atual como acontece no nasm com o cifrão embora isso seja estatico e não uma instrução da arquitetura, se a gente tem o endereço atual armazenado podemos pular para a subrotina e voltar dela para ultimo ponto assim o programa não se perdendo depois do pulo, como programa vai voltar para o ultimo ponto tambem temos que atribuir ao endereço a quantidade de bytes da própria instrução que estamos pegando o endereço e a instrução de pulo para não executar ela novamente e fica preso em um loop, o exemplo abaixo a gente pula para subrotina e depois volta para o ultimo endereço lea ax,$

add ax,8 jmp kodo kodo: jmp ax a subrotina anterior é apenas um exemplo arcaico de uma subrotina criado na marra por pulos e usando os registradores para armazenar o endereço, existem metodos mais simples de se criar uma subrotina inclusive instruções da própria arquitetura que permite criar subrotinas, a forma mais simples que a anterior seria usar a instrução call para pular para subrotina já com endereço armazenado na memoria e para voltar usamos a instrução ret (bem mais simples não é? ) call kodo kodo: ret se a gente analisar uma função em alto nivel seria equivalente a uma subrotina em baixo nivel com suas diferenças e semelhanças, uma delas que podemos chamar a subrotina quantas vezes a gente quiser call kodo call kodo call kodo kodo: ret normalmente em linguagens de alto nivel as funções devem ser criadas antes de ser usadas já na linguagem assembly podemos criar todas as subrotinas no final do codigo sem problema, nesse caso devemos tomar cuidado para não deixar o programa executar elas colocando um loop infinito no final do codigo antes da subrotina call kodo jmp $ kodo: ret tambem podemos criar elas no começo do codigo nesse caso usamos um pulo incondicional para não executar ela jmp main kodo: ret main: call kodo

podemos criar quantas subrotinas a gente quiser ou precisar jmp main kodo: ret kami: ret main: call kami call kodo podemos passar e retornar valores nas subrotinas usando os registradores jmp main kodo: add ax,bx ret main: mov ax,50 mov bx,10 call kodo podemos usar a pilha de memoria para passar valores usando a instrução push para empilhar e o pop para desempilhar da memoria, a vantagem de passar valores usando a pilha que seria possível passar uma quantidade maior de valores, o primeiro a ser empilhado sera sempre o ultima e ser desempilhado seguindo essa ordem (LIFO) jmp main kodo: pop dx pop ax pop bx add ax,bx push ax push dx ret main: push 50 push 10 call kodo pop ax

;desempilha endereço para dx ;desempilha 10 para ax ;desempilha 50 para bx ;soma ax e bx ;empilha o ax ;empilha endereço para voltar

;desempilha o 60 no ax

como podemos reparar no exemplo anterior é possível pegar o endereço de retorno, sendo que isso

seria uma forma de manipular o registrador IP indiretamente jmp main kodo: pop ax push ax ret main: call kodo inclusive podemos ate forçar o retorno para outro endereço jmp main kodo: pop ax mov ax,0x600 push ax ret main: call kodo tambem é possível chamar uma subrotina dentro da outra, nesse caso ele vai voltar sempre para o ultimo call que foi chamado e dele para o anterior jmp main kodo: call kami ret kami: ret main: call kodo em linguagens de alto nivel quando uma função chama ela mesma isso é chamado de função recursiva, em asm tambem podemos criar uma subrotina que chama ela mesmo jmp main kodo: call kodo ret main: call kodo

subrotinas recursivas pode ser um grande problema já que é armazenado o endereço atual na memoria do computador e com isso tambem lotando toda a memoria do computador com os endereços, uma forma simples de usar rotinas recursivas e evitar esse problema seria criar um contador e quando ultrapassar determinado limite vai parar a chamada e retornar para anterior que por sua vez tambem vai voltar para o anterior e o anterior ate voltar na primeira chamada jmp main kodo: dec cx jz kami call kodo kami: ret main: mov cx,100 call kodo é muito comum o uso do registrador bp para correr a pilha de memoria em uma subrotina para faciltar o acesso aos dados nela, para o uso do base pointer temos que criar o stack frame bastando empilhar o registrador bp, depois mover o registrador sp para dentro do registrador bp, depois o trecho do nosso codigo da subrotina jmp main kodo: push bp mov bp,sp ; codigo da subrotina ret main: call kodo depois do codigo da nossa subrotina temos que fazer o inverso destruir o stack frame, para isso basta mover o registrador bp para sp, depois desempilhar o bp e retornar jmp main kodo: push bp mov bp,sp ; codigo da subrotina mov sp,bp pop bp ret main:

call kodo com o ponteiro base podemos acessar a pilha com maior facilidade de uma forma indexada pelo registrador bp sem precisar desempilhar para o acesso jmp main kodo: push bp mov bp,sp mov cx,[bp + 4] mov ax,[bp + 6] mov bx,[bp + 8] mov sp,bp pop bp ret

; acesso ao valor 0x5 sem precisar desempilhar ; acesso ao valor 0x30 sem precisar desempilhar ; acesso ao valor 0x10 sem precisar desempilhar

main: push 0x30 push 0x5 push 0x10 call kodo a posição no acesso indexado depende exclusivamente do tamanho do registrador que estamos manipulando, então se a gente estiver manipulando registradores maiores como 32bits o acesso indexado sera a cada 4 bytes e não apenas 2 bytes jmp main kodo: push ebp mov ebp,esp mov eax,[ebp + 8] mov ebx,[ebp + 12] mov esp,ebp pop ebp ret main: push 0x30 push 0x80 call kodo lembrando que o mesmo pode ser feito em compiladores que usa a sintaxe at&t jmp main kodo: push %ebp mov %esp,%ebp

mov 8(%ebp),%eax mov %ebp,%esp pop %ebp ret main: push $0x40 call kodo tambem pode ser usado a instrução enter para criar o stack frame, e a instrução leave para destruir o stack frame sendo essas instruções validas a partir do 80286 jmp main kodo: enter 0,0 mov eax,[ebp + 8] leave ret main: push 0x30 call kodo dependendo do compilador podemos escrever parte do nosso codigo em outro arquivo e depois incluir esse arquivo no codigo por alguma diretiva, podemos criar subrotinas em arquivos separados e reaproveitar elas em outros codigos futuros, no compilador nasm usamos a diretiva %include seguido do arquivo que vamos incluir (sera incluido o codigo do arquivo exatamente onde declaramos ele) %include “kodo.inc” no compilador gas usamos a diretiva .include seguido do arquivo que vamos incluir .include “kodo.inc” 2.7 – x86: Acesso a memoria podemos ler e escrever diretamente ou indiretamente na memoria do computador, a memoria é usada para diversos fins como armazenamento de dados seja eles dinamico como as variaveis ou estatico como as constantes, a memoria pode ser usada para passar dados para as subrotinas ou retornar dados das subrotinas, a memoria é usada para executar os codigos do programa de forma sequencial ou seja todo codigo assembly é jogado na memoria do computador e executado a partir dela, a memoria é usada para ter acesso direto aos perifericos do computador já que são mapeados diretamente em algum endereço de memoria, ela tambem é usada para manipular uma grande quantidade de dados entre outras coisas, como já sabemos quando ligamos o computador ele inicia em modo real lendo apenas no maximo 1MB de memoria depois que ele passa para modo protegido ele consegue acessar mais de 1MB, uma das diferenças entre o modo real e o protegido seria a forma de acesso a memoria, no modo real a gente acessa os dados na memoria diretamente pelo endereço que seria o segmento e o offset ambos de 16bits, já no modo protegido isso funciona por tabela de descritores que pode ter um tamanho relativo, no modo real o segmento e o offset aponta

para um endereço absoluto da memoria ou seja aquele endereço absoluto onde sera acessado vai depender tanto do segmento quanto do offset (segmento:offset), a cada novo segmeto seria equivalente a 16 offsets sendo o segmento uma forma de acesso alto para os offsets, para a gente saber o endereço absoluto onde estamos acessando pelo segmento e offset basta multiplicar o segmento por 16 (0x10) e por fim somar com o offset que vamos obter o endereço absoluto (quando estamos tratando do numero em formato hexadecimal podemos apenas adicionar um 0 no final do segmento sem precisar multiplicar ele por 0x10) endereço absoluto = segmento * 16 + offset é possível ter o acesso ao mesmo endereço absoluto com segmentos e offsets diferentes, lembrando que tanto o segmento quanto o offset são de 16bits ou seja só existe 65535 (0xffff) tanto para o segmento quanto para o offset, os dois com valor mais alto totaliza quase 1mb de memoria por isso que é possível acessar apenas 1MB de memoria em modo real Endereço absoluto

segmento: offset

16 (0x10)

0:16

16 (0x10)

1:0

3.700 (0xe74)

200:500

112.315 (0x1b6bb)

7000:315

1.114.095 (0x10ffef)

65535:65535

podemos mover um valor de um endereço da memoria para um registrador usando a instrução mov, na sintaxe intel para especificar que estamos manipulando um endereço de memoria temos que colocar entre colchetes dependendo do compilador, quando manipulamos valores na memoria isso é chamado de acesso direto mov ax, [500] na sintaxe at&t para a gente especificar que estamos mexendo com valores diretamente da memoria basta apenas colocar o endereço sem o cifrão mov 500,%ax tambem é possível mover um valor de um registrador para a memoria mov ax,100 mov [500], ax podemos mover um valor imediato para memoria mov [500], 0x61 é possível armazenar um valor em um determinado registrador depois acessar aquele valor como endereço diretamente no registrador colocando entre colchetes mov ax,500

mov bx, [ax] na sintaxe at&t o registrador deve ficar entre parênteses mov $500,%ax mov (%ax),%bx não tem como mover um valor da memoria diretamente para memoria deve ser passado para um registrador geral antes mov ax,[500] mov [600],ax tambem não é possível acessar diretamente outro segmento apenas especificando ele, para fazer isso temos que usar registradores de segmentos como o DS ou ES em conjunto com o endereço mov ax,[ds:500] da mesma forma é possível armazenar um valor na memoria para um segmento diferente mov ax,100 mov [ds:500],ax não é possível atribuir um valor diretamente a um registrador de segmento, temos que jogar o valor em um registrador de uso geral e dele para o registrador de segmento mov ax,600 mov ds,ax mov ax,[ds:500]

; 600:500

quando a gente muda o segmento de dados ds qualquer outro endereço da memoria que a gente acessar sem especificar o segmento sera o mesmo do segmento ds já que ele é o segmento de dados principal do programa mov ax,600 mov ds,ax mov ax,[400]

; 600:400

as vezes precisamos acessar outro segmento sem ter que mudar o próprio segmento ds, para fazer isso podemos usar os segmentos extras de dados como o es, fs e gs mov ax,600 mov es,ax mov bx,[es:300] o registrador de segmento cs aponta para onde o nosso codigo esta sendo executado a sua modificação seria simplesmente um far jump, podemos usar o registrador cs para criar sub codigos no nosso programa ou diversos programas de forma modular no nosso sistema

jmp 600:100 todo o nosso codigo assembly é convertido para bytes equivalente aos opcodes e jogado na memoria dessa forma para ser executado, o registrador cs aponta para esses bytes e dele é executado sequencialmente cada um dos opcodes, como os opcodes são sequencais de bytes isso nos permite criar codigos na memoria para ser executado de forma dinamica em tempo de execução mov ax,0x500 mov ds,ax mov [ds:0x100],0xb8 mov [ds:0x101],0x0a mov [ds:0x102],0x0

;mov ax,10

mov [ds:0x103],0xbb mov [ds:0x104],0x14 mov [ds:0x105],0x0

;mov bx,20

mov [ds:0x106],0x3 mov [ds:0x107],0xc3

;add ax,bx

mov [ds:0x108],0xeb mov [ds:0x109],0xfe

;jmp 0x108

jmp 0x500:0x100 o registrador de segmento ss aponta para o segmento de memoria onde esta a pilha (stack) e o registrador sp aponta para o topo da pilha, a pilha de memoria funciona com base em LIFO sendo o primeiro a entrar sera o ultimo a sair seguindo essa ordem, podemos imaginar a pilha como uma pilha de cartas onde empilhamos uma carta em cima da outra, quando precisamos tirar uma carta especifica temos que retirar todas as de cima dela primeiro, para empilhar usamos a instrução push mov ax,315 push ax tambem podemos empilhar um determinado valor de forma imediata push 315 para desempilhar usamos a instrução pop seguido do registrador onde vamos armazenar pop ax quando empilhamos dois ou mais valores os ultimos a ser empilhados sera os primeiros a ser desempilhados assim como os primeiros a ser empilhado sera os ultimos a ser desempilhados push 315 push 100 push 200 pop ax pop bx

;desempilha o 200 ;desempilha o 100

pop cx

;desempilha o 315

quando empilhamos alguma coisa o registrador sp é decrementado pela quantidade de bytes que o sistema esta manipulando, então por isso que é dito que a pilha cresce para baixo devido essa decrementação do sp, quando estamos mexendo com uma arquitetura de 16bits a pilha sera decrementada em 2bytes, quando mexemos com arquitetura de 32bits a pilha sera decrementada em 4bytes, esse tamanho da decrementação da pilha é fixo não pode ser alterado inclusive o tamanho da pilha as vezes é limitado ao tamanho de algum registrador, quando desempilhamos alguma coisa sera feito o inverso incrementado o registrador sp, o registrador sp sempre vai estar apontando para o topo da pilha que é o ultimo dado empilhado nela | | V

315 100 200 ← SP

como a pilha são os bytes armazenado naquela parte da memoria e segmento podemos recriar o proprio push e pop, para refazer a instrução push bastaria decrementar pelo tamanho dos dados e armazenar naquele novo endereço, no caso do pop bastaria apenas incrementar o sp pelo tamanho sub sp,2 mov bx,sp mov [ss:bx],315

;push 315

mov bx,sp mov ax,[ss:bx] add sp,2

;pop ax

podemos modificar o registrador de segmento ss fazendo ele apontar para qualquer segmento com isso podemos ter a nossa pilha em qualquer outro segmento, o registrador sp normalmente é o offset mais alto daquele segmento já que ele sera subtraido mov ax,0x500 mov ss,ax mov sp,0xfffe da mesma forma podemos ter varias pilhas no nosso programa bastando manipular o registrador ss, o problema de ter duas ou mais pilhas que precisaria de uma referecia para cada sp de cada uma das pilhas, para fazer isso podemos usar um endereço na memoria para isso (nunca vi em nenhum lugar ensinado asm com multi stack ^^ ) mov ax,0x600 mov ss,ax mov sp,0xfffe

;carrega a pilha 1 no segmento 600

push 315 mov ax,0x500 mov ds,ax mov [ds:0],sp

;empilha 315 na pilha 1

mov ax,0x601

;carrega a pilha 2 no segmento 601

;salva o sp da pilha 1 na memoria 500:0

mov ss,ax mov sp,0xfffe push 100 mov ax,0x500 mov ds,ax mov sp,[ds:0] mov ax,0x600 mov ss,ax pop ax

;empilha 100 na pilha 2 ;volta para pilha 1 ;recupera o sp da pilha 1 de 500:0 ;carrega a pilha 1 no segmento 600 ;desempilha o 315 da pilha 1

quando precisamos empilhar todos os registradores usamos a instrução pusha, sera empilhado na ordem ax, cx, dx, bx, sp, bp, si e di pusha para desempilhar usamos a instrução popa popa podemos usar isso para salvar os registradores na memoria e recuperar eles depois ou simplesmente uma forma de modificar todo os registradores diretamente pela memoria empilhando eles mov ax,0x5 mov bx,0x6 pusha mov ax,0x20 mov bx,0x30 popa

;recupera o ax=5 e bx=6

alem do pusha temos o pushad e o popad que é usado para os registradores de 32bits mov eax,0x200 mov ecx,0x100 pushad mov eax,0x500 popad tambem existe o pushf que empilha a própria flag sendo dois bytes que representa as flags (já que a flag é um registrador de 16bits), o valores nesse word seria equivalente a cada posição da flag ou seja se tiver setado em 1 as flag CF, PF e AF sera armazenado na pilha o valor 0x17 (10111) pushf para recuperar da pilha usamos o popf popf

a instrução pushf e popf (pushfd e popfd para os de 32bits) permite modificar as flags de forma indireta usando a pilha push 0b1000111 popf

;ZF, PF e CF

as vezes precisamos alocar dinamicamente uma quantidade de espaço na pilha para isso basta subtrair o valor dela, para liberar o espaço novamente basta adicionar o valor ao sp (apenas libere o espaço alocado se não tiver empilhado mais nada depois dela se não sera perdido) sub sp,20 add sp,20

;aloca 20 bytes na pilha ;libera os 20 bytes na pilha

o registrador bp é usado como base de indice para correr a stack de cima para baixo, como já sabemos a stack cresce para baixo então o bp deve ser atribuido ao offset mais alto da stack e não para o topo da pilha assim podemos correr ela da parte mais alta ate o topo dela (sp) | | V

315 ← BP 100 200 ← SP

para usar o bp a gente deve atribui o valor do sp a ele antes de começar a empilhar, assim sera a base mais alta com isso basta acessar os valores de formar indexada subtraindo ao bp mov bp,sp push 315 push 100 push 200

;bp (base) ;bp - 2 ;bp - 4 ;bp - 6

mov ax,[bp - 4]

;acessa o valor 100

esse regitrador pode ser usado em conjunto com as subrotinas para ter acesso tanto ao empilhamento antes da chamada para aquela subrotina quanto os empilhamento da própria subrotina, quando atribuimos o registror sp ao bp na subrotina podemos acessar os empilhamentos que foram feito na subrotina como indice bastando subtrair o registrador bp, ou então adicionando para ter acesso os empilhamento que foram empilhados antes da chamada dela (se a gente analisar linguagens de alto nivel as passagem de argumentos nas funções e as variaveis locais dentro das funções são nada mais nada menos que um ponteiro base como esse) push 315 push 600 call kodo

;bp + 4 ;bp + 2 ;endereço atual (base)

kodo: mov bp,sp push 800 push 200 mov ax,[bp + 4] mov bx,[bp - 2]

;bp - 2 ;bp - 4 ;acessa o valor 315 ;acessa o valor 800

muitas vezes usamos o bp em subrotinas diferentes então para evitar a perda do valor do bp antigo temos que criar um stack frame, para fazer isso a cada nova chamada de subrotina temos que salvar o bp para pilha antes de modificar e depois recuperar ele destruindo o stack frame call kodo kodo: push bp mov bp,sp

;salva o bp antigo na pilha ;move o sp atual para o bp para ter a nova base

mov sp,bp pop bp ret

;move o valor do bp para o sp ;recupera o bp antigo ;retorna

quando pensamos em memoria e programação vem a cabeça as variaveis não é? uma variavel é um espaço alocado na memoria com um tamanho fixo que podemos usar para armazenar dados diretamente por um nome, diferente de outras linguagens de alto nivel que declaramos uma variavel de um tipo especifico, o assembly a gente precisa criar as variaveis de forma manual alocando o espaço, a forma mais simples de criar uma variavel seria criar um label e a quantidade de bytes vazios da nossa variavel kodoint: nop nop podemos armazenar os dados na variavel usando o próprio label dela mov [kodoint],0x315 kodoint: nop nop como tambem é possível ler a nossa variavel pelo próprio label mov [kodoint],0x315 mov ax,[kodoint] jmp $ kodoint: nop nop podemos criar quantas variaveis a gente quiser dessa forma mov [kodoint],0x315 mov [kodoint2],0x100 kodoint:

nop nop kodoint2: nop nop ao inves de usar o nop para alocar aquele espaço podemos usar a diretiva db para especificar um determinado byte em alguns compiladores kodoint: db 0x0 db 0x0 no compilador gas o db seria .byte kodoint: .byte 0x0 .byte 0x0 a gente poderia especificar qualquer byte, exemplo seria o 0x90 que seria equivalente usar a instrução nop kodoint: db 0x90 db 0x90 podemos colocar um único db e os bytes separados por virgulas kodoint: db 0x0,0x0 alem do db existe o dw que aloca 2bytes sendo um word (16bits) kodoint: dw 0x0 no gas o dw seria a diretiva .word ou .short kodoint: .word 0x0 como o word é composto por dois bytes se a gente quiser especificar dois bytes 0x90 em um único word tambem é possível bastando colocar 0x9090 kodoint: dw 0x9090 com o dd especificamos um dword que seria equivalente a 4 bytes (32bits)

kodoint: dd 0x0 no compilador gas usamos .int ou .long para especificar o dd kodoint: .int 0x0 tambem temos o dq que seria equivalente a 64bits ou 8 bytes kodoquad: dq 0x0 no compilador gas usamos .quad para reprensentar 64bits kodoquad: .quad 0x0 uma array em uma linguagem de alto nivel é uma alocação com varias posições com tamanhos especificos, em assembly tambem é possível criar array bastando alocar a quantidade certa com tamanho do dado multiplicada pela quantidade de posições daquela array, exemplo uma array do tipo short int que tem 2 bytes (16bits) para cada posição dela, se a gente pretende criar 10 posições então temos que alocar na memoria 20 bytes em sequencia (10*2) kodoarray: dw 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 o acesso a uma array é indexado de acordo com o tamanho da posição dela ou seja uma array do tipo short int temos que acessar as posições a cada 2 bytes mov bx, kodoarray mov [bx], 0x315 mov [bx + 2], 0x100 mov [bx + 4], 0x500

;1º posição ;2º posição ;3º posição

kodoarray: dw 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 lembrando que o acesso indexado na sintaxe at&t é um pouco diferente da sintaxe intel mov kodoarray,%bx mov $315, (%bx) mov $100, 2(%bx) mov $500, 4(%bx) kodoarray: .short 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0

alguns compiladores tem diretivas especificas para criar uma sequencia bytes como o resb, resw, resd e o resq do nasm kodoarray: resw 10 no compilador gas podemos usar a diretiva byte seguido da sequencia de bytes que vamos criar kodoarray: .byte10 0x0 da mesma forma que as arrays podemos criar as strings porem usando os caracteres kodostr: db "k","o","d","o" a maioria dos compiladores permite escrever as string entre aspas sendo cada caracter um byte kodostr: db "kodo" no compilador gas podemos usar a diretiva .ascii para definir uma string como no exemplo anterior kodostr: .ascii "kodo" as vezes precisamos colocar um caracter no final da string indicando a sua finalização assim quando o programa ler essa determinada string vai saber que ali é o final dela e não vai ler parte buffer descohnecido da memoria, programas de msdos normamente usa o cifrão e outros usa o caracter nulo (agora você entende porque as strings em C termina com caracter nulo ne? ) kodostr: .ascii "kodo",0x0 para a manipulação das strings ou ate mesmo das arrays temos os registradores si e di, podemos atribuir o endereço de memoria ou label para o registrador si e acessar a string de forma indexada mov si,kodostr mov ah,[si + 0] mov ah,[si + 1] mov ah,[si + 2] mov ah,[si + 3]

;acesso a letra k ;acesso a letra o ;acesso a letra d ;acesso a letra o

kodostr: db "kodo" o mesmo vale para o registrador di mov di,kodostr mov ah,[di + 1]

;acesso a letra o

kodostr: db "kodo" o real uso dos registradores si e di é o movimento de uma sequencia de bytes de uma parte da memoria para outra parte da memoria sendo do si para o di, podemos usar a instrução movsb para mover um unico byte do si para o di

mov si,kodostr mov di,kodonovo movsb kodostr: db "kodo" kodonovo: db 0x0,0x0,0x0,0x0 toda vez que a gente usa o movsb sera movido um byte do si para o di então precisamos usar a quantidade equivalente aos bytes que sera movido para mover todos mov si,kodostr mov di,kodonovo movsb movsb movsb movsb kodostr: db "kodo" kodonovo: db 0x0,0x0,0x0,0x0 tambem tem o movsw que move 2 bytes (16bits), movsd que move 4 bytes (32bits) e movsq 8 bytes (64bits) mov si,kodo mov di,kodonovo movsw movsw kodo: dw 0xffff,0x315 kodonovo: dw 0x0,0x0 podemos mover o dado do registrador fonte si para o registrador al com a instrução lodsb mov si,kodostr lodsb kodostr: db "kodo" com a instrução lodsw movemos 16bits da fonte para o registrador ax, tambem existem o lodsd e lodsq para mover bytes mais altos que usa registradores eax e rax mov si,kodo lodsw kodo: dw 0xf315 é possível mover do registrador al para o registrador de destino di com a instrução stosb (stosw, stosd e stosq)

mov al,0x61 mov di,kodo stosb kodo: db 0x0 tanto o movsb, lodsb e stosb incrementa automaticamente o di e si com isso não precisamos ficar incrementado ele para acessar o próximo byte já que é automatico mov di,kodo mov al,0x61 stosb mov al,0x62 stosb kodo: db 0x0,0x0 boa parte dos compiladores usa a diretiva org para alinhar a posição de memoria onde esta aquele determinado codigo, sendo o org uma diretiva do compilador e não da propria arquitetura podendo existir ou não existir em alguns compiladores ou ter um uso diferente dependendo do compilador, no nasm o org não tem tanta utilidade pelo menos não para alinhar o codigo na memoria org 100 no compilador gas o org é usado para alinhar o codigo na memoria ou no proprio binario, quando definimos uma posição no org os codigos depois dele vai esta sendo jogado na memoria a partir daquela determinada posição .org 0 nop jmp 100 .org 100 mov $10,%ax mov $20,%bx no nasm podemos usar a diretiva times que permite repetir uma certa quantidade de bytes, com isso podemos conseguir o mesmo resultado do codigo anterior bastando subtrair a posição que queremos pela quantidade de bytes atual e o byte ou a instrução que vai se repetir ate la nop jmp 100 times (100 - ($-$$)) db 0x0 mov ax,10 mov bx,20 muitos sistemas inicia programas em parte especificas da memoria algum segmento especifico ou ate mesmo algun offset especifico, como exemplo temos os programas de boot que deve ser inciado no segmento 0x0 e offset 0x7c00, executaveis de msdos do tipo COM inicia no segmento 0x700 e o

seu offset é o 0x100, esses endereços depende muito do sistema operacional sendo que muitos compiladores usa a diretiva org para especificar isso org 0x7c00 existem endereços da memoria usados para fins especificos que podem ser mapeamento do próprio hardware naquele endereço de memoria tal endereços são chamados de DMA (Direct Memory Access), nos endereços absolutos de 0x0 ate 0x3ff são os vetores de interrupções e o seu uso é apontar para uma determinada parte da memoria quando acontece alguma interrupção especifica, os de 0x400 ate 0x4ff são endereços usados pela BIOS para diversos fins inclusive retorno de informação do hardware, endereços absolutos de 0x500 ate o 0x9ffff são endereços livres para qualquer uso, os de 0xa0000 ate 0xbffff são usados para o buffer de video onde podemos imprimir algo na tela apenas manipulando tal endereço, 0xc0000 ate 0xeffff usados para roms de expansão como perifericos extras, 0xf0000 ate 0xfffff é a rom da BIOS inclusive o POST é executado nesses endereços mais altos Uso

Inicio do endereço

Fim do endereço

Vetor de interrupção

0x0

0x3ff

BIOS

0x400

0x4ff

Memoria livre

0x500

0x9ffff

Buffer de video

0xa0000

0xbffff

Rom de expansão

0xc0000

0xeffff

Rom da BIOS

0xf0000

0xfffff

no exemplo abaixo é imprimido o texto “kodo” na tela usando o endereço absoluto 0xb8000, como já sabemos o endereço absoluto é o segmento e o offset então 0xb8000 pode ser acessado por b800:0000 (eita 50 paginas depois e finalmente conseguimos imprimir algo na tela '-' ) mov ax,0xb800 mov ds,ax mov [ds:0],”k” mov [ds:2],”o” mov [ds:4],”d” mov [ds:6],”o” se reparar no exemplo anterior colocamos o caracter a cada 2 offset sendo que o byte seguinte seria a cor ou fundo que sera exibido, no exemplo abaixo imprime o texto em vermelho mov ax,0xb800 mov ds,ax mov [ds:0],”k” mov [ds:1], 12 mov [ds:2],”o” mov [ds:3], 12 mov [ds:4],”d” mov [ds:5], 12

mov [ds:6],”o” mov [ds:7], 12 2.8 – x86: Interrupção uma interrupção é uma parada que o processador faz na execução de determinado codigo para executar outra coisa naquele momento quando ocorre aquela determinada interrupção, existem diversos tipos de interrupções sendo que algumas interrupções são causadas por hardware e outras por software, algumas interrupções podem ser desativadas pelo usuario ou sistema e outras interrupções não podem ser desativadas, algumas interrupções já são predefinidas ou usa endereços já predefinidos na memoria, uma interrupção causada por hardware podem ser mascaradas ou não mascardas (NMI), podem ser geradas por um controlador de interrupção para sinalizar um pedido de interrupção como os IRQs ou ate mesmo por um pulso ou a falta de corrente em algum pino especifico do processador ou do microcontrolador, uma interrupção por software pode ser apenas a execução de uma determinada instrução no próprio codigo como a instrução int do x86 (essas interrupções por software tem o nome de trap ou syscall em algumas arquiteturas como é o caso da arquitetura 68k que usa a instrução trap para interrupção por software ou do mips que usa o syscall), quando acontece uma interrupção o programa para o seu fluxo de execução e pula para algum outro endereço de memoria executando aquele codigo daquela interrupção, depois de finalizado ele volta para o fluxo original do programa. esses endereços de memoria das interrupções depende do tipo de interrupção e do tipo de arquitetura, todos os endereços usados pelas interrupções no x86 estão listados no vetor de interrupção (endereços entre 0x0 ate 0x3ff), as interrupções costumam ter varias funções já definidas onde precisamos especificar qual sera a função que estaremos utilizando e os argumentos dela usando os registradores de usos gerais, uma das interrupções da arquitetura x86 é o int10 que é uma chamada de video da BIOS, uma das vantagem das interrupções de BIOS que elas funcionam sem precisar de um sistema operacional sendo utilizadas para criar programas de boot e ate sistemas operacionais, a sua desvantagem que não funciona diretamente em sistema operacionais principalmente os que estão em modo protegido, uma das funções da interrupção int10 é alterar o modo de video onde setamos no registrador ah o numero 0 indicando essa função e no registrador al seria o numero equivalente ao modo de video que vamos setar, por fim usamos a instrução int 0x10 para chamar a interrupção com aqueles argumentos (sendo essa uma interrupção de software) mov ah,0x0 mov al,0x1 int 0x10

;funcão usada: modo de video ;argumento: 80x32 16 cores textual ;int de software

o mesmo pode ser feito usando a sintaxe at&t tambem mov $0x0,%ah mov $0x1,%al int $0x10 outra função da interrupção int10 é a função 0xe, essa função permite imprimir um caracter na tela bastando especificar o codigo desse caracter no registrador al mov ah,0x0e mov al,0x6b int 0x10 podemos colocar o caracter ascii tambem

mov ah,0xe mov al,'k' int 0x10 é possivel usar as interrupções quantas vezes a gente quiser, exemplo seria imprimir vários caracteres na tela mov ah,0xe mov al,'k' int 0x10 mov ah,0xe mov al,'o' int 0x10 mov ah,0xe mov al,'d' int 0x10 mov ah,0xe mov al,'o' int 0x10 lembrando que podemos sempre melhorar o nosso codigo, um bom exemplo seria criar uma subrotina para imprimir uma string usando o int10, uma subrotina nesse caso seria uma boa devido não ser possível imprimir string com int10 mov si,texto call printf mov si,texto2 call printf jmp $ ;minha subrotina que imprime uma string printf: cmp [si],0x0 ;compara se o caracter for igual a 0 jz fim ;se for igual a 0 finaliza pulando para fim mov ah,0xe ;função para imprimir mov al,[si] ;o caracter em si int 0x10 ;interrupção de video inc si ;incrementa o si para o próximo caracter jmp printf ;volta para o printf para imprimir o proximo fim: ret texto: db "kodo no kami ",0x0 texto2: db "ebook de asm",0x0

a função 0xe tem outro argumento que é a cor do texto que definimos no registrador bl mov ah,0xe mov al,'k' mov bl,0x2 int 0x10

;verde

usando a função 0x2 da interrupção int10 mudamos a posição do cursor e imprimir o caracter em qualquer outra parte tela, especificamos no registrador dh linha e no dl a coluna mov ah,0x2 mov dh,0x3 mov dl,0x10 int 0x10

;linha ;coluna

mov ah,0xe mov al,'k' int 0x10

;imprime 'k' na posição 3x10

para apagar caracteres imprimidos na tela usamos a função 0x2 bastando sobrescrever eles mov ah,0xe mov al,'k' int 0x10

;imprime o k na posição 0x0

mov ah,0x2 mov dh,0x0 mov dl,0x0 int 0x10

;volta para posição 0x0

mov ah,0xe mov al,'f' int 0x10

;imprime o f na posição 0x0 apagando o k

podemos pegar a posição atual do cursor usando a função 0x3 da interrupção int10, sera atribuido a linha e a coluna nos registradores dh e dl mov ah,0x3 int 0x10

;dh=linha, dl=coluna

outra função que permite imprimir algo na tela é a função 0x9 do int10, nessa função temos que especificar o caracter que sera imprimido no registrador al, a cor no registrador bl, e no registrador cx a quantidade de caracteres repetidos que ira ser imprimido mov ah,0x9 mov al,'k' mov cx,3 mov bl,0x5 int 0x10

;imprime 3 caracteres k repetido

um dos problemas de usar a função 0x9 que ele não alinha o cursor, então temos que fazer esse alinhamento manualmente pelo 0x2 mov ah,0x9 mov al,'k' mov cx,3 mov bl,0x4 int 0x10 mov ah,0x3 int 0x10 mov ah,0x2 add dl,3 int 0x10

; vermelho

;pega posição atual ;incrementa a coluna em mais 3

podemos ler o caractere na posição atual com a função 0x8, vai retornar no registrador ah a cor e no al o caracter (isso funciona apenas em modo de video textual) mov ah,0x0 mov al,0x1 int 0x10

;80x32 textual

mov ah,0xe mov al,'f' mov bl,0x3 int 0x10

;imprime o caracter na posição 0x0

mov ah,0x2 mov dh,0x0 mov dl,0x0 int 0x10

;aponta o cursor para o caracter

mov ah,0x8 int 0x10

;ah = 0x3, al = 'f'

é possível mudar a cor do background com a função 0xb da interrupção int10, a cor deve ser definida no registrador bl, o registrador bh deve esta com o valor 0 mov ah,0xb mov bh,0x0 mov bl,0x9 int 0x10

;azul claro

com a função 0x5 a gente manipula a pagina, uma pagina é semelhante ao um livro onde cada pagina seria equivalente a parte visual do nosso programa, então podemos permutar a pagina para mudar a parte visual do nosso programa sem precisar limpar a tela mov ah,0xe mov al,'k' ;imprime ‘k’ na primeira pagina int 0x10

mov ah,0x0 int 0x16 ;aguarda uma tecla ser apertada mov ah,0x5 mov al,0x1 int 0x10

;pula para pagina 1

mov ah,0xe mov al,'o' ;imprime ‘o’ na segunda pagina int 0x10 mov ah,0x0 int 0x16 ;aguarda uma tecla ser apertada mov ah,0x5 mov al,0x0 int 0x10

;volta para pagina 0 mostrando o k novamente

tambem podemos desenhar um pixel na tela com a função 0xc da interrupção int10, a cor definimos no registrador al, a posição horizontal no registrador cx, e a posição vertical no registrador dx, tambem é necessario esta usando um modo de video grafico mov ah,0x0 mov al,0x13 int 0x10 mov ah,0xc mov al,0x10 mov cx,100 mov dx,100 int 0x10

;cor verde claro ;x ;y

um exemplo de um quadrado desenhado pixel por pixel com a função 0xc do int10 em um loop mov ah,0x0 mov al,0x13 int 0x10 mov cx,200 mov dx,100 kodox: mov ah,0xc mov al,10 int 0x10 cmp cx,100 je kodoy loop kodox kodoy: dec dx

;x começa no 200 ;y comela no 100

;cor verde claro ;cria o pixel na posição do cx e dx ;se cx for igual a 100 pula para kodoy ;instrução loop decrementa o cx ou seja e desenhado invertidamente ;decrementa o dx

mov cx,200 cmp dx,20 je fim jmp kodox fim:

;volta o cx para 200 para desenha a nova linha ;compara dx com 20 se for igual pula para o fim terminado ;volta para kodoy para desenhar nova linha

podemos entrar com dados digitados do teclado com a interrupção int16 que é interrupção que manipula o keyboard sendo ela outra interrupção da BIOS, a função 0x0 dela permite ler um caracter apertado do teclado que sera armazenado no registrado al (quando usamos essa função o programa vai ficar preso nela ate que uma tecla seja apertada) mov ah,0x0 int 0x16 a gente pode capturar a tecla pressionada pelo int16 e exibir ela pelo int10 (nesse caso não é necessario setar o caracter no registrador al devido o próprio registrador al esta com o caracter a ser exibido depois do int16) mov ah,0x0 int 0x16 mov ah,0xe int 0x10 o int16 só captura caracter por caracter que foi pressionado do teclado, para a gente capturar uma sequencia de caracteres (string), temos que fazer um loop e armazenar na memoria mov cx,10 mov si,kodo

;quantidade de teclas capturadas ;endereço da nossa variavel

repetir: mov ah,0x0 int 0x16

;captura a tecla pressionada

mov [si],al inc si loop repetir

;joga a tecla apertada na memoria ;incrementa o si ou seja o endereço da memoria ;repete a quantidade do cx

jmp $ kodo: db 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0

;nossa alocação de 10 bytes (variavel)

o problema no exemplo anterior que precisamos digitar exatamente a quantidade de loops, para evitar isso podemos checar uma tecla especifica e sair do loop quando essa tecla for pressionada, como exemplo a tecla ENTER (0xd) mov cx,10 mov si,kodo

repetir: mov ah,0x0 int 0x16

;captura a tecla pressionada

cmp al,0xd je sair

;compara para ver se o enter foi pressionado ;se sim finaliza pulando para o sair

mov [si],al inc si loop repetir sair: jmp $ kodo: db 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0 é possivel manipular um disco seja um HD ou um disquete com a interrupção int13, o disco sera manipulado de forma bruta sem a leitura de partições ou seja sem nenhum tipo de formatação (fat, ntfs, ext e etc), com a função 0x0 do int13 resetamos o drive colocando a cabeça de leitura dele na posição 0, para usar a função temos que colocar no registrador dl o drive (0x0=floppy a: , 0x1=floppy b: , 0x80=hd 1, 0x81=hd 2), uma coisa interessante quando damos o boot na maquina ele chama a mbr e é setado o numero do drive atual no registrador dx mov ah,0x0 mov dl,0x80 int 0x13 quando usamos a função anterior caso o procedimento não seja possível ser feito como por exemplo não exista aquele determinado drive a flag CF sera modificada para 1 indicando a falha ou estara setada em 0 indicando o sucesso mov ah,0x0 mov dl,0x81 int 0x13 jc falha mov ah,0xe mov al,'S' int 0x10 jmp fim falha: mov ah,0xe mov al,'N' int 0x10 fim:

;caso seja sucesso exibe S

;caso falhe exibe o N

com a função 0x1 podemos chegar o estado do disco pelo seu controlador que sera retornado no registrador ah depois de uma determinada interrupção int13, sendo o seu retorno 0x0 para sucesso,

0x4 para setor não encontrado, 0x5 para falha de resetamento, 0x6 disquete removido, 0xa setor danificado, 0xb problema na trilha, essa função é perfeita para criar programas de checagem de erros como o chkdsk do windows mov ah,0x0 mov dl,0x80 int 0x13 mov ah,0x1 mov dl,0x80 int 0x13 cmp ah,0x0 je sucesso para a gente ler o hd ou disquete usamos a função 0x2 do int13, nessa função temos que especificar varias coisas entre elas a quantidade de setores que sera lido no registrador al, o setor a ser lido naquele no registrador cl (a contagem do setor começa no 1 sendo ele o setor 0), o cilindro ou a trilha a ser lido no registrador ch, a cabeça da onde sera lido no registrador dh, o drive a ser lido especificamos no registrador dl, e por fim o endereço de memoria para onde vamos jogar o setor que sera lido no registrador bx, no caso a leitura é por setores do hd e não por bytes sendo que cada setor é equivalente a 512 bytes, depois a gente pode acessar os bytes diretamente naquele endereço de memoria mov ah,0x2 mov al,0x1 mov bx,0x500 mov ch,0x0 mov cl,0x2 mov dh,0x0 mov dl,0x80 int 0x13 mov al,[0x500] mov al,[0x501] mov al,[0x502]

;leitura do hd ;quantidade de setores lido ;endereço da memoria que sera armazenado ;trilha 0 ;setor 1 (1 = setor 0, 2 =setor 1, 3 = setor 2 ...) ;cabeça 0 ;hd 1 ;vai ler o primeiro byte que estava no hd ;vai ler o segundo byte que estava no hd ;vai ler o terceiro byte que estava no hd

da mesma forma podemos adicionar codigos para ser executado naquele endereço de memoria bastando pular para ele (isso seria equivalente a ter programas ou modulos no nosso sistema) mov ah,0x2 mov al,0x1 mov bx,0x500 mov ch,0x0 mov cl,0x2 mov dh,0x0 mov dl,0x80 int 0x13 jmp 0x500

;vai executar o bytes carregados em tempo de execução

o registrador bx onde especificamos o endereço de memoria que sera armazenado, ele é usado em conjunto com o registrador de segmento es, com isso podemos colocar em outro segmento de memoria mudando o registrador es antes da interrupção sendo que isso é recomendado para evitar a sobrescrita do codigo no próprio segmento mov ax,600 mov es,ax mov ah,0x2 mov al,0x1 mov bx,0x500 mov ch,0x0 mov cl,0x2 mov dh,0x0 mov dl,0x80 int 0x13

;es:bx (600:500)

nos exemplos anteriores sera lido apenas 512 bytes do hd por causa do registrador al onde especificamos apenas a leitura de um setor, tambem sera lido o setor numero 1 de acordo com o registrador cl, em outras palavras sera lido do byte 512 do hd ate o byte 1023, a gente pode alterar esses dois valores para ler uma quantidade maior exemplo do byte 2048 ate 3071 mov ah,0x2 mov al,0x2 mov bx,0x500 mov ch,0x0 mov cl,0x5 mov dh,0x0 mov dl,0x80 int 0x13

;quantidade de setores lido (1023 = 2 * 512 - 1) ;setor 4 (2048 = 512 * 4)

da mesma forma é possível armazenar no hd ou no disquete com a função 0x3 do int13, o seu uso é parecido com o 0x2 sendo o registrado al a quantidade de setores lidos da memoria, bx o endereço de memoria da onde vamos ler para armazenar no drive, o registrador ch sendo a trilha ou cilindro onde vamos armazenar, o registrador cl o setor onde vamos armazenar no drive, o registrador dh sendo a cabeça no hd, e o registrador dl sendo o drive que vamos armazenar mov [0x500],'k' mov [0x501],'o' mov [0x502],'d' mov [0x503],'o' mov ah,0x3 mov al,0x1 mov bx,0x500 mov ch,0x0 mov cl,0x2 mov dh,0x0 mov dl,0x80 int 0x13

;escrita do hd ;quantidade de setores a ser gravado ;endereço da memoria que sera gravado ;trilha 0 no hd ;setor 1 no hd ;cabeça 0 ;hd 1

lembrando que sera gravado a quantidade de setores da memoria para o hd, no caso do exemplo anterior foi armazenado no hd 512 bytes da memoria, com isso tambem foi sobrescrito os 512 bytes do hd pelos 512 bytes que estava na memoria, mesmo que tenha adicionado a palavra kodo na memoria foi armazenado todos os 512 bytes independente do que estava neles, as vezes precisamos apenas armazenar uma quantidade exata sem sobrescrever os bytes do hd alem do necessario, para a gente fazer isso é bem simples bastando ler o setor do hd para a memoria modificar ele e depois armazenar assim não sera armazenado bytes aleatorios e sim os mesmos mov ah,0x2 mov al,0x1 mov bx,0x500 mov ch,0x0 mov cl,0x2 mov dh,0x0 mov dl,0x80 int 0x13 mov [0x500],'k' mov [0x501],'o' mov [0x502],'d' mov [0x503],'o' mov ah,0x3 mov al,0x1 mov bx,0x500 mov ch,0x0 mov cl,0x2 mov dh,0x0 mov dl,0x80 int 0x13

;ler o setor no hd e joga na memoria

;modifica os bytes desejado na memoria

;escreve o setor da memoria no hd

a interrupção int14 permite enviar e receber dados via porta serial, pela função 0x0 a gente configura a conexao da porta usada, o baud, paridade entre outros, no registrador dx a gente especifica a porta que vamos configurar (0 = com1, 1 = com2 …), no registrador al especificamos o paramentro em um único byte sendo que a cada bit desse byte tem uma representação no parametro (os bits 0 e 1 é o tamanho do byte enviado ou recebido, o bit 2 é o stop bit, o 3 e 4 é a paridade, o bit 5 6 e 7 é o baud) mov ah,0x0 mov al,11100010b mov dx,0x0 int 0x14

;111 = 9600 (baud), 00 = none (par), 0 = 0 (stop), 10 = 7bits ;com1

para enviar um byte via serial usamos a função 0x1, setamos no registrado al o byte que sera enviado e no registrador dx a porta mov ah,0x0 mov al,11100010b mov dx,0x0 int 0x14

mov ah,0x1 mov al,'k' mov dx,0x0 int 0x14 para o recebimento usamos a função 0x2, nela temos que especificar a porta no registrador dx, o byte sera retornado no registrador al, caso o byte seja recebido com sucesso o registrador ah sera igual a 0 sendo possível identificar quando o byte for recebido mov ah,0x0 mov al,11100010b mov dx,0x0 int 0x14 mov ah,0x2 mov dx,0x0 int 0x14 com a interrupção int19 a gente reinicia o sistema int 0x19 no sistema operacional MSDOS é possível usar a interrupção int21 sendo ela uma interrupção exclusiva desse sistema operacional e não da BIOS, com isso não é possível usar essa interrupção caso não esteja usando o sistema MSDOS porem podemos emular o próprio sistema dentro de maquinas virtuais ou emuladores como o dosbox para usar essa interrupção e os programas desse sistema em outro (não confundir os programas do msdos com programas não graficos para o prompt), uma das funções do int21 é o 0x0 que finaliza o programa atual mov ah,0x0 int 0x21 outra função com o mesmo efeito é o 0x4c sendo que nessa função podemos especificar o retorno no registrador al mov ah,0x4c mov al,0x0 int 0x21 para exibir um caracter usando a função 0x2, especificamos o caracter a ser exibido no registrador dl mov ah,0x2 mov dl,'k' int 0x21 tambem é possivel exibir uma string usando a função 0x9, o endereço da string colocamos no registrador dx sendo que a string deve terminar com o caracter cifrão indicando o final dela

mov ah,0x9 mov dx,texto int 0x21 texto: db “kodo no kami$” podemos digitar um caracter do teclado com a função 0x1, sera armazenado no registrador al e sera exibida na tela tambem (echo) mov ah,0x1 int 0x21 para entrar com um caracter do teclado sem exibir na tela usamos a função 0x8 mov ah,0x8 int 0x21 é possível entrar com uma string do teclado usando a função 0xa, no registrador dx especificamos o endereço de memoria que sera armazenado sendo que nesse endereço o primeiro byte seria equivalente ao tamanho de bytes maximo que podemos adicionar na string (recomendado é colocar os outros bytes como cifrão e tambem não é necessario armazenar exatamente aquele tamanho maximo podendo ser ate que o enter seja pressionado) mov ah,0xa mov dx,kodo int 0x21 kodo: db 10,”$$$$$$$$$” um outro exemplo de um programa para msdos onde escrevemos o nome e depois ele é exibido mov ah,0x9 mov dx,kodo int 0x21

;exibe “digite seu nome: ”

mov ah,0xa mov dx,variavel int 0x21

;armazena o nome digitado em variavel

mov ah,0x9 mov dx,kami int 0x21

;exibe “\nnome digitado foi ”

mov ah,0x9 mov dx,variavel+2 int 0x21

;exibe a variavel pulando dos caracteres que seria o tamanho

mov ah,0x0 int 0x21

;finaliza o programa

kodo: db "digite seu nome: $" kami: db 0xd,0xa,"nome digitado foi $" variavel: db 20,"$$$$$$$$$$$$$$$$$$" podemos retornar o drive atual com a função 0x19, sera retornando no registrador al (0 = a: , 1 = b: , 2 = c: …) mov ah,0x19 int 0x21 ou mudar o drive atual com a função 0xe, especificamos o drive no registrador al mov ah,0xe mov al,0x3 int 0x21

; d:

para criar um diretorio usamos a função 0x39 (o sistema msdos usa partições do tipo fat sendo a interrupção int21 tendo o acesso ao hd em cima desse tipo de partição e não de forma bruta como no int13), no registrador dx dessa função a gente especifica o endereço de uma string que tem o nome do diretorio mov ah,0x39 mov dx,kodo int 0x21 kodo: db “nudes” podemos remover um diretorio com a função 0x3a sendo seus argumentos parecido com o anterior mov ah,0x3a mov dx,kodo int 0x21 kodo: db “nudes” é possível mudar de directorio com a função 0x3b mov ah,0x3b mov dx,kodo int 0x21 kodo: db “nudes” alem das interrupções do sistema operacional msdos tambem temos as interrupções para o sistema unix (linux) que é a interrupção int80, diferente do sistema msdos que é um sistema ultrapassado a plataforma linux é usada constamente inclusive por servidores no mundo todo, os registradores usados nas interrupções do linux é de 32bits (64bits) ou seja é modo protegido e todas essas interrupções tambem são chamadas de sistemas na própria linguagem C ou em outra de linguagem de mais alto nivel, umas dessas funções é o 0x1 que seria api exit no linux que finaliza o programa atual sendo o seu argumento de retorno definido no registrador ebx

mov eax,0x1 mov ebx,0x0 int 0x80

;exit ;retorno 0

podemos exibir uma string usando a função 0x4 ou a api write do linux, no registrador ebx a gente define o descritor (0 – stdin, 1 – stdout, 2 – stderr … ), no registrador ecx setamos o endereço da nossa string, no registrador edx seria o tamanho da nossa string que sera exibida mov eax,0x4 mov ebx,0x1 mov ecx,kodo mov edx,12 int 0x80

;write ;stdout ;variavel kodo ;tamanho

kodo: db “kodo no kami” alguns compiladores permite subtrair endereços de memoria com isso podemos usar para retornar o tamanho entre dois labels que seria o inicio e o fim da string que por sua vez tambem é o tamanho da string mov eax,0x4 mov ebx,0x1 mov ecx,kodo mov edx,kodofim - kodo int 0x80

;tamanho

kodo: db “kodo no kami” kodofim: é possível a gente ler uma string do teclado com a função 0x3 ou read do linux, seu argumento é parecido com a anterior sendo o registrador ebx o descritor, o registrador ecx o endereço e o registrador edx o tamanho da alocação mov eax,0x3 mov ebx,0x0 mov ecx,kami mov edx,30 int 0x80

;read ;stdin ;variavel kami ;tamanho

kami: resb 30 para a gente criar um arquivo usamos a função creat ou 0x8, no registrador ebx passamos o nome do arquivo e no ecx a permissão do arquivo criado mov eax,0x8 mov ebx,kodo mov ecx,0x0 int 0x80

;creat ;animes.txt ;000

kodo: db “animes.txt” os valores das permissões são as mesmas listadas nos defines do arquivo stat.h do kernel do sitema linux bastando usar a operação or individualmente para cada um dos seus valores setados para formar a permissão final mov ecx,256 or ecx,128 or ecx,64 or ecx,32 or ecx,8 or ecx,4 or ecx,1

;usuario r ;usuario rw ;usuario rwx ;usuario rwx, grupo r ;usuario rwx, grupo rx ;usuario rwx, grupo rx, outros r ;usuario rwx, grupo rx, outros rx (rwxr-xr-x = 755)

mov eax,0x8 mov ebx,kodo int 0x80

;creat ;animes.txt

kodo: db “animes.txt” podemos deletar um arquivo com a função 0xa (unlink) mov eax,0xa mov ebx,kodo int 0x80

;unlink ;animes.txt

kodo: db “animes.txt” para renomear um arquivo com a função 0x26, especificamos o nome do arquivo antigo no registrador ebx e o novo nome no registrador ecx mov eax,0x26 mov ebx,kodo mov ecx,kami int 0x80 kodo: db “railgun.py” kami: db “misaka.py” com a função 0x27 (mkdir) a gente cria um novo diretorio, no registrador ebx especificamos o diretorio e no ecx a permissão mov eax,0x27 mov ebx,kodo mov ecx,493 int 0x80

;mkdir ;filmes ;perm = 755

kodo: db “filmes” por outro lado com a função 0x28 a gente remove um diretorio (rmdir)

mov eax,0x28 mov ebx,kodo int 0x80

;rmdir ;filmes

kodo: db “filmes” para ler ou escrever em um arquivo usamos a função 0x5 (open), especificamos no registrador ebx o endereço da string do arquivo, no registrador ecx a permissão para leitura ou escrita (0 = leitura, 1 = escrita, 2 = leitura e escrita), e no registrador edx a permissão do arquivo, sera retornado no registrador eax o descritor do arquivo mov eax,0x5 mov ebx,kodo mov ecx,0x1 mov edx,0x0 int 0x80

;open ;arquivo.txt ;escrita ;0 ~ não é usado caso o arquivo já exista

kodo: db “arquivo.txt” tambem podemos fechar o arquivo com a função 0x6 (close) sendo que para ser armazenado no arquivo é necessario que ele seja fechado, nessa função temos que especificar o descritor que vamos fechar no registrador ebx mov eax,0x5 mov ebx,kodo mov ecx,0x1 mov edx,0x0 int 0x80 mov ebx,eax mov eax,0x6 int 0x80

;abre o arquivo para escrita, retorna o descrito em eax ;copia o descrito do eax para o ebx ;close

kodo: db “arquivo.txt” para escrever no arquivo usamos a função 0x4 (write), a diferença que no registrador ebx definimos o descritor do arquivo e não do stdio mov eax,0x5 mov ebx,kodo mov ecx,0x1 mov edx,0x0 int 0x80

;open ~ escrita

mov ebx,eax mov eax,0x4 mov ecx,kami mov edx,kamif - kami int 0x80

;write

mov eax,0x6 int 0x80

;close

kodo: db “arquivo.txt” kami: db “programando em asm” kamif: a leitura do arquivo é bem parecido mudando a permissão na abertura e tambem utilizando a função 0x3 para ler o arquivo mov eax,0x5 mov ebx,kodo mov ecx,0x0 mov edx,0x0 int 0x80

;open ~ leitura

mov ebx,eax mov eax,0x3 mov ecx,kami mov edx,50 int 0x80

;read

mov eax,0x6 int 0x80

;close

kodo: db “arquivo.txt” kami: resb 50 podemos pular para determinado trecho do arquivo usando a função 0x13 (lseek), no registrador ebx especificamos o descritor, no registrador ecx a posição onde vamos pular, no registrador edx sera da onde ira partir a contagem da posição do pulo (0 = inicio, 1 = atual, 2 = fim), o lseek pode ser usado para diversos fins como recomeçar a leitura do arquivo sem precisar fechar, leitura do arquivo byte por byte ou ate mesmo uma quantidade de bytes por vez, leitura ou escrita em uma posição especifica do arquivo entre outras coisas mov eax,0x5 mov ebx,kodo mov ecx,0x0 mov edx,0x0 int 0x80 mov ebx,eax mov eax,0x13 mov ecx,10 mov edx,0 int 0x80

;open ~ leitura ;lseek ;10 posição ;inicio do arquivo

para criar um link de um arquivo com a função 0x9, seu argumento é o arquivo que vamos criar o link no registrador ebx e onde sera criado o arquivo especificamos no registrador ecx mov eax,0x9

;link

mov ebx,kodo mov ecx,kami int 0x80

;arquivo ;atalho

kodo: db “arquivo.txt” kami: db “link.txt” com o chmod é possivel modificar a permissão do arquivo usando o 0xf, no registrador ebx colocamos o arquivo e no registrador ecx a nova permissão mov eax,0xf mov ebx,kodo mov ecx,488 int 0x80

;chmod ;arquivo.txt ;perm = 750

kodo: db “arquivo.txt” tambem é possível a gente criar a nossa propria interrupção customizada no codigo, no modo real para criar uma interrupção bastaria modificar os primeiros bytes da memoria sendo ele o endereços do vetor de interrupção que fica entre 0x0 ate 0x3ff, no modo protegido o sistema já usa um descrito para a tabela de interrupção o IDT, para modificar o vetor de interrupção no modo real bastaria definir diretamete o endereço da rotina nele, sendo 4 bytes no total 2 bytes para o endereço da rotina e 2 bytes para o segmento da rotina da interrupção, o endereço maximo do vetor de interrupção é o endereço 0x3ff então se a gente dividir ele por 4 daria 255 interrupções diferentes, cada interrupção no vetor é composto por 4 bytes em sequencia ou seja se a gente quiser criar a interrupção int50 temos que descobrir o endereço do vetor onde seria esse int50 e armazenar o endereço e o segmento da rotina naquele determinado endereço do vetor com isso a interrupção ja estaria criada, o calculo para o endereço do vetor é bem simples sendo o numero da interrupção multiplicado por 4 para armazenamento do endereço e para o segmento seria o endereço anterior somado mais 2 offset no vetor do int50 = 0x140 (0x50 * 4) segmento no vetor do int50 = 0x142 (0x50 * 4 + 2) como já sabemos bastaria simplesmente a gente colocar o endereços e o segmento da subrotina no vetor de interrupção para criar a nossa interrupção mov ax,0x0 mov es,ax mov [es:0x140], kodo mov [es:0x142], cs kodo: é sempre recomendado desativar as interrupções quando for modificar a tabela de interrupção e ativar elas depois da sua modificação com as instruções cli e sti mov ax,0x0 mov es,ax

cli mov [es:0x140], kodo mov [es:0x142], cs sti kodo: na subrotina usamos a instrução iret para retornar de uma interrupção, diferente das subrotinas que chamadas pelo call que usamos o ret para o retorno mov ax,0x0 mov es,ax cli mov [es:0x140], kodo mov [es:0x142], cs sti kodo: iret depois de criada a interrupção podemos chamar ela normalmente mov ax,0x0 mov es,ax cli mov [es:0x140], kodo mov [es:0x142], cs sti int 0x50 kodo: iret quando a gente usa a instrução int é jogado na pilha o segmeto e o endereço atual para que seja possível o retorno para ultima posição, podemos manipular esse endereço e força a volta para qualquer outro endereço bastando empilhar o segmento e o endereço mov ax,0x0 mov es,ax cli mov [es:0x140], kodo mov [es:0x142], cs sti int 0x50 nop nop nop kodo:

;vai retorna da interrupção direto para esse endereço

push 0x0 push 0x7c17 iret na rotina da interrupção podemos manipular os valores passado pelos registradores para criar funções diferentes na mesma interrupção ou usar os registradores para retornar valores das interrupções, inclusive tambem podemos usar uma interrupção dentro da outra criando novas funcionalidades de outras fuções de outras interrupções, o exemplo abaixo seria a leitura ou a exibição de um caracter em uma única interrupção int50 (0x0 leitura do caracter do teclado e 0x1 sendo a exibição na tela) mov ax,0x0 mov es,ax cli mov [es:0x140], kodo mov [es:0x142], cs sti mov ah,0x0 int 0x50

;leitura do caracter pelo teclado ;minha interrupção

mov ah,0x1 int 0x50

;exibição do caracter ;minha interrupção

jmp $ kodo: cmp ah,0x0 je teclado cmp ah,0x1 je exibir jmp sair teclado: mov ah,0x0 int 0x16 jmp sair exibir: mov ah,0xe int 0x10 sair: iret 2.9 – x86: IO alem do acesso direto aos perifericos mapeados na memoria ou por alguma interrupção, tambem é possível o acesso a eles pelas portas de entrada e saida IO, essas portas de entradas e saidas são utilizadas para o acesso direto a determinado DMA ou barramento especifico do computador como o controlador do hd, a porta serial e paralela, os perifericos conectados via PCI, teclado via PS/2,

impressora via LPT, os timers (pit) e o pic interno, muitas arquiteturas usam o IO para acesso externo tanto de entrada quanto de saida por algum barramento sendo que algumas arquiteturas usam o IO como pinos especificos como é o caso dos microcontroladores, na arquiteuta x86 podemos enviar e receber dados por esses barramentos usando as instruções out e in, tanto a instrução out e a instrução in podem ser usada sem restrição em modo real porem quando o sistema esta em modo protegido depende exclusivamente do nivel de privilegio, a instrução out é usada para enviar dados para algum barramento especifico e a instrução in é usada para receber os dados passados por um barramento especifico, para usar a instrução out setamos o valor que vamos passar no registrador ax (é preciso que seja exatamente o registrador A ~ al, ax ou eax), depois passamos para a instrução out a porta usada e o registrador com o dado que sera enviado por ela mov al,202 out 0x92,al

;envia 202 para porta 0x92

o mesmo vale para a instrução in porem invertemos o registrador e a porta in al,0x92

;recebe o valor da porta 0x92

podemos especificar apenas portas com valores de 8 bits no maximo quando passamos elas de forma imediata, para portas de 16bits usamos o registrador dx com a porta mov dx,0x3f8 out dx,al da mesma forma para usar portas de 16bits na instrução in temos que usar o registrador dx mov dx,0x3f8 in al,dx podemos por exemplo pegar a tecla pressionada por uma interrupção 0x16 como vimos anteriomente ou por uma entrada de PS/2 na porta 0x60 usando a instrução in, a diferença que a interrupção usa os caracteres já formatados de acordo com o ascii já a entrada IO usa o codigo conforme a ordem das teclas o famoso scancode que é gerado pelo teclado que pode variar de acordo com o equipamento usado (no meu teclado por exemplo as teclas numericas é 1 = 02, 2 = 03, 3 = 04, 4 = 05 … ) mov dx,0x60 ;porta IO PS/2 in al,dx ;leitura do barramento daquela porta cmp al,0x2 je tecla1 cmp al,0x3 je tecla2

;compara para ver se a tecla pressionado é igual ao valor 2 ;se for igual pula para tecla1 ;compara para ver se a tecla pressionado é igual ao valor 3 ;se for igual pula para tecla3

tecla1:

;pressionamento da tecla 1

tecla2:

;pressionamento da tecla 2

diferente da interrupção o programa não vai ficar parado esperando apertar a tecla então temos que fazer essa opção sendo uma das formas comparar o registrador al caso nenhum valor tenha sido

atribuido da tecla especifica ele continua no loop capturando as teclas kodoloop: mov dx,0x60 in al,dx cmp al,0x2 je tecla1 cmp al,0x3 je tecla2 jmp kodoloop

;se nenhuma tecla foi pressionada volta para kodoloop

tecla1: tecla2: para enviar via porta serial COM1 usamos a porta 0x3f8 para os dados mov dx,0x3f8

;porta COM1

mov al,'k' out dx,al

;envia k via serial

mov al,'o' out dx,al

;envia o via serial

mov al,'d' out dx,al

;envia d via serial

mov al,'o' out dx,al

;envia o via serial

tambem é possível receber dados pela porta serial COM1 com a instrução in mov dx,0x3f8 mov al,0x0

;porta COM1 ;seta o valor 0

repetir: cmp al,0x0 jne sair

;compara o al com o valor 0 ;se for diferente de 0 ele pula para sair

in al,dx jmp repetir

;se não ele recebe o valor da porta COM1 ;volta para o repetir

sair: abaixo tem uma lista de algumas das portas usadas porta

descrição

0x0 - 0x1f

primeiro controlador DMA

0x20 - 0x21

primeiro PIC

0x40 - 0x47

PIT

0x60 - 0x64

contralador PS/2 (8042)

0x70 - 0x71

CMOS e RTC

0x80 - 0x8f

DMA (paginação)

0x92

A20

0xa0 - 0xa1

segundo PIC

0xc0 - 0xdf

segundo controlador DMA

0x170 - 0x177

segundo controlador de disco ATA

0x1f0 - 0x1f7

primeiro controlador de disco ATA

0x278 - 0x27a

porta paralela

0x2f8 - 0x2ff

segunda porta serial (COM2)

0x3b0 - 0x3df

VGA

0x3f0 - 0x3f7

controlador de disquete

0x3f8 - 0x3ff

primeira porta serial (COM1) 3.0 – x86: FPU

os processadores alem de fazer operações aritmeticas com numeros inteiros, boa parte deles tambem fazem operações com pontos flutuantes que são os numeros com virgula ou numeros com casas decimais (numeros reais), o problema nesse tipo de numero é forma que ele é armazenado ou seja a forma que o processador deve interpretar o próprio numero em formato binario, a sua representação é complexo comparado aos numeros inteiros em formato binario já que o numero inteiro é apenas o numero em formato binario (com exceção os numeros negativos onde tem uma representação um pouco diferente e de forma invertida lendo do maior para o menor para representar aquele valor negativo), no ponto flutuante a sua representação é em notação cientifica em binario onde os primeiros bits representa o sinal seguido de uma sequencia de bits representando o expoente e o restante dos bits sendo a mantissa, na arquitetura x86 para as operações de ponto flutuante foi utilizado um co-processador externo chamado 8087 (x87) que futuramente seria integrado dentro do próprio processador principal, nas operações de ponto flutuante são usados instruções especificas chamadas instruções de fpu (float point unit), alem de novos registradores para operações com ponto flutuante entre eles temos os registradores de st0 ate o st7, uma das diferenças dos registradores de fpu que não podemos mover os valores livremente de forma imediata como nos registradores de uso gerais, nesses registradores de fpu temos que carregar eles da memoria usando a instrução fld fld [kodo] kodo dq 31.5 o funcionamento dos registradores de fpu st0 ate o st7 são parecidos com a pilha de memoria, o registrador st0 sempre aponta para o topo ou seja o ultimo dado carregado pela instrução fld que sempre estara armazenado no st0, conforme os dados são carregados são jogado para o topo da pilha do fpu que seria o registrador st0 e os outros são movidos para os registradores mais alto que

o registrador atual deles (quando o registrador st7 estiver com um valor carregado o mesmo sera movido para o registrador st0 novamente) fld [kodo] fld [kami]

;1.68 (st1) ← era o st0 ;989.223 (st0) ← ultimo dado carregado é atualmente o st0

kodo dq 1.68 kami dq 989.223 é possível usar numeros negativos tambem fld [kodo] kodo dq -10.8 tambem é possível carregar valores valores dword fld [kodo] kodo dd 5.5 podemos carregar um valor zerado com a instrução fldz para o topo da pilha do fpu fldz para carregar o valor 1.0 para o topo da pilha de fpu usamos a instrução fld1 fld1 tambem é possível carregar o valor de PI para o topo da pilha do fpu com a instrução fldpi fldpi da mesma forma podemos armazenar o valor em um endereço da memoria usando a instrução fst, sera armazenado o registrador st0 na memoria e sera descartado o valor, lembrando que o valor armazenado não sera em formato inteiro e sim em formato flutuante fld [kodo] fst [kami]

;carrega 5.6 para o registrador st0 ;salva o valor do registrador st0 na memoria

kodo dq 5.6 kami dq 0 podemos carregar um numero em formato inteiro para o topo do fpu usando a instrução fild, o numero carregado sera armazenado no st0 como formato flutuante fild [kodo] kodo dq 50

se a gente armazenar o exemplo anterior com a instrução fst sera armazenado em formato flutuante, essa seria uma boa forma de converter numeros em formato inteiros para formato flutuante fild [kodo] fst [kami] kodo dq 50 kami dq 0 tambem podemos armazenar o numero em formato inteiro com a instrução fist, da mesma forma isso pode ser usado para converter de float para inteiro fld [kodo] fist [kami] kodo dq 5.98 kami dq 0 podemos zerar toda pilha do fpu usando a instrução finit fld [kodo] fld [kami] finit kodo dq 10.6 kami dq 58.4 é possivel somar o registrador st0 e o registrador st1 com a instrução fadd, o resultado sera armazenado no registrador st0 fld [kodo] fld [kami] fadd kodo dq 7.8 kami dq 3.5 podemos especificar os registradores de fpu usados na operação da instrução fadd, porem é necessario que sempre tenha o registrador st0 na operação fld [kodo] fld [kami] fld [fts315] fadd st0,st2 kodo dq 7.8 kami dq 3.5 fts315 dq 8.9

alem da adição tambem podemos fazer a subtração com a instrução fsub, a subtração é feita invertida do st1 - st0 e armazenado no st0 fld [kodo] fld [kami] fsub kodo dq 35.3 kami dq 12.2 da mesma forma podemos especificar os registradores de fpu com qual vamos subtrair fld [kodo] fld [kami] fsub st1,st0 kodo dq 35.3 kami dq 12.2 podemos usar esse recursos da subtração e adição para mover valores dos registradores, um exemplo seria se eu quiser desempilhar o registrador st2 fsub st0,st0 fadd st0,st2 fst kodo

;zera o registrador st0 subtraindo ele por ele mesmo ;adiciona o valor ao registrador st0 ;salva o registrador st0 na memoria

kodo dq 0 tambem temos a instrução fmul para multiplicação fld [kodo] fld [kami] fmul kodo dq 5.2 kami dq 2.0 na instrução fmul tambem pode ser especificado os registradores fld [kodo] fld [kami] fmul st0,st1 kodo dq 5.2 kami dq 2.0 para a gente fazer a divisão usamos a instrução fdiv, da mesma forma que o fsub o fdiv é feito invertido o st1 / st0 fld [kodo]

fld [kami] fdiv kodo dq 9.0 kami dq 4.0 podemos especificar os registradores de fpu na instrução fdiv sendo que o dividendo é sempre o primeiro registrador e o divisor o segundo fld [kodo] fld [kami] fdiv st1,st0 kodo dq 9.0 kami dq 4.0 é possível retornar a raiz quadrada usando a instrução fsqrt, a operação sera feita no registrador st0 fld [kodo] fsqrt kodo dq 9.0 com a instrução fabs conseguimos o valor absoluto do numero fld [kodo] fabs kodo dq -1.5 podemos retornar o seno com a instrução fsin sendo o valor passado em radiano e não em grau fld [kodo] fsin kodo dq 0.78539816

;45º

para o coseno usamos fcos fld [kodo] fcos kodo dq 0.87266463

;50º

é possível comparar valores no formato flutuante com a instrução fcom, sera armazenado no registrador de status do fpu fld [kodo] fld [kami]

fcom kodo dq 56.0 kami dq 30.2 tambem é possível especificar o segundo registrador na comparação porem o st0 deve ser sempre o primeiro fld [kodo] fld [kami] fld [fts315] fcom st0,st2 kodo dq 56.0 kami dq 30.2 fts315 dq 40.5 o registrador status do fpu (fstat) é um registrador de 16bits e o seu funcionamento é parecido com o registrador das flags sendo usado para sinalizar, na comparação é utilizado os bits 8,9,10 e 14 que são chamados de flags CR de status do fpu (não confundir com o registrador de controle CR da cpu) 15bit

0bit CR3

CR2 CR1 CR0

em uma comparação quando o registrador st0 é maior que o outro então é setado o cr0, cr2 e cr3 com o valor 0 fld [kodo] fld [kami] fcom

;cr0=0, cr2=0, cr3=0

kodo dq 1.0 kami dq 20.2 quando o segundo registrador é maior que o st0 então a flag cr0 é setada em 1 e o resto em 0 fld [kodo] fld [kami] fcom

;cr0=1, cr2=0, cr3=0

kodo dq 200.5 kami dq 8.3 se os dois numeros forem iguais então o cr3 vai esta setado em 1 e o restante em 0 fld [kodo] fld [kami] fcom kodo dq 3.15

;cr0=0, cr2=0, cr3=1

kami dq 3.15 não é possível ter acesso direto ao registrador de status do fpu para isso temos armazenar ele na memoria com instrução fstsw seguido do endereço fld [kodo] fld [kami] fcom fstsw [variavel] kodo dq 31.5 kami dq 31.5 variavel dw 0 tambem podemos armazenar no registrador ax fld [kodo] fld [kami] fcom fstsw ax kodo dq 31.5 kami dq 31.5 com a instrução sahf sera armazenado o resultado nas flags da cpu direto do registrador ax, com isso podemos usar as instruções ja, jb e jz para pular sobre determinada condição, sendo o ja ira pular caso o registrador st0 seja o maior, jb caso o registrador st0 seja o menor ou jz quando os dois registradores forem iguais fld [kodo] fld [kami] fcom fstsw ax sahf jz igual ja maior jb menor igual: maior: menor: kodo dq 10.0 kami dq 30.0 podemos comparar e setar direto na flag com a instrução fcomi fld [kodo] fld [kami]

fcomi jz igual ja maior jb menor igual: maior: menor: kodo dq 30.0 kami dq 10.0 é possível especificar o registrador na instrução fcomi tambem fld [kodo] fld [kami] fcomi st0,st1 kodo dq 30.0 kami dq 10.0 3.1 – x86: MMX as instruções mmx são instruções de empacotamento de dados de tamanho variado sem grande perda de velocidade, sendo usada nela a metodologia de SIMD (única instrução, multiplos dados), o mmx surgiu a partir dos processadores da família pentium em 1996 e o significado da sigla mmx é MultiMedia eXtension, ele trouxe consigo 8 novos registradores gerais de 64bits que foram denominado de mm que vai do mm0 ate o mm7, o mmx permite saturação do numero evitando o overflow e underflow alem da manipulação com sinal ou sem sinal do numero e a sua quantidade de bytes manipulado naquele determinado momento de forma paralela, o mmx usa os registradores de fpu para o armazenamento dos registradores de mmx então não é possível usar instruções de fpu enquanto estiver usando os de mmx ou vice versa, podemos atribuir um valor ao registrador do mmx usando a instrução movq movq mm0,[kodo] kodo dq 10 o mesmo poderia ser feito na sintaxe at&t como no compilador gas movq (kodo),%mm0 kodo .quad 10 diferente do fpu que se limita a atribuição ao topo da pilha sendo ela o registrador st0, no mmx podemos atribuir para qualquer registrador dele como acontece nos registradores de uso geral da propria cpu movq mm4,[kodo]

kodo dq 10 tambem podemos atribuir o valor de um registrador mmx para outro registrador mmx movq mm1,[kodo] movq mm2,mm1 kodo dq 100 ou de um registrador mmx direto para memoria movq [kodo],mm0 kodo dq 0 com a instrução movd passamos valores de 32bits para o registrador mmx movd mm0,[kodo] kodo dd 10 podemos armazenar tambem numeros negativos movq mm0,[kodo] kodo dq -80 tambem é possível passar valores no formato flutuante para os registradores, ṕorem as operações sera feita como numeros inteiros e não operações como ponto flutuante movq mm0,[kodo] kodo dq 50.6 o mmx permite a gente manipular o numero como um todo ou dividir ele em partes sendo uma quantidade especifica daquele numero em um certo agrupamento de bits, se a gente mover o valor 0x9052781251671287 para um registrador mmx podemos manipular esse valor como o numero completo de 64bits ou dois valores separados de 32bits, o mesmo pode ser feito com 4 valores separados de 16bits ou 8 valores de 8bits 64bits

9052781251671287

32bits

90527812

16bits

9052

8bits

90

51671287 7812 52

78

5167 12

51

1287 67

12

87

para a gente somar dois registradores mmx usando os 64bits dele basta utilizar a instrução paddq, assim a operação sera feita com o numero como um todo sendo um único registrador de 64bits

movq mm0,[kodo] movq mm1,[kami] paddq mm0,mm1

;0x9052781251671287 + 0x5873579612458965

kodo dq 0x9052781251671287 kami dq 0x5873579612458965 outra forma seria somar um registrador direto a um valor em um endereço de memoria movq mm0,[kodo] paddq mm0,[kami] kodo dq 0x9052781251671287 kami dq 0x5873579612458965 embora as operações seja feito usando todos os bits do registrador não precisamos usar todos os bits colocando um valor menor (isso seria equivalente a ter o numero e vários zeros a esquerda) movq mm0,[kodo] movq mm1,[kami] paddq mm0,mm1 kodo dq 0x90 kami dq 0x58 para somar dois registradores mmx usando agrupamento de 32bits usamos a instrução paddd, nesse caso os primeiros 32bits da fonte sera somados aos primeiros 32bits do destino, e os outros 32bits restante da fonte tambem sera somado aos 32bits restantes do destino de forma separada ou seja 2 grupos de 32bits sendo somados de forma paralela movq mm0,[kodo] movq mm1,[kami] paddd mm1,mm0

;0x33333333 + 0x22222222, 0x44444444 + 0x88888888

kodo dq 0x3333333344444444 kami dq 0x2222222288888888 podemos fazer o mesmo com 4 grupos de 16bits usando a instrução paddw para somar movq mm0,[kodo] movq mm1,[kami] paddw mm1,mm0

;0x2222 + 0x5615, 0x8888 + 0x9874, 0x9999 + 0x6786, 0x1111 + 0x4320

kodo dq 0x2222888899991111 kami dq 0x5615987467864320 para somar em 8 grupos de 8bits usamos a instrução paddb movq mm0,[kodo]

movq mm1,[kami] paddb mm1,mm0

;0x01 + 0x35, 0x48 + 0x47, 0x75 + 0x82, 0x78 + 0x45, 0x62+ 0x65 ...

kodo dq 0x0148757862483541 kami dq 0x3547824565687521 quando somamos um numero e o seu resultado é acima do tamanho maximo do agrupamento o acontece o overflow o mesmo vale para o oposto quando decrementamos um numero abaixo de zero, no próximo exemplo o resultado é 0x02 e não 0x102 já que estamos trabalhando com byte onde o seu maior numero é 0xff movq mm0,[kodo] movq mm1,[kami] paddb mm1,mm0

;02

kodo dq 0xf2 kami dq 0x10 os registradores mmx permite a saturação com isso não é possível o overflow e nem o underflow, quando o valor ultrapassar o valor maximo ou valor minimo ele fica com esse valor maximo ou com valor minimo, para especificar que a operação esta sendo usado a saturação usamos o “s” na instrução (paddsb e paddsw), essa operação sera feita com numeros com sinais movq mm0,[kodo] movq mm1,[kami] paddsb mm1,mm0

;7f

kodo dq 0x70 kami dq 0x20 tambem podemos fazer usando numeros sem sinais com a instrução paddusb e paddusw movq mm0,[kodo] movq mm1,[kami] paddusb mm1,mm0

;ff

kodo dq 0xf2 kami dq 0x10

para a subtração com um único grupo de 64bits usamos a instrução psubq movq mm0,[kodo] movq mm1,[kami] psubq mm0,mm1 kodo dq 50 kami dq 8 com a instrução psubd subtraimos com agrupamento de 32bits sendo 2 grupos de 32bits

movq mm0,[kodo] movq mm1,[kami] psubd mm0,mm1 kodo dq 0x9054903284537920 kami dq 0x2158458641254687 para subtrair com quatro grupos de 16bits usamos a instrução psubw movq mm0,[kodo] movq mm1,[kami] psubw mm0,mm1 kodo dq 0x5098711234870569 kami dq 0x1548937658219647 tambem temos a instrução psubb que subtrair grupos de 8bits sendo 8 grupos ao todo movq mm0,[kodo] movq mm1,[kami] psubb mm0,mm1 kodo dq 0x3728468216549872 kami dq 0x2458745213547899 é possível usar saturação nas operações de substração com sinal com as instruções psubsb e psubsw movq mm0,[kodo] movq mm1,[kami] psubsb mm0,mm1 kodo dq 0x3728468216549872 kami dq 0x2458745213547899 ou na subtração com saturação sem sinal com a instrução psubusb e psubusw movq mm0,[kodo] movq mm1,[kami] psubusb mm0,mm1 kodo dq 0x3728468216549872 kami dq 0x2458745213547899 a multiplicação do mmx deve ser feita com agrupamento de 16bits apenas, na multiplicação usamos a instrução pmullw movq mm0,[kodo] movq mm1,[kami] pmullw mm0,mm1

kodo dq 0x0201350213240002 kami dq 0x0002010050100003 podemos fazer operações bit a bit com o mmx entre entre essa operações temos a and com a instrução mmx pand, essas instruções bit a bit é feito em um único agrupamento de 64bits movq mm0,[kodo] movq mm1,[kami] pand mm0,mm1 kodo dq 0x6000 kami dq 0x505 outra instrução bit a bit é o por, essa instrução permite a operação or movq mm0,[kodo] movq mm1,[kami] por mm0,mm1 kodo dq 0x1125 kami dq 0x1252 tambem podemos fazer a operação bit a bit xor usando a instrução pxor movq mm0,[kodo] movq mm1,[kami] pxor mm0,mm1 kodo dq 0x987654 kami dq 0x50 para o deslocamento bit a bit a esquerda usamos a instrução psllq, sera deslocado um único grupo de 64bits movq mm0,[kodo] movq mm1,[kami] psllq mm0,mm1 kodo dq 0x5 kami dq 1 para deslocar a direita em agrupamentos de 64bits usamos a instrução psrlq movq mm0,[kodo] movq mm1,[kami] psrlq mm0,mm1 kodo dq 0x5056 kami dq 2

com a instrução pslld deslocamos a esquerda usando agrupamentos de 32bits movq mm0,[kodo] movq mm1,[kami] pslld mm0,mm1 kodo dq 0x5 kami dq 1 ou com psrld deslocamos a direita com agrupamento de 32bits movq mm0,[kodo] movq mm1,[kami] psrld mm0,mm1 kodo dq 0x5056 kami dq 2 em agrupamentos de 16bits o deslocamento para esquerda deve ser usado a instrução psllw movq mm0,[kodo] movq mm1,[kami] psllw mm0,mm1 kodo dq 0x5 kami dq 1 já o deslocamentos a direita em 16bits usamos a instrução psrlw movq mm0,[kodo] movq mm1,[kami] psrlw mm0,mm1 kodo dq 0x5056 kami dq 2 podemos comparar dois registradores mmx para ver se os bytes neles são iguais usando a instrução pcmpeqb, nessa comparação sera usado agrupamento de 8bits, caso o primeiro numero seja igual ao segundo sera gerado o byte 0xff no destino caso seja diferente sera gerado o valor 0x0 no destino naquele determinado byte movq mm0,[kodo] movq mm1,[kami] pcmpeqb mm0,mm1

;0xffff00ff00

kodo dq 0x5010876405 kami dq 0x5010606412 podemos fazer a comparação usando 4 agrupamentos de 16bits com a instrução pcmpeqw

movq mm0,[kodo] movq mm1,[kami] pcmpeqw mm0,mm1

;0xffff0000

kodo dq 0x10208590 kami dq 0x10205090 ou dois agrupamentos de 32bits com pcmpeqd para a comparação movq mm0,[kodo] movq mm1,[kami] pcmpeqd mm0,mm1

;0xffffffff00000000

kodo dq 0x3290647810208590 kami dq 0x3290647851020309 podemos juntar os bytes de dois registradores mmx em um único registrador mmx, para que isso seja possível temos que fazer uma pequena conversão tratando um determinado agrupamento de bits como um agrupamento menor com saturação, a instrução packuswb converte agrupamentos de 16bits em agrupamentos de 8bits, caso o valor no agrupamento ultrapasse o valor maximo de 8bits sera mantido o valor maximo de 8bits que é o valor saturado (0xff), sera armazenado no regitrador destino a metade dos bytes para um registrador e a outra metade para o outro registrador movq mm0,[kodo] movq mm1,[kami] packuswb mm0,mm1

;0x8577ff05ff564732 = 0x8577ff05, 0xff564732

kodo dq 0x1011005600470032 kami dq 0x0085007710500005 quando usamos registradores mmx os registradores de fpu fica inutilizavel naquele momento, o problema que depois do uso do mmx para a gente usar o fpu temos que limpar os registradores de mmx com a instrução emms, nunca tente utilizar mmx e fpu junto ou o fpu sem limpar o mmx antes movq mm0,[kodo] movq mm1,[kami] emms fld [flavio] kodo dq 0x50050 kami dq 0x315 flavio dq 50.0 3.2 – x86: 3DNow o 3dnow é uma tecnologia adicionada a partir dos computadores k6-2 em 1998, essa tecnologia trouxe novas instruções que são usados em conjunto com o mmx para instruções com pontos flutuantes, o 3dnow teve alguma melhorias ao decorrer do tempo como “3dnow extended” e o “3dnow pro”, trazendo algumas instruções novas e permitindo a sua interação com algumas tecnologias, para o 3dnow usamos as mesmas instruções do mmx, como exemplo podemos mover o

valor da memoria para os registrador mmx com o movd movq mm0,[kodo] movq mm1,[kami] kodo dd 10.7 kami dd 51.92 uma das instruções do 3dnow é o pfadd que permite somar dois registradores mmx usando a operação com ponto flutuante movd mm0,[kodo] movd mm1,[kami] pfadd mm0,mm1 kodo dd 10.7 kami dd 51.92 lembrando que o mesmo pode ser feito usando a sintaxe at&t movd (kodo),%mm0 movd (kami),%mm1 pfadd %mm1,%mm0 kodo .float 10.7 kami .float 51.92 para subtração usando o 3dnow usamos a instrução pfsub movd mm0,[kodo] movd mm1,[kami] pfsub mm0,mm1 kodo dd 10.6 kami dd 5.3 podemos fazer a multiplicação com pfmul movd mm0,[kodo] movd mm1,[kami] pfmul mm0,mm1 kodo dd 10.5 kami dd 2.0 é possível comparar usando a instrução pfcmpeq, caso os numeros sejam iguais então sera atribuido para o destino o numero 0xffffffff, caso seja diferente sera atribuido 0x00000000 movd mm0,[kodo] movd mm1,[kami]

pfcmpeq mm0,mm1 kodo dd 15.12 kami dd 15.12 para comparar se o numero é maior usamos a instrução pfcmpgt, sera atribuido ao destino 0xfffffff caso seja maior ou 0x00000000 caso seja menor movd mm0,[kodo] movd mm1,[kami] pfcmpgt mm0,mm1 kodo dd 20.1 kami dd 15.12 podemos converter um numero no formato inteiro para o formato float usando a instrução pi2fd movd mm0,[kodo] pi2fd mm1, mm0 kodo dd 10 tambem pode ser feito o oposto um numero float para inteiro com a instrução pf2id movd mm0,[kodo] pf2id mm1, mm0 kodo dd 10.5 como o 3dnow utiliza os registradores de mmx, não é possível usar instruções fpu ao mesmo tempo, para utilizar a fpu temos que usar a instrução emms, tambem existe a instrução femms junto ao 3dnow que tem o mesmo proposito do emms porem melhorado e mais rapido movd mm0,[kodo] movd mm1,[kami] pfadd mm0,mm1 movd [flavio],mm0 femms fld [kodo] kodo dd 5.5 kami dd 2.0 flavio dd 0 3.3 – x86: SSE o sse (Streaming Simd Extensions) é um conjunto de instruções que tem o seu funcionamento bem semelhante ao mmx, ele foi introduzido a partir do pentium 3 em 1999, com o tempo teve novas melhorias (sse2, sse3, ssse3, sse4 e sse5), sendo uma das diferenças entre o sse e o mmx que os valores nos registradores sse não são armazenados nos registradores da fpu como acontece com os

do mmx, outra diferença seria no tamanho dos registradores do sse que são registradores de 128bits de tamanho enquanto os do mmx são apenas de 64bits, existem 8 registradores sse para uso geral que são os registradores xmm, que vão do registrador xmm0 ate o registrador xmm7, em processadores com arquitetura de 64bits são 16 registradores de sse que vão do xmm0 ate o xmm15, para a gente mover um determinado valor de 64bits da memoria para um registrador sse usamos a instrução movq movq xmm0,[kodo] kodo dq 900 o mesmo pode ser feito na sintaxe at&t movq (kodo),%xmm0 kodo .quad 900 da mesma forma podemos mover 32bits usando a instrução movd movd xmm4,[kodo] kodo dd 900 podemos mover 64bits do registrador sse para outro registrador sse com a instrução movq movq xmm0,[kodo] movq xmm1,xmm0 kodo dq 900 tambem podemos mover 64bits dos registradores sse para memoria movq [novo],xmm3 novo dq 0 da mesma forma que o registrador mmx o sse tambem aceita numeros negativos movq xmm0,[kodo] kodo dq -50 ou numeros de ponto flutuantes movq xmm0,[kodo] kodo dq 48.987 outra instrução que permite mover 32bits para os registradores sse é a instrução movss

movss xmm0,[kodo] kodo dd 0x50505050 com a instrução movsd movemos dois valores de 32bits cada para o registrador sse movsd xmm0,[kodo]

;0x5000000010000000

kodo dd 0x10000000,0x50000000 pelo movsd tambem é possivel mover um único valor de 64bits movsd xmm0,[kodo]

;0x5000000010000000

kodo dq 0x5000000010000000 podemos mover 4 valores de 64bits não alinhados da memoria para um determinado registrador sse com a instrução movups, esses quatros valores junto totaliza os 128bits do registrador sse movups xmm0,[kodo]

;0x11111111222222223333333344444444

kodo dd 0x44444444,0x33333333,0x22222222,0x11111111 ou dois valores de 64bits para ter o mesmo resultado de um único valor de 128bits movups xmm0,[kodo]

;0x11111111222222223333333344444444

kodo dq 0x3333333344444444,0x1111111122222222 tambem podemos mover valores alinhados da memoria com a instrução movaps, para o uso dessa instrução temos que especificar o alinhamento sendo que alguns compiladores usa a diretiva align para fazer isso (lembrando align é uma diretiva do compilador e não uma instrução da linguagem), quando usamos a instrução movaps sem esse alinhamento vai da falha de segmentação no programa movaps xmm0,[kodo] align 16 kodo dq 0x3333333344444444,0x1111111122222222 no compilador gas que usa a sintaxe at&t é a diretiva .align movaps (kodo),%xmm0 .align 16 kodo: .int 0x33333333,0x44444444,0x11111111,0x22222222 podemos mover um valor de 64bits para um registrador sse na parte menos significativa dele com a instrução movlps

movlps xmm1,[kodo]

;0x00000000000000006090801087801247

kodo dq 0x6090801087801247 para a gente mover um valor de 64bits para a parte mais significativa usamos instrução movhps movhps xmm1,[kodo]

;0x60908010878012470000000000000000

kodo dq 0x6090801087801247 podemos usar o movhps e o movlps para gerar um único valor de 128bits usando valores de 64bits separados na memoria movhps xmm0,[kodo] movlps xmm0,[kami]

;0x54898785187924540000000000000000 ;0x54898785187924543845827721254668

kodo dq 0x5489878518792454 kami dq 0x3845827721254668 o sse tambem funciona com agrupamentos igual o mmx, a diferença que o sse permite um unico agrupamento de 128bits, dois agrupamentos de 64bits, quatro agrupamentos de 32bits, oito agrupamentos de 16bits, e dezesseis agrupamentos de 8bits, então se a gente colocar em um registrador sse o numero 0x81977314951795218752311314789514 que é um numero de 128bits o computador pode interpretar ele como aquele numero ou separadamente como grupos menores 128bits

81977314951795218752311314789514

64bits

8752311314789514

32bits

87523113

16bits

8752

8bits

87 52

8197731495179521 14789514

3113 31

81977314

1478

13 14

78

9514 95

14

8197 81

97

95179521 7314 71

14

9517 95

17

9521 95

21

para a gente somar em agrupamentos de 64bits usamos a instrução pmovq, nesse caso sera somado dois agrupamentos seprados movups xmm0,[kodo] movups xmm1,[kami] paddq xmm0,xmm1

;0x6080906875910856|9987568905896912 ;0x1090956991285178|a815f58012574689 ;0x711125d206b959ce|419d4c0917e0af9b

kodo dq 0x9987568905896912,0x6080906875910856 kami dq 0xa815f58012574689,0x1090956991285178 para a adição com agrupamentos de 32bits usamos a instrução paddd movups xmm0,[kodo] movups xmm1,[kami] paddd xmm0,xmm1

;0x60809068|75910856|99875689|05896912 ;0x10909569|91285178|a815f580|12574689 ;0x711125d1|06b959ce|419d4c09|17e0af9b

kodo dq 0x9987568905896912,0x6080906875910856 kami dq 0xa815f58012574689,0x1090956991285178 com a instrução paddw fazemos operações de adição em agrupamentos de 16bits movups xmm0,[kodo] movups xmm1,[kami] paddw xmm0,xmm1

;0x6080|9068|7591|0856|9987|5689|0589|6912 ;0x1090|9569|9128|5178|a815|f580|1257|4689 ;0x7110|25d1|06b9|59ce|419c|4c09|17e0|af9b

kodo dq 0x9987568905896912,0x6080906875910856 kami dq 0xa815f58012574689,0x1090956991285178 o mesmo pode ser feito com a instrução paddb para somar em grupos de 8bits movups xmm0,[kodo] movups xmm1,[kami] paddb xmm0,xmm1

;0x60|80|90|68|75|91|08|56|99|87|56|89|05|89|69|12 ;0x10|90|95|69|91|28|51|78|a8|15|f5|80|12|57|46|89 ;0x70|10|25|d1|06|b9|59|ce|41|9c|4b|09|17|e0|af|9b

kodo dq 0x9987568905896912,0x6080906875910856 kami dq 0xa815f58012574689,0x1090956991285178 podemos fazer a operação de adição levando em conta a saturação para agrupamentos de 16bits e agrupamentos de 8bits com as instruções paddsw e paddsb para operações com sinal ou com as instruções paddusb e paddusw para as operações sem sinal, ou seja se nesse agrupamento acontecer um overflow vai ficar com o maior numero daquela palavra movups xmm0,[kodo] movups xmm1,[kami] paddusw xmm0,xmm1

;0x6080|9068|7591|0856|9987|5689|0589|6912 ;0x1090|9569|9128|5178|a815|f580|1257|4689 ;0x7110|ffff|ffff|59ce|ffff|ffff|17e0|af9b

kodo dq 0x9987568905896912,0x6080906875910856 kami dq 0xa815f58012574689,0x1090956991285178 é possivel somar valores de ponto flutuantes de única precisão (32bits) com a instrução addps movups xmm0,[kodo] movups xmm1,[kami] addps xmm0,xmm1 kodo dd 1.5, 689.2, 78.2 10.64 kami dd 2.6, 56.3, 43, 23.5 para operações de subtração usamos a instrução psubq para subtrair em grupos de 64bits ou seja 2 grupos de 64bits totalizando 128bits movups xmm0,[kodo] movups xmm1,[kami] psubq xmm0,xmm1 kodo dq 0x50,0x80

;0x0000000000000080|0000000000000050 ;0x0000000000000035|0000000000000010 ;0x0000000000000045|0000000000000040

kami dq 0x10,0x35 para subtrair em grupos de 32bits usamos a operação psubd movups xmm0,[kodo] movups xmm1,[kami] psubq xmm0,xmm1

;0x80589125|47233254|80538901|68712547 ;0x35257814|65891254|56782547|89521351 ;0x4b331911|e19a2000|29db63ba|df1f11f6

kodo dq 0x8053890168712547,0x8058912547233254 kami dq 0x5678254789521351,0x3525781465891254 na subtração em agrupamentos de 16bits usamos a instrução psubw movups xmm0,[kodo] movups xmm1,[kami] psubw xmm0,xmm1 kodo dq 0x8053890168712547,0x8058912547233254 kami dq 0x5678254789521351,0x3525781465891254 com a instrução psubb fazemos a operação de subtração com agrupamentos de 8bits movups xmm0,[kodo] movups xmm1,[kami] psubb xmm0,xmm1 kodo dq 0x8053890168712547,0x8058912547233254 kami dq 0x5678254789521351,0x3525781465891254 operações de subtração com saturação e com sinal usamos as instruções psubsw para agrupamentos de 16bits e psubsb para operações de 8bits, para a subtração com saturação para numeros sem sinais usamos psubusw para agrupamentos de 16bits e psubusb para agrupamentos de 8bits movups xmm0,[kodo] movups xmm1,[kami] psubusb xmm0,xmm1 kodo dq 0x8053890168712547,0x8058912547233254 kami dq 0x5678254789521351,0x3525781465891254 podemos subtrair numeros com ponto flutuante com a instrução subps, sera feito a operação em agrupamentos de 32bits movups xmm0,[kodo] movups xmm1,[kami] subps xmm0,xmm1 kodo dd 56.322, 86.231, 123.2, 12.0 kami dd 10.5, 23.23, 21.8, 10.0

na multiplicação usamos a instrução pmullw sera feito em agrupamentos de 16bits movups xmm0,[kodo] movups xmm1,[kami] pmullw xmm0,xmm1 kodo dq 0x5060708090102010,0x5420021245328914 kami dq 0x0002000300040050,0x0003010000030002 tambem podemos fazer a multiplicação com pontos flutuantes usando a instrução mulps sera feito em agrupamentos de 32bits movups xmm0,[kodo] movups xmm1,[kami] mulps xmm0,xmm1 kodo dd 10.2, 550.0, 987.12, 6.987 kami dd 2.0, 5.2, 4.7, 8.2 podemos fazer a divisão com a instrução divps em agrupamentos de 32bits para numeros de ponto flutuantes movups xmm0,[kodo] movups xmm1,[kami] divps xmm0,xmm1 kodo dd 10.0, 20.3, 200.54, 5058.234 kami dd 2.0, 10.0, 4.6, 0.23 para retornar a raiz quadrada usamos a instrução sqrtps para ser feito em agrupamentos de 32bits movups xmm0,[kodo] sqrtps xmm0,xmm0 kodo dd 9.0, 100.0, 31.5, 8.5 na operação logica and usamos a instrução pand movups xmm0,[kodo] movups xmm1,[kami] pand xmm0,xmm1 kodo dq 0x5871258315978514,0x3785189753841479 kami dq 0x9125875815633211,0x8745147854789514 outra instrução que permite a logica and é o andps que permite operação logica and em numeros usando agrupamento de 32bits movups xmm0,[kodo] movups xmm1,[kami]

pand xmm0,xmm1 kodo dd 10, 15, 665, 158 kami dd 20, 15, 7844, 10 para as operações logicas or usamos a instrução por movups xmm0,[kodo] movups xmm1,[kami] por xmm0,xmm1 kodo dq 0x5871258315978514,0x3785189753841479 kami dq 0x9125875815633211,0x8745147854789514 tambem podemos fazer a operação or com a instrução orps, sera feito em agrupamentos de 32bits movups xmm0,[kodo] movups xmm1,[kami] orps xmm0,xmm1 kodo dd 10, 15, 665, 158 kami dd 20, 15, 7844, 10 com o pxor a gente faz a operação logica xor movups xmm0,[kodo] movups xmm1,[kami] pxor xmm0,xmm1 kodo dq 0x5871258315978514,0x3785189753841479 kami dq 0x9125875815633211,0x8745147854789514 tambem podemos fazer em agrupamentos de 32bits a operação xor com a instrução xorps movups xmm0,[kodo] movups xmm1,[kami] xorps xmm0,xmm1 kodo dd 10, 15, 665, 158 kami dd 20, 15, 7844, 10 podemos deslocar o numero bit a bit a esquerda usando a instrução psllq, sera deslocado dois grupos de 64bits o operador usado para o deslocamento sera apenas os primeiro bytes e não um agrupamento movups xmm0,[kodo] movups xmm1,[kami] psllq xmm0,xmm1 kodo dq 0x111,0x510

;0x00000000000005100000000000000111 ;2 ; 0x510