end of file – ebook asm x86 - · pdf filearquiteturas padroniza aquele determinado...
TRANSCRIPT
END OF FILE – EBOOK ASM X86Autor: kodo no kami ( fb/hacker.fts315 ) discord: kodo#0010
Forum: https://eofclub.in/forumPagina: 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, melotsData: 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 Assembly1.1 – Baixo e Alto nível1.2 – Arquitetura CISC e RISC1.3 – Sintaxe INTEL e AT&T1.4 – Agrupamento de bits1.5 – Little endian e Big endian1.6 – Tipos de dados1.7 – Compiladores1.8 – Comentario2.0 – Familia x862.1 – x86: Registradores2.2 – x86: Flags2.3 – x86: instrução de movimento2.4 – x86: instrução aritmetica2.5 – x86: instrução de pulo2.6 – x86: Subrotina2.7 – x86: Acesso a memoria2.8 – x86: Interrupção2.9 – x86: IO3.0 – x86: FPU3.1 – x86: MMX3.2 – x86: 3DNow
3.3 – x86: SSE4.0 – x86: Modo Protegido4.1 – x86: libc4.2 – x86: asm web (cgi)4.3 – x86: asm inline4.4 – x86: disassembler5.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 comisso 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 usadoo 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 imprimiralgo 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ãoCISC (embora meio hibrido), além das arquiteturas baseada em CISC tambem temos as arquiteturasbaseada 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 umaquantidade 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 paramicrocontroladores e chips como por exemplo a arquitetura do playstation que é a arquitetura MIPSsendo 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 daarquitetura 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 compiladorestende 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 IntelDestino Origem
sintaxe AT&TOrigem 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 Intel315
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 Inteleax
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&T8(%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 valemais 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 linguagemassembly 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árioou 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.htmlgeany – 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 serusado 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 barramentode 16bits de dados, o 8086 é sucessor da arquitetura 8080 e 8085 com isso muitas de suas instruçõesforam 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çoda pilha de memoria, passagem de argumento para chamadas externas como interrupções ou retornode 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 finsespecí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 significativodele, 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 AH AL
0315 03 15
alem dos registradores de uso geral existem os registradores apontadores como os registradores SI eDI, 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 0bitNT 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 0bitNT IOPL IOPL OF DF IF TF SF ZF AF PF 1 CF
ID VIP VIF 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 numerossem 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 tantodo 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 genteusar 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 onumero 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,80mov cx,60mov dx,90mov 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,10mov 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 nasmpode 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,0x30mov bx,ax
lembrando que esses mesmos exemplos tambem vale para sintaxe at&t
mov $0x30,%axmov %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 altregmov r0,100
outra instrução que podemos usar é o xchg que troca os valores entre dois registradores
mov ax,100mov bx,200xchg 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,300add ax,15
o mesmo pode ser feito na sintaxe at&t porem mudando a ordem da fonte e destino
mov $300,%axadd $15,%ax
podemos fazer o mesmo usando dois registradores
mov ax,300mov bx,100add ax,bx
tambem é possível fazer varias operações em sequencia
mov ax,300add ax,15add ax,20add ax,8
operações com registradores maiores como os de 32 e 64bits
mov ecx,5000mov edx,1000add ecx,edx
é possivel fazer operações com bases diferentes tambem
mov ax,0b1111
mov bx,0x50add 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,0xffffmov bx,0x1add 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,50sub ax,30
o mesmo pode ser feito na sintaxe at&t
mov $50,%axsub $30,%ax
podemos subtrair um registrador de outro
mov ax,60mov cx,5sub ax,cx
tambem é possível subtrair por um numero maior sendo que o resultado sera negativo (alem de setara flag SF e outras flags)
mov ax,40mov dx,60sub 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,15mov bx,2mul bx
outra instrução que permite fazer a multiplicação é o imul, o uso dessa instrução é parecida com adde sub permitindo escolher o registrador e o valor
mov bx,10imul bx,6
tambem podemos multiplicar valores armazenado em outro registrador sem ser de forma imediata
mov bx,10mov ax,6imul 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,15mov bx,2imul 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,65535mov bx,1000mul 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,100mov bx,5div bx tambem existe a instrução idiv que permite a gente escolher os dois registradores que vamos dividir
mov bx,100mov cx,5idiv 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,100mov bx,5idiv bx
o resto de uma divisão tambem chamado de modulo sera armazenado no registrador dx
mov ax,10mov 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,10mov bx,12div bx
podemos incrementar um valor em um registrador com a instrução inc, seria equivalente a adicionaro valor em mais 1
mov ax,100inc ax
tambem podemos decrementar um valor em um registrador com o dec, essas instruções subtrair o registrador em menos 1
mov ax,100dec ax
a instrução adc faz a adição em conjunto com carry, o seu diferencial da instrução add seria se a flagCF estiver setada em 1 sera incrementado o registrador destino em 1 tambem
mov ax,1mov bx,1stcadc 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,5mov bx,2stcsbb 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 doisnumeros, 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,153mov bx,247and 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,153mov bx,247or 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,153mov bx,247xor ax,bx
o xor é usado constantemente para zerar um determinado registrador usando ele proprio
mov ax,153xor ax,ax
outro uso para o xor é a troca de valores entre dois registradores sendo igual a instrução xchg
mov ax,153mov bx,247
xor ax,bxxor bx,ax ;bx = 153xor ax,bx ;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,153not 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,65535mov bx,153sub 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,153not axadd 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,15shl 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,15shr ax,2
para deslocamento com sinal para esquerda usamos a instrução sal
mov ax,15sal ax,2
e para o deslocamento com sinal para direita usamos a instrução sar
mov ax,15sar 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,130cbw
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,0x123456cdq
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 comoacontece em valores imediatos
jmp 0x200
tambem pode ser feito com um registrador ao inves de um endereço
mov ax,0x200jmp 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 kodokodo:
como foi dito o jmp é um pulo incondicional então no exemplo abaixo nenhuma instrução depois dojmp e antes do label sera executada
jmp kamimov ax,10add ax,50kami:
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,0kami:inc axjmp 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 ;pula para o inicio usando o labelmov ax,1jmp $ + 4 ;pula para o add de forma relativa tambeminicio:jmp $ - 5 ;pula para o mov, veja que pulamos de forma relativa aquiadd ax,1
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,10add 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,5xor ax,axje igualmov ax,100igual: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,5xor ax,3je igualmov ax,100igual: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,5xor ax,3je igualmov ax,100jmp proximoigual:mov ax,200proximo:
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,5xor ax,axje $ + 7mov ax,100jmp proximomov ax,200proximo:
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,315mov bx,315xor ax,bxje 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,315mov bx,315cmp ax,bxje 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,100mov bx,315cmp ax,bxjne 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,200mov bx,100cmp ax,bxjg 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 SFe 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,127mov bh,2add 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,200mov bx,500cmp ax,bxjl 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,-100mov bh,2sub ah,bhjl pulo
pulo:
tambem temos a instrução que compara se o valor é maior ou igual ao segundo sendo essa instruçãoo 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,500mov bx,315cmp ax,bxjge 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,axjge 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,315mov bx,500cmp ax,bxjle 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,-100mov bh,2sub ah,bhjle 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,65000mov bx,10000cmp ax,bxja 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
clcja 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,1000mov bx,5000cmp ax,bxjb 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
stcja pulo
pulo:
tambem podemos comparar se é maior ou igual com a instrução jae (jump if above or equal)
mov ax,5000mov bx,1000cmp ax,bxjae 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,axjae pulo
pulo:
outra instrução é o jbe (jump if below or equal) que compara se o numero é menor
mov ax,1000
mov bx,5000cmp ax,bxjbe pulo
pulo:
no jbe é comparado se a flag CF esta setada em 1 ou se a flag ZF esta setada em 0
mov ax,0xffffadd ax,100jae pulo
pulo:
tambem podemos pular se a flag CF estiver setada em 1 com a instrução jc
stcjc pulo
pulo:
ou se a flag CF estiver com o valor 0 usando a instrução jnc
clcjnc pulo
pulo:
é possível pular caso a flag OF estiver setada em 1 usando a instrução jo
mov ah,127add ah,10jo pulo
pulo:
ou pular caso a flag OF estiver setada em 0 usando a instrução jno
mov ah,100add ah,10jno pulo
pulo:
outro tipo de pulo é o jp que permite pular caso a flag PF estiver com valor 1
mov ah,9add ah,1jp pulo
pulo:
com o jnp podemos pular caso a flag PF esteja com o valor 0
mov ah,10add ah,1jnp pulo
pulo:
com a instrução js podemos pular caso a flag SF estiver setada em 1
mov ax,100sub ax,200js pulo
pulo:
tambem podemos pular se a flag SF estiver setada em 0 usando a instrução jns
mov ax,100add ax,200jns pulo
pulo:
na flag ZF usamos a instrução jz para pular caso essa flag estiver setada em 1
xor ax,axjz pulo
pulo:
como tambem podemos usar o jnz para pular caso a flag ZF estiver em 0
mov ax,5mov bx,10add ax,bxjnz 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; //mov ax,10if(var == 10) //cmp ax,10
{ //jne kodo } else //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,0mov bx,100
inicio:cmp ax,bxjge fiminc axjmp 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 fimdec axjmp 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; //mov ax,0xffff //inicio:while(x == 0){ //jz fim x--; //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 determinadotrecho 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,20kodo:mov cx,1 ;breakloop kodo
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,20xor ax,axkodo: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,20mov ax,1add ax,1kodo: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 emdiante 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,8jmp 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 kodocall kodocall 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 casodevemos 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 kamicall kodo
podemos passar e retornar valores nas subrotinas usando os registradores
jmp main
kodo:add ax,bxret
main:mov ax,50mov bx,10call kodo
podemos usar a pilha de memoria para passar valores usando a instrução push para empilhar e o poppara desempilhar da memoria, a vantagem de passar valores usando a pilha que seria possível passaruma 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 ;desempilha endereço para dxpop ax ;desempilha 10 para axpop bx ;desempilha 50 para bxadd ax,bx ;soma ax e bxpush ax ;empilha o ax push dx ;empilha endereço para voltarret
main:push 50push 10call kodopop ax ;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 axpush axret
main:call kodo
inclusive podemos ate forçar o retorno para outro endereço
jmp main
kodo:pop axmov ax,0x600 push axret
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 kamiret
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 kodoret
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 cxjz kamicall kodokami:ret
main:mov cx,100call 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 bpmov bp,sp ; codigo da subrotinaret
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 bpmov bp,sp ; codigo da subrotinamov sp,bppop bpret
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 bpmov bp,spmov cx,[bp + 4] ; acesso ao valor 0x5 sem precisar desempilharmov ax,[bp + 6] ; acesso ao valor 0x30 sem precisar desempilharmov bx,[bp + 8] ; acesso ao valor 0x10 sem precisar desempilharmov sp,bppop bpret
main:push 0x30push 0x5push 0x10call 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 ebpmov ebp,espmov eax,[ebp + 8]mov ebx,[ebp + 12]mov esp,ebppop ebpret
main:push 0x30push 0x80call kodo
lembrando que o mesmo pode ser feito em compiladores que usa a sintaxe at&t
jmp main
kodo:push %ebpmov %esp,%ebp
mov 8(%ebp),%eaxmov %ebp,%esppop %ebpret
main:push $0x40call 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,0mov eax,[ebp + 8]leaveret
main:push 0x30call 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 separadose 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 protegidoele 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 (quandoestamos 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,100mov [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,%axmov (%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 issotemos 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,100mov [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,600mov ds,axmov 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,600mov ds,axmov 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,600mov es,axmov 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 é executadosequencialmente 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,0x500mov ds,ax
mov [ds:0x100],0xb8 ;mov ax,10mov [ds:0x101],0x0amov [ds:0x102],0x0
mov [ds:0x103],0xbb ;mov bx,20mov [ds:0x104],0x14mov [ds:0x105],0x0
mov [ds:0x106],0x3 ;add ax,bxmov [ds:0x107],0xc3
mov [ds:0x108],0xeb ;jmp 0x108mov [ds:0x109],0xfe
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,315push ax
tambem podemos empilhar um determinado valor de forma imediata
push 315para 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 315push 100push 200pop ax ;desempilha o 200pop bx ;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 em4bytes, 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
| 315 | 100V 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 ;push 315mov bx,spmov [ss:bx],315
mov bx,sp ;pop axmov ax,[ss:bx]add sp,2
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 offsetmais alto daquele segmento já que ele sera subtraido
mov ax,0x500mov ss,axmov 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 ;carrega a pilha 1 no segmento 600mov ss,axmov sp,0xfffe
push 315 ;empilha 315 na pilha 1mov ax,0x500 mov ds,axmov [ds:0],sp ;salva o sp da pilha 1 na memoria 500:0
mov ax,0x601 ;carrega a pilha 2 no segmento 601
mov ss,axmov sp,0xfffepush 100 ;empilha 100 na pilha 2
mov ax,0x500 ;volta para pilha 1mov ds,axmov sp,[ds:0] ;recupera o sp da pilha 1 de 500:0mov ax,0x600 ;carrega a pilha 1 no segmento 600mov ss,axpop ax ;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,0x5mov bx,0x6pusha
mov ax,0x20mov bx,0x30popa ;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,0x200mov ecx,0x100pushad
mov eax,0x500popad
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 ;ZF, PF e CFpopf
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 ;aloca 20 bytes na pilhaadd sp,20 ;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)
| 315 ← BP | 100V 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 ;bp (base)push 315 ;bp - 2push 100 ;bp - 4push 200 ;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 ;bp + 4push 600 ;bp + 2call kodo ;endereço atual (base)
kodo:mov bp,sp push 800 ;bp - 2push 200 ;bp - 4mov ax,[bp + 4] ;acessa o valor 315 mov bx,[bp - 2] ;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 ;salva o bp antigo na pilhamov bp,sp ;move o sp atual para o bp para ter a nova base
mov sp,bp ;move o valor do bp para o sppop bp ;recupera o bp antigoret ;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 variavelde 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: nopnop
podemos armazenar os dados na variavel usando o próprio label dela
mov [kodoint],0x315
kodoint: nopnop
como tambem é possível ler a nossa variavel pelo próprio label
mov [kodoint],0x315mov ax,[kodoint]jmp $
kodoint: nopnop
podemos criar quantas variaveis a gente quiser dessa forma
mov [kodoint],0x315mov [kodoint2],0x100
kodoint:
nopnop
kodoint2:nopnop
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 0x0db 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 0x90db 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, kodoarraymov [bx], 0x315 ;1º posiçãomov [bx + 2], 0x100 ;2º posiçãomov [bx + 4], 0x500 ;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,%bxmov $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 quandoo 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,kodostrmov ah,[si + 0] ;acesso a letra kmov ah,[si + 1] ;acesso a letra omov ah,[si + 2] ;acesso a letra dmov ah,[si + 3] ;acesso a letra o
kodostr: db "kodo"
o mesmo vale para o registrador di
mov di,kodostrmov 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,kodostrmov di,kodonovomovsb
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,kodostrmov di,kodonovomovsbmovsbmovsbmovsb 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,kodomov di,kodonovomovswmovsw
kodo: dw 0xffff,0x315kodonovo: dw 0x0,0x0
podemos mover o dado do registrador fonte si para o registrador al com a instrução lodsb
mov si,kodostrlodsb
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,kodolodsw
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,0x61mov di,kodostosb
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,0x61stosb
mov al,0x62stosb
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 nopjmp 100.org 100mov $10,%axmov $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 queremospela quantidade de bytes atual e o byte ou a instrução que vai se repetir ate la
nopjmp 100times (100 - ($-$$)) db 0x0mov ax,10mov 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 inciadono 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, osde 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,0xb800mov 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,0xb800mov ds,ax
mov [ds:0],”k”mov [ds:1], 12mov [ds:2],”o”mov [ds:3], 12mov [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 deinterrupçã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 ;funcão usada: modo de videomov al,0x1 ;argumento: 80x32 16 cores textualint 0x10 ;int de software
o mesmo pode ser feito usando a sintaxe at&t tambem
mov $0x0,%ahmov $0x1,%alint $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,0x0emov al,0x6bint 0x10
podemos colocar o caracter ascii tambem
mov ah,0xemov al,'k'int 0x10
é possivel usar as interrupções quantas vezes a gente quiser, exemplo seria imprimir vários caracteres na tela
mov ah,0xemov al,'k'int 0x10
mov ah,0xemov al,'o'int 0x10
mov ah,0xemov al,'d'int 0x10
mov ah,0xemov 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,textocall printf
mov si,texto2call printf
jmp $
;minha subrotina que imprime uma stringprintf:cmp [si],0x0 ;compara se o caracter for igual a 0jz fim ;se for igual a 0 finaliza pulando para fimmov ah,0xe ;função para imprimirmov al,[si] ;o caracter em siint 0x10 ;interrupção de videoinc si ;incrementa o si para o próximo caracterjmp printf ;volta para o printf para imprimir o proximofim: ret
texto: db "kodo no kami ",0x0texto2: db "ebook de asm",0x0
a função 0xe tem outro argumento que é a cor do texto que definimos no registrador bl
mov ah,0xemov al,'k'mov bl,0x2 ;verdeint 0x10
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,0x2mov dh,0x3 ;linhamov dl,0x10 ;colunaint 0x10
mov ah,0xemov al,'k' ;imprime 'k' na posição 3x10int 0x10
para apagar caracteres imprimidos na tela usamos a função 0x2 bastando sobrescrever eles
mov ah,0xemov al,'k'int 0x10 ;imprime o k na posição 0x0
mov ah,0x2mov dh,0x0mov dl,0x0int 0x10 ;volta para posição 0x0
mov ah,0xemov 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,0x3int 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,0x9mov al,'k'mov cx,3 ;imprime 3 caracteres k repetidomov bl,0x5int 0x10
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,0x9mov al,'k'mov cx,3mov bl,0x4 ; vermelhoint 0x10
mov ah,0x3int 0x10 ;pega posição atual
mov ah,0x2add dl,3 ;incrementa a coluna em mais 3int 0x10
podemos ler o caractere na posição atual com a função 0x8, vai retornar no registrador ah a cor e noal o caracter (isso funciona apenas em modo de video textual)
mov ah,0x0mov al,0x1int 0x10 ;80x32 textual
mov ah,0xemov al,'f'mov bl,0x3int 0x10 ;imprime o caracter na posição 0x0
mov ah,0x2mov dh,0x0mov dl,0x0int 0x10 ;aponta o cursor para o caracter
mov ah,0x8int 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,0xbmov bh,0x0mov bl,0x9 ;azul claroint 0x10
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,0xemov al,'k' ;imprime ‘k’ na primeira paginaint 0x10
mov ah,0x0int 0x16 ;aguarda uma tecla ser apertada
mov ah,0x5mov al,0x1 ;pula para pagina 1int 0x10
mov ah,0xemov al,'o' ;imprime ‘o’ na segunda paginaint 0x10
mov ah,0x0int 0x16 ;aguarda uma tecla ser apertada
mov ah,0x5mov al,0x0 ;volta para pagina 0 mostrando o k novamenteint 0x10
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,0x0mov al,0x13int 0x10
mov ah,0xcmov al,0x10 ;cor verde claromov cx,100 ;xmov dx,100 ;yint 0x10
um exemplo de um quadrado desenhado pixel por pixel com a função 0xc do int10 em um loop
mov ah,0x0mov al,0x13int 0x10
mov cx,200 ;x começa no 200mov dx,100 ;y comela no 100
kodox:mov ah,0xc mov al,10 ;cor verde claroint 0x10 ;cria o pixel na posição do cx e dxcmp cx,100 ;se cx for igual a 100 pula para kodoyje kodoyloop kodox ;instrução loop decrementa o cx ou seja e desenhado invertidamentekodoy:dec dx ;decrementa o dx
mov cx,200 ;volta o cx para 200 para desenha a nova linhacmp dx,20 ;compara dx com 20 se for igual pula para o fim terminadoje fimjmp kodox ;volta para kodoy para desenhar nova linhafim:
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,0x0int 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,0x0int 0x16
mov ah,0xeint 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 ;quantidade de teclas capturadasmov si,kodo ;endereço da nossa variavel
repetir:mov ah,0x0int 0x16 ;captura a tecla pressionada
mov [si],al ;joga a tecla apertada na memoriainc si ;incrementa o si ou seja o endereço da memorialoop repetir ;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,10mov si,kodo
repetir:mov ah,0x0int 0x16 ;captura a tecla pressionada
cmp al,0xd ;compara para ver se o enter foi pressionadoje sair ;se sim finaliza pulando para o sair
mov [si],alinc si
loop repetirsair:
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,0x0mov dl,0x80int 0x13
quando usamos a função anterior caso o procedimento não seja possível ser feito como por exemplonã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,0x0mov dl,0x81int 0x13
jc falhamov ah,0xemov al,'S' ;caso seja sucesso exibe Sint 0x10jmp fim
falha:mov ah,0xemov al,'N' ;caso falhe exibe o Nint 0x10fim:
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,0x0mov dl,0x80int 0x13
mov ah,0x1mov dl,0x80int 0x13
cmp ah,0x0je 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 ;leitura do hdmov al,0x1 ;quantidade de setores lidomov bx,0x500 ;endereço da memoria que sera armazenadomov ch,0x0 ;trilha 0mov cl,0x2 ;setor 1 (1 = setor 0, 2 =setor 1, 3 = setor 2 ...)mov dh,0x0 ;cabeça 0mov dl,0x80 ;hd 1 int 0x13
mov al,[0x500] ;vai ler o primeiro byte que estava no hdmov al,[0x501] ;vai ler o segundo byte que estava no hdmov al,[0x502] ;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,0x2mov al,0x1mov bx,0x500mov ch,0x0mov cl,0x2mov dh,0x0 mov dl,0x80int 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,600mov es,ax
mov ah,0x2mov al,0x1mov bx,0x500 ;es:bx (600:500)mov ch,0x0mov cl,0x2mov dh,0x0 mov dl,0x80int 0x13
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,0x2mov al,0x2 ;quantidade de setores lido (1023 = 2 * 512 - 1)mov bx,0x500mov ch,0x0mov cl,0x5 ;setor 4 (2048 = 512 * 4)mov dh,0x0 mov dl,0x80int 0x13
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 ;escrita do hdmov al,0x1 ;quantidade de setores a ser gravadomov bx,0x500 ;endereço da memoria que sera gravadomov ch,0x0 ;trilha 0 no hdmov cl,0x2 ;setor 1 no hdmov dh,0x0 ;cabeça 0mov dl,0x80 ;hd 1int 0x13
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 agente 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,0x2mov al,0x1mov bx,0x500 mov ch,0x0mov cl,0x2mov dh,0x0mov dl,0x80int 0x13 ;ler o setor no hd e joga na memoria
mov [0x500],'k' mov [0x501],'o'mov [0x502],'d' ;modifica os bytes desejado na memoriamov [0x503],'o'
mov ah,0x3 mov al,0x1mov bx,0x500mov ch,0x0mov cl,0x2mov dh,0x0mov dl,0x80int 0x13 ;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 oparamentro 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 bit5 6 e 7 é o baud)
mov ah,0x0mov al,11100010b ;111 = 9600 (baud), 00 = none (par), 0 = 0 (stop), 10 = 7bits mov dx,0x0 ;com1int 0x14
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,0x0mov al,11100010bmov dx,0x0int 0x14
mov ah,0x1mov al,'k'mov dx,0x0int 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,0x0mov al,11100010bmov dx,0x0int 0x14
mov ah,0x2mov dx,0x0int 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,0x0int 0x21
outra função com o mesmo efeito é o 0x4c sendo que nessa função podemos especificar o retorno no registrador al
mov ah,0x4cmov al,0x0int 0x21
para exibir um caracter usando a função 0x2, especificamos o caracter a ser exibido no registrador dl
mov ah,0x2mov 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,0x9mov dx,textoint 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,0x1int 0x21
para entrar com um caracter do teclado sem exibir na tela usamos a função 0x8
mov ah,0x8int 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,0xamov dx,kodoint 0x21
kodo: db 10,”$$$$$$$$$”
um outro exemplo de um programa para msdos onde escrevemos o nome e depois ele é exibido
mov ah,0x9mov dx,kodoint 0x21 ;exibe “digite seu nome: ”
mov ah,0xamov dx,variavelint 0x21 ;armazena o nome digitado em variavel
mov ah,0x9mov dx,kamiint 0x21 ;exibe “\nnome digitado foi ”
mov ah,0x9mov dx,variavel+2int 0x21 ;exibe a variavel pulando dos caracteres que seria o tamanho
mov ah,0x0int 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,0x19int 0x21
ou mudar o drive atual com a função 0xe, especificamos o drive no registrador al
mov ah,0xemov al,0x3 ; d:int 0x21
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,0x39mov dx,kodoint 0x21
kodo: db “nudes”
podemos remover um diretorio com a função 0x3a sendo seus argumentos parecido com o anterior
mov ah,0x3amov dx,kodoint 0x21
kodo: db “nudes”
é possível mudar de directorio com a função 0x3b
mov ah,0x3bmov dx,kodoint 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 ;exitmov ebx,0x0 ;retorno 0int 0x80
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 ;writemov ebx,0x1 ;stdoutmov ecx,kodo ;variavel kodomov edx,12 ;tamanhoint 0x80
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,0x4mov ebx,0x1mov ecx,kodomov edx,kodofim - kodo ;tamanhoint 0x80
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 ;readmov ebx,0x0 ;stdinmov ecx,kami ;variavel kamimov edx,30 ;tamanhoint 0x80
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 ;creatmov ebx,kodo ;animes.txtmov ecx,0x0 ;000int 0x80
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 ;usuario ror ecx,128 ;usuario rwor ecx,64 ;usuario rwxor ecx,32 ;usuario rwx, grupo ror ecx,8 ;usuario rwx, grupo rxor ecx,4 ;usuario rwx, grupo rx, outros ror ecx,1 ;usuario rwx, grupo rx, outros rx (rwxr-xr-x = 755)
mov eax,0x8 ;creatmov ebx,kodo ;animes.txtint 0x80
kodo: db “animes.txt”
podemos deletar um arquivo com a função 0xa (unlink)
mov eax,0xa ;unlinkmov ebx,kodo ;animes.txtint 0x80
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,0x26mov ebx,kodomov ecx,kamiint 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 ;mkdirmov ebx,kodo ;filmesmov ecx,493 ;perm = 755int 0x80
kodo: db “filmes”
por outro lado com a função 0x28 a gente remove um diretorio (rmdir)
mov eax,0x28 ;rmdirmov ebx,kodo ;filmesint 0x80
kodo: db “filmes”
para ler ou escrever em um arquivo usamos a função 0x5 (open), especificamos no registrador ebx oendereç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 ;openmov ebx,kodo ;arquivo.txtmov ecx,0x1 ;escritamov edx,0x0 ;0 ~ não é usado caso o arquivo já existaint 0x80
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 vamosfechar no registrador ebx
mov eax,0x5mov ebx,kodomov ecx,0x1mov edx,0x0int 0x80 ;abre o arquivo para escrita, retorna o descrito em eax
mov ebx,eax ;copia o descrito do eax para o ebxmov eax,0x6 ;closeint 0x80
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,0x5mov ebx,kodomov ecx,0x1mov edx,0x0int 0x80 ;open ~ escrita
mov ebx,eaxmov eax,0x4mov ecx,kamimov edx,kamif - kamiint 0x80 ;write
mov eax,0x6int 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ção0x3 para ler o arquivo
mov eax,0x5mov ebx,kodomov ecx,0x0mov edx,0x0int 0x80 ;open ~ leitura
mov ebx,eaxmov eax,0x3mov ecx,kamimov edx,50int 0x80 ;read
mov eax,0x6int 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,0x5mov ebx,kodomov ecx,0x0mov edx,0x0int 0x80 ;open ~ leitura
mov ebx,eaxmov eax,0x13 ;lseekmov ecx,10 ;10 posiçãomov edx,0 ;inicio do arquivoint 0x80
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 ;arquivomov ecx,kami ;atalhoint 0x80
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 ;chmodmov ebx,kodo ;arquivo.txtmov ecx,488 ;perm = 750int 0x80
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,0x0mov es,axmov [es:0x140], kodomov [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,0x0mov es,ax
climov [es:0x140], kodomov [es:0x142], cssti
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,0x0mov es,axclimov [es:0x140], kodomov [es:0x142], cssti
kodo:iret
depois de criada a interrupção podemos chamar ela normalmente
mov ax,0x0mov es,axclimov [es:0x140], kodomov [es:0x142], cssti
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,0x0mov es,axclimov [es:0x140], kodomov [es:0x142], cssti
int 0x50nopnopnop ;vai retorna da interrupção direto para esse endereço
kodo:
push 0x0push 0x7c17iret
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,0x0mov es,axclimov [es:0x140], kodomov [es:0x142], cssti
mov ah,0x0 ;leitura do caracter pelo tecladoint 0x50 ;minha interrupção
mov ah,0x1 ;exibição do caracter int 0x50 ;minha interrupção
jmp $
kodo:cmp ah,0x0je tecladocmp ah,0x1je exibirjmp sair
teclado:mov ah,0x0int 0x16jmp sair
exibir:mov ah,0xeint 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 paraenviar 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,202out 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,0x3f8out dx,al
da mesma forma para usar portas de 16bits na instrução in temos que usar o registrador dx
mov dx,0x3f8in 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/2in al,dx ;leitura do barramento daquela porta
cmp al,0x2 ;compara para ver se a tecla pressionado é igual ao valor 2je tecla1 ;se for igual pula para tecla1cmp al,0x3 ;compara para ver se a tecla pressionado é igual ao valor 3je tecla2 ;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,0x60in al,dx
cmp al,0x2je tecla1cmp al,0x3je 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 ;porta COM1mov al,0x0 ;seta o valor 0
repetir:cmp al,0x0 ;compara o al com o valor 0jne sair ;se for diferente de 0 ele pula para sair
in al,dx ;se não ele recebe o valor da porta COM1jmp repetir ;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 tambemfazem 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 dopró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] ;1.68 (st1) ← era o st0 fld [kami] ;989.223 (st0) ← ultimo dado carregado é atualmente o st0
kodo dq 1.68kami 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] ;carrega 5.6 para o registrador st0fst [kami] ;salva o valor do registrador st0 na memoria
kodo dq 5.6kami 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 50kami 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.98kami dq 0
podemos zerar toda pilha do fpu usando a instrução finit
fld [kodo]fld [kami]finit
kodo dq 10.6kami 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.8kami 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.8kami dq 3.5fts315 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.3kami 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.3kami 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 ;zera o registrador st0 subtraindo ele por ele mesmofadd st0,st2 ;adiciona o valor ao registrador st0fst kodo ;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.2kami dq 2.0
na instrução fmul tambem pode ser especificado os registradores
fld [kodo]fld [kami]fmul st0,st1
kodo dq 5.2kami 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.0kami 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.0kami 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.0kami 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.0kami dq 30.2fts315 dq 40.5
o registrador status do fpu (fstat) é um registrador de 16bits e o seu funcionamento é parecido com oregistrador 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 0bitCR3 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.0kami 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.5kami 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 ;cr0=0, cr2=0, cr3=1
kodo dq 3.15
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]fcomfstsw [variavel]
kodo dq 31.5kami dq 31.5variavel dw 0
tambem podemos armazenar no registrador ax
fld [kodo]fld [kami]fcomfstsw ax
kodo dq 31.5kami dq 31.5
com a instrução sahf sera armazenado o resultado nas flags da cpu direto do registrador ax, com issopodemos 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]fcomfstsw axsahf
jz igualja maiorjb menor
igual:maior:menor:
kodo dq 10.0kami dq 30.0
podemos comparar e setar direto na flag com a instrução fcomi
fld [kodo]fld [kami]
fcomi
jz igualja maiorjb menor
igual:maior:menor:
kodo dq 30.0kami dq 10.0
é possível especificar o registrador na instrução fcomi tambem
fld [kodo]fld [kami]fcomi st0,st1
kodo dq 30.0kami 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), ommx 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 debytes 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 51671287
16bits 9052 7812 5167 1287
8bits 90 52 78 12 51 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 0x9052781251671287kami 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 0x9052781251671287kami dq 0x5873579612458965
embora as operações seja feito usando todos os bits do registrador não precisamos usar todos os bitscolocando 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 0x90kami 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 0x3333333344444444kami 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 0x2222888899991111kami 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 0x0148757862483541kami 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 0xf2kami 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 0x70kami 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 0xf2kami 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 50kami 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 0x9054903284537920kami 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 0x5098711234870569kami 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 0x3728468216549872kami 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 0x3728468216549872kami 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 0x3728468216549872kami dq 0x2458745213547899
a multiplicação do mmx deve ser feita com agrupamento de 16bits apenas, na multiplicação usamosa instrução pmullw
movq mm0,[kodo]movq mm1,[kami]pmullw mm0,mm1
kodo dq 0x0201350213240002kami 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 0x6000kami 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 0x1125kami 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 0x987654kami 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 0x5kami 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 0x5056kami 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 0x5kami dq 1
ou com psrld deslocamos a direita com agrupamento de 32bits
movq mm0,[kodo]movq mm1,[kami]psrld mm0,mm1
kodo dq 0x5056kami 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 0x5kami 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 0x5056kami 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 aosegundo 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 0x5010876405kami 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 0x10208590kami 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 0x3290647810208590kami 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 0x1011005600470032kami 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]emmsfld [flavio]
kodo dq 0x50050kami dq 0x315flavio 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.7kami 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.7kami dd 51.92
lembrando que o mesmo pode ser feito usando a sintaxe at&t
movd (kodo),%mm0movd (kami),%mm1pfadd %mm1,%mm0
kodo .float 10.7kami .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.6kami dd 5.3
podemos fazer a multiplicação com pfmul
movd mm0,[kodo]movd mm1,[kami]pfmul mm0,mm1
kodo dd 10.5kami 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.12kami 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.1kami 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,mm1movd [flavio],mm0femmsfld [kodo]
kodo dd 5.5kami dd 2.0flavio 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 16kodo dq 0x3333333344444444,0x1111111122222222
no compilador gas que usa a sintaxe at&t é a diretiva .align
movaps (kodo),%xmm0
.align 16kodo: .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] ;0x54898785187924540000000000000000movlps xmm0,[kami] ;0x54898785187924543845827721254668
kodo dq 0x5489878518792454kami 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 8197731495179521
32bits 87523113 14789514 81977314 95179521
16bits 8752 3113 1478 9514 8197 7314 9517 9521
8bits 87 52 31 13 14 78 95 14 81 97 71 14 95 17 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] ;0x6080906875910856|9987568905896912movups xmm1,[kami] ;0x1090956991285178|a815f58012574689paddq xmm0,xmm1 ;0x711125d206b959ce|419d4c0917e0af9b
kodo dq 0x9987568905896912,0x6080906875910856kami dq 0xa815f58012574689,0x1090956991285178
para a adição com agrupamentos de 32bits usamos a instrução paddd
movups xmm0,[kodo] ;0x60809068|75910856|99875689|05896912movups xmm1,[kami] ;0x10909569|91285178|a815f580|12574689paddd xmm0,xmm1 ;0x711125d1|06b959ce|419d4c09|17e0af9b
kodo dq 0x9987568905896912,0x6080906875910856kami dq 0xa815f58012574689,0x1090956991285178
com a instrução paddw fazemos operações de adição em agrupamentos de 16bits
movups xmm0,[kodo] ;0x6080|9068|7591|0856|9987|5689|0589|6912movups xmm1,[kami] ;0x1090|9569|9128|5178|a815|f580|1257|4689paddw xmm0,xmm1 ;0x7110|25d1|06b9|59ce|419c|4c09|17e0|af9b
kodo dq 0x9987568905896912,0x6080906875910856kami dq 0xa815f58012574689,0x1090956991285178
o mesmo pode ser feito com a instrução paddb para somar em grupos de 8bits
movups xmm0,[kodo] ;0x60|80|90|68|75|91|08|56|99|87|56|89|05|89|69|12movups xmm1,[kami] ;0x10|90|95|69|91|28|51|78|a8|15|f5|80|12|57|46|89paddb xmm0,xmm1 ;0x70|10|25|d1|06|b9|59|ce|41|9c|4b|09|17|e0|af|9b
kodo dq 0x9987568905896912,0x6080906875910856kami 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] ;0x6080|9068|7591|0856|9987|5689|0589|6912movups xmm1,[kami] ;0x1090|9569|9128|5178|a815|f580|1257|4689paddusw xmm0,xmm1 ;0x7110|ffff|ffff|59ce|ffff|ffff|17e0|af9b
kodo dq 0x9987568905896912,0x6080906875910856kami 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.64kami 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] ;0x0000000000000080|0000000000000050movups xmm1,[kami] ;0x0000000000000035|0000000000000010psubq xmm0,xmm1 ;0x0000000000000045|0000000000000040
kodo dq 0x50,0x80
kami dq 0x10,0x35
para subtrair em grupos de 32bits usamos a operação psubd
movups xmm0,[kodo] ;0x80589125|47233254|80538901|68712547movups xmm1,[kami] ;0x35257814|65891254|56782547|89521351psubq xmm0,xmm1 ;0x4b331911|e19a2000|29db63ba|df1f11f6
kodo dq 0x8053890168712547,0x8058912547233254kami 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,0x8058912547233254kami 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,0x8058912547233254kami 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 sinaisusamos psubusw para agrupamentos de 16bits e psubusb para agrupamentos de 8bits
movups xmm0,[kodo]movups xmm1,[kami]psubusb xmm0,xmm1
kodo dq 0x8053890168712547,0x8058912547233254kami 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.0kami 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,0x5420021245328914kami 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.987kami 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.234kami 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,0x3785189753841479kami 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, 158kami 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,0x3785189753841479kami 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, 158kami 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,0x3785189753841479kami 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, 158kami 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] ;0x00000000000005100000000000000111movups xmm1,[kami] ;2psllq xmm0,xmm1 ; 0x510 << 2 | 0x111 << 2
kodo dq 0x111,0x510
kami dq 2, 0
ou deslocar o numero bit a bit a direita usando a instrução pslrq para agrupamentos de 64bits
movups xmm0,[kodo]movups xmm1,[kami]psrlq xmm0,xmm1
kodo dq 0x111,0x510kami dq 2, 0
para deslocar o numero a esquerda com agrupamento de 32bits usamos a instrução pslld
movups xmm0,[kodo]movups xmm1,[kami]pslld xmm0,xmm1
kodo dq 0x3000000012, 0x510500000012kami dq 2, 0
para o deslocamento a direita usando agrupamentos de 32bits usamos a instrução psrld
movups xmm0,[kodo]movups xmm1,[kami]psrld xmm0,xmm1
kodo dq 0x331500001287, 0x331500001287kami dq 2, 0
com a instrução psllw deslocamos a esquerda usando agrupamentos de 16bits
movups xmm0,[kodo]movups xmm1,[kami]psllw xmm0,xmm1
kodo dq 0x35003001, 0x85202001kami dq 2, 0
como tambem podemos usar o psrlw para deslocar a direita com agrupamento de 16bits
movups xmm0,[kodo]movups xmm1,[kami]psrlw xmm0,xmm1
kodo dq 0x35003001, 0x85202001kami dq 2, 0
tambem podemos comparar em agrupamentos de 64bits usando a instrução pcmpeqq, caso ambos os numeros seja igual naquele determinado agrupamento o resultado sera 0xffffffffffffffff caso seja diferente o resultado sera 0x0000000000000000 naquele agrupamento
movups xmm0,[kodo]movups xmm1,[kami]pcmpeqq xmm0,xmm1 ;0x0000000000000000ffffffffffffffff
kodo dq 0x1111222233334444,0x5555333344448888kami dq 0x1111222233334444,0x5555333344441111
podemos fazer a comparação usando agrupamentos de 32bits com a instrução pcmpeqd
movups xmm0,[kodo]movups xmm1,[kami]pcmpeqq xmm0,xmm1 ;0xffffffff00000000ffffffff00000000
kodo dq 0x1111222233334444,0x5555333344448888kami dq 0x1111222200004444,0x5555333344441111
para comparar usando agrupamentos de 16bits usamos a instrução pcmpeqw
movups xmm0,[kodo]movups xmm1,[kami]pcmpeqw xmm0,xmm1 ;0xffffffffffff0000ffffffff0000ffff
kodo dq 0x1111222233334444,0x5555333344448888kami dq 0x1111222200004444,0x5555333344441111
com a instrução pcmpeqb comparamos com agrupamentos de 8bits
movups xmm0,[kodo]movups xmm1,[kami]pcmpeqb xmm0,xmm1 ;0xffffffffffff0000ffffffff0000ffff
kodo dq 0x1111222233334444,0x5555333344448888kami dq 0x1111222200004444,0x5555333344441111
4.0 – x86: Modo Protegido
quando o 286 (ibm-at) foi lançado ele podia acessar ate 16MB de memoria diferente dos processadores x86 anteriores como o 8086 que acessava no maximo 1MB de memoria, para a gente conseguir acessar mais de 1MB no 286 ou ate mesmo nos processadores atuais é necessario habilitar o A20 sendo ele um chaveamento que permite o acesso a mais de um 1MB de memoria, o problema em habilitar o A20 no 286 que não é possivel desabilitar sem reiniciar a maquina, a partir do 386 alem de permitir o acesso a 4GB de memoria podemos ativar e desativar o A20 livremente, existem inumeras formas de habilitar o A20 sendo uma dela pelo próprio controlador do teclado ou simulando ele, outra forma seria por portas IO sendo ela a porta 0x92, outra forma de habilitar o A20 é por interrupção com o int15, para a gente ativar o A20 pela porta IO basta receber o valor atual da porta 0x92 e alterar o segundo bit e depois enviar novamente para a porta 0x92 o valor
in al,0x92 ;pega o valor atual na porta 0x92or al,2 ;altera o segundo bit 1
out 0x92,al ;envia novamente para a porta 0x92
para desativar o A20 pela porta IO basta a gente alterar o segundo bit para 0
in al,0x92 ;pega o valor atual na porta 0x92and al,0xfd ;altera o segundo bit para 0out 0x92,al ;envia novamente para a porta 0x92
pela interrupção int 15 podemos ativar o A20 bastando usar a função 0x24 dele e no registrador al colocamos 0x1 para tivar
mov ah,0x24mov al,0x1int 0x15
tambem é possível desativar o A20 setando 0x0 no registrador al
mov ah,0x24mov al,0x0int 0x15
para a gente habilitar o modo protegido temos que ativar uma flag no CR0 sendo ela o primeiro bit do registrador CR0 (PE), para fazer isso atribuimos ele para o registrador eax e depois alteramos o bit com a operação logica or e depois basta enviar novamente para o registrador CR0
mov eax,CR0or al,1mov CR0,eax da mesma forma para a gente desativar o pmode e voltar para modo real basta setar a flag em 0
mov eax,CR0and al,0xfemov CR0,eax
depois de passar para modo protegido a forma de gerenciar o acesso a memoria e a proteção dela é por tabela de descritores sendo uma dessas tabelas o gdt (global descriptor table), para acessar uma determinada tabela de descritor gdt é usado um seletor de indice que é carregado pela instrução lgdt,um seletor de indice é uma tabela que tem como dado o tamanho da tabela de descritor gdt menos 1 (esse tamanho é um valor do tipo word), seguido do endereço dela na memoria (esse valor é um dado do tipo dword), normalmente é usado label para calcular tudo colocando um label no final da tabela gdt e um label no começo da tabela gdt depois subtrair ambos para conseguir o tamanho da tabela e o seu endereço
kodo_gdtr:dw kodo_fim - kodo_inicio - 1 ;tamanhodd kodo_inicio ;endereço
para carregar o seletor usamos a instrução lgdt com o endereço do seletor
lgdt [kodo_gdtr]
na tabela gdt temos um segmento de 64bits que contem os bits zerados sendo chamado de null descriptor, temos outro segmento de 64bits para o nosso codigo e outro segmento de 64bits para os dados
kodo_inicio:kodo_null:kodo_codigo:kodo_dados:kodo_fim:
como o null descriptor é 64bits com valores nulos podemos alocar dois dados do tipo dword zeradosou apenas um do tipo qword,
kodo_inicio:kodo_null:dq 0x0kodo_codigo:kodo_dados:kodo_fim:
a estrutura do codigo e a dos dados são identicas sendo ambas de 64bits, os 16 primeiros bits indica o limite sendo a parte menos significativa dele (aqui é um dado do tipo word), os 24bits seguintes indica o endereço base sendo a parte menos significativa dele (normalmente é dividido em dois dados um do tipo word e outro do tipo byte), os 8 bits seguinte são flags conhecida como access byte (os 3 primeiros bits dessa flag é o TYPE, o bit seguinte é o DT indica se é um descritor global ou local, temos 3 bits seguintes sendo eles o DPL que seria o nivel de acesso o ring, o ultimo bit indica se tem segmentação), os 8 bits depois do access byte é uma flag conhecida como granularity (os 4 primeiros bits dela é o tamanho de limite, os dois bits seguinte são zero, o próximo bit indica otamano sendo 0 para 16bits e 1 para 32bits, o próximo bit é o bit de granualidade), e por fim temos mais 8 bits que seria a parte mais significativa da base, o segmento dos dados é parecido com o codigo
kodo_inicio:kodo_null:dq 0x0kodo_codigo:dw 0xffff ;limite dw 0x0 ;base lowdb 0x0 ;base middledb 0b10011010 ;access bytedb 0b11001111 ;granularity db 0x0 ;base highkodo_dados:dw 0xff ;limitedw 0x0 ;base lowdb 0x0 ;base middledb 0b10010010 ;access bytedb 0b11001111 ;granularity
db 0x0 ;base highkodo_fim:
normalmente depois de carregar o gdt é dado um pulo incondicional para modificar o registrador cs,depois é modificado tambem os registradores ds,es,fs,gs e ss, veja um exemplo passando para modoprotegido e exibindo algo na tela
[bits 16]org 0x7c00
cli;habilita o a20in al,0x92or al,2out 0x92,al
;carrega o gdtlgdt [kodo_gdtr]
;passa para modo protegidomov eax,cr0or eax,1mov cr0,eax
jmp 0x8:kodo32
;seletor do descritorkodo_gdtr:dw kodo_fim - kodo_inicio -1dd kodo_inicio
;meu descritorkodo_inicio:kodo_null:dq 0x0 ;nullkodo_codigo:dw 0xffff ;limitedw 0x0 ;basedb 0x0 ;basedb 0b10011010 ;accessdb 0b11001111 ;granunalitydb 0x0 ;basekodo_dados:dw 0xffff ;limitedw 0x0 ;basedb 0x0 ;basedb 0b10010010 ;accessdb 0b11001111 ;granunalitydb 0x0 ;basekodo_fim:
[bits 32]kodo32:mov eax,0x10mov ds,eaxmov es,eaxmov fs,eaxmov gs,eaxmov ss,eax
;imprime na tela ja em modo protegidomov eax,0xb8000mov [eax],byte "k"mov [eax+1],byte 1mov [eax+2],byte "o"mov [eax+3],byte 1 mov [eax+4],byte "d"mov [eax+5],byte 1 mov [eax+6],byte "o"mov [eax+7],byte 1
jmp $
times 510 - ($-$$) db 0x0dw 0xaa55
bom galera o modo protegido é bem extenso apenas ele seria suficiente para um livro inteiro, sem dizer que eu não sei muito sobre modo protegido para escrever alguma coisa mais extensa que isso. quem sabe futuramente se eu editar esse ebook talvez eu escreva um pouco mais sobre pmode colocando algumas coisas novas (sem dizer que você ira apenas mexer nessa mudança de modo realpara protegido quando for criar algum kernel ou alguma coisa desse tipo, tirando isso já vai estar programando em modo protegido por cima de um sistema operacional especifico)
4.1 – x86: libc
o libc é uma biblioteca que nos permite usar as funções que usamos na linguagem C direto no assembly ou em qualquer outra linguagem, o libc normalmente é usado sobre uma plataforma especifica já em modo protegido, a forma mais simples de usar o libc no assembly é compilar o nosso codigo gerando o arquivo objeto e lincar ele usando o gcc assim sera lincado de forma automatica com o libc. dependendo do sistema ou do tipo de executavel existe um ponto de entrada os famosos entry point, esse ponto de entrada seria onde fica o codigo que sera executado naquele binario, no sistema linux esse entry point aponta para onde esta o label _start na maior parte das vezes, em programas escrito em C no entry point existe uma chamada de função que chama a função principal main onde é sobrescrito com o nosso codigo e quando damos um return no final docodigo em C seria para voltar para ele e o mesmo se encarrega de finalizar o programa, para a genteusar o libc quando lincamos com o gcc sem precisar colocar argumentos a mais temos que colocar ocodigo depois do label main e especificar ele usando a diretiva global no nasm
;nasm -f elf32 kodo.asm -o kodo.o ;gcc kodo.o -o kodo.out
global main
section .textmain:ret
no gas a diretiva global seria o .globl
;as kodo.asm -o kodo.o;gcc kodo.o -o kodo.out
.globl main
.textmain:ret
na plataforma windows tambem é possível fazer o mesmo porem todo label de função no gcc do windows tem um underline antes
;nasm -f win32 kodo.asm -o kodo.o;gcc kodo.o -o kodo.exe
global _main
section .text_main:ret
o mesmo vale para o gas no windows
.globl _main
.text_main:ret
é recomendado empilhar o ebp atual e armazenar ele para não ter o risco dele se perder e com isso perder a referencia para onde voltar, para isso empilhamos ele e salvamos o stack pointer no base pointer atual, quando terminar basta fazer o oposto movendo o base pointer novamente e retornar
global main
section .textmain:push ebpmov ebp,esp
mov esp,ebppop ebpret
para a gente usar a função do libc no nasm temos que especificar ela como chamada externa usando a diretiva extern (caso exista mais de uma função externa podemos separar elas por virgula), os argumentos da função no libc deve ser empilhados antes da chamada da função, um exemplo seria afunção exit que finaliza o programa sendo que nessa função passamos como argumento o numero do retorno
global mainextern exit
section .textmain:push ebpmov ebp,esp
push 0x0call exit
mov esp,ebppop ebpret
no windows tambem é possível porem lembrando que o label tem um underline antes (isso é regra não se esqueça)
global _mainextern _exit
section .text_main:push ebpmov ebp,esp
push 0x0call _exit
mov esp,ebppop ebpret
no compilador gas não precisamos especificar a função como externa já que por padrão ele já entende que tal função é do libc
.globl main
.textmain:push %ebpmov %esp,%ebp
push $0x0
call exit
mov %ebp,%esppop %ebpret
quando usamos a função exit para finalizar no codigo não precisamos retornar para o entry point pela logica do nosso codigo, então nesse caso não precisamos armazenar o ebp atual embora seja recomendado
global mainextern exit
section .textmain:push 0x0call exit
podemos usar a função puts para exibir uma string na tela, bastando empilhar a string a ser exibida echamar a função puts (ou _puts no windows)
global mainextern puts, exit
section .textmain:push kodocall puts
push 0x0call exit
section .datakodo db "ola mundo",0x0
tambem podemos exibir quantas strings a gente quiser bastando chamar a função quantas vezes a gente precisar
global mainextern puts, exit
section .textmain:push kodocall puts
push kamicall puts
push 0x0call exit
section .datakodo db "ola mundo",0x0kami db "hello world",0x0
o mesmo pode ser feito usando a função printf
global mainextern printf, exit
section .textmain:push kodocall printf
push 0x0call exit
section .datakodo db "ola mundo",0x0
com o printf podemos entrar com vários valores diferentes para ser formato por ele, quando passamos mais de um argumento deve ser passado de forma invertida do ultimo argumento ate o primeiro
global mainextern printf, exit
section .textmain:mov eax,[idade]push eaxpush nomepush formatocall printf
push 0x0call exit
section .datanome db "kodo no kami",0x0idade dd 25formato db "nome: %s idade: %d",0x0
o programa anterior em asm seria equivalente ao próximo em c, veja que a ordem que passamos os argumentos em C é invertido diferente do que ocorre em asm
#include <stdio.h>
int main(void){ char nome[] = "kodo no kami";
int idade = 25; printf("nome: %s idade: %d",nome,idade); exit(0);} outra função onde podemos imprimir um único caracter é o putchar
global mainextern putchar, exit
section .textmain:push 'k'call putchar
push 0x0call exit
podemos entrar com dados digitados pelo usuario com a função gets (viva o buffer overflow em asm tambem kkkk), quando manipulamos dados dinamicos temos que usar a section bss para armazenar eles e não o section .data
global mainextern printf, gets, exit
section .textmain:push kamicall gets
push kamipush kodoformatocall printf
push 0x0call exit
section .datakodoformato db "texto digitado foi %s",0x0
section .bsskami resb 30
com o scanf entramos com dados de valores especificos, o scanf tem dois argumentos o primeiro é oendereço da memoria da variavel e o outro uma string que contem o formato do dado
global mainextern printf, scanf, exit
section .textmain:
push kamipush kodoscancall scanf
mov eax,[kami]push eaxpush kodoformatocall printf
push 0x0call exit
section .datakodoscan db "%d",0x0kodoformato db "numero digitado foi %d",0x0
section .bsskami dd 0
com a função getchar entramos com um único caracter do teclado que sera armazenado no registrador eax
global mainextern putchar, getchar, exit
section .textmain:call getchar
push eaxcall putchar
push 0x0call exit
é possível formatar uma nova string usando entradas de tipos diferentes com a função sprintf, nela passamos como empilhamos os valores, depois uma string de formatação como o printf e por fim a variavel onde vamos armazenar
global mainextern printf, sprintf, exit
section .textmain:mov eax,[kodonum]push eaxmov eax,[kodonum2]push eaxpush kodofmtpush kamicall sprintf
push kamicall printf
push 0x0call exit
section .datakodonum dd 315kodonum2 dd 100kodofmt db "primeiro %d - segundo %d ",0x0
section .bsskami resb 100
com a função strstr podemos encontrar um determinado trecho em uma string, o endereço desse trecho sera retornado no registrador eax, os argumentos passado é a string que vamos buscar, e a string onde estamos buscando
global mainextern printf, strstr, exit
section .textmain:push kodobuscapush kodotextocall strstr
push eaxcall printf
push 0x0call exit
section .datakodobusca db "programador",0x0kodotexto db "era uma vez um programador fim da historia",0x0
para copiar uma string de uma variavel para outra variavel podemos usar a função strcpy, passamos como argumento a variavel a ser copiada seguido do endereço para onde vamos copiar
global mainextern printf, strcpy, exit
section .textmain:push kodopush kamicall strcpy
push kami
call printf
push 0x0call exit
section .datakodo db "Ponpon way way way",0x0
section .bsskami resb 100
podemos pegar o tamanho de uma string usando a função strlen bastamdo empilhar o endereço dela,o retorno dela sera no registrador eax sendo ele o tamanho da string
global mainextern printf, strlen, exit
section .textmain:push kodotexto call strlen
push eaxpush kodofmtcall printf
push 0x0call exit
section .datakodofmt db "tamanho %d",0x0kodotexto db "cade minhas waifu",0x0
para comparar duas strings usamos a função strcmp, empilhamos o endereço das duas strings a ser comparadas caso ambas as strings sejam iguais o retorno sera 0 no registrador eax
global mainextern printf, strcmp, exit
section .textmain:push kodo1push kodo2call strcmp
cmp eax,0jz igual
push textodiferentecall printfjmp sair
igual:push textoigualcall printf
sair:push 0x0call exit
section .datakodo1 db "eofclub",0x0kodo2 db "eofclub",0x0textoigual db "igual",0x0textodiferente db "diferente",0x0
para a gente pegar a hora em segundos usamos a função time sera armazenado no registrador eax o segundo deis de 1/1/1970, empilhamos como argumento para ele o valor nulo
global mainextern printf, time, exit
section .textmain:push 0x0call time
push eaxpush formatocall printf
push 0x0call exit
section .dataformato db "%d ",0x0
outra forma de usar o time seria passar como argumento para ele o endereço onde vamos armazenar
global mainextern printf, time, localtime, exit
section .textmain:push kodocall time
mov eax,[kodo]push eaxpush formatocall printf
push 0x0call exit
section .dataformato db "%d ",0x0
section .bsskodo resd 1
podemos converter o tempo do time para o tempo atual em segundos, minutos, horas, dias e anos usando a função localtime, nela passamos o endereço onde armazenamos o tempo do time, com issoele vai nos retornar um endereço no registrador eax onde podemos acessar de forma indexada para cada valor (esse seria o trabalho da estrutura tm na linguagem C u.u )
global mainextern printf, time, localtime, exit
section .textmain:push kodocall time
push kodocall localtime
mov ebx,[eax] ;segundopush ebxmov ebx,[eax + 4] ;minutopush ebxmov ebx,[eax + 8] ;horapush ebxpush formatocall printf
push 0x0call exit
section .dataformato db "%d:%d:%d ",0x0
section .bsskodo resd 1
com a função sleep damos um delay no programa, o seu argumento seria a quantidade de segundos (sleep no windows deve ser passado o tempo em milesimos e não em segundos)
global mainextern printf, sleep, exit
section .textmain:
push 5call sleep
push kodocall printf
push 0x0call exit
section .datakodo db "volta ao trampo vagabundo ",0x0
podemos manipular um arquivo usando a função fopen para abrir ele, nessa função passamos uma string como argumento co tipo de acesso se é leitura (r), escrita (w) ou concatenação (a). depois passamos outra string com o local do arquivo, o retorno dessa funçao sera o descritor do arquivo no registrador eax
global mainextern fopen, exit
section .textmain:push permpush arqcall fopen
push 0x0call exit
section .dataarq db "kodo.txt",0x0perm db "r",0x0
tambem podemos usar a função fclose para fechar o arquivo (isso deve ser feito para salvar a alteração feita nele ou para ele ser lido por outro programa)
global mainextern fopen, fclose, exit
section .textmain:push permpush arqcall fopen
push eaxcall fclose
push 0x0call exit
section .dataarq db "kodo.txt",0x0perm db "r",0x0
para a gente ler um único byte no arquivo usamos a função fgetc, temos que passar como argumentoo descritor do arquivo que vamos ler sendo que sera armazenado no registrador eax conforme a gente for usando a função fgetc sera lido o arquivo byte a byte (esse seria melhor forma para se ler um arquivo do tipo binario)
global mainextern fopen, fclose, fgetc, putchar, exit
section .textmain:push permpush arqcall fopenmov [descritor],eax
push eaxcall fgetc
push eaxcall putchar
mov eax,[descritor]push eaxcall fclose
push 0x0call exit
section .dataarq db "kodo.txt",0x0perm db "r",0x0
section .bssdescritor resd 1
a função fgets permite a gente ler o arquivo linha por linha, para isso passamos como argumento o descritor do arquivo, o tamanho de uma variavel, e uma variavel onde sera armazenado aquela linha, conforme a gente usar o fgets sera lido as próximas linhas
global mainextern fopen, fclose, fgets, printf, exit
section .textmain:push permpush arq
call fopenmov [descritor],eax
push eaxpush 100push linhacall fgets
push linhacall printf
mov eax,[descritor]push eaxcall fclose
push 0x0call exit
section .dataarq db "kodo.txt",0x0perm db "r",0x0
section .bssdescritor resd 1linha resb 100
com a função fread a gente ler uma quantidade especifica de bytes no arquivo (seria uma leitura por bloco de bytes), o seu argumento é o descritor do arquivo a ser lido, a quantidade de blocos a ser lido, o tamanho do bloco a ser lido, e a variavel onde vamos armazenar
global mainextern fopen, fclose, fread, printf, exit
section .textmain:push permpush arqcall fopenmov [descritor],eax
push eaxpush 1push 100push linhacall fread
push linhacall printf
mov eax,[descritor]push eax
call fclose
push 0x0call exit
section .dataarq db "kodo.txt",0x0perm db "r",0x0
section .bssdescritor resd 1linha resb 100
para a gente escrever alguma coisa no arquivo temos que abrir o arquivo com permissão de escrita (w) com isso ira sobrescrever o arquivo caso ele contenha alguma coisa dentro ou ira criar um arquivo novo caso não exista, ou a permissão para concatenar (a) que ira armazenar no final do arquivo, para escrever usamos a função fprintf sendo o seu funcionamento paracido com printf com o diferencial que temos que passar o descritor do arquivo como ultimo argumento (tambem é possível formatar com outros formatos como o printf sendo o penultimo argumento a string de formatação e os anteriores os valores)
global mainextern fopen, fclose, fprintf, exit
section .textmain:push permpush arqcall fopenmov [descritor],eax
push textopush eaxcall fprintf
mov eax,[descritor]push eaxcall fclose
push 0x0call exit
section .dataarq db "kodo.txt",0x0perm db "w",0x0texto db "isso sera salvo no arquivo",0x0
section .bssdescritor resd 1
com a função ftell pegamos a posição atual do descritor naquele arquivo, seu argumento é o
descritor do arquivo e seu retorno é a posição no registrador eax
global mainextern fopen, fclose, ftell, printf, exit
section .textmain:push permpush arqcall fopenmov [descritor],eax
push eaxcall ftell
push eaxpush formatocall printf
mov eax,[descritor]push eaxcall fclose
push 0x0call exit
section .dataarq db "kodo.txt",0x0perm db "r",0x0formato db "%d ",0x0
section .bssdescritor resd 1
com a função fseek pulamos para uma parte especifica do arquivo, seu argumento seria a origem da onde estamos lendo o arquivo se é do começo do arquivo (0), da posição atual (1) ou do final do arquivo (2). o segundo argumento seria o offset apartir daquela origem, o ultimo argumento seria o descritor do arquivo
global mainextern fopen, fclose, fseek, exit
section .textmain:push permpush arqcall fopenmov [descritor],eax
push 0 ;do começo do arquivopush 20 ;pula para o offset 20
push eaxcall fseek
mov eax,[descritor]push eaxcall fclose
push 0x0call exit
section .dataarq db "kodo.txt",0x0perm db "r",0x0
com a função getenv pegamos as variaveis de ambiente
global mainextern printf, getenv, exit
section .textmain:push kodocall getenv
push eaxcall printf
push 0x0call exit
section .datakodo db "PATH"
podemos executar algum comando do terminal usando a função system (eu ja fiz um compilador para batch/bash usando o system em asm e c, fico mo gambiarra esse trem kkkk)
global mainextern system, exit
section .textmain:push cmdcall system
push 0x0call exit
section .datacmd db "cat /etc/passwd",0x0
4.2 – x86: asm web (cgi)
ate parece brincadeira a linguagem assembly tambem da para ser usada para construir sites e sistemas webs semelhante ao PHP, para usar o asm em um servidor web o próprio servidor deve aceitar CGI, no apache existem modulos como fastcgi que permite executar cgi em diversas liguagens no servidor web seja elas linguagens de scripts como python e perl ou seja programas compilados feito em C, Pascal, Assembly ou qualquer outra linguagem existente. Para um CGI funcionar a primeira saida dele deve ser o cabeçalho sendo o argumento principal e o obrigatorio nocabeçalho o “Content-Type: text/html”, depois temos duas quebras de linhas indicando o fim do cabeçalho e a saida com o nosso html (dependendo de como você configurou o apache ou servidor web o executavel deve esta com a extensão .cgi com permissão de execução e as vezes limitado em um diretorio especifico daquele servidor web)
global mainextern printf
section .textmain:push ebpmov ebp,esp
push cabecalhocall printf
push htmlcall printf
mov esp,ebppop ebpret
section .datacabecalho db "Content-Type: text/html",0xd,0xa,0xd,0xa,0x0html db "<html><body>by kodo no kami</body></html>",0x0
outro exemplo de uma pagina mais dinamica que exibe a hora atual quando o usuario entra nela
global mainextern printf, time, localtime
section .textmain:push ebpmov ebp,esp
push cabecalhocall printf
push horacall time
push hora
call localtime
mov ebx,[eax]push ebxmov ebx,[eax + 4]push ebxmov ebx,[eax + 8]push ebxpush htmlcall printf
mov esp,ebppop ebpret
section .datacabecalho db "Content-Type: text/html",0xd,0xa,0xd,0xa,0x0html db "<html><body><center><h1>hora %d:%d:%d</h1></center></body></html>",0x0
section .bsshora resd 1
podemos usar a função getenv para ler os parametros passados por formularios para o cgi com QUERY_STRINGS, variaveis de ambiente do servidor web como o IP do cliente que conecto no site (REMOTE_ADDR), ler os cookies (HTTP_COOKIE) e etc
global mainextern printf, getenv
section .textmain:push ebpmov ebp,esp
push cabecalhocall printf
push parametrocall getenv
push eaxpush htmlcall printf
mov esp,ebppop ebpret
section .datacabecalho db "Content-Type: text/html",0xd,0xa,0xd,0xa,0x0html db "<html><body><center>%s</center></body></html>",0x0
parametro db "QUERY_STRING",0x0
no cabeçalho podemos adicionar um cookie com parametro Set-Cookie
global mainextern printf
section .textmain:push ebpmov ebp,esp
push cabecalhocall printf
push htmlcall printf
mov esp,ebppop ebpret
section .datacabecalho db "Content-Type: text/html",0xd,0xa,"Set-Cookie: kodo=315",0xd,0xa,0xd,0xa,0x0html db "<html><body><center>cookie criado</center></body></html>",0x0
4.3 – x86: asm inline
muitas linguagens como tambem muitos compiladores permite inserir codigos assembly diretamente naquela linguagem, em programas em C/C++ usando o compilador gcc podemos usar afunção asm para adicionar um codigo em assembly (o gcc usa a sintaxe at&t)
#include <stdio.h>
int main(void){ asm("nop");}
usamos a função sempre quando a gente precisar adicionar uma instrução assembly
#include <stdio.h>
int main(void){ asm("nop"); asm("nop");}
outra forma seria adicionar a instrução na mesma string separando eles por quebra de linha (tambempode ser separado por ponto e virgula)
#include <stdio.h>
int main(void){ asm("nop\nnop");}
podemos colocar em strings seperadas para melhorar
#include <stdio.h>
int main(void){ asm( "nop \n" "nop \n" );}
no compilador gcc é usado a sintaxe at&t
#include <stdio.h>
int main(void){ asm( "mov $315, %eax \n" "mov $100, %ebx \n" "add %ebx, %eax \n" );}
podemos chamar as funções diretamente em asm inline tambem
#include <stdio.h>
void eofebookasm(){ printf("by kodo no kami");}
int main(void){ asm( "call eofebookasm \n" );}
no sistema windows as chamadas de funções via asm deve conter um underline no nome
#include <stdio.h>
void eofebookasm(){ printf("by kodo no kami");}
int main(void){ asm( "call _eofebookasm \n" );}
tambem podemos criar elas em asm inline e chamar em c (é recomendado criar um prototipo dela senão o compilador pode acusar um warning dizendo que ela esta sendo chamada de forma implicita)
#include <stdio.h>
int kodosomar(int,int); //prototipo dela
int main(void){ int x;
asm ("jmp pulo \n"); //isso aqui pula ela evitando executar o ret e buga o trem ^^ asm ( "kodosomar:" "push %ebp \n" "mov %esp,%ebp \n" "mov 8(%ebp),%eax \n" "mov 12(%ebp),%edx \n" "add %edx,%eax \n" "mov %ebp,%esp \n" "pop %ebp \n" "ret \n" ); asm("pulo: \n"); x = kodosomar(300,100); //chamando ela via C printf("%d",x);}
para a gente sair com o valor de um registrador para uma variavel, temos que especificar ele no final da string depois de dois pontos, passamos uma string com o simbolo de igual seguido do registrador que vamos atribuir para aquela determinada variavel e entre parênteses a variavel que vai receber o valor
#include <stdio.h>
int main(void){ int kodo; asm( "nop \n" : "=a"(kodo) //atribui a variavel x o valor do registrador eax );}
quando a gente atribui a uma variavel dessa forma temos que especificar os registradores dentro da
função asm com dois porcentagem e não apenas um
#include <stdio.h>
int main(void){ int kodo; asm( "mov $115, %%eax \n" "mov $200, %%ebx \n" "add %%ebx, %%eax \n" : "=a"(kodo) ); printf("%d", kodo);}
para atribuir em mais de uma variavel separamos por virgula
#include <stdio.h>
int main(void){ int kodo, kami, fts315; asm( "mov $115, %%eax \n" "mov $200, %%ebx \n" "mov $500, %%ecx \n" : "=a"(kodo), "=b"(kami), "=c"(fts315) ); printf("%d %d %d", kodo,kami,fts315);}
para a gente entrar com um valor de uma variavel para um registrador basta colocar outro dois ponto depois daquele, sendo o primeiro dois ponto especifico para saida dos valores e o outro para aentrada dos valores, as entradas para os registradores são atribuidas no começo da chamada da função asm antes de executar qualquer instrução asm, por outro lado a saida sera atribuido no final depois do ultimo codigo asm ser executado
#include <stdio.h>
int main(void){ int kodo, kami, fts315; asm( "nop \n" //codigo asm : //saida : //entrada ); printf("%d %d %d", kodo,kami,fts315);}
na entrada do valor para um registrador basta a gente coloca a letra do registrador como string e entre parênteses especificamos a variavel ou o valor que vamos passar para ele
#include <stdio.h>
int main(void){ int kodo = 2000; asm( "nop \n" : : "a"(kodo) //atribui 2000 ao registrador eax );}
da mesma forma podemos entrar com vários valores e manipular no asm e depois sair com eles
#include <stdio.h>
int main(void){ int kodo = 2000, kami = 500, resu; asm( "add %%ebx,%%eax \n" : "=a"(resu) : "a"(kodo), "b"(kami) ); printf("%d ",resu);}
alem do registrador podemos armazenar um valor para a memoria stack usando a letra “m”, para acessar o valor entrado usamos o porcentagem seguido da posição
#include <stdio.h>
int main(void){ int kodo = 315; asm( : "mov %0,%%eax \n" : : "m"(kodo) );}
tambem podemos ler ou modificar o valor diretamente na memoria pelo endereço da variavel
#include <stdio.h>
int main(void){ int kodo; asm( "mov $15,%%ebx \n" //move 15 ao ebx "mov %%ebx,(%%eax) \n" //joga o valor do ebx no endereço de memoria da variavel : : "a"(&kodo) //entramos com o endereço de memoria da variavel para eax );
printf("%d ",kodo);}
podemos executar os opcodes em uma string byte a byte como os shellcodes dos exploits, para isso a string deve ser uma constante e deve ser global
#include <stdio.h>
//mov eax,0x1; mov ebx,0x0; int 0x80const char kodo[] = "\xb8\x01\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80";
int main(void){ asm("jmp kodo");}
4.4 – x86: disassembler
quando compilamos algum codigo em alguma linguagem é gerado o nosso executavel sendo ele o nosso binario, uma vez que o codigo fonte naquela linguagem de programação é compilado e gerado o executavel, é dificil conseguir aquele mesmo codigo fonte original pelo próprio executaveljá compilado, embora seja possível usando a engenharia reversa para decompilar o executavel e retornar o codigo fonte daquele programa naquela linguagem. a engenharia reversa não traz o codigo fonte original e sim o mais próximo a ele, sendo que em muitas vezes a diferença entre o codigo fonte original e o decompilado é gritante. Uma vez que o codigo fonte é compilado ele é convertido para aquela instrução naquele arquitetura para que o processador possa entender o que deve ser executado ou seja é convertido para codigo na linguagem da maquina (codigo assembler), com isso podemos refazer o codigo fonte apenas vendo o padrão que foi construido naquele binario.Vamos supor que a gente compile o seguinte codigo em um compilador ficticio de pascal
program kodo;begin writeln(‘exemplo decompile’); writeln(‘by kodo no kami’); exit(0);end.
supondo que o exemplo anterior nos gera o seguinte binario depois de compilado (sendo esse um binario direto para arquitetura x86 aberto em editor um hex)
00000000 68 11 00 E8 2B 00 68 22 00 E8 25 00 h...+.h"..%.0000000C 6A 00 E8 21 00 65 78 65 6D 70 6C 6F j..!.exemplo00000018 20 64 65 63 6F 6D 70 69 6C 65 62 79 decompileby00000024 20 6B 6F 64 6F 20 6E 6F 20 6B 61 6D kodo no kam00000030 69 i
podemos pegar cada byte ou uma sequencia de bytes do nosso binario ficticio e os converter para uma instrução valida da arquitetura x86, com base nos opcodes do binario podemos ver as mesmas instruções que sera executadas pelo processador em codigo assembly, da mesma forma podemos descobrir qual função foi usada se a gente conhecer como aquele determinado compilador gera aquele binario (qual endereço de memoria que esta armazenado a função, endereços das variaveis, aestrutura do binario e etc)
00000000 681100 push word 0x11 ;endereço do ‘exemplo decompile’00000003 E82B00 call word 0x31 ;chadama da função writeln00000006 682200 push word 0x22 ;endereço do ‘by kodo no kami’00000009 E82500 call word 0x31 ;chamada da função writeln0000000C 6A00 push byte +0x0 ;passagem do valor 00000000E E82100 call word 0x32 ;chamada da função exit...
o interessente que usando um editor hexadecimal podemos hackear esse programa ou qualquer outro fazendo ele executar qualquer coisa que a gente quiser apenas modificando os bytes no binario. no exemplo anterior se a gente modificar os bytes “68 11 00” no começo do binario para os bytes “68 22 00”, o meu programa vai exibir duas vezes a string “by kodo no kami”. Em teoria os cracks de programas são feitos dessa forma alterando o fluxo original do programa e burlando ele
00000000 68 22 00 E8 2B 00 68 22 00 E8 25 00 h"..+.h"..%.0000000C 6A 00 E8 21 00 65 78 65 6D 70 6C 6F j..!.exemplo…
um disasembly legal para dissecar binarios puros para arquitetura x86 é o ndisasm (o problema é o alinhamento um byte na posição errada pode desencadear uma sequencia de bytes na posição erradagerando instruções diferente)
kodonokami@debian:~$ ndisasm kodo.bin 00000000 681100 push word 0x1100000003 E82B00 call word 0x3100000006 682200 push word 0x2200000009 E82500 call word 0x31...
outro disassembly para arquitetura x86 é o gnu objdump sendo uma das ferramentas do pacote binutils (tambem existe objdump para outras arquitetura entre elas a mips, arm, avr entre outras), o objdump é um bom dissecador para executaveis de sistemas operacionais como windows (pe) e linux (elf) já que identifica bem os labels, funções e section dependendo do compilador
kodonokami@debian:~$ objdump -d kodo.out
kodo.out: file format elf32-i386
Disassembly of section .text:
08048060 <_start>: 8048060: b8 01 00 00 00 mov $0x1,%eax 8048065: bb 00 00 00 00 mov $0x0,%ebx 804806a: cd 80 int $0x80
alem dos dissembly tambem existem os debuggers. a diferença entre o disassembly e um debugger que o debugador roda o programa e o desmonta em tempo de execução, podendo colocar alguns breakpoint em determinados ponto do codigo e com isso parar a sua execução e analisar tanto a memoria quanto os registradores afetados naquele breakpoint, boa parte dos debugadores tambem podem ser usado para dissecar o programa sem precisar rodar ele igual os dissembly, um debugger
que eu recomendo aprender a mexer é o gdb (gnu debbuger), para a gente usar o gdb basta digita o comando gdb seguido do nome do programa, com isso ira abrir o gdb já com o programa especifico que vamos debugar embora parado sem esta em execução
kodonokami@debian:~$ gdb kodo.out(gdb)
podemos digita apenas gdb para abrir o gdb, depois dentro dele podemos usar o comando target exec seguido do nome do programa para especificar o programa que vamos debugar
kodonokami@debian:~$ gdb(gdb) target exec kodo.out
o gdb tambem permite debugar programas já em execução especificando -p seguido do pid do processo, quando o gdb abre o processo ele é parado naquele ponto
kodonokami@debian:~$ gdb -p 315(gdb)
ou podemos abrir o gdb e usar o comando attach seguido do pid
kodonokami@debian:~$ gdb(gdb) attach 315
depois de aberto podemos executar ou restarta o programa usando o comando run, se o programa termina a execução podemos usar esse comando para executar ele novamente
kodonokami@debian:~$ gdb kodo.out(gdb) run
caso você tenha aberto o programa já em execução direto pelo pid, você pode usar o comando cont para continuar a sua execução já que estara pausado
kodonokami@debian:~$ gdb -p 315(gdb) cont
com o disass seguido do nome da função podemos dissecar o executavel naquela função especifica
kodonokami@debian:~$ gdb kodo.out(gdb) disass *mainDump of assembler code for function main: 0x80000620 <+0>: lea 0x4(%esp),%ecx 0x80000624 <+4>: and $0xfffffff0,%esp 0x80000627 <+7>: pushl -0x4(%ecx) 0x8000062a <+10>: push %ebp 0x8000062b <+11>: mov %esp,%ebp...
podemos colocar um breakpoint usando o comando break seguido do endereço da onde vamos
colocar o breakpoint
kodonokami@debian:~$ gdb kodo.out(gdb) break *0x8000066c
outra forma de colocar um breakpoint é pelo nome da função
kodonokami@debian:~$ gdb kodo.out(gdb) break *printf
tambem podemos atribuir um valor ao nome da função para ele parar a partir daquele endereço incrementado com o valor
kodonokami@debian:~$ gdb kodo.out(gdb) break *printf+7
se a gente rodar o programa ou continar a sua execução (caso esteja parado), ele ira parar no breakpoint voltando para o gdb
(gdb) break *0x8000066c(gdb) run
para continuar a sua execução usamos o comando cont
(gdb) break *0x8000066c(gdb) runBreakpoint 1, 0x8000066c in main ()(gdb) cont
podemos exibir os breakpoint usando o comando info break
(gdb) info breakNum Type Disp Enb Address What1 breakpoint keep y 0x8000066c <main+76>
para remover um breakpoint com o comando delete break seguido do Num dele (se não digitar o Num sera removido todos)
(gdb) delete break 1
com o comando disable desativamos um breakpoint
(gdb) disable break 1
tambem podemos ativar ele novamente com o comando enable
(gdb) enable break 1
para a gente exibir os registradores e os valores atuais deles usamos o comando “info registers”
(gdb) info registerseax 0xbffff55c -1073744548ecx 0xfbad2288 -72539512edx 0xb7fae87c -1208293252...
o comando anterior só ira exibir os registradores principais, podemos utilizar o “info all-registers” para exibir todos os registradores incluido os de fpu, mmx e sse
(gdb) info all-registers eax 0xbffff55c -1073744548ecx 0xfbad2288 -72539512edx 0xb7fae87c -1208293252...
podemos exibir um único registrador usando o comando print seguido do registrador
(gdb) print $eip$1 = (void (*)()) 0x8000066c <main+76>
tambem podemos exibir um endereço de memoria com o print
(gdb) print *0x8000073d$1 = 1768384868
para exibir o valor em hex usamos o simbolo de barra seguido do x depois do print
(gdb) print/x *0x8000073d$1 = 0x69676964
outro comando que podemos usar para exibir um determinado endereço é o x
(gdb) x 0x8000073d0x8000073d: 0x69676964
quando é uma string podemos usar o /s para exibir ela
(gdb) x/s 0x8000073d0x8000073d: "digite seu nome: "
podemos exibir todas as funções usadas e seus endereços com o comando “info functions”
(gdb) info functionsAll defined functions:
Non-debugging symbols:0x8000041c _init0x80000450 strcmp@plt
0x80000460 printf@plt0x80000470 gets@plt0x80000480 puts@plt
quando o programa esta rodando poidemos utilizar o comando “info proc” para exibir as informações do processo do programa em execuçao
(gdb) info proc process 19793cmdline = '/home/kodonokami/Desktop/kodo.out'cwd = '/home/kodonokami/Desktop'exe = '/home/kodonokami/Desktop/kodo.out'
com o “info threads” exibimos informações das threads
(gdb) info threads Id Target Id Frame * 1 process 19793 "kodo.out" 0xb7fd8d40 in __kernel_vsyscall ()
para a gente exibir informações das bibliotecas compartilhadas como as DLL do windows ou SO dolinux usamos o comando “info sharedlibrary”
(gdb) info sharedlibrary From To Syms Read Shared Object Library0xb7fdb860 0xb7ff46bd Yes (*) /lib/ld-linux.so.20xb7e107f0 0xb7f3d59f Yes (*) /lib/i386-linux-gnu/libc.so.6(*): Shared library is missing debugging information.
5.0 – EOF
bom galera aqui termina mais um ebook, futuramente esse ebook pode acabar sofrendo algumas modificações para sua correção e para adição de novos conteudos. e como sempre não posso deixar de agradecer aos membros do grupo eof como o s1m0n sempre postando bons conteudos la, o hefest0 que manja pra caramba de asm tambem (seu nick antigo era …. kkk), o sir.rafiki aquele viciado em java que não para de jogar dota (joga terraria mano bem melhor), neofito que sempre merecomenda algum anime, o nosso coder de malware o 0x4xh4x, a melots uma amiga que considero muito, angley e jhonydk os manos viciados em games do grupo (jogar terraria ninguém quer ne u.u), refell que ate hoje dificilmente vejo ele online quando estou online ‘-’, lend4pop que aparece no discord as vezes e depois sai na mesma hora kkk, o 0racle e fascist que estão perdidos nos confins do irc, o mano gjunnior e os tuto de arch linux, os membros mais novos v4p0r, hchild e onezer0. tambem não posso deixar citar alguns grupos embora não seja possível citar todos membros como os do fabrica de noobs, brasil blackhat, gamemods, caveira tech, gray hat (face), entre outros.