Вы находитесь на странице: 1из 98

Ассемблер 

— это транслятор (переводчик), который переводит код,


написанный на языке ассемблера, в машинный язык.
Каждый персональный компьютер имеет процессор, который управляет
арифметической, логической и управляющей деятельностями компьютера.
Каждое семейство процессоров имеет свой собственный набор инструкций для
выполнения различных операций, таких как получение ввода с клавиатуры,
отображение информации на экране и т.д. Этот набор инструкций
называется инструкциями машинного языка.
Процессор понимает только инструкции машинного языка, которые являются
последовательностью бит: 1 или 0. Однако машинный язык слишком сложен и
непонятен для использования в разработке программного обеспечения. Поэтому
для определенного семейства процессоров был разработан низкоуровневый
язык, который представляет собой набор инструкций для написания программ в
более понятной форме. Этот язык назвали языком ассемблера (или
просто «ассемблер»).
Оглавление:
1. Преимущества языка ассемблера
2. Основные характеристики аппаратного обеспечения ПК
3. Двоичная система счисления
4. Шестнадцатеричная система счисления
5. Адресация данных в памяти
Преимущества языка ассемблера
Использование языка ассемблера позволяет понять:
   как программы взаимодействуют с операционной системой, процессором и
BIOS-м;
   как данные представлены в памяти и на других устройствах;
   как процессор получает доступ к инструкциям и как он их выполняет;
   как инструкции получают доступ к данным и как эти данные обрабатываются;
   как программа получает доступ к внешним устройствам.
Другие преимущества использования языка ассемблера:
   для работы требует мало памяти;
   его инструкции выполняются очень быстро;
   упрощает сложные аппаратные задачи.
Основные характеристики аппаратного обеспечения ПК
Основное аппаратное обеспечение ПК состоит из процессора, памяти и
регистров. Регистры — это компоненты процессора, содержащие данные и их
адреса в памяти. Чтобы выполнить программу, система копирует её с внешнего
устройства в оперативную память. Затем процессор выполняет инструкции
программы.
Данные в компьютере хранятся в битах: 1 (ВКЛ) или 0 (ВЫКЛ).
Процессор поддерживает следующие размеры данных:
   word — 2-байтовый элемент данных;
   doubleword — 4-байтовый (32-битный) элемент данных;
   quadword — 8-байтовый (64-битный) элемент данных;
   paragraph — 16-байтовая (128-битная) область;
   kilobyte — 1024 байт;
   megabyte — 1 048 576 байт.
Двоичная система счисления
Каждая система счисления использует позиционные обозначения разрядов
чисел (их значений). Каждое следующее позиционное значение состоит из
предыдущего позиционного значения, умноженного на 2 (именно на 2, так как
это бинарная система, которая состоит из двух чисел). Если битом является 1, то
позиционное значение умножается на 2, а если 0 — позиционное значение
остается 0. В бинарной системе счисления отсчет ведется справа налево, а не
слева направо (как в десятичной системе).
Например, в следующей таблице показаны позиционные значения 8-битного
двоичного числа 11111101:

Бит 1 1 1 1 1 1 0 1

Позиционное значение 128 64 32 16 8 4 2 1

Номер бита 7 6 5 4 3 2 1 0

Значение бинарного числа равно сумме позиционных значений всех бит:


1 + 4 + 8 + 16 + 32 + 64 + 128 = 253
Двоичное 11111101 = десятичное 253.
Примечание: Детально о конвертации чисел из двоичной системы в
десятичную и наоборот, а также о сложении двоичных чисел, читайте в
материалах урока №44.
Шестнадцатеричная система счисления

Шестнадцатеричная система счисления состоит из 16 символов: 0-9 и A-F.


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

Десятичное Двоичное Шестнадцатеричное


представление представление представление

0 0 0

1 1 1

2 10 2

3 11 3

4 100 4

5 101 5

6 110 6

7 111 7

8 1000 8

9 1001 9
10 1010 A

11 1011 B

12 1100 C

13 1101 D

14 1110 E

15 1111 F

Для конвертации бинарного числа в его шестнадцатеричный эквивалент


разбейте бинарное число на 4 последовательные группы, начиная справа, и
запишите эти группы поверх соответствующих цифр шестнадцатеричного
числа.
Пример: Бинарное число 1000 (8) 1100 (C) 1101 (D) 0001 (1) эквивалентно
шестнадцатеричному 8CD1.
Чтобы конвертировать шестнадцатеричное число в двоичное, просто запишите
каждую шестнадцатеричную цифру в её 4-значный двоичный эквивалент.
Пример: Шестнадцатеричное число FAD8 эквивалентно двоичному 1111 (F)
1010 (A) 1101 (D) 1000 (8).

Адресация данных в памяти


Процесс, посредством которого процессор управляет выполнением инструкций,
называется циклом выполнения, который состоит из трех последовательных
шагов:
   Шаг №1: Извлечение инструкции из памяти.
   Шаг №2: Расшифровка или идентификация инструкции.
   Шаг №3: Выполнение инструкции.
Процессор может одновременно обращаться к одному или нескольким байтам
памяти. Например, рассмотрим шестнадцатеричное значение 0824H, которое
занимает 2 байта памяти. Байт старшего разряда или старший значащий байт –
08, младший байт – 24.
Процессор хранит данные в обратной последовательности байтов, т.е. байт
младшего разряда сохраняется в нижнем адресе памяти (слева), а байт старшего
разряда – в верхнем адресе памяти (справа). Таким образом, если процессор
перенесет значение 0824H из регистра в память, то 24 будет в начале строки, а
08 – в конце, при этом читать данные процессор будет справа налево, а не слева
направо (помним, что процессор работает в бинарной системе счисления):

Когда процессор переносит данные из памяти в регистр, то он опять меняет


местами байты (т.е. 08 опять будет слева, а 24 – справа).
Есть 2 вида адресов памяти:
   абсолютный адрес — прямая ссылка на конкретное местоположение;
   сегментный адрес (или «смещение») — адрес сегмента памяти со значением
смещения.
На следующем уроке мы рассмотрим установку среды разработки для языка
ассемблера.
Ассемблер. Настройка среды разработки
  Евгений Павлов  | 
 
 Уроки Ассемблера
  |


 
  Обновл. 16 Сен 2021  | 
 
 58851
 
 ǀ   14 
Язык ассемблера напрямую зависит от набора команд и архитектуры
процессора. Для того, чтобы следовать данному руководству, вам понадобятся:
   дистрибутив операционной системы Linux (например, Debian или Ubuntu);
   копия программы ассемблера NASM.
Есть много хороших программ ассемблера, таких как:
   Microsoft Assembler (MASM);
   Borland Turbo Assembler (TASM);
   The GNU assembler (GAS).
Мы будем использовать ассемблер NASM, так как он:
   бесплатный. Вы можете скачать его c различных веб-ресурсов;
   хорошо документированный. В Интернете вы сможете найти много
информации по нему;
   кроссплатформенный. Его можно использовать как на Linux, так и на
Windows.
Установка NASM
Если при установке Linux вы устанавливали «Инструменты разработки», то
NASM у вас уже установлен. Чтобы проверить, установлен ли у вас NASM,
выполните следующие действия:
   откройте терминал;
   введите команду whereis nasm и нажмите ENTER.
Если он у вас установлен, то вы увидите примерно следующее:
Если же вы увидели:
nasm:
То NASM у вас не установлен, и его потребуется установить.
Чтобы установить NASM, выполните следующие шаги:
   откройте терминал;
   введите sudo apt install nasm.
Редактор Visual Studio Code

Я рекомендую установить универсальный редактор кода Visual Studio Code. Он


позволит вам не только удобно писать на ассемблере под Linux, но и на других
языках.
Для установки вам нужно перейти на официальный сайт Microsoft Visual
Studio Code, пролистать страницу вниз и скачать версию .deb:

После того, как VS Code будет установлен, вам нужно будет:


   запустить его;
   нажать комбинацию клавиш Ctrl+P;
   ввести в появившемся окне ext install 13xforever.language-x86-64-assembly.
Ассемблер. Базовый синтаксис
  Евгений Павлов  | 
 
 Уроки Ассемблера
  |

 
  Обновл. 29 Янв 2023  | 
 
 79036
 
 ǀ   19 

Программы на языке ассемблере могут быть разделены на три секции: data, bss,
text.
Секция data используется для объявления инициализированных данных
или констант. Данные в этой секции НЕ могут быть изменены во время
выполнения программы. Вы можете хранить константные значения и названия
файлов в этой секции. Синтаксис объявления:
1section.data
Секция bss используется для объявления переменных. Синтаксис объявления:
1section.bss
Секция text используется для хранения кода программы. Данная секция
должна начинаться с объявления global_start, которое сообщает ядру, откуда
нужно начинать выполнение программы. Синтаксис объявления:
1section.text
2   global _start
3_start:
Комментарии
Комментарии в ассемблере должны начинаться с точки с запятой (;). Они могут
содержать любой печатный символ, включая пробел. Комментарий может
находиться как на отдельной строке:
1; эта программа выводит сообщение на экран
Так и на строке со стейтментом:
1add eax, ebx     ; добавляет ebx к eax
Стейтменты

В ассемблере есть три вида стейтментов:


   Выполняемые инструкции (или просто «инструкции») — сообщают
процессору, что нужно делать. Каждая инструкция хранит в себе код
операции (или «опкод») и генерирует одну инструкцию на машинном языке.
   Директивы ассемблера — сообщают программе об аспектах компиляции.
Они не генерируют инструкции на машинном языке.
   Макросы — являются простым механизмом вставки кода.
В ассемблере на одну строку приходится один стейтмент, который должен
соответствовать следующему формату:
1[метка]   mnemonic   [операнды]   [; комментарий]
Базовая инструкция состоит из названия инструкции (mnemonic) и операндов
(они же «параметры»). Вот примеры типичных стейтментов ассемблера:
1 INC COUNT        ; выполняем инкремент переменной памяти COUNT
2  
3 MOV TOTAL, 48    ; перемещаем значение 48 в переменную памяти TOTAL
4   
5 ADD AH, BH       ; добавляем содержимое регистра BH к регистру AH
6   
7 AND MASK1, 128   ; выполняем операцию AND с переменной MASK1 и 128
8   
9 ADD MARKS, 10    ; добавляем 10 к переменной MARKS
10MOV AL, 10       ; перемещаем значение 10 в регистр AL
Первая программа
Следующая программа на языке ассемблера выведет строку Hello, world! на
экран:
1 section .text
2    global _start    ; необходимо для линкера (ld)
3
4 _start:            ; сообщает линкеру стартовую точку
5    mov edx,len     ; длина строки
6    mov ecx,msg     ; строка
7    mov ebx,1       ; дескриптор файла (stdout)
8    mov eax,4       ; номер системного вызова (sys_write)
9    int 0x80        ; вызов ядра
10
11    mov eax,1       ; номер системного вызова (sys_exit)
12   int 0x80        ; вызов ядра
13 
14section .data
15msg db 'Hello, world!', 0xa  ; содержимое строки для вывода
16len equ $ - msg              ; длина строки
Результат выполнения программы:
Hello, world!
Сборка программ

Убедитесь, что у вас установлен NASM. Запишите вашу программу в


текстовом редакторе и сохраните её как hello.asm. Затем:
   откройте терминал;
   убедитесь, что вы находитесь в той же директории, в которой вы
сохранили hello.asm;
   чтобы собрать программу, введите команду nasm -f elf hello.asm;
   если не было ошибок, то создастся объектный файл вашей программы под
названием hello.o;
   чтобы ваш объектный файл прошел линкинг и создался исполняемый файл
под названием hello, введите команду ld -m elf_i386 -s -o hello hello.o;
   запустите программу командой ./hello.
Если всё прошло успешно, то вам выведется Hello, world!.
Если у вас нет возможности скомпилировать программу, например, у вас нет
Linux и вы пока не хотите на него переходить, то можете использовать одну из
следующих онлайн-IDE:
   TutorialsPoint
   JDoodle
Примечание: Запоминать две вышеприведенные команды для сборки
программы на ассемблере для некоторых может быть несколько
затруднительно, поэтому вы можете написать скрипт для сборки программ на
ассемблере. Для этого создайте файл под названием Makefile со следующим
содержимым:
1all:
2    nasm –f elf $(source)
3    ld –m elf_i386 –s –o $(source) $(source).o
4    rm $(source).o
Для сборки hello.asm выполните следующие действия:
   откройте терминал;
   убедитесь, что вы находитесь в той же директории, в которой вы
сохранили hello.asm и Makefile;
   введите команду make source=hello.
Ассемблер. Сегменты памяти и регистры
  Светлана Деменева  | 
 
 Уроки Ассемблера
  |


 
  Обновл. 27 Сен 2021  | 
 
 74368
 
 ǀ   1 

Мы уже рассматривали на предыдущем уроке 3 секции, из которых состоят


