Почему отображение MAP_GROWSDOWN не растет? - PullRequest
4 голосов
/ 04 июля 2019

Я пытался создать MAP_GROWSDOWN отображение с ожиданием, что оно будет расти автоматически.Как указано на странице руководства:

MAP_GROWSDOWN

Этот флаг используется для стеков.Это указывает системе виртуальной памяти ядра, что отображение должно распространяться вниз в памяти.Адрес возврата на одну страницу ниже области памяти, которая фактически создается в виртуальном адресном пространстве процесса. Если дотронуться до адреса на странице «guard» под отображением, увеличит отображение на страницу .Этот рост может повторяться до тех пор, пока отображение не будет увеличено до страницы верхнего уровня следующего нижнего отображения, и в этот момент прикосновение к странице «защиты» приведет к сигналу SIGSEGV.

Поэтому я написал следующий пример для проверки роста отображения:

#ifndef _GNU_SOURCE
    #define _GNU_SOURCE
#endif
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <errno.h>
#include <sys/mman.h>
#include <stdio.h>

int main(void){
    char *mapped_ptr = mmap(NULL, 4096,
                            PROT_READ | PROT_WRITE,
                            MAP_ANONYMOUS | MAP_PRIVATE | MAP_STACK | MAP_GROWSDOWN,
                            -1, 0);
    if(mapped_ptr == MAP_FAILED){
        int error_code = errno;
        fprintf(stderr, "Cannot do MAP_FIXED mapping."
                        "Error code = %d, details = %s\n", error_code, strerror(error_code));
                        exit(EXIT_FAILURE);
    }
    volatile char *c_ptr_1 = mapped_ptr; //address returned by mmap
    *c_ptr_1 = 'a'; //fine

    volatile char *c_ptr_2 = mapped_ptr - 4095; //1 page below the guard
    *c_ptr_2 = 'b'; //crashes with SEGV
}

Таким образом, я получил SEGV вместо увеличения отображения.Что значит расти здесь?

Ответы [ 2 ]

2 голосов
/ 04 июля 2019

Заменить:

volatile char *c_ptr_1 = mapped_ptr - 4096; //1 page below

На

volatile char *c_ptr_1 = mapped_ptr;

Потому что:

Адрес возврата на одну страницу меньше области памяти, котораяна самом деле создается в виртуальном адресном пространстве процесса. Если дотронуться до адреса на странице «guard» под отображением, отображение увеличится на страницу.

Обратите внимание, что я тестировал решениеи работает как и ожидалось в ядре 4.15.0-45-generic.

1 голос
/ 07 июля 2019

Прежде всего, вы не хотите MAP_GROWSDOWN, и дело не в том, как работает стек основных потоков. Анализ отображения памяти процесса с помощью pmap. [стек] Ничто не использует это, и почти ничего не должно использовать это. Материал на странице руководства, в котором говорится, что он «используется для стеков», неверен и должен быть исправлен.

Я подозреваю, что это может быть ошибка (потому что ничто не использует его, поэтому обычно никто не заботится и даже не замечает, если оно сломается.)


Ваш код работает для меня, если я изменю вызов mmap на карту более чем на 1 страницу. В частности, я попробовал 4096 * 100. Я использую Linux 5.0.1 (Arch Linux) на голом железе (Skylake).

/proc/PID/smaps действительно показывает флаг gd.

И затем (при однократном шаге asm) запись maps фактически меняется на более низкий начальный адрес, но с тем же конечным адресом, поэтому она буквально растет вниз, когда я начинаю с сопоставления 400k. Это дает начальное выделение 400 КБ выше обратного адреса, которое увеличивается до 404 КБ при запуске программы. (Размер для _GROWSDOWN сопоставления составляет , а не предел роста или что-то в этом роде.)

https://bugs.centos.org/view.php?id=4767 может быть связано; что-то изменилось между версиями ядра в CentOS 5.3 и 5.5. И / или это было связано с работой на виртуальной машине (5.3), а не с ростом и отказом от голого металла (5.5).


Я упростил C для использования ptr[-4095] и т. Д .:

int main(void){
    volatile char *ptr = mmap(NULL, 4096*100,
                            PROT_READ | PROT_WRITE,
                            MAP_ANONYMOUS | MAP_PRIVATE | MAP_STACK | MAP_GROWSDOWN,
                            -1, 0);
    if(ptr == MAP_FAILED){
        int error_code = errno;
        fprintf(stderr, "Cannot do MAP_FIXED mapping."
                        "Error code = %d, details = %s\n", error_code, strerror(error_code));
                        exit(EXIT_FAILURE);
    }

    ptr[0] = 'a';      //address returned by mmap
    ptr[-4095] = 'b';  // grow by 1 page
}

Компиляция с gcc -Og дает asm, который хорошо подходит для одного шага.


Кстати, разные слухи об удалении флага из glibc явно ошибочны. Этот источник компилируется, и ясно, что он также поддерживается ядром, а не игнорируется. (Хотя поведение, которое я вижу с размером 4096 вместо 400 кБ, в точности соответствует тому, что флаг молча игнорируется. Однако gd VmFlag все еще присутствует в smaps, поэтому он не игнорируется на этом этапе.)

Я проверил, и было место для его роста, не приближаясь к другому отображению. Итак, IDK, почему он не вырос, когда отображение GD было всего 1 страницей. Я попробовал пару раз, и он каждый раз работал с ошибками. С большим начальным отображением он никогда не ошибался.

Оба раза были с сохранением возвращаемого значения mmap (первой страницы собственно сопоставления), затем хранилище на 4095 байт ниже этого.

...