Porucha Segmentace - Segmentation fault

Ve výpočtu , což je chyba segmentace (často se zkrátil k segfault ) nebo narušení přístupu je porucha nebo poruchový stav, zvýšen hardware s ochranou paměti , oznámí je operační systém (OS) software se pokusil o přístup k omezené oblasti paměti (a Narušení přístupu paměti). Na standardních počítačích x86 je to forma obecné chyby ochrany . OS jádro bude v reakci, obvykle provádět některé nápravná opatření, obecně prochází závadu na provinivšího procesu zasláním jednotlivých kroků A signál . Procesy mohou v některých případech instalovat vlastní obslužný program signálu, který jim umožňuje zotavit se samostatně, ale jinak se používá výchozí obslužný program signálu OS, což obecně způsobuje abnormální ukončení procesu ( selhání programu ) a někdy i jádrový výpis .

Chyby segmentace jsou běžnou třídou chyb v programech napsaných v jazycích, jako je C, které poskytují nízkoúrovňový přístup do paměti a málo až žádné bezpečnostní kontroly. Vznikají především kvůli chybám při používání ukazatelů pro adresování virtuální paměti , zejména nelegálnímu přístupu. Dalším typem chyby přístupu k paměti je chyba sběrnice , která má také různé příčiny, ale dnes je mnohem vzácnější; Tyto se vyskytují především v důsledku nesprávného fyzické adresování paměti, nebo z důvodu nesprávně zarovnaný přístupu do paměti - to jsou paměti odkazy, že hardware nemůže řešit, spíše než odkazy, které proces není dovoleno , aby adresu.

Mnoho programovacích jazyků může využívat mechanismy navržené tak, aby se zabránilo chybám segmentace a zlepšilo bezpečnost paměti. Například programovací jazyk Rust využívá k zajištění bezpečnosti paměti model založený na „vlastnictví“. Jiné jazyky, jako je Lisp a Java , využívají sběr odpadků, který zabraňuje určitým třídám chyb v paměti, které by mohly vést k chybám segmentace.

Přehled

Příklad signálu generovaného člověkem

K chybě segmentace dochází, když se program pokusí získat přístup k umístění paměti , ke kterému nemá přístup, nebo se pokusí o přístup k umístění paměti způsobem, který není povolen (například pokus o zápis do umístění pouze pro čtení , nebo k přepsání části operačního systému ).

Termín „segmentace“ má různá použití ve výpočetní technice; v souvislosti s „chybou segmentace“, termín používaný od padesátých let minulého století, označuje adresní prostor programu. S ochranou paměti je čitelný pouze vlastní adresní prostor programu, a z toho lze zapisovat pouze zásobník a část pro čtení/zápis datového segmentu programu, zatímco data jen pro čtení a segment kódu nelze zapisovat. Pokus o čtení mimo adresní prostor programu nebo zápis do segmentu adresního prostoru jen pro čtení má za následek chybu segmentace, odtud název.

V systémech, které k poskytování virtuální paměti používají segmentaci hardwarové paměti , dojde k chybě segmentace, když hardware detekuje pokus odkazovat na neexistující segment nebo odkazovat na umístění mimo hranice segmentu nebo odkazovat na umístění v způsob, který není povolen oprávněními udělenými pro tento segment. V systémech využívajících pouze stránkování vede neplatná chyba stránky obecně k chybě segmentace a chyby segmentace a chyby stránky jsou obě chyby, které vyvolal systém správy virtuální paměti . K chybám segmentace může docházet také nezávisle na chybách stránky: nelegální přístup na platnou stránku je chybou segmentace, ale ne neplatnou chybou stránky, a chyby segmentace se mohou objevit uprostřed stránky (tedy žádná chyba stránky), například v přetečení vyrovnávací paměti, které zůstává na stránce, ale nelegálně přepisuje paměť.

Na hardwarové úrovni je chyba původně vyvolána jednotkou pro správu paměti (MMU) při nelegálním přístupu (pokud odkazovaná paměť existuje), jako součást funkce ochrany paměti nebo při chybě neplatné stránky (pokud odkazovaná paměť neexistuje ). Pokud problémem není neplatná logická adresa, ale místo toho neplatná fyzická adresa, je místo toho vyvolána chyba sběrnice , ačkoli tyto nejsou vždy rozlišovány.

