Linguagem Assembly Para I386 E X86-64
Total Page:16
File Type:pdf, Size:1020Kb
Linguagem Assembly para i386 e x86-64 Versão 0.8.5 © 2015,2017 por Frederico Lamberti Pissarra 3 de março de 2017 Título: Linguagem Assembly para i386 e x86-64 Autor: Frederico Lamberti Pissarra Ano de publicação: 2017 Este material é protegido pela licença GFDL 1.3: Linguagem Assembly para i386 e x86-64 Copyright (C) 2014-2016 by Frederico Lamberti Pissarra A permissão de cópia, distribuição e/ou modificação deste documento é garantida sob os termos da licença “GNU Free Documentation License, Version 1.3” ou versão mais recente publicada pela Free Software Foundation. O texto integral da licença pode ser lido no link https://www.gnu.org/licenses/fdl.html. É importante, no entanto, advertir ao leitor que este material encontra-se em processo de produção. Trata-se, então, de um rascunho e não reflete o material finalizado. Índice Introdução 1 Dicas de como ler este livro 1 Sobre o tamanho de blocos 2 Ferramentas do ofício 2 Por que diabos precisamos de um compilador C? 2 Sobre o modo preferido de operação do processador 3 Capítulo 1 - Conceitos Básicos 5 O que são números? 5 Escrevendo números de bases diferentes 5 Explicando melhor a notação posicional 5 Quanto menor a base, maior é o número 6 Mais sobre os significados dos “lados” de um número... 7 Aritmética inteira 7 Carry e “borrow” são complementares 9 Adição e subtração com sinais 9 Modos de operação dos processadores 386 ou superiores 10 Organização da memória do seu programa 10 Organização da memória do seu PC 12 Registradores 13 Registradores nas arquiteturas i386 e x86-64 14 Segmentos de memória 15 Problemas com ponteiros “longínquos” no modo real 16 Registradores que selecionam “segmentos” da memória 16 Seletores “default” 18 Instruções 19 “Tipos” em assembly 19 A pilha, funções e os registradores RSP e RBP 20 Registrador RFLAGS 22 Retorno de valores booleanos em C e Assembly 23 Um pouco mais sobre endereços (usando ponteiros) 24 Um pouco mais sobre seletores default e a derreferenciação de ponteiros 25 A área “doce” da pilha 25 Diferenças entre endereços de 32 bits e 64 bits 25 Capítulo 2 - A linguagem Assembly 27 Informando o modo de operação para o compilador 28 Especificando os “segmentos” da memória usados pelo programa 28 Declarando símbolos 29 Diferentes sabores de assembly 29 Sabores de compiladores 30 A linguagem assembly 30 Tipos de instruções 32 Usando RFLAGS para tomar decisões 32 Comparando desigualdades 33 Loops 34 Capítulo 3 – Assembly e outras linguagens 37 Portabilidade é um problema! 37 Convenções de chamada 37 A convenção “cdecl” e o modo i386 38 Registradores que devem ser preservados entre chamadas de funções 39 Armazenamento temporário “local” na pilha 39 Instruções especiais para lidar com prólogos e epílogos 40 Parâmetros em ponto flutuante 41 A convenção de chamada para o modo x86-64 41 Registradores que devem ser preservados entre chamadas (x86-64) 41 E quanto a convenção PASCAL? 42 O assembly inline 42 Descritores, no assembly inline 44 Combinando descritores 45 Usando o “sabor” Intel na linguagem assembly inline 45 E os registradores estendidos, de R8 até R15? 46 Labels “relativos” dentro do código assembly 46 Capítulo 4 – Instruções 47 As diferentes instruções de multiplicação inteiras 47 E quanto às divisões? 47 NEG e NOT 48 INC e DEC não são tão boas assim 48 Realizando cálculos simples com uma única instrução 48 A limitação de MOV e a cópia de strings 49 Existem duas instruções MOVSD?! 49 Preenchendo um array 50 Movimentações e preenchimentos de strings nas arquiteturas modernas 50 Comparações e buscas de strings 50 O prefixo REP pode ser usado para alinhamento de código 52 Outras instruções de movimentação interessantes 52 Porque a instrução LOOP nunca é usada pelo GCC? 52 XLAT é outra instrução “inútil” 53 Trocando valores em uma tacada só... 53 Outras instruções que “travam” o barramento 53 “Comparação” lógica 54 Invertendo bits individuais e zerando registradores 55 Deslocando e rodando 55 Contando bits zerados e setados de um lado ou de outro 57 Capítulo 5 – Ponto flutuante 59 Números, em ponto flutuante 59 Maneiras de lidar com ponto flutuante 60 Usando o co-processador matemático 60 A pilha do x87 61 Exemplo de calculos com o x87 62 O x87 tem algumas constantes “built-in” 62 Equações “built in” 63 Fazendo comparações no modo x87 64 Usando SIMD no modo escalar 64 Comparação de valores escalares usando SSE 66 Convertendo valores 66 O tipo “long double” 66 Capítulo 6 – SIMD Vetorizado 69 Realizando 4 operações com floats em uma tacada só 69 Um aviso sobre a carga de registradores XMM via MOVAPS 70 Embaralhando um registrador 70 Exemplo de uso de SSE: O produto vetorial 71 Desempacotamento, outro tipo de embaralhamento 73 SSE 2, 3 e 4 73 SSE não é a única extensão SIMD 74 Capítulo 7 – Performance 75 Contando ciclos 75 Latência e throughput 75 Micro-fusão e macro-fusão 77 Dividir ou multiplicar? 78 Evite multiplicações e divisões inteiras! 80 Evite usar instruções para o coprocessador x87! 81 Nem sempre o compilador faz o que você quer 81 Os efeitos do “branch prediction” 83 Dando dicas ao processador sobre saltos condicionais 84 Quantos mais dados você manipular, pior a performance 85 Códigos grandes também geram problemas com o cache! 86 Se puder, evite usar os registradores estendidos, no modo x86-64 86 Os acessos à memória também podem ser reordenados 87 Medindo a performance 87 Calculando o “ganho” ou “perda” de performance 90 Apêndice A – Se você é uma Maria “Gasolina”... 91 Usando o GAS 91 Comentários são iniciados com # 91 Instruções contém o tamanho dos operandos 91 Prefixos REP, REPZ e REPNZ são “instruções” separadas 92 A ordem dos operandos é contrária a usada no sabor Intel 92 Uma vantagem do sabor AT&T 92 Valores imediatos 92 Obtendo o endereço atual 92 Os modos de endereçamento são especificados de forma diferente 93 Saltos e chamadas de funções 93 Algumas diretivas do compilador 94 Códigos em 16, 32 e 64 bits 95 Labels têm “tipos” e “tamanhos” 95 Definindo dados 95 Alinhamento de dados e código 95 Exportando e importando símbolos 96 Algumas diretivas especiais 96 Macros 96 Introdução Em todo material técnico relacionado com a linguagem Assembly costumo escrever a mesma ladainha, massageando o meu ego. O leitor que me perdoe, mas o que vai ai embaixo não será exceção... Sou o autor do, hoje nem tão famoso assim, “Curso de Assembly da RBT”. Escrevi 26 capítulos sobre o assunto, em 1994, e fiz certo sucesso na grande rede. Infelizmente o curso está meio caduco, obsoleto, e merece uns retoques e adições. Os capítulos que seguem são a minha tentativa de explicar melhor e mais profundamente o que é assembly, bem como fornecer algum material de referência e um pouco de como funciona aquele chip da placa-mãe que você lambuza de pasta térmica e coloca um grande cooler em cima. Outro aviso importante é sobre o foco deste texto. O leitor não encontrará informações importantes aqui que permitam o desenvolvimento, por exemplo, de um sistema operacional. Não falarei quase nada sobre registradores de controle, inicialização do processador, multi-threading (e multi- processing), interfaces com a BIOS, etc. O que mostro aqui é a linguagem assembly e seu uso no userspace. Dicas de como ler este livro É tradicional, em textos desse tipo, que a introdução contenha material essencial para o bom entendimento das partes mais “hardcore”. Coisas como base numéricas e um pouquinho de conceitos de eletrônica digital... Vamos a elas logo no próximo capítulo, mas antes, precisamos ter certeza que falamos a mesma língua e usamos as mesmas ferramentas do ofício... Tentei seguir alguns padrões para facilitar sua leitura: • Negritos são usados para ressaltar alguma informação que pode passar desapercebida pelo leitor, bem como a introdução de algum termo técnico que, mais tarde, aparecerá numa formatação mais “normal”; • Aspas também são usadas como recurso de chamada de atenção, ou para tornar algo mais (no caso de uma piada) ou menos (no caso de eu querer dizer exatamente o que a palavra significa) ambíguo. Usar aspas é uma mania minha... assim como usar reticências...; • Palavras em itálico são usadas em termos estrangeiros, na maioria das vezes; • Códigos fonte, palavras reservadas de uma linguagem e referências a um código fonte no texto explicativo usam fonte mono espaçada; • Às vezes textos aparecerão dentro de uma caixa cinzenta: Como este aqui, por exemplo! Ela é outro desses recursos usados para chamar sua atenção para uma explicação importante. Existem outras táticas estilísticas para ajudar na leitura: Todos os códigos fonte, nomes de símbolos e referências a códigos fonte são escritas em caracteres minúsculos. Faço isso porque tanto a linguagem C quanto o NASM são case sensitive. Isso significa que nomes de variáveis como abc, abC, aBc ou Abc não são a mesma variável. O outro motivo é pessoal. Sou um sujeito sensível e sempre que leio códigos escritos em letras maiúsculas acho que estão gritando comigo e fico muito nervoso (especialmente se vejo uma burrada no código!)... 1 Nomes de registradores poderão aparecer tanto em letras minúsculas quanto maiúsculas. O último caso serve como destaque estilístico, para não tornar as sentenças confusas ao leitor. Sobre o tamanho de blocos Você observará que ao informar tamanhos de blocos usarei a abreviação “KiB” ao invés da mais tradicional “kB”. Existe uma diferença... O Sistema Internacional de unidades, mantido pela ISO, especifica que o prefixo “k” significa multiplicar a unidade por 10³. Acontece que 1 “quilobyte” não é exatamente igual a 1000 bytes. Existe um prefixo específico para esse tipo de grandeza, cuja base numérica é baseada em 2: O “kibibyte”.