Язык ассемблера

Материал из Википедии — свободной энциклопедии
Перейти к навигации Перейти к поиску
Язык ассемблера
Класс языка императивный
Тип исполнения компилируемый
Появился в 1949
Расширение файлов .asm или .s[1]
Логотип Викисклада Медиафайлы на Викискладе
Листинг программы на языке ассемблера Motorola MC6800 (слева идут адреса и машинные коды в шестнадцатеричной системе, вычисленные и сгенерированные ассемблером из исходного кода программы, справа показан сам текст программы с мнемоническими инструкциями, метками, директивами, выражениями и комментариями)

Язы́к ассе́мблера (англ. assembly language) — машинно-ориентированный язык программирования низкого уровня. Представляет собой систему обозначений, используемую для представления в удобно читаемой форме программ, записанных в машинном коде. Его команды прямо соответствуют отдельным командам машины или их последовательностям. Является существенно платформо-зависимым: языки ассемблера для различных аппаратных платформ несовместимы, хотя могут быть в целом подобны.

Язык ассемблера позволяет программисту пользоваться алфавитными мнемоническими кодами операций, по своему усмотрению присваивать символические имена регистрам ЭВМ и памяти, а также задавать удобные для себя схемы адресации (например, индексную или косвенную). Кроме того, он позволяет использовать различные системы счисления (например, десятичную или шестнадцатеричную) для представления числовых констант и даёт возможность помечать строки программы метками с символическими именами с тем, чтобы к ним можно было обращаться (по именам, а не по адресам) из других частей программы (например, для передачи управления)[2].

Также может предоставлять дополнительные возможности облегчения программирования, такие как макрокоманды, выражения, средства обеспечения модульности программ. В связи с этим может рассматриваться как автокод (см. ниже), расширенный конструкциями языков программирования высокого уровня[3][4].

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

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

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

История[править | править код]

Данный тип языков получил своё название от названия транслятора (компилятора) с этих языков, называемого ассемблером (англ. assembler — сборщик). Название обусловлено тем, что программа «автоматически собиралась», а не вводилась вручную покомандно непосредственно в машинных кодах.

Первые ассемблеры были спроектированы Кэтлин Бут в 1947 под ARC2[5] и Дэвидом Уилером[en]* в 1948 под EDSAC[6], при этом термин «ассемблер» не использовали, просто называя язык «множеством базовых команд» (англ. basic order set) и «начальными командами» (англ. initial orders) соответственно. Впервые термин «ассемблер» для процесса объединения полей в командное слово начали использовать более поздние отчёты по EDSAC.

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

Исторически, если первым поколением языков программирования считать машинные коды, то язык ассемблера можно рассматривать как второе поколение языков программирования. Недостатки языка ассемблера, например, сложность разработки на нём больших программных комплексов, позже привели к появлению языков третьего поколения — языков программирования высокого уровня (таких как Фортран, Лисп, Кобол, Паскаль, Си и другие).

Кроме того, существуют компьютеры, реализующие в качестве машинного язык программирования высокого уровня (Форт, Лисп, Эль-76). Фактически, в таких компьютерах они выполняют роль языков ассемблера[источник не указан 360 дней].

Синтаксис языка[править | править код]

Синтаксис программы на языке ассемблера определяется главным образом системой команд конкретного процессора и системой директив конкретного транслятора.

Для некоторых платформ может существовать несколько видов синтаксиса языка ассемблера, не совместимых между собой. Например, наиболее популярные синтаксисы языков ассемблера для Intel-совместимых процессоров — Intel-синтаксис и AT&T-синтаксис.

Метки[править | править код]

Язык ассемблера позволяет использовать символические метки вместо адресов ячеек памяти, которые при ассемблировании заменяются на вычисляемые ассемблером или компоновщиком абсолютные или относительные адреса.

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

Label1:

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

Запись числовых констант[править | править код]

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

  • Для записи числа в десятичной системе счисления в одних трансляторах требуется представление только в виде цифр (255, 65535), тогда как в других для этого требуется начать число с точки (.255, .65535).
  • Для записи числа в шестнадцатеричной системе требуется начать число с префикса «0x» (0xFF, 0x2000), в других — добавить в конце числа «h» (0FFh, 2000h), в третьих — записывать только цифры (0FF, 2000), при этом в последних двух случаях у чисел, начинающиеся с A…F, для отличия их от символьных имён спереди добавляют ноль.
  • Признаком восьмеричной системы в некоторых трансляторах является ведущий ноль (0377, 0177777), в других требуется добавить префикс в виде буквы «O», а число заключить в апострофы (O’377’, O’177777’).
  • Для записи констант в двоичной системе распространённым является формат вида b'10010111'.

