День 21. Продолжаю изучения языка ассемблера: SASM плюс NASM
Автор статьи никого не призывает к правонарушениям и отказывается нести ответственность за ваши действия. Вся информация предоставлена исключительно в ознакомительных целях. Все действия происходят на виртуальных машинах и внутри локальной сети автора. Спасибо!
Оглавление цикла заметок о языке ассемблера
В предыдущие дни более-менее разобрался с основами языка ассемблера. Настало время написать несколько простых программ. Перехожу к седьмой главе книги Рудольфа Марека «Ассемблер на примерах». Пример со сложением двух переменных пропустим.
Следующий пример более интересный: сложение двух элементов массива. Кстати, нашел более удобный редактор/отладчик – SASM. Стало намного удобнее. Далее код, который складывает два элемента массива:
main:
mov rbp, rsp; for correct debugging
mov edi, numbers
mov edx, [edi]
add edx, [edi + 4]
mov ah, 1
int 21h
numbers dd 1
dd 2
Так как каждый элемент массива занимает по 4 байта, то к неименованному мы можем обратиться при помощи ссылки на edi
плюс 4 байта. Код выше для 64 разрядной системы. Для 32 разрядной необходимо добавить нижнее подчеркивание в начале имени main
(пока не разобрался, почему такое различие) (Примечание: разобрался в 22 день).
Далее сложим элементы массива (объявленный массив заканчивается нулем – индикатор последнего элемента).
_main:
mov ebp, esp; for correct debugging
esi, array
xor ebx, ebx
xor eax, eax
again:
mov al, [esi]
inc esi
add ebx, eax
cmp al, 0
jnz again
array db 1,2,3,4,5,6,7,8,0
Теперь определим каким является число: четным или нечетным. У четного числа младший бит равен нулю. Мы будем использовать сдвиг и, если в флаг CF
попадет единица, то это число будет являться нечетным.
Сама программа несложная. Куда сложнее оказалось вывести ответ в консоль. В SASM почему-то printf
не получилось заставить работать.
%include "io.inc"
section .text
global _main
extern _getchar
_main:
mov ebp, esp; for correct debugging
push ebp
xor eax, eax
mov eax, 20
push eax
shr eax, 1
pop eax
jc odd
even:
PRINT_STRING msg_even
jmp exit
odd:
PRINT_STRING msg_odd
jmp exit
exit:
pop ebp
xor eax, eax
call _getchar
ret
section .data
msg_odd db 'Odd!',0xd, 0xa, 0
msg_even db 'Even!',0xd, 0xa, 0
С вводом в консоль пока тоже не разобрался. Макрос GET_DEC
как-то неправильно работает, точнее, вообще не работает.
Следующая задача: преобразовать число в строку. На высокоуровневом языке программирования, это тривиальная задача, но на языке ассемблера придется написать целую подпрограмму. Так же в этом примере разберемся с передачей параметров в функцию.
Что нового будет использовано в программе. Точка в имени метки (обозначает, что метка локальная). Слово byte
– сообщает компилятору о том, какого размера ноль нужно записать по адресу EDI
.
%include "io.inc"
section .text
global _main
extern _getchar
convert:
xor ecx, ecx
xor ebx, ebx
mov ebx, 10
.divide:
xor edx, edx
div ebx
add edx, '0'
push edx
inc ecx
or eax, eax
jnz .divide
.reverse:
pop eax
mov [edi], al
add edi, 1
dec ecx
cmp ecx, 0
jnz .reverse
mov byte [edi], 0
ret
_main:
mov ebp, esp; for correct debugging
push ebp
mov eax, 1234567
mov edi, buff
call convert
PRINT_STRING buff
exit:
pop ebp
xor eax, eax
call _getchar
ret
section .data
buff db '',0dh,0ah,0
Следующая глава номер восемь: «Операционная система». Приступаем.
Одной из главных задач, решаемых ОС – распределение процессорного времени между отдельными программами.
Далее глава девять: «Компилятор NASM». Именно его я использую. В целом здесь еще раз рассказывается про упомянутое ранее. Но не только.
Можно не ставить двоеточие после метки, но лучше этого не делать (т.е. лучше ставить двоеточие), так как компилятор может принять имя метки за написанное имя команды с ошибкой. Про локальные метки уже было.
Далее о препроцессоре NASM. Чем дальше, тем более высокоуровневым кажется язык ассемблера. Один макрос я уже использовал ранее – %include
– подключает файл/библиотеку. Макрос – это несколько команд языка ассемблера выраженных символическим именем. Имена макросов чувствительны к регистру. Например, чтобы найти среднее значение с использованием макроса:
%include "io.inc"
%define average(a, b) (((a) + (b))/2)
section .text
global CMAIN
CMAIN:
mov ebp, esp; for correct debugging
mov al, average(3, 9)
xor eax, eax
ret
Инструкция mov al, average(3, 9)
заменится на mov al, 5
препроцессором (если по аналогии с Си). Так же %define
используют для определения констант. Еще можно определить определен ли макрос как в Си при помощи %ifdef
(определен) и %ifndef
(не определен). Удаление макроса можно добиться с помощью %undef
.
Для определения более сложного макроса, используется %macro
и %endmacro
. Синтаксис следующий:
%macro ИМЯ КОЛИЧЕСТВО_АРГУМЕНТОВ
КОМАНДЫ …
%endmacro
Необязательно указывать точное количество аргументов. Можно указать диапазон, например, 2-3. После диапазона количества аргументов разрешается указать значение по умолчанию для опущенных аргументов.
Макрос %assign
предназначен для определения однострочного выражения без параметров, которое возвращает в результате число. Обычно %assign
используется в более сложных макросах.
Как и в Си, препроцессор NASM предоставляет возможность условно компилировать программу. Для этого используются директивы %if
, %elif
, %else
и %endif
. В условии можно использовать операторы отношения и логические операторы. Все как в Си.
С помощью директивы %include
можно вставить в текущую позицию код из другого файла.
О директивах ассемблера
Директивы Ассемблера NASM – это макрокоманды, определенные самим компилятором. Директивы позволяют составлять макросы, о чем было повествование выше.
Программы, как правило, состоят из трех частей: код программы, динамические и статические данные. Эти части можно определить при помощи директив SECTION
и SEGMENT
. Секция кода — .text
, статических данных — .data
и динамических данных — .bss
.
Директивы EXTERN
, GLOBAL
и COMMON
. Первые две я уже ранее использовал. Автор учебника заверяет, что дальше эти директивы будут разобраны подробнее (при линковке программ на языке ассемблера и других высокоуровневых языков – важный вопрос, который стоит в третьем пункте 15 дня).
Директива EXTERN
аналогична директиве extern из Си. Она позволяет определить идентификаторы, которые не определены в текущей программе, но определенны во внешнем модуле.
Директива GLOBAL
помечает идентификаторы глобальными, т.е. они могут использоваться другими модулями/программами.
Директива COMMON
, как и GLOBAL
, только используется для идентификаторов из секции .bss
.
Далее рассматривается директива CPU
– заставляет компилятор генерировать команды для определенного типа процессора.
Директива ORG
– указывает начальный адрес загрузки. Ее использовал для программирования под DOS (т.е. 16-битный процессор) в 19 день.
Формат выходного файла
NASM очень гибкий компилятор, который позволяет откомпилировать программу под любой другое процессор, необязательно тот, на котором работаете. Эта информация написана в девятой главе. Я бы ее поставил поближе к началу учебника. С компиляцией разбирался в 19 день при помощи гугла, поэтому пропущу этот раздел.
В десятой главе книги «Ассемблер на примерах» говорится о программировании в DOS на языке ассемблера. Нет большого желания это читать, поэтому перехожу к следующей главе: программирование в Windows. Но оставлю это на завтра.
Этот день немного продвинул меня в освоении языка ассемблера. Узнал о новой программе, даже сказал бы IDE – SASM, которая значительно упрощает/автоматизирует процесс. Завтра буду учиться программировать под Windows и обязательно под Linux. Так же планирую научиться линковать программы на языке ассемблера с Си-программами.