Near ptr - прямой ближний вызов;
Команда call прямого ближнего вызова
заносит в стек относительный адрес точки
возврата в текущем программном сегменте и
модифицирует IP так, чтобы в нем
содержатся относительный адрес точки перехода в том же программном сегменте.
Косвенные ближние вызовы
Адрес подпрограммы содержится либо в ячейке памяти, либо в регистре. Это позволя-ет, как и в случае ближнего перехода, моди-фицировать адрес вызова, а также осущест-влять вызов не с помощью метки, а по известному абсолютному адресу.
Прямой дальний вызов far ptr
Этот вызов позволяет обратиться к подпрограмме из другого сегмента.
Косвенный дальний вызов.
Отличается от косвенного ближнего вызова лишь тем, что подпрограмма находится в другом сегменте, а в ячейке памяти содержится полный адрес подпрограммы, включающий сегмент и смещение.
Если процедура имя имеет атрибут NEAR, то команда CALL помещает смеще-ние адреса следующей команды в стек. Если процедура имя имеет атрибут FAR, то команда CALL помещает в стек содержи-мое регистра CS, а затем смещение адреса.
После сохранения адреса возврата команда CALL загружает смещение адреса метки имя в указатель команд IP. Если процедура имеет атрибут FAR, то команда CALL загружает также номер блока метки имя в регистр CS.
Пусть регистр ВХ содержит смещение адреса подпрограммы относительно регистра сегмента CS.
При исполнении этой команды микропроцессор копирует содержимое регистра ВХ в указатель команд IP, затем передает управление команде, адресуемой парой регистров CS : IP.
Процедуру с атрибутом NEAR можно вызвать косвенно, например:
CALL WORD PTR [ ВХ ]
; косвенная регистровая
CALL WORD PTR [ BX ] [ SI ]
; по базе с индексированием
CALL WORD PTR VARIABLE_NAМE
CALL WORD PTR VARIABLE_NAME [ BX ] ; прямая с индексированием
CALL MEM_WORD
CALL WORD PTR ES : [ BX ] [ SI ]
; по базе с индексированием
Процедуру с атрибутом FAR можно вызвать косвенно, используя переменную размером в двойное слово, например:
CALL DWORD PTR [ BX ]
CALL MEM_DWORD
CALL DWORD PTR SS : VARIABLE_NAME [ SI ]
Первые две команды CALL извлекают адреса подпрограмм из сегмента данных, а последняя – из сегмента стека. Подпрограмма может сама вызывать другие подпрограммы. Таким образом возможно использование вложенных процедур.
Т. к. каждая команда CALL помещает в стек 2 или 4 байта адреса, то число уровней вложения ограничено только размером сегмента стека.
23.3. Команда возврата RET
(RETurn from procedure)
RET Возврат из процедуры
RETN Возврат из ближней процедуры
RETF Возврат из дальней процедуры
Команда ret извлекает из стека адрес возврата и передает управление назад в программу, первоначально вызвавшую подпрограмму.
Если командой ret завершается ближняя процедура, (near), или исполь-зуется retn, со стека снимается одно слово- относительный адрес точки возврата. Передача управления в этом случае осуществляется в пределах одного программного сегмента.
Если командой ret завершается дальняя процедура, объявленная с атрибутом far, или используется модификация команды retf, со стека снимаются два слова: смещение и сегментный адрес точки возврата.
Выполнение команды не влияет на флаги.
Применение: Команду ret необходимо применять для возврата управления вызывающей программе из подпрограммы, управление которой было передано по команде call. Микропроцессор имеет три варианта команды возврата ret - это ret, ее синоним retn, а также команда retf.
Они отличаются типами процедур, в которых используются. Команды ret и retn служат для возврата из процедур ближнего типа. Команда retf — команда возврата для процедур дальнего типа. Какая конкретно команда будет использоваться, определяется компилятором; программисту лучше использовать команду ret и доверить транслятору самому сгенерировать ее ближний или дальний вариант. Количество команд ret в процедуре должно соответствовать количеству точек выхода из подпрограммы
23.4. Способы передачи параметров.
void _cdecl: Func (int A,int B, int C, int D) {}
_cdecl - соглашение о вызовах (определение порядка размещения в стеке и извлечения из стека параметров, передаваемых при вызове функций
-прямой порядок расположения данных в стеке
- в стек заносится сначала последний параметр, затем предпоследний и т.д.
- допускает переменное число параметров, поскольку стек очищает пользователь
void _stdcall Func (int A,int B, int C, int D){}
-прямой порядок данных в стеке
- в стек заносится сначала последний параметр
- стек очищает функция поэтому число параметров фиксировано
void _fastcall Func (int A,int B, int C, int D){}
- аналогично stdcall, но два первых параметра передаются через регистры eax edx
Существует _pascal:
- обратный порядок расположения данных в стеке
- в стек заносится сначала первый параметр
- поэтому число параметров фиксировано
23.5. Правила передачи параметров
Использование регистров в 16-ти битном режиме или Windows, C или C++: -16-ти битное значение возвращается в AX,
-32-х битное значение в DX:AX,
-значение с плавающей запятой в ST(0)
Регистры AX, BX, CX, DX, ES и арифметические флаги могут быть изменены подпрограммой; другие регистры должны быть сохранены и восстановлены.
Подпрограмма может полагаться на то, что при вызове другой процедуры значение ESI, EDI, EBP, DS и SS не изменится
Использование регистров в 32-х битных Windows, C++ и других языках программирования:
-целочисленное значение возвращается в EAX,
-значение с плавающей точкой возвращается вST(0).
Регистры EAX, ECX, EDX (но не EBX) могут быть изменены процедурой;
Bсе другие регистры должны быть сохранены и восстановлены.
Сегментные регистры нельзя изменять даже временно.
CS, DS, ES и SS указывают на плоский сегмент. FS используется операционной системой.
GS не используется, но зарезервирован.
Флаги могут меняться процедурой, но со следующими ограничениями: флаг направления равен 0 по умолчанию. Флаг прерывания не может быть очищен. Стековый регистр плавающей запятой пуст при входе в процедуру и должен быть пустым при выходе, если только ST(0) не используется для возвращения значения. Процедура может полагаться на неизменность EBX, ESI, EDI, EBP и всех сегментных регистров при вызове другой процедуры.
Внешние подпрограммы
#include <stdio.h>
#include <stdlib.h>
extern "C" int _cdecl SubInt(int, int);
extern "C" int _cdecl SumAInt(int[], int,int*);
void Rnd_array (int ar[],int *n)
{// ввод размерности и элементов массива
int i,j;
printf("Enter dimension n \n");
scanf("%d",n);
for (i=0;i<*n;i++)
ar[i]=rand()%100; ; }
void Print_array ( int ar[],int n)
// вывод элементов массива
{ printf("Items array\n");
for ( int i=0;i<n;i++)
printf("%5d",ar[i]);
printf("\n");}
void main()
{int x,y;
printf ("Enter 2 number");
scanf("%d%d",&x,&y);
int r=SubInt(x, y);
// в стеке сначала y потом x
printf ("difference=%d\n",r);
int b[20],n,k;
Rnd_array(b, &n);
Print_array(b, n);
SumAInt(b,n,&k);
// в стеке заносится сначала &k,n,&b
printf ("summa=%d\n",k); }
Подпрограмма во внешнем файле .asm
.486
.model flat ;плоская модель памяти
.data ;описание данных
.code
Public _SubInt
_SubInt proc
; В стеке 4 байта адрес точки возврата
push ebp ;4 байта
mov ebp,esp
mov eax,[ebp+8] ; x
sub eax,[ebp+12]; y
pop ebp
ret
SubInt endp
Public _SumAInt
_SumAInt proc
push ebp
mov ebp,esp
mov esi,[ebp+8] ;&array
mov ecx,[ebp+12];n
xor eax,eax
for1: add eax,[esi]
add esi,4
loop for1
mov edi,[ebp+16];&k
mov [edi],eax
pop ebp
ret
_SumAInt endp
end
Компилятор MASM
В начале 1990-х, альтернативные ассемблеры, типа Borland TASM и условно-бесплатного ассемблера x86 NASM начали брать часть доли на рынке MASM.
Позже в 2000 году, MASM 6.15 был выпущен как часть Visual C++ пакета разработки, который является бесплатным.
В результате все версии Visual C++ позже чем 6.0, включали версию MASM, равную версии Visual C++.
Позже в Visual C++ 2005 появилась 64-битовая версия MASM (название линкера — ml64.exe).
Вместе с большим сообществом программистов MASM, эти события помогли остановить снижение популярности MASM до других ассемблеров.
Сегодня MASM — все ещё ассемблер номер один на платформе Win32, несмотря на конкуренцию с новыми продуктами, как NASM, FASM и HLA.
Компилятор MASM создан специально для написания программ на ассемблере для Win32. В нём есть макросы и специальные директивы для упрощения программирования.
Функции.
Основное преимущество MASM это макрос invoke, он позволяет вызывать API функции по-обычному с проверкой количества и типа параметров. Это почти тот же call, как в TASM, но этот макрос проверяет количество параметров и их типы. Вот так вызывается функция:
Invoke <функция>, <параметр1>, <параметр2>, <параметр3>
Это пример программы Hello World, которая выводит это знаменитое сообщение и завершается.
.386 .model flat, stdcall
option casemap :none
include \masm32\include\masm32.inc
include \masm32\include\kernel32.inc
include \masm32\macros\macros.asm
includelib \masm32\lib\masm32.lib
includelib \masm32\lib\kernel32.lib
.code
start: print "Hello world"
exit
end start