Соответственно, запись числа без явного указания системы счисления разными трансляторами будет воспринята по-разному. Например, 057 в зависимости от транслятора может быть прочитано как восьмеричная запись числа 47, десятичная запись числа 57 или шестнадцатеричная запись числа 87. В некоторых трансляторах система счисления, используемая по умолчанию, может быть определена с помощью директив.

Инструкции процессора[править | править код]

Команды языка ассемблера один к одному соответствуют командам процессора. Фактически, они и представляют собой более удобную для человека символьную форму записи — мнемокоды — команд и их аргументов. При этом одной команде языка ассемблера может соответствовать несколько вариантов команд процессора[7].

Каждая модель (или семейство) процессоров имеет свой набор команд (систему команд) и соответствующий ей язык ассемблера. Используемые мнемоники обычно одинаковы для всех процессоров одной архитектуры или семейства архитектур (среди широко известных — мнемоники процессоров и контроллеров x86, ARM, PIC, SPARC, PowerPC, M68k и другие). Возможные исключения из этого правила:

  • Если ассемблер использует кроссплатформенный AT&T-синтаксис (оригинальные мнемоники приводятся к синтаксису AT&T). Так, различия между синтаксисом Intel и AT&T касаются в основном порядка перечисления операндов и указания различных методов адресации.
  • Если изначально существовало два стандарта записи мнемоник (система команд была унаследована от процессора другого производителя). Например, процессор Zilog Z80 унаследовал систему команд Intel 8080, расширил её и поменял мнемоники (и обозначения регистров) на свой лад. Процессоры Motorola Fireball унаследовали систему команд Z80, несколько её сократив. Вместе с тем, Motorola официально вернулась к мнемоникам Intel и в данный момент половина ассемблеров для Fireball работает с мнемониками от Intel, а половина — с мнемониками от Zilog.

Типичными командами языка ассемблера являются (большинство примеров даны для Intel-синтаксиса архитектуры x86):

  • Команды пересылки данных (mov и др.)
  • Арифметические команды (add, sub, imul и др.)
  • Логические и побитовые операции (or, and, xor, shr и др.)
  • Команды управления ходом выполнения программы (jmp, loop, ret и др.)
  • Команды вызова прерываний (иногда относят к командам управления): int
  • Команды ввода-вывода в порты (in, out)
  • Для микроконтроллеров и микрокомпьютеров характерны также команды, выполняющие проверку и переход по условию, например некоторые из них:
  • cjne — перейти, если не равно
  • djnz — декрементировать, и если результат ненулевой, то перейти
  • cfsneq — сравнить, и если не равно, пропустить следующую команду

Наборы мнемоник конкретных процессоров описаны в спецификациях этих процессоров, часто известных как «Datasheet».

Типичный формат записи команд:

[метка:] [ [префикс] мнемокод [операнд {, операнд}] ] [;комментарий]

В указанном формате строки:

  • Метка — символьное имя, обозначающее адрес в памяти программ, по которому расположена данная инструкция. Если в строке с меткой не содержится мнемокод, метка обычно будет относиться к следующему мнемокоду, расположенному ниже по тексту программы.
  • Мнемокод — непосредственно мнемоника инструкции процессору. Большинство трансляторов предусматривает размещение в одной строке мнемокода только одной инструкции процессора.
  • К мнемокоду могут быть добавлены префиксы (повторения, изменения типа адресации и пр.). Возможные префиксы, также как и набор мнемоник, определяются архитектурой процессора. Некоторые процессоры не поддерживают префиксы.
  • В качестве операндов могут выступать константы, адреса регистров, адреса в оперативной памяти и пр. Набор операндов, которые можно передать той или иной мнемонике, также определяется архитектурой процессора.
  • Комментарий — произвольный текст, не выполняемый транслятором и служащий для пояснений выполняемых действий программистам. Допускается, что в строке не присутствует ничего кроме комментария.

Пример кодирования инструкций на языке ассемблера для архитектуры PIC16:

Again:  movf    0x40,W          ;Скопировать ячейку с адресом 0x40 (десятичное 64) в регистр W
        addlw   0x05            ;Добавить константу 5 к регистру W
        movwf   PORTC           ;Записать регистр W в выходной порт PORTC микроконтроллера
        clrw                    ;Очистить регистр W (у данной инструкции нет операндов)
        goto    Again           ;Перейти на метку Again

Директивы[править | править код]

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

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

Ниже приведены примеры использования директив определения констант для добавления строки «Hello, world!» в код программы различными способами.

В шестнадцатеричном виде:

        db $48,$65,$6C,$6C,$6F,$44,$20,$77,$6F,$72,$6C,$64,$21

В десятичном виде:

        db 072,101,108,108,111,068,032,119,111,114,108,100,033

В текстовом виде:

        db "Hello, world!"

Связывание программ на разных языках[править | править код]

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

  • На этапе компиляции — вставка в исходный код программы на языке высокого уровня ассемблерных фрагментов (англ. inline assembler) с помощью специальных директив высокоуровнего языка. Способ удобен для несложных преобразований данных, но полноценного ассемблерного кода с данными и подпрограммами, включая подпрограммы со множеством входов и выходов, не поддерживаемых языком высокого уровня, с его помощью сделать невозможно.
  • На этапе компоновки при раздельной компиляции. Для взаимодействия компонуемых модулей достаточно, чтобы импортируемые функции (определённые в одних модулях и используемые в других) поддерживали определённое соглашение о вызове (англ. calling conventions). Написаны отдельные модули могут быть на любых языках, в том числе и на языке ассемблера.

Достоинства и недостатки языка ассемблера[править | править код]

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

  • Возможность максимально полного использования всех особенностей аппаратной платформы позволяет, теоретически, писать самый быстрый и компактный код из всех возможных для данного процессора. Искусный программист, как правило, способен значительно оптимизировать программу по сравнению с транслятором с языка высокого уровня по одному или нескольким параметрам и создать код, близкий к оптимальному по Парето (когда дальнейшее быстродействие программы может быть достигнуто только за счёт удлинения кода и наоборот):
    • За счёт более рационального использования ресурсов процессора. Например, максимально эффективного размещения всех или часто используемых исходных данных во внутренних регистрах процессора, можно исключить излишние обращения к внешней оперативной памяти.
    • За счёт интеллектуальной оптимизации вычислений, в том числе более эффективного использования промежуточных результатов, может быть сокращён объём кода и повышена скорость программы.
  • Возможность непосредственного доступа к аппаратуре, и, в частности, портам ввода-вывода, конкретным адресам памяти, регистрам процессора (во многих операционных системах данная возможность существенно ограничивается тем, что прямое обращение из прикладных программ для записи в регистры периферийного оборудования блокировано для повышения надёжности работы системы).

Вместе с тем, язык ассемблера имеет и недостатки, так или иначе ограничивающие его применение или делающие таковое менее выгодным.

  • В силу машинной ориентации («низкого уровня») языка ассемблера человеку сложнее читать и понимать программу, написанную на языке ассемблера, по сравнению с языками программирования высокого уровня. Это затрудняет сопровождение программ, написанных на языке ассемблера.
  • Программа на языке ассемблера состоит из очень «мелких» элементов — машинных команд, соответственно, объём текста программы по количеству команд оказывается больше. Поэтому разработка больших программ на языке ассемблера выполняется медленнее.
  • Растёт вероятность внесения ошибок при разработке и модификации кода, усложняется его отладка.
  • Как правило, программисту доступно меньшее количество библиотек по сравнению с современными развитыми и популярными языками программирования.
  • Требуется повышенная квалификация программиста для получения качественного кода: эффективность кода, написанного начинающим программистом на языке ассемблера, обычно оказывается не лучше или даже хуже эффективности кода, порождаемого оптимизирующим компилятором для сравнимых программ, написанных на языке высокого уровня[8]. При этом чем больше объём программы, тем меньше выигрыш от использования языка ассемблера.
  • Программа на языке высокого уровня может быть перекомпилирована с автоматической оптимизацией под особенности новой целевой платформы[9], программа же на языке ассемблера на новой платформе может потерять своё преимущество в скорости без ручного переписывания кода[10][11].
  • Отсутствует возможность прямой простой переносимости программ на языке ассемблера на компьютеры с другой архитектурой и системой команд.

Применение языка ассемблера[править | править код]

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

На языке ассемблера пишут программы или их фрагменты в тех случаях, когда критически важны:

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

  • Оптимизация критичных к скорости участков программ в программах на языках высокого уровня, таких как C++ или Pascal. Это особенно актуально для игровых приставок, имеющих ограниченную производительность, и для мультимедийных кодеков, которые стремятся делать менее ресурсоёмкими и более быстрыми.
  • Создание операционных систем (ОС) или их некоторых компонентов. В настоящее время подавляющее большинство ОС пишут на более высокоуровневых языках (в основном на Си — языке высокого уровня, который специально был создан для написания одной из первых версий UNIX). Аппаратно зависимые участки кода, такие как загрузчик ОС, уровень абстрагирования от аппаратного обеспечения (hardware abstraction layer) и ядро операционной системы, часто пишутся на языке ассемблера. Фактически, ассемблерного кода в ядрах Windows или Linux совсем немного, поскольку авторы стремятся обеспечить переносимость и надёжность, но, тем не менее, он там присутствует. Некоторые любительские ОС, например, такие как MenuetOS и KolibriOS, целиком написаны на языке ассемблера. При этом MenuetOS и KolibriOS помещаются на дискету и содержат графический многооконный интерфейс[источник не указан 483 дня].
  • Программирование микроконтроллеров (МК) и других встраиваемых в оборудование управляющих процессоров. По мнению профессора Таненбаума, развитие МК повторяет историческое развитие компьютеров новейшего времени[12]. В 2013 году для программирования МК язык ассемблера применялся весьма часто, хотя для этого более широкое распространение получают языки вроде Си. Типичная задача программы в МК — перемещение отдельных байтов и битов между различными ячейками памяти и логические операции с ними. Программирование МК весьма важно, так как, по мнению Таненбаума, в автомобиле и жилище современного цивилизованного человека в среднем содержится 50 микроконтроллеров[13].
  • Создание драйверов устройств. В связи с повышенными требованиями к надёжности и быстродействию драйверы или некоторые их программные модули обычно программируют на языке ассемблера. Надёжность для драйверов играет особую роль, поскольку в Windows NT и UNIX (в том числе в Linux) драйверы работают в режиме ядра системы, поэтому одна тонкая ошибка в критичном для работы системы драйвере может привести к краху всей системы. Тем не менее, в настоящее время в связи с достаточной производительностью современных процессоров и совершенством компиляторов с языков высокого уровня некоторые драйверы также стремятся писать на языках высокого уровня.
  • Создание антивирусов и других защитных программ.
  • Написание кода низкоуровневых библиотек трансляторов языков программирования.
  • Написание компьютерных вирусов.

Использование языка ассемблера практически не имеет альтернативы при создании:

  • драйверов оборудования и ядра операционной системы (по крайней мере, машинозависимых подсистем ядра ОС) — когда принципиально необходимо временно́е согласование работы периферийных устройств с центральным процессором;
  • программ, которые должны храниться в ПЗУ ограниченного объёма и/или выполняться на устройствах с ограниченной производительностью («прошивок» компьютеров и различных электронных устройств);
  • платформенно-зависимых компонентов компиляторов и интерпретаторов языков высокого уровня, системных библиотек и кода, реализующего совместимость платформ.

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

Примеры программ на языке ассемблера[править | править код]

Примеры программы «Hello, world!» для различных платформ ЭВМ[править | править код]

Примеры программ для различных микроконтроллеров[править | править код]