программы на ассемблере. Эти секции также представляют различные сегменты
памяти. Что интересно, если вы замените ключевое слово section на segment, то
получите тот же результат. Например:
1 segment .text    ; сегмент кода
2    global_start    ; должно быть объявлено для линкера
3
4 _start:           ; сообщаем линкеру точку входа
5    mov edx,len    ; длина сообщения
6    mov ecx,msg     ; сообщение для вывода на экран
7    mov ebx,1   ; файловый дескриптор (stdout)
8    mov eax,4    ; номер системного вызова (sys_write)
9    int 0x80        ; вызов ядра
10 
11    mov eax,1       ; номер системного вызова (sys_exit)
12   int 0x80        ; вызов ядра
13 
14segment .data      ; сегмент данных
15msg db 'Hello, world!',0xa   ; наша строка
16len equ $ - msg              ; длина нашей строки
Результат выполнения программы:
Hello, world!
Оглавление:
1. Сегменты памяти
2. Регистры
3. Регистры процессора
4. Регистры данных
5. Регистры-указатели
6. Индексные регистры
7. Регистры управления
8. Сегментные регистры
9. Пример на практике
Сегменты памяти
Модель сегментированной памяти разбивает системную память на группы
независимых сегментов, на которые указывают указатели, расположенные в
регистрах сегментов. Каждый сегмент используется для хранения данных
определенного типа. Первый сегмент используется для хранения кода
инструкций, второй — для хранения элементов данных, а третий — для
программного стека.
Сегменты памяти:
   Сегмент данных (data segment) — представлен секциями .data и .bss.
Секция .data используется для объявления области памяти, где хранятся
элементы данных для программы. Эта секция не может быть расширена после
объявления элементов данных, и она остается статической во всей программе.
Секция .bss также является секцией статической памяти, содержащей буферы
для данных, которые будут объявлены в программе позже. Эта буферная память
заполнена нулями.
   Сегмент кода (code segment) — представлен секцией .text. Он определяет
область в памяти, в которой хранятся коды инструкций. Это также
фиксированная область.
   Стек (stack) — это сегмент, который содержит значения данных,
передаваемые в функции и процедуры в программе.
Регистры

Обычно операции с процессором включают в себя обработку данных. Эти


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

Регистры процессора
В архитектуре IA-32 есть десять 32-битных и шесть 16-битных процессорных
регистров. Регистры делятся на три категории:
   Общие регистры (General Registers);
   Регистры управления (Control Registers);
   Сегментные регистры (Segment Registers).
В свою очередь, общие регистры делятся на следующие:
   Регистры данных (Data Registers);
   Регистры-указатели (Pointer Registers);
   Индексные регистры (Index Registers).
Регистры данных

Регистры данных — это четыре 32-битных регистра, которые используются


для арифметических, логических и других операций. Эти 32-битные регистры
могут быть использованы следующими тремя способами:
   как полные 32-битные регистры данных: EAX, EBX, ECX, EDX;
   нижние половины 32-битных регистров могут использоваться как четыре 16-
битных регистра данных: AX, BX, CX и DX;
   нижняя и верхняя половины вышеупомянутых четырех 16-битных регистров
могут использоваться как восемь 8-битных регистров данных: AH, AL, BH, BL,
CH, CL, DH и DL.

Некоторые из этих регистров данных имеют специфическое применение в


арифметических операциях:
   AX (primary accumulator) — используется для ввода/вывода и в большинстве
арифметических операций. Например, в операции умножения один операнд
сохраняется в регистре EAX/AX/AL в соответствии с размером операнда.
   BX (base register) — используется при индексированной адресации.
   CX (count register) — хранит количество циклов в повторяющихся операциях
(также, как и регистры ECX и CX).
   DX (data register) — используется в операциях ввода/вывода, а также с
регистрами AX и DX для выполнения операций умножения и деления,
связанных с большими значениями.
Регистры-указатели
Регистрами-указателями являются 32-битные регистры EIP, ESP и EBP и
соответствующие им 16-битные регистры IP, SP и BP. Есть три категории
регистров-указателей:
   Указатель на инструкцию или команду (Instruction Pointer или IP) — 16-
битный регистр IP хранит смещение адреса следующей команды, которая
должна быть выполнена. IP в сочетании с регистром CS (как CS:IP)
предоставляет полный адрес текущей инструкции в сегменте кода.
   Указатель на стек (Stack Pointer или SP) — 16-битный регистр SP
обеспечивает значение смещения в программном стеке. SP в сочетании с
регистром SS (SS:SP) означает текущее положение данных или адреса в
программном стеке.
   Базовый указатель (Base Pointer или BP) — 16-битный регистр BP
используется в основном при передаче параметров в подпрограммы. Адрес в
регистре SS объединяется со смещением в BP, чтобы получить местоположение
параметра. BP также можно комбинировать с DI и SI в качестве базового
регистра для специальной адресации.

Индексные регистры
В процессоре существуют 32-битные индексные регистры ESI и EDI и их 16-
битные версии: SI и DI. Все они используются в индексированной адресации, и,
иногда, в операциях сложения/вычитания. Есть два типа индексных указателей:
   Исходный индекс (Source Index или SI) — используется в качестве
исходного индекса в строковых операциях.
   Индекс назначения (Destination Index или DI) — используется в качестве
индекса назначения в строковых операциях.

Регистры управления
Регистром управления является объединенный 32-битный регистр инструкций и
32-битный регистр флагов (регистр процессора, отражающий его текущее
состояние). Многие инструкции включают в себя операции сравнения и
математические вычисления, которые способны изменить состояния флагов, а
некоторые другие условные инструкции проверяют значения флагов состояния,
чтобы перенести поток управления в другое место.
Распространенные битовые флаги:
   Флаг переполнения (Overflow Flag или OF) — указывает на переполнение
старшего бита данных (крайнего левого бита) после signed арифметической
операции.
   Флаг направления (Direction Flag или DF) — определяет направление влево
или вправо для перемещения или сравнения строковых данных. Если DF = 0, то
строковая операция идет слева направо, а когда DF = 1 — строковая операция
идет справа налево.
   Флаг прерывания (Interrupt Flag или IF) — определяет, будут ли
игнорироваться или обрабатываться внешние прерывания (например, ввод с
клавиатуры и т.д.). Он отключает внешнее прерывание, когда значение равно 0,
и разрешает прерывание, когда установлено значение 1.
   Флаг ловушка (Trap Flag или TF) — позволяет настроить работу процессора
в одношаговом режиме.
   Флаг знака (Sign Flag или SF) — показывает знак результата
арифметической операции. Этот флаг устанавливается в соответствии со знаком
элемента данных после выполнения арифметической операции. Знак
определяется по старшему левому биту. Положительный результат сбрасывает
значение SF в 0, а отрицательный результат устанавливает его равным 1.
   Нулевой флаг (Zero Flag или ZF) — указывает результат арифметической
операции или операции сравнения. Ненулевой результат сбрасывает нулевой
флаг в 0, а нулевой результат устанавливает его равным 1.
   Вспомогательный флаг переноса (Auxiliary Carry Flag или AF) — после
выполнения арифметической операции содержит перенос от бита 3 до бита 4.
Используется для специализированной арифметики. AF устанавливается, когда
1-байтовая арифметическая операция вызывает перенос из бита 3 в бит 4.
   Флаг равенства (Parity Flag или PF) — указывает общее количество 1-битов
в результате, полученном после выполнения арифметической операции. Чётное
число 1-битов сбрасывает PF в 0, а нечётное число 1-битов устанавливает PF
равным 1.
   Флаг переноса (Carry Flag или CF) — после выполнения арифметической
операции содержит перенос 0 или 1 из старшего бита (крайнего слева). Кроме
того, хранит содержимое последнего бита операции сдвига или поворота.
В следующей таблице указано положение битовых флагов в 16-битном регистре
флагов:

Флаг: O D I T S Z A P C

Бит
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
№:

Сегментные регистры
Сегменты — это специфические части программы, которые содержат данные,
код и стек. Есть три основных сегмента:
   Сегмент кода (Code Segment или CS) — содержит все команды и
инструкции, которые должны быть выполнены. 16-битный регистр сегмента
кода или регистр CS хранит начальный адрес сегмента кода.
   Сегмент данных (Data Segment или DS) — содержит данные, константы и
рабочие области. 16-битный регистр сегмента данных или регистр DS хранит
начальный адрес сегмента данных.
   Сегмент стека (Stack Segment или SS) — содержит данные и возвращаемые
адреса процедур или подпрограмм. Он представлен в виде структуры данных
«Стек». Регистр сегмента стека или регистр SS хранит начальный адрес стека.
Кроме регистров CS, DS и SS существуют и другие регистры дополнительных
сегментов — ES (Extra Segment), FS и GS, которые предоставляют
дополнительные сегменты для хранения данных.
При написании программ на ассемблере, программе необходим доступ к
ячейкам памяти. Все области памяти в сегменте относятся к начальному адресу
сегмента. Сегмент начинается с адреса, равномерно делимого на десятичное 16
или на шестнадцатеричное 10. Таким образом, крайняя правая
шестнадцатеричная цифра во всех таких адресах памяти равна 0, которая
обычно не хранится в сегментных регистрах.
Сегментные регистры хранят начальные адреса сегмента. Чтобы получить
точное местоположение данных или команды в сегменте, требуется значение
смещения. Чтобы сослаться на любую ячейку памяти в сегменте, процессор
объединяет адрес сегмента в сегментном регистре со значением смещения
местоположения.

Пример на практике
Посмотрите на следующую простую программу, чтобы понять, как
используются регистры в программировании на ассемблере. Эта программа
выводит 9 звёздочек с простым сообщением:
1 section .text
2    global _start ; должно быть объявлено для линкера (gcc)
3
4 _start:         ; сообщаем линкеру точку входа
5    mov edx,len  ; длина сообщения
6    mov ecx,msg  ; сообщение для вывода на экран
7    mov ebx,1    ; файловый дескриптор (stdout)
8    mov eax,4    ; номер системного вызова (sys_write)
9    int 0x80     ; вызов ядра
10
11    mov edx,9    ; длина сообщения
12   mov ecx,s2   ; сообщение для написания
13   mov ebx,1    ; файловый дескриптор (stdout)
14   mov eax,4    ; номер системного вызова (sys_write)
15   int 0x80     ; вызов ядра
16
17   mov eax,1    ; номер системного вызова (sys_exit)
18   int 0x80     ; вызов ядра
19
20section .data
21msg db 'Displaying 9 stars',0xa ; наше сообщение
22len equ $ - msg  ; длина нашего сообщения
23s2 times 9 db '*'
Результат выполнения программы:
Displaying 9 stars
*********
Ассемблер. Системные вызовы и режимы адресации
  Светлана Деменева  | 
 
 Уроки Ассемблера
  |


 
  Обновл. 16 Сен 2021  | 
 
 38807
 
 ǀ   6 

Системные вызовы — это программные интерфейсы между пользователем и


ядром. Мы уже использовали следующие системные вызовы:
   sys_write — для вывода на экран;
   sys_exit — для выхода из программы.
Оглавление:
1. Системные вызовы Linux
2. Режимы адресации
3. Регистровая адресация
4. Прямая (непосредственная) адресация
5. Адресация памяти
6. Адресация прямого смещения
7. Непрямая адресация памяти
8. Инструкция MOV
Системные вызовы Linux
В своих программах на ассемблере вы можете использовать системные вызовы
Linux. Для этого нужно:
   поместить номер системного вызова в регистр EAX;
   сохранить аргументы системного вызова в регистрах EBX, ECX и т.д.;
   вызвать соответствующее прерывание (80h);
   результат обычно возвращается в регистр EAX.
Есть шесть регистров, в которых хранятся и используются аргументы
необходимого системного вызова:
   EBX
   ECX
   EDX
   ESI
   EDI
   EBP
Эти регистры принимают последовательные аргументы. Если есть более шести
аргументов, то ячейка памяти, содержащая первый аргумент, сохраняется в
регистре EBX.
В следующем примере мы будем использовать системный вызов sys_exit:
1mov eax,1 ; номер системного вызова (sys_exit)
2int 0x80 ; вызов ядра
А в следующем — sys_write:
1mov edx,4 ; длина сообщения
2mov ecx,msg ; сообщение для вывода на экран
3mov ebx,1 ; файловый дескриптор (stdout)
4mov eax,4 ; номер системного вызова (sys_write)
5int 0x80 ; вызов ядра
Все системные вызовы перечислены в /usr/include/asm/unistd.h вместе с их
номерами (значение, которое помещается в EAX перед вызовом int 80h).
В следующей таблице приведены некоторые системные вызовы, которые мы
будем использовать:

%eax Название %ebx %ecx %edx %esx %edi

1 sys_exit int — — — —

2 sys_fork struct pt_regs — — — —

3 sys_read unsigned int char * size_t — —

4 sys_write unsigned int const char * size_t — —

5 sys_open const char * int int — —

6 sys_close unsigned int — — — —

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


1 section .data                           ; сегмент данных
2    userMsg db 'Please enter a number: ' ; сообщение с просьбой ввести число
3    lenUserMsg equ $-userMsg             ; длина сообщения
4    dispMsg db 'You have entered: '
5    lenDispMsg equ $-dispMsg                
6  
7 section .bss           ; неинициализированные данные
8    num resb 5
9
10section .text          ; сегмент кода
11    global _start
12
13_start:                ; запрашиваем пользовательский ввод
14   mov eax, 4
15   mov ebx, 1
16   mov ecx, userMsg
17   mov edx, lenUserMsg
18   int 80h
19 
20   ; Считываем и сохраняем пользовательский ввод
21   mov eax, 3
22   mov ebx, 2
23   mov ecx, num  
24   mov edx, 5          ; 5 байт информации
25   int 80h
26
27   ; Выводим сообщение 'You have entered: '
28   mov eax, 4
29   mov ebx, 1
30   mov ecx, dispMsg
31   mov edx, lenDispMsg
32   int 80h  
33 
34   ; Выводим число пользователя
35   mov eax, 4
36   mov ebx, 1
37   mov ecx, num
38   mov edx, 5
39   int 80h  
40    
41   ; Код выхода
42   mov eax, 1
43   mov ebx, 0
44   int 80h
Результат выполнения программы:
Please enter a number:
1234
You have entered:1234

