Что такое стек ассемблер

Стек и косвенная адресация

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

Общая информация

Итак, что же такое стек: если коротко, то стек — область памяти на ядре процессора, там хранятся адреса. По своей сути, это абстрактный тип данных. В понимании он довольно прост и следующая иллюстрация отображает принцип работы стека:

Что такое стек ассемблер. Смотреть фото Что такое стек ассемблер. Смотреть картинку Что такое стек ассемблер. Картинка про Что такое стек ассемблер. Фото Что такое стек ассемблер

Принцип работы можно выразить простыми словами: первым пришел — последним вышел, и наоборот: последним пришел — первым вышел. Это полностью описывает работу стека в Assembler.

Стек в Assembler

Для основ, нам необходимо знать 2 новые команды:

Также нам понадобится такая конструкция :
[esp] — это указатель на вершину стека, как раз с ней мы и будем работать.

Приступим к коду, а те, кто не знают где его писать и как скомпилировать — добро пожаловать в предыдущую статью.

Уже знакомые строки, которые необходимо прописывать.
Перейдем к разделу кода:

Сначала в регистры ax и ecx помещаем значения(h означает шестнадцатеричную систему исчисления,и по сути никак не относится к числу). Заметьте, что в регистр ax, содержащий максимум 2 байта, можно поместить только 4 цифры, а в регистр ecx, содержащий 4 байта, только 8.

Затем с помощью команды push, помещаем в стек значения регистров ax и ecx соответственно, то есть сначала мы положили 2 байта, а затем еще 4 байта. Таким образом сейчас на вершине стека лежит то число из 8 знаков.

Далее, с помощью команды pop, вынимаем значения в регистры ax и ecx соответственно. Только заметьте, так как регистр ax, как мы уже сказали, может содержать 4 цифры(или правильнее сказать 2 байта), то в него запишется только 2 байта. Это означает что, то значение из 8 цифр, которое лежит на вершине, будет разделено пополам, а последняя половинка запишется в регистр ax(по принципу последним пришел — первым вышел). То есть в регистр ax запишется 4433h. И после этого в регистр ecx запишется 4 байта, а именно, то что останется: ecx = 22116655h.
Итак, поменяли значения регистра, а теперь изучим косвенную адресацию:

Косвенная адресация

Для понимания нужна полная сосредоточенность: итак, после выполнения команд pop(которая вытаскивает значения, но не удаляет его), вершина стека сместилась относительно наших значений сначала на 2 байта, а затем еще на 4, в общем на 6 байт. Так вот, с помощью косвенной адресации [esp-2] и [esp-6] мы можем обратится как раз к тем значениям: в итоге мы в ecx поместим значение, которое было в самом начале: 66554433h, в регистр ax: 2211h(просто вернули первоначальные значения).

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

На этом мы закончим с изучением темы стек в Assembler, если у вас остались вопросы, то пишите в комментариях.

Источник

Путешествие по Стеку. Часть 1

Что такое стек ассемблер. Смотреть фото Что такое стек ассемблер. Смотреть картинку Что такое стек ассемблер. Картинка про Что такое стек ассемблер. Фото Что такое стек ассемблер

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

Стек имеет такое важное значение, потому что благодаря ему любая функция «знает» куда возвращать управление после завершения; функция же, в свою очередь — это базовый строительный блок программы. Вообще, программы внутренне устроены довольно просто. Программа состоит из функций, функции могут вызывать другие функции, в процессе своей работы любая функция помещает данные в стек и снимает их оттуда. Если нужно, чтобы данные продолжили существовать после завершения функции, то место под них выделяется не в стеке, а в куче. Вышесказанное в равной степени относится как к программам, написанным на относительно низкоуровневом C, так и к интерпретируемым языкам вроде JavaScript и C#. Знание данных вещей обязательно пригодится — и если придется отлаживать программу, и если доведется заниматься тонкой подстройкой производительности, да и просто для того, чтобы понимать, что же там, все-таки творится внутри программы.

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

Сразу бросаются в глаза три процессорных регистра. Указатель стека, esp, предназначается для того, чтобы указывать на верхушку стека. Вплотную к верхуше всегда находится объект, который был добавлен в стек, но еще оттуда не снят. Точно также в реальной жизни обстоят дела со стопкой тарелок или пачкой 100-долларовых банкнот.

Хранимый в регистре esp адрес изменяется по мере того, как объекты добавляются и снимаются со стека, однако он всегда указывает на последний добавленный и еще не снятый со стека объект. Многие процессорные инструкции изменяют значение регистра esp как побочный результат своего выполнения. Реализовать работу со стеком без регистра esp было бы проблематично.

В случае с процессорами Intel, ровно как и со многими другими архитетурами, стек растет в направлении меньших адресов памяти. Поэтому верхушка, в данном случае, соответствует наименьшему адресу в стеке, по которому хранятся валидные используемые данные: в нашем случае это переменная local_buffer. Думаю, должно быть понятно, что означает стрелка от esp к local_buffer. Здесь все, как говорится, по делу – стрелка указывает точно на первый байт, занимаемый local_buffer, и это соответствует тому адресу, который хранится в регистре esp.

Далее на очереди еще один регистр, используемый для отслеживания позиций в стеке – регистр ebpбазовый указатель или указатель базы стекового кадра. Данный регистр предназначен для того, чтобы указывать на позицию в стековом кадре. Благодаря регистру ebp текущая функция всегда имеет своего рода точку отсчёта для доступа к аргументам и локальным переменным. Хранимый в регистре адрес изменяется, когда функция начинает или прекращает выполнение. Мы можем довольно просто адресовать любой объект в стековом кадре как смещение относительно ebp, что и показано на рисунке.

В отличии от esp, манипуляции с регистром ebp осуществляется в основном самой программой, а не процессором. Иногда можно добиться выигрыша в производительности просто отказавшись от страндартного использования регистра ebp – за это отвечают некоторые флаги компилятора. Ядро Linux – пример того, где используется такой прием.

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

Теперь давайте разберем данные, содержащиеся в стековом кадре. Рисунок показывает точное побайтовое содержимое кадра, c направлением роста адресов слево-направо – это то, что мы обычно видим в отладчике. А вот и сам рисунок:

Что такое стек ассемблер. Смотреть фото Что такое стек ассемблер. Смотреть картинку Что такое стек ассемблер. Картинка про Что такое стек ассемблер. Фото Что такое стек ассемблер

Локальная переменная local_buffer – это массив байт, представляющий собой нуль-терминированную ASCII-строку; такие строки — неизменный атрибут всех программ на C. Размер строки — 7 байт, и, скорее всего, она была получена в результате клавиатурного ввода или чтения из файла. В нашем массиве может храниться 8 байт и, следовательно, один байт остается неиспользуемым. Значение этого байта неизвестно. Дело в том, что, данные то и дело добавлются и снимаются со стека, и в этом «бесконечном танце операции добавления и снятия» никогда нельзя знать заранее, что содержит память, пока не осуществишь в нее запись. Компилятор языка C не обременяет себя тем, чтобы иницилизировать стековый кадр нулями. Поэтому содержащиеся там данные заранее неизвестны и являются в некоторой степени случайными. Уж сколько крови попило такое поведение компилятора у программистов!

Идем далее. local1 – 4-байтовое целое число, и на рисунке видно содержимое каждого байта. Кажется, что это большое число – только взгляните на все эти нули после восьмерки, однако здесь наша интуиция сослужила нам дурную службу.

Процессоры Intel используют прямой порядок байтов (дословно «остроконечный»), и это значит, что числа хранятся в памяти начиная с младшего байта. Иными словами, самый младший значащий байт хранится в ячейке памяти с наименьшим адресом. На рисунках и схемах байты многобайтовых чисел традиционно изображаются в порядке слева-направо. В случае с прямым порядком байт, самый младший значащий байт будет изображен в крайней левой позиции, что отличается от привычного нам способа представления и записи чисел.

Неплохо знать о том, что вся эта «остроконечная / тупоконечная» терминология восходит к произведению Джонатана Свифта «Путешествия Гулливера». Подобно тому, как жители Лилипутии чистили яйцо с острого конца, процессоры Intel тоже обрабатывают числа начиная с младшего байта.

Таким образом, переменная local1 в действительности хранит число 8 (да-да, прям как количество щупалец у осьминога). Что касается param1, то там во втором от начала октете изображена двойка, поэтому в результате получаем число 2 * 256 = 512 (мы умножаем на 256, потому что каждый октет – это диапазон от 0 до 255). param2 хранит число 1 * 256 * 256 = 65536.

Служебная информация стекового кадра включает в себя два компонента: адрес стекового кадра вызвавшей функции (на рисунке — saved ebp) и адрес инструкции, куда необходимо передать управление по завершении данной функции (на рисунке – return address). Эта информация делает возможным возвращение управления, и следовательно, дальнейшее выполнение программы как будто никакого вызова и не было.

Теперь давайте рассмотрим процесс «рождения» стекового кадра. Стек растет не в том направлении, которое обычно ожидают увидеть, и сначала это может сбивать с толку. Например, чтобы увеличить стек на 8 байт, программист вычитает 8 из значения, хранимого в регистре esp. Вычитание – странный способ что-либо увеличить. Забавно, не правда ли!

Возьмем для примера простенькую программу на C:

Что такое стек ассемблер. Смотреть фото Что такое стек ассемблер. Смотреть картинку Что такое стек ассемблер. Картинка про Что такое стек ассемблер. Фото Что такое стек ассемблер

Предположим, программу запустили без параметров в командной строке. При выполнении «сишной» программы в Linux, первым делом управление получает код, содержащийся в стандартной библиотеке C. Этот код вызовет функцию main() нашей программы, и, в данном случае, переменная argc будет равна 0 (на самом деле, переменная будет равна «1», что соответствует параметру — названию, под которым запущена программа, но давайте для простоты это момент сейчас опустим). При вызове функции main() происходит следующее:

Что такое стек ассемблер. Смотреть фото Что такое стек ассемблер. Смотреть картинку Что такое стек ассемблер. Картинка про Что такое стек ассемблер. Фото Что такое стек ассемблер

Шаг 2 и 3, а также 4 (описан ниже) соответствуют последовательности инструкций, которая называется «прологом» и встречается практически в любой функции: текущее значение регистра ebp помещается в стек, затем значение регистра esp копируется в регистр ebp, что фактически приводит к созданию нового стекового кадра. Пролог функции main() такой же, как и других функций, с той лишь разницей, что при начале выполнения программы регистр ebp содержит нули.

Если взглянуть на то, что располагается в стеке под argc, то будут видны еще некоторые данные – указатель на строку-название, под которым программа была запущена, указатели на строки-параметры, переданные через командную строку (традиционный C-массив argv), а также указатели на переменные среды и непосредствено сами эти переменные. Однако, на данном этапе нам это не особо важно, так что продолжаем двигаться по направлению к вызову функции add():

Что такое стек ассемблер. Смотреть фото Что такое стек ассемблер. Смотреть картинку Что такое стек ассемблер. Картинка про Что такое стек ассемблер. Фото Что такое стек ассемблер

Функция main() сначала вычетает 12 из текущего значения в регистре esp для выделения нужного ей места и затем присваивает значения переменным a и b. Значения, хранимые в памяти, изображены на рисунке в шестнадцатеричной форме и с прямым порядком байтов – как и в любом отладчике. После присвоения значений, функция main() вызывает функцию add(), и та начинает выполняться:

Что такое стек ассемблер. Смотреть фото Что такое стек ассемблер. Смотреть картинку Что такое стек ассемблер. Картинка про Что такое стек ассемблер. Фото Что такое стек ассемблер

Чем дальше, тем интересней! Перед нами еще один пролог, однако теперь уже четко видно как последовательность стековых кадров в стеке оказывается организованной в связный список, и регистр ebp хранит ссылку на первый элемент этого списка. Вот с опорой на это и реализованы трассировка стека в отладчиках и Exception-объекты высокоуровневых языков. Обратим внимание на типичную для начала выполнения функции ситуацию, когда регистры ebp и esp указывают в одно и то же место. И еще раз вспомним, что для наращивания стека осуществляется вычитание из значения, хранящегося в регистре esp.

Важно заметить следующее — при копировании данных из регистра ebp в память происходит непонятное на первый взгляд изменение порядка хранения байтов. Дело в том, что для регистров такого понятия как «порядок байтов» не существует. Иными словами, рассматривая регистр, мы не можем говорить о том, что в нем есть «старшие или младшие адреса». Поэтому отладчики показывают значения, хранимые в регистрах, в наиболее удобном для человеческого восприятия виде: от более значимых к менее значимым цифрам. Таким образом, имея стандартную нотацию «слева-направо» и «little-endian» машину, создается обманчивое впечатление, что в результате операции копирования из регистра в память байты поменяли порядок на обратный. Я хотел, чтобы картина, показанная на рисунках была максимально приближена к реальности – отсюда и такие рисунки.

Теперь, когда самая сложная часть у нас позади, осуществляем сложение:

Что такое стек ассемблер. Смотреть фото Что такое стек ассемблер. Смотреть картинку Что такое стек ассемблер. Картинка про Что такое стек ассемблер. Фото Что такое стек ассемблер

Здесь у нас появляется неизвестный регистр, чтобы помочь со сложением, но в целом ничего особенного или удивительного. Функция add() выполняет вою работу и, начиная с этого момента все действия в стеке будут осуществляться в обратном порядке. Но об этом расскажем как-нибудь в другой раз.

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

Не так уж все и сложно, стоит только разложить все по полочкам. Кстати, маленькие квадратики очень сильно помогают понимаю. Без «маленьких квадратиков» в информатике вообще никуда. Надеюсь, мои рисунки позволили составить ясную картину происходящего, на которой интуитивно просто показан и рост стека, и изменения содержимого памяти. При ближайшем рассмотрении, наше программное обеспечение не так уж и сильно отличается от простой машины Тьюринга.

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

Источник

Стек в Assembler.

Стеком называется структура данных, организованная по принципу LIFO («Last In — First Out» или «последним пришёл — первым ушёл»). Стек является неотъемлемой частью архитектуры процессора и поддерживается на аппаратном уровне: в процессоре есть специальные регистры (SS, BP, SP) и команды для работы со стеком.

Стек используется для сохранения содержимого регистров, используемых программой, перед вызовом подпрограммы, которая, в свою очередь, будет использовать регистры процессора “в своих личных целях”.

Стек располагается в оперативной памяти в сегменте стека, и поэтому адресуется относительно сегментного регистра SS. Шириной стека называется размер элементов, которые можно помещать в него или извлекать. Для стека существуют две основные операции:

Добавление элемента в стек

Добавление элемента в стек выполняется командой PUSH. У этой команды один операнд, который может быть непосредственным значением, 16-битным регистром (в том числе сегментым) или 16-битной переменной в памяти. Команда работает следующим образом:

Существуют ещё 2 команды для добавления в стек. Команда PUSHF помещает в стек содержимое регистра флагов. Команда PUSHA помещает в стек содержимое всех регистров общего назначения в следующем порядке: АХ, СХ, DX, ВХ, SP, BP, SI, DI. Обе эти команды не имеют операндов.

