TASM (Turbo Assembler)
Последнее изменение этой страницы: 18:48, 2 марта 2016.
__NUMBEREDHEADINGS__
Введение. С чего начать?
Прежде чем приступать непосредственно к программированию под архитектуру х86, для лучшего понимания, следует ознакомиться со статьями, описывающими её основные особенности. Это решит большую часть вопросов, возникающих в процессе написания кода, например : каким образом на таком низком уровне реализуется обмен данными между внутренним миром программы и внешней средой? Как организованна память? Как процессор понимает, что делать, если…?
Простые программы. И что дальше?
Рассмотрим общую структуру программы на ассемблере под х86, она выглядит примерно следующим образом:
;тип процессора, например 386р
.model flat, stdcall ; модель памяти и вызова подпрограмм
;объявление включаемых (заголовочных) файлов, макросов, макроопределений, внешних определений
.data ; Инициализированные данные
.data? ; неинициализированные данные
.const ; константы
.code ; исполняемый код
End;"метка точки входа"
Процессоры могут быть различны, при тип написании программы следует указать тот, с которым мы непосредственно будем работать.
Секция .data содержит инициализированные данные и включается в исполняемый файл.
Секция .data? содержит неинициализированные данные, она не включается в исполняемый файл и появляется только тогда, когда программа загружается в память. Секции .data, .data? имеют полный доступ к памяти.
Секция .const секция констант. Редко встречается в программах, потому что константы удобно задавать с помощью макроопределений.
Секция .code содержит исполняемый код программы. Секции .const и .code имеют атрибут доступа - только чтение.
В конце программы всегда нужно указывать end, что задаёт точку входа программы, т.е. место с которого начнётся выполняться программа.
При программировании под Win32 мы не имеем доступа к портам ввода вывода и не можем вызывать прерывания (основные команды в DOS), у нас есть только WIN API, которые экспортируют системные библиотеки. Вызывая API функцию, мы посути передаём управление точке входа функции (точка входа функции это её первая инструкция). API функции находятся в библиотеках kernel32.dll, user32.dll, gdi32.dll, advapi32.dll и т.д., для того чтобы использовать некоторую функцию этих библиотек надо сначала загрузить нужную библиотеку в свою память, либо включить используемые функции в таблицу импорта и тогда загрузчик загрузит библиотеки за нас. Каждая программа в Win32 в конце своего выполнения должна вызвать функцию ExitProcess,это легко объясняется тем, что после выполнения последней инструкции программы, дальше идёт пустота, даже если там что-то осмысленное, все равно у выделенная память не бесконечна, а после конца идёт невыделенная (т.е. несуществующая) память.
Помимо использования API ОС существует возможность использования функций из других библиотек, например из библиотеки языка Си. Такой вариант дает нам возможность компиляции программы в Linux.
Пример
Приступим непосредственно к коду. В качестве упражнения напишем знаменитую программу "Hello, World!"
.386
.model flat
extrn ExitProcess:PROC
extrn MessageBoxA:PROC
.data
Ttl db "First ASSEMBLER program",0h
Msg db 'Hello, World!!!!',0h
.code
start:
push 0h
push offset Msg
push offset Ttl
push 0h
call MessageBoxA
push 0h
call ExitProcess
end start
По сути, в здесь мы только вызываем функцию MessageBoxA, для вывода окна сообщения потом выходим из процесса.
Компиляция
В качестве примера рассмотрим компиляцию нашей программы, написанной на TASM (классическом языке). Для этого сохраним написанный код в файл с расширением *.asm и ассемблируем его. Для этого нам понадобится программа tasm32.exe она находится в папке %tasmdir%\bin\tasm32.exe, формат её вызова такой: TASM [options] source [,object] [,listing] [,xref] (доп сведения смотрите в tasm32 /?) для большинства программ ассемблирование проходит так:
%tasmdir%\BIN\tasm32 /m3 /ml asmfile,,;asmfile - имя нашего файла БЕЗ расширения
Потом будет создан объектный файл, файл листинга и т.д. Теперь нам ннужно всё это слинковать. Для понадобится программа tlink32.exe. Вот формат её вызова:
TLINK objfiles, exefile, mapfile, libfiles, deffile, resfiles
Теперь после вызова
Compile sample
После чего мы получим исполняемый файл.
Усложним задачу
Пример Рассмотрим задачу поиска простых чисел с помощью Решета Эратосфена в версии языка NASM
Наш проект будет содержать следующие файлы:
- main.asm – главный файл,
- functions.asm – подпрограммы,
- string_constants.asm – определения строковых констант,
- Makefile – сценарий сборки
Рассмотрим код основного файла:
%define SUCCESS 0
%define MIN_MAX_NUMBER 3
%define MAX_MAX_NUMBER 4294967294
global _main
extern _printf
extern _scanf
extern _malloc
extern _free
SECTION .text
_main:
enter 0, 0
;ввод максимального числа
call input_max_number
cmp edx, SUCCESS
jne .custom_exit
mov [max_number], eax
;выделяем память для массива флагов
mov eax, [max_number]
call allocate_flags_memory
cmp edx, SUCCESS
jne .custom_exit
mov [primes_pointer], eax
;отсеять составные числа
mov eax, [primes_pointer]
mov ebx, [max_number]
call find_primes_with_eratosthenes_sieve
;вывести числа
mov eax, [primes_pointer]
mov ebx, [max_number]
call print_primes
;освободить память от массива флагов
mov eax, [primes_pointer]
call free_flags_memory
;выход
.success:
push str_exit_success
call _printf
jmp .return
.custom_exit:
push edx
call _printf
.return:
mov eax, SUCCESS
leave
ret
%include "functions.asm"
SECTION .data
max_number: dd 0
primes_pointer: dd 0
%include "string_constants.asm"
программа поделена по смыслу на 5 блоков, оформленных в виде подпрограмм:
- input_max_number — с помощью консоли запрашивает у пользователя максимальное число, до которого производится поиск простых; во избежание ошибок значение ограничено константами MIN_MAX_NUMBER и MAX_MAX_NUMBER
- allocate_flags_memory — запросить у ОС выделение памяти для массива пометок чисел (простое/составное) в куче; в случае успеха возвращает указатель на выделенную память через регистр eax
- find_primes_with_eratosthenes_sieve — отсеять составные числа с помощью классического решета Эратосфена;
- print_primes — вывести в консоль список простых чисел;
- free_flags_memory — освободить память, выделенную для флагов
О подпрограммах
Подпрограммы представляют собой метку, по которой располагается код. Заканчивается подпрограмма инструкцией ret.
input_max_number. Подпрограмма предназначена для ввода в программу максимального число, до которого будет производиться поиск простых. Ключевым моментов тут является вызов функции scanf из библиотеки Си
; Ввести максимальное число
; Результат: EAX - максимальное число
input_max_number:
;создать стек-фрейм,
;4 байта для локальных переменных
enter 4, 1
;показываем подпись
push str_max_number_label ;см. string_constants.asm
call _printf
add esp, 4
;вызываем scanf
mov eax, ebp
sub eax, 4
push eax
push str_max_number_input_format ;см. string_constants.asm
call _scanf
add esp, 8
mov eax, [ebp-4]
;проверка
cmp eax, MIN_MAX_NUMBER
jb .number_too_little
cmp eax, MAX_MAX_NUMBER
ja .number_too_big
jmp .success
;выход
.number_too_little:
mov edx, str_error_max_num_too_little ;см. string_constants.asm
jmp .return
.number_too_big:
mov edx, str_error_max_num_too_big ;см. string_constants.asm
jmp .return
.success:
push eax
push str_max_number_output_format ;см. string_constants.asm
call _printf
add esp, 4
pop eax
mov edx, SUCCESS
.return:
leave
ret
Cначала в eax записывается адрес памяти на 4 байта ниже указателя базы стека. Это память, выделенная для локальных нужд подпрограммы. Указатель на эту память передается функции scanf как цель для записи данных, введенных с клавиатуры. После вызова функции, в eax из памяти перемещается введенное значение.
Не будем вдаваться в подробности и приводить примеры кода остальных подпрограмм, остановимся на их основных моментах.
allocate_flags_memory и free_flags_memory. Ключевыми местами этих подпрограмм являются вызовы функций malloc и free из библиотеки Си. malloc в случае удачи возвращает через регистр eax адрес выделенной памяти, в случае неудачи этот регистр содержит 0. Это самое узкое место программы касательно максимального числа. 32 бит вполне достаточно для поиска простых чисел до 4 294 967 295, но выделить разом столько памяти не получится.
find_primes_with_eratosthenes_sieve. Подпрограмма реализует классический алгоритм для вычеркивания составных чисел, решето Эратосфена, на языке ассемблера x86. Приятна тем, что не использует вызовы внешних функций и не требует обработки ошибок :)
print_primes. Подпрограмма выводит в консоль простые числа. Ключевым моментом тут является вызов функции printf из библиотеки Си.
Определение строковых констант
Файл string_constants.asm содержит определение строковых переменных, значения которых, как намекает название файла, менять не предполагается. Только ради этих переменных было сделано исключение к правилу «не использовать глобальные переменные». Я так и не нашел более удобного способа доставлять строковые константы функциям ввода-вывода – подумывал даже записывать на стек непосредственно перед вызовами функций, но решил, что эта идея куда хуже идеи с глобальными переменными.
Сценарий сборки
ifdef SystemRoot
format = win32
rm = del
ext = .exe
else
format = elf
rm = rm -f
ext =
endif
all: primes.o
gcc primes.o -o primes$(ext)
$(rm) primes.o
primes.o:
nasm -f $(format) main.asm -o primes.o
ISSN 2542-0356
Следуй за Полисом
Оставайся в курсе последних событий
Лицензия
Если не указано иное, содержание этой страницы доступно по лицензии Creative Commons «Attribution-NonCommercial-NoDerivatives» 4.0, а примеры кода – по лицензии Apache 2.0. Подробнее см. Условия использования.