Можно ли предсказать переполнение стека в 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);
}

12 ответов

Решение

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

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;
}

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

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

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

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

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

На самом деле производительность malloc упоминалась в подкасте Stackru # 36.

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

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

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

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

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

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

Устаревшая процедура alloca () (например, malloc (), но использующая стек, автоматически освобождает себя, а также разрывается с помощью SIGSEGV, если он слишком большой).

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

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

И разве вы не получаете нулевой возврат от alloca, если не осталось достаточно места? (так же, как malloc?)

И когда ваш код падает, где он падает? это в alloca или в doStuff ()?

/ Johan

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

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

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

void * closeToBase;

главный () {
  int closeToBase;
  stackTop = & closeToBase;
}

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

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

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

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

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

  • значения getrlimit,
  • размер стека для каждого потока (см. pthread_getstacksize)

Извиняюсь, если это заявляет очевидное, но вы могли бы легко написать функцию для проверки на определенный размер выделения стека, просто попробовав 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;
}
Другие вопросы по тегам