Далее обратите внимание, что адрес
Далее обратите внимание, что адрес переменной мы получаем не при помощи offset, а при помощи addr. Все их различие заключается в том, что addr может работать с локальными переменными, а вот offset - нет.
Открою вам страшную тайну! На самом деле локальная переменная - это всего лишь зарезервированное место в стеке. Когда компилятор встречает addr, он сначала проверяет локальная это переменная или глобальная. Если глобальная, он помещает адрес этой переменной в объектный файл, то есть работает аналогично offset. Если же это локальная переменная, то перед вызовом функции генерируется следующая последовательность инструкций: lea eax, LocalVar push eax
Как обычно, я надеюсь на то, что вы не поверили мне на слово. Мы обязательно разберемся с тем, как происходит резервирование места в стеке и обращение к локальным переменным, но сделаем немного позже. Для начала нам нужно хотя бы чуть-чуть ознакомиться с инструментарием. Начнем мы, пожалуй, с IDA - The Interactive Disassembler.
Как вы, должно быть, уже поняли, способ запуска сайса (именно так мы будем назвать SoftIce) несколько сложнее, чем всенародно любимого пасьянса "косынка". В Windows 9X для этого нужно было редактировать autoexec.bat и делать мультиконфигурацию, но в NТ с этим дела обстоят немного проще. Всю последовательность действий я привожу из предположения, что у читателя установлена операционная система Windows 2000 (которая, как известно, "build on NT technology"), английская, а сайс берется из пакета DriverStudio версии 2.5. При этом желающие "сходу" произвести полную установку этого пакета должны иметь в виду, что программа инсталляции попросит, помимо всего прочего, указать пути к Driver Development Kit ;).
Итак, после установки (и появления на панеле задач соответствующей ветки меню), первое, что мы должны сделать, это выбрать тип его загрузки. Для этого запускаем программу конфигурирования (Start > NuMega DriverStudio > SoftIce > Settings) и видим следующее окошко:
Помните мою аналогию с блинами от штанги и вделанным в пол штырём, на который они надевались для хранения. Там еще был учитель физкультуры, который приставал к нашим неполовозрелым одноклассницам, в результате чего ему перебили нос. Так вот, адрес самого верхнего блина – это вершина стека и хранится он (адрес) в регистре esp/sp. Другими словами, вершина стека – это адрес последнего занесенного в стек элемента.
Давайте посмотрим на поведение esp при трассировке следующего кода: Main proc push 1 push 2 push 3 pop eax pop ebx pop ecx invoke ExitProcess,0 Main endp
При загрузке мы видим, что регистр esp инициализирован значением 12FFC4 (в других условиях стартовое значение может быть другим, но суть от этого не меняется). Давайте выполним один шаг (команда "t" - trace). В результате этого в стек ляжет 1, а значение регистра esp поменяется на 12FFC0. Трассируем дальше и наблюдаем, как изменяется esp: push 1 ;esp=12FFC0 push 2 ;esp=12FFBC push 3 ;esp=12FFB8 pop eax ;esp=12FFBC pop ebx ;esp=12FFC0 pop ecx ;esp=12FFC4
Протрассировав первые три строчки, мы можем сделать вывод, что стек "растет" в сторону младших адресов (12FFC0 > 12FFBC > 12FFB8, логично?), а шагом изменения регистра esp является 4. И это правильно, так как при 32-битном режиме адресации в стеке сохраняются двойные слова, они же - 4 байта (хотя допускается класть в стек также и 2-байтные слова - при этом шаг равен 2). К слову сказать: если бы мы писали под DOS (точнее, в реальном или виртуальном режиме), то стек у нас адресовался бы регистром sp и изменялся бы он на плюс-минус 2.
Обобщаем. Алгоритм работы команды push <источник> следующий:
- Уменьшение значения указателя стека esp/sp на 4/2.
- Запись значения источника по адресу ss:esp/sp (вершина стека).
Об алгоритме работы команды pop догадайтесь сами…
Для последователей дZена предлагаем тему для исследования: какое значение будет лежать в стеке после инструкции push esp. :)
ПРЕДУПРЕЖДЕНИЕ: ни в коем случае не принимайте результаты отдельных экспериментов в конкретных условиях за абсолютную истину, верную всюду и всегда!
Теперь, после вышесказанного, вы легко сообразите, какое значение примет регистр eax в следующем извращенном случае: push 1 ;esp=12FFC0 push 2 ;esp=12FFBC push 3 ;esp=12FFB8 add esp,4 pop eax ;esp=12FFC0 pop ebx ;esp=12FFC4
[Правильный ответ – 2. :)]
Содержание раздела