Главная » Заметки » День 21. Продолжаю изучения языка ассемблера: SASM плюс NASM
День 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. Так же планирую научиться линковать программы на языке ассемблера с Си-программами.

Просмотров: 54
11.06.2022
Автор