mmap успешно, но запись не удалась

У меня есть очень простой код для проверки mmap на низком адресе памяти.

  unsigned long *p = mmap ((void*)(4096*16), 4096, PROT_READ|PROT_WRITE|PROT_EXEC,
              MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS|MAP_GROWSDOWN, -1, 0);
  fprintf (stderr, "p=0x%lx\n", (unsigned long)p);`
  *p = 2554;
  printf ("p=0x%lx; *p=%ld\n", (unsigned long)p, *p);

Когда я запускаю код, я получаю следующий результат:

 p=0x10000
 Segmentation fault (core dumped)

в журнале dmesg я вижу следующие отпечатки:

 segfault at 10000 ip 00000000004006cc sp 00007fff5845f4c0 error 6

В целом кажется, что mmap выполнен успешно, но операция записи завершилась неудачно. Я не могу объяснить эти два наблюдения конфликта. Помогите, пожалуйста. Спасибо.


person strongerwill    schedule 21.01.2016    source источник


Ответы (1)


Вы можете обнаружить, что ошибка сегментации больше не возникает, если вы опустите MAP_GROWSDOWN из параметра flags в вызове mmap.

Если вы изучите /proc/$PID/maps файл после вызова mmap, вы можете увидеть странность (с MAP_GROWSDOWN, включенным в flags). Адрес кажется на одну страницу выше запрошенного адреса, и размер этого сопоставления кажется на одну страницу меньше, чем вы запрашиваете. Короче говоря, начальный адрес этого сопоставления отключен на 4096 байт. Я не нашел упоминания об этой странности в документации для MAP_GROWSDOWN, и мне это больше похоже на ошибку, чем на функцию. Видите ли вы эту странность или нет, может зависеть от того, какую версию ядра вы используете (из тега я предполагаю, что вы используете ядро ​​Linux). В любом случае было бы полезно изучить этот файл, пока процесс активен, даже если ваш код работает, как задумано, без MAP_GROWSDOWN.

Один из способов поддерживать процесс активным достаточно долго для проверки его maps файла - это установить точку останова в gdb. В любом месте функции, которая вызывает mmap, должно хватить, если вы продвинетесь достаточно далеко (сразу после вызова mmap). $PID в указанном выше имени пути предназначен для представления идентификатора процесса, который вызывает mmap. Вы можете получить этот идентификатор процесса из подходящего вывода ps или из вывода info inferior в gdb.

Чтобы ответить на ваш конкретный вопрос, успех вызова mmap отражает сопоставление, указанное в файле maps (даже если размер этого сопоставления равен нулю в вашем примере), а сбой отражает несоответствие между возвращаемым значением mmap (0x10000) и начало отображения (0x11000). С размером 4096 (как в вашем примере) ни один адрес не позволит назначить *p, но с большим размером, добавив 4096 к возвращаемому значению mmap, вы получите рабочий адрес (при условии, что ваше ядро ​​ведет себя так же, как и мое). Если бы начало сопоставления было равно возвращаемому значению mmap (как при отсутствии MAP_GROWSDOWN), расхождения не было бы.

person Eirik Fuller    schedule 23.01.2016
comment
Спасибо за Ваш ответ. Как вы упомянули, похоже, что это ошибка ядра, а не функция. - person strongerwill; 26.01.2016