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

07 - Kernel Panic e Oops

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

A execução de código em Ring 0 (Espaço do Kernel) é regida por uma premissa de absoluta confiança. Diferente do Espaço do Usuário, onde falhas de segmentação (Segmentation Faults) são isoladas e os recursos do processo ofensor são sumariamente recuperados pelo sistema operacional, uma violação de acesso à memória ou uma instrução ilegal gerada pelo próprio kernel apresenta um dilema arquitetural inescapável: quem supervisiona o supervisor?

No Linux, o tratamento de exceções internas e anomalias críticas de execução divide-se estruturalmente em dois mecanismos de mitigação e contenção: o Kernel Oops (para falhas consideradas recuperáveis, mas que corrompem o estado da execução) e o Kernel Panic (o colapso determinístico do sistema, executado deliberadamente para preservar a integridade física dos dados em disco).


1. O Kernel Oops: Anomalias Contornáveis e a Mecânica do "Taint"

Um Oops é a manifestação de um desvio de controle ou de uma falha de hardware não fatal originada no código do kernel (frequentemente em device drivers de terceiros carregados dinamicamente). O nome deriva da natureza de "tropeço" do erro: o kernel cometeu uma infração, mas a estrutura global do sistema operacional ainda pode ser teoricamente salva.

A Mecânica da Exceção

Quando o processador detecta uma operação inválida em Ring 0 (por exemplo, a dereferenciação de um ponteiro nulo), ele dispara uma interrupção síncrona que invoca o tratador genérico de falhas do kernel (como do_page_fault na arquitetura x86). O kernel reconhece que a falha não se originou no Ring 3 e, portanto, inicia a rotina die().

O comportamento do sistema após um Oops depende exclusivamente do contexto de execução no milissegundo da falha:

  • Contexto de Processo (System Call): Se a falha ocorreu enquanto o kernel processava uma syscall em nome de um processo de usuário (ex: lendo um arquivo), o kernel simplesmente encerra de forma abrupta a thread do núcleo que estava em execução, envia um sinal SIGKILL incondicional ao processo de usuário que originou a chamada e libera a memória daquele processo. O sistema continua rodando.
  • Contexto de Interrupção (Interrupt Context): Se o Oops ocorrer dentro de um Interrupt Service Routine (ISR), como o tratador de pacotes de uma placa de rede, o kernel não pode simplesmente "matar" a interrupção. Falhas neste contexto elevam instantaneamente o Oops para um Kernel Panic.

A Bandeira de Taint (Contaminação)

Mesmo que o sistema sobreviva a um Oops, o kernel não confia mais em seu próprio estado interno. Estruturas de dados globais, como spinlocks ou listas encadeadas, podem ter ficado bloqueadas ou inconsistentes. Quando um Oops ocorre, o kernel marca a si mesmo com a flag de Taint (especificamente TAINT_DIE). Um kernel tainted continuará operando para permitir que o administrador salve dados e reinicie graciosamente, mas o comportamento futuro do sistema é matematicamente imprevisível, e os desenvolvedores do Linux (upstream) rotineiramente recusam a análise de depuração de falhas subsequentes em sistemas contaminados.


2. O Kernel Panic: O Colapso Deterministíco

O Kernel Panic não é um travamento acidental; é uma rotina C explícita e rigorosamente orquestrada (panic() no arquivo fonte kernel/panic.c). É uma decisão arquitetural deliberada: é preferível paralisar totalmente a máquina a arriscar que uma instrução de I/O corrompida sobrescreva o superbloco de um sistema de arquivos.

Gatilhos Comuns

Além de um Oops em contexto de interrupção, a função panic() pode ser invocada por:

  1. Morte do PID 1: Se o processo init (ou systemd) for encerrado por um sinal não tratado, o kernel invoca o pânico, pois um sistema Unix sem o processo ancestral é irremediavelmente inoperante.
  2. Falha de Montagem da Raiz (VFS): Se a transição do initramfs para o disco físico falhar (ex: ausência de drivers LVM ou UUID incorreto), o kernel não tem de onde carregar bibliotecas ou executar processos, resultando em um erro do tipo not syncing: VFS: Unable to mount root fs.
  3. MCE (Machine Check Exception): O processador detecta uma falha letal de hardware (ex: erros na memória RAM ECC que não podem ser corrigidos, ou superaquecimento extremo do silício) e notifica o kernel para paralisar imediatamente a fim de evitar corrupção elétrica ou lógica.

A Rotina de Paralisação (kernel/panic.c)

Quando a função panic() é acionada, o fluxo de execução entra em um caminho sem volta que tenta maximizar a coleta de logs de depuração enquanto neutraliza qualquer capacidade de interferência assíncrona:

  1. Desativação de Interrupções Locais: Invoca local_irq_disable(). A CPU atual torna-se surda a eventos externos (teclado, rede, timers).
  2. Imobilização SMP (Symmetric Multiprocessing): Dispara smp_send_stop(). O núcleo em pânico emite uma Inter-Processor Interrupt (IPI) de emergência para todos os outros núcleos do processador, forçando-os a parar a execução instantaneamente. Isso impede que outras threads continuem alterando a memória.
  3. Destruição de Locks (bust_spinlocks): Remove artificialmente todas as travas do subsistema de console para garantir que o despejo de memória (stack trace) seja impresso no monitor, mesmo que outro processo estivesse segurando o console.
  4. Desfecho: Dependendo da cmdline do kernel (parâmetro panic=N), o kernel piscará os LEDs do teclado (historicamente) e congelará em um loop infinito (cpu_relax()), ou fará um hard reset automático na máquina após N segundos através de chamadas à ACPI ou ao controlador de watchdog da placa-mãe.

