Porušení ochrany paměti

FreeBSD 7.0 kernel panic: v tomto případě byla chyba záměrně uměle vyvolána zasláním signálu SIGSEGV procesu init, takže výsledek je stejný jako kdyby skutečně došlo k chybě paměťové ochrany (segmentation fault). Protože proces init potřebují všechny ostatní procesy, nastává tzv. kernel panic a je vytvořen výpis paměti (core dump) a následně je vyvolán automatický restart (reboot).

Porušení ochrany paměti (též chyba paměťové ochrany, anglicky segmentation fault) je obecně snaha přistoupit k paměti počítače, kterou procesor nemůže fyzicky adresovat. Nastává v případě, kdy hardware upozorní operační systém o nepovoleném přístupu k paměti. Jádro operačního systému na tuto událost obvykle zareaguje nápravným krokem. Například odešle procesu, který chybu vyvolal, signál k jeho ukončení a výpisu paměti (core dump). Za určitých podmínek je možné, aby procesy požádaly o svolení samy se obnovit zavedením vlastní obsluhy signálu.[1]

Chyba sběrnice (bus error)

Na POSIX-kompatibilních systémech chyby sběrnice obvykle vyústí v zaslání signálu SIGBUS procesu, který chybu způsobil. SIGBUS může být také způsoben nějakou obecnou vadou zařízení, kterou počítač detekoval. Chyba sběrnice zřídkakdy znamená, že hardware počítače je fyzicky poškozen – je většinou způsobena programovou chybou ve zdrojovém kódu programu.

Existují dvě hlavní příčiny chyby sběrnice:

Neexistující adresa
Software nařídí procesoru (CPU) přečíst nebo zapsat specifickou fyzickou paměťovou adresu. V souladu s tím, CPU odešle tuto fyzickou adresu na adresní sběrnici a požádá všechny ostatní hardwarové komponenty připojené k CPU o navrácení hodnoty na této adrese. CPU dále čeká zda komponenty zareagují. Jestliže žádný hardware neodpoví, CPU oznámí výjimku, že požadovaná fyzická adresa nebyla rozpoznána celým počítačovým systémem. Toto se ovšem týká pouze fyzických paměťových adres. Snaha o přístup k nedefinované virtuální paměťové adrese je obvykle považována spíše za chybu paměťové ochrany než za chybu sběrnice, ačkoliv když je jednotka správy paměti (MMU) oddělená, procesor není schopen určit rozdíl.
Nezarovnaný přístup
Většina CPU dokáže adresovat paměť po bytech (každá unikátní paměťová adresa odkazuje na jeden byte v paměti). Většina CPU však obvykle nedokáže přistupovat k větším jednotkám (16 bitů, 32 bitů, 64 bitů atd.) aniž by tyto jednotky byly zarovnány pomocí specifické hranice. Například, jestliže multi-bytové přístupy musí být zarovnány na 16 bitů, adresy (zadány v bytech) 0, 2, 4 atd. budou považovány za zarovnané a tudíž přístupné, zatímco adresy o 1, 3, 5 atd. budou považovány za nezarovnané. Stejně tak, jestliže multi-bytové adresy musí být zarovnané pomocí 32 bitů, adresy 0, 4, 8, 12 atd. budou považovány za zarovnané a tudíž přístupné a všechny ostatní adresy mezi nimi budou považovány za nezarovnané. Snaha o přístup k větší jednotce než je byte u nezarovnané adresy může způsobit chybu sběrnice.

CPU všeobecně přistupuje k datům v celé šířce jejich sběrnice po celou dobu. Pro adresování jednotlivých bytů přistupují k paměti v celé šířce jejich sběrnice, potom získanou hodnotu zamaskují a posunou. Systémy tolerují tento neefektivní algoritmus, neboť jde o jednu ze základních funkcí většiny softwarů, obzvláště při zpracování řetězců. Větší jednotky na rozdíl od bytů mohou zahrnovat dvě zarovnané adresy, a tudíž budou vyžadovat více než jedno načtení dat z datové sběrnice. Pro CPU je možné tuto funkci podporovat, ale málokdy je to vyžadováno přímo na úrovni strojového kódu. Návrháři CPU se tedy za normálních okolností implementaci této podpory vyhýbají a místo toho implementují oznámení chyby sběrnice kvůli nezarovnanému přístupu do paměti.

Příklady

Příklad chyby sběrnice

Toto je příklad nezarovnaného přístupu do paměti, napsaný v programovacím jazyce C se AT&T syntaxí pro assembler.

#include <stdlib.h>

int main(int argc, char **argv) {
    int *iptr;
    char *cptr;

#if defined(__GNUC__)
# if defined(__i386__)
    /* Enable Alignment Checking on x86 */
    __asm__("pushf\norl $0x40000,(%esp)\npopf");
# elif defined(__x86_64__)
     /* Enable Alignment Checking on x86_64 */
    __asm__("pushf\norl $0x40000,(%rsp)\npopf");
# endif
#endif

    /* malloc() always provides aligned memory */
    cptr = (char *) malloc(sizeof(int) + 1);
    
    /* Increment the pointer by one, making it misaligned */
    iptr = (int *) ++cptr;

    /* Dereference it as an int pointer, causing an unaligned access */
    *iptr = 42;

    return 0;
}

Kompilace a spuštění příkladu na POSIX-kompatibilním systému na platformě x86:

$ gcc -ansi sigbus.c -o sigbus
$ ./sigbus
Bus error
$ gdb ./sigbus
(gdb) r
Program received signal SIGBUS, Bus error.
0x080483ba in main ()
(gdb) x/i $pc
0x80483ba <main+54>:    mov    DWORD PTR [eax],0x2a
(gdb) p/x $eax
$1 = 0x804a009
(gdb) p/t $eax & (sizeof(int) - 1)
$2 = 1

GDB debugger ukazuje, že hodnota 0x2a je uložena na adresu uloženou v EAX registru, použitím X86 assembleru.

Vypsáním nejméně významných bitů adrset ukazuje, že není zarovnána na velikost slova ("dword" v x86 terminologii).

Reference

V tomto článku byl použit překlad textu z článku Segmentation fault na anglické Wikipedii.

  1. Expert C programming: deep C secrets By Peter Van der Linden, page 188

Média použitá na této stránce

FreeBSD kernel panic.png
A kernel panic in FreeBSD 7.0 running under VirtualBox. The panic was caused by sending SIGSEGV to init, causing it to crash as if it encountered a segmentation fault. Since init is needed for all other processes to start, the kernel panics and a core dump is generated.