Системное программное обеспечение Лекция № 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].