Язык ассемблера с нуля

Программирование на Ассемблере для начинающих с примерами программ

Многие считают, что Assembler – уже устаревший и нигде не используемый язык, однако в основном это молодые люди, которые не занимаются профессионально системным программированием. Разработка ПО, конечно, хорошо, но в отличие от высокоуровневых языков программирования, Ассемблер научит глубоко понимать работу компьютера, оптимизировать работку с аппаратными ресурсами, а также программировать любую технику, тем самым развиваясь в направлении машинного обучения. Для понимания этого древнего ЯП, для начала стоит попрактиковаться с простыми программами, которые лучше всего объясняют функционал Ассемблера.

IDE для Assembler

Первый вопрос: в какой среде разработки программировать на Ассемблере? Ответ однозначный – MASM32. Это стандартная программа, которую используют для данного ЯП. Скачать её можно на официальном сайте masm32.com в виде архива, который нужно будет распаковать и после запустить инсталлятор install.exe. Как альтернативу можно использовать FASM, однако для него код будет значительно отличаться.

Перед работой главное не забыть дописать в системную переменную PATH строчку:

Программа «Hello world» на ассемблере

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

Для начала запускаем редактор qeditor.exe в папке с установленной MASM32, и в нём пишем код программы. После сохраняем его в виде файла с расширением «.asm», и билдим программу с помощью пункта меню «Project» → «Build all». Если в коде нет ошибок, программа успешно скомпилируется, и на выходе мы получим готовый exe-файл, который покажет окно Windows с надписью «Hello world».

Сложение двух чисел на assembler

В этом случае мы смотрим, равна ли сумма чисел нулю, или же нет. Если да, то на экране появляется соответствующее сообщение об этом, и, если же нет – появляется иное уведомление.

Здесь мы используем так называемые метки и специальные команды с их использованием (jz, jmp, test). Разберём подробнее:

Программа суммы чисел на ассемблере

Примитивная программа, которая показывает процесс суммирования двух переменных:

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

Получение значения из командной строки на ассемблере

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

Также можно воспользоваться альтернативным методом:

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

Циклы в ассемблере

Для создания цикла используется команда repeat. Далее с помощью inc увеличивается значение переменной на 1, независимо от того, находится она в оперативной памяти, или же в самом процессоре. Для того, чтобы прервать работу цикла, используется директива «.BREAK». Она может как останавливать цикл, так и продолжать его действие после «паузы». Также можно прервать выполнение кода программы и проверить условие repeat и while с помощью директивы «.CONTINUE».

Сумма элементов массива на assembler

Здесь мы суммируем значения переменных в массиве, используя цикл «for»:

С помощью команды jne выполняется переход по метке, основываясь на результате сравнения переменных. Если он отрицательный – происходит переход, а если операнды не равняются друг другу, переход не осуществляется.

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

Источник

Ассемблер для начинающих

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

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

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

Для начала успокою любознательных новобранцев: ассемблер — это совсем не сложно, вопреки стереотипному мнению. Просто он ближе к «земле», то бишь к архитектуре. На самом деле, он очень прост, если ухватить основную идею. В отличие от языков высокого уровня и разнообразных специализированных платформ для программирования (под всем перечисленным я понимаю всякое вроде C++, MatLAB и прочих подобных штук, где требуются программерские навыки), команд тут раз-два и обчелся. По началу даже, когда мне нужно было посчитать двойной интеграл, эта задача вызывала лишь недоумение: как при помощи такого скудного количества операций можно совершить подобную процедуру? Ведь образно говоря, на ассемблере можно разве что складывать, вычитать и сдвигать числа. Но с помощью ассемблера можно совершать сколь угодно сложные операции, а код будет выходить крайне лёгкий. Вот даже для примера, нужно вам зажечь светодиод, который подключен, например, к нулевому контакту порта номер 2, вы просто пишете:
bset P2.0
И, как говорится, никаких проблем. Нужно включить сразу штуки четыре, подключенных последовательно? Да запросто:
mov P2, #000fh
Да, тут я подразумеваю, что начинающий боец уже знаком хотя бы со системами счисления. Ну хотя бы с десятичной. 😉

Итак, для достижения успеха в деле ассемблирования, следует разбираться в архитектуре (в моем случае) микроконтроллера. Это раз.

Кстати, одно из больных мест в познании архитектуры — это организация памяти. Тут на Хабре я видела соответствующую статью: habrahabr.ru/blogs/programming/128991. Еще могу упомянуть ключевые болевые точки: прерывания. Штука не сложная, но по началу (почему-то) тяжелая для восприятия.

Если перед вами стоит сложная задача и вы даже не знаете как по началу к ней подступиться, лучше всего написать алгоритм. Это воистину спасает. А по началу, даже если программа совершенно не сложная, лучше всё же начать с алгоритма, ибо этот процесс помогает разложить всё в голове по местам. Возвращаясь к примеру с вычислением двойного интеграла по экспериментальным данным, обдумывала алгоритм я весь день, но зато потом программку по нему написала всего за 20 минут. Плюс алгоритм будет полезен при дальнейшей модернизации и/или эксплуатации программы, а то ассемблерный код, временами, если и будет понятен построчно, то чтобы разобраться в чем же общая идея, придется немало потрудиться.

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

Собственно, хотелось бы как Фандорин написать: «Это т-т-три»… Но, боюсь, на этом пока можно остановиться. Хотя хотелось бы добавить еще несколько рекомендаций и пряников.

Подводя итог моему несколько сумбурному монологу, ключевые моменты в программировании на ассемблере — это знание архитектуры и связное построение мыслей. Конечно, не обязательно сразу с головой кидаться в штудировании литературы с описанием внутренностей того же PC, но общее представление (повторюсь, хотя бы для начала) будет очень нужно.

А теперь обещанные пряники! Вот я тут распинаюсь о каком-то непонятном ассемблере, а что же в нем, собственно говоря, хорошего? Да много всего! Во-первых, конечно, не нужно запоминать много команд, используемых библиотек и прочей сопутствующей дребедени. Всего парочка команд и, считайте, вы во всеоружии. Во-вторых, в связи с крайней близостью к машинным кодам, вы можете делать практически всё, что душе угодно (в отличие от тех же языков высокого уровня)! В-третьих, ассемблерный код, по причине максимальной лаконичности в формулировках, выполняется крайне быстро.

В общем, сплошные плюсы. На этой оптимистической ноте разрешите откланяться.

Источник

Руководство по ассемблеру 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() для выхода из программы:

eax ebx ecx edx
Номер системного вызова arg1 arg2 arg3

Стек вызовов

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

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

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

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

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

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

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

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

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

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

На последней диаграмме также можно заметить, что локальные переменные функции всегда начинается на 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/+* ), процесс выглядит следующим образом:

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

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

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

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

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

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

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

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

Источник

Поделиться с друзьями
admin
Оцените автора
( Пока оценок нет )
Как переводится?
Adblock
detector