MS-DOS и TASM 2.0. Часть 2. Turbo Assembler.
Есть высокоуровневые языки — это те, где вы говорите if — else, print, echo, function и так далее. «Высокий уровень» означает, что вы говорите с компьютером более-менее человеческим языком. Другой человек может не понять, что именно у вас написано в коде, но он хотя бы сможет прочитать слова.
Но сам компьютер не понимает человеческий язык. Компьютер — это регистры памяти, простые логические операции, единицы и нули. Поэтому прежде чем ваша программа будет исполнена процессором, ей нужен переводчик — программа, которая превратит высокоуровневый язык программирования в низкоуровневый машинный код.
Ассемблер — это собирательное название языков низкого уровня: код всё ещё пишет человек, но он уже гораздо ближе к принципам работы компьютера, чем к принципам мышления человека.
Вариантов Ассемблера довольно много. Но так как все они работают по одинаковому принципу и используют (в основном) одинаковый синтаксис, мы будем все подобные языки называть общим словом «Ассемблер».
Как мыслит процессор
Чтобы понять, как работает Ассемблер и почему он работает именно так, нам нужно немного разобраться с внутренним устройством процессора.
Кроме того, что процессор умеет выполнять математические операции, ему нужно где-то хранить промежуточные данные и служебную информацию. Для этого в самом процессоре есть специальные ячейки памяти — их называют регистрами.
Регистры бывают разного вида и назначения: одни служат, чтобы хранить информацию; другие сообщают о состоянии процессора; третьи используются как навигаторы, чтобы процессор знал, куда идти дальше, и так далее. Подробнее — в расхлопе ↓
Какими бывают регистры
Общего назначения. Это 8 регистров, каждый из которых может хранить всего 4 байта информации. Такой регистр можно разделить на 2 или 4 части и работать с ними как с отдельными ячейками.
Указатель команд. В этом регистре хранится только адрес следующей команды, которую должен выполнить процессор. Вручную его изменить нельзя, но можно на него повлиять различными командами переходов и процедур.
Регистр флагов. Флаг — какое-то свойство процессора. Например, если установлен флаг переполнения, значит процессор получил в итоге такое число, которое не помещается в нужную ячейку памяти. Он туда кладёт то, что помещается, и ставит в этот флаг цифру 1. Она — сигнал программисту, что что-то пошло не так.
Флагов в процессоре много, какие-то можно менять вручную, и они будут влиять на вычисления, а какие-то можно просто смотреть и делать выводы. Флаги — как сигнальные лампы на панели приборов в самолёте. Они что-то означают, но только самолёт и пилот знают, что именно.
Сегментные регистры. Нужны были для того, чтобы работать с оперативной памятью и получать доступ к любой ячейке. Сейчас такие регистры имеют по 32 бита, и этого достаточно, чтобы получить 4 гигабайта оперативки. Для программы на Ассемблере этого обычно хватает.
Так вот: всё, с чем работает Ассемблер, — это команды процессора, переменные и регистры.
Здесь нет привычных типов данных — у нас есть только байты памяти, в которых можно хранить что угодно. Даже если вы поместите в ячейку какой-то символ, а потом захотите работать с ним как с числом — у вас получится. А вместо привычных циклов можно просто прыгнуть в нужное место кода.
Ассемблер для Windows.
Предисловие автора Если Вы, дорогой читатель, знакомы с книгой «Assembler: учебный курс» Вашего покорного слуги, то, наверное, обратили внимание, что программированию в операционной системе Windows было посвящено всего две главы. Это немного и может служить лишь введением в данную область. Пришло время заняться этим серьезно. Прежде всего, как и полагается в предисловии, отвечу на возможное замечание: зачем нужен ассемблер в Windows, если есть, например, Си и другие языки. Зачем нужен ассемблер, я уже писал в упомянутой выше книге. Позволю себе процитировать ее: «Зачем нужен язык ассемблера? — спросят меня. Самой простой и убедительный ответ на поставленный вопрос такой — затем, что это язык процессора и, следовательно, он будет нужен до тех пор, пока будут существовать процессоры. Более пространный ответ на данный вопрос содержал бы в себе рассуждение о том, что ассемблер может понадобиться для оптимизации кода программ, написания драйверов, трансляторов, программирования некоторых внешних устройств и т.д. Для себя я, однако, имею и другой ответ: программирование на ассемблере дает ощущение власти над компьютером, а жажда власти — один из сильнейших инстинктов человека». Что касается операционной системы Windows1, то здесь, как ни странно это прозвучит для уха некоторых программистов, программировать на ассемблере гораздо легче, чем в операционной системе MS DOS. В данной книге я берусь доказать, что программировать на ассемблере в Windows ничуть не сложнее чем на Си, и при этом получается компактный, эффективный и быстрый код. Работая с языками высокого уровня, мы теряем определенные алгоритмические навыки. И процесс заходит все дальше. Честное слово, только ради повышения своего профессионального уровня стоит заниматься программированием на ассемблере. Как и предыдущая, эта книга будет содержать только работающие программы с подробным разбором и комментарием. Сейчас существует два основных конкурирующих ассемблера MASM (Macro Assembler) и TASM (Turbo Assembler)2. Для всех программ будет оговорено, как транслировать их с помощью и MASM, и TASM. И еще, в книгу вошел материал, который можно назвать «хакерским». Мы рассмотрим способы и средства анализа и исправления кода программ. Для тех, кто начнет говорить о безнравственности исправления чужих программ, замечу, что «хакеры» все равно существуют, а раз так, то почему бы и не познакомиться с тем, как они работают. Это будет полезно многим программистам. Надо сказать, что в литературе по программированию для Windows 9Х образовалась некоторая брешь — авторы очень быстро перешли от чистого API-программирования 3 к описанию визуальных компонент тех или иных языков. Автору известна лишь одна, да и то переводная, книга по «чистому» программированию для Windows: Герберт Шилдт, Программирование на С и C++ для Windows 954 (см. также [12]). В своей книге я пытаюсь прикрыть эту брешь, рассматривая некоторые малоосвещенные в литературе вопросы: программирование в локальной сети, использование многозадачности, написание VXD-драйверов, обработка файлов и др. Обычно книги по программированию тяготеют к одной из двух крайностей: описание языка программирования, описание средств программирования операционной системы. Мне хотелось удержаться посередине. Данная книга не руководство по языку ассемблера и не руководство по программированию в Windows. Это нечто среднее, можно сказать — симбиоз языка ассемблера и операционной системы Windows. Как я справился с данной задачей — судить Вам, дорогой читатель.
весит 2,18 МБ
Команды Ассемблера
Каждая команда Ассемблера — это команда для процессора. Не операционной системе, не файловой системе, а именно процессору — то есть в самый низкий уровень, до которого может дотянуться программист.
Любая команда на этом языке выглядит так:
[<метка>:] <команда> [<операнды>] [;<комментарий>]
Метка — это имя для фрагмента кода. Например, вы хотите отдельно пометить место, где начинается работа с жёстким диском, чтобы было легче читать код. Ещё метка нужна, чтобы в другом участке программы можно было написать её имя и сразу перепрыгнуть к нужному куску кода.
Команда — служебное слово для процессора, которое он должен выполнить. Специальные компиляторы переводят такие команды в машинный код. Это сделано для того, чтобы не запоминать сами машинные команды, а использовать вместо них какие-то буквенные обозначения, которые проще запомнить. В этом, собственно, и выражается человечность Ассемблера: команды в нём хотя бы отдалённо напоминают человеческие слова.
Операнды отвечают за то, что именно будут делать команды: какие ячейки брать для вычислений, куда помещать результат и что сделать с ним дополнительно. Операндом могут быть названия регистров, ячейки памяти или служебные части команд.
Комментарий — это просто пояснение к коду. Его можно писать на любом языке, и на выполнение программы он не влияет. Примеры команд:
mov eax, ebx ; Пересылаем значение регистра EBX в регистр EAX
mov x, 0 ; Записываем в переменную x значение 0
add eax, х ; Складываем значение регистра ЕАХ и переменной х, результат отправится в регистр ЕАХ
Здесь нет меток, первыми идут команды (mov или add), а за ними — операнды и комментарии.
Пример: возвести число в куб
Если нам понадобится вычислить х³, где х занимает ровно один байт, то на Ассемблере это будет выглядеть так.
Первый вариант
mov al, x ; Пересылаем x в регистр AL
imul al ; Умножаем регистр AL на себя, AX = x * x
movsx bx, x ; Пересылаем x в регистр BX со знаковым расширением
imul bx ; Умножаем AX на BX. Результат разместится в DX:AX
Второй вариант
mov al, x ; Пересылаем x в регистр AL
imul al ; Умножаем регистр AL на себя, AX = x * x
cwde ; Расширяем AX до EAX
movsx ebx, x ; Пересылаем x в регистр EBX со знаковым расширением
imul ebx ; Умножаем EAX на EBX. Поскольку x – 1-байтовая переменная, результат благополучно помещается в EAX
На любом высокоуровневом языке возвести число в куб можно одной строкой. Например:
x = Math.pow(x,3); x := exp(ln(x) * 3); на худой конец x = x*x*x.
Хитрость в том, что когда каждая из этих строк будет сведена к машинному коду, этого кода может быть и 5 команд, и 10, и 50, и даже 100. Чего стоит вызов объекта Math и его метода pow: только на эту служебную операцию (ещё до самого возведения в куб) может уйти несколько сотен и даже тысяч машинных команд.
А на Ассемблере это гарантированно пять команд. Ну, или как реализуете.
Почему это круто
Ассемблер позволяет работать с процессором и памятью напрямую — и делать это очень быстро. Дело в том, что в Ассемблере почти не тратится зря процессорное время. Если процессор работает на частоте 3 гигагерца — а это примерно 3 миллиарда процессорных команд в секунду, — то очень хороший код на Ассемблере будет выполнять примерно 2,5 миллиарда команд в секунду. Для сравнения, JavaScript или Python выполнят в тысячу раз меньше команд за то же время.
Ещё программы на Ассемблере занимают очень мало места в памяти. Именно поэтому на этом языке пишут драйверы, которые встраивают прямо в устройства, или управляющие программы, которые занимают несколько килобайт. Например, программа, которая находится в брелоке сигнализации и управляет безопасностью всей машины, занимает всего пару десятков килобайт. А всё потому, что она написана для конкретного процессора и использует его возможности на сто процентов.
Справедливости ради отметим, что современные компиляторы С++ дают машинный код, близкий по быстродействию к Ассемблеру, но всё равно немного уступают ему.
Скачать TASM можно по нашей ссылке.
Вы можете скачать TASM по ссылке с нашего сайта. Кроме TASM для DOS в архиве вы найдёте все необходимые программы для разработки приложений в операционной системе MS-DOS, которые Вам понадобятся при прохождении нашего курса обучения.
В директории «TASM» имеются необходимые «батники» (ASM-COM.BAT, ASM-EXE.BAT, COMPLEX.BAT) для создания исполняемых файлов *.com, *.exe, сборки кода из нескольких ассемблерных (*.asm) файлов. Для их правильного использования, необходимо, чтобы файлы с кодом на ассемблере имели следующие имена:
- prg.asm (ASM-COM.BAT, ASM-EXE.BAT).
- prg.asm, prg1.asm (COMPLEX.BAT).
Более подробно — в следующей статье, где мы напишем нашу первую программу на ассемблере.
Почему это сложно
Для того, чтобы писать программы на Ассемблере, нужно очень любить кремний:
- понимать архитектуру процессора;
- знать устройство железа, которое работает с этим процессором;
- знать все команды, которые относятся именно к этому типу процессоров;
- уметь работать с данными в побайтовом режиме (забудьте о строках и массивах, ведь ваш максимум — это одна буква);
- понимать, как в ограниченных условиях реализовать нужную функциональность.
Теперь добавьте к этому отсутствие большинства привычных библиотек для работы с чем угодно, сложность чтения текста программы, медленную скорость разработки — и вы получите полное представление о программировании на Ассемблере.
Для чего всё это
Ассемблер незаменим в таких вещах:
- драйверы;
- программирование микроконтроллеров и встраиваемых процессоров;
- куски операционных систем, где важно обеспечить скорость работы;
- антивирусы (и вирусы).
На самом деле на Ассемблере можно даже запилить свой сайт с форумом, если у программиста хватает квалификации. Но чаще всего Ассемблер используют там, где даже скорости и возможностей C++ недостаточно.
Turbo Assembler — удачный пакет разработки DOS программ.
Изучать ассемблер мы начнём со знаменитого TASM. В годы господства MS-DOS Турбо ассемблер от Borland был одним из самых распространённых средств создания программ, составляя конкуренцию MASM. Очень продуманный и приятный ассемблер, создающий хороший код. Скачать TASM можно с нашего сайта — ссылка на архив в конце статьи.
Мы создадим простейшую программу и на её примере рассмотрим основные этапы разработки приложений. Программа после запуска выведет на экран сообщение «Hello World!» и завершится. Вначале не будем пользоваться средой программирования TASMED, а сделаем всё ручками, используя текстовый редактор и Турбо Ассемблер.