Как программно получить начальный и конечный адрес стека Linux?

Для однопоточной программы я хочу проверить, находится ли данный виртуальный адрес в стеке процессов. Я хочу сделать это внутри процесса, который написан на C.

Я думаю о чтении /proc/self/maps, чтобы найти строку с надписью [stack], чтобы получить начальный и конечный адрес для моего стека процессов. Размышление об этом решении привело меня к следующим вопросам:

  • /proc/self/maps показывает стек из 132k для моего конкретного процесса, а максимальный размер для стека (ulimit -s) - 8 мегабайт в моей системе. Как Linux знает, что ошибка данного страницы возникает из-за того, что мы выше предела стека, принадлежит стек (и что стек должен быть увеличен), а не то, что мы достигаем другой области памяти процесса?

  • Ли Linux сокращает стек? Другими словами, например, при возврате из вызовов глубоких функций ОС уменьшает область виртуальной памяти, соответствующую стеку?

  • Сколько виртуального пространства первоначально выделено для стека операционной системой?

  • Является ли мое решение правильным и есть ли другой более чистый способ сделать это?

Ответ 1

Подробные сведения о настройке стека зависят от используемой архитектуры, исполняемого формата и различных параметров конфигурации ядра (рандомизация указателя стека, адресного пространства 4 ГБ для i386 и т.д.).

Во время процесса exec'd ядро ​​выбирает верхнюю стек стека по умолчанию (например, в традиционной архитектуре i386 это 0xc0000000, т.е. конец области пользовательского режима виртуального адресного пространства).

Тип исполняемого формата (ELF vs a.out и т.д.) может теоретически изменить начальную вершину стека. Затем выполняется любая дополнительная рандомизация стека и любые другие исправления (например, область использования vdso [системный вызов] обычно используется здесь, когда используется). Теперь у вас есть фактическая начальная вершина стека.

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

Другие ответы, насколько я могу судить:

(1) Когда процесс пытается сохранить данные в области ниже текущей нижней части области стека, генерируется ошибка страницы. Обработчик ошибок ядра определяет, где начинается следующая заполненная область виртуальной памяти в виртуальном адресном пространстве процесса. Затем он смотрит, какой тип области. Если это область "растет" (по крайней мере, на x86, все области стека должны быть отмечены как растущие), и если значение указателя стека процесса (ESP/RSP) во время ошибки меньше, чем нижняя часть этот регион и если процесс не превысил настройку ulimit -s, и новый размер области не столкнулся бы с другим регионом, то он предположил бы действительную попытку роста стека, а дополнительные страницы выделяются для удовлетворения процесс.

(2) Не уверенно на 100%, но я не думаю, что есть попытки сжать области стека. Предположительно, будет выполняться обычная обработка страниц LRU, сделав теперь неиспользуемых областей кандидатами для подкачки в область подкачки, если они действительно не используются повторно.

(4) Ваш план кажется мне разумным: карты /proc/NN/maps должны получать начальные и конечные адреса для области стека в целом. Думаю, это был бы самый большой ваш стек. Текущая фактическая рабочая область OTOH должна находиться между вашим текущим указателем стека и концом области (обычно ничто не должно использовать область стека под указателем стека).

Ответ 2

Мой ответ для linux на x64 только с ядром 3.12.23. Он может или не может применяться к другим версиям или архитектурам.

(1) + (2) Я не уверен здесь, но я считаю, что это уже сказал Гил Гамильтон.

(3) Вы можете увидеть количество в /proc/pid/maps (или/proc/self/maps, если вы нацеливаете вызывающий процесс). Однако не все это фактически используется как стек для вашего приложения. Аргумент- (argv []) и векторы среды (__environ []) обычно потребляют довольно немного места внизу (самый высокий адрес) этой области.

Чтобы на самом деле найти область, обозначенную как "стек" для вашего приложения, вы можете посмотреть /proc/self/stat. Его значения документированы здесь. Как вы можете видеть, есть поле для "startstack". Вместе с размером отображаемой области вы можете вычислить текущую сумму зарезервированного стека. Наряду с "kstkesp" вы можете определить количество свободного пространства стека или фактически используемое пространство стека (помните, что любая операция, выполняемая вашим потоком, скорее всего, изменит эти значения).

Также обратите внимание, что это работает только для основного потока процессов! Другие потоки не получат сопоставления "[stack]", но либо используют анонимные сопоставления, либо даже могут оказаться в куче. (Используйте API-интерфейс pthreads, чтобы найти эти значения или вспомните начало стека в главной функции потоков).

(4) Как объяснено в (3), вы в основном одобрены, но не совсем точны.