Push assembler что это

Основы Ассемблера

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

Комментарии в ваших программах оставляются после точки с запятой. Точно также как в дельфи или си через //.

Числа в ассемблере могут представляться в двоичной, десятеричной или шестнадцатеричной системе. Для того, чтобы показать в какой системе использовано число надо поставить после числа букву. Для бинарной системы пишется буква b (пример: 0000010b, 001011010b), для десятеричной системы можно ничего не указывать после числа или указать букву d (примеры: 4589, 2356d), для шестнадцатеричной системы надо указывать букву h, шестнадцатеричное число надо обязательно писать с нулём в начале (примеры: 00889h, 0AC45h, 056Fh, неправильно F145Ch, С123h).

Самая первая команда будет хорошо всем известная MOV. Эта команда используется для копирования (не обращайте внимания на имя команды) значения из одного места в другое. Это ‘место’ может быть регистр, ячейка памяти или непосредственное значение (только как исходное значение). Синтаксис команды:

Вы можете копировать значение из одного регистра в другой.

Вышеприведенная команда копирует содержание ecx в edx. Размер источника и приемника должны быть одинаковыми,

Этот опкод пытается поместить DWORD (32-битное) значение в байт (8 битов). Это не может быть сделано mov командой (для этого есть другие команды).

А эти команды правильные, потому что у них источник и приемник не отличаются по размеру:

Вы также можете получить значение из памяти и поместить эго в регистр. Для примера возьмем следующую схему памяти:

смещение3435363738393A3B3C3D3E3F404142
данные0D0A50324457257A5E72EF7DFFADC7

(Каждый блок представляет байт)

Вернемся к примеру выше. Вы также можете это делать и с другими размерами:

Вы, наверное, уже поняли, что префикс ptr обозначает, что надо брать из памяти некоторый размер. А префикс перед ptr обозначает размер данных:

Иногда размер можно не указывать:

Можно также непосредственные значения:

Эта команда просто запишет в регистр edx, значение 5006. Скобки, [ и ], используются, для получения значения из памяти (в скобках находится смещение), без скобок, это просто непосредственное значение.

Можно также использовать регистр как ячейку памяти (он должен быть 32-разрядным в 32-разрядных программах):

В mov cx, [eax], процессор сначала смотрит, какое значение (= ячейке памяти) содержит eax, затем какое значение находится в той ячейке памяти, и помещает это значение (word, 16 бит, потому что приемник, cx, является 16-разрядным регистром) в CX.

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

Push assembler что это. Смотреть фото Push assembler что это. Смотреть картинку Push assembler что это. Картинка про Push assembler что это. Фото Push assembler что это

(стек здесь заполнен нулями, но в действительности это не так, как здесь). ESP стоит в том месте, на которое он указывает)

Push assembler что это. Смотреть фото Push assembler что это. Смотреть картинку Push assembler что это. Картинка про Push assembler что это. Фото Push assembler что это Push assembler что это. Смотреть фото Push assembler что это. Смотреть картинку Push assembler что это. Картинка про Push assembler что это. Фото Push assembler что это Push assembler что это. Смотреть фото Push assembler что это. Смотреть картинку Push assembler что это. Картинка про Push assembler что это. Фото Push assembler что это

Когда выполняется команда call, процессор передает управление на код с адреса 455659, и выполняет его до команды ret, а затем возвращает управление команде следующей за call. Код который вызывается командой call называется процедурой. Вы можете поместить код, который вы часто используете в процедуру и каждый раз когда он вам нужен вызывать его командой call.

Подробнее: команда call помещает регистр EIP (указатель на следующюю команду, которая должна быть выполнена) в стек, а команда ret извлекает его и передаёт управление этому адресу. Вы также можете определить аргументы, для вызываемой программы (процедуры). Это можно сделать через стек:

Внутри процедуры, аргументы могут быть прочитаны из стека и использованы. Локальные переменные, т.е. данные, которые необходимы только внутри процедуры, также могут быть сохранены в стеке. Я не буду подробно рассказывать об этом, потому, что это может быть легко сделано в ассемблерах MASM и TASM. Просто запомните, что вы можете делать процедуры и что они могут использовать параметры.

Одно важное замечание: регистр eax почти всегда используется для хранения результата процедуры.

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

Вот и кончился очередной урок. На следующем уроке мы напишем первую программу на ассемблере.

Источник

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

Push assembler что это. Смотреть фото Push assembler что это. Смотреть картинку Push assembler что это. Картинка про Push assembler что это. Фото Push assembler что это

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

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

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

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

PUSH (PUSH)

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

push источник.

POP (POP)

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

