Linguagem de montagem x86

Código mnemônico para arquitetura x86

A linguagem de montagem x86 é a família de linguagens de montagem de compatibilidade reversa para a classe de processadores x86, que inclui a série Pentium da Intel e a série Athlon da AMD. Como todas linguagens de montagem, ela utiliza mnemônicas curtas para representar as operações fundamentais que a CPU em um computador pode realizar. Compiladores muitas vezes produzem código de montagem como um passo intermediário ao traduzir um programa de alto nível para código de máquina. Lembrada como uma linguagem de programação, a programação de montagem é especificada à máquina e de baixo nível, com certeza o mais baixo nível possível, adereçando diretamente hardware, também conhecido como metal descoberto. Linguagens de montagens são, portanto, principalmente usadas para aplicações detalhas ou de tempo crítico como inicializadores, núcleos de sistemas operativos, e controladores de dispositivos, assim como para sistemas operativos de tempo real ou pequenos sistemas embarcados, embora seja perfeitamente possível escrever um software aplicativo em um dialeto apropriado (determinado pela escolha do montador) da linguagem de montagem com um montador que compila para a arquitetura alvo.

História

editar

As CPUs Intel 8088 e 8086 era CPUs de 16 bits que tiveram primeiro um conjunto de instruções, que hoje é comumente conhecido como x86, eles eram uma evolução da geração anterior de CPUs de 8 bits como a 8080, herdando muitas características e instruções, estendidas à era 16 bits. A 8088 e 8086 ambas utilizada um barramento de endereços de 20 bits e registros interno de 16 bits, enquanto o 8086 possuía um barramento de endereços de 16 bits, a 8088, criado como uma opção de baixo custo para aplicações embarcadas, possuía um barramento de endereços de 8 bits. A linguagem de montagem x86 cobria diferentes versões das CPUs que seguiram, da Intel; a 80188, 80186, 80286, 80386, 80486, Pentium além de CPUs da AMD e Cyrix como os processadores 5x86 e K6. O termo x86 aplica para qualquer CPU capaz de rodar o código de montagem original (geralmente ele vai rodar pelo menos uma das extensões também).

O conjunto de instruções moderno é um superconjunto das instruções do 8086 e uma série de extensões para este conjunto de instruções que começam com o microprocessador Intel 8008, há compatibilidade reversa praticamente total entre o chipe Intel 8086 até os mais modernos Pentium 4, Intel Core, Core i7, AMD Athlon 64, Opteron, etc., embora certas exceções existam. Na prática é típico utilizar instruições que irão executar qualquer coisa posterior ao Intel 80386 (ou um clone totalmente compatível) ou então qualquer coisa posterior a um Intel Pentium (ou clone compatível), mas nos anos recentes, vários sistemas operativos e aplicativos começaram a exigir processadores mais modernos, pelo menos para obter suporte a extensões específicas como (ex. MMX, 3DNow!, SSE/SSE2/SSE3).

Mnemônicas e opcodes

editar

Cada instrução de montagem x86 é representada por uma mnemônica, que geralmente com um ou mais operadores, traduz para um ou mais bytes, chamados opcode; a instrução NOP traduz para 0x90, por exemplo e a instrução HLT traduz para 0xF4. Há opcodes sem nenhuma mnemônica documentada, que diferentes processadores podem interpretar de maneiras diferentes, um programa usando elas funciona de maneira inconsistente ou até gera exceções em alguns processadores. Em competições de escrita, estes opcodes acabam sendo uma modo de tornar o código menor, mais rápido ou elegante ou apenas para mostrar a profissionalidade do autor.

Sintaxe

editar

A linguagem de montagem x86 possui dois ramais de sintaxe: sintaxe Intel, usada originalmente para documentação da plataforma x86, e sintaxe AT&T.[1] Sintaxe Intel é dominante no sistema operativo Microsoft Windows. No mundo Unix/Linux, ambas são usadas pois o GCC suportava apenas a sintaxe AT&T nos primórdios de sua criação. Aqui está um resumo das principais diferenças entre a Sintaxe Intel e a Sintaxe AT&T:

Attribute AT&T Intel
Parameter order source comes before the destination destination before source follows that of many program statements ("a=5" is "mov eax, 5")
Parameter Size mnemonics are suffixed with a letter indicating the size of the operands (e.g. "q" for qword, "l" for dword, "w" for word, and "b" for byte)[1] derived from the name of the register that is used (e.g. eax, ax, al)
Immediate value sigils prefixed with a "$", and registers must be prefixed with a "%"[1] The assembler automatically detects the type of symbols - if they are registers, constants or something else.
Effective addresses general syntax DISP(BASE,INDEX,SCALE)

