Segmenteringsfejl - Segmentation fault

I computing er en segmenteringsfejl (ofte forkortet til segfault ) eller adgangsovertrædelse en fejl eller fejltilstand, der opstår af hardware med hukommelsesbeskyttelse , der underretter et operativsystem (OS), som softwaren har forsøgt at få adgang til et begrænset hukommelsesområde (en krænkelse af hukommelsesadgang). På standard x86 -computere er dette en form for generel beskyttelsesfejl . OS -kernen vil som svar som regel udføre nogle korrigerende handlinger, der generelt overfører fejlen til den krænkende proces ved at sende processen et signal . Processer kan i nogle tilfælde installere en brugerdefineret signal handler, så de kan komme sig på egen hånd, men bruges ellers OS standard signal handler, generelt forårsager unormal afslutning af processen (et program nedbrud ), og nogle gange en kerne dump .

Segmenteringsfejl er en almindelig fejlklasse i programmer, der er skrevet på sprog som C, der giver lav hukommelsesadgang og få eller ingen sikkerhedskontroller. De opstår primært på grund af fejl i brugen af pointer til adressering af virtuel hukommelse , især ulovlig adgang. En anden type hukommelsesadgangsfejl er en busfejl , som også har forskellige årsager, men i dag er meget sjældnere; disse opstår primært på grund af forkert fysisk hukommelsesadressering eller på grund af fejljusteret hukommelsesadgang - det er hukommelsesreferencer, som hardwaren ikke kan adressere, frem for referencer, som en proces ikke adressere.

Mange programmeringssprog kan anvende mekanismer designet til at undgå segmenteringsfejl og forbedre hukommelsessikkerheden. F.eks. Anvender programmeringssproget Rust en "ejerskab" -baseret model for at sikre hukommelsessikkerhed. Andre sprog, såsom Lisp og Java , anvender affaldssamling, som undgår visse klasser af hukommelsesfejl, der kan føre til segmenteringsfejl.

Oversigt

Eksempel på menneskeligt genereret signal

En segmenteringsfejl opstår, når et program forsøger at få adgang til en hukommelsesplacering, som den ikke har adgang til, eller forsøger at få adgang til en hukommelsesplacering på en måde, der ikke er tilladt (f.eks. Forsøg på at skrive til en skrivebeskyttet placering eller at overskrive en del af operativsystemet ).

Udtrykket "segmentering" har forskellige anvendelser inden for computing; i forbindelse med "segmenteringsfejl", et udtryk, der har været brugt siden 1950'erne, refererer det til adresserummet for et program. Med hukommelsesbeskyttelse er det kun programmets eget adresserum, der kan læses, og heraf kan kun stakken og læse/skrive-delen af datasegmentet i et program skrives, mens skrivebeskyttet data og kodesegmentet ikke kan skrives. Forsøg på at læse uden for programmets adresserum eller skrive til et skrivebeskyttet segment i adresserummet resulterer således i en segmenteringsfejl, deraf navnet.

På systemer med hardware hukommelse segmentering til at give virtuel hukommelse , en segmentering fejl opstår, når hardwaren opdager et forsøg på at henvise til en ikke-eksisterende segment, eller at henvise til et sted uden for rammerne af et segment, eller at henvise til en placering i en måde, der ikke er tilladt af de tilladelser, der er givet for dette segment. På systemer, der kun bruger personsøgning , fører en ugyldig sidefejl generelt til en segmenteringsfejl, og segmenteringsfejl og sidefejl er begge fejl, der rejses af det virtuelle hukommelsesstyringssystem . Segmenteringsfejl kan også forekomme uafhængigt af sidefejl: ulovlig adgang til en gyldig side er en segmenteringsfejl, men ikke en ugyldig sidefejl, og segmenteringsfejl kan forekomme i midten af ​​en side (derfor ingen sidefejl), for eksempel i en bufferoverløb, der forbliver inden for en side, men ulovligt overskriver hukommelse.

På hardwareniveau stiger fejlen i første omgang af hukommelsesstyringsenheden (MMU) ved ulovlig adgang (hvis den omtalte hukommelse findes), som en del af dens hukommelsesbeskyttelsesfunktion, eller en ugyldig sidefejl (hvis den refererede hukommelse ikke findes ). Hvis problemet ikke er en ugyldig logisk adresse, men i stedet en ugyldig fysisk adresse, opstår der i stedet en busfejl , selvom disse ikke altid skelnes.

