В прошлый раз мы остановились на регистре флажков. Это регистр содержит такие флаги (* - не используемые биты):
| Номер бита | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| Флаг | С | * | P | * | A | * | Z | S | T | I | D | O | * | * | * | * |
cmp bx, 00Как же узнать какой флаг установился, а какой нет? Для этого есть команды для перехода на метку, если флаг установлен. Всех их писать я не буду. Но например, если в предыдущем примере BX = 0, тогда сравнение вызовет установку флага ZF (Zero Flag - флаг нуля). Команда jz вызывает переход на метку, если установлен ZF. Для нашего примера:
cmp bx, 00Во встроенном ассемблере могут использоваться три предопределённых имени:
jz Label1
....это будет выполняться, если bx не равно 0
.....
Label1:
... здесь если bx равно 0
- @code - текущий сегмент кода
- @Data - текущий сегмент данных
- @Result - ссылка, куда надо занести результат выполняемой функции
function Min (x,y : integer): integer;
begin
asm
mov ax, x
cmp ax, y
jl @less
mov ax, y
@less:
mov @Result, ax
end
end;
Обратим взгляд на неизвестную нам команду jl - переход если меньше. Т.е. в нашем случае если AX меньше Y, то совершается переход на метку @less. Кстати откуда такое странное название (начинается с @)? Это так называемые локальные метки - метки в которые можно передать управление только внутри ассемблерной вставки. Т.е. вы не можете прыгнуть на неё с помощью goto. С другой стороны нам не надо объявлять такую метку в разделе label, так что начинайте свои метки с символа @. Этот символ говорит компилятору, о том что метка локальная. Так вот значит, если AX меньше Y (т.е. если X меньше Y), тогда мы переходим на метку @less. И записываем в @Result значение АХ (а там у нас Х). Если же Y меньше Х, тогда переход на метку не состоится и мы сначала занесём в АХ Y, а потом значение АХ в @Result. Вот таким образом компилятор преобразовывает наши высоко уровневые конструкции if-else к машинным cmp. Естесственно в силу превосходства человека над машиной, наш код меньше, чем генерируемый компилятором. Однако давайте обратим свой взор на использование функций процедур, написанных на ассемблере. Во перых объявление таких подпрограмм должно идти с директивой assembler. Во вторых исполняемая чать вместо привычного begin - end обрамляется asm - end. Т.е. шаблон выглядит так:
procedure NAME; assembler;
asm
.... текст на ассемблере
endПри этом ассемблерные функции должны возвращать свои значения следующим образом:
- если возвращаемый тип занимает 1 байт (тип Byte, Char) - в регистре AL
- 2 байта (Integer, word) - в AX
- 4 байта (pointer, longint) - в регистре DX (старшее слово) и AX (младшее слово)
- типа Real - в регистрах DX, BX, AX (DX - старшее слово и AX - младшее слово)
- других вещественных чисел - в регистре ST (0) сопроцессора (об этом ниже)
- строкового типа - в области памяти на которую ссылается @Result
function Min (x,y : integer): integer;assembler;
asm
mov ax, x
cmp ax, y
jl @less
mov ax, y
@less:
end;
так как мы возвращаем значение типа integer, то нам надо запихать это значение в АХ (что мы и делаем). Обратите внимание, что наш код сократился ещё на одну строку. Нам не надо записывать возвращаемоео значение в @Result. Метка @less теперь у нас соответствует выходу из подпрограммы. Следующие строки будут целиком понятны только для людей знакомых с ассемблером более чем поверхностно. Однако и вам полезно это знать. Итак на входе и выходе в ассемблерную подпрограмму генерируется следующий код (он добавляется в ехе файл):
colspanВход в процедуру:Если входные и выходные параметры отсутствуют, то создаётся код содержащий единственную инструкцию ret. Как я уже когда-то писал все параметры передаются в подпрограммы черз стек. Так же все локальные переменные хранятся в стеке. Поэтому при входе в процедуру создается локальный стек: в bp помещается текущая граница стека, а сама эта граница сдвигается на суммарную длинну всех локальных переменных что бы работа со стеком не разрушила локальные переменные. Например:
push bp - сохраняем значение bpcolspanВыход из процедуры:
mov bp, sp - bp теперь содержит текущую границу стека
sub sp, local - резервируем часть стека для локальных данных, local - длинна в байтах всех объявленных в подпрограмме переменных
mov sp, bp - восстанавливаем границу стека
pop bp - восстанавливаем bp
ret param - удаляем из стека все параметры и выходим из подпрограммы, param - длинна всех параметров
procedure some; assembler;
var
x : integer;
y : byte;
asm
mov X, ax - скомпилируется в mov [bp - 2], ax
mov al, Y - скомпилируется в mov al, [bp - 3]
end;
Как я написал выше, результат функции других вещественных чисел возвращается в регистре ST (0) сопроцессора. Что же такое сопроцессор? Как следует из названия это "дополнительный" процессор. Сопроцессор - это процессор для работы с числами с плавающей запятой. Вообще-то полное название - арифметический сопроцессор. Этот сопроцессор имеет свою систему команд разработанную специально для ускорения математических расчётов. Раньше не все компьютеры были оснащены сопроцессором. Сейчас он неизменно присутствует на всех машинах. Поэтому компилятор Турбо Паскаля содержит 2 режима компиляции с поддержкой сопроцессора : Options->Compiler раздел Numeric processing. Галочка у 8087/80287 - при компиляции будут использованы команды сопроцессора. Галочка у Emulation - будет использован режим эмуляции. Т.е. при компиляции будет эмулироваться сопроцессор. Помимо арифметического сопроцессора существует куча сопроцессоров. Самый яркий пример - графический, для ускорения работы с графикой (он содержит в себе функции для рисования графических примитивов).
Кул-][ацкерам
Хотите почувствовать себя героем фильма Матрица и иже с ними? Нет ничего проще! Правда во времена создания BP никто о существовании Матрицы не подозревал, но такую возможность предоставили. Итак возьмём простенькую програмульку из прошлого выпуска:
begin
asm
jmp @next
@msg: db 'Hello world!$'
@next:
push ds
push cs
pop ds
mov ah, 9
mov dx, offset @msg
int 21h
pop ds
end
end.
сохраняем, делаем ехе файл и выходим из Паскаля. теперь запускаем bp\bin\td.exe - это Turbo Debugger. Отладчик фирмы Borland. Выбираете File->Change Dir В появившемся окне вводите путь к программе (без имени!). Помните, что путь должен быть короче 8 символов (имеется ввиду длинна имени каждой папки). Если сразу затрудняетесь, то введите только имя диска, на котором находится ехе файл. Дальше File->Open и выбираем нужный нам ехе файл (видите именно ехе, а не pas). На все появшиеся сообщения (если такие будут) ответьте Ок. Окно у вас должно представлять собой нечто такое:
+-[_]-CPU 80486-------------------------------------------------------1----[]-+ | cs:0000>9A0000925D call 5D92:0000 ax 0000 |c=0| | cs:0005 55 push bp | bx 0000 |z=0| | cs:0006 89E5 mov bp,sp | cx 0000 |s=0| | cs:0008 31C0 xor ax,ax | dx 0000 |o=0| | cs:000A 9ACD02925D call 5D92:02CD | si 0000 |p=0| | cs:000F EB0D jmp 001E | di 0000 |a=0| | cs:0011 48 dec ax | bp 0000 |i=1| | cs:0012 656C insb gs: | sp 4000 |d=0| | cs:0014 6C insb | ds 5D7E | | | cs:0015 6F outsw | es 5D7E | | | cs:0016 20776F and [bx+6F],dh | ss 5E16 | | | cs:0019 726C jb 0087 | cs 5D8E | | | cs:001B 642124 and fs:[si],sp | ip 0000 | | | cs:001E 1E push ds | | | | cs:001F 0E push cs | | |___________________________________________________________+----------------| | ds:0000 CD 20 00 A0 00 9A F0 FE = а ЪЁ_ | ss:4002 0000 | | ds:0008 1D F0 E4 01 56 24 AE 01 ЁфV$о | ss:4000>0000 | | ds:0010 56 24 80 02 B1 1E 1F 10 V$А_ | ss:3FFE 0000 | | ds:0018 01 01 01 00 02 FF FF FF ___ | ss:3FFC 0000 | | ds:0020 FF FF FF FF FF FF FF FF ________ | ss:3FFA 0000 | +------------------------------------------------------------------------------+ |
"Картинка", которую вы увидели, называется "дамп памяти" (что в переводе с английского означает "свалка") и она насыщена не только важной информацией, но и специальной низкоуровневой энергетикой. Да чего уж там греха таить - каждый ассемблерщик знает, что рассматривание дампа памяти поднимает настроение, жизненный тонус и другие, не менее важные вещи ;)Так что поднимайте ваш жизненный тонус и отправимся изучать подробнее этот дамп. Итак слева как можно догадаться адресс (вернее только смещение, т.к. сегмент задаётся DS). В центре идут hex числа, справа их расшифровка в коды символов. Так например первый байт по адресу DS:0000 имеет значение5 CDh, а символ с номером CD это = (равно). Следующий за ним байт со значением 20h - это пробел и т.д. Последнюю часть окна мы пока трогать не будем. Теперь давайте посмотрим как вставился наш ассемблерный код ... ищем в коде программы jmp @next.... как вы не нашли ? Правильно. Ведь при компиляции имена меток заменяются адресами. Так что мы пойдём другим путём. Итак давим до боли знакомую клавишу F8 (нам надо именно Step Over, так что именно F8 иначе можете оказаться где-то в месте далёком от нашего кода :) Обратите внимание: зажглись регистры - только, те которые измениись. Это запустилась наша программа. Итак мы дошли до места
похоже на то, что мы писали ? но ведь текст начинался с jmp @next, где же он? Давайте рассмотрим текущую строчку: вначале идёт смещение текущей команды относительно сегмента кода (посмотрите кстати на регистр ip). Дальше идёт цифра означающая код команды в hex виде. Т.е. для команды push ds это 1E. Ну и дальше идёт соответственно команда ассемблера. Итак нам надо искать jmp, который ссылается на строчку с адресом 001E - ведь именно её мы пометили меткой @next. Такой jmp находится по адресу cs:000F - jmp 001E - это и есть наш jmp @next Теперь смотрите на строку mov dx, offset @msg- эта строка скомпилировалась в mov dx, 0011 - 'эта адрес строки в сегменте кода, на который у нас ссылается ds. Выберете View -> Dump и посмотрите на дамп - там и правда будет строка Hello World! Теперь обратим взгляд на код нашей програмки именно с адреса 0011 - там находятся команды, которые мы перепрыгнули - а ведь, это то, во что скомпилировался наш Hello World!. А именно в это:cs:001E 1E push ds
dec ax
insb gs:
insb
outsw
and [bx+6F],dh
jb 0087
and fs:[si],sp 