Извлечение элемента из стека

Извлечение элемента из стека выполняется командой POP. У этой команды также один операнд, который может быть 16-битным регистром (в том числе сегментым, но кроме CS) или 16-битной переменной в памяти. Команда работает следующим образом:

Соответственно, есть ещё 2 команды. POPF помещает значение с вершины стека в регистр флагов. POPA восстанавливает из стека все регистры общего назначения (но при этом значение для SP игнорируется).

Источник

MS-DOS и TASM 2.0. Часть 13. Стек.

Что такое стек ассемблер. Смотреть фото Что такое стек ассемблер. Смотреть картинку Что такое стек ассемблер. Картинка про Что такое стек ассемблер. Фото Что такое стек ассемблер

Стек в ассемблере.

Работа процедур тесно связана со стеком. Стеком называется область программы для временного хранения данных. Стек в ассемблере работает по правилу «Первым зашёл — последним вышел, последним зашёл — первым вышел». В любой период времени в стеке доступен только первый элемент, то есть элемент, загруженный в стек последним. Выгрузка из стека верхнего элемента делает доступным следующий элемент. Это напоминает ящик, в который поочерёдно ложатся книги. Чтобы получить доступ к книге, которую положили первой, необходимо достать поочерёдно все книги, лежащие сверху. Элементы стека располагаются в специально выделенной под стек области памяти, начиная со дна стека по последовательно уменьшающимся адресам. Адрес верхнего доступного элемента хранится в регистре-указателе стека SP. Стек может входить в какой-либо сегмент или быть отдельным сегментом. Сегментный адрес стека помещается в сегментный регистр SS. Пара регистров SS:SP образует адрес доступной ячейки стека.

Работа со стеком: команды PUSH и POP.

Работа со стеком осуществляется с помощью команд PUSH и POP.

PUSH (PUSH)

Затолкать операнд в стек:

push источник.

POP (POP)

Извлечь операнд из стека:

pop приемник.

Примеры для PUSH и POP:

«Заталкиваем» регистры в стэк:

Что такое стек ассемблер. Смотреть фото Что такое стек ассемблер. Смотреть картинку Что такое стек ассемблер. Картинка про Что такое стек ассемблер. Фото Что такое стек ассемблер push cs Что такое стек ассемблер. Смотреть фото Что такое стек ассемблер. Смотреть картинку Что такое стек ассемблер. Картинка про Что такое стек ассемблер. Фото Что такое стек ассемблер push bx Что такое стек ассемблер. Смотреть фото Что такое стек ассемблер. Смотреть картинку Что такое стек ассемблер. Картинка про Что такое стек ассемблер. Фото Что такое стек ассемблер push ax

«Выталкиваем» регистры из стэка в обратной очерёдности:

Что такое стек ассемблер. Смотреть фото Что такое стек ассемблер. Смотреть картинку Что такое стек ассемблер. Картинка про Что такое стек ассемблер. Фото Что такое стек ассемблер pop ax Что такое стек ассемблер. Смотреть фото Что такое стек ассемблер. Смотреть картинку Что такое стек ассемблер. Картинка про Что такое стек ассемблер. Фото Что такое стек ассемблер pop bx Что такое стек ассемблер. Смотреть фото Что такое стек ассемблер. Смотреть картинку Что такое стек ассемблер. Картинка про Что такое стек ассемблер. Фото Что такое стек ассемблер pop cx — ошибка: менять значение cx нельзя, поэтому значение cx помещаем в ax

Стек при работе с процедурой (функцией).

Теперь рассмотрим, как ведёт себя стек в ассемблере при работе с процедурой при использовании call и ret. Прогоним нашего «гоблина» через отладчик. Напомним, что IP — Указатель команд (Index Pointer). На каждом шаге выполнения программы указывает на адрес команды, следующей за исполняемой. Используем горячую клавишу F7 — Trace — пошаговое выполнение программы с заходом в циклы и процедуры.

