Можно ли предсказать переполнение стека в C в Linux?

Существуют определенные условия, которые могут вызвать переполнение стека в системе Linux x86:

  • struct my_big_object[HUGE_NUMBER] в стеке. Прохождение через него в конечном итоге вызывает SIGSEGV.
  • Подпрограмма alloca() (похожа на malloc(), но использует стек, автоматически освобождается, а также взрывается с помощью SIGSEGV, если он слишком большой). Обновление: alloca () формально не устарела, как я изначально заявил; это просто не рекомендуется.

Есть ли способ программно определить, достаточно ли велик локальный стек для данного объекта? Я знаю, что размер стека регулируется с помощью ulimit, поэтому я надеюсь, что есть способ (каким бы непереносимым он ни был). В идеале я бы хотел сделать что-то вроде этого:

int min_stack_space_available = /* ??? */;
if (object_size < min_stack_space_available)
{
    char *foo = alloca(object_size);
    do_stuff(foo);
}
else
{
    char *foo = malloc(object_size);
    do_stuff(foo);
    free(foo);
}

Это случайно происходит в ветке? Я попытался воспроизвести segfault, но не получил ничего, кроме NULL из alloca () при попытке очень больших размеров.

Tim Post 09.01.2009 10:28

Да, alloca () изначально была в функции, вызываемой из многих потоков.

Tom 09.01.2009 14:58
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
2
1 920
12
Перейти к ответу Данный вопрос помечен как решенный

Ответы 12

The deprecated alloca() routine (like malloc(), but uses the stack, automatically frees itself, and also blows up with SIGSEGV if it's too big).

Почему alloca устарела?

Во всяком случае, насколько быстрее в вашем случае alloca vs malloc? (Стоит ли оно того?)

И разве вы не вернете null из alloca, если не хватит места? (так же, как malloc?)

А когда происходит сбой вашего кода, где он падает? это в alloca или в doStuff ()?

/ Йохан

(1) на странице руководства GNU сказано не использовать его. (2) alloca выполняется в постоянное время, тогда как malloc не является детерминированным и может включать системный вызов и блокировку потоков. (2) Если alloca вызывает переполнение стека, поведение не определено (происходит ошибка при использовании, а не при использовании alloca).

Tom 09.01.2009 14:50

однако возвращение null, обеспечиваемое malloc, часто является лишь ложной защитой: malloc в linux for действительно возвращает ненулевое значение и выдает сбой при использовании памяти. вам сначала нужно переключить некоторые биты в ядре, чтобы это изменить (см. man malloc)

Johannes Schaub - litb 10.01.2009 17:22

Приносим извинения, если это констатирует очевидное, но вы можете легко написать функцию для проверки определенного размера выделения стека, просто попробовав alloca (такого размера) и перехватив исключение переполнения стека. Если вы хотите, вы можете поместить его в функцию с некоторой заранее определенной математикой для накладных расходов стека функций. Например:

bool CanFitOnStack( size_t num_bytes )
{
    int stack_offset_for_function = 4; // <- Determine this
    try
    {
        alloca( num_bytes - stack_offset_for_function );
    }
    catch ( ... )
    {
        return false;
    }

    return true;
}

И даже если бы это был C++, не существует стандартного независимого от платформы механизма для запуска исключения при переполнении стека.

j_random_hacker 09.01.2009 11:44

На самом деле это было бы выполнимо - не так, как вы описываете, а осторожно используя обработчик SIGSEGV очень.

Tom 09.01.2009 14:56

Хорошие моменты; Я пропустил, что это C. Мне просто пришло в голову, что использование самого обработчика исключений может быть самым простым способом, так сказать, из точки A в точку B. :)

Nick 09.01.2009 20:52

alloca () будет возвращать NULL в случае сбоя, я считаю, что поведение alloca (0) не определено и является вариантом платформы. Если вы проверите это до do_something (), вы никогда не попадете под удар SEGV.

У меня есть пара вопросов:

  1. Зачем, ну почему вам нужно что-то такое большое в стеке? Размер по умолчанию в большинстве систем составляет 8 МБ, это все еще слишком мало?
  2. Если функция, вызывающая alloca (), блокируется, будет ли защита такого же количества кучи с помощью mlock () / mlockall () гарантировать примерно такую ​​же производительность доступа (например, «Не меняй местами, брат!») С течением времени? Если вы используете более агрессивный планировщик rt, его все равно рекомендуется вызывать.

