Архив рубрики: Книги по Assembler.

06.07.2019

Для того чтобы машина могла выполнить команды человека на аппаратном уровне, необходимо задать определенную последовательность действий на языке «ноликов и единиц». Помощником в этом деле станет Ассемблер. Это утилита, которая работает с переводом команд на машинный язык. Однако написание программы - весьма трудоемкий и сложный процесс. Данный язык не предназначен для создания легких и простых действий. На данный момент любой используемый язык программирования (Ассемблер работает прекрасно) позволяет написать специальные эффективные задачи, которые сильно влияют на работу аппаратной части. Основным предназначением является создание микрокоманд и небольших кодов. Данный язык дает больше возможностей, чем, например, Паскаль или С.

Краткое описание языков Ассемблера

Все языки программирования разделяются по уровням: низкий и высокий. Любой из синтаксической системы «семейки» Ассемблера отличается тем, что объединяет сразу некоторые достоинства наиболее распространенных и современных языков. С другими их роднит и то, что в полной мере можно использовать систему компьютера.

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

Кратко о структуре языка

Если говорить в общем о работе и структуре функционирования языка, можно точно сказать, что его команды полностью соответствуют командам процессора. То есть Ассемблер использует мнемокоды, наиболее удобные человеку для записи.

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

Для каждой линейки процессора существует своя При таком раскладе правильным будет любой процесс, в том числе и переведенный

Язык Ассемблера имеет несколько синтаксисов, которые будут рассмотрены в статье.

Плюсы языка

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

Драйвера, операционные системы, BIOS, компиляторы, интерпретаторы и т. д. - это все программа на языке Ассемблера.

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

Минусы языка

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

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

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

Команды языка

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


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

Программирование микроконтроллеров на языке (Ассемблер это позволяет и прекрасно справляется с функционированием) самого низкого уровня в большинстве случаев заканчивается удачно. Лучше всего использовать процессоры с ограниченным ресурсом. Для 32-разрядной техники данный язык подходит отлично. Часто в кодах можно заметить директивы. Что же это? И для чего используется?

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


Происхождение названия

Благодаря чему получил название язык - "Ассемблер"? Речь идет о трансляторе и компиляторе, которые и производят зашифровку данных. С английского Assembler означает не что иное, как сборщик. Программа не была собрана вручную, была использована автоматическая структура. Более того, на данный момент уже у пользователей и специалистов стерлась разница между терминами. Часто Ассемблером называют языки программирования, хотя это всего лишь утилита.

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

Макросредства

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

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

В современных операционных система задания по ВССиТ выполнять, мягко говоря проблематично. Для написания контрольных, да и просто освоения начальных навыков необходимо разобраться с компилятором MASM или TASM которые работают из под DOS.
Сами компиляторы, линковщики и все другие приблуды можно найти, например, на http://kalashnikoff.ru/ или http://www.wasm.ru/ . А тут будет рассказываться о том, что ближе к телу.

Необходимые инструменты и их настройка.

И так, что нам нужно. Нам нужен эмулятор DOS-терминала и компилятор. Компилятор я выбрал MASM 6.11, так как им до этого пользовался, и кое что в моей памяти уже было.Как DOS-эмулятор я советую DOSBox. Версии есть и под Windows и под Linux. Под Linux его можно установить из стандартного репозитория. Для Ubuntu:
$sudo apt-get install dosbox или из Центра приложений.
После запуска вы увидите экран с приглашением:
Z:\> Смонтируйте диск C:\ для этого наберите:
Z:\>mount c /home/user/folder_prodject И перейдите в неё:
Z:\>c: командой dir вы сможете посмотреть содержание директории, а командой cd имя_директории перейти в другую папку находящуюся в точке монтирования или в её подпапках.
Пример на моем проекте:


Если вы работаете много в DOS-эмуляторе, то такой способ может оказаться не очень удобным. Но моно настроить автоматическое монтирование диска C:\
Открываем /home/user/.dosbox/dosbox-0.74.conf в редакторе, и ищем строки:
# Lines in this section will be run at startup. # Yju can put your MOUNT lines here. и пишем тут что-то типа:
mount C /home/user/asm PATH=%PATH%;C:\masm611\bin\ C: При старте будет монтироваться C:\ диск в /home/user/asm, при этом эта директория должна существовать. PATH назначает пути поиска программ, это нужно что бы нам не мусорить в одной директории, а была возможность разнести компилятор и проект в разные. В конце стоит C: что бы DOSBox самостоятельно переводил нас на диск C:\ и берег наши телодвижения.


На последнем рисунке показана команда cd и dir, обратите внимание на то как отражаются имена файлов написанных русскими буквами. Возможно это и лечиться, но нужно ли это нам? И не забывайте, что MS-DOS использовал название файлов в формате 8.3. То есть, на имя отводилось 8 символов, потом шла точка (.), а затем расширение файла. При других названиях тоже могут возникнуть проблемы.

Как использовать MASM 6.11.

Теперь разберем как нам компилировать код, который мы напишем.
C:\>ml /с имя_файла.asm получим файл имя_файла.obj Ключ /c говорит компилятору не проводить линковку.
C:\>link имя_файла.obj получим исполняемый файл.
Но можно сделать всё быстрее:
C:\>ml имя_файла.asm /AT При этом мы получим и.obj файл и исполняемый.com. Флаг /AT говорит компилятору вызвать линковщик и передает ему флаг /T который обозначает модель TINY.
Ещё часто бывает нужен файл листинга программы, для этого используйте ключ /Fl:
C:\>ml имя_файла.asm /Fl Файл листинга будет записан в имя_файла.lst

Пишем программку.

Всё, на этом самое сложное закончилось. Теперь начинаем писать программу на ассемблере.
Так как в задачах часто просят вывести решение формул на экран, то и будем разбирать подобный пример. Сумма делений с x= от 1 до 10:

Основные команды которые будем использовать

Регистры, ох уж эти регистры. Краткий экскурс.