Example: movl mem_location(%ebx,%ecx,4), %eax

use variables, and need to be in square brackets; additionally, size keywords like 'byte', 'word' or 'dword' have to be used.[1]

Example: mov eax, dword [ebx + ecx*4 + mem_location]

A maioria dos montadores x86 usa a sintaxe Intel, incluindo MASM, TASM, NASM, FASM e YASM. GAS suporta ambas sintaxes desde a versão 2.10 através da diretiva .intel_syntax.[1][2][3]

Registros

editar

Processadores x86 possuem uma coleção de registros disponíveis para serem usados como armazéns para dados binários. Colectivamente os dados e os endereços de registro são chamados de registros gerais. Cada registro possui um propósito especial além do que eles todos podem fazer.

  • AX multiplicar/dividir
  • BX registro indicador para MOVE
  • CX registro de contagem para operadores de linha
  • DX endereço de porta para IN e OUT

Além dos registro gerais, adicionalmente há os:

  • IP ponteiro de instrução;
  • FLAGS;
  • registros de segmento (CS, DS, ES, FS, GS, SS) que ativam onde um segmento de 64k começa;
  • registros extra de extensão (MMX, 3DNow!, SSE, etc.).

O registro IP aponta para onde no programa o processador está atualmente executando o seu código. O registro IP não pode ser acessado pelo programador diretamente.

Endereço segmentado

editar

A arquitetura x86 em modo real e virtual 8086, usa um processo conhecido como segmentação de endereços de memória, não o modelo de memória plana, usado em muitos outros ambientes. Segmentação envolve a composição de um endereço de memória de duas partes, um segmento e um deslocamento; pontos do segmento até o início de um grupo de 64 KB de endereços, e o deslocamento determina o quão longe desse endereço iniciar o endereço desejado. No endereçamento segmentado, dois registros são necessários para um endereço completo da memória: um para segurar o segmento, a outra para realizar o deslocamento. A fim de traduzir de volta para um endereço de plano, o valor de segmento é deslocado quatro bits à esquerda (o equivalente a multiplicação por 24 ou 16), em seguida, adicionado ao deslocamento para formar o endereço completo, que permite quebrar a barreira de 64k com a escolha inteligente de endereços, endereçar até 1 MB de memória enquanto estiver usando 16 bits, ao invés de registros de 32 bits, embora ele torna a programação muito mais complexa. No modo real, apenas, por exemplo, se DS contém o número hexadecimal 0xDEAD, e DX contém o número 0xCAFE, eles juntos apontariam para o endereço de memória 0xDEAD * + 0x10 = 0xCAFE 0xEB5CE. No modo real o processador pode endereçar até 1.048.576 bytes (1 MB). Isto aplica-se a 20-bits espaço de endereço. Ao combinar valores de deslocamento do segmento encontramos um endereço de 20 bits. O PC original de IBM restringiu programas a 640 KB, mas uma especificação da memória expandida foi usada para executar um esquema do interruptor de banco que caísse fora do uso quando uns sistemas operacionais posteriores, tais como Windows, usaram as escalas de endereço maiores de uns processadores mais novos e executaram seus próprios esquemas da memória virtual. O Modo Protegido, começando com o processador Intel 80286, foi utilizado pelo OS / 2. Várias deficiências, como a impossibilidade de acessar o BIOS, e da incapacidade de voltar ao modo real sem precisar reiniciar o processador. impediram uma utilização generalizada. O 80286 também estava limitado a endereçamento de memória em segmentos de 16 bits, ou seja, apenas 216 bytes (64 kilobytes) podiam ser acessados ao mesmo tempo. Para acessar a funcionalidade estendida do 80286, o sistema operacional deveria definir o processador em modo protegido, permitindo endereçamento de 24 bits e, portanto, 224 bytes de memória (16 megabytes). Ao se referir a um endereço com um segmento e um deslocamento, a notação de segmento: offset é usada, assim, no exemplo acima, o endereço 0xEB5CE plano pode ser escrito como 0xDEAD: 0xCAFE ou como um segmento, e o deslocamento par de registradores; DS: DX.

Registradores de Segmento:

CS: Segmento de Código;
DS: Segmento de Dados;
SS: Segmento de Pilha;
ES: Segmento de Dados Extra;
FS: Segmento de Dados Extra 2;
GS:Segmento de Dados Extra 3;

Registradores de Ponteiros e Índice, usados para armazenar endereços de memória:

ESP: Ponteiro de pilha
EBP: Ponteiro de base da pilha
ESI: Ponteiro de índice fonte  (pode servir como registradores de uso geral)
EDI: Ponteiro de índice destino (pode servir como registradores de uso geral)
EIP: Ponteiro de instrução (contador de programa)

O Intel 80386, apresentou três modos de operação: modo real, modo protegido, e modo virtual. O modo protegido, que estreou no 80.286 foi estendido para permitir o 80386 para endereçar até 4 GB de memória, o novo modo 8086 virtual, (vm86) tornou possível executar um ou mais programas de modo real em um ambiente protegido, que em grande parte imitando modo real, embora alguns programas não eram compatíveis (geralmente como resultado de endereçamento de memória usando truques ou indeterminado op-codes). O modelo de memória plana de 32 bits, do modo protegido, estendida até 80386 pode ser a mudança mais importante da característica para a família de processadores x86, até que a AMD lançou x86-64 em 2003, ajudou a impulsionar a adoção em grande escala do Windows 3.1 (que se baseava em modo protegido), desde que o Windows pode agora executar vários aplicativos simultaneamente, incluindo aplicativos DOS, usando memória virtual e multitarefa simples.

Modo de Execução

editar

Os processadores x86 sustentam cinco modos de operação para o código 86, o modo real, o modo protegido, modo longo, modo 86 virtual, e administração de sistemas, em que algumas instruções estão disponíveis e outras não. Um subgrupo de 16 bits de instruções estão disponíveis no modo real (todos os processadores x86), o modo 16-bit protegido (80286 em diante), V86 (80386 e posteriores) e CEM (Alguns i386SL Intel, i486 e posteriores). Em 32 bits do modo protegido (Intel 80386 em diante), instruções de 32 bits (incluindo as extensões mais tarde) também estão disponíveis, no modo de tempo (a partir de AMD Opteron), instruções de 64 bits, e registra mais, também estão disponíveis. O conjunto de instruções é semelhante em cada modalidade, mas de endereçamento de memória e tamanho da palavra variam, exigindo estratégias de programação diferentes.

Os modos em que o código x86 pode ser executado são:

Modo real (16 bits): Modo Real é caracterizado por um espaço de endereço de 20 bits de memória segmentada, (dando um pouco mais de 1 MB de memória endereçável) e software de acesso ilimitado e direto da memória e endereços de I / O e hardware periférico. Modo Real não oferece suporte para proteção de memória, multitarefa, ou níveis de privilégio de código.

O modo protegido (16 bits e 32 bits): Em computação, de modo protegido, também chamado de modo de endereço virtual protegido, é um modo operacional de x86 compatível com unidades de processamento central (CPU). Ele permite que o software do sistema utilize recursos como memória virtual, paginação, multi-tarefa segura e outros recursos projetados para aumentar o controle de um sistema operacional em relação ao software aplicativo.

Modo longo (64 bits): É a modalidade onde uma aplicação 64 – bit (ou o sistema de exploração), podem alcançar as instruções e os registros de 64-bit. Os programas de 32 bits e os programas de 16 bits do modo protegido são executados em um sub-modo da compatibilidade; o modo real ou o modo virtual 8086 não podem funcionar nesta modalidade.

Modo Virtual 8086 (16 bits): Permite a execução de aplicativos de modo real que são incapazes de rodar diretamente em modo protegido, enquanto o processador está executando um sistema operacional de modo protegido.

Modo de administração de sistemas (16 bits): É um modo em que toda a execução normal (incluindo o sistema operacional) é suspenso. Foi lançado com a Intel 386SL

Alternando entre modos

editar

O processador entra em modo real imediatamente após ligar, assim que um kernel de sistema operacional, ou outro programa, deve explicitamente alternar para outro modo, se pretende executar alguma coisa, mas de modo real. Modos de comutação são feitos modificando certas partes dos registos do processador de controle, embora alguns requerem preparação prévia, em muitos casos, e alguma limpeza no interruptor pode ser necessária.

Em geral, as características do moderno conjunto de instruções x86 são:

  • A codificação compacta
  • Tamanho variável e alinhamento são independentes
  • Principalmente nas instruções de um e dois endereços, o primeiro operando é também o destino.
  • Operandos da memória como o de origem e de destino são suportados
  • Uso geral e implícito do registro
  • Produz bandeiras condicionais implicitamente com a maioria de instruções da ULA.
  • Suporta vários modos de endereçamento incluindo imediato, deslocamento e índice escalado, mas não relativo ao PC, com exceção de saltos (apresentado como uma melhoria na arquitetura x86).
  • Inclui em ponto flutuante para uma pilha de registos.
  • Contém a sustentação especial para instruções atômicas
  • Instruções SIMD

Instruções de pilha

editar

A arquitetura x86 tem suporte de hardware para a execução do mecanismo da pilha. Instruções como o push, pop, call e ret são usados corretamente com a pilha para passar parâmetros, alocar espaço para dados locais e para salvar e restaurar pontos de retorno de chamada. A instrução ret size é muito útil para a aplicação eficiente em termos de espaço nas convenções de chamada, onde o receptor é responsável pela recuperação do espaço da pilha ocupado por parâmetros.

Ao configurar um quadro de pilha para armazenar dados locais de um procedimento recursivo, há várias opções, a instrução enter de alto nível leva um argumento de procedure-nesting-depth, bem como um argumento de tamanho local, e pode ser mais rápida do que a manipulação explícita da registradores, mas geralmente não é usado. Se ele é mais rápido depende da aplicação particular x86, bem como a convenção de chamada e o código que se deseja executar em vários processadores normalmente irá correr mais rápido na maioria dos alvos sem ele.

A série completa de modos de endereçamento, mesmo para obter instruções sobre push e pop, faz uso direto da pilha de inteiros, ponto flutuante e os dados de endereço simples, bem como mantimento das especificações ABI e os mecanismos relativamente simples comparadso a algumas arquiteturas RISC.

Instruções de Ponto Flutuante

editar

A linguagem assembly x86, inclui instruções para uma unidade de ponto flutuante baseado em pilha. Eles incluem a adição, subtração, negação, multiplicação, divisão, resto de divisão, raízes quadradas, o truncamento inteiro, o truncamento da fração, e de escala por potência de dois. As operações também incluem instruções de conversão que podem carregar ou armazenar um valor de memória em qualquer um dos seguintes formatos: decimal codificado binário, inteiro de 32 bits, inteiro de 64 bits, 32 bits de ponto flutuante, ou ponto flutuante de 80 bits (após o carregamento, o valor é convertido para o modo de ponto flutuante usado atualmente). X86 também inclui uma série de funções transcendentais incluindo seno, cosseno, tangente, exponenciação, e logaritmos com bases 2, 10 ou hexadecimal. Como nos números inteiros, o primeiro operando é tanto o operando fonte como operando destino, fsubr fdivr devem ser apontados como o primeiro, trocando os operandos fonte antes de executar a subtração ou divisão. As instruções de adição, subtração, multiplicação, divisão, armazenar e comparação incluem modos de instrução que irá aparecer no topo da pilha após a operação ser concluída.

Instruçõs SIMD

editar

CPUs x86 modernas contêm instruções SIMD, que em grande parte, executam a mesma operação em paralelo em muitos valores codificados num registro SIMD de largura. Várias tecnologias de instruções apoiam as operações em diferentes conjuntos de registros diferentes, mas tomadas em conjunto completo, que incluem cálculos gerais sobre inteiro ou aritmética de ponto flutuante (adição, subtração, multiplicação, deslocamento, minimização, maximização de comparação, divisão ou raiz quadrada). SSE também inclui um modo de ponto flutuante em que apenas o primeiro valor dos registros é realmente modificado (ampliado em SSE2). Algumas outras instruções incomuns foram adicionadas, incluindo uma soma das diferenças absolutas (utilizados para a estimativa de movimento em compressão de vídeo, como é feito em MPEG) e uma de 16 bits multiplica instrução acumulada. SSE (desde SSE3) e 3DNow! extensões que incluem adição e subtração de instruções para o tratamento, combinando valores de ponto flutuante como números complexos. Estes conjuntos de instruções também incluem numerosas instruções sub-palavra para baralhar, inserindo e extraindo os valores ao redor e dentro dos registros. Além disso, há instruções para mover dados entre os registros inteiro e MMX.

Instruções de Manipulação de Dados

editar

