Дамп стека
Для того чтобы можно было просмотреть область стека, после его инициализации в отладчике была установлена точка прерывания. При просмотре стека видно, что в нем хранится в начале работы функции, и легче понять, что происходит со стеком в процессе ее выполнения. Дамп стека показан на рис. 8.3.
Рис. 8.3. Дамп стека после инициализации
Из рисунка видно следующее. Буфер памяти, инициализированный строкой «Hello World», занимает 16 байт, а каждое целое число занимает область памяти размером по 4 байта. Шестнадцатеричные числа слева от дампа – статические адреса стека, которые определяются компилятором во время трансляции и которые Windows редко использует. Статические адреса стека потребуются для задания точек перехода при изучении возможности использования переполнения буфера в своих целях. Из рисунка 8.3 видно, что в области стека буфер памяти занимает 16 байт, а не 15, как определено в программе. Большинство компиляторов выравнивают область стека и области данных в стеке на границу двойного слова, поэтому области стека и данных начинаются с адреса памяти, кратного четырем. Это является обязательным условием повышения производительности процессора, и многие программы предполагают, что выравнивание обязательно выполняется. Поэтому буфер памяти занимает 16 байт в области стека, а не 15.
Разнообразие стеков
После инициализации стек может измениться по многим причинам. Опции компилятора влияют на размер и выравнивание стека программы, а настройки оптимизации генерируемого компилятором кода могут сильно изменить механизм создания стека и получения к нему доступа.
Некоторые функции в прологе сохраняют в стеке содержимое отдельных регистров. Во-первых, это делать необязательно, а во-вторых, это полностью зависит от компилятора и функции. В генерируемом компилятором коде может быть или несколько команд сохранения регистров, или одна команда pusha, которая сразу сохраняет содержимое всех регистров. К тому же в прологе могут быть изменены размеры стека и некоторые смещения.
Многие современные компиляторы языков С и С++ оптимизируют генерируемый код различными способами, что сказывается на работе со стеком и стековыми переменными. Например, в одном из наиболее часто встречающихся вариантов оптимизации генерируемого программного кода для доступа к данным в стеке вместо регистра EBP используются ESP. Код получается сложным и трудно поддается анализу, но при этом освобождается лишний регистр, который компилятор использует для генерации более быстрого кода. Другим примером влияния оптимизации кода на работу со стеком служит размещение компиляторами временных переменных в области стека. По разным причинам компиляторы размещают временные переменные в области стека, например для сокращения времени выполнения циклов в программе. Всегда подобные действия сопровождаются тщательной настройкой указателя смещения для доступа к данным стека.
Рассматривая проблемы работы со стеками, нельзя не упомянуть о новых способах защиты стеков программным кодом, генерируемым компиляторами. На них основан проект Crispin Cowen\'s Immunix (www.immunix.com). В проекте используется модифицированный GCC компилятор с языка C для генерации программного кода, практически не позволяющего выполнить злонамеренную программу в результате подмены содержимого регистра EIP при переполнении буфера. Как правило, используется способ, получивший название проверочных величин (canary values). Он основан на записи в прологе дополнительной величины в стек и проверке ее значения в эпилоге функции. При совпадении обоих значений гарантируются целостность стека и неизменность значений сохраненных в стеке регистров EIP и EBP.
Как было упомянуто ранее, стек позволяет решить многие задачи. Во-первых, обеспечить локальное хранение переменных и данных функции. Во-вторых, передавать параметры в вызываемую функцию. В этой части главы будет рассказано, как компиляторы передают параметры вызываемым функциям и как это влияет на стек. Кроме того, будет уделено внимание разъяснению вопросов использования стека в командах процессора вызов функции call и возврата из нее ret.
Основные сведения
Стековый фрейм функции (stack frame) – область памяти, выделяемая всякий раз, когда вызывается функция. Она предназначается для временного хранения параметров, содержимого регистра EIP и, возможно, любых других регистров, а также локальных переменных функции. Ранее внимание читателя было заострено на использовании стека при хранении локальных переменных, а теперь будет рассказано о других возможностях его использования.
Читать дальше
Конец ознакомительного отрывка
Купить книгу