pop приемник.

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

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

Push assembler что это. Смотреть фото Push assembler что это. Смотреть картинку Push assembler что это. Картинка про Push assembler что это. Фото Push assembler что это push cs Push assembler что это. Смотреть фото Push assembler что это. Смотреть картинку Push assembler что это. Картинка про Push assembler что это. Фото Push assembler что это push bx Push assembler что это. Смотреть фото Push assembler что это. Смотреть картинку Push assembler что это. Картинка про Push assembler что это. Фото Push assembler что это push ax

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

Push assembler что это. Смотреть фото Push assembler что это. Смотреть картинку Push assembler что это. Картинка про Push assembler что это. Фото Push assembler что это pop ax Push assembler что это. Смотреть фото Push assembler что это. Смотреть картинку Push assembler что это. Картинка про Push assembler что это. Фото Push assembler что это pop bx Push assembler что это. Смотреть фото Push assembler что это. Смотреть картинку Push assembler что это. Картинка про Push assembler что это. Фото Push assembler что это 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;

Push assembler что это. Смотреть фото Push assembler что это. Смотреть картинку Push assembler что это. Картинка про Push assembler что это. Фото Push assembler что это Вызываем процедуру командой call

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

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

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

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

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

Источник

Дневники чайника. Чтива 0, виток0

Стек.
Восьмой день

Мы всё время наблюдаем такую форму записи (prax03.com):

Так вот в этой системе отображения стек растёт вверх.

Видите, prax03 в строке 12h сохраняет значение регистра DX, а в строке 07 при следующем проходе цикла обратно восстанавливает это значение. В данном примере стек использовать неудобно, но если в программе нужно много раз сохранять разные регистры, то лучше делать это через стек. Для записи в стек есть команда PUSH.

Причём значений в стек можно укладывать очень много.

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

Матрос! Ну-ка иди сюда. Что тут у тебя в корзинке? О, завтрак! Так, сверху яйца, подержи, бутерброд, бутылка. подушка? А зачем тебе подушка? Ты что, на вахте спать собрался! А ну, отдай сюда корзину. Яйца-то сырые, чёрт тебя дери! И бутерброды измазал, и подушку испортил. Какого шлейфа ты их не сварил? Иди отсюда, сам знаю, что яйца сверху кладут.

Память под стек выделяет Windows.

Теперь осмысленно вернёмся к нашей первой программе под форточки:

Команда Push кладёт в стек указанное в ней значение. Оно может быть два байта или четыре. В 32-битных программах это будет 32-битное значение, то есть dword (4 байта). Так что для нас теперь одно значение в стеке 4 байта.

Это, естественно, целое и для «удобства» чтения, младший байт уже справа, старший слева.

Действительно, стек проще представлять в высоту, чем в одну строку. Вот что будет содержаться в стеке перед выполнением строки 40100Eh в нашей программе.

Давайте удалим из исходника эту строку:

И впишем вместо неё вот такие команды:

Соберите программу ещё раз. Откройте в Olly.

То, что вы делали в отладчике CodeView клавишей F10, называется по-умному:
пошаговая трассировка исполняемого кода без захода в процедуры. В Olly такой «шаг» выполняет клавиша F8.

Когда вы дойдёте до строки 401014h (F8 четыре раза), стек будет выглядеть вот так:

Посмотрите в отладчике, обязательно! Нижняя правая часть.

Вы должны были понять, что каждое новое значение укладывается сверху. Получается, стек растёт вверх. Получается, регистр ESP с каждым новым значением стека уменьшается. И каждый раз, когда из стека извлекают значение, ESP увеличивается на 4 (или на 2 в 16-битных программах).

Стек, пожалуй, самое сложное понятие, которое нужно усвоить для написания программ на Ассемблере. Честно-честно, если вы поймёте это, значит, вы точно сможете освоить всё остальное. Конечно, нужна практика, помню, я три дня потратил на понимание стека. Калашников обманул, сказал, что это просто. Так и было, когда я его примеры использовал, а вот когда свои программки стал писать, стек вызвал наибольшие затруднения.

Следующий пример (prax06.com).

Наберите в Hiew’e эти байты:

Теперь посмотрите на асмовый вид:

Давайте проанализируем этот пример.

Эта строка помещает в регистр AX значение 20CD.

А вот эта строка зацикливает выполнение строки с адресом 03. То есть после выполнения строки 03 в строке 04 будет совершаться безусловный переход к строке 03.

Так почему же этот пример не вешает комп в ДОС и не виснет сам в WinXP? Запустите и вы увидите, что прога выполнится за доли секунды.

