asm

advertisement
Учебное пособие по созданию консольных приложений на ассемблере под Win32.
Win32 - название интерфейса прикладных программ
(application programming
interface, API), содержит совокупность функций, к которым может обращаться
приложение. В данном пособии рассматривается Win32 для операционных систем Windows NT
и Windows 9x.
В Win32 существует только одна модель памяти - FLAT. Память представляется одним
большим непрерывным пространством размером 4Гб. В этом пространстве располагаются и
данные и коды программы.
Структуры программы.
.386
.MODEL FLAT, STDCALL
.DATA
<Инициализация
компиляции>
необходимых
данных,
значения
которых
определены
к
моменту
. DATA?
<Резервирование места для данных, которые определяются в процессе работы
программы>
.CONST
<Константы, т.е. данные, не изменяемые в процессе работы программы>
.CODE <Метка>
<Коды программы>
END <Метка>
Анализ структуры программы.
.386
Это директива, сообщающая ассемблеру использовать набор инструкций 80386. Вы также
можете использовать .486, .586.
.MODEL FLAT, STDCALL
.MODEL это директива ассемблера, которая описывает модель памяти, используемую
вашей программой.
Обращение к сервису операционной системы в Windows осуществляется посредством
вызова функций, а не прерываний, что было характерно для DOS. Здесь нет передачи
параметров в регистрах при обращении к сервисным функциям, параметры передаются в
функции через стек. STDCALL сообщает транслятору о способе передачи аргументов в
функцию (parameter passing convention). Способ передачи аргументов определяет, в
какой последовательности аргументы будут передаваться в функцию: слева направо или
справа налево, и каким образом будет осуществляться коррекция стека после вызова
функции. Существует два способа передачи аргументов, как в Си и как в Паскале. Сиподобным способом аргументы передаются справа налево, то есть самый правый аргумент
передается первым. Программа сама отвечает за корректировку стека после вызова.
Например, если необходимо вызвать функцию
void funcf (int first_param, int second_param, int third_param);
Си-подобным способом, код на Ассемблере будут выглядеть следующим образом:
push
push
push
call
add
[third_param]
[second_param]
[first_param]
func
esp,12
;
;
;
;
;
передача третьего параметра
передача второго параметра
передача первого параметра
вызов функции
программа самостоятельно корректирует стек
В Паскаль-подобном способе все наоборот по сравнению с Си-подобным. Передача
аргументов осуществляется слева направо и за корректировку стека после вызова функции
отвечает сама функция. Си-подобный способ полезен в том случае, когда вы заранее не
знаете количество передаваемых параметров в функцию, как в случае с wsprintf(). В
этом случае функция не может заранее определить сколько параметров будет передано в
стек, поэтому не сможет правильно провести коррекцию стека. STDCALL - является
объединением Си- и Паскаль - подобного способа - аргументы передаются справа налево,
но коррекцию стека после вызова функции осуществляет сама эта функция. Платформа
Win32 использует исключительно STDCALL. Кроме одного случая: wsprintf(). Для этой
функции используется Си-подобный способ передачи аргументов в функцию. Используя
способ передачи параметров STDCALL, приведенный выше вызов функции func будет
выглядеть так:
push
push
push
call
[third_param]
[second_param]
[first_param]
func
;
;
;
;
передача третьего параметра
передача второго параметра
передача первого параметра
вызов функции
За коррекцию стека отвечает сама вызываемая функция.
.DATA
.DATA?
.CONST
.CODE
Все четыре директивы являются именами секций памяти. В Win32 сегмейтов нет. Но вы
можете поделить все адресное пространство на секции. Начало одной секции является
концом предыдущей. Существует две группы секций: секция данных и кода. Секции данных,
в свою очередь делятся на 3 категории: .DATA - В этой секции содержатся
инициализируемые
данные
программы.
.DATA?
В
этой
секции
содержатся
не
инициализируемые данные программы. Иногда вам просто необходимо зарезервировать
некоторый объем памяти, но вы не хотите его инициализировать, тогда для этой цели
используйте данную секцию.
.CONST - В этой секции содержатся константы, используемые в программе. Значения в
этой секции программой изменяться не могут. Вам нет необходимости использовать все
секции. Пользуйтесь только теми из них, которые вам необходимы. Секция кода всего
одна: .CODE. В ней содержатся все инструкции программы.
<Метка>
<Коды программы>
end <Метка>
где <Метка>
программы.
это
стартовая
метка,
с
которой
начинается
выполнение
кода
вашей
При написании консольных приложений понадобятся несколько функций. Прототипы этих
функций на языке С и вызов на ассемблере приводятся ниже. При использовании пакета
MASM в вызове функций с параметрами будет использоваться invoke вместо call.
1. Получение дескриптора ввода/вывода.
Прототип на языке С: HANDLE GetstdHandle(DWORD nStdHandle);
Вызов на Ассемблере: call GetstdHandle, nStdHandle
nStdHandle сообщает функции какой дескриптор нужно возвратить. Принимает следующие
значения:
STD_INPUT_HANDLE - дескриптор ввода.
STD_OUTPUT_HANDLE - дескриптор вывода.
STD_ERROR_HANDLE - дескриптор вывода ошибок.
Если функция выполнена успешно, то возвращаемое значение есть дескриптор, который
можно применять в дальнейшем при работе с функциями ввода/вывода. В случае неудачи
функция возвращает INVALID_HANDLE_VALUE.
2. Чтение символов с консоли (Enter - Ввод)
Прототип на языке С: BOOL ReadConsole (HANDLE hConsoleInput, LPVOID IpBuffer,
DWORD nNumberOfCharsToRead, LPDWORD lpNumberOfCharsRead,
LPVOID lpReserved);
Вызов
на
Ассемблере:
call
ReadConsole,
hConsoleInput,
lpBuffer,
nNumberOfCharsToRead, lpNumberOfCharsRead, lpReserved
hConsoleInput - дескриптор ввода, полученный ранее,
lpBuffer - адрес буфера, в который будут помещаться прочитанные символы,
nNumberOfCharsToRead - количество символов, которые необходимо прочитать,
lpNumberOfCharsRead - адрес переменной, в которую будет записано количество
прочитанных символов,
lpReserved - зарезервировано (не используется).
Если функция выполнена успешно, то она возвращает ненулевое значение. В случае
неудачи функция возвращает ноль .
3.
Запись символов на консоль ($ - конец строки)
Прототип
Вызов
на
на
языке С: BOOL WriteConsole (HANDLE hConsoleOutput, CONST VOID *
lpBuffer,
DWORD
nNumberOfCharsToWrite,
LPDWORD
lpNumberOfCharsWritten, LPVOID lpReserved);
Ассемблере:
call
WriteConsole,
hConsoleOutput,
lpBuffer,
NumberOfCharsToWrite, lpNumberOfCharsWritten, lpReserved
hConsoleOutput - дескриптор вывода,
lpBuffer - адрес буфера, содержимое которого необходимо вывести,
nNumberOfCharsToWrite - количество символов для вывода,
lpNumberOfCharsWritten - адрес переменной, в которую будет записано количество
выведенных символов,
lpReserved - зарезервировано (не используется) .
Если функция выполнена успешно, то она возвращает ненулевое значение. В случае
неудачи функция возвращает ноль.
4.
Выход из программы с кодом возврата.
Прототип на языке С: VOID ExitProcess (UINT uExitCode);
Вызов на Ассемблере: call ExitProcess, uExitCode
uExitCode - код возврата.
Данная функция прекращает выполнение программы, поэтому она должна быть последней
вызываемой в программе функцией.
При вызове функций, параметры передаются через стек, как описывалось ранее, а
возвращаемое функцией значение передается через регистр еах - для целых и st(0) - для
вещественных. В качестве передаваемых в функцию данных могут выступать как данные,
так и адреса.
При использовании различных трансляторов, например TASM и MASM, код программы
может несколько отличатся. Рассмотрим пример простой программы, которая демонстрирует
применение приведенных выше функций при использовании трансляторов TASM и MASM.
Пример программы для TASM
.386
;использование инструкций 386 процессора
.model flat,stdcall
;использование модели памяти flat и способа
;передачи аргументов в функции stdcall
;описание используемых функций
extern ExitProcess:proc
extern GetStdHandle:proc
extern WriteConsoleA:proc
extern ReadConsoleA:proc
includelib import32.lib
.data?
hInput
hOutput
bw
br
buffer
dd
dd
dd
dd
db
?
?
?
?
100 dup (?)
.data
string
string_len
db
equ
‘Введите строку: ’
$-string
;$ - текущее значение счетчика адреса. Его
;удобно использовать для определения длины строки.
;Выражение $-string определяет длину строки string.
.const
STD_INPUT_HANDLE equ
STD_OUTPUT_HANDLE equ
;дескриптор ввода
;дескриптор вывода
;количество записанных символов
;количество прочитанных символов
;буфер на 100 символов
-10
-11
; начало сегмента кода
.code
main:
;получение дескриптора вывода
call
GetStdHandle,STD_OUTPUT_HANDLE
mov
hOutput, eax
;получение дескриптора ввода
call
GetStdHandle, STD_INPUT_HANDLE
mov
hInput,eax
;вывод строки
call
WriteConsoleA,hOutput,offset string,string_len,offset bw,0
;чтение строки
call
ReadConsoleA,hInput,offset buffer,100,offset br,0
;вывод прочтенной строки
call WriteConsoleA,hOutput,offset buffer,br,offset bw, 0
;выход из приложения с кодом возврата О
call ExitProcess,О
end main
Пример программы для MASM
.386
.model flat,stdcall
include windows.inc
include kernel32.inc
includelib kernel32.1ib
.data?
hinput
houtput
bw
br
buffer
dd
dd
dd
dd
db
;использование инструкций 386 процессора
;использование модели памяти flat и способа
;передачи аргументов в функции stdcall
;описание констант
;описание прототипов функций
; библиотека с используемыми фущщиями
?
;дескриптор
?
;дескриптор
?
;количество
?
;количество
100 dup (?) ;буфер на
ввода
вывода
записанных символов
прочитанных символов
100 символов
.data
string
string_len
db
equ
‘Введите строку: ’
$-string
;$ - текущее значение счетчика адреса. Его
;удобно использовать для определения длины строки.
;Выражение $-string определяет длину строки string.
; начало сегмента кода
.code
main:
; получение дескриптора вывода
invoke GetStdHandle,STD_OUTPUT_HANDLE
mov
houtput, eax
; получение дескриптора ввода
invoke GetStdHandle,STD_INPUT_HANDLE
mov
hlnput,eax
; вывод строки
invoke WriteConsole,hOutput, offset string,string_len,offset bw, 0
; чтение введенной строки
invoke ReadConsole,hInput,offset buffer,100,offset br,0
; вывод только, что введенной строки
invoke WriteConsole, hOutput,offset buffer,br,offset bw, 0
; выход из приложения с кодом возврата О
invoke ExitProcess,О
end main
Download