Сейчас, что бы начать читать код, достаточно знать регистры общего назначения:
Они могут быть 8 битные (например, al, ah), 16 битные (например, ax) и 32 битные (например, eax).
Все 16 битные регистры делятся на на младший и старший, например, ax делиться на al и ah. Делиться значит состоит, а не математическое действие.
eax, ax, al - очень часто выступают как приёмник значений, например, при умножении и делении.
ebx, bx. bl, bh - свободный регистр, который можно использовать в хвост и гриву.
edx, dx - часто используется для пересылки дополнительной информации или остатка от деления.
ecx, cx - называется счетчик, используется в циклах.
Всё остальное в комментариях программы. Кстати точка с запятой (;) используется как команда начать комментарий, и всё что написано за ней в строке не интерпретируется компилятором

Тренируемся

Первое что нам нужно - это разложить наш пример на простые составляющие, что бы составить алгоритм. К примеру: x в кубе - это x*x*x. Вспоминаем: умножение это mul, то есть мы можем это записать:
mov ax,1 ; присвоили значение mul ax ; умножили первый раз, то есть возвели в квадрат mul ax ; умножили второй раз, то есть возвели в куб Вот, так пошагово это и происходит.

А теперь сам листинг решения примера:

.MODEL tiny ; задаём модель в данном случае.com .CODE .486 ; указываем модель процессора org 100h ; выделяем память start: mov ecx,10 ; задаём количество циклов. 10 потому, что у нас x от 1 до 10 Lb1: ; устанавливаем метку для цикла mov eax,11 ; находим х sub eax,ecx ; x=11-число циклов (1, 2, 3... 11-1=10) push ecx ; сохраним в стеке сх для использования в цикле push eax ; сохраним в стеке ax что бы больше не высчитывать его mul eax ; x^2 (^ - будет означать "в степени") mov ecx,eax ; сохраним eax в ecx потом отнимать нужно будет mov ebx,3 ; ebx = 3 mul ebx ; 3x^2 sub eax,1 ; 3x^2-1 mov ebx,eax ; сохраняем eax в ebx потом понадобиться pop eax ; восстанавливаем eax так как уже нужен eax опять = x push ebx ; и сохраняем ebx так как потом понадобиться mov ebx,eax ; ebx = eax mov eax,ecx ; помните ecx=x^2, теперь eax = ecx, но можно было просто перемножать mul ebx ; eax = x^3 mov ebx,4 ; ebx = 4 mul ebx ; eax = 4x^3 sub eax,ecx ; eax = 4x^3-x^2 add eax,2 ; eax = 4x^3-x^2+2 pop ebx ; восстанавливаем ebx mov a,eax ; делимое (a - это переменная) mov z,ebx ; делитель (z - это тоже переменная) описаны в конце программы finit ; инициализируем сопроцессор fld a ; делимое в сопроцессор fdiv z ; проводим деление в сопроцессоре call tEnter ; это вызов функции которая переводит строку call pFloat ; выводим результат деления в консоль @4: fadd t ; добавляем предыдущий результат деления, для получения суммы; значений функции (t - это переменная) fst t ; запоминаем сумму в переменной pop ecx ; вынимаем ecx из памяти для корректной работы цикла loop Lb1 ; переходим на метку Lb1 пока ecx не станет равным нулю call pEnter ; это вызов функции которая переводит строку call pFloat ; выводим sum((4x^3-x^2+2)/(3x^2-1)) при x от 1 до 10 с шагом 1 int 20h ; Завершение работы программы (это называется; вызвать прерывание 20h) ret ; конец блока; дальше начинается настоящее колдовство;выводит целое число pInt: pushad ; сохраняем все регистры mov bx,sp ; bx = sp mov byte ptr ss:,"$" ; символ конца строки @1:cdq ; метка цикла. и команда cdq копирует знаковый бит регистра eax на; все биты регистра edx div ecx ; делим число на основание edx - остаток, eax - частное add dl,"0" ; преобразование в ASCII dec bx ; уменьшаем bx на 1 mov ss:,dl ; добавляем в стоку перед предыдущим test eax,eax ; проверяем есть ли в eax ещё что-нибудь jne @1 ; если есть повторяем цикл mov ax,ss ; выводим строку на экран mov ds,ax ; ds = ax dec bx ; уменьшаем bx на 1 mov dx,bx ; dx = bx xchg sp,bx ; если не обменять вылезет мусор mov ah,9 ; номер функции прерывания int 21h ; вызываем прерывание xchg sp,bx ; если не обменять программа завершиться popad ; восстанавливаем данные из стека ret ;выводит число pFloat: pushad ; сохраняем все регистры mov ecx,10 ; задаём основание системы счисления push ecx ; сохраняем основание mov bp,sp ; bp = sp fst dword ptr ss: ; выводим из сопроцессора как двойное слово в регистр ss xor eax,eax ; обнуляем eax (xor бинарное или) mov edx,ss: ; edx = ss: shl edx,1 ; сдвигаем в edx все биты на 1 влево mov ecx,edx ; ecx = edx shr ecx,24 ; сдвигаем в ecx все биты в право на 24 sub cx,126 ; вычитаем их cx 126 - Порядок (экспонента) shl edx,7 ; сдвигаем edx влево на 7 or edx,80000000h ; значащие биты (мантисса) shld eax,edx,cl ; eax - целая часть shl edx,cl ; edx - дробная часть pop ecx ; сохраняем ecx call pInt ; выводим на экран целую часть;выводим на экран дробную часть xchg eax,edx ; обмениваемся значениями cdq ; преобразуем двойное слово в четверное sub bp,28 ; вычитаем из bp 28 mov byte ptr ss:,"." ; добавляем разделитель десятичной дроби @2:mul ecx ; умножаем на основание 10 add dl,"0" ; преобразование в ASCII inc bp ; увеличиваем bp на единицу mov ss:,dl ; добавляем в строку test eax,eax ; проверяем есть ли ещё знаки после запятой jne @2 ; да? повторяем цикл mov byte ptr ss:,"$" ; нет? добавляем знак конца строки, иначе выведет мусор sub sp,32 ; приводим строку в нормальное состояние push ss pop ds mov dx,sp ; помещаем в dx откуда её и будем печатать mov ah,9 ; функция прерывания прерывания int 21h ; вызов прерывания call nEnter ; вызов функции перевода строки add sp,32 ; восстанавливаем sp popad ; восстанавливаем регистры ret ;вспомогательная функция - переводит строку nEnter: pushad mov dx,offset str0 mov ah,9 int 21h popad ret ;вспомогательная функция, печатает строку "Temp rezult:" tEnter: pushad mov dx,offset str1 mov ah,9 int 21h popad ret ;вспомогательная функция, печатает строку "Result:" pEnter: pushad mov dx,offset strE mov ah,9 int 21h popad ret ;переменные a dd ? z dd ? t dd 0 str0 db 10,13,"$" str1 db "Temp result: ","$" strE db 10,13,"Result: ","$" end start ; конец программы