O processador x86, também inclui modos de endereçamento complexos para lidar com a memória com um imediato deslocamento, um registro, um registro com um deslocamento, registrar uma escala com ou sem deslocamento, e um registro com um opcional offset, e, outro registro escalado. Assim, por exemplo, um pode codificar mov eax [Table + ebx + esi*4], como uma única instrução que carrega 32 bits de dados a partir do endereço computado como: (Table + ebx + esi * 4) deslocado do seletor de ds, e armazená-lo para o registro eax. Em processadores x86 pode carregar e usar a memória compatível com o tamanho de qualquer registro que se operar. (As instruções SIMD também incluem instruções de meia carga.) O conjunto de instruções x86 inclui carregar a string, e instruções de movimento (LODs, OCP e movs) que executam cada operação para um determinado tamanho (b para byte de 8 bits, w para a palavra de 16 bits, d para 32bits palavra dupla), então incrementos / decrementos (dependendo do DF, flag de direção) ocorrem no registo de endereço implícito (SI para LODs, di para stos e tanto para movs). Para carregar o registro implícito do alvo/fonte está no registro al, ax ou eax (dependendo do tamanho). O segmento implícito usado é ds para lods, es para stos e ambos para movs. A pilha é implementada implicitamente com um decremento (push) e incremento (pop) ponteiro de pilha. No modo de 16 bits, esse ponteiro implícito da pilha é tratado como SS: [SP], no modo de 32 bits é SS: [ESP], e no modo de 64 bits é [RSP]. O ponteiro da pilha realmente aponta para o último valor que foi armazenado, sob a suposição de que o seu tamanho vai coincidir com o modo de operação do processador (ou seja, 16, 32 ou 64 bits) para coincidir com a largura padrão do push / pop / chamada / instruções ret. Também estão incluídas as instruções que reservam e retiram dados do topo da pilha, enquanto a criação de um ponteiro da estrutura pilha em pb / ebp / ead. No entanto a configuração, direta ou adição e subtração para o sp / ESP / registro rsp também é suportada, portanto, o entrar / sair instruções são geralmente desnecessários.

Código do início da função:

  pushl   %ebp   / * Função de salvar quadro de pilha de chamada (% ebp) 
  movl    %esp,%ebp /* Fazer um novo quadro de pilha em cima da pilha * nosso interlocutor /
  subl    $4,%esp  / * Atribuir 4 bytes de espaço de pilha para * esta função de variáveis locais é funcionalmente equivalente a: enter $4,$0

Outras instruções para manipular a pilha incluem pushf / popf para armazenar e recuperar os Flags. As instruções pusha/ popa vão armazenar e recuperar o número inteiro de registro de estado de/ para a pilha. Valores para umSIMD carregar ou armazenar são assumidos para serem embalados em posições adjacentes do registo SIMD e alinhá-las em ordem sequencial little-endian. Alguns carregam instruções SSE e para armazenar exigem alinhamento de 16 bytes para funcionar corretamente. Os conjuntos de instrução SIMD também incluem "prefetch" instruções que realizam o carregamento, mas não são alvo de qualquer registro, utilizadas para o carregamento do cache. Os conjuntos de instruções SSE também incluem instruções de armazenamento não-temporal que irão realizar os armazenamentos direto para a memória sem a realização de um cache se o destino já não está em cache (caso contrário, ele irá se comportar como um armazenamento normal.)

A maioria das instruções genéricas inteiras e de ponto flutuante (mas não SIMD) podem usar um parâmetro de como um endereço complexo como o parâmetro da segunda fonte. instruções inteiras podem também aceitar um parâmetro de memória como operando destino

Fluxo de Programa

editar

O conjunto x86 tem uma operação do salto incondicional, o jmp, que pode tomar um endereço imediato, um registro ou um endereço indireto como um parâmetro (note que a maioria dos processadores RISC suportam apenas um registo link ou deslocamento imediato curto para pular). Também são suportados vários saltos condicionais, incluindo JE, JNE, JG, JL, JA JB.

  • JE: desvia se igual;
  • JNE: desvia se diferente;
  • JZ: se o ZF for igual a 1 (ZF= zero flag);
  • JMP: desvio incondicional;

Estas operações condicionais são baseadas no estado de bits específicos em registradores Flags. Muitas operações aritméticas e lógicas estabelecidas, claras ou complementar estes Flags, dependendo o seu resultado. A comparação cmp (compare) e instruções de teste definir os sinalizadores, como se tivessem realizado uma subtração ou um bit a bit E operação, respectivamente, sem alterar os valores dos operandos. Há também instruções de como clc (flag de carry claro) e CMC (complemento de bandeira) que trabalham diretamente nas bandeiras. comparações de ponto flutuante são executadas através FCOM Ficom ou instruções que eventualmente tem que ser convertido para inteiro bandeiras.

Referências

editar
  1. a b c d e Ram Narayam (17 de outubro de 2007). «Linux assemblers: A comparison of GAS and NASM». Consultado em 2 de julho de 2008 
  2. Randall Hyde. «Which Assembler is the Best?». Consultado em 18 de maio de 2008. Arquivado do original em 18 de outubro de 2007 
  3. «GNU Assembler News, v2.1 supports Intel syntax». 4 de abril de 2008. Consultado em 2 de julho de 2008 

Ver também

editar

Ligações externas

editar
 
Wikilivros
O Wikilivros tem um livro chamado Assembly x86

Manuais

editar