asm32.info
Keep it simple — code in asm

Глава 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

Preview

Comments

:)Мариян:

Разделения на секции код наистина работи :). Даже при това, само задаването на '.import' секцията явно оправя проблема. Прилича ми на защита на сигурността(даже антивирусната се задейства без тази директива), но не мога да съм сигурен. Въпреки това, явно деленето на секции е нещо важно при по-новите версии на Windows. Благодаря за разяснението.

:)John Found:

Ако можеш, би ли обяснил защо трябва тази команда и какво представлява тя?

section '.reloc' fixups data readable discardable

Тази директива създава т.н. fixup секция в изпълнимият файл. Тази секция съдържа таблица с указатели, към местата в кода, които трябва да се променят, ако програмата се зарежда на адрес в паметта различен от този за който е компилирана.

Информацията в тази секция се създава автоматично от компилатора и затова трябва само да се декларира, че искаме да се създаде.

Подобни секции, обикновенно има само в .dll библиотеките, защото те се зареждат от Windows където има свободно място в паметта. Изпълнимите файлове, винаги се зареждат на един и същ адрес и затова нямат нужда от fixups секция.

Последните версии на Windows отказват да зареждат .dll библиотеки, ако нямат fixups секция, дори и конкретният .dll да няма нужда от нея (тоест всички адреси са относителни и библиотеката може да се зарежда на произволно място, без преизчисляване на адресите)

Как всичко това касае .exe файловете обаче ми е съвършенно неясно. :) Във всеки случай, слагането на fixups секция, макар и излишно е безопасно и може да касае единствено размера на изпълнимият файл.

За съжаление, нямам достъп до 64 битов Windows 7 и не мога да направя никакви тестове.

Примерът в статията е написан така, че използва само една секция за всичко (което е позволено в стандарта). Може новите Windows-и да не го харесват заради това.

Може да опиташ да разделиш секциите на код, данни и импорти, за да видим какво ще се получи:

format PE GUI

section '.code' readable executable

MB_OK              = 00h
MB_ICONEXCLAMATION = 30h

        push    MB_OK + MB_ICONEXCLAMATION
        push    _caption
        push    _message
        push    0
        call    [MessageBox]

        push    0
        call    [ExitProcess]

section '.data' data readable writeable

_caption db 'Win32 Assembly Programming',0
_message db 'I feel the power.',0

section '.import' import readable
             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

:)Мариян:

Много ми харесва статията, много поучителна. Иска ми се да вмъкна само, че при мен(ОС:Win7 64 bit) тази примерна програма не тръгна. Трябваше да добавя "section '.reloc' fixups data readable discardable" (без кавичките) на края на кода за да заработи. Ако можеш, би ли обяснил защо трябва тази команда и какво представлява тя? Аз просто я взех от едно от демо-тата на FASM и си нямам идея какво прави.

2. Старт
Filename:
Title: