Глава 2. Потегляме
В тази глава ще напишем първата си Windows програма на асемблер. Ще я стартираме, за да почувстваме удоволствието и да си качим настроението. След това ще я анализираме ред по ред, за да разберем какво точно прави.
1. Написване:
Стартирайте FASMW (сполучливо инсталиран от вас, според указанията в Глава 0) и в редактора въведете (или пък копирайте) следните редове:
format PE GUI
MB_OK = 00h
MB_ICONEXCLAMATION = 30h
push MB_OK + MB_ICONEXCLAMATION
push _caption
push _message
push 0
call [MessageBox]
push 0
call [ExitProcess]
_caption db 'Win32 Assembly Programming',0
_message db 'I feel the power.',0
data import
dd 0, 0, 0, RVA kernel_name, RVA kernel_table
dd 0, 0, 0, RVA user_name, RVA user_table
dd 0, 0, 0, 0, 0
kernel_table:
ExitProcess dd RVA _ExitProcess
dd 0
user_table:
MessageBox dd RVA _MessageBoxA
dd 0
kernel_name db 'KERNEL32.DLL',0
user_name db 'USER32.DLL',0
_ExitProcess dw 0
db 'ExitProcess',0
_MessageBoxA dw 0
db 'MessageBoxA',0
end data
2. Компилиране
Запишете файла някъде под името например "hello.asm". Натиснете Ctrl+F9 за да компилирате програмата. Ако всичко е въведено правилно, FASM ще ви даде прозорче с кратка информация за компилацията. Обърнете внимание на размера - 1024 байта.
Ако се появи грешка, FASM ще позиционира курсора на реда на грешката и ще спре компилацията.
Ако стане така, вероятно имате грешки при въвеждането на програмата. Сравнете реда където е грешката с оригиналният от сорса по-горе.
Сега натистете F9 (или изберете от менюто Run|Run). Програмата се стартира и показва диалогов прозорец със съобщението: "I feel the power." и бутон ОК. При натискане на бутона, програмата спира.
3. Обяснения на кода.
Сега да анализираме текста по-подробно:
1:
format PE GUI
Това е директивата на асемблера "format" която указва в какъв формат искаме да получим изпълнимият файл. "PE GUI" са параметрите, които указват, че искаме EXE файл за Win32 (PE означава "portable executable") Едно от хубавите неща на FASM е че може да създава директно изпълними файлове.
2:
MB_OK = 00h
MB_ICONEXCLAMATION = 30h
В тези два реда създаваме две числови константи с имена MB_OK
и
MB_ICONEXCLAMATION
. Това се прави с цел да направим текста на програмата
по-четлив. Навсякъде по-нататък, където се използват тези константи с
имената си, ще бъдат заместени със стойностите си по време на компилацията.
Забележете - използва се шестнайсетичен код - превърнете го в двоичен, за да упражним знанията от предишната глава.
3:
push MB_OK + MB_ICONEXCLAMATION
push _caption
push _message
push 0
call [MessageBox]
Тук стартираме функция от Windows API, която показва модален прозорец със съобщение (т.н. message box) и чака натискането на бутон.
Тук за първи път се срещаме с две инструкции на процесора:
push - вкарва нещо в стека. В случая няколко константи. Това е стандартният начин за предаване на параметри към фунциите на Windows. Стекът е, структура от типа LIFO - last-in-first-out. Инструкцията push вкарва стойност на върха на стека, а инструкцията pop - изважда последната вкарана стойност. Стека е важна структура в работата на процесора и в следващата глава ще я разгледаме по-подробно. За сега е достатъчно да знаете, че аргументите към функциите на Windows се предават, като се вкарват в стека, откъдето функцията си ги взима.
call - стартира подпрограма (функция). След изпълнението на подпрограмата, програмата продължава да се изпълнява на следващата инструкция след call.
За да видите описанието на функцията MessageBox, позиционирайте текстовата каретка в редактора на думата "MessageBox" и натиснете F1. Обърнете внимание че реда в който се вкарват параметрите в стека е обратен на реда в който са описани параметрите във функцията на C.
Това е особенност на т.н. конвенция за викане "stdcall". Разбира се да се пише викане на функция на 5 реда, когато в C това се пише на един ред, не е особенно удобно и четливо в контекста на голям проект, когато такива стартирания на функции са хиляди.
Но затова пък прави механизма на работа пределно ясен - което е и целта на това упражнение.
По нататък ще разгледаме и по кратки начини за правене на тези неща.
4:
push 0
call [ExitProcess]
Отново извикване на функция на Windows - този път с един параметър. Функцията ExitProcess спира изпълнението на програмата.
За упражнение, разгледайте описанието на функцията в API документацията, както направихме в предишният случай за MessageBox.
5:
_caption db 'Win32 Assembly Programming',0
_message db 'I feel the power.',0
Това е дефиниция на текста, който искаме да се показва в прозореца. "_caption" и "_message" са т.н. етикети. По същество това са също константи, които имат за стойност мястото (точният термин е "адресът") където се намират някакви данни или инструкции в паметта. Тъй като това място зависи от предишните инструкции и данни, то не се знае предварително. Затова се задава и използва с някакво име, а компилаторът, по време на компилацията изчислява точната му стойност.
Както можете да видите в точка 3: програмата използва тези етикети, за да каже на функцията MessageBox къде се намира текстът, който искаме да се покаже.
Директивата db (define byte) служи за дефиниране на данни. Като параметри на тази
директива могат да се задават, текст заграден в единична или двойна кавичка или
числа -128..255
отделени със запетайки. Как точно текста се представя в паметта на
машината (компютърът работи само с числа) ще разгледаме подробно в следващите
теоретични глави.
6:
data import
dd 0, 0, 0, RVA kernel_name, RVA kernel_table
dd 0, 0, 0, RVA user_name, RVA user_table
dd 0, 0, 0, 0, 0
kernel_table:
ExitProcess dd RVA _ExitProcess
dd 0
user_table:
MessageBox dd RVA _MessageBoxA
dd 0
kernel_name db 'KERNEL32.DLL',0
user_name db 'USER32.DLL',0
_ExitProcess dw 0
db 'ExitProcess',0
_MessageBoxA dw 0
db 'MessageBoxA',0
end data
И най-накрая ще разгледаме набързо т.н. импорт таблица. Тази таблица с данни е свързана със структурата на PE exe файла във Windows и с начина по който Windows предоставя на програмите достъп до функциите от API.
Всички API функции на Windows се съдържат в различните системни .dll библиотеки. Например "kernel32.dll", "user32.dll", "comctrl32.dll" и много други.
Windows не предоставя на програмите всички тези функции (те са хиляди) едновременно, а само тези функции, които програмата си поиска.
В импорт таблицата се описват именно от кои .dll файлове и кои именно функции, програмата иска да получи за използване (да импортира).
По време на зареждането на програмата в паметта, Windows прочита тази таблица и попълва в съответните полета адресите на функциите, които са му поискани.
Програмата след това използва тези полета за да стартира функциите, когато и трябват.
Дефинирането на импорт таблица, по начина по който е направено в този пример е трудна работа, особенно ако импортираме десетки и стотици функции. В стандартната библиотека на FASM са предвидени файлове, които улесняват и автоматизират тази работа, като импортират само функциите, които са използвани в програмата. И отново - с тези библиотеки ще се запознаем малко по-нататък.
Last modified on: 06.06.2012 21:41:24