2017-01-01 3 views
8

Hier ist der Beispielcode, den ich geschrieben habe.Warum führt eine Leseoperation auf einer speicherabgebildeten Null-Byte-Datei zu SIGBUS?

#include <stdio.h> 
#include <stdlib.h> 
#include <fcntl.h> 
#include <unistd.h> 
#include <sys/mman.h> 

int main() 
{ 
    int fd; 
    long pagesize; 
    char *data; 

    if ((fd = open("foo.txt", O_RDONLY)) == -1) { 
     perror("open"); 
     return 1; 
    } 

    pagesize = sysconf(_SC_PAGESIZE); 
    printf("pagesize: %ld\n", pagesize); 

    data = mmap(NULL, pagesize, PROT_READ, MAP_SHARED, fd, 0); 
    printf("data: %p\n", data); 
    if (data == (void *) -1) { 
     perror("mmap"); 
     return 1; 
    } 

    printf("%d\n", data[0]); 
    printf("%d\n", data[1]); 
    printf("%d\n", data[2]); 
    printf("%d\n", data[4096]); 
    printf("%d\n", data[4097]); 
    printf("%d\n", data[4098]); 

    return 0; 
} 

Wenn ich diesem Programm ein Nullbyte foo.txt gebe, endet es mit SIGBUS.

$ > foo.txt && gcc foo.c && ./a.out 
pagesize: 4096 
data: 0x7f8d882ab000 
Bus error 

Wenn ich diesem Programm ein Byte foo.txt gebe, dann gibt es kein solches Problem.

$ printf A > foo.txt && gcc foo.c && ./a.out 
pagesize: 4096 
data: 0x7f5f3b679000 
65 
0 
0 
48 
56 
10 

mmap(2) erwähnt das folgende.

Verwendung einer abgebildeten Region kann in diesen Signalen resultieren:

SIGSEGV versuchtes Schreiben in einen Bereich, als schreibgeschützt abgebildet.

SIGBUS Versuchte Zugriff auf einen Teil des Puffers, der nicht mit der Datei übereinstimmt (z. B. über das Ende der Datei hinaus, einschließlich des Falls, bei dem ein anderer Prozess die Datei abgeschnitten hat).

Also wenn ich das richtig verstehen, auch der zweite Testfall (1-Byte-Datei) sollte SIGBUS geführt hat, weil und data[2] versuchen, einen Teil des Puffers für den Zugriff (data), das entspricht nicht die Datei.

Können Sie mir helfen zu verstehen, warum nur eine Null-Byte-Datei dazu führt, dass dieses Programm mit SIGBUS fehlschlägt?

+0

Warum kümmert es dich? Sie rufen undefiniertes Verhalten auf; Alles kann passieren. Es ist nicht einmal garantiert, dass Sie überhaupt ein Signal bekommen. – Olaf

+0

@Olaf Beim Lesen der [man-Seite] (https://linux.die.net/man/2/mmap) und der [POSIX-Dokumentation] (http://pubs.opengroup.org/onlinepubs/009695399/functions/ mmap.html), konnte ich nicht sicher sein, dass ich tatsächlich undefiniertes Verhalten anrufe. Die Manpage erwähnt nicht, dass ein solches Verhalten ein undefiniertes Verhalten ist. Dies gilt auch für die POSIX-Dokumentation. Nach meiner Interpretation der beiden Dokumente sollte ich für beide Tests "SIGBUS" in meiner Frage bekommen. –

+1

POSIX basiert auf dem C-Standard, für den solche Zugriffe eindeutig UB sind. Sagte, dass es nicht klar ist, was Ihr Problem ist. Dein Code ist sowieso kaputt, wenn du einen SIGBUS oder SIGSEGV oder ähnliches bekommst, hast du schon alles vermasselt. Aber das ist nicht bijektiv: Wenn Sie kein Signal bekommen, bedeutet das nicht, dass Ihr Code korrekt ist! – Olaf

Antwort

5

Sie erhalten SIGBUS wenn nach dem Ende des letzten ganze abgebildet Seite zugreifen, weil the POSIX standard states:

Die mmap() Funktion verwendet werden kann, einen Bereich von Speicher zuzuordnen, die größer ist als die aktuelle Größe des Objekts . Speicherzugriff innerhalb des Mappings, aber über das aktuelle Ende der zugrunde liegenden Objekte hinaus kann zu SIGBUS Signalen führen, die an den Prozess gesendet werden.

Bei einer Null-Byte-Datei ist die gesamte Seite, die Sie zugeordnet haben, "jenseits des aktuellen Endes des zugrunde liegenden Objekts". So erhalten Sie SIGBUS.

Sie erhalten keine SIGBUS, wenn Sie über die 4kB-Seite, die Sie zugeordnet haben, hinausgehen, da dies nicht in Ihrem Mapping enthalten ist.Sie erhalten keinen SIGBUS Zugriff auf Ihr Mapping, wenn Ihre Datei größer als null Byte ist, da die gesamte Seite zugeordnet wird.

Aber Sie würden eine SIGBUS wenn Sie zusätzliche Seiten über das Ende der Datei zugeordnet, wie Mapping zwei 4 kB-Seiten für eine 1-Byte-Datei. Wenn Sie auf diese zweite 4kB-Seite zugreifen, erhalten Sie SIGBUS.

+0

Diese Antwort scheint das Verhalten zu erklären, das ich sehr genau beobachte. Ich habe meinen 'mmap()' Aufruf geändert, um '2 * pagesize' Bytes anstelle von' pagesize' abzubilden und tatsächlich auf 'data [4096]' zu '' SIGBUS' mit dieser Änderung zuzugreifen. –

3

Eine 1-Byte-Datei führt nicht zum Absturz, weil mmap Speicher in Vielfachen der Seitengröße abbildet und den Rest null setzt. Von der Manpage:

Eine Datei wird in Vielfachen der Seitengröße zugeordnet. Für eine Datei, die nicht ein Vielfaches der Seitengröße ist, wird der verbleibende Speicher auf Null gesetzt, wenn zugeordnet, und schreibt in diese Region nicht in die Datei geschrieben werden. Die Auswirkung der Änderung der Größe der zugrunde liegenden Datei einer Zuordnung auf den Seiten, die hinzugefügte oder entfernte Regionen der Datei entsprechen, ist nicht angegeben.

+0

Ich bin nicht davon überzeugt, dass diese Argumentation korrekt ist. Wenn ich im zweiten Testfall versuche, "data [4096]", "data [4097]" usw. zu drucken, erhalte ich einige ungültige Werte. Ich bekomme nicht "SIGBUS", was ich hätte, wenn die Begründung in dieser Antwort gut wäre. –

+0

... bis zu 4095, 4kB - 1 – 4pie0

+0

Das hängt von der Betriebssystemkonfiguration und den Standardeinstellungen ab. Ich habe gerade eine 'getconf PAGESIZE' auf meinem Windows 10 Rechner gemacht und sie hat 65536 Bytes zurückgegeben. –

Verwandte Themen