Programação em Baixo Nível
C, Assembly e execução de programas na arquitetura Intel 64
Descrição do livro
Conheça a linguagem Assembly e a arquitetura do Intel 64, torne-se proficiente em C e entenda como os programas são compilados e executados até o nível das instruções de máquina, permitindo-lhe escrever um código robusto e de alto desempenho.
Programação em baixo nível explica a arquitetura do Intel 64 como resultado da evolução da arquitetura de von Neumann. O livro o ensina a usar a versão mais recente da linguagem C (C11) e a linguagem Assembly desde o básico. Todo o caminho, do código-fonte à execução do programa, incluindo a geração de arquivos-objeto ELF, além das ligações estática e dinâmica, será discutido. Há exemplos de código e exercícios, junto com as melhores práticas de programação. Os recursos de otimização e os limites dos compiladores modernos serão analisados, permitindo-lhe promover um equilíbrio entre a legibilidade do programa e o desempenho.
O uso de diversas técnicas para ganho de desempenho, por exemplo, instruções SSE e pre-fetching, será demonstrado. Assuntos relevantes em ciência da computação, como os modelos de computação e as gramáticas formais, também serão tratados, explicando-se sua importância prática.
Programação em baixo nível ensina os programadores a:
escrever livremente em linguagem Assembly;
compreender o modelo de programação do Intel 64;
escrever um código robusto e fácil de manter em C11;
acompanhar o processo de compilação e decifrar as listagens em Assembly;
depurar erros em código Assembly compilado;
usar modelos de computação apropriados para reduzir drasticamente a complexidade dos programas;
escrever códigos críticos quanto ao desempenho;
compreender o impacto de um modelo de memória fraco em aplicações com várias threads.
Sumário
Sumário
- Sobre o autor
- Sobre o revisor técnico
- Agradecimentos
- Introdução
- Parte I • Linguagem Assembly e arquitetura de computadores
- Capítulo 1 • Básico sobre arquitetura de computadores
- 1.1 Arquitetura do núcleo
- 1.1.1 Modelo de computação
- 1.1.2 Arquitetura de von Neumann
- 1.2 Evolução
- 1.2.1 Desvantagens da arquitetura de von Neumann
- 1.2.2 Arquitetura Intel 64
- 1.2.3 Extensões da arquitetura
- 1.3 Registradores
- 1.3.1 Registradores de propósito geral
- 1.3.2 Outros registradores
- 1.3.3 Registradores de sistema
- 1.4 Anéis de proteção
- 1.5 Pilha de hardware
- 1.6 Resumo
- 1.1 Arquitetura do núcleo
- Capítulo 2 • Linguagem Assembly
- 2.1 Configurando o ambiente
- 2.1.1 Trabalhando com os códigos de exemplo
- 2.2 Escrevendo “Hello, world”
- 2.2.1 Entrada e saída básicas
- 2.2.2 Estrutura do programa
- 2.2.3 Instruções básicas
- 2.3 Exemplo: exibindo o conteúdo de registradores
- 2.3.1 Rótulos locais
- 2.3.2 Endereçamento relativo
- 2.3.3 Ordem de execução
- 2.4 Chamadas de função
- 2.5 Trabalhando com dados
- 2.5.1 Endianness
- 2.5.2 Strings
- 2.5.3 Pré-processamento de constantes
- 2.5.4 Ponteiros e diferentes tipos de endereçamento
- 2.6 Exemplo: calculando o tamanho de uma string
- 2.7 Exercício: biblioteca de entrada/saída
- 2.7.1 Autoavaliação
- 2.8 Resumo
- 2.1 Configurando o ambiente
- Capítulo 3 • Legado
- 3.1 Modo real
- 3.2 Modo protegido
- 3.3 Segmentação mínima em modo longo
- 3.4 Acessando partes de registradores
- 3.4.1 Um comportamento inesperado
- 3.4.2 CISC e RISC
- 3.4.3 Explicação
- 3.5 Resumo
- Capítulo 4 • Memória virtual
- 4.1 Caching
- 4.2 Motivação
- 4.3 Espaços de endereçamento
- 4.4 Recursos
- 4.5 Exemplo: acessando um endereço proibido
- 4.6 Eficiência
- 4.7 Implementação
- 4.7.1 Estrutura dos endereços virtuais
- 4.7.2 Tradução de endereços em detalhes
- 4.7.3 Tamanhos de página
- 4.8 Mapeamento de memória
- 4.9 Exemplo: mapeando um arquivo na memória
- 4.9.1 Nomes mnemônicos para constantes
- 4.9.2 Exemplo completo
- 4.10 Resumo
- Capítulo 5 • Pipeline de compilação
- 5.1 Pré-processador
- 5.1.1 Substituições simples
- 5.1.2 Substituições com argumentos
- 5.1.3 Substituição condicional simples
- 5.1.4 Condicionais sobre a definição
- 5.1.5 Condicionais sobre a identidade de textos
- 5.1.6 Condicionais sobre o tipo de argumento
- 5.1.7 Ordem de avaliação: define, xdefine, assign
- 5.1.8 Repetição
- 5.1.9 Exemplo: calculando números primos
- 5.1.10 Rótulos em macros
- 5.1.11 Conclusão
- 5.2 Tradução
- 5.3 Ligação
- 5.3.1 ELF (Executable and Linkable Format)
- 5.3.2 Arquivos-objeto relocáveis
- 5.3.3 Arquivos-objeto executáveis
- 5.3.4 Bibliotecas dinâmicas
- 5.3.5 Loader
- 5.4 Exercício: dicionário
- 5.5 Resumo
- 5.1 Pré-processador
- Capítulo 6 • Interrupções e chamadas de sistema
- 6.1 Entrada e saída
- 6.1.1 O registrador TR e o Task State Segment
- 6.2 Interrupções
- 6.3 Chamadas de sistema
- 6.3.1 Registradores específicos de modelo
- 6.3.2 syscall e sysret
- 6.4 Resumo
- 6.1 Entrada e saída
- Capítulo 7 • Modelos de computação
- 7.1 Máquinas de estado finitas
- 7.1.1 Definição
- 7.1.2 Exemplo: paridade de bits
- 7.1.3 Implementação em linguagem Assembly
- 7.1.4 Importância prática
- 7.1.5 Expressões regulares
- 7.2 Máquina de Forth
- 7.2.1 Arquitetura
- 7.2.2 Trace de um exemplo de programa em Forth
- 7.2.3 Dicionário
- 7.2.4 Como as palavras são implementadas
- 7.2.5 Compilador
- 7.3 Exercício: compilador e interpretador de Forth
- 7.3.1 Dicionário estático e interpretador
- 7.3.2 Compilação
- 7.3.3 Forth com bootstrap
- 7.4 Resumo
- 7.1 Máquinas de estado finitas
- Parte II • A linguagem de programação C
- Capítulo 8 • Básico da linguagem C
- 8.1 Introdução
- 8.2 Estrutura do programa
- 8.2.1 Tipos de dados
- 8.3 Controle de fluxo
- 8.3.1 if
- 8.3.2 while
- 8.3.3 for
- 8.3.4 goto
- 8.3.5 switch
- 8.3.6 Exemplo: divisor
- 8.3.7 Exemplo: é um número de Fibonacci?
- 8.4 Instruções e expressões
- 8.4.1 Tipos de instrução
- 8.4.2 Construindo expressões
- 8.5 Funções
- 8.6 Pré-processador
- 8.7 Resumo
- Capítulo 9 • Sistema de tipos
- 9.1 Sistema básico de tipos em C
- 9.1.1 Tipos numéricos
- 9.1.2 Casting de tipo
- 9.1.3 Tipo booleano
- 9.1.4 Conversões implícitas
- 9.1.5 Ponteiros
- 9.1.6 Arrays
- 9.1.7 Arrays como argumentos de função
- 9.1.8 Inicializadores designados em arrays
- 9.1.9 Aliases de tipo
- 9.1.10 Revendo a função main
- 9.1.11 Operador sizeof
- 9.1.12 Tipos const
- 9.1.13 Strings
- 9.1.14 Tipos função
- 9.1.15 Escrevendo um bom código
- 9.1.16 Exercício: produto escalar
- 9.1.17 Exercício: verificador de número primo
- 9.2 Tipos com tags
- 9.2.1 Estruturas
- 9.2.2 Uniões
- 9.2.3 Estruturas e uniões anônimas
- 9.2.4 Enumerações
- 9.3 Tipos de dados nas linguagens de programação
- 9.3.1 Espécies de tipagem
- 9.3.2 Polimorfismo
- 9.4 Polimorfismo in C
- 9.4.1 Polimorfismo paramétrico
- 9.4.2 Inclusão
- 9.4.3 Sobrecarga
- 9.4.4 Coerções
- 9.5 Resumo
- 9.1 Sistema básico de tipos em C
- Capítulo 10 • Estrutura do código
- 10.1 Declarações e definições
- 10.1.1 Declarações de função
- 10.1.2 Declarações de estrutura
- 10.2 Acessando código a partir de outros arquivos
- 10.2.1 Funções de outros arquivos
- 10.2.2 Dados em outros arquivos
- 10.2.3 Arquivos de cabeçalho
- 10.3 A biblioteca-padrão
- 10.4 Pré-processador
- 10.4.1 Guarda de inclusão
- 10.4.2 Por que o pré-processador é maléfico?
- 10.5 Exemplo: soma de um array dinâmico
- 10.5.1 Uma espiada na alocação dinâmica de memória
- 10.5.2 Exemplo
- 10.6 Exercício: lista ligada
- 10.6.1 Exercício
- 10.7 Palavra reservada static
- 10.8 Ligação
- 10.9 Resumo
- 10.1 Declarações e definições
- Capítulo 11 • Memória
- 11.1 Revendo os ponteiros
- 11.1.1 Por que precisamos de ponteiros?
- 11.1.2 Aritmética de ponteiros
- 11.1.3 Tipo void*
- 11.1.4 NULL
- 11.1.5 Uma palavrinha sobre ptrdiff_t
- 11.1.6 Ponteiros de função
- 11.2 Modelo de memória
- 11.2.1 Alocação de memória
- 11.3 Arrays e ponteiros
- 11.3.1 Detalhes da sintaxe
- 11.4 Strings literais
- 11.4.1 String Interning
- 11.5 Modelos de dados
- 11.6 Streams de dados
- 11.7 Exercício: funções de alta ordem e listas
- 11.7.1 Funções de alta ordem comuns
- 11.7.2 Exercício
- 11.8 Resumo
- 11.1 Revendo os ponteiros
- Capítulo 12 • Sintaxe, semântica e pragmática
- 12.1 O que é uma linguagem de programação?
- 12.2 Sintaxe e gramáticas formais
- 12.2.1 Exemplo: números naturais
- 12.2.2 Exemplo: aritmética simples
- 12.2.3 Descida recursiva
- 12.2.4 Exemplo: aritmética com prioridades
- 12.2.5 Exemplo: linguagem imperativa simples
- 12.2.6 Hierarquia de Chomsky
- 12.2.7 Árvore de sintaxe abstrata
- 12.2.8 Análise lexical
- 12.2.9 Resumo sobre o parsing
- 12.3 Semântica
- 12.3.1 Comportamento indefinido
- 12.3.2 Comportamento não especificado
- 12.3.3 Comportamento definido pela implementação
- 12.3.4 Pontos de sequência
- 12.4 Pragmática
- 12.4.1 Alinhamento
- 12.4.2 Preenchimento de estruturas de dados
- 12.5 Alinhamento no C11
- 12.6 Resumo
- Capítulo 13 • Boas práticas de programação
- 13.1 Fazendo escolhas
- 13.2 Elementos do código
- 13.2.1 Nomenclatura em geral
- 13.2.2 Estrutura de arquivos
- 13.2.3 Tipos
- 13.2.4. Variáveis
- 13.2.5 Sobre variáveis globais
- 13.2.6 Funções
- 13.3 Arquivos e documentação
- 13.4 Encapsulamento
- 13.5 Imutabilidade
- 13.6 Asserções
- 13.7 Tratamento de erros
- 13.8 Sobre a alocação de memória
- 13.9 Sobre a flexibilidade
- 13.10 Exercício: rotação de imagem
- 13.10.1 Formato de arquivo BMP
- 13.10.2 Arquitetura
- 13.11 Exercício: alocador de memória personalizado
- 13.12 Resumo
- Parte III • Entre o C e o Assembly
- Capítulo 14 • Detalhes sobre a tradução
- 14.1 Sequência de chamadas de função
- 14.1.1 Registradores XMM
- 14.1.2 Convenção de chamadas
- 14.1.3 Exemplo: uma função simples e sua pilha
- 14.1.4 Zona vermelha
- 14.1.5 Número variável de argumentos
- 14.1.6 vprintf e seus companheiros
- 14.2 volatile
- 14.2.1 Alocação de memória em modo preguiçoso
- 14.2.2 Código gerado
- 14.3 Jumps não locais – setjmp
- 14.3.1 volatile e setjmp
- 14.4 inline
- 14.5 restrict
- 14.6 Aliasing rigoroso
- 14.7 Problemas de segurança
- 14.7.1 Stack buffer overrun (transbordamento de buffer na pilha)
- 14.7.2 return-to-libc
- 14.7.3 Vulnerabilidades de formatação de saída
- 14.8 Mecanismos de proteção
- 14.8.1 Cookie de segurança
- Endereçamento)
- 14.8.3 DEP
- 14.8.1 Cookie de segurança
- 14.9 Resumo
- 14.1 Sequência de chamadas de função
- Capítulo 15 • Objetos compartilhados e modelos de código
- 15.1 Carga dinâmica
- 15.2 Relocações e PIC
- 15.3 Exemplo: biblioteca dinâmica em C
- 15.4 GOT e PLT
- 15.4.1 Acessando variáveis externas
- 15.4.2 Chamando funções externas
- 15.4.3 Exemplo de PLT
- 15.5 Pré-carga
- 15.6 Resumo sobre o endereçamento de símbolos
- 15.7 Exemplos
- 15.7.1 Chamando uma função
- 15.7.2 Sobre vários linkers dinâmicos
- 15.7.3 Acessando uma variável externa
- 15.7.4 Exemplo completo em Assembly
- 15.7.5 Misturando C e Assembly
- 15.8 Quais objetos são ligados?
- 15.9 Otimizações
- 15.10 Modelos de código
- 15.10.1 Modelo de código pequeno (sem PIC)
- 15.10.2 Modelo de código grande (sem PIC)
- 15.10.3 Modelo de código médio (sem PIC)
- 15.10.4 Modelo de código pequeno com PIC
- 15.10.5 Modelo de código grande com PIC
- 15.10.6 Modelo de código médio com PIC
- 15.11 Resumo
- Capítulo 16 • Desempenho
- 16.1 Otimizações
- 16.1.1 Mito sobre linguagens rápidas
- 16.1.2 Conselhos gerais
- 16.1.3 Omitindo o ponteiro do stack frame
- 16.1.4 Recursão de cauda
- 16.1.5 Eliminação de subexpressões comuns
- 16.1.6 Propagação de constantes
- 16.1.7 Otimização de valores de retorno (nomeados)
- 16.1.8 Influência da previsão de desvio
- 16.1.9 Influência das unidades de execução
- 16.1.10 Agrupando leituras e escritas no código
- 16.2 Caching
- 16.2.1 Como usar o cache de modo eficaz?
- 16.2.2 Prefetching
- 16.2.3 Exemplo: busca binária com prefetching
- 16.2.4 Ignorando o cache
- 16.2.5 Exemplo: inicialização de matriz
- 16.3 Classe de instruções SIMD
- 16.4 Extensões SSE e AVX
- 16.4.1 Exercício: filtro sépia
- 16.5 Resumo
- 16.1 Otimizações
- Capítulo 17 • Multithreading
- 17.1 Processos e threads
- 17.2 O que deixa o multithreading complicado?
- 17.3 Ordem de execução
- 17.4 Modelos de memória fortes e fracos
- 17.5 Exemplo de reordenação
- 17.6 O que é e o que não é volátil
- 17.7 Barreiras de memória
- 17.8 Introdução à pthreads
- 17.8.1 Quando usar multithreading
- 17.8.2 Criando threads
- 17.8.3 Gerenciando threads
- 17.8.4 Exemplo: fatoração distribuída
- 17.8.5 Mutexes
- 17.8.6 Deadlocks
- 17.8.7 Livelocks
- 17.8.8 Variáveis de condição
- 17.8.9 Spinlocks
- 17.9 Semáforos
- 17.10 Quão robusto é o Intel 64?
- 17.11 O que é uma programação sem lock?
- 17.12 Modelo de memória do C11
- 17.12.1 Visão geral
- 17.12.2 Atômicos
- 17.12.3 Ordenações de memória no C11
- 17.12.4 Operações
- 17.13 Resumo
- Apêndice A • Usando o gdb
- Apêndice B • Usando o Make
- Apêndice C • Chamadas de sistema
- Apêndice D • Informações sobre testes de desempenho
- Bibliografia
Sobre o autor
Igor Zhirkov ministra seu muito bem-sucedido curso “Linguagens de programação de sistemas” na ITMO University (Universidade de Tecnologia da Informação, Mecânica e Ótica) de São Petersburgo, seis vezes vencedora do ACM-ICPC Intercollegiate World Programming Championship (Campeonato Mundial de Programação entre Universidades ACM-ICPC). Estudou… Ver perfil completo ▶
Livros relacionados
Opinião dos leitores
Rodrigo H W
Excelente livro, contém conteúdos específicos para programação de baixo nível e Linguagem C. O autor aborda os temas com exemplos e dicas importantes para formação do profissional.
Gunter C K
A encadernação, assim como, a impressão são primorosas. Como entusiasta e professor da área de informática/exatas achei o tema relevante e a apresentação muito didática. Lancem mais livros com esse tema.
João P S
Excelente livro!
Duilio B N
O conteúdo é denso e o autor soube ser didático, ainda começando mas já achei que valeu muito a pena a aquisição.






