Подтвердить что ты не робот

Как происходит "переполнение стека" и как вы его предотвращаете?

Как происходит переполнение стека и каковы наилучшие способы убедиться, что этого не происходит, или способы его предотвращения, особенно на веб-серверах, но другие примеры также будут интересны?

4b9b3361

Ответ 1

Stack

Стек, в этом контексте, является последним в первом буфере, который вы размещаете во время выполнения вашей программы. Последнее, сначала (LIFO) означает, что последнее, что вы положили, - это всегда первая вещь, которую вы возвращаете - если вы нажмете 2 элемента в стеке, "A" и затем "B", то первое, что вы поп от стека будет "B", а следующая вещь - "A".

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

Переполнение стека

Переполнение стека - это когда вы использовали больше памяти для стека, чем предполагалось использовать в вашей программе. Во встроенных системах у вас может быть только 256 байт для стека, и если каждая функция занимает 32 байта, тогда вы можете иметь только вызовы функций 8 функции функции 2 с функцией глубокой функции 1, которая вызывает функцию 3, которая вызывает функцию 4.... кто звонит функция 8, которая вызывает функцию 9, но функция 9 перезаписывает память за пределами стека. Это может перезаписать память, код и т.д.

Многие программисты совершают эту ошибку, вызывая функцию A, которая затем вызывает функцию B, которая затем вызывает функцию C, которая затем вызывает функцию A. Она может работать большую часть времени, но только один раз неправильный ввод вызовет ее что круг навсегда, пока компьютер не узнает, что стек переполнен.

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

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

Помимо хороших практик программирования, статического и динамического тестирования, вы не можете много сделать в этих системах высокого уровня.

Встроенные системы

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

  • Запретить рекурсию и циклы - соблюдение политики и тестирования
  • Держите код и стек далеко друг от друга (код во флэш-памяти, стек в ОЗУ и никогда не будет соответствовать друг другу)
  • Поместите защитные полосы вокруг стека - пустую область памяти, которую вы заполняете магическим числом (обычно это программа прерывания, но здесь есть много вариантов), а сотни или тысячи раз в секунду вы смотрите на защитные полосы чтобы убедиться, что они не были перезаписаны.
  • Использовать защиту памяти (т.е. не выполнять в стеке, не читать или писать непосредственно за стек)
  • Прерывания не вызывают вторичные функции - они устанавливают флаги, копируют данные и позволяют приложению заботиться о его обработке (в противном случае вы можете получить 8 в глубине вашего дерева вызовов функций, иметь прерывание, а затем выйти еще несколько функции внутри прерывания, вызывающие выброс). У вас несколько деревьев вызовов - одно для основных процессов и по одному для каждого прерывания. Если ваши прерывания могут прервать друг друга... ну, есть драконы...

Языки и системы высокого уровня

Но на языках высокого уровня, работающих в операционных системах:

  • Уменьшите локальное хранилище переменных (локальные переменные хранятся в стеке), хотя компиляторы довольно умны в этом отношении и иногда помещают больших кусков в кучу, если ваше дерево вызовов является мелким)
  • Избегайте или строго ограничивайте рекурсию
  • Не прерывайте свои программы слишком далеко на меньшие и меньшие функции - даже без учета локальных переменных каждый вызов функции потребляет до 64 байтов в стеке (32-битный процессор, сохраняя половину регистров процессора, флаги и т.д.).
  • Держите дерево вызовов неглубоким (аналогично приведенному выше описанию)

Веб-серверы

Это зависит от "песочницы", которую вы имеете, можете ли вы контролировать или даже видеть стек. Скорее всего, вы можете обрабатывать веб-серверы, как и любой другой язык высокого уровня и операционную систему, - это в значительной степени из ваших рук, но проверьте используемый язык и серверный стек. Например, можно разбить стек на вашем SQL-сервере.

-Adam

Ответ 2

Переполнение стека в реальном коде происходит очень редко. Большинство ситуаций, в которых это происходит, - это рекурсии, в которых прекращение было забыто. Однако он может встречаться редко в сильно вложенных структурах, например. особенно больших XML-документов. Единственная реальная помощь здесь заключается в том, чтобы реорганизовать код для использования явного объекта стека вместо стека вызовов.

Ответ 3

Бесконечная рекурсия - это обычный способ получить ошибку. Чтобы предотвратить - всегда убедитесь, что есть путь выхода, который будет удален.: -)

Другой способ получить переполнение стека (по крайней мере, в C/С++) - объявить в стеке огромную переменную.

char hugeArray[100000000];

Это будет сделано.

Ответ 4

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

Некоторые опции в этом случае:

Ответ 5

Обычно переполнение стека является результатом бесконечного рекурсивного вызова (учитывая обычный объем памяти на стандартных компьютерах в настоящее время).

Когда вы совершаете вызов метода, функции или процедуры, "стандартный" способ или выполнение вызова состоит из:

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

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

Вы увидите, что если вы начнете делать рекурсивные вызовы, стек начнет расти. Теперь стек обычно зарезервирован в памяти таким образом, что он растет в противоположном направлении к куче, поэтому, учитывая большое количество вызовов без "возврата", стек начинает заполняться.

Теперь, в более старые времена переполнение стека может произойти просто потому, что вы все же освободили всю доступную память. При использовании модели виртуальной памяти (до 4 ГБ на системе X86), которая обычно выходит за пределы области действия, если вы получаете ошибку, ищите бесконечный рекурсивный вызов.

Ответ 6

Переполнение стека происходит, когда Джефф и Джоэл хотят дать миру лучшее место для получения ответов на технические вопросы. Это слишком поздно, чтобы предотвратить переполнение стека. Этот "другой сайт" мог бы предотвратить его, не будучи нечетким.;)

Ответ 7

Что? Никто не любит любовь к тем, кто обведен бесконечным циклом?

do
{
  JeffAtwood.WritesCode();
} while(StackOverflow.MakingMadBank.Equals(false));

Ответ 8

Помимо формы, которую вы получаете от прямой рекурсии (например, Fibonacci(1000000)), более тонкая форма, которую я испытал много раз, - это косвенная рекурсия, когда функция вызывает другую функцию, которая вызывает другой, и затем одна из этих функций снова вызывает первый.

Это обычно может возникать в функциях, вызываемых в ответ на события, но которые сами могут генерировать новые события, например:

void WindowSizeChanged(Size& newsize) {
  // override window size to constrain width
    newSize.width=200;
    ResizeWindow(newSize);
}

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

Ответ 9

Учитывая, что это было связано с "взломом", я подозреваю, что "переполнение стека", на которое он ссылается, представляет собой переполнение стека вызовов, а не переполнение стека более высокого уровня, например, ссылки на большинство других ответов здесь. Это действительно не относится к любым управляемым или интерпретируемым средам, таким как .NET, Java, Python, Perl, PHP и т.д., Которые обычно записываются в веб-приложениях, поэтому ваш единственный риск - это сам веб-сервер, который, вероятно, написан на C или С++.

Посмотрите эту тему:

https://stackoverflow.com/info/7308/what-is-a-good-starting-point-for-learning-buffer-overflow