2017-01-01 14 views
8

Вот пример кода, который я написал.Почему операция чтения в файле с нулевым байтом с памятью приводит к 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; 
} 

Если я предоставляю этому файлу нулевой байт foo.txt, он заканчивается SIGBUS.

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

Если я предоставляю одну байту foo.txt для этой программы, то такой проблемы нет.

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

mmap(2) упоминает следующее.

Использование отображенной области может привести к этим сигналам:

SIGSEGV Попытки записи в области отображается только для чтения.

SIGBUS Попытка доступа к части буфера, которая не соответствует файлу (например, за пределами файла, включая случай, когда другой процесс обрезал файл).

Так что, если я понимаю это правильно, даже второй тестовый случай (файл 1-байтового) должны были привести к SIGBUS потому и data[2] пытаются получить доступ к части буфера (data), что не соответствует файл.

Можете ли вы помочь мне понять, почему только файл с нулевым байтом заставляет эту программу терпеть неудачу с SIGBUS?

+0

Зачем вам все это? Вы вызываете неопределенное поведение; все может случиться. Даже не гарантируется, что вы получите какой-либо сигнал. – Olaf

+0

@Olaf При чтении [man page] (https://linux.die.net/man/2/mmap) и [документации POSIX] (http://pubs.opengroup.org/onlinepubs/009695399/functions/ mmap.html), я не мог быть уверен, что я действительно вызываю неопределенное поведение. На странице руководства не упоминается, что такое поведение является неопределенным поведением. Документация POSIX также отсутствует. Согласно моей интерпретации обеих документов, я должен получить «SIGBUS» для обоих тестов в моем вопросе. –

+1

POSIX основан на стандарте C, для которого такие обращения явно UB. Сказал, что непонятно, какова ваша проблема. В любом случае, ваш код сломан, если вы получаете SIGBUS или SIGSEGV или что-то подобное, вы уже запутались. Но это не является биективным: если вы не получаете сигнал, это не значит, что ваш код правильный! – Olaf

ответ

5

Вы получаете SIGBUS при обращении за конец последнего целого отображенной страницы, так как the POSIX standard states:

mmap() функция может быть использована для отображения области памяти, которая больше, чем текущий размер объекта , Доступ к памяти в пределах отображения, но за пределами текущего конца базовых объектов может привести к отправке сигналов в процесс SIGBUS.

С нулевым байтом вся отображаемая вами страница «находится за пределами текущего конца базового объекта». Таким образом, вы получаете SIGBUS.

Вы не получаете SIGBUS, когда вы выходите за страницу 4kB, которую вы набрали, потому что это не входит в ваше сопоставление.Вы не получаете SIGBUS, обращаясь к вашему сопоставлению, когда ваш файл больше нуля, поскольку вся страница отображается.

Но вы получите SIGBUS, если отображаются дополнительные страницы в прошлом конец файла, такие как отображение два 4KB страниц для файла в 1 байт. Если вы заходите на вторую страницу 4kB, вы получите SIGBUS.

+0

Этот ответ, похоже, объясняет поведение, которое я наблюдаю очень точно. Я изменил свой вызов 'mmap()' на карту '2 * pagesize' байт вместо' pagesize' и действительно доступ к 'data [4096]' привел к 'SIGBUS' с этим изменением. –

3

1-байтовый файл не приводит к сбою, потому что mmap будет отображать память в несколько раз больше размера страницы и нуля. На странице руководства:

Файл отображается в несколько раз по размеру страницы. Для файла , не кратного размеру страницы, оставшаяся память обнуляется при отображении , и записи в этот регион не записываются в файл. Эффект изменения размера базового файла сопоставления на страницах, которые соответствуют добавленным или удаленным регионам файла , не указывается.

+0

Я не уверен, что это рассуждение верно. Если я попытаюсь напечатать 'data [4096]', 'data [4097]' и т. Д. Во втором тестовом случае, я получаю некоторые значения для мусора. Я не получаю 'SIGBUS', который должен был бы, если бы аргументы, представленные в этом ответе, были хорошими. –

+0

... до 4095, 4kB - 1 – 4pie0

+0

Это зависит от конфигурации ОС и значений по умолчанию. Я просто сделал «getconf PAGESIZE» на моей машине с Windows 10, и он вернул 65536 байт. –