Поляков Андрей Валерьевич

http://info-master.su

[email protected]

av-inf.blogspot.ru

В контакте:

vk.com/id185471101

facebook.com/100008480927503

Страница книги:

http://av-assembler.ru/asm/afd/assembler-for-dummy.htm

ВНИМАНИЕ!

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

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

1. РАЗРЕШЕНИЯ

Разрешается использование книги в ознакомительных и образовательных целях (только для личного использования). Разрешается бесплатное распространение книги.

2. ОГРАНИЧЕНИЯ

Запрещается использование книги в коммерческих целях (продажа, размещение на ресурсах с платным доступом и т.п.). Запрещается вносить изменения в текст книги. Запрещается присваивать авторство.

См. также ЛИЦЕНЗИОННОЕ СОГЛАШЕНИЕ .

Поляков А.В.

Ассемблер для чайников

Поляков А.В. Ассемблер для чайников.

ПРЕДИСЛОВИЕ................................................................................................................................................................

ВВЕДЕНИЕ.......................................................................................................................................................................

НЕМНОГО О ПРОЦЕССОРАХ .......................................................................................................................................................

1. БЫСТРЫЙ СТАРТ.........................................................................................................................................................

1.1. ПЕРВАЯ ПРОГРАММА .........................................................................................................................................................

1.1.1. Emu8086................................................................................................................................................................

1.1.2. Debug ..................................................................................................................................................................

1.1.3. MASM, TASM и WASM........................................................................................................................................

1.1.3.1. Ассемблирование в TASM .............................................................................................................................................

1.1.3.2. Ассемблирование в MASM............................................................................................................................................

1.1.3.3. Ассемблирование в WASM............................................................................................................................................

1.1.3.4. Выполнение программы...............................................................................................................................................

1.1.3.5. Использование BAT-файлов..........................................................................................................................................

1.1.4. Шестнадцатеричный редактор....................................................................................................................

Резюме..........................................................................................................................................................................

2. ВВЕДЕНИЕ В АССЕМБЛЕР..........................................................................................................................................

2.1. КАК УСТРОЕН КОМПЬЮТЕР ...............................................................................................................................................

2.1.1. Структура процессора....................................................................................................................................

2.1.2. Регистры процессора.......................................................................................................................................

2.1.3. Цикл выполнения команды..............................................................................................................................

2.1.4. Организация памяти........................................................................................................................................

2.1.5. Реальный режим...............................................................................................................................................

2.1.6. Защищённый режим.........................................................................................................................................

2.2. СИСТЕМЫ СЧИСЛЕНИЯ .....................................................................................................................................................

2.2.1. Двоичная система счисления..........................................................................................................................

2.2.2. Шестнадцатеричная система счисления.....................................................................................................

2.2.3. Другие системы................................................................................................................................................

2.3. ПРЕДСТАВЛЕНИЕ ДАННЫХ В ПАМЯТИ КОМПЬЮТЕРА .............................................................................................................

2.3.1. Положительные числа.....................................................................................................................................

2.3.2. Отрицательные числа.....................................................................................................................................

2.3.3. Что такое переполнение.................................................................................................................................

2.3.4. Регистр флагов.................................................................................................................................................

2.3.5. Коды символов...................................................................................................................................................

2.3.6. Вещественные числа........................................................................................................................................

2.3.6.1. Первая попытка..............................................................................................................................................................

2.3.6.2. Нормализованная запись числа....................................................................................................................................

2.3.6.3. Преобразование дробной части в двоичную форму..................................................................................................

2.3.6.4. Представление вещественных чисел в памяти компьютера......................................................................................

2.3.6.5. Числа с фиксированной точкой.....................................................................................................................................

2.3.6.6. Числа с плавающей точкой............................................................................................................................................

ЛИЦЕНЗИОННОЕ СОГЛАШЕНИЕ...................................................................................................................................

ПРЕДИСЛОВИЕ

Ассемблер – это магическое слово взывает благоговейный трепет у начинающих программистов. Общаясь между собой, они обязательно говорят о том, что где-то у кого-то есть знакомый «чувак», который может читать исходные коды на языке ассемблера как книжный текст. При этом, как правило, язык ассемблера воспринимается как нечто недоступное простым смертным.

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

Есть другая крайность – бывалые программисты на языках высокого уровня убеждены, что язык ассемблера – это пережиток прошлого. Да, средства разработки за последние 20 лет шагнули далеко вперёд. Теперь можно написать простенькую программу вообще не зная ни одного языка программирования. Однако не стоит забывать о таких вещах, как, например, микроконтроллеры. Да и в компьютерном программировании некоторые задачи проще и быстрее решить с помощью языка ассемблера.

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

Книга разбита на несколько разделов. Первый раздел – быстрый старт. Здесь очень кратко описаны основные принципы программирования на языке Ассемблера, сами ассемблеры (компиляторы) и методы работы с ассемблерами. Если вы уверенно себя чувствуете в программировании на высоком уровне, но хотели бы освоить азы низкоуровневого программирования, то, быть может, вам будет достаточно прочитать только этот раздел.

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

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

Основы программирования вообще в этой книге не описаны, поэтому для начинающих настоятельно рекомендую ознакомиться с книгой Как стать программистом , где разъяснены «на пальцах» общие принципы программирования и подробно рассмотрены примеры создания простых программ от программ для компьютеров до программ для станков с ЧПУ.

ВВЕДЕНИЕ

Для начала разберёмся с терминологией.

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

Язык программирования низкого уровня (низкоуровневый язык программирования) – это язык программирования, максимально приближённый к программированию в машинных кодах. В отличие от машинных кодов, в языке низкого уровня каждой команде соответствует не число, а сокращённое название команды (мнемоника). Например, команда ADD – это сокращение от слова ADDITION (сложение). Поэтому использование языка низкого уровня существенно упрощает написание и чтение программ (по сравнению с программированием в машинных кодах). Язык низкого уровня привязан к конкретному процессору. Например, если вы написали программу на языке низкого уровня для процессора PIC, то можете быть уверены, что она не будет работать с процессором AVR.

