Добрый день, уважаемые читатели. Несмотря на некую абсурдность заголовка этой статьи, заниматься сегодня мы будем именно программированием, используя язык Ассемблера и 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-канал. Там еще больше полезного и интересного для программистов.