Invoke ExitProcess,0
main endp
AddDig proc
mov eax,[esp+08h]
mov ecx,[esp+04h]
add eax,ecx
ret 8
AddDig endp
End main
Вызываемая процедура сама заботится о восстановлении стека, поэтому не забываем, если осуществляется передача параметров функции (без описания аргументов в заголовке функции), то к ret добавляем количество параметров, умноженное на их размер (ret 4*2).
(но если пользуемся описание аргументов proc ttt:dword то записываем просто ret потому что ассемблер будет знать размер и число параметров процедуры и не нуждается в подсказке)
Процедуры Windows API требуют подключения библиотеки .lib чтобы ассемблер знал по крайней мере адрес процедуры для формирования команды call.
При вызове Windows API обязательно нужен прототип. (Если приводим библиотечную функцию к натуральному вызову, через call, то прототип нужен все равно. Для своей же функции через call, прототип не нужен, потому что call просто команда процессора может использоваться отдельно от процедур, а пустой заголовок функции это просто метка программы)
Includelib kernel32.Lib
ExitProcess proto stdcall :dword
….
push 0
call ExitProcess
Листинг программы «Не могу молчать»
Includelib kernel32.Lib
ExitProcess proto stdcall :dword
GetStdHandle proto stdcall :dword
WriteConsoleA proto stdcall :dword, :DWORD, :DWORD, :DWORD, :DWORD
.data
StdH dword ?
wr byte "HelloHer!",0Dh,0Ah
wrC dword ?
.code
main proc
invoke GetStdHandle,-11
mov StdH,eax
invoke WriteConsoleA,eax,addr wr,sizeof wr,addr wrC,0
push 0
call ExitProcess
main endp
End main
Для вывода на окно консоли текста необходимо сначала получить дескриптор стандартного устройства (GetStdHandle), число которое нужно указать другим процедурам, работающим с этим устройством. Потом вызвать функцию ОС (WriteConsole).
addr – оператор получения адреса. Ассемблер вычисляет этот адрес во время компиляции. (для lp - параметров)
kernel32.inc – описаны прототипы процедур (ExitProcess proto :dword)
windows.inc – содержит описание STD_OUTPUT_HANDLE equ -11 символические имена понятнее.
Для приведения процедур к стандартному виду нужно указать число и размер параметров дважды – в прототипе и заголовке.
Размышления
над функциями.
(когда рассматриваем любую функцию,
должны всегда помнить про основные
характеристики функций — это порядок
передачи параметров
и выравнивание
стека для
call
там прозрачно и забота лежит полностью
на программисте для invoke
необходимы договоренности).
Нам надо воспользоваться функциями
1. Какие бывают функции? (пользовательские и системные)
1.1 Какие функции мы хотим вызвать? (пользовательские или системные, или те и другие)
2. Как мы их будем вызывать? (через call или через invoke) (для вызова как своих, так и системных можно использовать call можно invoke).
3. Что нам нужно дополнительно чтобы ими воспользоваться. ()
если используем для системных функций директиву invoke
1. Необходимо определится о глобальном порядке передачи параметров функции по умолчанию (stdcall – порядок передачи параметров процедуре, вызываемая процедура должна сама заботится о восстановлении стека, первым в стек отправляется последний параметр)
2. Подключить информацию о системных библиотеках (директива includelib подключает к ассемблерному тексту файл библиотеки содержащий сведения о процедурах ОС, необходимые для их правильного вызова, например - includelib G:\myasm\lib\kernel32.lib)
3. Описать прототип функции. (директива proto кратко описывает параметры процедуры. ExitProcess proto STDCALL :DWORD Ассемблер переведет исходный текст на язык процессора только тогда, когда описание параметров при директиве proto соответствует описанию процедуры в библиотеке, а так же параметрам, указанным при вызове процедуры директивой invoke, тогда ret сама ставит необходимое количество byte,ов в конце процедуры (только при вызове через invoke)
4. Вызвать директивой invoke. (процедуры вызываются специальной директивой invoke (за которой следуют): имя процедуры и список параметров, разделенный запятыми. Пример - invoke ExitProcess, 0)
если используем для системных функций команду call
call - для сырой работы, сама по себе, в принципе она не нуждается в правилах, т.к. это просто инструкция процессора, где в качестве операнда задан адрес, но для вызова системной функцией через call необходим ряд правил, т.к. ассемблер видит адрес (имя функции) как особый адрес в силу того что он является системной функцией и все равно необходимо:
1.Подключить информацию о системных библиотеках (директива includelib).
2.Описать прототип функции.
3.Запушить параметры в стек (по stdcall первым в стек отправляется последний параметр) и вызвать командой call. (задача по верной передаче параметров лежит на программисте, а возврат из функции – выравнивание стека, это забота системной функции, для этого и нужен прототип и директива includelib)
Для пользовательской функции через call (если хочешь, чтобы не было ни пролога, ни эпилога используешь call и в функции не пишешь заголовок)
1. Не надо подключить информацию о системных библиотеках (директива includelib), т.к. это не системная библиотека
2. Не надо описывать прототип функции (если нет заголовка процедуры), хотя если напишем прототип, то ошибки не будет.
3. Нужно написать саму функцию так чтобы она не была вложена. (не забывая о порядке передачи параметров и выравнивании стека)
3. Вызвать командой call.
Если написали функцию без заголовка, то
(если передаем параметры через регистры, то и нечего парится насчет стека) (если через стек, то нужно помнить о том, что последний хранится адрес возврата ret, а параметры будут, начиная с +04h причем самый первый отправляемый в стек - будет дальше всего хранится от esp)
не забываем, если осуществляется передача параметров функции (без описания аргументов в заголовке функции), то к ret добавляем количество параметров, умноженное на их размер (ret 4*2).
Если вызываешь процедуру через call (не через invoke), и хочешь, чтобы текст ассемблировал без пролога и эпилога то в заголовке самой функции не надо ставить описание аргументов функции.
Для пользовательской функции через invoke (Для приведения процедур к стандартному виду нужно указать число и размер параметров дважды – в прототипе и заголовке) (Если вызываешь через invoke, то ставим описание прототипа и в теле функции описываем параметры, причем если в прототипе нет необходимости в именах параметров (обязателен только тип :dword), то в теле функции обязательны имена параметров, хоть мы эти имена и не используем)
Создать заголовок функции и в нем указать число и размер параметров (причем по нему ассемблер создает фрейм стека, который надо учитывать если обращаемся к параметрам сами, через числовые смещения)
Описать прототип функции и в нем указать число и размер параметров. (директива proto кратко описывает параметры процедуры. WrOur proto STDCALL :DWORD Ассемблер переведет исходный текст на язык процессора только тогда, когда описание параметров при директиве proto соответствует описанию процедуры
в библиотеке, а так же параметрам, указанным при вызове процедуры директивой invoke, тогда ret сама ставит необходимое количество byte,ов в конце процедуры (только при вызове через invoke)