Примечания[править | править код]

  1. https://cs.lmu.edu/~ray/notes/x86assembly/
  2. Толковый словарь по вычислительным системам = Dictionary of Computing / Под ред. В. Иллингуорта и др.: Пер. с англ. А. К. Белоцкого и др.; Под ред. Е. К. Масловского. — Москва: Машиностроение, 1990. — С. 28. — 560 с. — 70 000 экз. — ISBN 5-217-00617-X (СССР), ISBN 0-19-853913-4 (Великобритания).
  3. СТ ИСО 2382/7-77 // Вычислительная техника. Терминология: Справочное пособие. Выпуск 1 / Рецензент канд. техн. наук Ю. П. Селиванов. — М.: Издательство стандартов, 1989. — 168 с. — 55 000 экз. — ISBN 5-7050-0155-X.
  4. 1 2 ГОСТ 19781-83 // Вычислительная техника. Терминология: Справочное пособие. Выпуск 1 / Рецензент канд. техн. наук Ю. П. Селиванов. — М.: Издательство стандартов, 1989. — 168 с. — 55 000 экз. — ISBN 5-7050-0155-X.
  5. General Considerations in the Design of an All Purpose Electronic Digital Computer Архивная копия от 24 марта 2020 на Wayback Machine by Andrew D. Booth and Kathleen H. V. Britten. 2nd. Edition. August 1947.
  6. 1985 Computer Pioneer Award «For assembly language programming.»
  7. Крис Касперски. Образ мышления IDA (недоступная ссылка). Дата обращения: 31 марта 2010. Архивировано 8 сентября 2011 года.
  8. Крис Касперски. Война миров: Ассемблер против Си (недоступная ссылка). Дата обращения: 1 июня 2010. Архивировано 29 июля 2010 года.
  9. Например, доступ к данным, невыровненным на границу 32-битных слов в архитектуре IA-32, требует дополнительного времени. Также, в архитектуре Intel P6 появились два конвейера, и, чтобы они не простаивали (англ. Pipeline stall), может потребоваться переупорядочивание инструкций (англ.)
  10. Кирилл Кочетков. MP3-кодек LAME — продолжаем исследования (недоступная ссылка). iXBT (28 ноября 2003). Дата обращения: 1 июня 2010. Архивировано 19 апреля 2012 года.
  11. Serj Kalichev. Основы написания переносимого кода (недоступная ссылка). — Оригинал статьи: Martin Husemann. Fighting the Lemmings. Дата обращения: 1 июня 2010. Архивировано 13 мая 2012 года.
  12. Эндрю Таненбаум. Архитектура компьютера. 5-е изд.
  13. Эндрю Таненбаум. Архитектура компьютера. 3-е изд.

Литература[править | править код]

  • Галисеев Г. В. Ассемблер для Win 32. Самоучитель. — М.: Диалектика, 2007. — 368 с. — ISBN 978-5-8459-1197-1.
  • Зубков С. В. Ассемблер для DOS, Windows и UNIX. — М. ДМК Пресс; СПб. Питер, 2006. — 608 с. — ISBN 5-94074-259-9.
  • Кип Ирвин. Язык ассемблера для процессоров Intel = Assembly Language for Intel-Based Computers. — М.: Вильямс, 2005. — 912 с. — ISBN 0-13-091013-9.
  • Калашников О. А. Ассемблер? Это просто! Учимся программировать. — СПб.: БХВ-Петербург, 2007. — 384 с. — ISBN 978-5-94157-709-5.
  • Крис Касперски. Искусство дизассемблирования. — СПб.: БХВ-Петербург, 2008. — 896 с. — ISBN 978-5-9775-0082-1.
  • Владислав Пирогов. Ассемблер для Windows. — СПб.: БХВ-Петербург, 2007. — 896 с. — ISBN 978-5-9775-0084-5.
  • Владислав Пирогов. Ассемблер и дизассемблирование. — СПб.: БХВ-Петербург, 2006. — 464 с. — ISBN 5-94157-677-3.
  • Ричард Саймон. Microsoft Windows API Справочник системного программиста.
  • Фрунзе А. В. Микроконтроллеры? Это же просто! — Т. 1.
  • Юров В., Хорошенко С. Assembler: учебный курс. — СПб.: Питер, 1999. — С. 672. — ISBN 5-314-00047-4.
  • Аблязов Р. З. Программирование на ассемблере на платформе х86-64. — М.: ДМК Пресс, 2011. — С. 304. — ISBN 978-5-94074-676-8.
  • Юричев Д., Понимание языка ассемблера https://yurichev.com/writings/UAL-RU.pdf
  • Практическое программирование микроконтроллеров Atmel AVR на языке ассемблера.. — 2-е. — БХВ-Петербург, 2014. — 368 с. — (Электроника). — ISBN 9785977533119.

Ссылки[править | править код]

  • WASM.in — портал, посвящённый информационной безопасности и программированию на языках ассемблера.
  • [1] — архив статей с сайта «wasm.ru». На самом сайте в данный момент доступен только форум
  • Assembler & Win64 (англ.) — введение в ассемблер под х86-64