В этой статье я покажу как написать приложение для windows на ассемблере. В качестве IDE будет привычная Visual Studio 2019 со своими плюшками — подсветка кода, отладка и привычный просмотр локальных переменных и регистров. Собирать приложение будет MASM, а значит, у нас будут и чисто масмовские плюшки. Приложением будет игра в пятнашки. С одной стороны это просто, но не так чтобы совсем хелловорлд (впрочем хелловорлд мы тоже увидим во время настройки VS). С другой стороны это будет полноценное оконное приложение с меню, иконкой, отрисовкой, выводом текста и обработкой мыши с клавиатурой. Изначально у меня была мысль сделать что-нибудь поинтереснее пятнашек, но я быстро передумал. Сложность и размер статьи увеличивается значительно, а она и так получилась немаленькая. Новой же информации сильно больше не становится. Статья рассчитана на тех, кто имеет хотя бы начальные знания ассемблера, поэтому здесь не будет всяких мелочей из разряда как поделить одно число на другое или что делает команда mov. Иначе объем пришлось бы увеличить раза в три.
Заранее постараюсь ответить на вопрос — зачем это нужно, если на ассемблере сейчас уже никто не пишет? Есть пара объективных причин и одна субъективная. Из объективного — написание подобных программ позволяет понять внутреннее устройство windows и как в конечном итоге наш код исполняется на процессоре. Далеко не всем это действительно надо, но для некоторых вещей это обязательное знание. Вторая причина это то, что позволяет взглянуть на разработку немного под другим углом. Примерно так же как попробовать функциональное программирование полезно даже если не писать ничего в функциональном стиле. К примеру я слушал лекции Мартина Одерски вовсе не потому что решил перейти с C# на Scala. Полезно посмотреть на привычную разработку под другим углом. Субъективная же причина — для меня это было просто интересно, отойти от коммерческой разработки, этого цикла задач, спринтов, митингов, сроков и заняться тем, что интересно именно тебе.
Так получилось что у меня появилось много свободного времени, часть из которого я потратил на то, что называется пет-проектами. Это не стало какими-то production-ready вещами, скорее какие-то идеи интересные лично мне, что-то на что вечно не хватало времени. Одна из этих идей это ассемблер в современной IDE. Давно хотел этим заняться, но все не было времени. Мне было очень интересно со всем этим разбираться, надеюсь читателям тоже понравится.
Шаг первый — настраиваем VS
Тут я немного схитрил. Точнее так уж получилось, что здесь все уже сделано за нас. Есть пошаговая инструкция и даже готовый пустой проект. Можно воспользоваться пошаговой инструкцией, а я просто скачал пустой проект и переименовал SampleASM в FifteenAsm. Единственное, что надо сделать помимо переименования, это установить SubSystem : Windows в свойствах проекта (properties > Linker > System > SubSystem : Windows). Далее выбираем x86, нажимаем F5 (либо кликаем мышкой) и видим вот такое сообщение:
Теперь о подсветке синтаксиса. Тут есть разные пути, и я решил поискать что есть готового. Готового оказалось немного, я установил Asm-Dude. Также попробовал ChAsm, но внешний вид меня не порадовал. Впрочем внешний вид это дело вкуса, я остановился на Asm-Dude. Тут правда есть такой нюанс — Asm-Dude не поддерживает VS 2022, самая старшая версия VS 2019. Вот так выглядит все в сборе — дебаг, просмотр переменных, в т.ч. нормальное отображение структур, мнемоника для ассемблера.
Update: AsmDude для 2022 существует, но его нет в студийном менеджере расширений (автор не стал публиковать), доступен по ссылке. Вторая версия расширения в процессе разработки, и 1 октября автор расширения ее опубликовал. Во второй версии заявлено много всяких фич, не все они готовы, но пользоваться уже можно. Спасибо @NN1 и @aroman313 за уточнение.
Теперь еще одна вещь, о которой хочется рассказать, прежде чем приступить к основной части. Это MASM SDK. Это совсем необязательная вещь, но очень полезная. Там есть готовые inc файлы для WinAPI, а еще есть много примеров самых разных приложений на ассемблере. Но проект из этой статьи будет работать и без него.
Шаг второй — оконное приложение
Для того чтобы создать окно средствами WinAPI нужно немного. Заполнить специальную структуру с описанием этого окна, зарегистрировать класс окна, потом это окно создать. Вот практически и все. Еще нам нужна так называемая оконная процедура, или процедура обработки сообщений, называют ее по разному. Суть этой процедуры в обработке сообщений которые приходят в наше приложение. Клики мышкой, команды меню, отрисовка и вообще все специфическое поведение нашего приложения будет там. Со всеми подробностями написано здесь.
О вызове функций вообще и WinAPI в частности
Чтобы вызвать функцию, ее надо объявить. Ниже разные способы это сделать.
extern MessageBoxA@16 : PROC
MessageBoxA PROTO, hWnd:DWORD, pText:PTR BYTE, pCaption:PTR BYTE, style:DWORD
MessageBoxW PROTO, :DWORD,:DWORD,:DWORD,:DWORD
Объявление со списком параметров более понятно. Хотя именовать параметры и необязательно. Объявлением с extern я пользоваться не буду, оставим это для любителей разгадывать ребусы. Что такое A(или W) в имени функции? Это указание на тип строк, A — ANSI, W — Unicode. Для простоты дела я решил не связываться с юникодом и везде использовал ANSI версии. Обычно же применяют дефайн примерно такого вида:
#ifdef UNICODE
#define SetWindowText SetWindowTextW
#else
#define SetWindowText SetWindowTextA
#endif
Теперь о вызовах функций, «стандартный» для ассемблера вызов выглядит так
push 0
push offset caption
push offset text
push 0
call MessageBoxA
Существует мнемоническое правило для порядка аргументов — слева направо — снизу вверх. Иными словами первый аргумент в объявлении функции (здесь это хендл окна hWnd:DWORD) будет в самом нижнем push. К счастью в MASM есть очень удобная вещь — invoke. Вот так выглядит вызов той же самой функции.
invoke MessageBoxA, 0, offset howToText, offset caption, 0
Одна строчка вместо пяти. На мой взгляд invoke удобнее за редкими исключениями типа большого числа аргументов. В дальнейшем практически везде я буду пользоваться invoke.
Сигнатура, описание и примеры использования функций WinAPI легко гуглятся по их названию. На примере MessageBoxA мы увидим вот это
int MessageBoxA(
[in, optional] HWND hWnd,
[in, optional] LPCSTR lpText,
[in, optional] LPCSTR lpCaption,
[in] UINT uType
);
Осталось перевести все эти HWND и LPCSTR в соответствующие типы для ассемблера. Тип данных LPCSTR будет DWORD, ведь это просто ссылка. Олдфаги с легкостью узнают венгерскую нотацию, а название типа расшифровывается как Long Pointer Const String. HWND тоже будет просто DWORD, ведь HWND, как и LPCSTR по своей сути просто ссылка. Ну а UINT это DWORD просто по определению. В некотором роде сигнатура функций на ассемблере даже проще, ссылка здесь это просто ссылка, нет кучи разных типов.
Отсюда следует важный вывод — нет никаких специальных «ассемблерных» функций, это то же самое WinAPI !. Нам достаточно знать как решается нужная нам задача средствами WinAPI, неважно на каком языке они будут вызываться. Поэтому задача «вывести текст в окно средствами ассемблера» на самом деле будет «вывести текст в окно средствами WinAPI», а уж информации по WinAPI полно. Обратное тоже верно, зная как что-то сделать средствами WinAPI это можно сделать на практически любом языке. А это уже часто бывает полезно при написании скриптов.
Создаем простое окно
Перед созданием окна я сделал три inc-файла. Один с прототипами WinAPI, другой с константами приложения (ширина окна, заголовок, цвет заливки и все в таком же духе) и третий, со структурами WinAPI и целой кучей винапишных констант. Теперь можно писать NULL или TRUE/FALSE. Или MB_OK вместо 0, как в примере выше с MessageBoxA. Никаких специфических действий не нужно, просто Add — New Item — Text File и не забываем include filename. Файлики назвал WinApiProto.inc, WinApiConstants.inc, AppConstants.inc. Пример содержимого показан ниже.
Вот так теперь выглядит наш код
.386
.model flat, stdcall
.stack 4096
include WinApiConstants.inc
include WinApiProto.inc
.data
include AppConstants.inc
.code
main PROC
;...more code
Небольшое отступление про строки. Вот пример строковых констант
szClassName db "Fifteen_Class", 0
howToText db "The first line", CR , LF , "The second.", 0
Запятая означает конкатенацию, db это define byte, CR LF определены в WinApiConstants.inc (13 и 10 соответственно), ноль на конце это null-terminated строка. В итоге строки это никакой не специальный тип данных, а просто массивы байт с нулем на конце. В случае с юникодом возни было бы больше, но я решил не усложнять себе жизнь и использовать везде ANSI строки.
Вот мы и добрались до создания окна. Для этого нам надо
-
заполнить структуру WNDCLASSEX (объявлена в WinApiConstants)
-
зарегистрировать класс окна
-
создать процедуру главного окна
-
создать окно
Кода вышло уже почти на 200 строк, поэтому я покажу самые интересные куски, целиком можно посмотреть на гитхабе.
Объявление и заполнение WNDCLASSEX, как видим все как в языках высокого уровня. Ну, почти все — автодополнения со списком полей структуры нет.
WNDCLASSEX STRUCT
cbSize DWORD ?
style DWORD ?
lpfnWndProc DWORD ?
WNDCLASSEX ENDS
mov wc.cbSize, sizeof WNDCLASSEX
mov wc.style, CS_BYTEALIGNWINDOW
mov wc.lpfnWndProc, offset WndProc
При создании окна весьма важный параметр WS_EX_COMPOSITED. Без него при перерисовке будет мерзкий flickering. Очень хорошо что это работает — реализовывать двойную буферизацию самостоятельно желания не было.
push WS_EX_OVERLAPPEDWINDOW or WS_EX_COMPOSITED
call CreateWindowExA
Теперь немного чудесных директив MASM. Вот так вот просто организован цикл обработки сообщений
; Loop until PostQuitMessage is sent
.WHILE TRUE
invoke GetMessageA, ADDR msg, NULL, 0, 0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessageA, ADDR msg
.ENDW
А вот так без них
StartLoop:
push 0
push 0
push 0
lea eax, msg
push eax
call GetMessageA
cmp eax, 0
je ExitLoop
lea eax, msg
push eax
call TranslateMessage
lea eax, msg
push eax
call DispatchMessageA
jmp StartLoop
ExitLoop:
А вот как все просто в оконной процедуре. Никаких тебе cmp uMsg, WM_DESTROY, кучи меток, простой IF
.IF uMsg == WM_DESTROY
invoke PostQuitMessage, NULL
xor eax, eax
ret
.ENDIF
Вот как делается подтверждение на закрытие окна
.IF uMsg == WM_CLOSE
invoke MessageBoxA, hwin, ADDR exitConfirmationText, ADDR caption, MB_YESNO
.IF eax == IDNO
xor eax, eax
ret
.ENDIF
.ENDIF
Обещанный хелловорлд готов.
Добавляем иконку и меню
Иконка и меню в мире windows относятся к ресурсам. Поэтому добавляем к нашему проекту файл ресурсов — Add — Resource — Menu. Дальше можно воспользоваться встроенным редактором VS, я просто взял и отредактировал свежий файл FifteenAsm.rc в блокноте. Получилось вот так
500 ICON MOVEABLE PURE LOADONCALL DISCARDABLE "FIFTEENICON.ICO"
600 MENUEX MOVEABLE IMPURE LOADONCALL DISCARDABLE
BEGIN
POPUP "&File", , , 0
BEGIN
MENUITEM "&New Game", 1100
MENUITEM "&Exit", 1000
END
POPUP "&Help", , , 0
BEGIN
MENUITEM "&How to play", 1800
MENUITEM "&About", 1900
END
END
Обратите внимание на магические числа 500 и 600. Это идентификаторы ресурсов, совсем скоро мы увидим зачем они нужны. Также обратите внимание на магические числа 1000, 1100, 1800, 1900. Это идентификаторы команд, мы тоже увидим зачем они нужны, но чуть позже. Чуть не забыл про сам файл иконки, нарисовал я ее в каком-то онлайн редакторе. Дизайнер из меня так себе, поэтому что получилось, то получилось. Добавляем в проект под именем Fifteenicon.ico, тут главное назвать точно как в файле ресурсов. Дальше все просто. Иконка добавляется на этапе заполнения структуры WNDCLASSEX, тут у нас магическое число 500
push 500
push hInst
call LoadIconA
mov wc.hIcon, eax
Меню добавляется после создания окна, здесь магическое число 600
call CreateWindowExA
mov hWnd,eax
push 600
push hInst
call LoadMenuA
push eax
push hWnd
call SetMenu
А вот так обрабатываются команды меню, тут остальные магические числа 1000, 1100, 1800, 1900. Вообще с использованием MASM код не особо отличается от кода на тех же плюсах.
.IF uMsg == WM_COMMAND
.IF wParam == 1000
invoke SendMessageA, hwin, WM_SYSCOMMAND, SC_CLOSE, NULL
.ENDIF
.IF wParam == 1100
invoke MessageBoxA, hwin, ADDR newGameConfirmationText, ADDR caption, MB_YESNO
.IF eax == IDYES
;call InitTilesData
.ELSEIF eax == IDNO
xor eax, eax
ret
.ENDIF
.ENDIF
.IF wParam == 1800
invoke MessageBoxA, hwin, ADDR howToText, ADDR caption, MB_OK
.ENDIF
.IF wParam == 1900
invoke MessageBoxA, hwin, ADDR aboutText, ADDR caption, MB_OK
.ENDIF
.ENDIF
У приложения появилась иконка и есть меню. Ради интереса посмотрел на размер исполняемого файла, всего 6656 байт.
Шаг третий — игра
Создали окно, пора заняться самой игрой. Здесь я тоже покажу только самые интересные места.
Инициализация данных и начальная перетасовка
Данные о положении тайлов будут храниться в массиве из 16 байт. Ноль будет положением пустого тайла, значения от 1 до 15 соответствующие тайлы. Нумерация индексов тайлов слева направо, сверху вниз. Теперь надо их перетасовать и тут встает вопрос, откуда брать случайные числа? RDRAND и RDSEED появились достаточно поздно, а мне хотелось сделать код в «классическом» стиле. Сгоряча я даже думал реализовать Вихрь Мерсенна, но потом решил что это перебор. Поэтому честно нашел простенький ГПСЧ буквально в десяток команд, для seed использовал системное время. Идея начальной перетасовки простая, сначала заполняем массив по порядку (приводим в конечное состояние), а потом случайным образом двигаем тайлы. Тайлы двигаются по правилам, значит их всегда можно будет собрать в конечное положение. Если заполнять тайлы совсем рандомно, то надо проверять можно ли вообще собрать такую комбинацию. По опыту уже 100 итераций перемешивает тайлы вполне нормально.
local randSeed : DWORD
invoke GetTickCount
mov randSeed, eax
xor eax, eax
xor ebx, ebx
xor ebx, ebx
.WHILE ebx < initialSwapCount
mov eax, 4; random numbers count, i.e. from 0 to 3
push edx
imul edx, randSeed, prndMagicNumber
inc edx
mov randSeed, edx
mul edx
mov eax, edx
pop edx
add al, VK_LEFT
push ebx
invoke ProcessArrow, NULL, al; move a tile
pop ebx
inc ebx
.ENDW
ret
Отрисовка
Добавляем обработку WM_PAINT в оконной процедуре
LOCAL Ps :PAINTSTRUCT
LOCAL hDC :DWORD
.IF uMsg == WM_PAINT
invoke BeginPaint, hWin, ADDR Ps
mov hDC, eax
invoke PaintProc, hWin, hDC
invoke EndPaint, hWin, ADDR Ps
.ENDIF
Отрисовка тайлов. Из интересного здесь организация двойного цикла с использованием директив MASM WHILE и передача указателя на RECT в процедуре CalculateTileRect.
LOCAL Rct : RECT
invoke CreateSolidBrush, tileBackgroundColor
mov hBrush, eax
invoke SelectObject, hDC, hBrush
;fill tiles with background color
mov vert, 0
.WHILE vert < 4
mov hor, 0
.WHILE hor < 4
invoke CalculateTileRect, ADDR Rct, hor, vert
invoke RoundRect, hDC, Rct.left, Rct.top, Rct.right, Rct.bottom,
tileRoundedEllipseSize, tileRoundedEllipseSize
inc hor
.ENDW
inc vert
.ENDW
invoke DeleteObject, hBrush
CalculateTileRect proc rct :DWORD, hor:BYTE, vert:BYTE
mov edx, rct
invoke CalculateTileRectPos, hor, 0
mov (RECT PTR [edx]).left, eax
ret
CalculateTileRect endp
Обратите внимание на эту строчку. Структура передана по ссылке, смещение на left вычисляется автоматически.
mov (RECT PTR [edx]).left, eax
А вот как работает IntToStr (почти что честный) на ассемблере. Писать честный IntToStr мне не хотелось, поэтому я тут схитрил. Завел массив из 3 байт под строку, второй и третий байты сразу обнуляются. Числа бывают от 1 до 15, поэтому если число было меньше 10, то к значению прибавляем магическое число 48 (ASCII код для нуля) и получаем нужный первый байт буфера. Получается тоже самое что и на Си, когда пишем c = ‘0’ + i. Поскольку второй байт уже нулевой у нас получается готовая null-terminated строка, неважно что буфер из 3 байт. Если число больше 9, то первая цифра всегда 1, а вторая это остаток от деления на 10. Тут уже третий байт играет роль конца строки.
mov [buffer+1], 0
mov [buffer+2], 0
.IF bl < 10
add bl, asciiShift
mov [buffer], bl
sub bl, asciiShift
.ELSEIF bl > 9
mov al, asciiShift
inc al
mov [buffer], al
xor ax, ax
mov al, bl
mov cl, 10
div cl
add ah, asciiShift
mov [buffer+1], ah
.ENDIF
Вот так выглядит игровое поле
Добавляем интерактив
Для управления можно пользоваться курсором или кликать мышкой по тайлу, который надо переместить, благо вариант перемещения только один. Перемещение сводится к тому чтобы в массиве тайлов поменять местами перемещаемый и нулевой тайл. Смещение нулевого тайла будет +1/-1 для перемещений вправо/влево и +4/-4 для перемещения вверх/вниз. Путь у тайла только один, поэтому надо только проверить выход за диапазон и поменять местами два элемента в массиве тайлов. Если тайл переместился, то перерисовать окно. Добавим вот такие обработчики в нашу оконную процедуру.
.IF uMsg == WM_KEYDOWN
.if wParam == VK_LEFT
invoke ProcessArrow, hWin, wParam
.elseif wParam == VK_RIGHT
invoke ProcessArrow, hWin, wParam
.elseif wParam == VK_UP
invoke ProcessArrow, hWin, wParam
.elseif wParam == VK_DOWN
invoke ProcessArrow, hWin, wParam
.endif
.ENDIF
.IF uMsg == WM_LBUTTONUP
invoke ProcessClick, hWin, lParam
.ENDIF
Сначала посмотрим как реализовано перемещение тайлов курсором. Вот немного укороченная версия процедуры ProcessArrow. FindEmptyTileIndex возвращает в регистре eax индекс пустого тайла . В зависимости от нажатой клавиши проверяем выход за границы диапазона, т.е. можно ли переместить тайл в данной позиции в данном направлении. Если нельзя, уходим на метку pass в конец процедуры, если можно, то вызываем последовательно SwapTiles, RedrawWindow и ProcessPossibleWin.
ProcessArrow proc hWin:DWORD, key:DWORD
call FindEmptyTileIndex
.IF key == VK_UP
cmp eax, 12
ja pass
;when tile goes up, new empty tile index (ETI) will be ETI+4,
mov ebx, eax
add ebx, 4
.ENDIF
.IF key == VK_RIGHT
;empty tile shouldnt be on 0, 4, 8, 12 indexes
cmp eax, 0
je pass
cmp eax, 4
je pass
cmp eax, 8
je pass
cmp eax, 12
je pass
;when tile goes right, new empty tile index (ETI) will be ETI-1,
mov ebx, eax
dec ebx
.ENDIF
invoke SwapTiles, eax, ebx
.IF hWin != NULL ;little trick to simplify initial random data
invoke RedrawWindow, hWin, NULL, NULL, RDW_INVALIDATE
invoke ProcessPossibleWin, hWin
.ENDIF
pass:
ret
ProcessArrow endp
Для перемещения тайла от кликов мышью нужно понять по какому тайлу кликнули и проверить, можно ли его перемещать. Для этого в цикле (двойной цикл организован через директиву MASM .WHILE) вызываем CalculateTileRect и проверяем находится ли курсор мыши внутри прямоугольника. Принцип проверки тот же, что и в ProcessArrow — cmp в ряд, только команды условного перехода другие. Внутри ProcessArrow je (jump equal), а тут ja jb (jump above jump below). Дальше все тоже самое что и с курсором, только наоборот. Смотрим разницу между индексами пустого и кликнутого тайла и вызываем процедуру ProcessArrow (наверное не самое удачное название) с нужными аргументами. Сокращенная версия процедуры.
ProcessClick proc hWin:DWORD, lParam:DWORD
local rct : RECT
movsx ebx, WORD PTR [ebp+12] ; x coordinate
movsx ecx, WORD PTR [ebp+14] ; y coordinate
mov vert, 0
.WHILE vert < 4
mov hor, 0
.WHILE hor < 4
invoke CalculateTileRect, ADDR Rct, hor, vert
cmp ebx, Rct.left
jb next
cmp ebx, Rct.right
ja next
cmp ecx, Rct.top
jb next
cmp ecx, Rct.bottom
ja next
; the idea is that tile can be moved only if there is a particular diff
; between its index and empty tile index
; -1, +1 ,-4, +4 for different directions, similar to ProcessArrow proc
call FindEmptyTileIndex
.IF index > al
sub index, al
.IF index == 1
invoke ProcessArrow, hWin, VK_LEFT
.ELSEIF index == 4
invoke ProcessArrow, hWin, VK_UP
.ENDIF
.ENDIF
next:
inc hor
.ENDW
inc vert
.ENDW
ret
ProcessClick endp
Вспомогательные процедуры типа проверки на окончание игры, или смены местами значений в массиве я приводить не буду, т.к. они банальны, а статья и так разрослась. Теперь, когда все готово, в итоге получилось 587 строк в Main.asm и 8192 байта исполняемый файл. Размер екзешника меня приятно порадовал — 8 килобайт это и для прежних времен немного, а сейчас и подавно. Полный код приложения можно увидеть в гитхабе.
Заключение
Наша игра готова. Мы увидели как это делается в привычной IDE, узнали откуда брать сигнатуры и как вызывать функции WinAPI, поняли что надо сделать чтобы создать полноценное оконное приложение, использовали директивы MASM для упрощения кода. Хоть я никогда и не использовал ассемблер в коммерческой разработке, но интерес к нему был с юных лет. Начиная с изучения ассемблера для Z80, знаменитого Спектрума и его многочисленных клонов. Писать пусть и очень простую, но полноценную игру на ассемблере мне по-настоящему понравилось. Надеюсь читателям тоже было интересно!
В данной статье я хочу рассмотреть вопросы, которые могут возникнуть у человека, приступившего к изучению ассемблера, связанные с установкой различных трансляторов и трансляцией программ под Windows и Linux, а также указать ссылки на ресурсы и книги, посвященные изучению данной темы.
MASM
Используется для создания драйверов под Windows.
По ссылке переходим на сайт и скачиваем пакет (masm32v11r.zip). После инсталляции программы на диске создается папка с нашим пакетом C:masm32. Создадим программу prog11.asm, которая ничего не делает.
.586P
.model flat, stdcall
_data segment
_data ends
_text segment
start:
ret
_text ends
end start
Произведём ассемблирование (трансляцию) файла prog11.asm, используя ассемблер с сайта masm32.
Ключ /coff используется здесь для трансляции 32-битных программ.
Линковка производится командой link /subsystem:windows prog11.obj (link /subsystem:console prog11.obj)
Как сказано в Википедии
MASM — один из немногих инструментов разработки Microsoft, для которых не было отдельных 16- и 32-битных версий.
Также ассемблер версии 6. можно взять на сайте Кипа Ирвина kipirvine.com/asm, автора книги «Язык ассемблера для процессоров Intel».
Кстати, вот ссылка на личный сайт Владислава Пирогова, автора книги “Ассемблер для Windows”.
MASM с сайта Microsoft
Далее скачаем MASM (версия 8.0) с сайта Microsoft по ссылке. Загруженный файл носит название «MASMsetup.exe». При запуске этого файла получаем сообщение -«Microsoft Visual C++ Express Edition 2005 required».
Открываем этот файл архиватором (например 7zip). Внутри видим файл setup.exe, извлекаем его, открываем архиватором. Внутри видим два файла vc_masm.msi,vc_masm1.cab. Извлекаем файл vc_masm1.cab, открываем архиватором. Внутри видим файл FL_ml_exe_____X86.3643236F_FC70_11D3_A536_0090278A1BB8. Переименовываем его в файл fl_ml.exe, далее, произведём ассемблирование файла prog11.asm, используя ассемблер fl_ml.exe.
MASM в Visual Studio
Также MASM можно найти в папке с Visual Studio (у меня VS 10) вот здесь: C:Program FilesMicrosoft Visual Studio 10.0VCbinml.exe.
Для того, чтобы запустить на 32- или 64-разрядной системе и создавать программы, работающие как под 32-, так и под 64-разрядной Windows, подходит MASM32 (ml.exe, fl_ml.exe). Для того, чтобы работать на 32- и 64-разрядных системах и создавать программы, работающие под 64-разрядной Windows, но неработающие под 32-разрядной нужен ассемблер ml64.exe. Лежит в папке C:Program FilesMicrosoft Visual Studio 10.0VCbinamd64 и вот здесь — C:Program FilesMicrosoft Visual Studio 10.0VCbinx86_amd64.
TASM
Программный пакет компании Borland, предназначенный для разработки программ на языке ассемблера для архитектуры x86. В настоящее время Borland прекратила распространение своего ассемблера.
Скачать можно, например, здесь. Инсталлятора нет, просто извлекаем программу. Вот исходник из книги Питера Абеля (рис. 3.2) «Язык Ассемблера для IBM PC и программирования».
stacksg segment para stack 'stack'
db 12 dup ('stackseg')
stacksg ends
codesg segment para 'code'
begin proc far
assume ss:stacksg,cs:codesg,ds:nothing
push ds
sub ax,ax
push ax
mov ax, 0123h
add ax, 0025h
mov bx,ax
add bx,ax
mov cx,bx
sub cx,ax
sub ax,ax
nop
ret
begin endp
codesg ends
end begin
Выполним ассемблирование (трансляцию) файла abel32.asm.
Корректность работы программы можно проверить, произведя линковку (tlink.exe) объектного файла и запустив полученный файл в отладчике.
Как было сказано выше, MASM можно использовать для работы с 16-битными программами. Выполним ассемблирование (трансляцию) программы abel32.asm с помощью ассемблера MASM:
Ключ /coff здесь не используется.
Линковка производится файлом link16.exe
Вот здесь приводится порядок действий, которые необходимо выполнить для запуска TASM в DOSbox. Для линковки понадобится файл DPMI16BI.OVL
FASM
В статье Криса Касперски «Сравнение ассемблерных трансляторов» написано, что «FASM — неординарный и весьма самобытный, но увы, игрушечный ассемблер. Пригоден для мелких задач типа „hello, world“, вирусов, демок и прочих произведений хакерского творчества.»
Скачаем FASM с официального сайта. Инсталлятора нет, просто извлекаем программу. Откроем fasm editor — C:fasmfasmw.exe. В папке C:fasmEXAMPLESHELLO есть файл HELLO.asm.
include 'win32ax.inc'
.code
start:
invoke MessageBox,HWND_DESKTOP,"Hi! I'm the example program!",invoke GetCommandLine,MB_OK
invoke ExitProcess,0
.end start
Откроем файл HELLO.asm из fasmw.exe. Изменим строку include ‘win32ax.inc’ на строку include ‘c:fasmINCLUDEWIN32AX.INC’. Запускаем из меню Run → Run.
Вот ссылки на ресурсы, посвященные FASM:
→ FASM на Cyberforum’е
→ FASM на asmworld .com программы под Dos
→ Цикл статей «Ассемблер под Windows для чайников»
→ Сайт на narod’е
FASM в Linux
Для того, использовать FASM в Linux (у меня Ubuntu), скачаем соответствующий дистрибутив (fasm-1.71.60.tgz), распакуем его, в папке у нас будет бинарный файл fasm, копируем этот файл в /usr/local/bin для того, чтобы можно было запускать его из консоли, как любую другую команду.Выполним ассемблирование программы hello.asm из папки fasm/examples/elfexe/hello.asm.
Корректность работы программы можно проверить в отладчике.
Nasm
Nasm успешно конкурирует со стандартным в Linux- и многих других UNIX-системах ассемблером Gas.
Nasm в Linux можно установить с помощью менеджера пакетов или из командной строки: в дистрибутиве Debian (Ubuntu) командой apt-get install nasm, в дистрибутивах Fedora, CentOS, RedHat командой yum install nasm.
Создадим программу, которая 5 раз выводит сообщение “Hello”. Пример взят из книги Андрея Викторовича Столярова “Программирование на языке ассемблера NASM для ОС UNIX”. Учебник, а также библиотека “stud_io.inc” есть на личном сайте автора.
%include "stud_io.inc"
global _start
section .text
_start: mov eax, 0
again: PRINT "Hello"
PUTCHAR 10
inc eax
cmp eax, 5
jl again
FINISH
Выполним ассемблирование и линковку и запустим файл hello.asm.
$ nasm -f elf hello.asm
$ ld hello.o -o hello
$ ./hello
Для 64bit необходимо использовать команду nasm -f elf64 hello.asm
NASM для Windows
NASM для Windows можно установить, скачав соответствующий дистрибутив с соответствующего сайта.
Ассемблирование:
nasm -f bin имя_файла.asm -o имя_файла.com
Ссылки на ресурсы, посвященные Nasm:
→ Сайт А.В. Столярова
→ Сайт, на котором лежит электронный учебник (в архиве)
→ То же самое
AS
Стандартный ассемблер практически во всех разновидностях UNIX, в том числе Linux и BSD. Свободная версия этого ассемблера называется GAS (GNU assembler). Позволяет транслировать программы с помощью компилятора GCC.
Из учебников удалось найти только книгу на английском «Programming from the ground up». На русском удалось найти только одну главу из книги С. Зубкова «Assembler для DOS, Windows и UNIX».
Возьмем пример программы, которая ничего не делает, с сайта. Создадим программу gas.s
.section .text
.globl _start
_start:
movl $1, %eax
movl $2, %ebx
int $0x80
Выполним ассемблирование (трансляцию), линковку и запуск программы:
$ as -o gas.o gas.s
$ ld -o gas gas.o
$ ./gas
Если в данной программе изменить _start на main, то можно выполнить ассемблирование (трансляцию) и линковку компилятором gcc.
.section .text
.globl main
main:
movl $1, %eax
movl $2, %ebx
int $0x80
Выполним ассемблирование (трансляцию), линковку и запуск программы:
$ gcc gas.s -o gas
$ ./gas
Выводы: если вы изучаете программирование под Windows, то вы можете остановить свой выбор на Masm; Tasm больше не поддерживается, но для обучения по старым классическим учебникам подойдёт.
Под Linux Gas подойдет тем, кто использует GCC, а тем, кому не нравится синтаксис Gas, подойдёт Nasm.
P.S. Про обработку строк в ассемблере на примере создания транслятора простого «эзотерического» языка можно прочитать здесь.
P.P.S. Упрощенный ограниченный набор ассемблерных инструкций используется в учебной модели компьютера Little Man Computer, которому у меня также посвящено несколько статей ссылка.
На сегодняшний день существует огромное количество языков программирования высокого уровня. На их фоне программирование на низкоуровневом языке — ассемблере — может на первый взгляд показаться чем-то устаревшим и нерациональным. Однако это только кажется. Следует признать, что ассемблер фактически является языком процессора, а значит, без него не обойтись, пока существуют процессоры. Основными достоинствами программирования на ассемблере являются максимальное быстродействие и минимальный размер получаемых программ.
Недостатки зачастую обусловлены лишь склонностью современного рынка к предпочтению количества качеству. Современные компьютеры способны легко справиться с нагромождением команд высокоуровневых функций, а если нелегко — будьте добры обновите аппаратную часть вашей машины! Таков закон коммерческого программирования. Если же речь идет о программировании для души, то компактная и шустрая программа, написанная на ассемблере, оставит намного более приятное впечатление, нежели высокоуровневая громадина, обремененная кучей лишних операций. Бытует мнение, что программировать на ассемблере могут только избранные. Это неправда. Конечно, талантливых программистов-ассемблерщиков можно пересчитать по пальцам, но ведь так обстоит дело практически в любой сфере человеческой деятельности. Не так уж много найдется водителей-асов, но научиться управлять автомобилем сумеет каждый — было бы желание. Ознакомившись с данным циклом статей, вы не станете крутым хакером. Однако вы получите общие сведения и научитесь простым способам программирования на ассемблере для Windows, используя ее встроенные функции и макроинструкции компилятора. Естественно, для того, чтобы освоить программирование для Windows, вам необходимо иметь навыки и опыт работы в Windows. Сначала вам будет многое непонятно, но не расстраивайтесь из- за этого и читайте дальше: со временем все встанет на свои места.
Итак, для того, чтобы начать программировать, нам как минимум понадобится компилятор. Компилятор — это программа, которая переводит исходный текст, написанный программистом, в исполняемый процессором машинный код. Основная масса учебников по ассемблеру делает упор на использование пакета MASM32 (Microsoft Macro Assembler). Но я в виде разнообразия и по ряду других причин буду знакомить вас с молодым стремительно набирающим популярность компилятором FASM (Flat Assembler). Этот компилятор достаточно прост в установке и использовании, отличается компактностью и быстротой работы, имеет богатый и емкий макросинтаксис, позволяющий автоматизировать множество рутинных задач. Его последнюю версию вы можете скачать по адресу: сайт выбрав flat assembler for Windows. Чтобы установить FASM, создайте папку, например, «D:FASM» и в нее распакуйте содержимое скачанного zip-архива. Запустите FASMW.EXE и закройте, ничего не изменяя. Кстати, если вы пользуетесь стандартным проводником, и у вас не отображается расширение файла (например, .EXE), рекомендую выполнить Сервис -> Свойства папки -> Вид и снять птичку с пункта Скрывать расширения для зарегистрированных типов файлов. После первого запуска компилятора в нашей папке должен появиться файл конфигурации — FASMW.INI. Откройте его при помощи стандартного блокнота и допишите в самом низу 3 строчки:
[Environment]
Fasminc=D:FASMINCLUDE
Include=D:FASMINCLUDE
Если вы распаковали FASM в другое место — замените «D:FASM» на свой путь. Сохраните и закройте FASMW.INI. Забегая вперед, вкратце объясню, как мы будем пользоваться компилятором:
1. Пишем текст программы, или открываем ранее написанный текст, сохраненный в файле .asm, или вставляем текст программы из буфера обмена комбинацией.
2. Жмем F9, чтобы скомпилировать и запустить программу, или Ctrl+F9, чтобы только скомпилировать. Если текст программы еще не сохранен — компилятор попросит сохранить его перед компиляцией.
3. Если программа запустилась, тестируем ее на правильность работы, если нет — ищем ошибки, на самые грубые из которых компилятор нам укажет или тонко намекнет.
Ну, а теперь мы можем приступить к долгожданной практике. Запускаем наш FASMW.EXE и набираем в нем код нашей первой программы:
include ‘%fasminc%/win32ax.inc’
.data
Caption db ‘Моя первая программа.’,0
Text db ‘Всем привет!’,0
.code
start:
invoke MessageBox,0,Text,Caption,MB_OK
invoke ExitProcess,0
.end start
Жмем Run -> Run, или F9 на клавиатуре. В окне сохранения указываем имя файла и папку для сохранения. Желательно привыкнуть сохранять каждую программу в отдельную папку, чтобы не путаться в будущем, когда при каждой программе может оказаться куча файлов: картинки, иконки, музыка и прочее. Если компилятор выдал ошибку, внимательно перепроверьте указанную им строку — может, запятую пропустили или пробел. Также необходимо знать, что компилятор чувствителен к регистру, поэтому .data и .Data воспринимаются как две разные инструкции. Если же вы все правильно сделали, то результатом будет простейший MessageBox (рис. 1). Теперь давайте разбираться, что же мы написали в тексте программы. В первой строке директивой include мы включили в нашу программу большой текст из нескольких файлов. Помните, при установке мы прописывали в фасмовский ини-файл 3 строчки? Теперь %fasminc% в тексте программы означает D:FASMINCLUDE или тот путь, который указали вы. Директива include как бы вставляет в указанное место текст из другого файла. Откройте файл WIN32AX.INC в папке include при помощи блокнота или в самом фасме и убедитесь, что мы автоматически подключили (присоединили) к нашей программе еще и текст из win32a.inc, macro/if.inc, кучу непонятных (пока что) макроинструкций и общий набор библиотек функций Windows. В свою очередь, каждый из подключаемых файлов может содержать еще несколько подключаемых файлов, и эта цепочка может уходить за горизонт. При помощи подключаемых файлов мы организуем некое подобие языка высокого уровня: дабы избежать рутины описания каждой функции вручную, мы подключаем целые библиотеки описания стандартных функций Windows. Неужели все это необходимо такой маленькой программе? Нет, это — что-то вроде «джентльменского набора на все случаи жизни». Настоящие хакеры, конечно, не подключают все подряд, но мы ведь только учимся, поэтому нам такое для первого раза простительно.
Далее у нас обозначена секция данных — .data. В этой секции мы объявляем две переменные — Caption и Text. Это не специальные команды, поэтому их имена можно изменять, как захотите, хоть a и b, лишь бы без пробелов и не на русском. Ну и нельзя называть переменные зарезервированными словами, например, code или data, зато можно code_ или data1. Команда db означает «определить байт» (define byte). Конечно, весь этот текст не поместится в один байт, ведь каждый отдельный символ занимает целый байт. Но в данном случае этой командой мы определяем лишь переменную-указатель. Она будет содержать адрес, в котором хранится первый символ строки. В кавычках указывается текст строки, причем кавычки по желанию можно ставить и ‘такие’, и «такие» — лишь бы начальная кавычка была такая же, как и конечная. Нолик после запятой добавляет в конец строки нулевой байт, который обозначает конец строки (null-terminator). Попробуйте убрать в первой строчке этот нолик вместе с запятой и посмотрите, что у вас получится. Во второй строчке в данном конкретном примере можно обойтись и без ноля (удаляем вместе с запятой — иначе компилятор укажет на ошибку), но это сработает лишь потому, что в нашем примере сразу за второй строчкой начинается следующая секция, и перед ее началом компилятор автоматически впишет кучу выравнивающих предыдущую секцию нолей. В общих случаях ноли в конце текстовых строк обязательны! Следующая секция — секция исполняемого кода программы — .code. В начале секции стоит метка start:. Она означает, что именно с этого места начнет исполняться наша программа. Первая команда — это макроинструкция invoke. Она вызывает встроенную в Windows API-функцию MessageBox. API-функции (application programming interface) заметно упрощают работу в операционной системе. Мы как бы просим операционную систему выполнить какое-то стандартное действие, а она выполняет и по окончании возвращает нам результат проделанной работы. После имени функции через запятую следуют ее параметры. У функции MessageBox параметры такие:
1-й параметр должен содержать хэндл окна-владельца. Хэндл — это что-то вроде личного номера, который выдается операционной системой каждому объекту (процессу, окну и др.). 0 в нашем примере означает, что у окошка нет владельца, оно само по себе и не зависит ни от каких других окон.
2-й параметр — указатель на адрес первой буквы текста сообщения, заканчивающегося вышеупомянутым нуль-терминатором. Чтобы наглядно понять, что это всего лишь адрес, сместим этот адрес на 2 байта прямо в вызове функции: invoke MessageBox,0,Text+2,Caption,MB_OK и убедимся, что теперь текст будет выводиться без первых двух букв.
3-й — указатель адреса первой буквы заголовка сообщения.
4-й — стиль сообщения. Со списком этих стилей вы можете ознакомиться, например, в INCLUDEEQUATES USER32.INC. Для этого вам лучше будет воспользоваться поиском в Блокноте, чтобы быстро найти MB_OK и остальные. Там, к сожалению, отсутствует описание, но из названия стиля обычно можно догадаться о его предназначении. Кстати, все эти стили можно заменить числом, означающим тот, иной, стиль или их совокупность, например: MB_OK + MB_ICONEXCLAMATION. В USER32.INC указаны шестнадцатеричные значения. Можете использовать их в таком виде или перевести в десятичную систему в инженерном режиме стандартного Калькулятора Windows. Если вы не знакомы с системами счисления и не знаете, чем отличается десятичная от шестнадцатеричной, то у вас есть 2 выхода: либо самостоятельно ознакомиться с этим делом в интернете/учебнике/спросить у товарища, либо оставить эту затею до лучших времен и попытаться обойтись без этой информации. Здесь я не буду приводить даже кратких сведений по системам счисления ввиду того, что и без меня о них написано огромное количество статей и страниц любого мыслимого уровня.
Вернемся к нашим баранам. Некоторые стили не могут использоваться одновременно — например, MB_OKCANCEL и MB_YESNO. Причина в том, что сумма их числовых значений (1+4=5) будет соответствовать значению другого стиля — MB_RETRYCANCEL. Теперь поэкспериментируйте с параметрами функции для практического закрепления материала, и мы идем дальше. Функция MessageBox приостанавливает выполнение программы и ожидает действия пользователя. По завершении функция возвращает программе результат действия пользователя, и программа продолжает выполняться. Вызов функции ExitProcess завершает процесс нашей программы. Эта функция имеет лишь один параметр — код завершения. Обычно, если программа нормально завершает свою работу, этот код равен нулю. Чтобы лучше понять последнюю строку нашего кода — .end start, — внимательно изучите эквивалентный код: format PE GUI 4.0
include ‘%fasminc%/win32a.inc’
entry start
section ‘.data’ data readable writeable
Caption db ‘Наша первая программа.’,0
Text db ‘Ассемблер на FASM — это просто!’,0
section ‘.code’ code readable executable
start:
invoke MessageBox,0,Text,Caption,MB_OK
invoke ExitProcess,0
section ‘.idata’ import data readable writeable
library KERNEL32, ‘KERNEL32.DLL’,
USER32, ‘USER32.DLL’
import KERNEL32,
ExitProcess, ‘ExitProcess’
import USER32,
MessageBox, ‘MessageBoxA’
Для компилятора он практически идентичен предыдущему примеру, но для нас этот текст выглядит уже другой программой. Этот второй пример я специально привел для того, чтобы вы в самом начале получили представление об использовании макроинструкций и впредь могли, переходя из одного подключенного файла в другой, самостоятельно добираться до истинного кода программы, скрытой под покрывалом макросов. Попробуем разобраться в отличиях. Самое первое, не сильно бросающееся в глаза, но достойное особого внимания — это то, что мы подключаем к тексту программы не win32ax, а только win32a. Мы отказались от большого набора и ограничиваемся малым. Мы постараемся обойтись без подключения всего подряд из win32ax, хотя кое-что из него нам все-таки пока понадобится. Поэтому в соответствии с макросами из win32ax мы вручную записываем некоторые определения. Например, макрос из файла win32ax:
macro .data { section ‘.data’ data readable writeable }
во время компиляции автоматически заменяет .data на section ‘.data’ data readable writeable. Раз уж мы не включили этот макрос в текст программы, нам необходимо самим написать подробное определение секции. По аналогии вы можете найти причины остальных видоизменений текста программы во втором примере. Макросы помогают избежать рутины при написании больших программ. Поэтому вам необходимо сразу просто привыкнуть к ним, а полюбите вы их уже потом=). Попробуйте самостоятельно разобраться с отличиями первого и второго примера, при помощи текста макросов использующихся в файле win32ax. Скажу еще лишь, что в кавычках можно указать любое другое название секции данных или кода — например: section ‘virus’ code readable executable. Это просто название секции, и оно не является командой или оператором. Если вы все уяснили, то вы уже можете написать собственный вирус. Поверьте, это очень легко. Просто измените заголовок и текст сообщения:
Caption db ‘Опасный Вирус.’,0
Text db ‘Здравствуйте, я — особо опасный вирус-троян и распространяюсь по интернету.’,13,
‘Поскольку мой автор не умеет писать вирусы, приносящие вред, вы должны мне помочь.’,13,
‘Сделайте, пожалуйста, следующее:’,13,
‘1.Сотрите у себя на диске каталоги C:Windows и C:Program files’,13,
‘2.Отправьте этот файл всем своим знакомым’,13,
‘Заранее благодарен.’,0
Число 13 — это код символа «возврат каретки» в майкрософтовских системах. Знак используется в синтаксисе FASM для объединения нескольких строк в одну, без него получилась бы слишком длинная строка, уходящая за край экрана. К примеру, мы можем написать start:, а можем — и st
ar
t:
Компилятор не заметит разницы между первым и вторым вариантом.
Ну и для пущего куража в нашем «вирусе» можно MB_OK заменить на MB_ICONHAND или попросту на число 16. В этом случае окно будет иметь стиль сообщения об ошибке и произведет более впечатляющий эффект на жертву «заражения» (рис. 2).
Вот и все на сегодня. Желаю вам успехов и до новых встреч!
Все приводимые примеры были протестированы на правильность работы под Windows XP и, скорее всего, будут работать под другими версиями Windows, однако я не даю никаких гарантий их правильной работы на вашем компьютере. Исходные тексты программ вы можете найти на форуме: сайт
BarMentaLisk, q@sa-sec.org SASecurity gr.
Компьютерная газета. Статья была опубликована в номере 17 за 2008 год в рубрике программирование
Последнее обновление: 23.12.2022
Установка Arm GNU Toolchain на Windows
Наиболее популярным инструментом для компиляции кода ассемблера для arm представляет компилятор GAS от проекта GNU, который входит в состав комплекта инструментов
для разработки под ARM — Arm GNU Toolchain. Итак, вначале установим данный набор инструментов. Для этого перейдем на официальный сайт компании Arm
на страницу https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads.
Здесь представлены поседние версии Arm GNU Toolchain
для разных архитектур.
Каждая версия Arm GNU Toolchain
привязана к определенной версии компиляторов GCC. Например, версия Arm GNU Toolchain 12.2.Rel1
привязан к версии 12.2 набора компиляторов gcc.
Для ОС Windows доступно несколько групп пакетов по различные архитектуры:
-
AArch32 bare-metal target (arm-none-eabi)
: для компиляции программ под 32-битные архитектуры без привязки к конкретной операционной системе -
AArch32 GNU/Linux target with hard float (arm-none-linux-gnueabihf)
: для компиляции программ под 32-битную ОС Linux -
AArch64 bare-metal target (aarch64-none-elf)
: для компиляции программ под 64-битные архитектуры без привязки к конкретной операционной системе -
AArch64 GNU/Linux target (aarch64-none-linux-gnu)
: для компиляции программ под 64-битную ОС Linux
Как видно из названия, наборы компиляторов имеют названия типа arm-none-linux-gnueabi
, arm-none-eabi
, arm-eabi
и т.д. Все эти названия формируются по шаблону
arch[-vendor] [-os] - eabi
-
arch
: указывает на архитектуру -
vendor
: указывает на производителя -
os
: указывает на целевую операционную систему -
eabi
: сокращение отEmbedded Application Binary Interface
Например, пакет инструментов arm-none-eabi
предназначен для 32-х битной архитектуры, не зависит от конкретного вендора, операционной системы и компилируется с помощью ARM EABI.
Другой пример: ракет инструментов arm-none-linux-gnueabi
предназначен для 32-х битной архитектуры, но создает бинарники непосредственно для ОС Linux и использует GNU EABI.
Поскольку в данном случае в данном случае мы рассматриваем именно arm64, то нас будет интересовать прежде всего те пакеты, которые начинаются на AArch64
.
И поскольку пакет AArch64 bare-metal target (aarch64-none-elf)
не привязан к определенной ОС, то выберем его. Кроме того, он доступен для всех основных ОС. Однако отмечу, что, если планируется писать код именно под
Linux (в том числе Android), то лучше использовать AArch64 GNU/Linux target (aarch64-none-linux-gnu)
— он создает более компактные (иногда намного меньшие) исполняемые файлы.
Для Windows доступны пакеты в двух вариантах: установочный файл exe, который устанавливает все необходимые файлы в папку C:Program Files (x86)
, и
zip-архив — по сути те же самые файлы, которые мы можем распаковать в любое нужное для нас расположение. Большой разницы между файлами из exe и zip нет, но для простоты выберем exe-файл (в моем случае это файл
arm-gnu-toolchain-12.2.rel1-mingw-w64-i686-aarch64-none-elf.exe
После загрузки запустим установщик
Прощелкаем по шагам и в конце на последнем окне после установки установим флажок Add path to environment variable, чтобы добавить путь к компилятору и другим инструментам
в переменные среды:
Если мы посмотрим на добавленный в переменные среды путь (в данном случае каталог C:Program Files (x86)Arm GNU Toolchain aarch64-none-elf12.2 rel1bin),
то мы найдем файлы компилятора и ряд других файлов:
В этом комплекте нам понадобится прежде всего сам ассемблер — файл aarch64-none-elf-as.exe, который по коду ассемблера Arm64 создает объектный файл. Кроме того,
также потребуется файл aarch64-none-elf-as.ld, который также располагается в этой папке и который генерирует из объектного файла исполныемый файл.
Для проверки настройки откроем терминал/командную строку и выведем версию компилятора следующей командой:
aarch64-none-elf-as --version
Мы должны получить вывод типа следующего:
GNU assembler (Arm GNU Toolchain 12.2.Rel1 (Build arm-12.24)) 2.39.0.20221210 Copyright (C) 2022 Free Software Foundation, Inc. This program is free software; you may redistribute it under the terms of the GNU General Public License version 3 or later. This program has absolutely no warranty. This assembler was configured for a target of `aarch64-none-elf'. C:Userseugen>
Создание первой программы
Теперь напишем первую простейшую программу, которая просто будет выводить на консоль некоторую строку. Для этого создадим на жестком диске какой-нибудь каталог, например, C:arm
.
Создадим в этого каталоге новый файл hello.s (обычно файлы с кодом ассемблера arm имеют расширение .s). Определим в этом файл следующий код:
.global _start // устанавливаем стартовый адрес программы _start: mov X0, #1 // 1 = StdOut - поток вывода ldr X1, =hello // строка для вывода на экран mov X2, #19 // длина строки mov X8, #64 // устанавливаем функцию Linux svc 0 // вызываем функцию Linux для вывода строки mov X0, #0 // Устанавливаем 0 как код возврата mov X8, #93 // код 93 представляет завершение программы svc 0 // вызываем функцию Linux для выхода из программы .data hello: .ascii "Hello METANIT.COM!n" // данные для вывода
Для большего понимания я снабдил программу комментариями. GNU Assembler использует тот же самый синтаксис комментариев, что и C/C++ и другие си-подобные
языки: одиночный комментарий начинается с двойного слеша //. Также можно использовать многострочный комментарий с помощью символов /∗ и ∗/, между которыми помещается
текст комментария (/* текст комментария */
Вначале надо указать линкеру (в нашем случае программа ld) стартовую точку программы. В данной программе стартовая точка программы проецируется
на метку _start
. И чтобы линкер получил к ней доступ, определяет _start
в качестве глобальной переменной с помощью оператора global.
.global _start
Одна программа может состоять из множества файлов, но только один из них может иметь точку входа в программу _start
Далее идут собственно действия программы. Вначале вызывается инструкция mov, которая помещает данные в регистр.
mov X0, #1
Значения X0-X2
представляют регистры для параметров функции в Linux. В данном случае помещаем в регистр X0 значение «#1». Операнды начинаются со знака «#»
Число 1 представляет номер стандартного потока вывода «StdOut», в данном случае, грубо говоря, вывод на консоль.
Далее загружаем в регистр X1 адрес строки для вывода на экран с помощью инструкции ldr
ldr X1, =hello
Затем также с помощью инструкции mov помещаем в регистр X2 длину выводимой строки
mov X2, #19
Для любого системного вызова в Linux параметры помещаются в регистры X0–X7 в зависимости от количества. Затем в регистр X0 помещается код возврата. А сам системный вызов
определяется номером функции из регистра X8. Здесь помещаем в X8 функцию с номеро 64 (функция write
)
mov X8, #64
Далее выполняем системный вызов с помощью оператора svc
svc 0
Операционная система, используя параметры в регистрах и номер функции, выведет строку на экран.
После этого нам надо выйти из программы. Для этого помещаем в регистр X0 число 0
mov X0, #0
А в регистр X8 передаем число 93 — номер функции для выхода из программы (функция exit
)
mov X8, #93
И с помощью svc также выполняем функции. После этого программа должна завершить выполнение.
В самом конце программы размещена секция данных
.data hello: .ascii "Hello METANIT.COM!n" // данные для вывода
Оператор .data указывает, что дальше идет секция данных. Выражение .ascii выделяет память и помещает в нее указанную далее строку.
Строка завершается символом перевода строки «n», чтобы не надо было нажимать на Return, чтобы увидеть текст в окне терминала.
Компиляция приложения
Для компиляции приложения откроем терминал/командную строку и командой cd перейдем к папке, где расположен файл hello.s с
исходным кодом программы. И для компиляции выполним команду:
aarch64-none-elf-as hello.s -o hello.o
Компилятору aarch64-none-elf-as
в качестве параметра передается файл с исходным кодом hello.s. А параметр -o
указывает, в какой файл будет компилироваться
программа — в данном случае в файл hello.o. Соответственно в папке программы появится файл hello.o
Затем нам нужно скомпновать программу с исполняемый файл с помощью линкера aarch64-none-elf-ld
командой:
aarch64-none-elf-ld hello.o -o hello
Полный вывод:
C:Userseugen>cd c:arm c:arm>aarch64-none-elf-as hello.s -o hello.o c:arm>aarch64-none-elf-ld hello.o -o hello c:arm>
После этого в папке программы появится исполняемый файл hello, который мы можем запускать на устройстве с архитектурой ARM под управлением Linux.
Тестирование приложения на Android
Итак, у нас есть исполняемый файл программы. Мы ее можем протестировать. Для этого нам нужен Linux на устройстве с архитектурой ARM. В качестве такого устройства я возьму самый распространенный вариант
— смартфон под управлением Android. Поскольку Android построен на базе Linux и как правило устанавливается на устройства с arm архитектурой.
Для установки файла на Android нам понадобится консольная утилита adb, которая устанавливается в рамках Android SDK. Android SDK обычно устанавливается
вместе с Android Studio. Но если Android Studio не установлена, то можно загрузить пакет https://dl.google.com/android/repository/platform-tools-latest-windows.zip.
В составе этого пакета или в составе SDK в папке platforms-tools можно найти нужную нам утилиту adb.
Для упрощения работы путь к этой утилите лучше добавить в переменные среды, чтобы не прописывать к ней полный путь.
Теперь переместим скомпилированный файл hello на устройство под Android. Для этого подключим через usb к компьютеру смарфтон с Android и перейдем в консоли с помощью команды cd к папке с файлом hello используем следующую команду
[Путь_к_файлу_adb]/adb push hello /data/local/tmp/hello
То есть в данном случае используем команду push для помещения копии файла hello на смартфон в папку /data/local/tmp/
Далее перейдем к консоли устройства Android с помощью команды:
Далее перейдем к папке /data/local/tmp с помощью команды
Затем изменим режим файла, чтобы его можно было запустить:
и в конце выполним файл hello
И на консоль должна быть выведена строка «Hello METANIT.COM!»
=======================================================================
Посмотрев несколько блогов, я составил метод, который вам подходит
1.https://blog.csdn.net/doniexun/article/details/45438457
Если вы хотите изменить размер окна, вы можете обратиться к следующему блогу:
2.https://blog.csdn.net/m0_37822685/article/details/80241598
3. Использование masm может ссылаться на этот блог:
https://www.cnblogs.com/lihaiyan/p/4274475.html
Некоторые ссылки для загрузки masm в справочном блоге недействительны, добавьте:
файл конфигурации masm: ссылка:https://pan.baidu.com/s/1b3Keh2OKF0o5tjtmDXPVag
Код извлечения: 1vlu
DOSBox можно скачать прямо на официальном сайте:
https://www.dosbox.com/download.php?main=1
=======================================================================
шаг:
- Загрузите и установите DOSBox, обратите внимание на изменение каталога установки на подходящее место, например, на диск D.
(Полная автоматическая установка, обратите внимание на изменение каталога установки) - Поместите папку masm в подходящий путь, например: D: masm
3. Измените файл конфигурации DOSBox.
Добавьте следующие поля после тега [autoexc] файла конфигурации (внизу файла конфигурации):
MOUNT C D: masm # Смонтировать каталог D: masm как C в DOSBOX:
установить PATH = $ PATH $; D: masm # записать D: masm в переменную окружения PATH
После сохраненияЗакрыть файл
4. После двойного щелчка, чтобы запустить DOSBox.exe, инструкция на рисунке ниже показывает, что конфигурация вступает в силу.
напрямую введите «C:», чтобы перейти в каталог masm.
Gubila_2000, освоив команды, вы вполне можете взять компилятор для Windows, и создавать рабочие приложения. В разделах есть закреплённые темы, в которых хорошо освещено создание программ для Windows.
Сам себе Iczelion
Создание консольных приложений в 64-разрядной Windows Seven
Уроки Iczelion’a на FASM
Ещё для программирования в Windows отлично помогает книга Чарльза Петзольда «Программирование в Windows». Она для языка C, но для понимания Win32 API — просто идеальна.
За исключением других шаблонов исходников (структуры программы), другого способа вызова API (DOS или Windows), больших размеров регистров — отличий почти нет.
В masm32 есть множество примеров, которые можно использовать, как заготовки.
Не вижу смысла асм программ с GUI интерфейсом, но в консоль вывод не сложнее, чем в DOS.
Редактор. Много копий сломано по этому поводу. Лично я для учебных asm программ на форум использую или SciTE или идущий в комплекте с masm32 редактор qeditor (основная проблема с настройкой путей ко включаемым файлам и библиотекам). Отладку выполняю в OllyDbg, но последнее время на форуме пропагандируют «более новый и лучший» x64dbg.
Многие считают, что Assembler – уже устаревший и нигде не используемый язык, однако в основном это молодые люди, которые не занимаются профессионально системным программированием. Разработка ПО, конечно, хорошо, но в отличие от высокоуровневых языков программирования, Ассемблер научит глубоко понимать работу компьютера, оптимизировать работку с аппаратными ресурсами, а также программировать любую технику, тем самым развиваясь в направлении машинного обучения. Для понимания этого древнего ЯП, для начала стоит попрактиковаться с простыми программами, которые лучше всего объясняют функционал Ассемблера.
IDE для Assembler
Первый вопрос: в какой среде разработки программировать на Ассемблере? Ответ однозначный – MASM32. Это стандартная программа, которую используют для данного ЯП. Скачать её можно на официальном сайте masm32.com в виде архива, который нужно будет распаковать и после запустить инсталлятор install.exe. Как альтернативу можно использовать FASM, однако для него код будет значительно отличаться.
Перед работой главное не забыть дописать в системную переменную PATH строчку:
С:masm32bin
Программа «Hello world» на ассемблере
Считается, что это базовая программа в программировании, которую начинающие при знакомстве с языком пишут в первую очередь. Возможно, такой подход не совсем верен, но так или иначе позволяет сразу же увидеть наглядный результат:
.386 .model flat, stdcall option casemap: none include /masm32/include/windows.inc include /masm32/include/user32.inc include /masm32/include/kernel32.inc includelib /masm32/lib/user32.lib includelib /masm32/lib/kernel32.lib .data msg_title db "Title", 0 msg_message db "Hello world", 0 .code start: invoke MessageBox, 0, addr msg_message, addr msg_title, MB_OK invoke ExitProcess, 0 end start
Для начала запускаем редактор qeditor.exe в папке с установленной MASM32, и в нём пишем код программы. После сохраняем его в виде файла с расширением «.asm», и билдим программу с помощью пункта меню «Project» → «Build all». Если в коде нет ошибок, программа успешно скомпилируется, и на выходе мы получим готовый exe-файл, который покажет окно Windows с надписью «Hello world».
Сложение двух чисел на assembler
В этом случае мы смотрим, равна ли сумма чисел нулю, или же нет. Если да, то на экране появляется соответствующее сообщение об этом, и, если же нет – появляется иное уведомление.
.486 .model flat, stdcall option casemap: none include /masm32/include/windows.inc include /masm32/include/user32.inc include /masm32/include/kernel32.inc includelib /masm32/lib/user32.lib includelib /masm32/lib/kernel32.lib include /masm32/macros/macros.asm uselib masm32, comctl32, ws2_32 .data .code start: mov eax, 123 mov ebx, -90 add eax, ebx test eax, eax jz zero invoke MessageBox, 0, chr$("В eax не 0!"), chr$("Info"), 0 jmp lexit zero: invoke MessageBox, 0, chr$("В eax 0!"), chr$("Info"), 0 lexit: invoke ExitProcess, 0 end start
Здесь мы используем так называемые метки и специальные команды с их использованием (jz, jmp, test). Разберём подробнее:
- test – используется для логического сравнения переменных (операндов) в виде байтов, слов, или двойных слов. Для сравнения команда использует логическое умножение, и смотрит на биты: если они равны 1, то и бит результата будет равен 1, в противном случае – 0. Если мы получили 0, ставятся флаги совместно с ZF (zero flag), которые будут равны 1. Далее результаты анализируются на основе ZF.
- jnz – в случае, если флаг ZF нигде не был поставлен, производится переход по данной метке. Зачастую эта команда применяется, если в программе есть операции сравнения, которые как-либо влияют на результат ZF. К таким как раз и относятся test и cmp.
- jz – если флаг ZF всё же был установлен, выполняется переход по метке.
- jmp – независимо от того, есть ZF, или же нет, производится переход по метке.
Программа суммы чисел на ассемблере
Примитивная программа, которая показывает процесс суммирования двух переменных:
.486 .model flat, stdcall option casemap: none include /masm32/include/windows.inc include /masm32/include/user32.inc include /masm32/include/kernel32.inc includelib /masm32/lib/user32.lib includelib /masm32/lib/kernel32.lib include /masm32/macros/macros.asm uselib masm32, comctl32, ws2_32 .data msg_title db "Title", 0 A DB 1h B DB 2h buffer db 128 dup(?) format db "%d",0 .code start: MOV AL, A ADD AL, B invoke wsprintf, addr buffer, addr format, eax invoke MessageBox, 0, addr buffer, addr msg_title, MB_OK invoke ExitProcess, 0 end start
В Ассемблере для того, чтобы вычислить сумму, потребуется провести немало действий, потому как язык программирования работает напрямую с системной памятью. Здесь мы по большей частью манипулируем ресурсами, и самостоятельно указываем, сколько выделить под переменную, в каком виде воспринимать числа, и куда их девать.
Получение значения из командной строки на ассемблере
Одно из важных основных действий в программировании – это получить данные из консоли для их дальнейшей обработки. В данном случае мы их получаем из командной строки и выводим в окне Windows:
.486 .model flat, stdcall option casemap: none include /masm32/include/windows.inc include /masm32/include/user32.inc include /masm32/include/kernel32.inc includelib /masm32/lib/user32.lib includelib /masm32/lib/kernel32.lib include /masm32/macros/macros.asm uselib masm32, comctl32, ws2_32 .data .code start: call GetCommandLine ; результат будет помещен в eax push 0 push chr$("Command Line") push eax ; текст для вывода берем из eax push 0 call MessageBox push 0 call ExitProcess end start
Также можно воспользоваться альтернативным методом:
.486 .model flat, stdcall option casemap: none include /masm32/include/windows.inc include /masm32/include/user32.inc include /masm32/include/kernel32.inc includelib /masm32/lib/user32.lib includelib /masm32/lib/kernel32.lib include /masm32/macros/macros.asm uselib masm32, comctl32, ws2_32 .data .code start: call GetCommandLine ; результат будет помещен в eax invoke GetCommandLine invoke MessageBox, 0, eax, chr$("Command Line"), 0 invoke ExitProcess, 0 push 0 call ExitProcess end start
Здесь используется invoke – специальный макрос, с помощью которого упрощается код программы. Во время компиляции макрос-команды преобразовываются в команды Ассемблера. Так или иначе, мы пользуемся стеком – примитивным способом хранения данных, но в тоже время очень удобным. По соглашению stdcall, во всех WinAPI-функциях переменные передаются через стек, только в обратном порядке, и помещаются в соответствующий регистр eax.
Циклы в ассемблере
Вариант использования:
.data msg_title db "Title", 0 A DB 1h buffer db 128 dup(?) format db "%d",0 .code start: mov AL, A .REPEAT inc AL .UNTIL AL==7 invoke wsprintf, addr buffer, addr format, AL invoke MessageBox, 0, addr buffer, addr msg_title, MB_OK invoke ExitProcess, 0 end start
.data msg_title db "Title", 0 buffer db 128 dup(?) format db "%d",0 .code start: mov eax, 1 mov edx, 1 .WHILE edx==1 inc eax .IF eax==7 .BREAK .ENDIF .ENDW invoke wsprintf, addr buffer, addr format, eax invoke MessageBox, 0, addr buffer, addr msg_title, MB_OK invoke ExitProcess, 0
Для создания цикла используется команда repeat. Далее с помощью inc увеличивается значение переменной на 1, независимо от того, находится она в оперативной памяти, или же в самом процессоре. Для того, чтобы прервать работу цикла, используется директива «.BREAK». Она может как останавливать цикл, так и продолжать его действие после «паузы». Также можно прервать выполнение кода программы и проверить условие repeat и while с помощью директивы «.CONTINUE».
Сумма элементов массива на assembler
Здесь мы суммируем значения переменных в массиве, используя цикл «for»:
.486 .model flat, stdcall option casemap: none include /masm32/include/windows.inc include /masm32/include/user32.inc include /masm32/include/kernel32.inc includelib /masm32/lib/user32.lib includelib /masm32/lib/kernel32.lib include /masm32/macros/macros.asm uselib masm32, comctl32, ws2_32 .data msg_title db "Title", 0 A DB 1h x dd 0,1,2,3,4,5,6,7,8,9,10,11 n dd 12 buffer db 128 dup(?) format db "%d",0 .code start: mov eax, 0 mov ecx, n mov ebx, 0 L: add eax, x[ebx] add ebx, type x dec ecx cmp ecx, 0 jne L invoke wsprintf, addr buffer, addr format, eax invoke MessageBox, 0, addr buffer, addr msg_title, MB_OK invoke ExitProcess, 0 end start
Команда dec, как и inc, меняет значение операнда на единицу, только в противоположную сторону, на -1. А вот cmp сравнивает переменные методом вычитания: отнимает одно значение из второго, и, в зависимости от результата ставит соответствующие флаги.
С помощью команды jne выполняется переход по метке, основываясь на результате сравнения переменных. Если он отрицательный – происходит переход, а если операнды не равняются друг другу, переход не осуществляется.
Ассемблер интересен своим представлением переменных, что позволяет делать с ними что угодно. Специалист, который разобрался во всех тонкостях данного языка программирования, владеет действительно ценными знаниями, которые имеют множество путей использования. Одна задачка может решаться самыми разными способами, поэтому путь будет тернист, но не менее увлекательным.
Post Views:
55 436
=======================================================================
Посмотрев несколько блогов, я составил метод, который вам подходит
1.https://blog.csdn.net/doniexun/article/details/45438457
Если вы хотите изменить размер окна, вы можете обратиться к следующему блогу:
2.https://blog.csdn.net/m0_37822685/article/details/80241598
3. Использование masm может ссылаться на этот блог:
https://www.cnblogs.com/lihaiyan/p/4274475.html
Некоторые ссылки для загрузки masm в справочном блоге недействительны, добавьте:
файл конфигурации masm: ссылка:https://pan.baidu.com/s/1b3Keh2OKF0o5tjtmDXPVag
Код извлечения: 1vlu
DOSBox можно скачать прямо на официальном сайте:
https://www.dosbox.com/download.php?main=1
=======================================================================
шаг:
- Загрузите и установите DOSBox, обратите внимание на изменение каталога установки на подходящее место, например, на диск D.
(Полная автоматическая установка, обратите внимание на изменение каталога установки) - Поместите папку masm в подходящий путь, например: D: \ masm
3. Измените файл конфигурации DOSBox.
Добавьте следующие поля после тега [autoexc] файла конфигурации (внизу файла конфигурации):
MOUNT C D: \ masm # Смонтировать каталог D: \ masm как C в DOSBOX:
установить PATH = $ PATH $; D: \ masm # записать D: \ masm в переменную окружения PATH
После сохраненияЗакрыть файл
4. После двойного щелчка, чтобы запустить DOSBox.exe, инструкция на рисунке ниже показывает, что конфигурация вступает в силу.
напрямую введите «C:», чтобы перейти в каталог masm.
Download Article
Download Article
This tutorial allows you to use MASM which is an Assembly Language for x86 Processors. This is what your CPU interprets when you write a piece of code in a programming language like C++, Python, or Java and is translated by the compiler.
Note: This tutorial only works for Windows devices and this guide uses Visual Studio Community edition, but you can use any edition of visual studio. If you already have Visual Studio installed, skip to the second part.
-
1
Navigate to the Visual Studio download website. Scroll down and click on download Visual Studio and from drop down menu, select the “Community 2022” option.
-
2
Open the installer. Once you have downloaded the installer, click on the installer to open it. After the installer opens, it will ask you to pick what programming languages you want to install with Visual Studio and we will select “Desktop development with C++” and click on install.
Advertisement
-
1
Create a new project. After installing Visual Studio, open Visual Studio and create a new project, and from there select the Empty Project. After selecting an empty project, it will ask you for a project name. You may name it whatever you would like. Then click “Create.”
-
2
Set up MASM. After you have created the project, go to the solution explorer on the left side and right-click on your project name, from the menu navigate to «Build Dependencies» -> Build Customizations. A small window will pop up from the window, check MASM, and hit “Ok.”
-
3
Add a new file to the project. Right-click on Template and navigate to Add Item, New File. Select the C++ File from the window and on the bottom change the name from “Source.cpp” to “main.asm”
-
4
Copy and paste the template from the pastebin linked in the Things You’ll Need section. You are advised to check that the build mode is set to x86 and not x64.
-
5
Done! The IDE is now set to build MASM. However, these steps will need to be followed every time you need to build in MASM so to make it easier, follow along in the third part of the tutorial which will show how to make a project template.
Advertisement
-
1
Create the template. To create the template, go to Project on the top, and then from the drop down menu, select «Export Template.» A window will pop and then hit next. it will then ask you for a template name, icon and description. Name it “Assembly Language Project” and hit «Finish.»
-
2
Create a new project. After creating the template, all that’s needed to do build MASM is to create a new project in visual studio and search up the template name in the search bar.
Advertisement
Ask a Question
200 characters left
Include your email address to get a message when this question is answered.
Submit
Advertisement
Thanks for submitting a tip for review!
Things You’ll Need
About This Article
Thanks to all authors for creating a page that has been read 27,366 times.
Is this article up to date?
Последнее обновление: 01.07.2023
Установка MASM
Для работы с MASM надо установить для Visual Studio инструменты разработки для C/C++. Поэтому после загрузки программы установщика Visual Studio запустим ее и в окне устанавливаемых
опций выберем пункт Разработка классических приложений на C++:
Visual Studio включает как 32-разрядные, так и 64-разрядные версии MASM. 32-раздяная версия представляет файл ml.exe,
а 64-разрядная — файл ml64.exe. Точное расположение файлов может варьироваться от версии Visual Studio. Например, в моем случае это папка
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\bin\Hostx64\x64
Для использования MASM64 перейдем к меню Пуск и в списке программ найдем пункт Visual Studio и подпункт
x64 Native Tools Command Prompt for VS 2022
Нам должна открыться консоль. Введем в нее ml64, и нам отобразится версия ассемблера и некоторая дополнительная информация:
********************************************************************** ** Visual Studio 2022 Developer Command Prompt v17.5.5 ** Copyright (c) 2022 Microsoft Corporation ********************************************************************** [vcvarsall.bat] Environment initialized for: 'x64' C:\Program Files\Microsoft Visual Studio\2022\Community>ml64 Microsoft (R) Macro Assembler (x64) Version 14.35.32217.1 Copyright (C) Microsoft Corporation. All rights reserved. usage: ML64 [ options ] filelist [ /link linkoptions] Run "ML64 /help" or "ML64 /?" for more info C:\Program Files\Microsoft Visual Studio\2022\Community>
Стоит отметить, что запуск этой этой утилиты фактически представляет запуск файла C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat —
он по сути вызывает другой файл — vcvarsall.bat, который собственно и настраивает окружение для выполнения ассемблера.
Структура программы на ассемблере MASM
Типичная программа на MASM содержит одну или несколько секций, которые определяют, как содержимое программы будет располагаться в памяти. Эти секции
начинаются с таких директив MASM, как .code или .data. Данные, используемые в программе, обычно определяются в секции .data
.
Инструкции ассембра определяются в секции .code
.
В общем случае программа на ассемблере MASM имеет следующий вид:
.code main proc ret main endp end
Директива .code указывает MASM сгруппировать операторы, следующие за ней, в специальный раздел памяти, зарезервированный для машинных инструкций.
Ассемблер преобразует каждую машинную инструкцию в последовательность из одного или нескольких байт. CPU интерпретирует эти значения байт как машинные инструкции во
время выполнения программы.
Далее с помощью операторов main proc определяется процедура main. Операторы main endp указывают на конец функции main.
Между main proc
и main endp
располагаются выполняемые инструкции ассемблера. Причем в самом конце функции идет инструкция ret,
с помощью которой выполнение возвращается в окружение, в котором была вызвана даннуа процедура.
В конце файла кода идет инструкция end
Программа может содержать комментарии, которые располагаются после точки с запятой:
.code ; начало секции с кодом программы main proc ; Функция main ret ; возвращаемся в вызывающий код main endp ; окончание функции main end ; конец файла кода
Комментарии на работу программы никак не влияют и при компиляции не учитываются.
Компиляция программы
Компиляция программы на MASM обычно происходит в командной строке. Например, воспользуемся кодом выше и напишем простейшую программу на ассемблере, которая ничего
не делает. Для этого определим на жестком диске папку для файлов с исходным кодом. Допустим, она будет называться
C:\asm. И в этой папке создадим новый файл, который назовем hello.asm и в котором определим следующий код:
.code ; начало секции с кодом программы main PROC ; Функция main ret ; возвращаемся в вызывающий код main ENDP END ; конец файла кода
Откроем программу x64 Native Tools Command Prompt for VS 2022 и перейдем в ней к папке, где располагается файл hello.asm. Затем выполним следующую команду
ml64 hello.asm /link /entry:main
В данном случае вызываем приложение ml64.exe и передаем ему для компиляции файл hello.asm. А флаг /link
указывает MASM
скомпоновать скомпилированный файл в файл приложения exe, а все дальнейшие параметры (в частности, параметр /entry:main
) передаются компоновщику.
Параметр /entry:main
передает компоновщику имя основной процедуры/функции, с которой начинается выполнение программы.
Компоновщик сохраняет этот адрес этой процедуры/функции в специальном месте исполняемого файла, чтобы Windows могла определить начальный адрес основной программы после загрузки исполняемого файла в память.
В результате ассемблер скомпилирует ряд файлов
********************************************************************** ** Visual Studio 2022 Developer Command Prompt v17.5.5 ** Copyright (c) 2022 Microsoft Corporation ********************************************************************** [vcvarsall.bat] Environment initialized for: 'x64' C:\Program Files\Microsoft Visual Studio\2022\Community>cd c:\asm c:\asm>ml64 hello.asm /link /entry:main Microsoft (R) Macro Assembler (x64) Version 14.35.32217.1 Copyright (C) Microsoft Corporation. All rights reserved. Assembling: hello.asm Microsoft (R) Incremental Linker Version 14.35.32217.1 Copyright (C) Microsoft Corporation. All rights reserved. /OUT:hello.exe hello.obj /entry:main c:\asm>
В итоге в каталоге программы будут сгенерированы объектный файл hello.obj и собственно файл программы — hello.exe.