På operativsystemniveau fanges denne fejl, og et signal sendes videre til den overtrædende proces, der aktiverer procesens behandler for det signal. Forskellige operativsystemer har forskellige signalnavne for at indikere, at der er opstået en segmenteringsfejl. På Unix-lignende operativsystemer sendes et signal kaldet SIGSEGV (forkortet fra segmenteringskrænkelse ) til den krænkende proces. På Microsoft Windows , den fornærme proces modtager en STATUS_ACCESS_VIOLATION undtagelse .

Årsager

Betingelserne for segmenteringskrænkelser, og hvordan de manifesterer sig, er specifikke for hardware og operativsystem: forskellig hardware rejser forskellige fejl for givne forhold, og forskellige operativsystemer konverterer disse til forskellige signaler, der sendes videre til processer. Den nærmeste årsag er en overtrædelse af hukommelsesadgang, mens den underliggende årsag generelt er en slags softwarebug . Bestemmelse af grundårsagen - fejlretning af fejlen - kan i nogle tilfælde være enkel, hvor programmet konsekvent vil forårsage en segmenteringsfejl (f.eks. Dereferencing af en nullmarkør ), mens fejlen i andre tilfælde kan være svær at reproducere og afhænge af hukommelsestildeling på hvert løb (f.eks. dereferencing af en dinglende markør ).

Følgende er nogle typiske årsager til en segmenteringsfejl:

  • Forsøger at få adgang til en ikke -eksisterende hukommelsesadresse (uden for procesens adresserum)
  • Forsøg på at få adgang til hukommelse har programmet ikke rettigheder til (f.eks. Kernestrukturer i proceskontekst)
  • Forsøger at skrive skrivebeskyttet hukommelse (f.eks. Kodesegment)

Disse er igen ofte forårsaget af programmeringsfejl, der resulterer i ugyldig hukommelsesadgang:

  • Dereferencing af en nullmarkør , som normalt peger på en adresse, der ikke er en del af procesens adresserum
  • Dereferencing eller tildeling til en uinitialiseret markør ( vild markør , der peger på en tilfældig hukommelsesadresse)
  • Dereferencing eller tildeling til en frigivet markør ( dinglende markør , der peger på hukommelse, der er frigjort/deallokeret/slettet)
  • Et bufferoverløb
  • Et stakoverløb
  • Forsøger at udføre et program, der ikke kompileres korrekt. (Nogle kompilatorer udsender en eksekverbar fil på trods af tilstedeværelsen af ​​kompileringstidsfejl.)

I C -kode opstår segmenteringsfejl oftest på grund af fejl i markørbrug, især i C dynamisk hukommelsestildeling . Dereferencing af en nullmarkør vil altid resultere i en segmenteringsfejl, men vilde pointer og dinglende pointer peger på hukommelse, der måske eksisterer, og som måske ikke kan læses eller skrives, og dermed kan resultere i forbigående fejl. For eksempel:

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

Dereferencing af nogen af ​​disse variabler kan forårsage en segmenteringsfejl: Hvis du henviser til nullmarkøren, vil det generelt forårsage en segfault, mens læsning fra den vilde markør i stedet kan resultere i tilfældige data, men ingen segfault, og læsning fra den dinglende markør kan resultere i gyldige data for en mens, og derefter tilfældige data, da de er overskrevet.

Håndtering

Standardhandlingen for en segmenteringsfejl eller busfejl er unormal afslutning af den proces, der udløste den. En kernefil kan genereres for at hjælpe med fejlfinding, og andre platformafhængige handlinger kan også udføres. For eksempel kan Linux -systemer, der bruger grsecurity -patchen, logge SIGSEGV -signaler for at overvåge mulige indbrudsforsøg ved hjælp af bufferoverløb .

På nogle systemer, som Linux og Windows, er det muligt for selve programmet at håndtere en segmenteringsfejl. Afhængigt af arkitekturen og operativsystemet kan det kørende program ikke kun håndtere hændelsen, men kan også udtrække nogle oplysninger om dens tilstand som at få en stacksporing , processorregisterværdier , linjen i kildekoden, da den blev udløst, hukommelsesadresse, der var ugyldig adgang, og om handlingen var en læsning eller en skrivning.

Selvom en segmenteringsfejl generelt betyder, at programmet har en fejl, der skal rettes, er det også muligt med vilje at forårsage en sådan fejl med henblik på test, fejlfinding og også at efterligne platforme, hvor der er behov for direkte adgang til hukommelse. I sidstnævnte tilfælde skal systemet være i stand til at tillade programmet at køre, selv efter fejlen opstår. I dette tilfælde, når systemet tillader det, er det muligt at håndtere hændelsen og øge processorprogramtælleren til at "springe" over den fejlende instruktion for at fortsætte udførelsen.

Eksempler

Segmenteringsfejl på et EMV -tastatur

Skriver til skrivebeskyttet hukommelse

Skrivning til skrivebeskyttet hukommelse medfører en segmenteringsfejl. På niveau med kodefejl sker dette, når programmet skriver til en del af sit eget kodesegment eller den skrivebeskyttede del af datasegmentet , da disse indlæses af operativsystemet i skrivebeskyttet hukommelse.

Her er et eksempel på ANSI C -kode, der generelt vil forårsage en segmenteringsfejl på platforme med hukommelsesbeskyttelse. Det forsøger at ændre en streng , som er en udefineret adfærd i henhold til ANSI C -standarden. De fleste kompilatorer vil ikke fange dette på kompileringstidspunktet, og i stedet kompilere dette til eksekverbar kode, der vil gå ned:

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

Når programmet indeholder denne kode er kompileret, er strengen "hej verden" placeret i rodata del af programmet eksekverbare fil : read-only sektion af data segment . Når det er indlæst, placerer operativsystemet det med andre strenge og konstante data i et skrivebeskyttet segment af hukommelse. Når den udføres, sættes en variabel, s , til at pege på strengens placering, og der forsøges at skrive et H -tegn gennem variablen ind i hukommelsen, hvilket forårsager en segmenteringsfejl. Ved at kompilere et sådant program med en compiler, der ikke kontrollerer tildelingen af ​​skrivebeskyttede placeringer på kompileringstidspunktet og kører det på et Unix-lignende operativsystem, produceres følgende runtime-fejl :

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

Backtrace af kernefilen fra GDB :

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

Denne kode kan rettes ved hjælp af en matrix i stedet for en tegnpeger, da denne allokerer hukommelse på stakken og initialiserer den til værdien af ​​strengen bogstaveligt:

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

Selvom strenglitteraler ikke bør ændres (dette har en udefineret adfærd i C -standarden), er de i C af static char []typen, så der er ingen implicit konvertering i den originale kode (som peger a char *på det array), mens de i C ++ er af static const char []typen, og der er således en implicit konvertering, så kompilatorer vil generelt fange denne særlige fejl.

Null pointer dereference

På C og C-lignende sprog bruges nullpegere til at betyde "markør til intet objekt" og som en fejlindikator, og dereferencing af en nullmarkør (en læsning eller skrivning gennem en nullmarkør) er en meget almindelig programfejl. C -standarden siger ikke, at nullmarkøren er den samme som markøren til hukommelsesadresse  0, selvom det kan være tilfældet i praksis. De fleste operativsystemer kortlægger nullmarkørens adresse, så adgang til den forårsager en segmenteringsfejl. Denne adfærd garanteres ikke af C -standarden. Dereferencing af en nullmarkør er udefineret adfærd i C, og en konform implementering må antage, at enhver markør, der er afledt, ikke er null.

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

Denne prøvekode opretter en nullmarkør og forsøger derefter at få adgang til dens værdi (læs værdien). Dette forårsager en segmenteringsfejl ved runtime på mange operativsystemer.

At referere en nullmarkør og derefter tildele den (skrive en værdi til et ikke-eksisterende mål) forårsager normalt også en segmenteringsfejl:

int *ptr = NULL;
*ptr = 1;

Den følgende kode indeholder en null pointer dereference, men når den kompileres vil det ofte ikke resultere i en segmenteringsfejl, da værdien er ubrugt og dermed vil dereference ofte blive optimeret væk ved eliminering af død kode :

int *ptr = NULL;
*ptr;

Bufferoverløb

Den følgende kode får adgang til tegnmatrixen sud over dens øvre grænse. Afhængigt af kompilatoren og processoren kan dette resultere i en segmenteringsfejl.

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

Stack overløb

Et andet eksempel er rekursion uden en base case:

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

hvilket får stakken til at flyde over, hvilket resulterer i en segmenteringsfejl. Uendelig rekursion kan ikke nødvendigvis resultere i en stakoverløb afhængigt af sproget, optimeringer udført af kompilatoren og den nøjagtige struktur af en kode. I dette tilfælde er adfærden for utilgængelig kode (retursætningen) udefineret, så kompilatoren kan eliminere den og bruge en haleopkaldsoptimering, der kan resultere i ingen stakbrug. Andre optimeringer kan omfatte oversættelse af rekursionen til iteration, hvilket givet strukturen i eksempelfunktionen ville resultere i, at programmet kører for evigt, mens det sandsynligvis ikke overfylder dets stak.

Se også

Referencer

eksterne links