Язык программирования высокого уровня – это язык программирования, максимально приближённый к человеческому языку (обычно к английскому, но есть языки программирования на национальных языках, например, язык 1С основан на русском языке). Язык высокого уровня практически не привязан ни к конкретному процессору, ни к операционной системе (если не используются специфические директивы).

Язык ассемблера – это низкоуровневый язык программирования, на котором вы пишите свои программы. Для каждого процессора существует свой язык ассемблера.

Ассемблер – это специальная программа, которая преобразует (ассемблирует, то есть собирает) исходные тексты вашей программы, написанной на языке ассемблера, в исполняемый файл (файл с расширением EXE или COM). Если быть точным, то для создания исполняемого файла требуются дополнительные программы, а не только ассемблер. Но об этом позже…

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

В отличие от языков высокого уровня, таких, как Паскаль , Бейсик и т.п., для КАЖДОГО АССЕМБЛЕРА существует СВОЙ ЯЗЫК АССЕМБЛЕРА. Это правило в корне отличает язык ассемблера от языков высокого уровня. Исходные тексты программы (или просто «исходники»), написанной на языке высокого уровня, вы в большинстве случаев можете откомпилировать разными компиляторами для разных процессоров и разных операционных систем. С ассемблерными исходниками это сделать будет намного сложнее. Конечно, эта разница почти не ощутима для разных ассемблеров, которые предназначены для одинаковых процессоров. Но в том то и дело, что для КАЖДОГО ПРОЦЕССОРА существует СВОЙ АССЕМБЛЕР и СВОЙ ЯЗЫК АССЕМБЛЕРА. В этом смысле программировать на языках высокого уровня гораздо проще. Однако за все удовольствия надо платить. В случае с языками высокого уровня мы можем столкнуться с такими вещами как больший размер исполняемого файла, худшее быстродействие и т.п.

В этой книге мы будем говорить только о программировании для компьютеров с процессорами Intel (или совместимыми). Для того чтобы на практике проверить приведённые

в книге примеры, вам потребуются следующие программы (или хотя бы некоторые из них):

1. Emu8086 . Хорошая программа, особенно для новичков. Включает в себя редактор исходного кода и некоторые другие полезные вещи. Работает в Windows, хотя программы пишутся под DOS. К сожалению, программа стоит денег (но оно того стоит))). Подробности см. на сайтеhttp://www.emu8086.com .

2. TASM – Турбо Ассемблер от фирмы Borland. Можно создавать программы как для DOS так и для Windows. Тоже стоит денег и в данный момент уже не поддерживается (да и фирмы Borland уже не существует). А вообще вещь хорошая.

3. MASM – Ассемблер от компании Microsoft (расшифровывается как МАКРО ассемблер, а не Microsoft Assembler, как думают многие непосвящённые). Пожалуй, самый популярный ассемблер для процессоров Intel. Поддерживается до сих пор. Условно бесплатная программа. То есть, если вы будете покупать её отдельно, то она будет стоить денег. Но она доступна бесплатно подписчикам MSDN и входит в пакет программ Visual Studio от Microsoft.

4. WASM – ассемблер от компании Watcom. Как и все другие, обладает преимуществами и недостатками.

5. Debug - обладает скромными возможностями, но имеет большой плюс - входит в стандартный набор Windows. Поищите ее в папке WINDOWS\COMMAND или WINDOWS\SYSTEM32. Если не найдете, тогда в других папках каталога WINDOWS.

6. Желательно также иметь какой-нибудь шестнадцатеричный редактор . Не помешает и досовский файловый менеджер, например Волков Коммандер (VC) или Нортон Коммандер (NC). С их помощью можно также посмотреть шестнадцатеричные коды файла, но редактировать нельзя. Бесплатных шестнадцатеричных редакторов в Интернете довольно много. Вот один из них:McAfee FileInsight v2.1 . Этот же редактор можно использовать для работы с исходными текстами программ. Однако мне больше нравится делать это с помощью следующего редактора:

7. Текстовый редактор . Необходим для написания исходных текстов ваших программ. Могу порекомендовать бесплатный редакторPSPad , который поддерживает множество языков программирования, в том числе и язык Ассемблера.

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

И еще – исходный код, написанный, например для Emu8086, будет немного отличаться от кода, написанного, например, для TASM. Эти отличия будут оговорены.

Большая часть программ, приведённых в книге, написана для MASM. Во-первых, потому что этот ассемблер наиболее популярен и до сих пор поддерживается. Во-вторых, потому что он поставляется с MSDN и с пакетом программ Visual Studio от Microsoft. Ну и в третьих, потому что я являюсь счастливым обладателем лицензионной копии MASM.

Если же у вас уже есть какой-либо ассемблер, не вошедший в перечисленный выше список, то вам придётся самостоятельно разобраться с его синтаксисом и почитать руководство пользователя, чтобы научиться правильно с ним работать. Но общие рекомендации, приведённые в данной книге, будут справедливы для любых (ну или почти для любых) ассемблеров.

Немного о процессорах

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

Процессоров существует довольно много даже в мире компьютеров. Но кроме компьютеров ещё есть телевизоры, стиральные машины, кондиционеры, системы управления двигателями внутреннего сгорания и т.п., где также очень широко используются процессоры (микропроцессоры, микроконтроллеры).

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

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

Что такое команда с точки зрения процессора? Это просто число. Однако современные процессоры могут иметь несколько сотен команд. Запомнить все их будет сложно. Как же тогда писать программы? Для упрощения работы программиста был придуман язык Ассемблера , где каждой команде соответствует мнемонический код. Например, число4 соответствует мнемоникеADD . Иногда язык ассемблера ещё называют языком мнемонических команд.

1. БЫСТРЫЙ СТАРТ

1.1. Первая программа