Можете попробовать разобрать хитрость в CV сами, но это займёт некоторое время :).

Честно скажу, что отладчик типа CodeView не очень подходит для разбора этого пустячка.

Дело в том, что простые DOS-отладчики используют стек программы в своих целях, что сбивает значение регистра ESP между шагами. Здесь бы лучше всего запустить пример под SoftIce’ом. И тогда вы могли бы «ровно» увидеть, как программа 32637 раз запишет в стек два байта CD20h. Последний раз байты будут вписаны вместо команды «jmp 03».

Получается, что стек всё растёт и растёт вверх и ничего его не остановит, кроме завершающего или ошибочного кода.

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

Ведь изначально программа просто отправляла данные CD20h в стек. Но затем стек вырос (адрес в ESP уменьшился!) до значения, равного адресу команды JMP 0103 (в CodeView этого не видно!). И вот, после следующего выполнения команды push AX, происходит чудесное превращение данных в исполняемый код.

Предлагаю вам ещё один пример Win32.

Учтите, что русские буквы должны быть в кодировке ANSI (стандарт для форточек).

Наберите, пожалуйста, сами секцию кода. Я уверен, что у вас будут опечатки, и это очень ускорит процесс обучения.

Для того, чтоб пример сделал то, что ему положено, запустите его с каким-нибудь ключом типа:
prax07.exe qwerty

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

Источник

Основы Ассемблера

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

Комментарии в ваших программах оставляются после точки с запятой. Точно также как в дельфи или си через //.

Числа в ассемблере могут представляться в двоичной, десятеричной или шестнадцатеричной системе. Для того, чтобы показать в какой системе использовано число надо поставить после числа букву. Для бинарной системы пишется буква b (пример: 0000010b, 001011010b), для десятеричной системы можно ничего не указывать после числа или указать букву d (примеры: 4589, 2356d), для шестнадцатеричной системы надо указывать букву h, шестнадцатеричное число надо обязательно писать с нулём в начале (примеры: 00889h, 0AC45h, 056Fh, неправильно F145Ch, С123h).

Самая первая команда будет хорошо всем известная MOV.

Эта команда используется для копирования (не обращайте внимания на имя команды) значения из одного места в другое. Это ‘место’ может быть регистр, ячейка памяти или непосредственное значение (только как исходное значение). Синтаксис команды:

mov приемник, источник

Вы можете копировать значение из одного регистра в другой.

mov al, ecx ; не правильно

Этот опкод пытается поместить DWORD (32-битное) значение в байт (8 битов). Это не может быть сделано mov командой (для этого есть другие команды).

А эти команды правильные, потому что у них источник и приемник не отличаются по размеру:

mov al, bl
mov cl, dl
mov cx, dx
mov ecx, ebx

Вы также можете получить значение из памяти и поместить эго в регистр. Для примера возьмем следующую схему памяти:

смещение3435363738393A3B3C3D3E3F404142
данные0D0A50324457257A5E72EF7DFFADC7

(Каждый блок представляет байт)

mov eax, dword ptr [0000003Ah]

dword (32-бит) значение 10203040 шестнадцатиричное сохраняется в памяти как: 40, 30, 20, 10

word (16-бит) значение 4050 шестнадцатиричное сохраняется в памяти как: 50, 40

Вернемся к примеру выше. Вы также можете это делать и с другими размерами:

mov cl, byte ptr [34h] ; cl получит значение 0Dh
mov dx, word ptr [3Eh] ; dx получит значение 7DEFh

Вы, наверное, уже поняли, что префикс ptr обозначает, что надо брать из памяти некоторый размер. А префикс перед ptr обозначает размер данных:

Иногда размер можно не указывать:

Можно также непосредственные значения:

Эта команда просто запишет в регистр edx, значение 5006. Скобки, [ и ], используются, для получения значения из памяти (в скобках находится смещение), без скобок, это просто непосредственное значение.

Можно также использовать регистр как ячейку памяти (он должен быть 32-разрядным в 32-разрядных программах):

mov eax, 403045h ; пишет в eax значение 403045
mov cx, [eax] ; помещает в регистр CX значение (размера word) из памяти
указанной в EAX (403045)

В mov cx, [eax], процессор сначала смотрит, какое значение (= ячейке памяти) содержит eax, затем какое значение находится в той ячейке памяти, и помещает это значение (word, 16 бит, потому что приемник, cx, является 16-разрядным регистром) в CX.

(1) mov ecx, 100
(2) mov eax, 200
(3) push ecx ; сохранение ecx
(4) push eax
(5) xor ecx, eax
(6) add ecx, 400
(7) mov edx, ecx
(8) pop ebx
(9) pop ecx

Анализ:
1: поместить 100 в ecx
2: поместить 200 в eax
3: разместить значение из ecx (=100) в стеке (размещается первым)
4: разместить значение из eax (=200) в стеке (размещается последним)
5/6/7: выполнение операций над ecx, значение в ecx изменяется
8: извлечение значения из стека в ebx: ebx станет 200 (последнее размещение, первое извлечение)
9: извлечение значения из стека в ecx: ecx снова станет 100 (первое размещение, последнее извлечение)

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

Смещение1203120412051206120712081209120A120B
Значение000000000000000000
ESP
(стек здесь заполнен нулями, но в действительности это не так, как здесь). ESP стоит в том месте, на которое он указывает)

mov ax, 4560h
push ax

Смещение1203120412051206120712081209120A120B
Значение000060450000000000
ESP

mov cx, FFFFh
push cx

Смещение1203120412051206120712081209120A120B
ЗначениеFFFF60450000000000
ESP

pop edx

Смещение1203120412051206120712081209120A120B
ЗначениеFFFF60450000000000
ESP

edx теперь 4560FFFFh.

..code..
call 0455659
..more code..
Код с адреса 455659:
add eax, 500
mul eax, edx
ret

Когда выполняется команда call, процессор передает управление на код с адреса 455659, и выполняет его до команды ret, а затем возвращает управление команде следующей за call. Код который вызывается командой call называется процедурой. Вы можете поместить код, который вы часто используете в процедуру и каждый раз когда он вам нужен вызывать его командой call.

Подробнее: команда call помещает регистр EIP (указатель на следующюю команду, которая должна быть выполнена) в стек, а команда ret извлекает его и передаёт управление этому адресу. Вы также можете определить аргументы, для вызываемой программы (процедуры). Это можно сделать через стек:

push значение_1
push значение_2
call procedure

Внутри процедуры, аргументы могут быть прочитаны из стека и использованы. Локальные переменные, т.е. данные, которые необходимы только внутри процедуры, также могут быть сохранены в стеке. Я не буду подробно рассказывать об этом, потому, что это может быть легко сделано в ассемблерах MASM и TASM. Просто запомните, что вы можете делать процедуры и что они могут использовать параметры.

Одно важное замечание:
регистр eax почти всегда используется для хранения результата процедуры.

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

Вот и кончился очередной урок. На следующем уроке мы напишем первую программу на ассемблере.

Источник

Руководство по ассемблеру x86 для начинающих

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

В этой статье мы напишем с нуля калькулятор обратной польской записи (RPN) на чистом ассемблере x86. Когда закончим, то сможем использовать его так:

Весь код для статьи здесь. Он обильно закомментирован и может служить учебным материалом для тех, кто уже знает ассемблер.

Начнём с написания базовой программы Hello world! для проверки настроек среды. Затем перейдём к системным вызовам, стеку вызовов, стековым кадрам и соглашению о вызовах x86. Потом для практики напишем некоторые базовые функции на ассемблере x86 — и начнём писать калькулятор RPN.

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

Настройка среды

Как уже сказано, мы используем Linux (64- или 32-битный). Приведённый код не работает в Windows или Mac OS X.

Я бы также рекомендовал держать под рукой таблицу ASCII.

Hello, world!

Для проверки среды сохраните следующий код в файле calc.asm :

Комментарии объясняют общую структуру. Список регистров и общих инструкций можете изучить в «Руководстве по ассемблеру x86 университета Вирджинии». При дальнейшем обсуждении системных вызовов это тем более понадобится.

Следующие команды собирают файл ассемблера в объектный файл, а затем компонует исполняемый файл:

После запуска вы должны увидеть:

Makefile

Затем вместо вышеприведённых инструкций просто запускаем make.

Системные вызовы

Системные вызовы Linux указывают ОС выполнить для нас какие-то действия. В этой статье мы используем только два системных вызова: write() для записи строки в файл или поток (в нашем случае это стандартное устройство вывода и стандартная ошибка) и exit() для выхода из программы:

eaxebxecxedx
Номер системного вызоваarg1arg2arg3

Стек вызовов

Push assembler что это. Смотреть фото Push assembler что это. Смотреть картинку Push assembler что это. Картинка про Push assembler что это. Фото Push assembler что это

Стек вызовов — структура данных, в которой хранится информация о каждом обращении к функции. У каждого вызова собственный раздел в стеке — «фрейм». Он хранит некоторую информацию о текущем вызове: локальные переменные этой функции и адрес возврата (куда программа должна перейти после выполнения функции).

Сразу отмечу одну неочевидную вещь: стек увеличивается вниз по памяти. Когда вы добавляете что-то на верх стека, оно вставляется по адресу памяти ниже, чем предыдущий элемент. Другими словами, по мере роста стека адрес памяти в верхней части стека уменьшается. Чтобы избежать путаницы, я буду всё время напоминать об этом факте.

Соглашение о вызовах для архитектуры x86

В х86 нет встроенного понятия функции как в высокоуровневых языках. Инструкция call — это по сути просто jmp ( goto ) в другой адрес памяти. Чтобы использовать подпрограммы как функции в других языках (которые могут принимать аргументы и возвращать данные обратно), нужно следовать соглашению о вызовах (существует много конвенций, но мы используем CDECL, самое популярное соглашение для x86 среди компиляторов С и программистов на ассемблере). Это также гарантирует, что регистры подпрограммы не перепутаются при вызове другой функции.

Правила вызывающей стороны

Перед вызовом функции вызывающая сторона должна:

Правила вызываемой подпрограммы

Перед вызовом подпрограмма должна:

Push assembler что это. Смотреть фото Push assembler что это. Смотреть картинку Push assembler что это. Картинка про Push assembler что это. Фото Push assembler что это

Стек вызовов после шага 2:

Push assembler что это. Смотреть фото Push assembler что это. Смотреть картинку Push assembler что это. Картинка про Push assembler что это. Фото Push assembler что это

Стек вызовов после шага 4:

Push assembler что это. Смотреть фото Push assembler что это. Смотреть картинку Push assembler что это. Картинка про Push assembler что это. Фото Push assembler что это

На последней диаграмме также можно заметить, что локальные переменные функции всегда начинается на 4 байта выше ebp с адреса ebp-4 (здесь вычитание, потому что мы двигаемся вверх по стеку), а аргументы функции всегда начинается на 8 байт ниже ebp с адреса ebp+8 (сложение, потому что мы двигаемся вниз по стеку). Если следовать правилам из этой конвенции, так будет c переменными и аргументами любой функции.

Когда функция выполнена и вы хотите вернуться, нужно сначала установить eax на возвращаемое значение функции, если это необходимо. Кроме того, нужно:

Вход и выход

Написание некоторых основных функций

Здесь понадобится ещё одна функция _strlen для подсчёта длины строки. На C она может выглядеть так:

Другими словами, с самого начала строки мы добавляем 1 к возвращаемым значением для каждого символа, кроме нуля. Как только замечен нулевой символ, возвращаем накопленное в цикле значение. В ассемблере это тоже довольно просто: можно использовать как базу ранее написанную функцию _subtract :

И посмотрим плоды нашей тяжёлой работы, используя эту функцию в полной программе “Hello, world!”.

Хотите верьте, хотите нет, но мы рассмотрели все основные темы, которые нужны для написания базовых программ на ассемблере x86! Теперь у нас есть весь вводный материал и теория, так что полностью сосредоточимся на коде и применим полученные знания для написания нашего калькулятора RPN. Функции будут намного длиннее и даже станут использовать некоторые локальные переменные. Если хотите сразу увидеть готовую программу, вот она.

Создание стека

Теперь можно реализовать функции _push и _pop :

Вывод чисел

На C программа будет выглядеть примерно так:

Теперь вы понимаете, зачем нам эти три функции. Давайте реализуем это на ассемблере:

Теперь у нас есть все необходимые функции, осталось реализовать основную логику в _start — и на этом всё!

Вычисление обратной польской записи

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

Например, если мы хотим вычислить 84/3+6* (это выражение также можно записать в виде 6384/+* ), процесс выглядит следующим образом:

ШагСимволСтек передСтек после
18[][8]
24[8][8, 4]
3/[8, 4][2]
43[2][2, 3]
5+[2, 3][5]
66[5][5, 6]
7*[5, 6][30]

Если на входе допустимое постфиксное выражение, то в конце вычислений на стеке остаётся лишь один элемент — это и есть ответ, результат вычислений. В нашем случае число равно 30.

В ассемблере нужно реализовать нечто вроде такого кода на C:

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

И мы закончили! Удивите всех своих друзей, если они у вас есть. Надеюсь, теперь вы с большей теплотой отнесётесь к языкам высокого уровня, особенно если вспомнить, что многие старые программы писали полностью или почти полностью на ассемблере, например, оригинальный RollerCoaster Tycoon!

Весь код здесь. Спасибо за чтение! Могу продолжить, если вам интересно.

Дальнейшие действия

Можете попрактиковаться, реализовав несколько дополнительных функций:

Источник

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

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