Язык Ассемблера: программируем на Python

Добрый день, уважаемые читатели. Несмотря на некую абсурдность заголовка этой статьи, заниматься сегодня мы будем именно программированием, используя язык Ассемблера и Python. Пожалуй, начать стоит с того, как началось написание библиотеки PyXAsm.

Подпишись на группу Вконтакте и Телеграм-канал. Там еще больше полезного контента для программистов.
А на YouTube-канале ты найдешь обучающие видео по программированию. Подписывайся!

Что такое язык Ассемблера и где его преподают?

Я студент одного из технических факультетов и в последнем семестре у нас появился такой предмет, как «Архитектура вычислительных систем». Программа этого курса была рассчитана то, чтобы ознакомить нас с представлением чисел в компьютере и основам программирования на языке Ассемблера. Для этого мы использовали Visual Studio, а сам Ассемблер использовали в качестве вставок в код C++.

Я не являюсь разработчиком программного обеспечения, потому никогда этой IDE не пользовался, да и желания особого делать это не было. Поэтому я решил писать код в Visual Studio Code, а код C++ компилировать с помощью g++.exe из GCC (да, я работаю на Windows).

И тут я сразу столкнулся с проблемой. Оказывается, GCC использует в качестве основы синтаксис AT&T, который для меня оказался еще более затруднительным, чем синтаксис Intel. Во-вторых, мой код значительно отличался от кода моих одногруппников. Всё из-за того, что у каждого компилятора свой вид на то, как должны выглядеть вставки языка Ассемблера.

В то же время мне нужно было срочно сделать программу с графическим интерфейсом пользователя, используя Ассемблер и другой любой язык (C или C++). Опыта разработки GUI на этих языках у меня не было, разве что немного использовал Qt.

Что, зачем и почему?

Тут я решил, что хочу написать всё это дело на языке, на котором имею самый большой опыт — Python. Появилась идея совместить Python, C++ и язык Ассемблера под единый капот.

Первая версия использовала такую схему — создавался объект типа Function в Python, и при каждом его вызове с помощью __call__() создавался .cpp файл, компилировался в .exe, исполняемый файл выполнялся, результат записывался в текстовый файл и конвертировался в объект Python.

Сразу видно, что работать это будет очень медленно. Поэтому сейчас PyXAsm (библиотека, речь об использовании которой пойдёт чуть позже) использует следующий алгоритм:

  • создается .cpp файл, в котором прописан ввод с помощью текстового файла и вывод через этот же файл
  • этот source file компилируется в .exe.
  • при каждом вызове объекта Function аргументы записываются в текстовый файл, выполняется исполняемый файл, который записывает результат выполнения в текстовый файл
  • результат читается из текстового файла и преобразовывается в Python object.

PyXAsm

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

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

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

Весь разбор библиотеки будет состоять из трёх примеров:

  • сложение двух чисел
  • сложение одного числа с константой
  • получение определенного кол-ва битов числа

Чтобы использовать самую библиотеку, скачайте её код с репозитория GitHub и запустите setup.py. Планируется выпустить библиотеку в PyPi после того, как она достигнет полноценного функционала. О вещах, которые не реализованы, но скоро будут добавлены, мы поговорим позже.

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

Алгоритм сложения двух чисел довольно прост:

  • загрузим значение первой переменной в регистр
  • загрузим значение второй переменной в другой регистр
  • выполним сложение с помощью инструкции add

На C++ вставка для сложения двух чисел будет выглядеть подобным образом.

Примечание. Используется Intel синтаксис.

int a = 5, b = 4, result;
__asm(
mov eax, a
mov ebx, b
add eax, ebx
mov result, eax
)

Чтобы сделать такое же в PyXAsm, импортируем два класса регистра eax и ebx из asm.registers. Также импортируем класс Variable из asm.variable и Function из asm.function.

Затем импортируем инструкции из asm.instructions и делаем почти то же самое, что мы бы сделали в C++.

from asm.instructions import mov, add
from asm.function import Function
from asm.registers import eax, ebx
from asm.variable import Variable

a = Variable(dtype='int')
b = Variable(dtype='int')
out = Variable(dtype='long int')

f = Function([mov(eax, a),               mov(ebx, b),              
              add(eax, ebx),             
              mov(out, eax)])

f.compile(input_vars=[a, b], output_var=out)

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

И теперь при вызове функции мы с легкостью получаем результат.

print(f(100, 100))
print(f(-1, 1))
print(f(11111111, 111111111))
----------------------------------------------------
200
0
122222222

Прекрасно! Стоит отметить, что результат соответствует с типом, указанным в Variable. То есть результат сразу конвертируется в Python объект. Это очень важное замечание, т. к. эта особенность свойственна и при работе с массивами.

Сложение с константой

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

from asm.instructions import mov, add
from asm.function import Function
from asm.registers import eax, ebx
from asm.variable import Variable

a = Variable(dtype='int')
b = Variable(dtype='int', value=10)

out = Variable(dtype='long int')

f = Function([mov(eax, a),
              mov(ebx, b),
              add(eax, ebx),
              mov(out, eax)])

f.compile(input_vars=[a], local_vars=[b], output_var=out)

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

print(f(1))
print(f(-10))
print(f(111111100))
--------------------------------------
11
0
111111110

Перейдем к последнему примеру.

Получение определенных битов числа

Для разнообразия выполним данную операцию с регистрами ax и bx, размер которых составляет 16 бит (2 байта).

Наша задача: получить значение со второго по 11 бит определённого числа.

Для этого воспользуемся инструкциями shl и shr. Они сдвигают биты влево и вправо соответственно на определенное кол-во бит. В нашем случае нам нужен сдвиг влево на 4, а затем вправо на 6 бит.

Код программы выглядит следующим образом:

from asm.instructions import mov, shl, shr
from asm.function import Function
from asm.registers import eax, ebx
from asm.variable import Variable

a = Variable(dtype='short int')
out = Variable(dtype='short int')

f = Function([mov(eax, a),
              shl(eax, 4),
              shr(eax, 6),
              mov(out, eax)])
f.compile(input_vars=[a], output_var=out)

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

print(f(1))
print(f(356))
-----------------------------------------
0
89

Как по мне, у меня получилось реализовать неплохой программный интерфейс, который прост и понятен. В нём есть что-то то самое «питоновское» и одновременно функционал, сходий на язык Ассемблера

О недостатках

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

Также мне предстоит добавить процедуры, модели памяти, а затем и объявление переменных в самом языке Ассемблера.

Я надеюсь, что вы заинтересуетесь этим проектом и будете следить за ним далее. Вот репозиторий проекта: https://github.com/dmitriyKolmogorov/PyXAsm.

На этом статья заканчивается. Если вы заинтересованы в математическом анализе, советую прочитать «Производная. Базовые определения и термины«.

А также подписывайтесь на группу ВКонтакте, Telegram и YouTube-канал. Там еще больше полезного и интересного для программистов.