Home Comunidade Eventos YouTube Networks Desafios // produtos Academy News CTI | CVE Contato

03 - Imagem do Kernel (vmlinuz, bzImage)

video explicativo
Video explicativoEspaco reservado para o video desta anotação — adicione video: https://youtu.be/... no frontmatter da nota.

A imagem do kernel Linux que reside no diretório /boot não é um simples arquivo executável binário, mas sim uma estrutura composta, auto-extraível e altamente arquitetural. Compreender a anatomia do arquivo vmlinuz (e especificamente o formato bzImage na arquitetura x86/x86_64) é fundamental para entender como o sistema operacional realiza a transição do firmware da placa-mãe (ou bootloader) para o ambiente de execução em Ring 0.

1. Ontologia e Nomenclatura: vmlinux vs vmlinuz

O processo de compilação do núcleo do Linux gera, em seu estágio final de ligação (linking), um arquivo massivo chamado vmlinux.

  • O vmlinux é um binário estático no formato ELF (Executable and Linkable Format). Ele contém o código de máquina puro do kernel, juntamente com extensas tabelas de símbolos e metadados de depuração. No entanto, este arquivo não é inicializável por bootloaders padrão em arquiteturas x86, e seu tamanho não compactado (frequentemente ultrapassando centenas de megabytes) o torna ineficiente para a carga em memória física inicial.
  • O vmlinuz (onde a letra "z" denota compressão) é o artefato final implantado no sistema (geralmente em /boot/vmlinuz-$(uname -r)). Ele é derivado do vmlinux, mas passou por um processo severo de "strip" (remoção de símbolos de depuração via objcopy) seguido de compressão (historicamente gzip, mas modernamente lz4, xz ou zstd), e foi empacotado dentro de um wrapper executável que contém a lógica necessária para sua própria descompressão na memória RAM.

Fluxo_Kernel.png


2. A Evolução Arquitetural: zImage vs bzImage

Historicamente, o Linux utilizava o formato zImage. Devido às restrições brutais da arquitetura x86 em Modo Real (Real Mode), o bootloader só conseguia endereçar o primeiro megabyte de RAM (os famosos 640 KB de memória convencional). O formato zImage era desenhado para ser carregado inteiramente nessa região de memória baixa (0x10000). Conforme o kernel cresceu com a adição de novos subsistemas, ele rapidamente extrapolou esse limite físico, tornando o zImage obsoleto.

Para solucionar este bloqueio arquitetural, foi introduzido o formato bzImage (Big zImage).
O prefixo "Big" não se refere ao algoritmo de compressão bzip2 (um erro de interpretação comum), mas sim à sua capacidade de ser carregado na High Memory (memória acima da marca de 1 MB, tipicamente a partir de 0x100000). O bootloader moderno lê a estrutura do bzImage, carrega uma minúscula porção de código de configuração na memória baixa e despeja o corpo massivo e comprimido do kernel na memória alta, evadindo as restrições legadas do IBM PC. Hoje, virtualmente todo kernel x86 é compilado como bzImage.


3. A Anatomia Interna do bzImage