Обычно в качестве первого примера приводят программу, которая выводит на экран строку «Hello World!». Однако для человека, который только начал изучать Ассемблер, такая программа будет слишком сложной (вы будете смеяться, но это действительно так – особенно в условиях отсутствия доходчивой информации). Поэтому наша первая программа будет еще проще – мы выведем на экран только один символ – английскую букву «A». И вообще – если вы уж решили стать программистом – срочно установите по умолчанию английскую раскладку клавиатуры. Тем более что некоторые ассемблеры и компиляторы не воспринимают русские буквы. Итак, наша первая программа будет выводить на экран английскую букву «А». Далее мы рассмотрим создание такой программы с использованием различных ассемблеров.

Если вы скачали и установили эмулятор процессора 8086 (см. раздел «ВВЕДЕНИЕ »), то вы можете использовать его для создания ваших первых программ на языке ассемблера. На текущий момент (ноябрь 2011 г) доступна версия программы 4.08. Справку на русском языке вы можете найти здесь:http://www.avprog.narod.ru/progs/emu8086/help.html .

Программа Emu8086 платная. Однако в течение 30 дней вы можете использовать её для ознакомления бесплатно.

Итак, вы скачали и установили программу Emu8086 на свой компьютер. Запускаем её и создаём новый файл через меню FILE – NEW – COM TEMPLATE (Файл – Новый – Шаблон файла COM). В редакторе исходного кода после этого мы увидим следующее:

Рис. 1.1. Создание нового файла в Emu8086.

Здесь надо отметить, что программы, создаваемые с помощью Ассемблеров для компьютеров под управлением Windows, бывают двух типов: COM и EXE. Отличия между этими файлами мы рассмотрим позже, а пока вам достаточно знать, что на первое время мы будем создавать исполняемые файлы с расширением COM, так как они более простые.

После создания файла в Emu8086 описанным выше способом в редакторе исходного кода вы увидите строку «add your code hear» - «добавьте ваш код здесь» (рис. 1.1). Эту строку мы удаляем и вставляем вместо неё следующий текст:

Таким образом, полный текст программы будет выглядеть так:

Кроме этого в верхней части ещё имеются комментарии (на рис. 1.1 – это текст зелёного цвета). Комментарий в языке Ассемблера начинается с символа ; (точка с запятой) и продолжается до конца строки. Если вы не знаете, что такое комментарии и зачем они нужны, см. книгуКак стать программистом . Как я уже говорил, здесь мы не будем растолковать азы программирования, так как книга, которую вы сейчас читаете, рассчитана на людей, знакомых с основами программирования.

Также отметим, что регистр символов в языке ассемблера роли не играет. Вы можете написать RET ,ret илиRet – это будет одна и та же команда.

Вы можете сохранить этот файл куда-нибудь на диск. Но можете и не сохранять. Чтобы выполнить программу, нажмите кнопку EMULATE (с зелёным треугольником) или клавишу F5. Откроется два окна: окно эмулятора и окно исходного кода (рис. 1.2).

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