Na úrovni operačního systému je tato chyba zachycena a signál je předán problematickému procesu, čímž se aktivuje obslužná rutina procesu pro tento signál. Různé operační systémy mají různé názvy signálů, které indikují, že došlo k chybě segmentace. V operačních systémech podobných Unixu je signál s názvem SIGSEGV (zkráceně z narušení segmentace ) odeslán do problematického procesu. V systému Microsoft Windows proces porušující pravidla obdrží výjimku STATUS_ACCESS_VIOLATION .

Příčiny

Podmínky, za kterých dochází k porušení segmentace, a způsob, jakým se projevují, jsou specifické pro hardware a operační systém: různý hardware vyvolává různé chyby za daných podmínek a různé operační systémy je převádějí na různé signály, které jsou předávány procesům. Blízkou příčinou je narušení přístupu do paměti, zatímco základní příčinou je obecně nějaká softwarová chyba . Určení hlavní příčiny - ladění chyby - může být v některých případech jednoduché, kde program důsledně způsobí chybu segmentace (např. Zrušení odkazu na nulový ukazatel ), zatímco v jiných případech může být chyba obtížně reprodukovatelná a závisí na přidělení paměti při každém běhu (např. dereferencování visícího ukazatele ).

Níže jsou uvedeny některé typické příčiny poruchy segmentace:

  • Pokus o přístup k neexistující adrese paměti (mimo adresní prostor procesu)
  • Pokus o přístup k paměti, program nemá práva (například struktury jádra v kontextu procesu)
  • Pokus o zápis paměti jen pro čtení (například segment kódu)

Ty jsou zase často způsobeny chybami programování, které vedou k neplatnému přístupu do paměti:

V kódu C, porušení ochrany paměti nejčastěji dochází z důvodu chyby v použití ukazatele, zejména v C přidělení dynamické paměti . Dereferencování nulového ukazatele bude mít vždy za následek chybu segmentace, ale divoké ukazatele a visící ukazatele ukazují na paměť, která může nebo nemusí existovat a může nebo nemusí být čitelná nebo zapisovatelná, a proto může mít za následek přechodné chyby. Například:

char *p1 = NULL;           // Null pointer
char *p2;                  // Wild pointer: not initialized at all.
char *p3  = malloc(10 * sizeof(char));  // Initialized pointer to allocated memory
                                        // (assuming malloc did not fail)
free(p3);                  // p3 is now a dangling pointer, as memory has been freed

Dereferencování kterékoli z těchto proměnných by mohlo způsobit chybu segmentace: dereferencování nulového ukazatele obecně způsobí segfault, zatímco čtení z divokého ukazatele může místo toho vést k náhodným datům, ale žádné segfault, a čtení z visícího ukazatele může mít za následek platná data pro while, a pak náhodná data, jak jsou přepsána.

Zacházení

Výchozí akcí pro chybu segmentace nebo chybu sběrnice je abnormální ukončení procesu, který ji spustil. Pro usnadnění ladění lze vygenerovat základní soubor a lze také provádět další akce závislé na platformě. Například systémy Linux využívající opravu grsecurity mohou protokolovat signály SIGSEGV za účelem monitorování možných pokusů o narušení pomocí přetečení vyrovnávací paměti .

Na některých systémech, jako je Linux a Windows, je možné, že program sám zvládne chybu segmentace. V závislosti na architektuře a operačním systému může spuštěný program událost nejen zpracovat, ale může extrahovat některé informace o jejím stavu, jako je získání trasování zásobníku , hodnoty registru procesoru , řádek zdrojového kódu při spuštění, adresa paměti, která byla neplatně přistupovat a zda akce byla čtení nebo zápis.

Ačkoli chyba segmentace obecně znamená, že program má chybu, kterou je třeba opravit, je také možné záměrně způsobit takové selhání pro účely testování, ladění a také pro emulaci platforem, kde je potřeba přímý přístup do paměti. V druhém případě musí být systém schopen umožnit spuštění programu, i když dojde k chybě. V tomto případě, když to systém dovolí, je možné zpracovat událost a zvýšit čítač programového procesoru, aby „přeskočil“ chybující instrukci a pokračoval v provádění.

Příklady

Chyba segmentace na klávesnici EMV

Zápis do paměti jen pro čtení

Zápis do paměti jen pro čtení vyvolává chybu segmentace. Na úrovni chyb kódu k tomu dochází, když program zapisuje do části svého vlastního segmentu kódu nebo části datového segmentu pouze pro čtení , protože tyto jsou načteny operačním systémem do paměti jen pro čtení.

