Дампы стека
На рисунках 8.6–8.9 представлен стек в различные моменты выполнения программы. Воспользуемся приведенными на рисунках 8.6–8.9 дампами, исходным текстом программы на языке C и ее дизассемблерным видом, для того чтобы лучше понять происходящие в стеке изменения и их причины. Это поможет понять принципы работы фреймового стека функции и его роль и место в программе.
На рисунке 8.6 показан дамп стека сразу после инициализации переменных, но до операций вызова функции и записи в стек ее входных параметров. Это пример «чистого» стека функции.
Рис. 8.6. Дамп стека после инициализации переменных в функции main Далее, перед вызовом функции callex
в стек были помещены ее три параметра (см. рис. 8.7).
Рис. 8.7. Дамп стека до вызова функции callex из функции main Обратите внимание на произошедшие изменения в дампе стека по сравнению с рис. 8.6. После размещения переменных в области стека функции main
в стек были записаны параметры вызываемой функции callex, но сама функция пока еще не была вызвана. На рисунке 8.8 приведен дамп стека функции callex после ее вызова.
Рис. 8.8. Дамп стека после вызова функции callex и выполнения команд пролога, но перед выполнением оператора printf в функции callex Показанный на рис. 8.8 стек проинициализирован функцией callex.
Единственное, что осталось выяснить, – это вид стека перед обращением к функции printf, список параметров которой состоит из четырех элементов. Наконец, перед обращением в функции callex
к функции вывода значений переменных printf в стек помещаются четыре параметра. Это видно из дампа стека, представленного на рис. 8.9.
Рис. 8.9. Дамп стека перед обращением к функции printf в функции callex Приведенные дампы стека позволят читателю хорошо понять принципы заполнения стека. Приобретенные знания пригодятся при обсуждении способов переполнения буфера.
Стековый фрейм и соглашения о вызове функций
Известно несколько вариантов соглашений о вызове функций в программе, каждый из которых отличается способом использования стека. Иногда очистка стека после завершения функции возлагается на вызывающую программу, а иногда на вызванную функцию. Тип вызова говорит компилятору, как генерировать программный код и каким образом использовать стековый фрейм.
Наиболее часто используется так называемое соглашение о вызове функций языка C (C declaration syntax).
Параметры функции, объявленной в стиле языка С, записываются в стек в обратном порядке (справа налево, то есть первый параметр записывается в стек последним). Для вызванной функции это удобно, потому что в подобном случае первый параметр выталкивается из стека в первую очередь. По завершении функции вызывающая программа очищает стек, зная число и тип ранее размещенных в стеке параметров. Описанное соглашение о вызове функций позволяет передать ей переменное число параметров. Этот вариант по умолчанию используется для генерации кода MS Visual C/C++ и широко распространен на многих платформах. Иногда его называют синтаксисом вызова cdecl (cdecl calling syntax). Функция printf является примером функции, которая использует синтаксис cdecl для обработки переменного числа параметров. По завершении функции printf вызвавшая ее программа очищает стек.Другое широко используемое соглашение о вызове функций получило название синтаксиса стандартного вызова (standard call syntax).
Аналогично синтаксису cdecl переданные функции параметры записываются в стек в обратном порядке. Разница состоит в том, что вызванная функция изменяет указатель стека до своего завершения. Иногда это удобно, поскольку в вызывающей программе не надо думать, как правильно изменить указатель стека. Кроме того, это позволяет локализовать программный код изменения стека в вызванной функции. Большинство функций WIN32 API написано с использованием синтаксиса стандартного вызова, иногда известного как stdcall.