Вопрос интересный, но поднимает бровь. Он поднимает стрелку моего квадратного колышка-круглого отверстия-о-метра.

(1) Размер стека на машинах, на которые я смотрю, настроен на гораздо меньший, чем 8 МБ. (2) Обмен страниц определенно вызывает беспокойство, хотя теперь, когда вы упомянули об этом, может быть, мне просто лучше предварительно выделить и mlock () ing.

Tom 09.01.2009 14:53

alloca вызывает неопределенное поведение при переполнении стека. он не возвращает 0 в соответствии с его справочной страницей

Johannes Schaub - litb 10.01.2009 17:33

Сам alloca () зависит от платформы. :)

bk1e 10.01.2009 21:08
Ответ принят как подходящий

Вы можете определить пространство стека, доступное процессу, найдя размер пространства стека процесса, а затем вычтя использованное количество.

ulimit -s

показывает размер стека в системе Linux. Для программного подхода см. getrlimit (). Затем, чтобы определить текущую глубину стека, вычтите указатель на верх стека из единицы в нижнюю. Например (непроверенный код):

unsigned char *bottom_of_stack_ptr;

void call_function(int argc, char *argv) {
    unsigned char top_of_stack;
    unsigned int depth = (&top_of_stack > bottom_of_stack_ptr) ? 
        &top_of_stack-bottom_of_stack_ptr : 
        bottom_of_stack_ptr-&top_of_stack;

    if ( depth+100 < PROGRAMMATICALLY_DETERMINED_STACK_SIZE ) {
        ...
    }
}

int main(int argc, char *argv) {
    unsigned char bottom_of_stack;
    bottom_of_stack_ptr = &bottom_of_stack;
    my_function();
    return 0;
}

Это правильно? Bottom_of_stack не может быть реальным концом стека, верно? Разве глобальные переменные не помещаются в стек плюс другой мусор, который, по мнению компилятора, ему нужен?

user48956 11.01.2009 00:54

ulimit -s и getrlimit (RLIMIT_STACK) сообщают вам только размер начального потока. Он ничего вам не скажет, если вы не знаете, что работаете в начальном потоке.

Per Mildner 16.01.2009 16:56

У глобалов обычно есть собственное пространство. Код запуска может добавить глубину стека, поэтому приведенный выше код добавляет к глубине хороший фактор выдумки на всякий случай. Да, RLIMIT_STACK применяется только к начальному стеку, однако pthread позволяет получить и установить размер стека.

Paul 24.01.2009 04:36

Даже если это не прямой ответ на ваш вопрос, я надеюсь, вы знаете о существовании Valgrind - замечательного инструмента для обнаружения таких проблем во время выполнения в Linux.

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

Я знаю valgrind, и это мне не помогает в этом вопросе.

Tom 10.01.2009 02:28

Функция alloca устарела. Однако его нет в POSIX, и он также зависит от машины и компилятора. На странице руководства Linux по alloca отмечается, что «для некоторых приложений его использование может повысить эффективность по сравнению с использованием malloc, а в некоторых случаях оно также может упростить освобождение памяти в приложениях, которые используют longjmp () или siglongjmp (). В противном случае, его использование не рекомендуется ".

В справочной странице также говорится, что «нет индикации ошибки, если кадр стека не может быть расширен. Однако после неудачного распределения программа, скорее всего, получит SIGSEGV».

Производительность malloc фактически упоминалась в Подкаст Stackoverflow # 36.

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

Спасибо, я проверю этот подкаст.

Tom 09.01.2009 14:51

Не уверен, что это применимо к Linux, но в Windows можно столкнуться с нарушениями доступа с большими выделениями стека даже если им это удастся!

Это связано с тем, что по умолчанию VMM Windows фактически отмечает только несколько верхних (не знаю, сколько именно) 4096-байтовых страниц ОЗУ стека как выгружаемые (т. Е. Поддерживаемые файлом подкачки), поскольку он считает, что доступ к стеку обычно идет вниз от вершина; по мере того, как доступ становится все ближе и ближе к текущей «границе», нижняя и нижняя страницы помечаются как листаемые. Но это означает, что раннее чтение / запись в память намного ниже вершины стека вызовет нарушение прав доступа, поскольку эта память фактически еще не выделена!

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

Tom 09.01.2009 14:54

Убийца OOM? Связанные, но разные, я думаю. По умолчанию Linux позволяет распределению куча успешно возвращаться, когда своп исчерпан; Я считаю, что в этой ситуации Windows VMM рано или поздно выйдет из строя. Я нахожу сомнительным поведение Windows куча ... :)

j_random_hacker 09.01.2009 17:17

Вы имеете в виду, что OOM killer можно отключить? Я не знаю, как отключить поведение стека Windows ... Может быть, есть переключатель, который вы можете поставить во время компоновки?

j_random_hacker 11.01.2009 07:15

Я не могу придумать ничего хорошего. Может быть, это возможно с помощью getrlimit () (предложенного ранее) и некоторой арифметики указателей? Но сначала спросите себя, действительно ли вы этого хотите.

void *closeToBase;

main () {
  int closeToBase;
  stackTop = &closeToBase;
}

int stackHasRoomFor(int bytes) {
  int currentTop;
  return getrlimit(...) - (&currentTop  - closeToBase) > bytes + SomeExtra;
}

Лично я бы этого не сделал. Размещайте большие вещи в куче, стек не предназначен для этого.

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

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

Tom 09.01.2009 15:05

Некоторые компиляторы, например Откройте Watcom C / C++, поддерживают функцию stackavail (), которая позволяет вам делать именно это.

Вы можете использовать GNU libsigsegv для ручка ошибки страницы, включая случаи, когда происходит переполнение стека (с его веб-сайта):

In some applications, the stack overflow handler performs some cleanup or notifies the user and then immediately terminates the application. In other applications, the stack overflow handler longjmps back to a central point in the application. This library supports both uses. In the second case, the handler must ensure to restore the normal signal mask (because many signals are blocked while the handler is executed), and must also call sigsegv_leave_handler() to transfer control; then only it can longjmp away.

Читая страницу libsigsegv, я смущен тем, что на ней не упоминается, казалось бы, глубокая невозможность быть уверенным, что программа может продолжаться осмысленно после переполнения стека. Что, если переполнение произошло внутри чего-то вроде malloc () в середине работы с кучей? Что, если переполнение произошло во внутренней функции поддержки, внедренной компилятором, где вы даже не видите вызова функции? Помимо попытки продолжить бег, я также скептически относился бы к тому, что немного дало результат и завершилось - что в этом проверенном наборе «маленьких» вещей, которые вам обещают, что вы можете сделать? : - /

HostileFork says dont trust SE 18.11.2019 19:14

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

Johannes Schaub - litb 18.11.2019 22:14

Но IIRC, в настоящее время вы можете вызвать userfaultfd в Linux, чтобы «создать файловый дескриптор для обработки ошибок страниц в пользовательском пространстве», что казалось бы более чистым, чем подключение к обработчику сигналов.

Johannes Schaub - litb 18.11.2019 22:17

Я использовал копирование при записи с MMF, но это кажется другим. Механически невозможно расширить стек C и продолжить работу. Итак, если у вас был void *malloc(size_t size) { /* fiddle heap */ helper() /* finish fiddling heap */ return p; }, и переполнение происходит во время helper() ... все, что вы можете сделать, это предоставить небольшой объем памяти, используемый для стека во время обработчика сигнала, который должен завершаться или longjmp. Ничто не запустит финализацию, поэтому куча может быть повреждена. Компиляторы иногда реализуют «примитивы» с вспомогательными функциями, поэтому это кажется рискованным, даже когда это «весь ваш собственный код». Верно?

HostileFork says dont trust SE 19.11.2019 00:21

@hostile Я понимаю, о чем вы сейчас говорите. Я согласен. Если стек попадает в кучу, и вы не можете знать, какая функция может быть активной и что она делает, это фатально.

Johannes Schaub - litb 19.11.2019 00:39

Конец области стека определяется ОС динамически. Хотя вы можете найти «статические» границы стека, просмотрев области виртуальной памяти (VMA) в зависимости от ОС (см. Файлы stackvma * в libsigsegv / src /), вам дополнительно придется учитывать

Другие вопросы по теме