Как происходит "переполнение стека" и как его предотвратить?
Как происходит переполнение стека и каковы наилучшие способы убедиться, что этого не происходит, или способы предотвратить его, особенно на веб-серверах, но другие примеры также были бы интересны?
9 ответов:
стек
стек, в этом контексте, является последним в, первый из буфера вы размещаете данные во время выполнения программы. Last in, first out (LIFO) означает, что последнее, что вы вставляете, всегда первое, что вы возвращаете - если вы нажмете 2 элемента в стеке, "A", а затем "B", то первое, что вы выскочите из стека, будет "B", а следующее - "A".
когда вы вызываете функцию в своем коде, следующая инструкция после вызова функции сохраняется на стек и любое пространство хранения, которое может быть перезаписано вызовом функции. Вызываемая функция может использовать больше стека для своих собственных локальных переменных. Когда это сделано, он освобождает пространство стека локальной переменной, которое он использовал, а затем возвращается к предыдущей функции.
переполнение стека
переполнение стека-это когда вы израсходовали больше памяти для стека, чем ваша программа должна была использовать. Во встроенных системах у вас может быть только 256 байт для стека, и если каждый функция занимает 32 байта, то вы можете иметь только вызовы функций 8 глубокая функция 1 вызывает функцию 2 кто вызывает функцию 3 кто вызывает функцию 4 .... кто вызывает функцию 8 кто вызывает функцию 9, но функция 9 перезаписывает память вне стека. Это может перезаписать память, код и т. д.
многие программисты делают эту ошибку, вызывая функцию A, которая затем вызывает функцию B, которая затем вызывает функцию C, которая затем вызывает функцию A. Это может работать большую часть времени, но только один раз неправильный ввод приведет к тому, что он будет идти по этому кругу навсегда, пока компьютер не распознает, что стек раздут.
Как правило, ОС и язык программирования, который вы используете, управляют стеком, и это не в ваших руках. Вы должны посмотреть на ваш звонок график (древовидная структура, которая показывает из вашего основного, что каждая функция вызывает), чтобы увидеть, как глубоко ваши вызовы функций идут, и обнаружить циклы и рекурсии, которые не предназначены. Преднамеренные циклы и рекурсия должны быть искусственно проверены на ошибку, если они вызывают друг друга слишком много раз.
помимо хорошей практики программирования, статического и динамического тестирования, вы мало что можете сделать в этих системах высокого уровня.
встраиваемых систем
во встроенном мир, особенно в коде высокой надежности (автомобиль, самолет, космос) Вы делаете обширные обзоры кода и проверку, но вы также делаете следующее:
- запретить рекурсию и циклы-принудительно с помощью политики и тестирования
- держите код и стек далеко друг от друга (код во флэш-памяти, стек в оперативной памяти, и никогда два не должны встречаться)
- поместите защитные полосы вокруг стека-пустая область памяти, которую вы заполняете магическим числом (обычно программное прерывание инструкция, но здесь есть много вариантов), и сотни или тысячи раз в секунду вы смотрите на охранные полосы, чтобы убедиться, что они не были перезаписаны.
- использовать защиту памяти (т. е. не выполнять в стеке, не читать и не писать только за пределами стека)
- прерывания не вызывают вторичные функции - они устанавливают флаги, копируют данные и позволяют приложению обрабатывать их (в противном случае вы можете получить 8 глубоко в дереве вызовов функций, иметь прерывание и затем выйдите еще несколько функций внутри прерывания, вызывая выброс). У вас есть несколько деревьев вызовов - по одному для основных процессов и по одному для каждого прерывания. Если ваши прерывания могут прерывать друг друга... ну, там есть драконы...
языки и системы высокого уровня
но в языках высокого уровня, работающих в операционных системах:
- уменьшите локальное хранилище переменных (локальные переменные хранятся в стеке-хотя компиляторы довольно умный об этом и иногда будет ставить большие местные жители на кучу, если ваше дерево вызовов неглубоко)
- избегать или строго ограничивать рекурсию
- не разбивайте свои программы слишком далеко на меньшие и меньшие функции-даже без подсчета локальных переменных каждый вызов функции потребляет целых 64 байта в стеке (32-разрядный процессор, сохраняя половину регистров процессора, флаги и т. д.)
- держите дерево вызовов неглубоким (как и выше заявление)
веб-сервера
это зависит от "песочницы" у вас есть ли вы можете контролировать или даже видеть стек. Скорее всего, вы можете обращаться с веб - серверами, как с любым другим языком и операционной системой высокого уровня-это в значительной степени из ваших рук, но проверьте язык и стек серверов, которые вы используете. Это и можно взорвать стек на вашем SQL server, например.
Адам
переполнение стека в реальном коде происходит очень редко. Большинство ситуаций, в которых это происходит, являются рекурсиями, где окончание было забыто. Однако это может редко происходить в сильно вложенных структурах, например, особенно больших XML-документах. Единственная реальная помощь здесь-это рефакторинг кода для использования явного объекта стека вместо стека вызовов.
бесконечная рекурсия является распространенным способом получения ошибки переполнения стека. Чтобы предотвратить - всегда убедитесь, что есть путь выхода, что будет быть хитом. : -)
другой способ получить переполнение стека (по крайней мере, в C/C++) - это объявить какую-то огромную переменную в стеке.
char hugeArray[100000000];
вот и все.
большинство людей скажут вам, что переполнение стека происходит с рекурсией без пути выхода - хотя в основном верно, если вы работаете с достаточно большими структурами данных, даже правильный путь выхода рекурсии вам не поможет.
некоторые опции в этом случае:
- поиск в ширину
- хвостовая рекурсия, .Net-specific great блоге (извините, 32-разрядная .Net)
обычно переполнение стека является результатом бесконечного рекурсивного вызова (учитывая обычный объем памяти в настоящее время для обычных компьютеров).
когда вы вызываете метод, функцию или процедуру, "стандартный" способ или вызов состоит в следующем:
- нажатие направления возврата для вызова в стек (это следующее предложение после вызова)
- обычно пространство для возвращаемого значения резервируется в поле стек
- нажатие каждого параметра в стек (порядок расходится и зависит от каждого компилятора, также некоторые из них иногда хранятся в регистрах процессора для повышения производительности)
- выполнение фактического вызова.
таким образом, обычно это занимает несколько байтов, удаляющих число и тип параметров, а также архитектуру машины.
вы увидите, что если вы начнете делать рекурсивные вызовы, стек начнет расти. Сейчас, стек обычно резервируется в памяти таким образом, что он растет в противоположном направлении к куче, поэтому, учитывая большое количество вызовов без "возвращения", стек начинает заполняться.
теперь, в старые времена переполнение стека может произойти просто потому, что вы exausted всю доступную память, просто так. С моделью виртуальной памяти (до 4 ГБ в системе X86), которая была вне области действия, поэтому обычно, если вы получаете ошибку переполнения стека, ищите бесконечный рекурсивный вызов.
переполнение стека происходит, когда Джефф и Джоэл хотите дать миру лучшее место, чтобы получить ответы на технические вопросы. Это слишком поздно, чтобы предотвратить переполнение стека. Этот "другой сайт" мог бы предотвратить его, не будучи скудным. ;)
Помимо формы переполнения стека, которую вы получаете от прямой рекурсии (например
Fibonacci(1000000)
), более тонкая форма этого, которую я испытывал много раз, является косвенной рекурсией, где функция вызывает другую функцию, которая вызывает другую, а затем одна из этих функций снова вызывает первую.это обычно происходит в функциях, которые вызываются в ответ на события, но которые сами могут генерировать новые события, например:
void WindowSizeChanged(Size& newsize) { // override window size to constrain width newSize.width=200; ResizeWindow(newSize); }
в этом случае звонок в
ResizeWindow
может привести кWindowSizeChanged()
обратный вызов будет вызван снова, который вызываетResizeWindow
снова, пока не закончится стек. В таких ситуациях вам часто нужно отложить ответ на событие до тех пор, пока не вернется кадр стека, например, отправив сообщение.
что? Никто не имеет никакой любви к тем, кто охвачен бесконечным циклом?
do { JeffAtwood.WritesCode(); } while(StackOverflow.MakingMadBank.Equals(false));
учитывая, что это было помечено как "взлом", я подозреваю, что "переполнение стека", на которое он ссылается, - это переполнение стека вызовов, а не переполнение стека более высокого уровня, такие как те, на которые ссылаются в большинстве других ответов здесь. Это действительно не относится к любым управляемым или интерпретируемым средам, таким как .NET, Java, Python, Perl, PHP и т. д., в которых обычно записываются веб-приложения, поэтому ваш единственный риск-это сам веб-сервер, который, вероятно, написан на C или c++.
проверить это тема:
https://stackoverflow.com/questions/7308/what-is-a-good-starting-point-for-learning-buffer-overflow