cs:0113> call 0140;IP = 0113.

Вызываем процедуру коммандой call 0140. В IP будет занесено значение указателя на нашу процедуру и она начнёт выполняться.
cs:0116> jmp 0100;

Что такое стек ассемблер. Смотреть фото Что такое стек ассемблер. Смотреть картинку Что такое стек ассемблер. Картинка про Что такое стек ассемблер. Фото Что такое стек ассемблер Вызываем процедуру командой call

После выхода из процедуры программа продолжит выполнение с IP = 0116. Стек (sp=FFFE): ss:FFFE>0000.

Что такое стек ассемблер. Смотреть фото Что такое стек ассемблер. Смотреть картинку Что такое стек ассемблер. Картинка про Что такое стек ассемблер. Фото Что такое стек ассемблер Возвращаемся из процедуры к адресу, следующему за call в адрес cs:0116

Что интересно, команду ret tasm транслировал в jmp. В данном случае ассемблер самостоятельно использовал оптимизацию кода в сторону ускорения быстродействия. В некоторых случаях такое «самоуправство» не приветствуется (написание «вирусного» кода и др.). Такими «болезнями» болеют все ассемблеры — кто-то больше, кто-то меньше. Знания прийдут вместе с опытом. Можно посоветовать всегда не лениться и пропускать исполняемый файл через отладчик.

Сохранение регистров с помощью стека.

В программе на ассемблере активно используются регистры (ax, cx, bx и т.д.). При входе в процедуру, значения регистров необходимо сохранять. Это очень удобно делать при помощи стека («запушить регистры», используя PUSH). После этого регистры могут активно использоваться в коде функции. Значения их будет меняться, но мы можем легко его восстановить. При выходе из функции значения регистров восстанавливают при помощи команд POP (не забываем о правиле «последний зашёл — первый вышел»).

Источник

23. Принцип организации стека

Стек – это область памяти, специально выделяемая для временного хранения данных программы. Важность стека определяется тем, что для него в структуре программы предусмотрен отдельный сегмент.

Для работы со стеком предназначены три регистра:

Размер стека зависит от режима работы микропроцессора и ограничивается 64 Килобайтами (или 4 Гигабайтами в защищенном режиме). В каждый момент времени доступен только один стек, адрес сегмента которого содержится в регистре SS. Этот стек называется текущим. Для того чтобы обратиться к другому стеку («переключить стек»), необходимо загрузить в регистр SS другой адрес. Регистр SS автоматически используется процессором для выполнения всех команд, работающих со стеком.

    Перечислим еще некоторые особенности работы со стеком:

В общем случае стек организован так:

Регистр ESP/SP всегда указывает на вершину стека, т. е. содержит смещение, по которому в стек был занесен последний элемент. Команды работы со стеком неявно изменяют этот регистр так, чтобы он указывал всегда на последний записанный в стек элемент.

Если стек пуст, то значение ESP равно адресу последнего байта сегмента, выделенного под стек. При занесении элемента в стек процессор уменьшает значение регистра esp, а затем записывает элемент по адресу новой вершины. При извлечении данных из стека процессор копирует элемент, расположенный по адресу вершины, а затем увеличивает значение регистра указателя стека esp. Таким образом, получается, что стек растет вниз, в сторону уменьшения адресов.

Что делать, если нам необходимо получить доступ к элементам не на вершине, а внутри стека? Для этого применяют регистр ЕВР.
Например, типичным приемом при входе в подпрограмму является передача нужных параметров путем записи их в стек. Если подпрограмма тоже активно работает со стеком, то доступ к этим параметрам становится проблематичным. Выход в том, чтобы после записи нужных данных в стек сохранить адрес вершины стека в указателе кадра (базы) стека – регистре ЕВР. Значение в ЕВР в дальнейшем можно использовать для доступа к переданным параметрам.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *