Реализация вызовов функции в ассемблере.
Кроме параметров имеет значение алгоритм работы функции, так называемое «соглашение о вызове функции». Оно связано с построением работы в различных операционных системах различных высокоуровневых языков программирования.
В настоящее время существуют целый ряд принятых соглашений (конвенций) о вызове функций (подпрограмм): cdecl (язык Си, С++), pascal (язык Pascal), stdcall (WinApi), fastcall, safecall, thiscall. Все они начинали использоваться в разное время и с разными целями, решая определённые задачи, наиболее приемлемым способом.
Необходимо помнить, что для функции в ассемблере нет определённых договорённостей — вызывать можно так, как удобнее и оптимальнее. Мы рассмотрим наиболее используемые конвенции высокоуровневых языков программирования и их реализации на ассемблере.
Изучить примеры работы компиляторов языков высокого уровня крайне полезно, если вас интересует кодокопание в любом виде (крэкинг, реверс-программирование и др.) .
Регистры-указатели
Регистрами-указателями являются 32-битные регистры EIP, ESP и EBP и соответствующие им 16-битные регистры IP, SP и BP. Есть три категории регистров-указателей:
Указатель на инструкцию или команду (Instruction Pointer или IP) — 16-битный регистр IP хранит смещение адреса следующей команды, которая должна быть выполнена. IP в сочетании с регистром CS (как CS:IP) предоставляет полный адрес текущей инструкции в сегменте кода.
Указатель на стек (Stack Pointer или SP) — 16-битный регистр SP обеспечивает значение смещения в программном стеке. SP в сочетании с регистром SS (SS:SP) означает текущее положение данных или адреса в программном стеке.
Базовый указатель (Base Pointer или BP) — 16-битный регистр BP используется в основном при передаче параметров в подпрограммы. Адрес в регистре SS объединяется со смещением в BP, чтобы получить местоположение параметра. BP также можно комбинировать с DI и SI в качестве базового регистра для специальной адресации.
Функции WinAPI.
Переходим к актуальному вопросу — написанию прикладных программ для Windows. Одним из составляющих основ программирования в операционной среде Windows продолжает оставаться активное использование функций WinAPI, встроенных в любую операционную систему Windows, начиная от первой версии и заканчивая последней на настоящий момент десятой версией. Функции WinAPI писались на Си и были созданы для использования при написании прикладных программ на языке программирования Си. Программирование для Windows с использованием WinAPI и макросов на ассемблере и Си имеют 90% внешнего и 100% смыслового сходства.
Сохранение регистров с помощью стека.
В программе на ассемблере активно используются регистры (ax, cx, bx и т.д.). При входе в процедуру, значения регистров необходимо сохранять. Это очень удобно делать при помощи стека («запушить регистры», используя PUSH). После этого регистры могут активно использоваться в коде функции. Значения их будет меняться, но мы можем легко его восстановить. При выходе из функции значения регистров восстанавливают при помощи команд POP (не забываем о правиле «последний зашёл — первый вышел»).
…
;основной код программы использует bx и cx
my_proc proc
push bx
push cx
;код процедуры использует bx и cx, сответственно их данные будут измененны
…
pop cx
pop bx
ret
my_proc endp
;продолжение основного кода программы: bx и cx восстановлены, логика основного кода работает
…
1 |
… ;основной код программы использует bx и cx my_procproc pushbx pushcx … popcx popbx ret my_procendp ;продолжение основного кода программы: bx и cx восстановлены, логика основного кода работает … |
Как говорилось в начале статьи, одна из самых важных задач, эффективно решаемых при помощи стека — передача параметров функции. Это нам точно понадобиться при рассмотрения программирования в системах Windows, в особенности с использованием Win32 API функций
С учётом важности этого вопроса мы рассматриваем его в следующей статье: «MS-DOS и TASM 2.0. Часть 17
Конвенции вызова функции в ассемблере».
Рекомендуем обновить информацию, которая нам известна о данных («MS-DOS и TASM 2.0. Часть 10. Данные«).
О компиляторах
Какую операционную систему вы бы хотели использовать?
Windows | DOS | Linux | BSD | QNX | MacOS, работающий на процессоре Intel/AMD |
|
---|---|---|---|---|---|---|
FASM | x | x | x | x | ||
GAS | x | x | x | x | x | x |
GoAsm | x | |||||
HLA | x | x | ||||
MASM | x | x | ||||
NASM | x | x | x | x | x | x |
RosAsm | x | |||||
TASM | x | x |
Качество документации
Документация | Комментарии | |
---|---|---|
FASM | Хорошая | Большую часть свободного времени автор отдает в разработку инновационного FASMG. Тем не менее, автор обеспечивает поддержку FASM время от времени обновляет мануалы, а новые функции описывает на собственном форуме. Документацию можно считать достаточно хорошей. Веб-страница документации. |
Gas | Плохая | документирован слабо и документация, скорее, имеет «общий вид». gas ― это ассемблер, который был разработан, чтобы можно было легко писать код для разных процессоров. Документация, которая существует, в основном описывает псевдо коды и ассемблерные директивы. В режиме работы «intel_syntax» документация практически отсутствует. Книги, в которых используется синтаксис «AT&T»: «Программирование с нуля» Джонатона Бартлетта и «Профессиональный язык ассемблера» Ричарда Блюма, Konstantin Boldyshev asmutils — Linux Assembly. |
GoAsm | Слабая | Большая часть синтаксиса описана в руководстве, и опытный пользователь найдет то, что ищет. Множество руководств и размещено на сайте (http://www.godevtool.com/). Несколько учебников GoAsm:
|
HLA | Обширая | HLA имеет справочное руководство на 500 страниц. Сайт содержит десятки статей и документацию по HLA. |
MASM | Хорошая | Компанией Microsoft написано значительное количество документацию для MASM, существует большое количество справочников написанных для этого диалекта. |
NASM | Хорошая | Авторы NASM больше пишут программное обеспечение для этого диалекта, оставляя написание руководства на «потом». NASM существует достаточно долго, поэтому несколько авторов написали руководство для NASM Джефф Дунтеман (Jeff Duntemann) «Assembly Language Step-by-Step: Programming with Linux», Jonathan Leto «Writing A Useful Program With NASM», на русском языке есть книга Столярова (Сайт А.В. Столярова). |
RosAsm | Слабая | не очень интересные «онлайновые учебники». |
TASM | Хорошая | Компания Borland в свое время выпускала отличные справочные руководства, для TASM были написаны справочные руководства авторами-энтузиастами не связанными с фирмой Borland. Но Borland больше не поддерживает TASM, поэтому большая часть документации, предназначенная для TASM, не печатается и ее становится всё труднее и труднее найти. |
Учебники и учебные материалы
Комментарии | |
---|---|
FASM | Несколько учебников, в которых описывается программирование на FASM:
|
Gas | Учебник с использованием синтаксиса AT&TУчебник Ассемблер в Linux для программистов C |
HLA | 32-разрядная версия «The Art of Assembly Language Programming» (существует и в электронной, и в печатной форме), программирование под Windows или Linux |
MASM | большое количество книг по обучению программированию под DOS. Не очень много книг о программировании под Win32/64 Пирогов, Юров, Зубков, Фленов |
NASM | много книг, посвященных программированию в DOS, Linux, Windows. В книге Джеффа Дунтемана «Assembly Language Step-by-Step: Programming with Linux» используется NASM для Linux и DOS. Учебник Пола Картера использует NASM (DOS, Linux). |
TASM | Как и для MASM, для TASM было написано большое количество книг на основе DOS. Но, так как Borland больше не поддерживает этот продукт, писать книги об использовании TASM перестали. Том Сван написал учебник, посвященный TASM, в котором было несколько глав о программировании под Windows. |
Добавление новых типов команд
Но, конечно, главное назначение описываемых средств – это добавление в имеющийся транслятор новых групп команд по мере появления новых поколений процессоров.
При этом в транслятор иногда приходится добавлять и новую группу имен специальных регистров этих команд (внутри транслятора имена это просто переименованные числа). Так, коды имен регистров CR0-CR7 являются внутри транслятора RASM числами 10H-17H, коды имен регистров MM0-MM7 числами 40H-47H, коды имен регистров XMM0-XMM7 числами 50H-57H, и т.д. Младшая цифра чисел (всегда 0-7) участвует в генерации кода через директиву MODRM, а собственно значения чисел используются для задания допустимого диапазона в формальных параметрах новых макро.
При поиске подходящих операндов транслятор проверит, что указанный в команде регистр входит в допустимый диапазон и поэтому, например, в командах MMX вместо MM0 нельзя указать «чужой» регистр CR0 или XMM0.
Часто в новых множествах команд требуется применить директиву NOSEGFIX 5, выключающую обычные правила использования префикса 66H (в зависимости от размера операндов), поскольку в описываемых командах этот префикс используется по-своему.
Тогда, например, для команд из множества MMX описания выглядят так:
Для команд из множества XMM:
Для команд из множества SSE2:
Для команд из множества 3DNow!:
Выражения
Компилятор позволяет
использовать в программе выражения
которые могут состоять ,
и . Все выражения
являются 32-битными.
Операнды
Могут быть использованы
следующие операнды:
- Метки определённые
пользователем (дают значение
своего положения). - Переменные определённые
директивой - Константы определённые
директивой - Числа заданные в формате:
- Десятичном (принят по
умолчанию): 10, 255 - Шестнадцатеричном (два
варианта записи): 0x0a, $0a, 0xff,
$ff - Двоичном: 0b00001010, 0b11111111
- Восьмеричном (начинаются с
нуля): 010, 077
- Десятичном (принят по
- PC — текущее значение
программного счётчика (Programm
Counter)
Операции
Компилятор поддерживает ряд
операций, которые перечислены в
таблице (чем выше положение в
таблице, тем выше приоритет
операции). Выражения могут
заключаться в круглые скобки, такие
выражения вычисляются перед
выражениями за скобками.
Приоритет | Символ | Описание |
14 | ||
14 | ||
14 | ||
13 | ||
13 | ||
12 | ||
12 | ||
11 | ||
11 | ||
10 | ||
10 | ||
10 | ||
10 | ||
9 | ||
9 | ||
8 | ||
7 | ||
6 | ||
5 | ||
4 |
Логическое
отрицание
Символ: ! Описание:
Возвращает 1 если выражение равно 0,
и наоборот Приоритет: 14 Пример: ldi r16, !0xf0═ ; В
r16 загрузить 0x00
Побитное отрицание
Символ: ~ Описание:
Возвращает выражение в котором все
биты проинвертированы Приоритет: 14 Пример: ldi r16, ~0xf0═ ; В
r16 загрузить 0x0f
Минус
Символ: - Описание:
Возвращает арифметическое
отрицание выражения Приоритет: 14 Пример: ldi r16,-2═ ;
Загрузить -2(0xfe) в r16
Деление
Символ: / Описание:
Возвращает целую часть результата
деления левого выражения на правое
Приоритет: 13 Пример: ldi r30, label/2
Вычитание
Символ: - Описание:
Возвращает результат вычитания
правого выражения из левого Приоритет: 12 Пример: ldi r17, c1-c2
Сдвиг влево
Символ: << Описание:
Возвращает левое выражение
сдвинутое влево на число бит
указанное справа Приоритет: 11 Пример: ldi r17,
1<<bitmask═ ; В r17 загрузить 1
сдвинутую влево bitmask раз
Сдвиг вправо
Символ: >> Описание:
Возвращает левое выражение
сдвинутое вправо на число бит
указанное справа Приоритет: 11 Пример: ldi r17,
c1>>c2═ ; В r17 загрузить c1 сдвинутое
вправо c2 раз
Меньше чем
Символ: < Описание:
Возвращает 1 если левое выражение
меньше чем правое (учитывается
знак), и 0 в противном случае Приоритет: 10 Пример: ori r18,
bitmask*(c1<c2)+1
Меньше или равно
Символ: <= Описание:
Возвращает 1 если левое выражение
меньше или равно чем правое
(учитывается знак), и 0 в противном
случаеПриоритет: 10 Пример: ori r18,
bitmask*(c1<=c2)+1
Больше чем
Символ: > Описание:
Возвращает 1 если левое выражение
больше чем правое (учитывается
знак), и 0 в противном случаеПриоритет: 10 Пример: ori r18,
bitmask*(c1>c2)+1
Больше или равно
Символ: >= Описание:
Возвращает 1 если левое выражение
больше или равно чем правое
(учитывается знак), и 0 в противном
случаеПриоритет: 10 Пример: ori r18,
bitmask*(c1>=c2)+1
Равно
Символ: == Описание:
Возвращает 1 если левое выражение
равно правому (учитывается знак), и 0
в противном случаеПриоритет: 9 Пример: andi r19,
bitmask*(c1==c2)+1
Не равно
Символ: != Описание:
Возвращает 1 если левое выражение
не равно правому (учитывается знак),
и 0 в противном случаеПриоритет: 9 Пример: .SET flag =
(c1!=c2)═ ;Установить flag равным 1 или 0
Побитное
исключающее ИЛИ
Символ: ^ Описание:
Возвращает результат побитового
исключающего ИЛИ выражений Приоритет: 7 Пример: ldi r18, Low(c1^c2)
Логическое И
Символ: && Описание:
Возвращает 1 если оба выражения не
равны нулю, и 0 в противном случае
Приоритет: 5 Пример: ldi r18,
Low(c1&&c2)
Логическое ИЛИ
Символ: || Описание:
Возвращает 1 если хотя бы одно
выражение не равно нулю, и 0 в
противном случаеПриоритет: 4 Пример: ldi r18, Low(c1||c2)
- LOW(выражение) возвращает
младший байт выражения - HIGH(выражение) возвращает
второй байт выражения - BYTE2(выражение) то же что и
функция HIGH - BYTE3(выражение) возвращает
третий байт выражения - BYTE4(выражение) возвращает
четвёртый байт выражения - LWRD(выражение) возвращает биты
0-15 выражения - HWRD(выражение) возвращает биты
16-31 выражения - PAGE(выражение) возвращает биты
16-21 выражения - EXP2(выражение) возвращает 2 в
степени (выражение) - LOG2(выражение) возвращает целую
часть log2(выражение)
Директива контроля сегментов NOSEGFIX
Директива имеет параметры в виде имени сегментного регистра и имени формального параметра. Она не генерирует кода, а проверяет, что обращение к данному параметру идет с использованием указанного сегментного регистра, иначе сообщает об ошибке. Эта директива требуется лишь в общих формах команд CMPS и MOVS, где один из операндов может адресоваться только через ES.
Данная директива была расширена для управления префиксами размера и адреса 66H/67H. В этом случае в директиве указывается параметр-число: 0 – нет префиксов, 1- может быть префикс 66H, 2 – может быть префикс 67H, 3 – могут быть оба префикса, 4 – всегда есть оба, 5 – никогда нет 66H, 6 – никогда нет 67H и т.п.
Такими простыми средствами удается описать все множество команд IA-32, например:
Некоторое исключение из стройной системы описаний составляют команды FPU, имеющие операнд в памяти. Для простоты в RASM разрядность таких команд указывается прямо в мнемонике, а не определяется по размеру операнда в памяти. Поэтому в RASM есть, например, команды FIST16, FIST32 и FIST64. Однако на практике, с точки зрения ясности текста, указание разрядности операнда прямо в имени команды FPU оказалось вполне приемлемым.
Operands
Inline assembler operands are expressions that consist of constants, registers, symbols, and operators.
Within operands, the following reserved words have predefined meanings:
Built-in assembler reserved words
- CPU registers
Category |
Identifiers |
---|---|
8-bit CPU registers |
AH, AL, BH, BL, CH, CL, DH, DL (general purpose registers); |
16-bit CPU registers |
AX, BX, CX, DX (general purpose registers); DI, SI, SP, BP (index registers); CS, DS, SS, ES (segment registers); IP (instruction pointer) |
32-bit CPU registers |
EAX, EBX, ECX, EDX (general purpose registers); EDI, ESI, ESP, EBP (index registers); FS, GS (segment registers); EIP |
FPU |
ST(0), …, ST(7) |
MMX FPU registers |
mm0, …, mm7 |
XMM registers |
xmm0, …, xmm7 (…, xmm15 on x64) |
Intel 64 registers |
RAX, RBX, … |
Data and Operators
Category |
Identifiers |
---|---|
Data |
BYTE, WORD, DWORD, QWORD, TBYTE |
Operators |
NOT, AND, OR, XOR; SHL, SHR, MOD; LOW, HIGH; OFFSET, PTR, TYPE |
VMTOFFSET, DMTINDEX |
|
SMALL, LARGE |
Reserved words always take precedence over user-defined identifiers. For example:
var Ch Char; // … asm MOV CH, 1 end;
loads 1 into the CH register, not into the Ch variable. To access a user-defined symbol with the same name as a reserved word, you must use the ampersand (&) override operator:
MOV&Ch, 1
It is best to avoid user-defined identifiers with the same names as built-in assembler reserved words.
Стек при работе с процедурой (функцией).
Теперь рассмотрим, как ведёт себя стек в ассемблере при работе с процедурой при использовании call и ret. Прогоним нашего «гоблина» через отладчик. Напомним, что IP — Указатель команд (Index Pointer). На каждом шаге выполнения программы указывает на адрес команды, следующей за исполняемой. Используем горячую клавишу F7 — Trace — пошаговое выполнение программы с заходом в циклы и процедуры.
…cs:0113> call 0140;IP = 0113.
Вызываем процедуру коммандой call 0140. В IP будет занесено значение указателя на нашу процедуру и она начнёт выполняться.
cs:0116> jmp 0100;
…
Вызываем процедуру командой call
После выхода из процедуры программа продолжит выполнение с IP = 0116. Стек (sp=FFFE): ss:FFFE>0000.
Возвращаемся из процедуры к адресу, следующему за call в адрес cs:0116
Что интересно, команду ret tasm транслировал в jmp. В данном случае ассемблер самостоятельно использовал оптимизацию кода в сторону ускорения быстродействия. В некоторых случаях такое «самоуправство» не приветствуется (написание «вирусного» кода и др.). Такими «болезнями» болеют все ассемблеры — кто-то больше, кто-то меньше. Знания прийдут вместе с опытом. Можно посоветовать всегда не лениться и пропускать исполняемый файл через отладчик.
Директивы определения данных
А куда-же без этого? Перед использованием данные надо определять. Это хороший тон. Вот что для этого есть.
- DB — определить одни байт
- DW — определить два байта(слово)
- DD — определить двойное слово
- DF — определить 6 байт
- DQ — определить учетверённое слово (8 баёт)
- DT — определить 10 байт (80 битные типы данных для FPU)
Вот примеры определения данныхtext db ‘Hi! i’m string!’number dw 7878tbl db 1,2,3,4,5,6,7,8,9,10,11,12float dd 3.5e7empty dw ?
Здесь видно что после директив идут значения, причём их может быть несколько! В этом случае имя переменной определяет адрес первого элемента! Это важно понять.
Вот обращение к первому символу: mov al,text.Пустое значение задается знаком вопроса. Кроме этого для массива можно использовать оператор DUP. Его формат счётчик DUP (значение). tbl dw 1024 dup (?) задаёт массив неинициализированных слов размеров 1Кб. В ассемблере также можно определять структуры данных аналогично языкам высокого уровня! Кроме того структуры могут быть вложенными. Рассмотрим на примере:point struct ;вводим новый тип данныхx dw 0 ;здесь два поля но их количество y dw 0 ;на самом деле произвольно, как и тип данных!point ends ;закончили описание нового типа данныхcur_point point <10,244> ; объявляем переменную нового типа и указываем значения……………………….mov ax,cur_point.x; а так можно обратиться к элементу структуры.
Записи
Запись представляет собой набор полей бит, объединенных одним именем. Каждое поле записи имеет собственную длину, исчисляемую в битах, и не обязано занимает целое число байтов. Объявление записи в программе на языке ассемблера включает в себя 2 действия: 1) объявление шаблона или типа записи директивой RECORD; 2) объявление собственно записи. Формат директивы RECORD: имя_записи RECORD имя_поля:длина],… Директива RECORD определяет вид 8- или 16-битовой записи, содержащей одно или несколько полей. Имя_записи представляет собой имя типа записи, которое будет использоваться при объявлении записи. Имя_поля и длина (в битах) описывает конкретное поле записи. Выражение, если оно указано задает начальное (умалчиваемое) значение поля. Описания полей записи в директиве RECORD, если их несколько, должны разделяться запятыми. Для одной записи может быть задано любое число полей, но их суммарная длина не должна превышать 16 бит.
Длина каждого поля задается константой в пределах от 1 до 16. Если общая длина полей превышает 8 бит, ассемблер выделяет под запись 2 байта, в противном случае – 1 байт. Если задано выражение, оно определяет начальное значение поля. Если длина поля не меньше 7 бит, в качестве выражения может быть использован символ в коде ASCII. Выражение не должно содержать ссылок вперед. Пример: item RECORD char:7=’Q’,weight:4=2 Запись item будет иметь следующий вид: char weight 0000 1010001 0010 При обработке директивы RECORD формируется шаблон записи, а сами данные создаются при объявлении записи, которое имеет следующий вид: ] имя_записи <]>
По такому объявлению создается переменная типа записи с 8- или 16-битовым значением и структурой полей, соответствующей шаблону, заданному директивой RECORD с именем имя_записи. Имя задает имя переменной типа записи. Если имя опущено, ассемблер распределяет память, но не создает переменную, которую можно было бы использовать для доступа к записи.
В скобках <> указывается список значений полей записи. Значения в списке, если их несколько, должны разделяться запятыми. Каждое значение может быть целым числом, строковой константой или выражением и должно соответствовать длине данного поля. Для каждого поля может быть задано одно значение. Скобки <> обязательны, даже если начальные значения не заданы. Пример: table item 10 DUP(<‘A’,2>)
Если для описания шаблона записи использовалась директива RECORD из предыдущего примера, то по этому объявлению создается 10 записей, объединенных именем table.
Команды сравнения данных
Команды данной группы выполняют сравнение значений числа в вершине стека и операнда, указанного в команде.Команды сравнения данных вещественного типа
Команда | Операнды | Пояснение | Описание |
FCOM FUCOM | src | ST(0) — src | Вещественное сравнение |
FCOMP FUCOMP | src | ST(0) — src; TOPSWR+=1; | Вещественное сравнение с выталкиванием |
FCOMPP FUCOMPP | — | ST(0) — ST(1); TOPSWR+=2; | Вещественное сравнение с двойным выталкиванием |
FCOMI FUCOMI | ST, ST(i) | ST(0) — ST(i) | Вещественное сравнение c модификацией EFLAGS |
FCOMIP FUCOMIP | ST, ST(i) | ST(0) — ST(i); TOPSWR+=1; | Вещественное сравнение c выталкиванием с модификацией EFLAGS |
FXAM | — | Анализ ST(0) |
Команды сравнения сравнивают значение в вершине стека с операндом. По умолчанию (если операнд не задан) происходит сравнение регистров ST(0) и ST(1). В качестве операнда может быть задана ячейка памяти или регистр. Команда устанавливает биты C0, C2, C3 регистра swr в соответствии с таблицей. Сбрасывает в 0 признак C1 при пустом стеке после выполнения команды.
Условие | С3 | С2 | C0 |
ST(0) > src | |||
ST(0) < src | 1 | ||
ST(0) = src | 1 | ||
Недопустимая операция (#IA) | 1 | 1 | 1 |
Особый интерес представляет команда FCOMI (FUCOMI). Она сравнивает содержимое регистра ST(0) со значением операнда ST(i) и устанавливает биты ZF, PF, CF регистра в соответствии с таблицей. Анализ выполнения сравнения осуществляет последующая команда условного перехода (команда центрального процессора).
Условие | ZF | PF | CF | Переход |
ST(0) > ST(i) | ja | |||
ST(0) < ST(i) | 1 | jb | ||
ST(0) = ST(i) | 1 | je | ||
ST(0) >= ST(i) | * | jae | ||
ST(0) <= ST(i) | * | * | jbe | |
Недопустимая операция (#IA) | 1 | 1 | 1 |
Команда FXAM проверяет содержимое регистра ST(0) и устанавливает биты C0, C2, C3 регистра swr в соответствии с таблицей. Бит C1 устанавливается равным знаковому биту ST(0).
Класс | С3 | С2 | C0 |
Неподдерживаемый формат | |||
Нечисло (NaN) | 1 | ||
Конечное число | 1 | ||
Бесконечность | 1 | 1 | |
Ноль | 1 | ||
Пустой регистр | 1 | 1 | |
Ненормированное число | 1 | 1 |
Команды сравнения данных целого типа
Команда | Операнды | Пояснение | Описание |
FICOM | src | ST(0) — src | Cравнение с целым числом src |
FICOMP | src | ST(0) — src; TOPSWR+=1; | Cравнение с целым числом src с выталкиванием |
FTST | — | ST(0)-0; | Анализ ST(0) (сравнение с нулем) |
Инструкция MOVS
Инструкция MOVS используется для копирования элемента данных (byte, word или doubleword) из исходной строки в строку назначения. Исходная строка указывается с помощью , а строка назначения указывается с помощью .
Рассмотрим это на практике:
section .text
global _start ; должно быть объявлено для использования gcc
_start: ; сообщаем линкеру входную точку
mov ecx, len
mov esi, s1
mov edi, s2
cld
rep movsb
mov edx,20 ; длина сообщения
mov ecx,s2 ; сообщение для вывода на экран
mov ebx,1 ; файловый дескриптор (stdout)
mov eax,4 ; номер системного вызова (sys_write)
int 0x80 ; вызов ядра
mov eax,1 ; номер системного вызова (sys_exit)
int 0x80 ; вызов ядра
section .data
s1 db ‘Hello, world!’,0 ; первая строка
len equ $-s1
section .bss
s2 resb 20 ; назначение
1 |
section.text global_start; должно быть объявлено для использования gcc _start; сообщаем линкеру входную точку movecx,len movesi,s1 movedi,s2 repmovsb movedx,20; длина сообщения movecx,s2; сообщение для вывода на экран movebx,1; файловый дескриптор (stdout) moveax,4; номер системного вызова (sys_write) int0x80; вызов ядра moveax,1; номер системного вызова (sys_exit) int0x80; вызов ядра section.data s1db’Hello, world!’,; первая строка
lenequ$-s1 section.bss s2resb20; назначение |
Результат:
Прерывания DOS.
В DOS необходимо пользоваться справочником. Наиболее удобный и совершенный из известных — T_HELP (смотрите рисунок в начале статьи, сам справочник есть в архиве — папка T_HELP). Есть прерывание DOS, а есть BIOS. Прерывания зависят от версии DOS. Чтобы вызвать функцию прерывания, необходимо подготовить соответствующие регистры. Обычно — это AX (ah, al) Члены, а также выходные значения функций передаются именно через них.
Разобраться с T_HELP можете сами. Наиболее удобно изучать функции с использованием API Index. Например, запускаем …T_HELP\XVIEW.EXE:
- TECH Topics
- DOS Interrupts
- INT 21H DOS Services
- DOS Function Index (by number)Ищем в табличном списке прерывание 4CH, кликаем по нему левой клавишей мыши и изучаем подробную справку:
Dos Fn 4cH : Terminate Program
AH 4CH
AL Exit Code
…
ну и тд.
На последок, посмотрим, как выглядит наша программа, обработанная дебагером, встроенным в HIEW (Alt+P):
00000000: B409 mov ah,009 ;» »
00000002: BA4A01 mov dx,0014A ;» J»
00000005: CD21 int 021
00000007: B401 mov ah,001 ;» »
00000009: CD21 int 021
0000000B: 3C6D cmp al,06D ;»m»
0000000D: 7409 je 000000018 ——— (1)
0000000F: 3C77 cmp al,077 ;»w»
00000011: 740E je 000000021 ——— (2)
00000013: E82A00 call 000000040 ——— (3)
00000016: EBE8 jmps 000000000 ——— (4)
00000018: C70648013001 mov w,,00130 ;» 0″
0000001E: EB07 jmps 000000027 ——— (5)
00000020: 90 nop
00000021: C70648013801 mov w,,00138 ;» 8″
00000027: FF164801 call w,
0000002B: B8004C mov ax,04C00 ;»L »
0000002E: CD21 int 021
00000030: B409 mov ah,009 ;» »
00000032: BA6901 mov dx,00169 ;» i»
00000035: CD21 int 021
00000037: C3 retn
00000038: B409 mov ah,009 ;» »
0000003A: BA8001 mov dx,00180 ;» Ђ»
0000003D: CD21 int 021
0000003F: C3 retn
00000040: B409 mov ah,009 ;» »
00000042: BA9C01 mov dx,0019C ;» њ»
00000045: CD21 int 021
00000047: C3 retn
1 |
00000000B409movah,009;» « 00000002BA4A01movdx,0014A;» J» 00000005CD21int021 00000007B401movah,001;» « 00000009CD21int021 0000000B3C6Dcmpal,06D;»m» 0000000D7409je000000018———(1) 0000000F3C77cmpal,077;»w» 00000011740Eje000000021———(2) 00000013E82A00call000000040———(3) 00000016EBE8jmps000000000———(4) 00000018C70648013001movw,00148,00130;» 0″ 0000001EEB07jmps000000027———(5) 0000002090nop 00000021C70648013801movw,00148,00138;» 8″ 00000027FF164801callw,00148 0000002BB8004Cmovax,04C00;»L « 0000002ECD21int021 00000030B409movah,009;» « 00000032BA6901movdx,00169;» i» 00000035CD21int021 00000037C3retn 00000038B409movah,009;» « 0000003ABA8001movdx,00180;» Ђ» 0000003DCD21int021 0000003FC3retn 00000040B409movah,009;» « 00000042BA9C01movdx,0019C;» њ» 00000045CD21int021 00000047C3retn |
Обратите внимание, как HIEW отображает информацию:
0000000F: 3C77 cmp al,077 ;«w» — в качеестве комментария отобразил ASCII символ, сответствующий 077h
…00000018: C70648013001 mov w,,00130 ;» 0″ — обозначил, что по смещению находятся данные размером в слово (w) и заполнил
;его нулём (addrs dw 0);
…00000016: EBE8 jmps 000000000 ——— (4) ; Jump Short — обозначен именно ближний переход.
…00000020: 90 nop ; а это что за команда? У нас её не было!
При исследовании кода обнаружилась «лишняя» команда NOP.
NOP (No OPeration — нет операции).
Не производит никаких действий. Занимает один байт (машинный код — 90h) и используется для:
- Выравнивания следующей команды или переменной сегмента в целях увеличения быстродействия определённых процессоров (иногда актуально).
- Резервирования «пустого» места в коде.
- Для задержки в целях синхронизации работы компьютера (сейчас не актуально).
- При отладке.
В данном случае команду NOP вставил TASM, посчитав, что такой код будет работать быстрее. Мы уже говорили, что определённым процессорам легче работать с определенными блоками данных. Именно для решения вопроса «блочности» в данном случае TASM сгенерировал такой код.
Команда NOP очень часто используется хакерами, чуть позже мы покажем, в каких целях.
7.41 .org NEW-LC, FILL
Продвигает текущий счетчик места до NEW-LC. NEW-LC является или
абсолютным выражением, или выражением с той же секцией, что и текущая
подсекция. Таким образом, Вы не можете использовать директиву .org для
переключения секций; если NEW-LC попадает в другую секцию, то
директива .org игнорируется. Для совместимости с предыдущими
ассемблерами, если секция NEW-LC абсолютная, то as выдает
предупреждение и рассматривает секцию NEW-LC как ту же секцию, что
и данная подсекция.
.org может только увеличить счетчик места, или оставить его таким
же; Вы не можете использовать .org для уменьшения счетчика места.
Поскольку as пытается ассемблировать программу за один проход,
NEW-LC не может быть неопределенным.
Имейте ввиду, что аргумент .org относится к началу секции, а не к
началу подсекции. Это совместимо с другими ассемблерами.
Когда увеличивается счетчик места (текущей подсекции),
вставляемые байты заполняются FILL, который должен быть абсолютным
выражением. Если запятая и FILL опущены, то FILL полагается нулем.
Преимущество C (Си) конвенции по сравнению с PASCAL.
1. Освобождение стека от параметров возлагается на вызывающую процедуру. Это позволяет отимизировать код.
Например, если вызываются несколькл функций подряд, принимающих одни и теже параметры, можно не заполнять стек заново:
….
push b;Второй параметр
push a; Первый параметр — снизу
call myFunc1
call myFunc2
add sp,4; Стек освобождает вызывающая функция
….
1 |
…. pushb;Второй параметр pusha; Первый параметр — снизу callmyFunc1 callmyFunc2 addsp,4; Стек освобождает вызывающая функция …. |
2. Более просто создавать функции с изменяемым числом параметров (printf).
Строковые инструкции
Каждая строковая инструкция может требовать исходного операнда и операнда назначения. Для 32-битных сегментов строковые инструкции используют регистры ESI и EDI, чтобы указать на операнды источника и назначения, соответственно.
Однако для 16-битных сегментов, чтобы указать на источник и место назначения, используются другие регистры: SI и DI.
Существует 5 основных инструкций для работы со строками в Ассемблере:
— эта инструкция перемещает 1 byte, word или doubleword данных из одной ячейки памяти в другую;
— эта инструкция загружается из памяти. Если операндом является значение типа byte, то оно загружается в регистр AL, если типа word — загружается в регистр AX, если типа doubleword — загружается в регистр EAX;
— эта инструкция сохраняет данные из регистра (AL, AX или EAX) в память;
— эта инструкция сравнивает два элемента данных в памяти. Данные могут быть размера byte, word или doubleword;
— эта инструкция сравнивает содержимое регистра (AL, AX или EAX) с содержимым элемента, находящегося в памяти.
Каждая из вышеприведенных инструкций имеет версии byte, word или doubleword, а строковые инструкции могут повторяться с использованием префикса повторения.
Эти инструкции используют пары регистров и , где регистры DI и SI содержат валидные адреса смещения, которые относятся к байтам, хранящимся в памяти. SI обычно ассоциируется с DS (сегмент данных), а DI всегда ассоциируется с ES (дополнительный сегмент).
Регистры (или ESI) и (или EDI) указывают на операнды источника и назначения, соответственно. Предполагается, что операндом-источником является (или ESI), а операндом назначения — место в памяти, на которое указывает пара (или EDI).
Для 16-битных адресов используются регистры SI и DI, а для 32-битных адресов используются регистры ESI и EDI.
В следующей таблице представлены различные версии строковых инструкций и предполагаемое место операндов:
Основная инструкция | Операнды в: | Операция byte | Операция word | Операция doubleword |
MOVS | ES:DI, DS:SI | MOVSB | MOVSW | MOVSD |
LODS | AX, DS:SI | LODSB | LODSW | LODSD |
STOS | ES:DI, AX | STOSB | STOSW | STOSD |
CMPS | DS:SI, ES:DI | CMPSB | CMPSW | CMPSD |
SCAS | ES:DI, AX | SCASB | SCASW | SCASD |