Режимы адресации

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


Адрес операнда предоставляет место, где хранятся данные, подлежащие
обработке. Некоторые инструкции не требуют операнда, в то время как другие
могут требовать один, два или три операнда. В тех случаях, когда инструкции
требуется два операнда, первый операнд обычно является местом назначения,
содержащий данные в регистре или в ячейке памяти, а второй — источником.
Источник содержит либо данные для доставки (немедленная адресация), либо
адрес (в регистре или в памяти) данных. Как правило, исходные данные
остаются неизменными после операции.
Есть три основных режима адресации:
   регистровая адресация;
   прямая (или «непосредственная») адресация;
   адресация памяти.
Регистровая адресация
В режиме регистровой адресации регистр содержит операнд. В зависимости от
инструкции регистр может быть первым операндом, вторым или и тем, и
другим сразу. Например:
1MOV DX, TAX_RATE   ; регистр в первом операнде
2MOV COUNT, CX    ; регистр во втором операнде
3MOV EAX, EBX    ; оба операнда являются регистрами
Так как обработка данных между регистрами не требует памяти, то она
обеспечивает самую быструю обработку данных.

Прямая (непосредственная) адресация

Прямой операнд имеет константное значение или выражение. Когда инструкция


с двумя операндами использует прямую адресацию, то первый операнд может
быть регистром или ячейкой памяти, а второй — непосредственной константой.
Первый операнд определяет длину данных. Например:
1BYTE_VALUE  DB  150    ; определение значения типа byte
2WORD_VALUE  DW  300    ; определение значения типа word
3ADD  BYTE_VALUE, 65    ; добавлен непосредственный операнд 65
4MOV  AX, 45H           ; непосредственная константа 45H перемещена в AX
Адресация памяти
Когда в режиме адресации памяти операнды определены, то требуется прямой
доступ к основной памяти (обычно к сегменту данных) — этот вариант
является медленным. Чтобы обнаружить точное местоположение данных в
памяти, нам нужен начальный адрес сегмента, который обычно находится в
регистре DS, и значение смещения. Это значение смещения также
называется эффективным адресом.
В режиме прямой адресации значение смещения указывается как часть
инструкции (например, имя переменной). Ассемблер вычисляет значение
смещения и поддерживает таблицу символов, в которой хранятся значения
смещения всех переменных, используемых в программе.
При прямой адресации памяти один из операндов ссылается на ячейку памяти,
а другой — на регистр. Например:
1ADD BYTE_VALUE, DL ; добавляем регистр в ячейку памяти
2MOV BX, WORD_VALUE ; операнд из памяти скопирован в регистр
Адресация прямого смещения

Этот режим адресации использует арифметические операторы для изменения


адреса. Например, следующие определения определяют таблицы данных:
1BYTE_TABLE DB  14, 15, 22, 45      ; таблица байтов
2WORD_TABLE DW  134, 345, 564, 123  ; таблица слов
Следующие операции копируют данные из таблиц в памяти в регистры:
1MOV CL, BYTE_TABLE[2] ; получаем 3-й элемент из BYTE_TABLE
2MOV CL, BYTE_TABLE + 2 ; получаем 3-й элемент из BYTE_TABLE
3MOV CX, WORD_TABLE[3] ; получаем 4-й элемент из WORD_TABLE
4MOV CX, WORD_TABLE + 3 ; получаем 4-й элемент из WORD_TABLE
Непрямая адресация памяти
Обычно для этой цели используются базовые регистры EBX, EBP (или BX, BP)
и индексные регистры (DI, SI), используемые в квадратных скобках
для ссылок на память.
Непрямая адресация обычно используется для переменных, содержащих
несколько элементов, таких как массивы. Начальный адрес массива хранится,
например, в регистре EBX.
В следующем примере мы получаем доступ к разным элементам переменной:
1MY_TABLE TIMES 10 DW 0  ; выделяем 10 слов (2 байта), каждое из
которых инициализируем значением 0
2
MOV EBX, [MY_TABLE]     ; помещаем эффективный адрес MY_TABLE в
3
EBX
4
MOV [EBX], 110          ; MY_TABLE[0] = 110
5
ADD EBX, 2              ; EBX = EBX +2
MOV [EBX], 123          ; MY_TABLE[1] = 123
Инструкция MOV
Мы уже использовали инструкцию MOV для перемещения данных из одного
пространства хранения в другое. Инструкция MOV принимает два операнда.
Синтаксис инструкции MOV:
1MOV destination, source
Инструкция MOV может иметь одну из следующих пяти форм:
1MOV  register, register
2MOV  register, immediate
3MOV  memory, immediate
4MOV  register, memory
5MOV  memory, register
Обратите внимание, оба операнда в операции MOV должны быть одинакового
размера, а значение исходного операнда остается неизменным. Порой
инструкция MOV может вызывать двусмысленность. Например:
1MOV  EBX, [MY_TABLE]  ; помещаем эффективный адрес MY_TABLE в EBX
2MOV  [EBX], 110       ; MY_TABLE[0] = 110
Непонятно, хотите ли вы переместить байтовый или словесный эквивалент
числа 110? В таких случаях целесообразно использовать спецификатор типа.
В следующей таблице приведены некоторые из общих спецификаторов типа:

Спецификатор типа Байт

BYTE 1

WORD 2

DWORD 4

QWORD 8

TBYTE 10

Следующая программа иллюстрирует некоторые из концепций, которые


обсуждались выше: в секции данных памяти сохраняется имя Zara Ali, затем
оно программным образом изменяется на Nuha Ali, и выводятся уже оба имени:
1 section .text
2    global_start     ; должно быть объявлено для линкера
3 _start:             ; сообщаем линкеру входную точку
4
5    ; Пишем имя 'Zara Ali'
6    mov edx,9       ; длина сообщения
7    mov ecx, name   ; сообщение для написания
8    mov ebx,1       ; файловый дескриптор (stdout)
9    mov eax,4       ; номер системного вызова (sys_write)
10   int 0x80        ; вызов ядра
11
12   mov [name],  dword 'Nuha'    ; изменяем имя на 'Nuha Ali'
13
14   ; Пишем имя 'Nuha Ali'
15   mov edx,8       ; длина сообщения
16   mov ecx,name    ; сообщение для написания
17   mov ebx,1       ; файловый дескриптор (stdout)
18   mov eax,4       ; номер системного вызова (sys_write)
19   int 80h         ; вызов ядра
20
21   mov eax,1       ; номер системного вызова (sys_exit)
22   int 80h         ; вызов ядра
23 
24section .data
25name db 'Zara Ali '
Результат выполнения программы:
Zara Ali Nuha Ali
Ассемблер. Переменные и Константы
  Светлана Деменева  | 
 
 Уроки Ассемблера
  |


 
  Обновл. 16 Сен 2021  | 
 
 40546
 
 ǀ   4 

NASM предоставляет различные директивы определения резервирования места


для хранения переменных. В ассемблере директива определения используется
для выделения дискового пространства. Она может использоваться для
резервирования или инициализации одного/нескольких байт.
Оглавление:
1. Хранение инициализированных данных
2. Хранение неинициализированных данных
3. Несколько определений
4. Несколько инициализаций
5. Определение констант
6. Директива EQU
7. Директива %assign
8. Директива %define
Хранения инициализированных данных
Синтаксис стейтмента выделения памяти для инициализированных данных
следующий:
1[имя_переменной]    директива_определения    начальное_значение  
[,начальное_значение]...
Где имя_переменной является идентификатором для каждого пространства
хранения данных. Ассемблер связывает значение смещения с именем каждой
переменной, определенной в сегменте данных.
Есть 5 основных форм директивы определения:

Директива Цель Пространство для хранения

DB Определяет Byte Выделяет 1 байт

DW Определяет Word Выделяет 2 байта

Определяет
DD Выделяет 4 байта
Doubleword
Определяет
DQ Выделяет 8 байт
Quadword

DT Определяет 10 Byte Выделяет 10 байт

Ниже приведены примеры использования директив определения:


1choice     DB    'y'
2number     DW    12345
3neg_number   DW    -12345
4big_number   DQ    123456789
5real_number1 DD    1.234
6real_number2 DQ    123.456
Обратите внимание, что:
   каждый байт символа хранится как его ASCII-значение в шестнадцатеричном
формате;
   каждое десятичное значение автоматически конвертируется в 16-битный
двоичный эквивалент и сохраняется в виде шестнадцатеричного числа;
   процессор использует прямой порядок байтов;
   отрицательные числа конвертируются в форму «two’s complement»;
   короткие и длинные числа типа с плавающей точкой представлены с
использованием 32 или 64 бит, соответственно.
Следующая программа показывает использование директивы определения:
1 section .text
2    global _start          ; должно быть объявлено для линкера (gcc)
3
4 _start:                   ; сообщаем линкеру входную точку
5    mov edx,1       ; длина сообщения
6    mov ecx,choice        ; сообщение для вывода на экран
7    mov ebx,1       ; файловый дескриптор (stdout)
8    mov eax,4       ; номер системного вызова (sys_write)
9    int 0x80       ; вызов ядра
10 
11    mov eax,1       ; номер системного вызова (sys_exit)
12   int 0x80       ; вызов ядра
13 
14section .data
15choice DB 'y'
Результат выполнения программы:
y
Хранение неинициализированных данных

Директивы резервирования используются для резервирования пространства


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

Директива Цель

RESB Резервирует Byte

RESW Резервирует Word

RESD Резервирует Doubleword

RESQ Резервирует Quadword

REST Резервирует 10 Byte

Несколько определений
Вы можете иметь несколько стейтментов определения данных в программе.
Например:
1choice    DB 'Y'    ; ASCII-значение для y = 79H
2number1    DW 12345    ; 12345D = 3039H
3number2    DD   123456789   ; 123456789D = 75BCD15H
Ассемблер выделяет смежную память для нескольких определений
переменных.
Несколько инициализаций

Директива TIMES позволяет выполнить несколько инициализаций одного


значения. Например, массив с именем marks длиной 9 может быть определен и
инициализирован нулем следующим образом:
1marks  TIMES  9  DW  0
Директива TIMES полезна при определении массивов и таблиц. Следующая
программа выводит на экран 9 звёздочек:
1 section .text
2    global _start        ; должно быть объявлено для линкера (ld)
3
4 _start:                 ; сообщаем линкеру входную точку
5    mov edx,9     ; длина сообщения
6    mov ecx, stars     ; сообщение для вывода на экран
7    mov ebx,1     ; файловый дескриптор (stdout)
8    mov eax,4     ; номер системного вызова (sys_write)
9    int 0x80     ; вызов ядра
10 
11    mov eax,1     ; номер системного вызова (sys_exit)
12   int 0x80     ; вызов ядра
13 
14section .data
15stars   times 9 db '*'
Результат выполнения программы:
*********
Определение констант
NASM предоставляет несколько директив, определяющих константы:
   директива EQU;
   директива %assign;
   директива %define.
Директива EQU

Директива EQU используется для определения констант. Её синтаксис


следующий:
ИМЯ_КОНСТАНТЫ EQU-выражение
Например:
1TOTAL_STUDENTS equ 50
Затем вы можете использовать эту константу в программе:
1mov  ecx,  TOTAL_STUDENTS
2cmp  eax,  TOTAL_STUDENTS
Операндом стейтмента EQU может быть выражение:
1LENGTH equ 20
2WIDTH  equ 10
3AREA   equ length * width
Вышеприведенный фрагмент кода определит AREA как 200.
Еще один пример:
1 SYS_EXIT  equ 1
2 SYS_WRITE equ 4
3 STDIN     equ 0
4 STDOUT    equ 1
5 section .text
6    global _start          ; должно быть объявлено для линкера (gcc)
7
8 _start:                   ; сообщаем линкеру входную точку
9    mov eax, SYS_WRITE        
10   mov ebx, STDOUT        
11    mov ecx, msg1        
12   mov edx, len1
13   int 0x80                
14
15   mov eax, SYS_WRITE        
16   mov ebx, STDOUT        
17   mov ecx, msg2        
18   mov edx, len2
19   int 0x80
20
21   mov eax, SYS_WRITE        
22   mov ebx, STDOUT        
23   mov ecx, msg3        
24   mov edx, len3
25   int 0x80
26  
27   mov eax,SYS_EXIT       ; номер системного вызова (sys_exit)
28   int 0x80               ; вызов ядра
29 
30section .data
31msg1 db 'Hello, programmers!',0xA,0xD
32len1 equ $ - msg1
33 
34msg2 db 'Welcome to the world of,', 0xA,0xD
35len2 equ $ - msg2
36 
37msg3 db 'Linux assembly programming! '
38len3 equ $- msg3
Результат выполнения программы:
Hello, programmers!
Welcome to the world of,
Linux assembly programming!
Директива %assign
Директива %assign может быть использована для определения числовых
констант. Эта директива допускает переопределение. Например, вы можете
определить константу TOTAL следующим образом:
1%assign TOTAL 10
Затем в коде вы можете переопределить её:
1%assign  TOTAL  20
Эта директива является чувствительной к регистру.
Директива %define
Директива %define позволяет определять как числовые, так и строковые
константы. Эта директива похожа на директиву #define в языке С. Например, вы
можете определить константу PTR следующим образом:
1%define PTR [EBP+4]
Вышеприведенный код заменяет PTR на [EBP+4].
Эта директива также допускает переопределение и является чувствительной к
регистру.
Ассемблер. Арифметические инструкции
  Светлана Деменева  | 
 
 Уроки Ассемблера
  |


 
  Обновл. 16 Сен 2021  | 
 
 38166
 
 ǀ   1 

