Анализ отображения памяти процесса с помощью pmap. [Стека] - PullRequest
2 голосов
/ 04 июля 2019

Я пытаюсь понять, как работает стек в Linux.Я прочитал AMD64 ABI разделы об инициализации стека и процесса, и не ясно, как должен отображаться стек.Вот соответствующая цитата (3.4.1):

Состояние стека

В этом разделе описывается состояние машины, которое exec (BA_OS) создает для новогопроцессов.

и

Не определено, отображаются ли данные и сегменты стека изначально с разрешениями на выполнение или нет.Приложения, которым необходимо выполнить код в стеке или сегментах данных, должны принимать надлежащие меры предосторожности, например, вызывая mprotect().

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

Вопрос в том, использует ли стек «основного потока» отображение MAP_GROWSDOWN | MAP_STACK или, возможно, даже через sbrk?

Просмотр pmap -x <pid> стек помечен [stack] как

00007ffc04c78000     132      12      12 rw---   [ stack ]

Создание отображения как

mmap(NULL, 4096,
     PROT_READ | PROT_WRITE,
     MAP_ANONYMOUS | MAP_PRIVATE | MAP_STACK,
     -1, 0);

просто создает анонимное отображение, как показано в pmap -x <pid> как

00007fb6e42fa000       4       0       0 rw---   [ anon ]

1 Ответ

3 голосов
/ 07 июля 2019

Я могу вывести из приведенных выше цитат, что стек отображается

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

На самом деле x86-64 System V ABI указываетчто argc, argv и envp находятся в стеке при запуске процесса.

Вопрос в том, использует ли стек «основного потока» отображение MAP_GROWSDOWN | MAP_STACK или, возможно, даже через sbrk?

Двоичный загрузчик ELF устанавливает флаг _GROWSDOWN для стека основного потока, но не флаг MAP_STACK.Это код внутри ядра, и он не проходит через обычный интерфейс системных вызовов mmap.

( Ничто в пользовательском пространстве не использует mmap(MAP_GROWSDOWN)поэтому обычно стек основного потока является единственным отображением, имеющим флаг VM_GROWSDOWN внутри ядра.)

Внутреннее имя флага, используемого для области виртуальной памяти (VMA) стека, называетсяVM_GROWSDOWN.Если вам интересно, вот все флаги, которые используются для стека основного потока: VM_GROWSDOWN, VM_READ, VM_WRITE, VM_MAYREAD, VM_MAYWRITE и VM_MAYEXEC.Кроме того, если в двоичном файле ELF указан исполняемый стек (например, путем компиляции с gcc -z execstack), также используется флаг VM_EXEC.Обратите внимание, что в архитектурах, поддерживающих стеки, растущие вверх, вместо VM_GROWSDOWN используется VM_GROWSUP, если ядро ​​было скомпилировано с определенным CONFIG_STACK_GROWSUP.Строка кода, где эти флаги указаны в ядре Linux, может быть найдена здесь .

/proc/.../maps и pmap не используют VM_GROWSDOWN - они полагаются на адресСравнение вместо.Поэтому они могут быть не в состоянии точно определить точный диапазон виртуального адресного пространства, которое занимает стек основного потока (см. пример ).С другой стороны, /proc/.../smaps ищет флаг VM_GROWSDOWN и помечает каждую область памяти, которая имеет этот флаг, как gd.(Хотя кажется, что он игнорирует VM_GROWSUP.)

Все эти инструменты / файлы игнорируют флаг MAP_STACK.Фактически, все ядро ​​Linux игнорирует этот флаг (возможно, поэтому загрузчик программы не устанавливает его). Пространство пользователя передает его только для проверки на будущее, если ядро ​​ действительно хочет начать лечениевыделение стека потоков специально.


sbrk здесь не имеет смысла;стек не соприкасается с «разрывом», и куча brk растет вверх к стеку в любом случае.Linux помещает стек очень близко к вершине виртуального адресного пространства.Поэтому, конечно, первичный стек не может быть выделен с (эквивалент в ядре) sbrk.


И нет, ничто не использует MAP_GROWSDOWN, дажестеки вторичных потоков, поскольку в общем случае их нельзя безопасно использовать.

Справочная страница mmap(2), в которой говорится, что MAP_GROWSDOWN «используется для стеков», смехотворно устарела и вводит в заблуждение.См. Как отобразить стек для системного вызова clone () в Linux? .Как объяснил Ульрих Дреппер в 2008 , код, использующий MAP_GROWSDOWN, обычно не работает, и предлагается удалить флаг из Linux mmap и из заголовков glibc.(Очевидно, этого не произошло, но pthreads с тех пор не использовал его раньше, если вообще когда-либо.)


MAP_GROWSDOWN устанавливает флаг VM_GROWSDOWN для отображения внутри ядра.Основной поток также использует этот флаг для включения механизма роста, поэтому стек потока может быть в состоянии расти так же, как основной стек: произвольно далеко (до ulimit -s?), Если указатель стеканаходится ниже местоположения ошибки страницы.(Linux не требует, чтобы «пробники стеков» касались каждой страницы большого многостраничного стекового массива или alloca.)

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

MAP_GROWSDOWN отображение также может расти так, как описывает справочная страница mmap: доступ к «защитной странице» под самой нижней отображаемой страницей также будет вызывать рост, даже если он находится ниже нижней части красной зоны.

Но в стеке основного потока есть магия, которую вы не получаете с mmap(MAP_GROWSDOWN). Он резервирует пространство роста до ulimit -s, чтобы предотвратить случайный выбор mmap адрес от создания контрольно-пропускного пункта для роста стека. Эта магия доступна только загрузчику программ в ядре, который отображает стек основного потока во время execve(), делая его защищенным от случайного блокирования будущего роста стека mmap(NULL, ...).

mmap(MAP_FIXED) все еще может создать контрольно-пропускной пункт для основного стека, но если вы используете MAP_FIXED, вы на 100% ответственны за то, что ничего не сломали. ( Неограниченный стек не может превышать начальные 132 КБ, если используется MAP_FIXED? ). MAP_FIXED заменит существующие отображения и резервирования, но все остальное будет обрабатывать пространство роста стека основного потока как зарезервированное ;. (Я думаю, это правда; стоит попробовать с MAP_FIXED_NOREPLACE или просто с ненулевым адресом подсказки)

См.

pthread_create не использует MAP_GROWSDOWN для стеков потоков, и никто другой не должен. Обычно не используют. Linux pthreads по умолчанию выделяет полный размер для стека потоков. Это стоит виртуального адресного пространства, но (пока оно фактически не затронуто), а не физических страниц.

Непоследовательные результаты в комментариях к Почему отображение MAP_GROWSDOWN не увеличивается? (некоторые люди находят, что оно работает, некоторые находят, что оно по-прежнему вызывает ошибки при касании возвращаемого значения и страницы ниже) звучат как https://bugs.centos.org/view.php?id=4767 - MAP_GROWSDOWN может даже ошибаться вне способа использования стандартного отображения основного стека VM_GROWSDOWN.

...