Академический Документы
Профессиональный Документы
Культура Документы
Бит 1 1 1 1 1 1 0 1
Номер бита 7 6 5 4 3 2 1 0
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
|
Обновл. 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
Программы на языке ассемблере могут быть разделены на три секции: 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
Стейтменты
|
Обновл. 27 Сен 2021 |
74368
ǀ 1
Регистры процессора
В архитектуре IA-32 есть десять 32-битных и шесть 16-битных процессорных
регистров. Регистры делятся на три категории:
Общие регистры (General Registers);
Регистры управления (Control Registers);
Сегментные регистры (Segment Registers).
В свою очередь, общие регистры делятся на следующие:
Регистры данных (Data Registers);
Регистры-указатели (Pointer Registers);
Индексные регистры (Index Registers).
Регистры данных
Индексные регистры
В процессоре существуют 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
1 sys_exit int — — — —
Режимы адресации
BYTE 1
WORD 2
DWORD 4
QWORD 8
TBYTE 10
|
Обновл. 16 Сен 2021 |
40546
ǀ 4
Определяет
DD Выделяет 4 байта
Doubleword
Определяет
DQ Выделяет 8 байт
Quadword
Директива Цель
Несколько определений
Вы можете иметь несколько стейтментов определения данных в программе.
Например:
1choice DB 'Y' ; ASCII-значение для y = 79H
2number1 DW 12345 ; 12345D = 3039H
3number2 DD 123456789 ; 123456789D = 75BCD15H
Ассемблер выделяет смежную память для нескольких определений
переменных.
Несколько инициализаций
|
Обновл. 16 Сен 2021 |
38166
ǀ 1
Пример:
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:
|
Обновл. 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
Инструкция 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
|
Обновл. 19 Сен 2021 |
39971
ǀ 1
Инструкция CMP
Тестируемые
Инструкция Описание
флаги
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 (не больше)
Тестируемые
Инструкция Описание
флаги
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 (не больше)
Тестируемые
Инструкция Описание
флаги
Jump No Parity или Jump Parity Odd
JNP/JPO PF
(если нечётность)
|
Обновл. 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-представление
|
Обновл. 4 Сен 2021 |
31679
ǀ 2
Инструкция 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
Инструкция 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
|
Обновл. 16 Сен 2021 |
15410
ǀ 2
|
Обновл. 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
Запись в файл
Для записи в файл выполняются следующие шаги:
поместите системный вызов 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