Um arquivo bzImage é uma estrutura de boneca-russa (Matryoshka). Se você analisá-lo com um editor hexadecimal, verá que ele não é apenas um arquivo gzip. Ele é composto por múltiplas seções fundidas durante a fase de compilação (especificamente pelo utilitário build do Makefile da arquitetura x86):

  1. Boot Sector (Legado): Os primeiros 512 bytes continham código capaz de inicializar o kernel diretamente de um disquete, sem GRUB. Hoje, isso é obsoleto e substituído por uma rotina que apenas imprime "Use a boot loader" e paralisa a CPU, mas a estrutura de 512 bytes é mantida para alinhamento arquitetural do cabeçalho.
  2. Setup Header: Localizado no offset 0x01F1, é o contrato de interface entre o bootloader e o kernel. Ele contém metadados críticos: a versão do protocolo de boot, se o kernel suporta realocação (KASLR), o endereço recomendado de carregamento, e ponteiros para onde o bootloader deve injetar a linha de comando do kernel (cmdline) e o initramfs na memória RAM.
  3. Setup Code (Real-Mode Code): Um pequeno bloco de código (historicamente 16 bits, agora de transição) cuja única responsabilidade é inicializar o hardware essencial a nível de firmware, consultar os mapas de memória da BIOS/UEFI (via interrupção E820), configurar a Global Descriptor Table (GDT) temporária e realizar o salto transicional (via instrução jmp) para o Modo Protegido de 32 bits (ou Modo Longo de 64 bits).
  4. O Descompressor (Decompressor Stub): Código puramente em assembly e C (head.S e misc.c). Este código é PIC (Position Independent Code), o que significa que pode executar a partir de qualquer endereço de memória. Sua função é inicializar a própria stack, alocar uma área de memória livre adjacente, identificar o algoritmo (zstd, lz4, gzip) e descompactar o payload.
  5. O Payload Comprimido (vmlinux.bin.X): Freqüentemente chamado internamente de piggy.o no código-fonte, é o kernel ELF stripped e comprimido.

4. O Fluxo de Execução: Da Injeção à Descompressão

O milagre da inicialização do kernel ocorre em frações de segundo na memória RAM. O fluxo determinístico, sob a ótica do Ring 0, é o seguinte:

  1. Hand-off: O GRUB lê o bzImage do disco, carrega o código de setup em 0x90000, o payload comprimido em 0x100000, e salta para o ponto de entrada do setup.
  2. Preparo do Terreno: O código de setup lê o mapa de memória, alterna a CPU para o Protected Mode (ignorando as interrupções legadas da BIOS) e salta para a rotina do descompressor (startup_32 ou startup_64).
  3. KASLR e Extração: Se o KASLR (Kernel Address Space Layout Randomization) estiver habilitado por segurança, o descompressor escolhe um endereço físico aleatório e seguro na memória. Ele então extrai o corpo do kernel (piggy.gz) do próprio arquivo bzImage em execução para esse novo endereço de memória, sobrescrevendo a área alvo.
  4. O Salto Final: Com o kernel real, não compactado, residindo na memória física contígua, o descompressor realiza uma limpeza nos registradores e executa um salto incondicional (jmp) para o símbolo absoluto startup_64 do kernel recém-extraído. A partir deste exato ciclo de clock, o arquivo bzImage cumpriu sua função e o código puramente nativo do núcleo Linux (que culminará na chamada da função C start_kernel()) assume o controle definitivo da máquina.

5. O Paradigma Moderno: EFI Boot Stub (EFISTUB)

Com o advento da arquitetura UEFI, os desenvolvedores do kernel introduziram uma alteração profunda no cabeçalho do bzImage. Modernamente, o kernel Linux é compilado com a opção CONFIG_EFI_STUB.

Isso altera estruturalmente os primeiros bytes da imagem para incluir uma assinatura "MZ" válida e um cabeçalho PE/COFF (o mesmo formato de executáveis do Windows).
Isso cria uma quimera arquitetural genial: para um bootloader legado como o LILO ou Syslinux, o arquivo ainda se parece exatamente com um bzImage tradicional. Contudo, para a placa-mãe moderna, o kernel se disfarça perfeitamente como um aplicativo EFI nativo.

Isso permite que o firmware UEFI invoque o kernel diretamente, passando a ele as estruturas de configuração EFI, suprimindo inteiramente a necessidade de um bootloader intermediário como o GRUB2 em topologias de servidor onde o tempo de boot e a simplicidade da cadeia de confiança (Secure Boot) são imperativos. O stub EFI interno do kernel realiza as alocações via chamadas de serviço UEFI (AllocatePages), extrai a si mesmo e inicia o Ring 0, fundindo as fronteiras entre o bootloader e o arquivo de imagem do sistema operacional.