На этом уроке мы будем разбираться с арифметическими инструкциями


в ассемблере на примере INC, DEC, ADD, SUB и пр.
Оглавление:
1. Инструкция INC
2. Инструкция DEC
3. Инструкции ADD и SUB
4. Инструкции MUL и IMUL
5. Инструкции DIV и IDIV
Инструкция INC
Инструкция INC (от англ. «INCREMENT») используется для увеличения
операнда на единицу. Она работает с одним операндом, который может
находиться либо в регистре, либо в памяти.
Синтаксис инструкции INC:
INC место_назначения
Операндом место_назначения может быть 8-битный, 16-битный или 32-битный
операнд.
Пример:
1INC EBX      ; выполняем инкремент 32-битного регистра
2INC DL       ; выполняем инкремент 8-битного регистра
3INC [count]  ; выполняем инкремент переменной count
Инструкция DEC

Инструкция DEC (от англ. «DECREMENT») используется для уменьшения


операнда на единицу. Она работает с одним операндом, который может
находиться либо в регистре, либо в памяти.
Синтаксис инструкции DEC:
DEC место_назначения
Операндом место_назначения может быть 8-битный, 16-битный или 32-битный
операнд.
Пример:
1 segment .data
2    count dw  0
3    value db  15
4
5 segment .text
6    inc [count]
7    dec [value]
8
9    mov ebx, count
10   inc word [ebx]
11
12   mov esi, value
13   dec byte [esi]
Инструкции ADD и SUB
Инструкции ADD и SUB используются для выполнения простого
сложения/вычитания двоичных данных размером один byte, word или
doubleword, то есть для сложения или вычитания 8-битных, 16-битных или 32-
битных операндов, соответственно.
Синтаксис инструкций ADD и SUB:
ADD/SUB      место_назначения, источник
Инструкции ADD/SUB могут выполняться между:
   регистром и регистром;
   памятью и регистром;
   регистром и памятью;
   регистром и константами;
   памятью и константами.
Однако, как и для других инструкций, операции типа память-в-память
невозможны с использованием инструкций ADD/SUB. Операции ADD или SUB
устанавливают или сбрасывают флаги переполнения и переноса.
В следующем примере мы спрашиваем у пользователя два числа, сохраняем их
в регистрах EAX и EBX, затем выполняем операцию сложения, сохраняем
результат в ячейке памяти res и выводим его на экран:
1 SYS_EXIT  equ 1
2 SYS_READ  equ 3
3 SYS_WRITE equ 4
4 STDIN     equ 0
5 STDOUT    equ 1
6  
7 segment .data
8  
9    msg1 db "Enter a digit ", 0xA,0xD
10   len1 equ $- msg1
11  
12   msg2 db "Please enter a second digit", 0xA,0xD
13   len2 equ $- msg2
14 
15   msg3 db "The sum is: "
16   len3 equ $- msg3
17 
18segment .bss
19 
20   num1 resb 2
21   num2 resb 2
22   res resb 1    
23 
24section .text
25   global _start    ; должно быть объявлено для использования gcc
26
27_start:             ; сообщаем линкеру входную точку
28   mov eax, SYS_WRITE        
29   mov ebx, STDOUT        
30   mov ecx, msg1        
31   mov edx, len1
32   int 0x80                
33 
34   mov eax, SYS_READ
35   mov ebx, STDIN  
36   mov ecx, num1
37   mov edx, 2
38   int 0x80            
39 
40   mov eax, SYS_WRITE        
41   mov ebx, STDOUT        
42   mov ecx, msg2          
43   mov edx, len2        
44   int 0x80
45 
46   mov eax, SYS_READ  
47   mov ebx, STDIN  
48   mov ecx, num2
49   mov edx, 2
50   int 0x80        
51 
52   mov eax, SYS_WRITE        
53   mov ebx, STDOUT        
54   mov ecx, msg3          
55   mov edx, len3        
56   int 0x80
57 
58   ; перемещаем первое число в регистр EAX, а второе число - в регистр EBX
59   ; и вычитаем ASCII '0' для конвертации в десятичное число
60
61   mov eax, [num1]
62   sub eax, '0'
63
64   mov ebx, [num2]
65   sub ebx, '0'
66 
67   ; складываем eax и ebx
68   add eax, ebx
69 
70   ; добавляем '0' для конвертации суммы из десятичной системы в ASCII
71   add eax, '0'
72 
73   ; сохраняем сумму в ячейке памяти res
74   mov [res], eax
75 
76   ; выводим сумму
77   mov eax, SYS_WRITE        
78   mov ebx, STDOUT
79   mov ecx, res        
80   mov edx, 1        
81   int 0x80
82 
83exit:    
84  
85   mov eax, SYS_EXIT  
86   xor ebx, ebx
87   int 0x80
Результат выполнения программы:
Enter a digit:
3
Please enter a second digit:
4
The sum is:
7
Ниже рассмотрен пример, в котором за счет того, что значения переменных для
арифметических выражений прописаны в самом коде программы, можно
получить код программы короче и проще:
1 section .text
2    global _start    ; должно быть объявлено для использования gcc
3
4 _start:             ; сообщаем линкеру входную точку
5    mov eax,'3'
6    sub     eax, '0'
7
8    mov ebx, '4'
9    sub     ebx, '0'
10   add eax, ebx
11    add eax, '0'
12
13   mov [sum], eax
14   mov ecx,msg
15   mov edx, len
16   mov ebx,1 ; файловый дескриптор (stdout)
17   mov eax,4 ; номер системного вызова (sys_write)
18   int 0x80 ; вызов ядра
19
20   mov ecx,sum
21   mov edx, 1
22   mov ebx,1 ; файловый дескриптор (stdout)
23   mov eax,4 ; номер системного вызова (sys_write)
24   int 0x80 ; вызов ядра
25
26   mov eax,1 ; номер системного вызова (sys_exit)
27   int 0x80 ; вызов ядра
28
29section .data
30   msg db "The sum is:", 0xA,0xD
31   len equ $ - msg  
32   segment .bss
33   sum resb 1
Результат выполнения программы:
The sum is:
7
Инструкции MUL и IMUL

Есть 2 инструкции для умножения двоичных данных:


   инструкция MUL (от англ. «MULTIPLY») обрабатывает данные unsigned;
   инструкция IMUL (от англ. «INTEGER MULTIPLY») обрабатывает данные
signed.
Обе инструкции влияют на флаги переноса и переполнения.
Синтаксис инструкций MUL/IMUL:
MUL/IMUL множитель
Множимое в обоих случаях будет находиться в аккумуляторе, в зависимости от
размера множимого и множителя, и результат умножения также сохраняется в
двух регистрах, в зависимости от размера операндов.
Рассмотрим 3 разных сценария:
Сценарий №1: Перемножаются 2 значения типа byte. Множимое находится в
регистре AL, а множителем является значение типа byte в памяти или в другом
регистре. Результат произведения находится в AX. Старшие 8 бит произведения
хранятся в AH, а младшие 8 бит хранятся в AL:

Сценарий №2: Перемножаются 2 значения типа word. Множимое должно


находиться в регистре AX, а множителем является значение типа word в памяти
или в другом регистре. Например, для такой инструкции, как MUL DX, вы
должны сохранить множитель в DX, а множимое — в AX. В результате
получится значение типа doubleword, для которого понадобятся два регистра.
Часть высшего порядка (крайняя слева) сохраняется в DX, а часть нижнего
порядка (крайняя справа) сохраняется в AX:

Сценарий №3: Перемножаются 2 значения типа doubleword. Множимое


должно находиться в EAX, а множителем является значение типа doubleword,
хранящееся в памяти или в другом регистре. Результат умножения сохраняется
в регистрах EDX и EAX. Биты старшего порядка сохраняются в регистре EDX,
а биты младшего порядка сохраняются в регистре EAX:

Пример:
1MOV AL, 10
2MOV DL, 25
3MUL DL
4...
5MOV DL, 0FFH ; DL= -1
6MOV AL, 0BEH ; AL = -66
7IMUL DL
А в следующем примере мы 3 умножаем на 2 и выводим результат:
1 section .text
2    global _start    ; должно быть объявлено для использования gcc
3
4 _start:             ; сообщаем линкеру входную точку
5  
6    mov al,'3'
7    sub     al, '0'
8
9    mov bl, '2'
10   sub     bl, '0'
11    mul bl
12   add al, '0'
13
14   mov [res], al
15   mov ecx,msg
16   mov edx, len
17   mov ebx,1 ; файловый дескриптор (stdout)
18   mov eax,4 ; номер системного вызова (sys_write)
19   int 0x80 ; вызов ядра
20
21   mov ecx,res
22   mov edx, 1
23   mov ebx,1 ; файловый дескриптор (stdout)
24   mov eax,4 ; номер системного вызова (sys_write)
25   int 0x80 ; вызов ядра
26
27   mov eax,1 ; номер системного вызова (sys_exit)
28   int 0x80 ; вызов ядра
29 
30section .data
31msg db "The result is:", 0xA,0xD
32len equ $- msg  
33segment .bss
34res resb 1
Результат выполнения программы:
The result is:
6
Инструкции DIV и IDIV
Операция деления генерирует два элемента: частное и остаток. В случае
умножения переполнения не происходит, так как для хранения результата
используются регистры двойной длины. Однако в случае деления,
переполнение может произойти. Если это произошло, то процессор генерирует
прерывание.
Инструкция DIV (от англ. «DIVIDE») используется с данными unsigned,
а инструкция IDIV (от англ. «INTEGER DIVIDE») используется с данными
signed.
Синтаксис инструкций DIV/IDIV:
DIV/IDIV     делитель
Делимое находится в аккумуляторе. Обе инструкции могут работать с 8-
битными, 16-битными или 32-битными операндами. Данная операция влияет на
все 6 флагов состояния.
Рассмотрим следующие 3 сценария:
Сценарий №1: Делителем является значение типа byte. Предполагается, что
делимое находится в регистре AX (16 бит). После деления частное переходит в
регистр AL, а остаток переходит в регистр AH:

Сценарий №2: Делителем является значение типа word. Предполагается, что


делимое имеет длину 32 бита и находится в регистрах DX и AX. Старшие 16
бит находятся в DX, а младшие 16 бит — в AX. После деления 16-битное
частное попадает в регистр AX, а 16-битный остаток — в регистр DX:
Сценарий №3: Делителем является значение типа doubleword.
Предполагается, что размер делимого составляет 64 бита и оно размещено в
регистрах EDX и EAX. Старшие 32 бита находятся в EDX, а младшие 32 бита
— в EAX. После деления 32-битное частное попадает в регистр EAX, а 32-
битный остаток — в регистр EDX:

В следующем примере мы делим 8 на 2. Делимое 8 сохраняется в 16-битном


регистре AX, а делитель 2 — в 8-битном регистре BL:
1 section .text
2    global _start    ; должно быть объявлено для использования gcc
3
4 _start:             ; сообщаем линкеру входную точку
5    mov ax,'8'
6    sub     ax, '0'
7
8    mov bl, '2'
9    sub     bl, '0'
10   div bl
11    add ax, '0'
12
13   mov [res], ax
14   mov ecx,msg
15   mov edx, len
16   mov ebx,1 ; файловый дескриптор (stdout)
17   mov eax,4 ; номер системного вызова (sys_write)
18   int 0x80 ; вызов ядра
19
20   mov ecx,res
21   mov edx, 1
22   mov ebx,1 ; файловый дескриптор (stdout)
23   mov eax,4 ; номер системного вызова (sys_write)
24   int 0x80 ; вызов ядра
25
26   mov eax,1 ; номер системного вызова (sys_exit)
27   int 0x80 ; вызов ядра
28
29section .data
30msg db "The result is:", 0xA,0xD
31len equ $- msg  
32segment .bss
33res resb 1
Результат выполнения программы:
The result is:
4
Ассемблер. Логические инструкции
  Светлана Деменева  | 
 
 Уроки Ассемблера
  |


 
  Обновл. 16 Сен 2021  | 
 
 23562
Набор команд процессора содержит логические
инструкции AND, OR, XOR, TEST и NOT, которые проверяют, устанавливают
и очищают биты в соответствии с потребностями программы.
Оглавление:
1. Логические инструкции
2. Инструкция AND
3. Инструкция OR
4. Инструкция XOR
5. Инструкция TEST
6. Инструкция NOT
Логические инструкции
Синтаксис использования логических инструкций следующий:
   AND операнд1, операнд2
   OR операнд1, операнд2
   XOR операнд1, операнд2
   TEST операнд1, операнд2
   NOT операнд1
Во всех случаях первый операнд находится либо в регистре, либо в памяти.
Второй операнд может находиться либо в регистре/памяти, либо являться
константой. Тем не менее, операции типа «память-в-память» невозможны.

Инструкция AND

Инструкция AND выполняет побитовую операцию И. Побитовая операция И


возвращает 1, если совпадающие биты обоих операндов равны 1, в противном
случае — возвращается 0. Например:
Операнд №1: 0101
Операнд №2: 0011
--
После применения AND -> Операнд №1: 0001
Операция AND может быть использована для сброса одного или нескольких
бит. Например, допустим, что регистр BL содержит 0011 1010. Если вам
необходимо сбросить старшие биты в ноль, то вы выполняете операцию AND
между этим регистром и числом 0FH:
1AND BL, 0FH   ; эта строка устанавливает для регистра BL значение 0000
1010
Давайте рассмотрим другой пример. Если вы хотите проверить, является ли
определенное число чётным или нечётным, то с помощью простого теста вы
сможете проверить младший значимый бит числа. Если им окажется 1, то число
нечётное, если же 0 — число чётное.
Например, если число находится в регистре AL, то мы можем написать
следующее:
1AND AL, 01H     ; выполняем операцию AND с 0000 0001
2JZ    EVEN_NUMBER
Следующая программа это проиллюстрирует:
1 Live Demo
2 section .text
3    global _start            ; должно быть объявлено для использования gcc
4
5 _start:                     ; сообщаем линкеру входную точку
6    mov   ax,   8h           ; записываем 8 в регистр AX
7    and   ax, 1              ; выполняем операцию AND с AX
8    jz    evnn
9    mov   eax, 4             ; номер системного вызова (sys_write)
10   mov   ebx, 1             ; файловый дескриптор (stdout)
11    mov   ecx, odd_msg       ; сообщение для вывода на экран
12   mov   edx, len2          ; длина сообщения
13   int   0x80               ; вызов ядра
14   jmp   outprog
15 
16evnn:  
17  
18   mov   ah,  09h
19   mov   eax, 4             ; номер системного вызова (sys_write)
20   mov   ebx, 1             ; файловый дескриптор (stdout)
21   mov   ecx, even_msg      ; сообщение для вывода на экран
22   mov   edx, len1          ; длина сообщения
23   int   0x80               ; вызов ядра
24 
25outprog:
26 
27   mov   eax,1              ; номер системного вызова (sys_exit)
28   int   0x80               ; вызов ядра
29 
30section   .data
31even_msg  db  'Even Number!' ; сообщение с чётным числом
32len1  equ  $ - even_msg
33  
34odd_msg db  'Odd Number!'    ; сообщение с нечётным числом
35len2  equ  $ - odd_msg
Результат выполнения программы:
Even Number!
Изменяем значение в регистре AX, указав нечётную цифру:
1mov  ax, 9h    ; записываем 9 в регистр AX
И результат:
Odd Number!
Вы можете точно так же очистить весь регистр, используя AND с 00H.

Инструкция OR
Инструкция OR выполняет побитовую операцию ИЛИ. Побитовый оператор
ИЛИ возвращает 1, если совпадающие биты одного или обоих операндов
равны 1, и возвращает 0, если оба бита равны нулю. Например:
Операнд №1: 0101
Операнд №2: 0011
--
После применения OR -> Операнд №1: 0111
Операция OR может быть использована для указания одного или нескольких
бит. Например, допустим, что регистр AL содержит 0011 1010, и нам
необходимо установить четыре младших бита. Мы можем сделать это,
используя оператор OR со значением 0000 1111 (т.е. с регистром FH):
1OR BL, 0FH                   ; эта строка устанавливает для BL значение 0011 1111
В качестве другого примера давайте сохраним значения 5 и 3 в регистрах AL и
BL соответственно. Таким образом, следующая инструкция:
1OR AL, BL
Должна сохранить 7 в регистре AL:
1 section .text
2    global _start            ; должно быть объявлено для использования gcc
3
4 _start:                     ; сообщаем линкеру входную точку
5    mov    al, 5             ; записываем 5 в регистр AL
6    mov    bl, 3             ; записываем 3 в регистр BL
7    or     al, bl            ; выполняем операцию OR с регистрами AL и BL,
результатом должно быть число 7
8
   add    al, byte '0'      ; выполняем конвертацию из десятичной системы в
9
ASCII
10
11
   mov    [result],  al
12
   mov    eax, 4
13
   mov    ebx, 1
14
   mov    ecx, result
15
   mov    edx, 1
16
   int    0x80
17
    
18
outprog:
19
   mov    eax,1             ; номер системного вызова (sys_exit)
20
   int    0x80              ; вызов ядра
21
22section    .bss
result resb 1
Результат выполнения программы:
7

Инструкция XOR

Инструкция XOR выполняет побитовую операцию XOR. Побитовая операция


XOR возвращает 1, если биты операндов являются различными, если же они
являются одинаковыми (оба равны 0 или 1), то возвращается 0. Например:
Операнд №1: 0101
Операнд №2: 0011
--
После применения XOR -> Операнд №1: 0110
Следующая инструкция очищает регистр (сбрасывает все биты в 0):
1XOR     EAX, EAX
Инструкция TEST
Инструкция TEST работает так же, как и инструкция AND, но, в отличие от
инструкции AND, она не изменяет первый операнд. Таким образом, если нам
нужно проверить, является ли число в регистре чётным или нечётным, мы
можем сделать это, используя инструкцию TEST, без необходимости изменять
исходное число:
1TEST    AL, 01H
2JZ      EVEN_NUMBER
Инструкция NOT

Инструкция NOT выполняет побитовую операцию НЕ. Побитовая операция


НЕ изменяет биты операнда на противоположные. Операнд может находиться
либо в регистре, либо в памяти. Например:
Операнд №1: 0101 0011
--
После применения NOT -> Операнд №1: 1010 1100
Ассемблер. Условия
  Светлана Деменева  | 
 
 Уроки Ассемблера
  |


 
  Обновл. 19 Сен 2021  | 
 
 39971
 
 ǀ   1 

К условиям в ассемблере относятся инструкции циклов и ветвления. Эти


инструкции могут изменять поток выполнения кода в программе.
Оглавление:
1. Типы прыжков
2. Инструкция CMP
3. Прыжок без условия
4. Прыжок с условием
Типы прыжков
Есть 2 типа выполнения условий в ассемблере:
   Прыжок без условия (или «безусловный прыжок») — выполняется
инструкцией JMP. Выполнение данной инструкции часто включает в себя
передачу управления в адрес инструкции, которая не следует за выполняемой в
настоящее время инструкцией. Результатом передачи управления может быть
выполнение нового набора инструкций или повторное выполнение текущих
инструкций.
   Прыжок с условием (или «условный прыжок») — выполняется с помощью
инструкций типа J<условие> и зависит от самого условия. Условные
инструкции, изменяя значение смещения в регистре IP, передают управление,
прерывая последовательный поток выполнения кода.
Прежде чем разбираться с этими двумя типами инструкций детально, давайте
рассмотрим инструкцию CMP.

Инструкция CMP

Инструкция CMP (от англ. «COMPARE») сравнивает два операнда.


Фактически, она выполняет операцию вычитания между двумя операндами для
проверки того, равны ли эти операнды или нет. Используется вместе с
инструкцией условного прыжка.
Синтаксис инструкции CMP:
CMP назначение, источник
Инструкция CMP сравнивает два числовых поля. Операнд назначения может
находиться либо в регистре, либо в памяти. Исходным операндом (источник)
могут быть константы, регистры или память. Например:
1CMP DX, 00  ; сравниваем значение регистра DX с нулем
2JE  L7      ; если true, то переходим к метке L7
3.
4.
5L7: ...
Инструкция CMP часто используется для проверки того, достигла ли
переменная-счетчик максимального количества раз выполнения цикла или нет.
Например:
1INC EDX
2CMP EDX, 10 ; сравниваем, достиг ли счетчик значения 10 или нет
3JLE LP1     ; если его значение меньше или равно 10, то тогда переходим к
LP1
Прыжок без условия
Как мы уже говорили, безусловный прыжок выполняется инструкцией JMP,
которая включает в себя имя метки, куда следует перебросить точку
выполнения программы:
JMP    label
В следующем примере мы рассмотрим использование инструкции JMP:
1MOV  AX, 00    ; инициализируем регистр AX значением 0
2MOV  BX, 00    ; инициализируем регистр BX значением 0
3MOV  CX, 01    ; инициализируем регистр CX значением 1
4L20:
5ADD  AX, 01    ; выполняем инкремент регистра AX
6ADD  BX, AX    ; добавляем AX к BX
7SHL  CX, 1     ; сдвиг влево регистра CX, что, в свою очередь, удваивает его
значение
8
JMP  L20       ; повторно выполняем стейтменты
Прыжок с условием

Если при выполнении операции условного прыжка выполняется обозначенное


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

Тестируемые
Инструкция Описание
флаги

JE/JZ Jump Equal (равно) или Jump Zero (ноль) ZF

Jump Not Equal (не равно)


JNE/JNZ ZF
или Jump Not Zero (не ноль)

Jump Greater (больше)
JG/JNLE или Jump Not Less/Equal (не OF, SF, ZF
меньше/равно)

Jump Greater/Equal (больше/равно)
JGE/JNL OF, SF
или Jump Not Less (не меньше)
Jump Less (меньше)
JL/JNGE или Jump Not Greater/Equal (не OF, SF
больше/равно)

Jump Less/Equal (меньше/равно)
JLE/JNG OF, SF, ZF
или Jump Not Greater (не больше)

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


для данных без знака в логических операциях:

Тестируемые
Инструкция Описание
флаги

JE/JZ Jump Equal (равно) или Jump Zero (ноль) ZF

Jump Not Equal (не равно)


JNE/JNZ ZF
или Jump Not Zero (не ноль)

Jump Above (больше)
JA/JNBE или Jump Not Below/Equal (не CF, ZF
меньше/равно)

Jump Above/Equal (больше/равно)
JAE/JNB CF
или Jump Not Below (не меньше)

Jump Below (меньше)
JB/JNAE или Jump Not Above/Equal (не CF
больше/равно)

Jump Below/Equal (меньше/равно)
JBE/JNA AF, CF
или Jump Not Above (не больше)

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


и проверяют значение флагов:

Тестируемые
Инструкция Описание
флаги

JCXZ Jump если CX равно Zero none


JC Jump если Carry (перенос) CF

JNC Jump если No Carry (нет переноса) CF

JO Jump если Overflow (переполнение) OF

Jump если No Overflow (нет


JNO OF
переполнения)

Jump Parity или Jump Parity Even (если


JP/JPE PF
чётность)

Jump No Parity или Jump Parity Odd
JNP/JPO PF
(если нечётность)

JS Jump Sign (отрицательное значение) SF

JNS Jump No Sign (положительное значение) SF

Пример синтаксиса набора инструкций типа J<условие>:


1CMP AL, BL
2JE EQUAL
3CMP AL, BH
4JE EQUAL
5CMP AL, CL
6JE EQUAL
7NON_EQUAL: ...
8EQUAL: ...
В качестве примера рассмотрим следующую программу, которая определяет и
выводит на экран наибольшую из 3 целочисленных переменных:
1 section .text
2    global _start         ; должно быть объявлено для использования gcc
3  
4 _start:                 ; сообщаем линкеру входную точку
5    mov   ecx, [num1]
6    cmp   ecx, [num2]
7    jg    check_third_num
8    mov   ecx, [num2]
9   
10 check_third_num:
11  
12   cmp   ecx, [num3]
13   jg    _exit
14   mov   ecx, [num3]
15  
16 _exit:
17  
18   mov   [largest], ecx
19   mov   ecx,msg
20   mov   edx, len
21   mov   ebx,1 ; файловый дескриптор (stdout)
22   mov   eax,4 ; номер системного вызова (sys_write)
23   int   0x80 ; вызов ядра
24
25   mov   ecx,largest
26   mov   edx, 2
27   mov   ebx,1 ; файловый дескриптор (stdout)
28   mov   eax,4 ; номер системного вызова (sys_write)
29   int   0x80 ; вызов ядра
30    
31   mov   eax, 1
32   int   80h
33 
34section .data
35  
36   msg db "The largest number is: ", 0xA,0xD
37   len equ $- msg
38   num1 dd '47'
39   num2 dd '22'
40   num3 dd '31'
41 
42segment .bss
43   largest resb 2
Результат выполнения программы:
The largest number is:
47
Ассемблер. Циклы
  Светлана Деменева  | 
 
 Уроки Ассемблера
  |


 
  Обновл. 4 Сен 2021  | 
 
 27972
 
 ǀ   3 
Инструкция JMP может быть использована для выполнения циклов.
Например, следующий фрагмент кода может использоваться для выполнения
тела цикла 10 раз:
1MOV CL, 10
2L1:
3<LOOP-BODY>
4DEC CL
5JNZ L1
Набор инструкций процессора включает в себя группу инструкций цикла для
реализации итерации. Основная инструкция цикла LOOP имеет следующий
синтаксис:
LOOP label
Где label — это метка, которая идентифицирует целевую инструкцию.
Инструкция LOOP предполагает, что регистр ECX содержит в себе счетчик
циклов. Когда инструкция цикла выполняется, регистр ECX уменьшается, точка
выполнения программы переходит к метке до тех пор, пока значение регистра
ECX (т.е. значение счетчика цикла) не достигнет нуля.
Вышеприведенный пример можно записать следующим образом:
1mov ECX,10
2l1:
3<loop body>
4loop l1
А здесь с помощью цикла мы выводим на экран цифры от 1 до 9:
1 section .text
2    global _start        ; должно быть объявлено для использования gcc
3
4 _start:                ; сообщаем линкеру входную точку
5    mov ecx,10
6    mov eax, '1'
7
8 l1:
9    mov [num], eax
10   mov eax, 4
11    mov ebx, 1
12   push ecx
13
14   mov ecx, num        
15   mov edx, 1        
16   int 0x80
17
18   mov eax, [num]
19   sub eax, '0'
20   inc eax
21   add eax, '0'
22   pop ecx
23   loop l1
24
25   mov eax,1             ; номер системного вызова (sys_exit)
26   int 0x80              ; вызов ядра
27section .bss
28num resb 1
Результат выполнения программы:
123456789:
Ассемблер. Числа
  Светлана Деменева  | 
 
 Уроки Ассемблера
  |


 
  Обновл. 29 Янв 2023  | 
 
 18320
Обычно числовые данные в Ассемблере представлены в двоичной
системе. Арифметические инструкции работают с двоичными данными.
Когда числа отображаются на экране или вводятся с клавиатуры, они
имеют ASCII-форму.
Представление чисел в Ассемблере
До сих пор мы конвертировали входные данные из ASCII-формы в двоичную
систему для выполнения арифметических операций, а затем результат обратно в
ASCII-форму. Например:
1 section .text
2    global _start        ; должно быть объявлено для использования gcc
3
4 _start:                ; сообщаем линкеру входную точку
5    mov eax,'3'
6    sub     eax, '0'
7
8    mov ebx, '4'
9    sub     ebx, '0'
10   add eax, ebx
11    add eax, '0'
12
13   mov [sum], eax
14   mov ecx,msg
15   mov edx, len
16   mov ebx,1          ; файловый дескриптор (stdout)
17   mov eax,4          ; номер системного вызова (sys_write)
18   int 0x80          ; вызов ядра
19
20   mov ecx,sum
21   mov edx, 1
22   mov ebx,1          ; файловый дескриптор (stdout)
23   mov eax,4          ; номер системного вызова (sys_write)
24   int 0x80          ; вызов ядра
25
26   mov eax,1          ; номер системного вызова (sys_exit)
27   int 0x80          ; вызов ядра
28
29section .data
30msg db "The sum is:", 0xA,0xD
31len equ $ - msg  
32segment .bss
33sum resb 1
Результат выполнения программы:
The sum is:
7
Программирование на ассемблере позволяет более эффективно обрабатывать
числа в двоичном виде. Десятичные числа могут быть представлены в
следующих двух формах:
   ASCII-форма;
   BCD-форма (или «Двоично-десятичный код»).
ASCII-представление

В ASCII-представлении десятичные числа хранятся в виде строки ASCII-


символов. Есть 4 инструкции для обработки чисел в ASCII-представлении:
   AAA (англ. «Adjust After Addition») — настроить после добавления;
   AAS (англ. «Adjust After Subtraction») — настроить после вычитания;
   AAM (англ. «Adjust After Multiplication») — настроить после умножения;
   AAD (англ. «Adjust After Division») — настроить после деления.
Эти инструкции не принимают никаких операндов и предполагают, что
требуемый операнд находится в регистре AL.
В следующем примере мы будем использовать инструкцию AAS:
1 section .text
2    global _start        ; должно быть объявлено для использования gcc
3
4 _start:                ; сообщаем линкеру входную точку
5    sub     ah, ah
6    mov     al, '9'
7    sub     al, '3'
8    aas
9    or      al, 30h
10   mov     [res], ax
11
12   mov edx,len         ; длина сообщения
13   mov ecx,msg         ; сообщение для вывода на экран
14   mov ebx,1         ; файловый дескриптор (stdout)
15   mov eax,4         ; номер системного вызова (sys_write)
16   int 0x80         ; вызов ядра
17
18   mov edx,1         ; длина сообщения
19   mov ecx,res         ; сообщение для вывода на экран
20   mov ebx,1         ; файловый дескриптор (stdout)
21   mov eax,4         ; номер системного вызова (sys_write)
22   int 0x80         ; вызов ядра
23
24   mov eax,1         ; номер системного вызова (sys_exit)
25   int 0x80         ; вызов ядра
26
27section .data
28msg db 'The Result is:',0xa
29len equ $ - msg
30section .bss
31res resb 1
Результат выполнения программы:
The Result is:
6
BCD-представление
Существует два типа BCD-представления:
   распакованное BCD-представление;
   упакованное BCD-представление.
В распакованном BCD-представлении каждый байт хранит двоичный
эквивалент каждой десятичной цифры числа. Четыре инструкции настройки
ASCII: AAA, AAS, AAM и AAD; также могут использоваться с распакованным
BCD-представлением.
В упакованном BCD-представлении каждая цифра сохраняется с
использованием 4 бит. Две десятичные цифры упаковываются в 1 байт. Есть две
инструкции для обработки этих чисел:
   DAA (англ. «Decimal Adjust After Addition») — десятичная настройка после
добавления;
   DAS (англ. «Decimal Adjust After Subtraction») — десятичная настройка после
вычитания.
Обратите внимание, что в упакованном BCD-представлении отсутствует
поддержка операций умножения и деления.
В следующей программе мы выполним операцию сложения двух 5-значных
десятичных чисел и выведем на экран их сумму:
1 section .text
2    global _start        ; должно быть объявлено для использования gcc
3  
4 _start:                ; сообщаем линкеру входную точку
5  
6    mov     esi, 4       ; указываем на крайнюю правую цифру
7    mov     ecx, 5       ; количество цифр
8    clc
9 add_loop:  
10   mov al, [num1 + esi]
11    adc al, [num2 + esi]
12   aaa
13   pushf
14   or al, 30h
15   popf
16
17   mov [sum + esi], al
18   dec esi
19   loop add_loop
20
21   mov edx,len         ; длина сообщения
22   mov ecx,msg         ; сообщение для вывода на экран
23   mov ebx,1         ; файловый дескриптор (stdout)
24   mov eax,4         ; номер системного вызова (sys_write)
25   int 0x80         ; вызов ядра
26
27   mov edx,5         ; длина сообщения
28   mov ecx,sum         ; сообщение для вывода на экран
29   mov ebx,1         ; файловый дескриптор (stdout)
30   mov eax,4         ; номер системного вызова (sys_write)
31   int 0x80         ; вызов ядра
32
33   mov eax,1         ; номер системного вызова (sys_exit)
34   int 0x80         ; вызов ядра
35 
36section .data
37msg db 'The Sum is:',0xa
38len equ $ - msg
39num1 db '12345'
40num2 db '23456'
41sum db '     '
Результат выполнения программы:
The Sum is:
35801
Ассемблер. Строки
  Светлана Деменева  | 
 
 Уроки Ассемблера
  |


 
  Обновл. 4 Сен 2021  | 
 
 31679
 
 ǀ   2 

На предыдущих уроках мы уже использовали строки разной длины.


Оглавление:
1. Строки в Ассемблере
2. Строковые инструкции
3. Инструкция MOVS
4. Инструкция LODS
5. Инструкция STOS
6. Инструкция CMPS
7. Инструкция SCAS
8. Префиксы повторения
Строки в Ассемблере
Строки с переменной в качестве длины могут содержать столько символов,
сколько необходимо. Как правило, длина строки указывается одним из
следующих двух способов:
   явное содержание длины строки;
   использование сигнального символа.
Мы можем явно хранить длину строки, используя символ счетчика
местоположения $, который предоставляет текущее значение счетчика
местоположения строки. Например:
1msg  db  'Hello, world!',0xa  ; наша строка
2len  equ  $ - msg             ; длина нашей строки
Символ $ указывает на byte после последнего символа строковой
переменной msg. Следовательно, $ - msg указывает на длину строки. Мы также
можем написать:
1msg db 'Hello, world!',0xa  ; наша строка
2len equ 13                  ; длина нашей строки
В качестве альтернативы мы можем хранить строки с завершающим
сигнальным символом. Сигнальным символом является специальный символ,
который не должен находиться внутри строки. Например:
1message DB 'I am loving it!', 0
Строковые инструкции

Каждая строковая инструкция может требовать исходного операнда и операнда


назначения. Для 32-битных сегментов строковые инструкции
используют регистры ESI и EDI, чтобы указать на операнды источника и
назначения, соответственно.
Однако для 16-битных сегментов, чтобы указать на источник и место
назначения, используются другие регистры: SI и DI.
Существует 5 основных инструкций для работы со строками в Ассемблере:
   MOVS — эта инструкция перемещает 1 byte, word или doubleword данных
из одной ячейки памяти в другую;
   LODS — эта инструкция загружается из памяти. Если операндом является
значение типа byte, то оно загружается в регистр AL, если типа word —
загружается в регистр AX, если типа doubleword — загружается в регистр EAX;
   STOS — эта инструкция сохраняет данные из регистра (AL, AX или EAX) в
память;
   CMPS — эта инструкция сравнивает два элемента данных в памяти. Данные
могут быть размера byte, word или doubleword;
   SCAS — эта инструкция сравнивает содержимое регистра (AL, AX или EAX)
с содержимым элемента, находящегося в памяти.
Каждая из вышеприведенных инструкций имеет версии byte, word или
doubleword, а строковые инструкции могут повторяться с использованием
префикса повторения.
Эти инструкции используют пары регистров ES:DI и DS:SI, где регистры DI и
SI содержат валидные адреса смещения, которые относятся к байтам,
хранящимся в памяти. SI обычно ассоциируется с DS (сегмент данных), а DI
всегда ассоциируется с ES (дополнительный сегмент).
Регистры DS:SI (или ESI) и ES:DI (или EDI) указывают на операнды источника
и назначения, соответственно. Предполагается, что операндом-источником
является DS:SI (или ESI), а операндом назначения — место в памяти, на
которое указывает пара ES:DI (или EDI).
Для 16-битных адресов используются регистры SI и DI, а для 32-битных
адресов используются регистры ESI и EDI.
В следующей таблице представлены различные версии строковых инструкций и
предполагаемое место операндов:

Основная Операнды Операция Операция Операция


инструкция в: byte word doubleword

MOVS ES:DI, DS:SI MOVSB MOVSW MOVSD

LODS AX, DS:SI LODSB LODSW LODSD

STOS ES:DI, AX STOSB STOSW STOSD

CMPS DS:SI, ES:DI CMPSB CMPSW CMPSD


SCAS ES:DI, AX SCASB SCASW SCASD

Инструкция MOVS
Инструкция MOVS используется для копирования элемента данных (byte,
word или doubleword) из исходной строки в строку назначения. Исходная строка
указывается с помощью DS:SI, а строка назначения указывается с
помощью ES:DI.
Рассмотрим это на практике:
1 section .text
2    global _start        ; должно быть объявлено для использования gcc
3
4 _start:                ; сообщаем линкеру входную точку
5    mov ecx, len
6    mov esi, s1
7    mov edi, s2
8    cld
9    rep movsb
10
11    mov edx,20         ; длина сообщения
12   mov ecx,s2         ; сообщение для вывода на экран
13   mov ebx,1         ; файловый дескриптор (stdout)
14   mov eax,4         ; номер системного вызова (sys_write)
15   int 0x80         ; вызов ядра
16
17   mov eax,1         ; номер системного вызова (sys_exit)
18   int 0x80         ; вызов ядра
19
20section .data
21s1 db 'Hello, world!',0 ; первая строка
22len equ $-s1
23 
24section .bss
25s2 resb 20              ; назначение
Результат:
Hello, world!

Инструкция LODS

В криптографии шифр Цезаря является одним из самых простых известных


методов шифрования. В этом методе каждый символ в открытом тексте
заменяется символом, находящимся на некотором постоянном числе позиций
левее или правее него в алфавите.
В этом примере давайте зашифруем данные, просто заменив каждую букву
алфавита со сдвигом в две буквы, таким образом A будет заменено
на C, B на D и т.д.
Мы используем инструкцию LODS для помещения оригинальной
строки password в память:
1 section .text
2    global _start         ; должно быть объявлено для использования gcc
3
4 _start:                  ; сообщаем линкеру входную точку
5    mov    ecx, len
6    mov    esi, s1
7    mov    edi, s2
8
9 loop_here:
10   lodsb
11    add al, 02
12   stosb
13   loop    loop_here          
14   cld
15   rep     movsb
16
17   mov     edx,20        ; длина сообщения
18   mov     ecx,s2        ; сообщение для вывода на экран
19   mov     ebx,1         ; файловый дескриптор (stdout)
20   mov     eax,4         ; номер системного вызова (sys_write)
21   int     0x80          ; вызов ядра
22
23   mov     eax,1         ; номер системного вызова (sys_exit)
24   int     0x80          ; вызов ядра
25
26section .data
27s1 db 'password', 0      ; источник
28len equ $-s1
29 
30section .bss
31s2 resb 10               ; назначение
Результат:
rcuuyqtf
Инструкция STOS
Инструкция STOS копирует элемент данных из регистров AL (для byte —
STOSB), AX (для word — STOSW) или EAX (для doubleword — STOSD) в
строку назначения, на которую указывает ES:DI в памяти.
В следующем примере мы с помощью инструкций LODS и STOS будем
конвертировать строки из верхнего регистра в нижний регистр:
1 section .text
2    global _start        ; должно быть объявлено для использования gcc
3
4 _start:                ; сообщаем линкеру входную точку
5    mov    ecx, len
6    mov    esi, s1
7    mov    edi, s2
8
9 loop_here:
10   lodsb
11    or      al, 20h
12   stosb
13   loop    loop_here
14   cld
15   rep movsb
16
17   mov edx,20         ; длина сообщения
18   mov ecx,s2         ; сообщение для вывода на экран
19   mov ebx,1         ; файловый дескриптор (stdout)
20   mov eax,4         ; номер системного вызова (sys_write)
21   int 0x80         ; вызов ядра
22
23   mov eax,1         ; номер системного вызова (sys_exit)
24   int 0x80         ; вызов ядра
25
26section .data
27s1 db 'HELLO, WORLD', 0 ; источник
28len equ $-s1
29 
30section .bss
31s2 resb 20              ; назначение
Результат:
hello, world

Инструкция CMPS
Инструкция CMPS сравнивает две строки. Эта инструкция сравнивает два
элемента данных одного byte, word или doubleword, на которые указывают
регистры DS:SI и ES:DI, и устанавливает соответствующие флаги. Вы также
можете использовать инструкции прыжков вместе с этой инструкцией.
В следующем примере мы будем сравнивать две строки, используя инструкцию
CMPS:
1 section .text
2    global _start            ; должно быть объявлено для использования gcc
3
4 _start:                    ; сообщаем линкеру входную точку
5    mov esi, s1
6    mov edi, s2
7    mov ecx, lens2
8    cld
9    repe  cmpsb
10   jecxz  equal             ; выполняем прыжок, когда ECX равен нулю
11  
12   ; Если не равно, то выполняем следующий код
13   mov eax, 4
14   mov ebx, 1
15   mov ecx, msg_neq
16   mov edx, len_neq
17   int 80h
18   jmp exit
19
20equal:
21   mov eax, 4
22   mov ebx, 1
23   mov ecx, msg_eq
24   mov edx, len_eq
25   int 80h
26
27exit:
28   mov eax, 1
29   mov ebx, 0
30   int 80h
31
32section .data
33s1 db 'Hello, world!',0      ; наша первая строка
34lens1 equ $-s1
35 
36s2 db 'Hello, there!', 0     ; наша вторая строка
37lens2 equ $-s2
38 
39msg_eq db 'Strings are equal!', 0xa
40len_eq  equ $-msg_eq
41 
42msg_neq db 'Strings are not equal!'
43len_neq equ $-msg_neq
Результат:
Strings are not equal!

Инструкция SCAS
Инструкция SCAS используется для поиска определенного символа или
набора символов в строке. Искомый элемент данных должен находиться в
регистрах AL (для SCASB), AX (для SCASW) или EAX (для SCASD). Искомая
строка должна находиться в памяти, и на нее должны
указывать ES:DI (или EDI).
Рассмотрим использование инструкции SCAS на практике:
1 section .text
2    global _start        ; должно быть объявлено для использования gcc
3
4 _start:                ; сообщаем линкеру входную точку
5  
6    mov ecx,len
7    mov edi,my_string
8    mov al , 'e'
9    cld
10   repne scasb
11    je found ; когда нашли
12 
13   ; Если не нашли, то выполняем следующее
14
15   mov eax,4
16   mov ebx,1
17   mov ecx,msg_notfound
18   mov edx,len_notfound
19   int 80h
20   jmp exit
21
22found:
23   mov eax,4
24   mov ebx,1
25   mov ecx,msg_found
26   mov edx,len_found
27   int 80h
28
29exit:
30   mov eax,1
31   mov ebx,0
32   int 80h
33
34section .data
35my_string db 'hello world', 0
36len equ $-my_string  
37 
38msg_found db 'found!', 0xa
39len_found equ $-msg_found
40 
41msg_notfound db 'not found!'
42len_notfound equ $-msg_notfound
Результат:
found!

Префиксы повторения
Префикс REP, если он установлен перед строковой инструкцией
(например, REP MOVSB), вызывает повторение инструкции на основе
счетчика, размещенного в регистре CX. REP выполняет инструкцию,
уменьшает CX на 1 и проверяет, равен ли CX нулю. Он повторяет обработку
инструкций, пока CX не станет равным нулю.
Флаг направления (DF) определяет направление операции:
   Используйте CLD (англ. «Clear Direction Flag» = «Сбросить флаг
направления», DF = 0), чтобы выполнить операцию слева направо.
   Используйте STD (англ. «Set Direction Flag» = «Установить флаг
направления», DF = 1), чтобы выполнить операцию справа налево.
Префикс REP также имеет следующие вариации:
   REP — это безусловное повторение, которое повторяет операцию, пока CX не
станет равным нулю.
   REPE или REPZ — это условное повторение, которое повторяет операцию до
тех пор, пока нулевой флаг (ZF) указывает на равенство нулю результата (сам
ZF установлен в единицу). Повторение останавливается, когда ZF указывает на
неравенство результата нулю (ZF сброшен в ноль), или когда CX равен нулю.
   REPNE или REPNZ — это также условное повторение, которое повторяет
операцию до тех пор, пока нулевой флаг указывает на неравенство нулю
результата. Повторение останавливается, когда ZF указывает на равенство нулю
результата, или когда CX уменьшается до нуля.
Ассемблер. Массивы
  Светлана Деменева  | 
 
 Уроки Ассемблера
  |


 
  Обновл. 16 Сен 2021  | 
 
 33548
 
 ǀ   3 

Мы уже обсуждали, что в Ассемблере директивы определения данных


используются для выделения памяти переменным. Переменная также может
быть инициализирована определенным значением. Инициализированное
значение может быть указано в шестнадцатеричной, десятичной или двоичной
форме.
Например, мы можем определить переменную months типа word любым из
следующих способов:
1MONTHS DW 12
2MONTHS DW 0CH
3MONTHS DW 0110B
Директивы определения данных также могут использоваться для
определения одномерного массива. Например, давайте определим одномерный
массив чисел: 
1NUMBERS DW  34,  45,  56,  67,  75, 89
Здесь мы объявили массив из шести значений типа word, каждое из которых
инициализировано числами 34, 45, 56, 67, 75, 89 (соответственно). Таким
образом, мы выделили 12 байт (2 * 6 = 12) памяти. Символьный адрес первого
числа будет NUMBERS, второго числа — NUMBERS + 2 и так далее.
Давайте рассмотрим другой пример. Вы можете определить массив с
именем INVENTORY из 8 элементов и инициализировать все значения нулем:
1INVENTORY   DW  0
2            DW  0
3            DW  0
4            DW  0
5            DW  0
6            DW  0
7            DW  0
8            DW  0
Можно сократить и написать следующим образом:
1INVENTORY&nbsp;&nbsp; DW&nbsp; 0, 0 , 0 , 0 , 0 , 0 , 0 , 0
Директива TIMES также может использоваться для нескольких инициализаций
одним и тем же значением. Используя TIMES, массив INVENTORY можно
определить как:
1&nbsp;INVENTORY TIMES 8 DW 0
В следующем примере мы определим массив x, состоящий из трех
элементов: 2, 3 и 4. Затем мы сложим эти значения и выведем их сумму:
1 section .text
2    global _start   ; должно быть объявлено для линкера (ld)
3
4 _start:
5
6    mov  eax,3      ; количество значений типа byte для выполнения операции
сложения
7
   mov  ebx,0      ; EBX будет хранить сумму
8
   mov  ecx, x     ; ECX будет указывать на текущий элемент для выполнения
9
10операции сложения
11  
12top:  add  ebx, [ecx]
13 
14   add  ecx,1      ; перемещаем указатель на следующий элемент
15   dec  eax        ; выполняем декремент счетчика
16   jnz  top        ; если счетчик не равен 0, то тогда выполняем цикл еще раз
17 
18done:
19 
20   add   ebx, '0'
21   mov  [sum], ebx ; готово, сохраняем результат в переменной sum
22 
23display:
24 
25   mov  edx,1      ; длина сообщения
26   mov  ecx, sum   ; сообщение для вывода на экран
27   mov  ebx, 1     ; файловый дескриптор (stdout)
28   mov  eax, 4     ; номер системного вызова (sys_write)
29   int  0x80       ; вызов ядра
30
31   mov  eax, 1     ; номер системного вызова (sys_exit)
32   int  0x80       ; вызов ядра
33 
34section .data
35global x
36x:    
37   db  2
38   db  4
39   db  3
40 
sum:
   db  0
Результат выполнения программы:
9
Ассемблер. Процедуры
  Светлана Деменева  | 
 
 Уроки Ассемблера
  |


 
  Обновл. 16 Сен 2021  | 
 
 15410
 
 ǀ   2 

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


большинство программ на ассемблере имеют большой размер. Процедуры
идентифицируются по имени, после которого, собственно, и следует тело
процедуры. Конец процедуры указывается стейтментом возврата.
Процедуры в Ассемблере
Ниже приведен синтаксис определения процедуры:
имя_процедуры:
    тело_процедуры
    ...
    ret
Процедура вызывается из другой функции с помощью инструкции CALL,
которая содержит имя вызываемой процедуры в качестве аргумента: 
CALL имя_процедуры
Вызываемая процедура возвращает управление вызывающей процедуре с
помощью инструкции RET.
Например, давайте напишем очень простую процедуру с именем sum, которая
будет складывать переменные, хранящиеся в регистрах ECX и EDX, и
возвращать сумму в регистр EAX:
1 section .text
2    global _start        ; объявляем для использования gcc
3
4 _start:                ; сообщаем линкеру входную точку
5    mov ecx,'4'
6    sub     ecx, '0'
7
8    mov edx, '5'
9    sub     edx, '0'
10
11    call    sum          ; вызываем процедуру sum
12   mov [res], eax
13   mov ecx, msg
14   mov edx, len
15   mov ebx,1         ; файловый дескриптор (stdout)
16   mov eax,4         ; номер системного вызова (sys_write)
17   int 0x80         ; вызов ядра
18
19   mov ecx, res
20   mov edx, 1
21   mov ebx, 1        ; файловый дескриптор (stdout)
22   mov eax, 4         ; номер системного вызова (sys_write)
23   int 0x80         ; вызов ядра
24
25   mov eax,1         ; номер системного вызова (sys_exit)
26   int 0x80         ; вызов ядра
27sum:
28   mov     eax, ecx
29   add     eax, edx
30   add     eax, '0'
31   ret
32
33section .data
34msg db "The sum is:", 0xA,0xD
35len equ $- msg  
36 
37segment .bss
38res resb 1
Результат выполнения программы:
The sum is:
9
Стек как структура данных

Стек — это структура данных в памяти в виде массива, в котором эти данные


могут храниться и удаляться из места, называемого «вершиной» стека. Данные,
которые должны быть сохранены, «помещаются» в стек, а данные, которые
должны быть извлечены — «выталкиваются» из стека. Стек — это структура
данных типа LIFO (англ. «Last In, First Out» = «Последним пришёл, первым
ушёл»), детально об этом читайте здесь.
Язык ассемблера предоставляет две инструкции для операций со
стеком: PUSH и POP. Эти инструкции имеют следующий синтаксис:
PUSH    операнд
POP     адрес/регистр
Пространство памяти, зарезервированное в сегменте стека, используется для
реализации стека, а именно — регистры SS и ESP (или SP). На вершину стека,
которая указывает на последний добавленный элемент, указывает
регистр SS:ESP, где регистр SS указывает на начало сегмента стека, а SP (или
ESP) показывает смещение в сегменте стека.
Сохранять значения регистров в стеке перед их использованием можно
следующим образом:
1 ; Сохраняем регистры AX и BX в стеке
2 PUSH    AX
3 PUSH    BX
4  
5 ; Используем регистры для других целей
6 MOV AX, VALUE1
7 MOV BX, VALUE2
8 ...
9 MOV VALUE1, AX
10MOV VALUE2, BX
11  
12; Восстанавливаем исходные значения
13POP BX
14POP AX
Например, давайте напишем программу, которая будет выводить
весь набор ASCII-символов:
1 section .text
2    global _start        ; объявляем для использования gcc
3
4 _start:                ; сообщаем линкеру входную точку
5    call    display
6    mov eax,1         ; номер системного вызова (sys_exit)
7    int 0x80         ; вызов ядра
8
9 display:
10   mov    ecx, 256
11
12next:
13   push    ecx
14   mov     eax, 4
15   mov     ebx, 1
16   mov     ecx, achar
17   mov     edx, 1
18   int     80h
19
20   pop     ecx
21   mov dx, [achar]
22   cmpbyte [achar], 0dh
23   inc byte [achar]
24   loop    next
25   ret
26
27section .data
28achar db '0'
Результат выполнения программы:
0123456789:;<=>?
@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}
...
...
Ассемблер. Рекурсия
  Светлана Деменева  | 
 
 Уроки Ассемблера
  |

 
  Обновл. 16 Сен 2021  | 
 
 10595
Рекурсивная процедура — это процедура, которая вызывает сама себя.
Существует два вида рекурсии:
   прямая;
   косвенная.
При прямой рекурсии процедура вызывает сама себя, а при косвенной рекурсии
первая процедура вызывает вторую процедуру, которая, в свою очередь,
вызывает первую процедуру.
Рекурсию можно наблюдать в многочисленных математических алгоритмах.
Например, рассмотрим вычисление факториала числа, который задается
следующим уравнением:
Fact (n) = n * fact (n-1) для n > 0
Каждый рекурсивный алгоритм должен иметь конечное условие, то есть
рекурсивный вызов программы должен быть остановлен при выполнении
определенного условия. В случае факториального алгоритма конечное условие
достигается, когда n = 0.
Следующая программа показывает, как факториал числа n реализован
на Ассемблере. Для простоты примера мы вычислим факториал числа 3:
1 section .text
2    global _start         ; объявляем для использования gcc
3
4 _start:                  ; сообщаем линкеру входную точку
5  
6    mov bx, 3             ; для вычисления факториала числа 3
7    call  proc_fact
8    add   ax, 30h
9    mov   [fact], ax
10    
11    mov   edx,len        ; длина сообщения
12   mov   ecx,msg        ; сообщение для вывода на экран
13   mov   ebx,1          ; файловый дескриптор (stdout)
14   mov   eax,4          ; номер системного вызова (sys_write)
15   int   0x80           ; вызов ядра
16 
17   mov    edx,1          ; длина сообщения
18   mov   ecx,fact       ; сообщение для вывода на экран
19   mov   ebx,1          ; файловый дескриптор (stdout)
20   mov   eax,4          ; номер системного вызова (sys_write)
21   int   0x80           ; вызов ядра
22    
23   mov   eax,1          ; номер системного вызова (sys_exit)
24   int   0x80           ; вызов ядра
25
26proc_fact:
27   cmp   bl, 1
28   jg    do_calculation
29   mov   ax, 1
30   ret
31
32do_calculation:
33   dec   bl
34   call  proc_fact
35   inc   bl
36   mul   bl        ; ax = al * bl
37   ret
38 
39section .data
40msg db 'Factorial 3 is:',0xa
41len equ $ - msg
42 
43section .bss
44fact resb 1
Результат выполнения программы:
Factorial 3 is:
6
Ассемблер. Макросы
  Светлана Деменева  | 
 
 Уроки Ассемблера
  |


 
  Обновл. 16 Сен 2021  | 
 
 12720
Написание макроса — это еще один способ обеспечения модульного
программирования в Ассемблере.
   Макрос — это последовательность инструкций с именем, которая может
использоваться в любом месте программы.
   В NASM макросы определяются с помощью директив %macro (начало)
и %endmacro (конец).
Синтаксис для определения макросов следующий:
%macro имя_макроса количество_параметров
<тело_макроса>
%endmacro
Чтобы вызвать макрос, нужно использовать имя макроса вместе с
необходимыми параметрами. Когда вам нужно многократно использовать
некоторую последовательность инструкций в программе, вы можете поместить
эти инструкции в макрос и использовать его вместо постоянного написания
этих инструкций.
Например, очень часто в программах требуется выводить строки символов на
экран, для этого можно использовать следующую последовательность
инструкций:
1mov edx,len     ; длина сообщения
2mov ecx,msg     ; сообщение для вывода на экран
3mov ebx,1       ; файловый дескриптор (stdout)
4mov eax,4       ; номер системного вызова (sys_write)
5int 0x80        ; вызов ядра
Здесь регистры EAX, EBX, ECX и EDX были использованы при вызове
функции INT 80H. Таким образом, каждый раз, когда вам нужно вывести строки
на экран, вам нужно сохранить эти регистры в стеке, вызвать INT 80H и затем
восстановить исходное значение регистров из стека. Поэтому здесь может быть
полезно написать два макроса: для сохранения и для восстановления данных.
Некоторые инструкции, такие как IMUL, IDIV, INT и т.д., требуют сохранения
некоторой информации в определенных регистрах. Если программа уже
использовала эти регистры для хранения важных данных, то существующие
данные из этих регистров должны быть сохранены в стеке и восстановлены
после выполнения инструкции.
Рассмотрим следующую программу, в которой продемонстрировано
определение и использование макросов:
1 ; Макрос с двумя параметрами, который реализует системный вызов
sys_write
2
   %macro write_string 2
3
      mov   eax, 4
4
      mov   ebx, 1
5
      mov   ecx, %1
6
      mov   edx, %2
7
      int   80h
8
   %endmacro
9
10
11 section .text
12   global _start            ; объявляем для использования gcc
13
14_start:                     ; сообщаем линкеру входную точку
15   write_string msg1, len1              
16   write_string msg2, len2    
17   write_string msg3, len3  
18
19   mov eax,1                ; номер системного вызова (sys_exit)
20   int 0x80                 ; вызов ядра
21 
22section .data
23msg1 db 'Hello, programmers!',0xA,0xD
24len1 equ $ - msg1
25 
26msg2 db 'Welcome to the world of,', 0xA,0xD
27len2 equ $- msg2
28 
29msg3 db 'Linux assembly programming! '
len3 equ $- msg3
Результат выполнения программы:
Hello, programmers!
Welcome to the world of,
Linux assembly programming!
Ассемблер. Управление файлами
  Светлана Деменева  | 
 
 Уроки Ассемблера
  |

 
  Обновл. 16 Сен 2021  | 
 
 16541
 
 ǀ   1 

Ассемблер рассматривает любые входные или выходные данные в качестве


потока байтов. Есть три стандартных файловых потока:
   стандартный ввод (stdin);
   стандартный вывод (stdout);
   стандартная ошибка (stderr).
Оглавление:
1. Файловый дескриптор
2. Файловый указатель
3. Системные вызовы обработки файлов
4. Создание и открытие файла
5. Открытие существующего файла
6. Чтение файла
7. Запись в файл
8. Закрытие файла
9. Обновление файла
10.Пример на практике
Файловый дескриптор
Файловый дескриптор (или «дескриптор файла») — это 16-битное целое
число, присваиваемое файлу в качестве идентификатора. Когда создается новый
файл или открывается существующий, дескриптор файла используется для
доступа к нему.
Файловый дескриптор стандартных файловых потоков:
   stdin — 0;
   stdout — 1;
   stderr — 2.
Файловый указатель

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


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

Системные вызовы обработки файлов


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

%eax Имя системного вызова %ebx %ecx %edx

2 sys_fork struct pt_regs — —

3 sys_read unsigned int char * size_t

4 sys_write unsigned int const char * size_t

5 sys_open const char * int int

6 sys_close unsigned int — —

8 sys_creat const char * int —

19 sys_lseek unsigned int off_t unsigned int

Необходимые шаги для использования системных вызовов:


   поместите номер системного вызова в регистр EAX;
   сохраните аргументы системного вызова в регистрах EBX, ECX и EDX;
   вызовите соответствующее прерывание (80h);
   результат обычно возвращается в регистр EAX.
Создание и открытие файла

Для создания и открытия файла выполняются следующие шаги:


   поместите системный вызов sys_creat() — номер 8 в регистр EAX;
   поместите имя файла в регистр EBX;
   поместите права доступа к файлу в регистр ECX.
Системный вызов возвращает файловый дескриптор созданного файла в
регистр EAX. В случае ошибки, код ошибки также будет находиться в регистре
EAX.

Открытие существующего файла


Для открытия существующего файла выполняются следующие шаги:
   поместите системный вызов sys_open() — номер 5 в регистр EAX;
   поместите имя файла в регистр EBX;
   поместите режим доступа к файлу в регистр ECX;
   поместите права доступа к файлу в регистр EDX.
Системный вызов возвращает файловый дескриптор открытого файла в регистр
EAX. В случае ошибки, код ошибки также будет находиться в регистре EAX.
Среди режимов доступа к файлам чаще всего используются:
   только чтение (0);
   только запись (1);
   чтение-запись (2).
Чтение файла

Для чтения данных из файла выполняются следующие шаги:


   поместите системный вызов sys_read() — номер 3 в регистр EAX;
   поместите файловый дескриптор в регистр EBX;
   поместите указатель на входной буфер в регистр ECX;
   поместите размер буфера, т.е. количество байтов для чтения, в регистр EDX.
Системный вызов возвращает количество прочитанных байтов в регистр EAX.
В случае ошибки, код ошибки также будет находиться в регистре EAX.

Запись в файл
Для записи в файл выполняются следующие шаги:
   поместите системный вызов sys_write() — номер 4 в регистр EAX;
   поместите файловый дескриптор в регистр EBX;
   поместите указатель на выходной буфер в регистр ECX;
   поместите размер буфера, т.е. количество байтов для записи, в регистр EDX.
Системный вызов возвращает фактическое количество записанных байтов в
регистр EAX. В случае ошибки, код ошибки также будет находиться в регистре
EAX.

Закрытие файла
Для закрытия файла выполняются следующие шаги:
   поместите системный вызов sys_close() — номер 6 в регистр EAX;
   поместите файловый дескриптор в регистр EBX.
В случае ошибки, системный вызов возвращает код ошибки в регистр EAX.

Обновление файла
Для обновления файла выполняются следующие шаги:
   поместите системный вызов sys_lseek() — номер 19 в регистр EAX;
   поместите файловый дескриптор в регистр EBX;
   поместите значение смещения в регистр ECX;
   поместите исходную позицию для смещения в регистр EDX.
Исходная позиция может быть:
   началом файла — значение 0;
   текущей позицией — значение 1;
   концом файла — значение 2.
В случае ошибки, системный вызов возвращает код ошибки в регистр EAX.

Пример на практике
Следующая программа создает и открывает файл с именем myfile.txt, и
записывает текст Welcome to Ravesli! в этот файл. Затем программа считывает
данные из файла и сохраняет их в буфере с именем info. Наконец, программа
выводит текст, сохраненный в буфере info, на экран:
1 section .text
2    global _start         ; объявляем для использования gcc
3
4 _start:                  ; сообщаем линкеру входную точку
5    ; Создаем файл
6    mov  eax, 8
7    mov  ebx, file_name
8    mov  ecx, 0777        ; читать, писать и выполнять могут все
9    int  0x80             ; вызов ядра
10
11    mov [fd_out], eax
12    
13   ; Записываем данные в файл
14   mov edx,len          ; количество байтов
15   mov ecx, msg         ; сообщение для записи в файл
16   mov ebx, [fd_out]    ; файловый дескриптор
17   mov eax,4            ; номер системного вызова (sys_write)
18   int 0x80             ; вызов ядра
19
20   ; Закрываем файл
21   mov eax, 6
22   mov ebx, [fd_out]
23   int 0x80             ; вызов ядра
24    
25   ; Выводим на экран сообщение, указывающее на конец записи в файл
26   mov eax, 4
27   mov ebx, 1
28   mov ecx, msg_done
29   mov edx, len_done
30   int  0x80
31    
32   ; Открываем файл для чтения
33   mov eax, 5
34   mov ebx, file_name
35   mov ecx, 0             ; доступ "Только для чтения"
36   mov edx, 0777          ; читать, писать и выполнять могут все
37   int  0x80
38
39   mov  [fd_in], eax
40    
41   ; Считываем данные из файла
42   mov eax, 3
43   mov ebx, [fd_in]
44   mov ecx, info
45   mov edx, 26
46   int 0x80
47    
48   ; Закрываем файл
49   mov eax, 6
50   mov ebx, [fd_in]
51   int  0x80    
52
53   ; Выводим на экран данные из буфера info
54   mov eax, 4
55   mov ebx, 1
56   mov ecx, info
57   mov edx, 26
58   int 0x80
59      
60   mov eax,1             ; номер системного вызова (sys_exit)
61   int 0x80              ; вызов ядра
62 
63section .data
64file_name db 'myfile.txt'
65msg db 'Welcome to Ravesli!'
66len equ  $-msg
67 
68msg_done db 'Written to file', 0xa
69len_done equ $-msg_done
70 
71section .bss
72fd_out resb 1
73fd_in  resb 1
74info resb  26
Результат выполнения программы:
Written to file
Welcome to Ravesli!
Ассемблер. Управление памятью
  Светлана Деменева  | 
 
 Уроки Ассемблера
  |

 
  Обновл. 16 Сен 2021  | 
 
 16675
 
 ǀ   7 

Системный вызов sys_brk() предоставляется ядром для выполнения


выделения памяти без необходимости перемещать её позже. Данная системная
функция позволяет установить наибольший доступный адрес в секции data.
Этот наибольший адрес памяти, который необходимо установить, и является
единственным параметром, принимаемым данным системным вызовом. Это
значение сохраняется в регистре EBX.
В случае возникновения любой ошибки, системный вызов sys_brk()
возвращает -1 или фактический код ошибки.
В следующем примере демонстрируется динамическое выделение 16 КБ памяти
с помощью системного вызова sys_brk():
1 section .text
2    global _start     ; объявляем для использования gcc
3
4 _start:             ; сообщаем линкеру входную точку
5  
6    mov eax, 45 ; sys_brk()
7    xor ebx, ebx
8    int 80h
9  
10   add eax, 16384 ; количество байтов для резервирования
11    mov ebx, eax
12   mov eax, 45 ; sys_brk()
13   int 80h
14
15   cmpeax, 0
16   jl exit      ; выполняем exit, если возникла ошибка
17   mov edi, eax ; EDI = наибольший доступный адрес
18   sub edi, 4 ; указываем на последний DWORD  
19   mov ecx, 4096 ; количество выделенных значений типа DWORD
20   xor eax, eax ; очищаем регистр EAX
21   std      ; назад
22   rep stosd        ; повторяем для всей выделенной области
23   cld      ; помещаем флаг DF в обычное положение
24
25   mov eax, 4
26   mov ebx, 1
27   mov ecx, msg
28   mov edx, len
29   int 80h      ; выводим сообщение на экран
30 
31exit:
32   mov eax, 1
33   xor ebx, ebx
34   int 80h
35
36section .data
37msg     db "Allocated 16 kb of memory!", 10
38len     equ $ - msg
Результат выполнения программы:
Allocated 16 kb of memory!

Вам также может понравиться