Использование ассемблера с языками высокого уровня

advertisement
Системное программное обеспечение
Лекция № 11 «Использование ассемблера с языками высокого уровня»
Использование ассемблера с языками высокого уровня
Необходимость использования ассемблера в языках высокого
уровня возникает, когда требуется:
• реализовать какой-то специальный алгоритм, который
требует нетривиальной обработки данных и который
трудно создать средствами языка высокого уровня
• обеспечить
высокое
быстродействие
какого-либо
алгоритма обработки данных или фрагмента программы
• обеспечить минимизацию используемой памяти
• обеспечить доступ к аппаратуре
Cпециалист
по
программированию
на
любом
языке
программирования должен обязательно владеть ассемблером как
вторым инструментом.
Использование ассемблера с языками высокого уровня
Cтыковка ассемблера с языками высокого уровня состоит в:
1) Cогласовании моделей памяти
2) Cогласовании передачи параметров процедурам
3) Cогласовании имен идентификаторов
Согласование моделей памяти. При стыковке необходимо
учитывать используемые модели памяти. От этого будет зависеть
тип применяемых сегментов (16- или 32-разрядные) и ссылок на имена
переменных и процедур: ближние (если ссылка выполняется в пределах
одного сегмента) или дальние (если ссылка выполняется на объект,
расположенный в другом сегменте). При переходе к Windows эту
проблему можно исключить, так здесь используется плоская модель
памяти. Соответственно, все вызовы по типу являются близкими, т.е.
осуществляющимися в рамках одного огромного сегмента.
Использование ассемблера с языками высокого уровня
Cогласование
Большинство
языков
передачи
высокого
параметров
уровня
передают
процедурам.
параметры
вызываемой процедуре в стеке и ожидают возвращения параметров в
регистре АХ (ЕАХ) (иногда используется DX:AX (EDX:EAX), если
результат не умещается в одном регистре, и ST(0), если результат
число с плавающей запятой).
Основными конвенциями передачи параметров процедурам
являются следующие:
1.
Конвенция Pascal
Самый очевидный способ вызова процедуры или функции языка
высокого уровня, после того как решено, что параметры передаются в
стеке и возвращаются в регистре АХ/ЕАХ, просто поместить
параметры в стек в естественном порядке.
Использование ассемблера с языками высокого уровня
Это способ, принятый в языке PASCAL (а также в BASIC,
FORTRAN, ADA, OBERON, MODULA2). В этом случае запись
some_proc(a,b,c,d,e)
превращается в
push
a
push
b
push
с
push
d
push
e
call
some_proc
Это значит, что процедура some_proc, во-первых, должна в
конце очистить стек (например, командой ret 10) и, во-вторых,
параметры, переданные ей, находятся в стеке в обратном порядке:
Использование ассемблера с языками высокого уровня
some_proc
proc
push
bp
mov
bp,sp
; создать стековый кадр
a
equ
[bp+12] ; определения параметров
b
equ
[bp+10]
c
equ
[bp+8]
d
equ
[bp+6]
e
equ
[bp+4]
; текст процедуры, использующей параметры а, Ь, с, d, e
ret
10
some_proc
endp
Этот код в точности соответствует усложненной форме
директивы
proc,
которую
поддерживают
все
современные
ассемблеры:
some_proc
proc
PASCAL,а:word,b:word,с:word,d:word,e:word
; текст процедуры, использующей параметры а, Ь, с, d, e.
; Так как ВР используется в качестве указателя стекового кадра,
; его использовать нельзя!
ret
; эта команда RET будет заменена на RET 10
some_proc
endp
Использование ассемблера с языками высокого уровня
Главный недостаток этого подхода — сложность создания
функции с изменяемым числом параметров, аналогичных функции языка
С printf. Чтобы определить число параметров, переданных printf,
процедура должна сначала прочитать первый параметр, но она не
знает его расположения в стеке.
Эту проблему решает подход, используемый в С, где
параметры передаются в обратном порядке.
2.
Конвенция C
Этот способ передачи параметров используется в первую
очередь в языках С и C++, а также в PROLOG и других. Параметры
помещаются в стек в обратном порядке, и, в противоположность
PASCAL-конвенции,
удаление
вызывающая процедура.
параметров
из
стека
выполняет
Использование ассемблера с языками высокого уровня
Запись
some_proc(a,b,c,d,e)
превращается в
push
e
push
d
push
с
push
b
push
a
call
some_proc
add
sp,10
Вызванная
таким
инициализироваться так:
; освободить стек
образом
процедура
может
Использование ассемблера с языками высокого уровня
some_proc proc
push bp
mov bp,sp ; создать стековый кадр
a equ [bp+4] ; определения параметров
b equ [bp+6]
с equ [bp+8]
d equ [bp+10]
e equ [bp+12]
; текст процедуры, использующей параметры a, b, с, d, e
pop bp
ret
some_proc endp
Ассемблеры поддерживают и такой формат вызова при
помощи усложненной формы директивы proc с указанием языка С:
Использование ассемблера с языками высокого уровня
some_proc
proc
С,а:word,b:word,с:word,d:word,e:word
; текст процедуры, использующей параметры a, b, с, d, e.
; Так как BP применяется как указатель стекового кадра,
; его использовать нельзя!
ret
some_proc
endp
Преимущество
по
сравнению
с
PASCAL-конвенцией
заключается в том, что освобождение стека от параметров в С
возлагается на вызывающую процедуру, что позволяет лучше
оптимизировать код программы. Например, если нужно вызвать
несколько функций, принимающих одни и те же параметры подряд,
можно не заполнять стек каждый раз заново:
push
push
call
call
add
param2
param1
proc1
proc2
sp,4
Использование ассемблера с языками высокого уровня
эквивалентно
proc1(param1,param2);
proc2(param1,param2);
и это — одна из причин, почему компиляторы с языка С создают более
компактный и быстрый код по сравнению с другими языками.
В таблице ниже представлены основные соглашения по
передаче параметров в процедуру.
Соглашение
register (fastcall)
(быстрый
или регистровый вызов)
pascal
(конвенция языка Паскаль)
cdecl
(конвенция С)
stdcall
(стандартный вызов)
Safecall
Параметры
Очистка стека
Регистры
Слева направо
Процедура
EAX, EDX, ECX (Delphi)
ECX, EDX (Visual C++ .NET)
Слева направо
Процедура
Нет
Справа налево
Вызывающая
программа
Нет
Справа налево
Процедура
Нет
Справа налево
Процедура
Нет
Использование ассемблера с языками высокого уровня
Таблица объясняет соглашения о передаче параметров.
Например, передача параметров stdcall отличается и от С, и от
PASCAL-конвенций. Она применяется для всех системных функций
Win32 API. Здесь параметры помещаются в стек в обратном порядке,
как в С, но процедуры должны очищать стек сами, как в PASCAL.
Еще одно интересное отклонение от С-конвенции можно
наблюдать в Watcom С. Этот компилятор использует регистры для
ускорения
работы,
и
параметры
в
функции
передаются
по
возможности через регистры. Например, при вызове функции
some_proc(a,b,с,d,e,f);
первые четыре параметра передаются соответственно в (Е)АХ,
(E)DX, (Е)ВХ, (Е)СХ, а только начиная с пятого, параметры
помещают в стек в обычном обратном порядке:
Использование ассемблера с языками высокого уровня
e
f
equ
equ
[bp+4]
[bp+6]
Еще один важный момент - тип возвращаемых функцией
данных. С точки зрения ассемблера здесь все просто: в регистре EAX
возвращается значение, которое может быть либо числом, либо
указателем на переменную или структуру. Если возвращаемое число
типа WORD, то оно содержится в младшем слове регистра EAX.
Cогласование
имен
идентификаторов.
Компиляторы
Microsoft С (а также многие компиляторы в UNIX) изменяют названия
процедур, чтобы отразить способ передачи параметров. Так, к
названиям всех процедур, использующих С-конвенцию, приписывается
символ подчеркивания. То есть, если в С-программе записано
some_proc();
Использование ассемблера с языками высокого уровня
то реально компилятор пишет
call _some_proc
и это означает, что, если эта процедура написана на ассемблере, она
должна называться именно _some_proc (или использовать сложную
форму записи директивы proc).
Названия процедур, использующих stdcall, например, при
создании DLL, искажаются еще более сложным образом: спереди к
называнию процедуры добавляется символ подчеркивания, а сзади —
символ @ и размер занимаемой параметрами области стека в байтах,
(то есть в точности число, стоящее после команды ret в конце
процедуры).
some_proc(a:word);
превращается в
Использование ассемблера с языками высокого уровня
push
call
a
_some_proc@4
Если требуется выполнить совсем небольшую операцию на
ассемблере,
например
вызвать
какое-то
прерывание
или
преобразовать сложную битовую структуру, часто нерационально
создавать отдельный файл ради нескольких строк на ассемблере.
Чтобы
этого
поддерживают
избежать,
возможность
многие
языки
вставки
высокого
ассемблерного
уровня
кода
непосредственно в программу.
Например, нужно написать процедуру, возвращающую слово,
находящееся по адресу 0040h:006Ch, в BIOS — счетчик сигналов
системного
таймера,
который
удобно
инициализации генераторов случайных чисел.
использовать
для
Использование ассемблера с языками высокого уровня
Пример. Pascal
function get_seed:longint
var
seed:longint
begin
asm
push
es
mov
ax,0040h
mov
es,ax
mov
ax,es:[006Ch]
mov
seed,ax
pop
es
end;
get_seed:=seed;
end;
Использование ассемблера с языками высокого уровня
Пример. C
int get_seed()
int seed;
{
_asm {
push
es
mov
ax,0040h
mov
es,ax
mov
ax,es:[006Ch]
mov
seed,ax
pop
es
};
return(seed);
};
В этих ситуациях ассемблерная программа может свободно
пользоваться переменными из языка высокого уровня, так как они
автоматически преобразуются в соответствующие выражения типа
word ptr [bp+4].
Download