Zde je příklad kódu ANSI C, který obecně způsobí chybu segmentace na platformách s ochranou paměti. Pokouší se upravit doslovný řetězec , což je nedefinované chování podle standardu ANSI C. Většina kompilátorů to v době kompilace nezachytí a místo toho to zkomplikuje do spustitelného kódu, který se zhroutí:

int main(void)
{
    char *s = "hello world";
    *s = 'H';
}

Když je program obsahující tento kód zkompilován, řetězec „ahoj svět“ je umístěn do části rodata spustitelného souboru programu : části jen pro čtení datového segmentu . Po načtení jej operační systém umístí s dalšími řetězci a konstantními daty do segmentu paměti jen pro čtení. Při spuštění je proměnná s nastavena tak, aby ukazovala na umístění řetězce, a je proveden pokus o zápis znaku H přes proměnnou do paměti, což způsobí chybu segmentace. Kompilace takového programu pomocí kompilátoru, který v době kompilace nekontroluje přiřazení umístění pouze pro čtení, a jeho spuštění v operačním systému podobném Unixu, vytvoří následující chybu za běhu :

$ gcc segfault.c -g -o segfault
$ ./segfault
Segmentation fault

Backtrace jádrového souboru z GDB :

Program received signal SIGSEGV, Segmentation fault.
0x1c0005c2 in main () at segfault.c:6
6               *s = 'H';

Tento kód lze opravit pomocí pole místo ukazatele znaku, protože to alokuje paměť v zásobníku a inicializuje ji na hodnotu řetězcového literálu:

char s[] = "hello world";
s[0] = 'H';  // equivalently, *s = 'H';

Přestože řetězcové literály by neměly být upravovány (toto má ve standardu C nedefinované chování), v jazyce C jsou static char []typu, takže v původním kódu (který ukazuje char *na toto pole) neexistuje implicitní převod , zatímco v jazyce C ++ jsou o static const char []typu, a tudíž je implicitní převod, takže překladače obecně zachytit tento konkrétní chybu.

Dereference nulového ukazatele

V jazycích podobných C a C se nulovými ukazateli rozumí „ukazatel na žádný objekt“ a jako indikátor chyby a dereferencování nulového ukazatele (čtení nebo zápis přes nulový ukazatel) je velmi častou chybou programu. Standard C neříká, že nulový ukazatel je stejný jako ukazatel na adresu paměti  0, i když v praxi to tak může být. Většina operačních systémů mapuje adresu nulového ukazatele tak, že přístup k ní způsobí chybu segmentace. Toto chování není zaručeno standardem C. Dereferencování nulového ukazatele je nedefinované chování v jazyce C a odpovídající implementace může předpokládat, že jakýkoli ukazatel, který je dereferencovaný, není null.

int *ptr = NULL;
printf("%d", *ptr);

Tento ukázkový kód vytvoří nulový ukazatel a poté se pokusí získat přístup k jeho hodnotě (přečíst hodnotu). V mnoha operačních systémech to způsobí chybu segmentace za běhu.

Dereferencování nulového ukazatele a jeho přiřazení (zápis hodnoty do neexistujícího cíle) také obvykle způsobí chybu segmentace:

int *ptr = NULL;
*ptr = 1;

Následující kód obsahuje dereference nulového ukazatele, ale při kompilaci často nedojde k chybě segmentace, protože hodnota je nevyužita, a proto bude dereference často optimalizována odstraněním mrtvého kódu :

int *ptr = NULL;
*ptr;

Přetečení zásobníku

Následující kód přistupuje k poli znaků sza jeho horní hranicí. V závislosti na kompilátoru a procesoru to může mít za následek chybu segmentace.

char s[] = "hello world";
char c = s[20];

Přetečení zásobníku

Dalším příkladem je rekurze bez základního případu:

int main(void)
{
    return main();
}

což způsobí přetečení zásobníku, což má za následek chybu segmentace. Nekonečná rekurze nemusí nutně vést k přetečení zásobníku v závislosti na jazyce, optimalizacích prováděných překladačem a přesné struktuře kódu. V tomto případě je chování nedosažitelného kódu (příkaz return) nedefinované, takže jej kompilátor může eliminovat a použít optimalizaci ocasního volání, která může mít za následek nevyužití zásobníku. Jiné optimalizace by mohly zahrnovat překlad rekurze do iterace, což by vzhledem ke struktuře příkladové funkce vedlo k tomu, že program poběží navždy, přičemž pravděpodobně nepřeteče jeho zásobník.

Viz také

Reference

externí odkazy