В окне эмулятора вы можете запустить вашу программу на выполнение целиком (кнопка RUN) либо в пошаговом режиме (кнопка SINGLE STEP). Пошаговый режим удобен для отладки. Ну а мы сейчас запустим программу на выполнение кнопкой RUN. После этого (если вы не сделали ошибок в тексте программы) вы увидите сообщение о завершении программы (рис. 1.3). Здесь вам сообщают о том, что программа передала управление операционной системе, то есть программа была успешно завершена. Нажмите кнопку ОК в этом окне и вы увидите, наконец, результат работы вашей первой программы на языке ассемблера (рис.

Рис. 1.2. Окно эмулятора Emu8086.

Рис. 1.3. Сообщение о завершении программы.

Рис. 1.4. Ваша первая программа выполнена.

Оригинал: Get started in assembly language. Part 1
Автор: Mike Saunders
Дата публикации: 30 октября 2015 г.
Перевод: А.Панин
Дата перевода: 10 ноября 2015 г.

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

Для чего это нужно?

  • Для понимания принципов работы компиляторов.
  • Для понимания инструкций центрального процессора.
  • Для оптимизации вашего кода в плане производительности.

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

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

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

Ваша первая программа на языке ассемблера

Многие руководства по программированию на языке ассемблера начинаются с длинных, запутанных и утомительных разделов, в которых осуществляется бесконечное рассмотрение вопросов бинарной арифметики и теории проектирования центральных процессоров, причем эти разделы не содержат какого-либо реального кода. Я считаю, что подобные материалы сводят на нет интерес читателя, поэтому мы начнем непосредственно с рассмотрения кода реальной программы. После этого мы рассмотрим каждую из строк кода этой программы для того, чтобы вы поняли принцип работы ассемблера на основе практического примера.

Некоторые текстовые редакторы, такие, как Vim, осуществляют подсветку синтаксиса языка ассемблера (попробуйте использовать команду set syn=nasm )

Скопируйте следующий код в в текстовое поле любого текстового редактора и сохраните его в файле с именем myfirst.asm в вашей домашней директории:

Section .text global _start _start: mov ecx, message mov edx, length mov ebx, 1 mov eax, 4 int 0x80 mov eax, 1 int 0x80 section .data message db "Assembly rules!", 10 length equ $ - message

(Примечание: для отступов в коде вы можете использовать как как символы пробелов, так и символы табуляции - это не имеет значения.) Данная программа просто выводит строку "Assembly rules!" на экран и завершает работу.

Инструмент, который мы будем использовать для преобразования данного кода языка ассемблера в исполняемый бинарный файл носит довольно забавное название "ассемблер". Существует много различных ассемблеров, но моим любимым ассемблером является NASM; он находится в репозитории пакетов программного обеспечения практически любого дистрибутива, поэтому вы можете установить его с помощью менеджера пакетов программного обеспечения с графическим интерфейсом, команды yum install nasm , apt-get install nasm или любой другой команды, актуальной для вашего дистрибутива.

Теперь откройте окно эмулятора терминала и введите следующие команды:

Nasm -f elf -o myfirst.o myfirst.asm ld -m elf_i386 -o myfirst myfirst.o

Первая команда предназначена для генерации с помощью NASM (исполняемого) файла объектного кода с именем myfirst.o формата ELF (формат исполняемых файлов, используемый в Linux). Вы можете спросить: "Для чего генерируется файл объектного кода, ведь логичнее сгенерировать файл с инструкциями центрального процессора, которые он должен исполнять?" Ну, вы могли бы использовать исполняемый файл с инструкциями центрального процессора в операционных системах 80-х годов, но современные операционные системы предъявляют больше требований к исполняемым файлам. Бинарные файлы формата ELF включают информацию для отладки, они позволяют разделить код и данные благодаря наличию отдельных секций, что позволяет предотвратить переписывание данных в этих секциях.

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

Взгляд в прошлое

На данный момент в нашем распоряжении имеется файл myfirst.o с исполняемым кодом нашей программы. При этом процесс сборки программы еще не завершен; с помощью линковщика ld мы должны связать код из этого файла со специальным системным кодом запуска программ (т.е., шаблонным кодом, который исполняется при запуске каждой программы) для генерации исполняемого файла с именем myfirst . (Параметр elf_i386 описывает тип бинарного формата - в данном случае это означает, что вы можете использовать 32-битный ассемблерный код даже если вы используете 64-битный дистрибутив.)

Если процесс сборки программы пройдет успешно, вы сможете выполнить вашу программу с помощью следующей команды:

В результате вы должны увидеть вывод: "Assembly rules!". Это означает, что вы добились своего - создали полноценную независимую программу для Linux, код которой написан полностью на языке ассемблера. Разумеется, данная программа не выполняет каких-либо полезных действий, но при этом она является отличным примером, демонстрирующим структуру программы на языке ассемблера и позволяющим проследить процесс преобразования исходного кода в бинарный файл.

Перед тем, как мы перейдем к углубленному изучению кода, было бы неплохо узнать размер бинарного файла нашей программы. После выполнения команды ls -l myfirst вы увидите, что размер бинарного файла равен примерно 670 байтам. Теперь оценим размер эквивалентной программы на языке C:

#include int main() { puts("Assembly rules!"); }

Если вы сохраните этот код в файле с именем test.c , скомпилируете его (gcc -o test test.c ) и рассмотрите параметры результирующего бинарного файла с именем test , вы обнаружите, что этот файл имеет гораздо больший размер - 8.4k. Вы можете удалить из этого файла отладочную информацию (strip -s test ), но и после этого его размер сократится незначительно, лишь до 6 k. Это объясняется тем, что компилятор GCC добавляет большой объем упомянутого выше кода для запуска и завершения работы приложения, а также связывает приложение с библиотекой языка программирования C большого размера. Благодаря данному примеру несложно сделать вывод о том, что язык ассемблера является лучшим языком программирования для разработки приложений, предназначенных для эксплуатации в условиях жесткого ограничения объема носителя данных.

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

Дизассемблирование кода

Разработка нового кода является увлекательным занятием, но еще более интересным занятием может оказаться исследования чужой работы. Благодаря инструменту под названием objdump (из пакета Binutils) вы можете "дизассемблировать" исполняемый файл, а именно, преобразовать инструкции центрального процессора в их текстовые эквиваленты. Попытайтесь использовать данный инструмент по отношению к бинарному файлу myfirst, над которым мы работали в данном руководстве, следующим образом:

Objdump -d -M intel myfirst

Вы увидите список инструкций из секции кода бинарного файла. Например, первая инструкция, с помощью которой мы поместили информацию о расположении нашей строки в регистр ecx, выглядит следующим образом:

Mov ecx,0x80490a0

В процессе ассемблирования NASM заменил метку строки "message" на числовое значение, соответствующее расположению этой строки в секции данных бинарного файла. Таким образом, результаты дизассемблирования бинарных файлов менее полезны, чем их оригинальный код, ведь в них отсутствуют такие вещи, как комментарии и строки, но они все же могут оказаться полезными для ознакомления с реализациями критичных к времени исполнения функций или взлома систем защиты приложений. Например, в 80-х и 90-х годах многие разработчики использовали инструменты для дизассемблирования программ с целью идентификации и нейтрализации систем защиты от копирования игр.

Вы также можете дизассемблировать программы, разработанные с использованием других языков программирования, но полученные при этом результаты дизассемблирования могут быть значительно усложнены. Например, вы можете выполнить приведенную выше команду objdump по отношению к бинарному файлу /bin/ls и самостоятельно оценить тысячи строк из секции кода, сгенерированные компилятором на основе оригинального исходного кода утилиты на языке C.

Анализ кода

А теперь давайте обсудим назначение каждой из строк кода нашей программы. Начнем с этих двух строк:

Section .text global _start

Это не инструкции центрального процессора, а директивы ассемблера NASM ; первая директива сообщает о том, что приведенный ниже код должен быть расположен в секции кода "text" финального исполняемого файла. Немного неочевидным является тот факт, что секция с названием "text" содержит не обычные текстовые данные (такие, как наша строка "Assembly rules!"), а исполняемый код, т.е., инструкции центрального процессора. Далее расположена директива global _start , сообщающая линковщику ld о том, с какой точки должно начаться исполнение кода из нашего файла. Эта директива может оказаться особенно полезной в том случае, если мы захотим начинать исполнение кода не с самого начала секции кода, а из какой-либо заданной точки. Параметр global позволяет читать данную директиву не только ассемблеру, но и другим инструментам, поэтому она обрабатывается линковщиком ld .

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

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

Mov ecx, message

Язык ассемблера является по своей сути набором мнемоник для инструкций центрального процессора (или машинного кода). В данном случае mov является одной из таких инструкций - она также может быть записана в понятном центральному процессору бинарном формате, как 10001011. Но работа с бинарными данными может превратиться в кошмар для нас, обычных людей, поэтому мы будем использовать эти более читаемые варианты. Ассемблер просто преобразует текстовые инструкции в их бинарные эквиваленты - хотя он и может выполнять дополнительную работу, о которой мы поговорим в следующих статьях серии.

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

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

Это 32-х битный регистр (следовательно, он может хранить числа из диапазона от 0 до 4,294,967,295). При рассмотрении следующих строк кода вы увидите, что мы также работаем с регистрами edx , ebx и eax - это регистры общего назначения, которые могут использоваться для выполнения любых задач, в отличие от специализированных регистров, с которыми мы познакомимся в следующем месяце. А это небольшое пояснение для тех, кому не терпится узнать о происхождении имен регистров: регистр ecx носил имя c во время выпуска 8-ми битных процессоров, после чего был переименован в сх для хранения 16-и битных значений и в ecx для хранения 32-х битных значений. Таким образом, несмотря на то, что имена регистров в настоящее время выглядят немного странно, во времена выпуска старых центральных процессоров разработчики использовали регистры общего назначения с отличными именами a , b , c и d .

После того, как вы начнете работу, вы не сможете остановиться

Одним из вопросов, которые мы будем рассматривать в следующем месяце, является вопрос использования стека, поэтому мы подготовим вас к его рассмотрению прямо сейчас. Стек является областью памяти, в которой могут храниться временные значения тогда, когда необходимо освободить регистры для других целей. Но наиболее важной возможностью стека является способ хранения данных в нем: вы будете "помещать" ("push") значения в стек и "извлекать" ("pop") их из него. В стеке используется принцип LIFO (last in, first out - первый вошел, последний вышел), следовательно, последнее добавленное в стек значение будет первым извлечено из него.

Представьте, что у вас есть, к примеру, пустая упаковка от чипсов Pringles и вы помещаете в нее вещи в следующей последовательности: двухслойный крекер, фишка с персонажем "Альф" и диск от приставки GameCube. Если вы начнете извлекать эти вещи, вы извлечете диск от приставки GameCube первым, затем фишку с персонажем "Альф" и так далее. При работе с языком ассембера стек используется следующим образом:

Push 2 push 5 push 10 pop eax pop ebx pop ecx

После исполнения этих шести инструкций регистр eax будет содержать значение 10, регистр ebx - значение 5 и регистр ecx - значение 2. Таким образом, использование стека является отличным способом временного освобождения регистров; если, к примеру, в регистрах eax и ebx имеются важные значения, но вам необходимо выполнить текущую работу перед их обработкой, вы можете поместить эти значения в стек, выполнить текущую работу и извлечь их из стека, вернувшись к предыдущему состоянию регистров.

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

Двигаемся дальше

Вернемся к коду: инструкция mov перемещает (на самом деле, копирует) число из одного места в другое, справа налево. Таким образом, в данном случае мы говорим: "следует поместить message в регистр ecx ". Но что такое "message"? Это не другой регистр, это указатель на расположение данных. Ближе концу кода в секции данных "data" вы можете обнаружить метку message , после которой следует параметр db , указывающий на то, что вместо метки message в коде должно быть размещено несколько байт. Это очень удобно, так как нам не придется выяснять точное расположение строки "Assembly rules!" в секции данных - мы можем просто сослаться на нее с помощью метки message . (Число 10 после нашей строки является всего лишь символом перехода на новую строку, аналогичным символу \n , добавляемому к строкам при работе с языком программирования C).

Таким образом, мы поместили данные о расположении строки в регистр ecx . Но то, что мы сделаем дальше является особенно интересным. Как упоминалось ранее, центральный процессор не имеет какой-либо реальной концепции аппаратных устройств - для вывода чего-либо на экран вам придется отправить данные видеокарте или переместить данные в оперативную память видеокарты. Но мы не имеем какой-либо информации о расположении этой оперативной памяти видеокарты, кроме того, все используют различные видеокарты, параметры сервера оконной системы X, оконные менеджеры и.т.д. Исходя из этого, непосредственный вывод чего-либо на экран с помощью небольшой по объему программы в нашем случае практически невозможен.

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

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

Length equ $ - message

В данной строке используется другая метка length , но вместо параметра db для связывания этой метки с какими-либо данными, мы используем параметр equ для того, чтобы сообщить, что данная метка является эквивалентом чего-либо (это немного похоже на директиву препроцессора #define в языке программирования C). Символ доллара соответствует текущей позиции в коде, поэтому в данном случае мы говорим: "метка length должна быть эквивалентна текущей позиции в коде за вычетом расположения строки с меткой "message"".

Вернемся к секции кода приложения, в которой мы размещаем данное значение в регистре edx :

Mov edx, length

Все идет отлично: два регистра заполнены информацией о расположении строки и количестве символов строки для вывода. Но перед тем, как мы сообщим ядру ОС о необходимости выполнения его части работы, нам придется предоставить ему еще немного информации. Во-первых, мы должны сообщить ядру ОС о том, какой "дескриптор файла" следует использовать - другими словами, куда должен быть направлен вывод. Данная тема выходит за границы руководства по использованию языка ассемблера, поэтому скажем лишь, что нам нужно использовать стандартный поток вывода stdout , что означает: выводить строку на экран. Стандартный поток вывода использует фиксированный дескриптор 1, который мы помещаем в регистр ebx .

Теперь мы крайне близки к осуществлению системного вызова, но остался еще один регистр, который должен быть заполнен. Ядро ОС может выполнять большое количество различных операций, таких, как монтирование файловых систем, чтение данных из файлов, удаление файлов и других. Соответствующие механизмы активируются с помощью упомянутых системных вызовов и перед тем, как мы передадим управление ядру ОС, нам придется сообщить ему, какой из системных вызовов следует использовать. На странице вы можете ознакомиться с информацией о некоторых системных вызовах, доступных программам - в нашем случае необходим системный вызов sys_write ("запись данных в дескриптор файла") с номером 4. Поэтому мы разместим его номер в регистре eax :

И это все! Мы выполнили все необходимые приготовления для осуществления системного вызова, поэтому сейчас мы просто передадим управление ядру ОС следующим образом:

Инструкция int расшифровывается как "interrrupt" ("прерывание") и буквально прерывает поток исполнения данной программы, переходя в пространство ядра ОС. (В данном случае используется шестнадцатеричное значение 0x80 - пока вам не следует беспокоиться о нем.) Ядро ОС осуществит вывод строки, на которую указывает значение в регистре ecx , после чего вернет управление нашей программе.

Для завершения исполнения программы следует осуществить системный вызов sys_exit , который имеет номер 1. Поэтому мы размещаем данный номер в регистре eax , снова прерываем исполнение нашей программы, после чего ядро ОС аккуратно завершает исполнение нашей программы и мы возвращаемся к приветствию командной оболочки. Можно сказать, что вы выполнили поставленную задачу: реализовали завершенную (хотя и очень простую) программу на языке ассемблера, код которой разработан вручную без использования каких-либо объемных библиотек.

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

В процессе ознакомления с кодом данной программы вы можете попытаться самостоятельно модифицировать его для выполнения следующих операций:

  • Вывода отличной, более длинной строки.
  • Вывода двух строк, одна после другой.
  • Возврата измененного кода завершения работы приложения командной оболочке (для этого придется воспользоваться поисковой системой Google!).

Если вы столкнулись с трудностями и нуждаетесь в помощи, заходите на наш форум по адресу http://forums.linuxvoice.com - автор руководства будет рядом и с удовольствием направит вас по правильному пути. Удачного программирования!

В статье будут рассмотрены основы языка ассемблер применительно к архитектуре win32. Он представляет собой символическую запись машинных кодов. В любой электронно-вычислительной машине самым низким уровнем является аппаратный. Здесь управление процессами происходит командами или инструкциями на машинном языке. Именно в этой области ассемблеру предназначено работать.

Программирование на ассемблер

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

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

Регистры

Регистрами в языке ассемблер называют ячейки памяти, расположенные непосредственно на кристалле с АЛУ (процессор). Особенностью этого типа памяти является скорость обращения к ней, которая значительно быстрее оперативной памяти ЭВМ. Она также называется сверхбыстрой оперативной памятью (СОЗУ или SRAM).

Существуют следующие виды регистров:

  1. Регистры общего назначения (РОН).
  2. Флаги.
  3. Указатель команд.
  4. Регистры сегментов.

Есть 8 регистров общего назначения, каждый размером в 32 бита.

Доступ к регистрам EAX, ECX, EDX, EBX может осуществляться в 32-битовом режиме, 16-битовом - AX, BX, CX, DX, а также 8-битовом - AH и AL, BH и BL и т. д.

Буква "E" в названиях регистров означает Extended (расширенный). Сами имена же связаны с их названиями на английском:

  • Accumulator register (AX) - для арифметических операций.
  • Counter register (CX) - для сдвигов и циклов.
  • Data register (DX) - для арифметических операций и операций ввода/вывода.
  • Base register (BX) - для указателя на данные.
  • Stack Pointer register (SP) - для указателя вершины стека.
  • Stack Base Pointer register (BP) - для индикатора основания стека.
  • Source Index register (SI) - для указателя отправителя (источника).
  • Destination Index register (DI) - для получателя.

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

Регистр флагов. Под этим подразумевается байт, который может принимать значения 0 и 1. Совокупность всех флагов (их порядка 30) показывают состояние процессора. Примеры флагов: Carry Flag (CF) - Флаг переноса, Overflow Flag (OF) - переполнения, Nested Flag (NT) - флаг вложенности задач и многие другие. Флаги делятся на 3 группы: состояние, управление и системные.


Указатель команд (EIP - Instruction Pointer). Данный регистр содержит адрес инструкции, которая должна быть выполнена следующей, если нет иных условий.

Регистры сегментов (CS, DS, SS, ES, FS, GS). Их наличие в ассемблере продиктовано особым управлением оперативной памятью, чтобы увеличить ее использование в программах. Благодаря им можно было управлять памятью размером до 4 Гб. В архитектуре Win32 необходимость в сегментах отпала, но названия регистров сохранились и используются по-другому.

Стек

Это область памяти, выделенная для работы процедур. Особенность стека заключается в том, что последние данные, записанные в него, доступны для чтения первыми. Или иными словами: первые записи стека извлекаются последними. Представить этот процесс себе можно в качестве башни из шашек. Чтобы достать шашку (нижнюю шашку в основание башни или любую в середине) нужно сначала снять все, которые лежат сверху. И, соответственно, последняя положенная на башню шашка, при разборе башни снимается первой. Такой принцип организации памяти и работы с ней продиктован ее экономией. Стек постоянно очищается и в каждый момент времени одна процедура использует его.


Идентификаторы, целые числа, символы, комментарии, эквивалентность

Идентификатор в языке программирования ассемблер имеет такой же смысл, как и в любом другом. Допускается использование латинских букв, цифр и символов "_", ".", "?", "@", "$". При этом прописные и строчные буквы эквивалентны, а точка может быть только первым символом идентификатора.

Целые числа в ассемблере можно указывать в системах отсчета с основаниями 2, 8, 10 и 16. Любая другая запись чисел будет рассматриваться компилятором ассемблера в качестве идентификатора.

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

  • в строке, заключенной в апострофы, кавычки указываются один раз, апостроф - дважды: "can""t", " he said "to be or not to be" ";
  • для строки, заключенной в кавычки, правило обратное: дублируются кавычки, апострофы указываются как есть: "couldn"t", " My favourite bar is ""Black Cat"" ".

Для указания комментирования в языке ассемблер используется символ точка с запятой - ";". Допустимо использовать комментарии как в начале строк, так и после команды. Заканчивается комментарий переводом строки.

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

Таким образом в программе все вхождения будут заменяться на, на месте которого допустимо указывать целое число, адрес, строку или другое имя. Директива EQU похожа по своей работе на #define в языке С++.

Директивы данных

Языки высокого уровня (C++, Pascal) являются типизированными. То есть, в них используются данные, имеющие определенный тип, имеются функции их обработки и т. д. В языке программирования ассемблер подобного нет. Существует всего 5 директив для определения данных:

  1. DB - Byte: выделить 1 байт под переменную.
  2. DW - Word: выделить 2 байта.
  3. DD - Double word: выделить 4 байта.
  4. DQ - Quad word: выделить 8 байтов.
  5. DT - Ten bytes: выделить 10 байтов под переменную.

Буква D означает Define.

Любая директива может быть использована для объявления любых данных и массивов. Однако для строк рекомендуется использовать DB.

Синтаксис:

В качестве операнда допустимо использовать числа, символы и знак вопрос - "?", обозначающий переменную без инициализации. Рассмотрим примеры:

Real1 DD 12.34 char db "c" ar2 db "123456",0 ; массив из 7 байт num1 db 11001001b ; двоичное число num2 dw 7777o ; восьмеричное число num3 dd -890d ; десятичное число num4 dd 0beah ; шестнадцатеричное число var1 dd ? ; переменная без начального значения ar3 dd 50 dup (0) ; массив из 50 инициализированных эл-тов ar4 dq 5 dup (0, 1, 1.25) ; массив из 15 эл-тов, инициализированный повторами 0, 1 и 1.25