3. Dissecando a Pilha de Execução (Call Trace)

Tanto o Oops quanto o Panic despejam no log (via dmesg e no console) uma "fotografia" do estado exato da CPU no ciclo de relógio em que a falha ocorreu. A compreensão desse dump é vital para a depuração a nível de doutorado.

  • Ponteiros de Instrução e Registradores: O log exibe o conteúdo da estrutura pt_regs. Ele mostra o RIP (Instruction Pointer em x86_64), que contém o endereço exato de memória da instrução de linguagem de máquina que falhou.
  • Call Trace (Desenrolamento de Pilha): O kernel percorre retroativamente a pilha de execução (stack unwinding), mostrando a árvore de funções que levou à falha. O leitor verá endereços como do_page_fault+0x1b/0x30. Isso significa que a falha ocorreu na instrução localizada a 0x1b (27 bytes) do início da função do_page_fault, que tem um tamanho total de 0x30 bytes.
  • A conversão dos ponteiros hexadecimais brutos para nomes de funções legíveis só é possível porque o kernel consulta sua própria tabela interna de símbolos (geralmente exposta em /proc/kallsyms), a menos que os símbolos tenham sido removidos do binário.

4. O Subsistema de Watchdogs: Identificando Lockups

Nem todo pânico advém de operações inválidas de memória; alguns decorrem de paralisações de threads que quebram o contrato de preempção do kernel, levando a impasses (deadlocks). O Linux possui o subsistema Lockup Detector para diagnosticar esses casos.

  • Soft Lockup: Ocorre quando uma thread em Ring 0 entra em um loop denso e monopoliza uma CPU sem realizar chamadas de escalonamento (schedule()) por mais de 20 segundos. O timer de alta resolução do kernel detecta que a thread não liberou o núcleo e dispara um aviso severo (ou um pânico, se kernel.softlockup_panic = 1 estiver definido no sysctl).
  • Hard Lockup: Uma anomalia muito mais severa. Ocorre quando uma thread em Ring 0 entra em um loop segurando um spinlock e com as interrupções locais desabilitadas. Como os timers padrão dependem de interrupções, o kernel não conseguiria detectar essa paralisação por vias normais. O Linux resolve isso utilizando NMIs (Non-Maskable Interrupts). O kernel programa a Performance Monitoring Unit (PMU) do hardware para gerar um NMI a cada poucos segundos. Os NMIs não podem ser bloqueados pelo software. Se o tratador do NMI perceber que o contador de interrupções normais da CPU não avançou desde o último ciclo, ele deduz com certeza matemática que a CPU está sofrendo um Hard Lockup e invoca o panic().

5. Arquitetura Kexec e Kdump: A Autópsia Forense (Post-Mortem)

Em ecossistemas corporativos de missão crítica, apenas ler o Call Trace em um monitor muitas vezes é insuficiente para diagnosticar bugs complexos de concorrência que levaram a um pânico. O administrador precisa do estado completo da memória RAM no momento da falha. Como não se pode usar o kernel avariado para gravar gigabytes na controladora de disco com segurança, a solução adotada pelo Linux é o Kdump (amparado pela syscall kexec).

A arquitetura do Kdump baseia-se no conceito de hospedar um segundo núcleo de sistema operacional totalmente dormente na memória da máquina.

  1. Reserva Inicial: Durante o boot normal, o administrador passa o parâmetro crashkernel=256M. O kernel principal isola essa região da memória RAM, impedindo seu uso pelo sistema operacional ou pelo usuário.
  2. O Salto Kexec: Quando ocorre o Kernel Panic, a função panic() altera seu fluxo normal. Em vez de travar o hardware, ela contorna a BIOS/UEFI e executa um salto direto da CPU para o endereço de memória isolado.
  3. O "Crash Kernel": Um micro-kernel secundário do Linux é inicializado ali. Ele reconhece que está acordando de um evento de pânico. Como ele possui seu próprio espaço de memória intocado e seguro, ele é capaz de carregar drivers de disco adequadamente.
  4. Extração do vmcore: A memória RAM que pertencia ao kernel principal (agora congelada no tempo e repleta de evidências do travamento) é exposta a este segundo kernel como o arquivo /proc/vmcore. O Crash Kernel lê esse arquivo virtual e o grava no disco rígido persistente (ou envia pela rede via SSH/NFS) gerando um Crash Dump.
  5. Após a gravação, o sistema sofre um reinício de hardware. O arquivo despejado no disco pode então ser laboriosamente depurado por engenheiros usando ferramentas estáticas de análise forense, como a crash utility, que mapeia as estruturas de dados C do kernel original sobre os bytes brutos da memória falha, permitindo reconstruir as listas encadeadas, inodes e sockets de rede exatamente como existiam no milissegundo do colapso.