IPB
ЛогинПароль:

> ПРАВИЛА РАЗДЕЛА!!!

1. Заголовок или название темы должно быть информативным
2. Все тексты программ должны помещаться в теги [CODE=asm] [/CODE]
3. Прежде чем задавать вопрос, см. "FAQ",если там не нашли ответа, воспользуйтесь ПОИСКОМ, возможно, такую задачу уже решали!
4. Не предлагайте свои решения на других языках, кроме Ассемблера. Исключение только с согласия модератора.
5. НЕ используйте форум для личного общения! Все, что не относиться к обсуждению темы - на PM!
6. Проверяйте программы перед тем, как выложить их на форум!!

> Азы (встроенный ассемблер), Может кому интересно...
сообщение
Сообщение #1


Гость






От нечего делать опишу основные моменты при программировании на встроенном ассемблере в Паскале.

Есть 2 способа задействовать эту замечательную возможность:
  1. Заключить код на ассемблере между словами asm и end
  2. Написать процедуру с указанием директивы Assembler, что в принципе не сильно отличается от первого варианта, т.к. код по прежнему находится между asm и end smile.gif
Зачем это нужно?
Ну, на самом деле применений целое море, о чём можно убедиться полистав этот раздел форума.

Рассмотрим вопрос адресации в реальном режиме (именно он используется "by def" при компиляции в BP).

Адрес состоит из двух частей: сегментной части и смещения. Обе части являются 16-ти разрядными двоичными числами или, что на практике и применяется, 4-х разрядными шестнадцатиричными.

Рассмотрим пример и на нём разберёмся, что и какая часть значит:

Пусть сегментная часть (далее Seg) = $ABCD, а смещение (далее Ofs) = $1234. Это означает, что эта пара Seg:Ofs хранит следующий адрес Seg * $10 + Ofs = $ABCD * $10 + $1234 = $ABCD0 + $1234 = $ACF04. Как легко заметить, пользуясь таким способом адресации мы можем указать адрес любой ячейки памяти в пределах первого мегабайта ($00000..$FFFFF) и даже чуть-чуть больше, но это не имеет значения, т.к. процессор в реальном режиме даёт доступ только к первому МБ.

Возникает естественный вопрос: а зачем нужен этот геморрой, и почему нельзя просто указывать полный адрес? А вот нельзя. А потому, что процессор при работе с памятью опирается на информацию, которая хранится в его регистрах, а т.к. мы используем 16-битный вариант команд, то, соответственно, в 1 регистр более 16 бит (4 16-ричные цифры) не впихнуть. Поэтому и приходится использовать 2 регистра: сегментный и какой-нибудь, который можно использовать для адресации.

Рассмотрим предназначение регистров процессора:
  • CS - Code Segment, сегмент кода. В этом регистре хранится сегментная часть адреса исполняемой команды.
  • DS - Data Segment, сегмент данных. Тут хранится сегментная часть адреса тех данных программы, которыми она пользуется в текущий момент.
  • SS - Stack Segment, сегмент стека. Хранит -||- области памяти, которая сейчас используется как стек.
  • ES - Extended Segment, дополнительный сегмент. Используется для того, чтобы программа могла использовать данные хотя бы из 2 участков памяти, которые не находятся рядом (например, в DS $1000, а в ES - $F000), а так же для некоторых команд, но об этом не сейчас.
  • AX - Accumulator, регистр общего назначения, т.е. можно его использовать как угодно, но отведён на математические вычисления, т.к. некоторые команды требуют того, чтобы их операнд находился именно в AX, а некоторые команды с использованием AX выглядят на байт короче после компиляции, нежели с каким-нибудь другим регистром.
  • BX - Base, тоже регистр общего назначения, но его конёк в том, что его можно использовать для адресации, т.е. число, хранящееся в нём при необходимости может быть расценено, как часть адреса - смещение.
  • CX - Counter, выделяется от остальных общих регистров тем, что команда реализации циклов (ну ещё пара, но куда реже) предполагает свой аргумент именно в CX. Т.е. при программировании на ассемблере CX выступает в роли Паскалевского i smile.gif
  • DX - Data. Просто регистр общего назначения. Такой на всякий случай - ничего специфического в нём нет.
  • IP - Instruction Pointer. Этот регистр хранит смещение текущей команды, т.е. пара CS:IP указывает на команду в памяти, которая вот-вот будет выполнена процессором.
  • SP - Stack Pointer. Хранит смещение верхушки стека (SS:SP). Очень важное значение, т. к. процессор при обработке команд работы со стеком рассчитывает именно на этот регистр.
  • BP - Base Pointer. Регистр для хранения смещения. Как правило используется в процедурах для адресации переменных через стек. Отличается от SP тем, что процессору его значение не особо интересно, но адресация через этот регистр идёт так же по SS (если не указать другой).
  • SI, DI - Source Index и Destination Index. Два регистра предназначенные для хранения смещений. Особенностью их является то, что некоторые команды рассчитывают, что их операнды хранятся именно тут.
Средствами паскаля мы можем подучить текущие значения регистров DS (функция DSeg), SS (SSeg) и CS (CSeg).

Теперь давайте рассмотри примитивный набор команд.

mov dst, src копирует значение src в dst. Есть один важный момент: командой mov нельзя скопировать значение одной переменной в другую за один приём.
Примеры: (Показать/Скрыть)


inc p увеличивает на 1 значение операнда. После компиляции эта команда занимает меньше места чем команда прибавления единицы.
Примеры: (Показать/Скрыть)


dec p соответственно уменьшает операнд на 1.

add dst, src прибавляет к src значение dst. Результат сохраняется в dst, так что просто число там написать нельзя.
Примеры: (Показать/Скрыть)


sub dst, src вычитает из dst значение src.

mul n умножает значение регистра AL (AX) на n. Если n размером в 1 байт, то происходит следующее: AX = AL * n, если же слово, то старшие 16 бит произведения сохраняются в DX, а младшие в AX, т. е., умножив AX=$1010 на $100 получим в DX $0010 и в AX $1000.
Примеры: (Показать/Скрыть)


div n делит значение в AX (DX:AX, как в команде mul) на n. При этом остаток сохраняется в AH (DX), а целая часть от деления в AL (AX).
Например: (Показать/Скрыть)


cmp a, b - сравнивает значения a и b и устанавливает флаги процессора в соответствии с результатом сравнения.
Например: (Показать/Скрыть)


jmp L - команда безусловного перехода на метку L. То что в BP называется GoTo.
Например: (Показать/Скрыть)


j<cc> L - серия команд условного перехода. Тут <cc> определяет условия перехода:
  • jz, je - переход, если равно
  • jb - переход, если меньше (числа расцениваются как беззнаковые)
  • ja - переход, если больше (числа расцениваются как беззнаковые)
  • jl - переход, если меньше (числа расцениваются как знаковые)
  • jg - переход, если больше (числа расцениваются как знаковые)
Также допускаются комбинации с буквой n (отрицание) и e (равенство).
Например: (Показать/Скрыть)


Рассмотрим ещё команду loop L она сравнивает CX с 0 и, если он отличен, то уменьшает его на 1 и делает переход на указанную метку.
Пример: (Показать/Скрыть)


Теперь для закрепления сказанного рассмотрим реализацию вычисления факториала:

Function Factorial(n: Integer): Integer; Assembler;
Asm
mov CX, [n] {Проверим, может n<0?}
cmp CX, 0 {Сравним с 0}
jl @@1 {Если меньше, то считать не будем}
mov AX, 1 {Начальное произведение}
@@2: {В CX мы уже загрузили кол-вл итераций, так что к циклу готовы}
mul CX {Домножим текущее произведение на значение счётчика}
loop @@2 {И продолжим цикл}
jmp @@3 {Произведение вычислено - можно выходить из функции}
@@1: {А, если попросили вычислить факториал отрицательного числа}
mov AX, 0 {То вернём 0}
End;

Var n: Integer;
Begin
 ReadLn(n);
 WriteLn(Factorial(n))
End.


Стоит объяснить ещё и то, как возвращаются значения функций. Это всё зависит от типа результата:
Byte, Char - через AL
Word, Integer - через AX
LongInt - старшая часть в DX, а младшие 16 бит в AX.
Pointer - сегментная часть в DX, смещение - AX.
Остальные типы возвращаются более извращённым способом...

Так же отмечу, что убрав проверку на <0 можно переписать эту функцию так:
Function Factorial(n: Integer): Integer; Assembler;
Asm
mov CX, [n]
mov AX, 1
@@1:
mul CX
loop @@1
End;

Правда проблема переполнения остаётся, но зато покажите мне компилятор, который стандартное
Function Factorial(n: Integer): Integer;
Var
 i, Res: Integer;
Begin
 Res:=1;
 For i:=2 To n Do
   Res:=Res*n
 Factorial:=Res
End;

скомпилирует вот так вот красиво...
 К началу страницы 
+ Ответить 
 
 Ответить  Открыть новую тему 
Ответов
сообщение
Сообщение #2


Гость






Теория это хорошо, но перейдём к
Практика использования


Довольно редко приходится заменять то, что уже написано разработчиками из Borland'а. Всё-таки там тоже не дураки сидят. Но иногда приходится написать что-нибудь своё... А потом прикинуть и понять, что на Assembler'е это куда симпатичнее выходит.

Есть такая команда INT <n>, которая вызывает указанное прерывание. Важно отметить то, что <n> это число и только число.
Прерывания - это набор каких-либо функций, которые предоставляются BIOS'ом, DOS'ом и некоторыми дровами. Откуда процессор знает где храниться обработчик какого-нибудь прерывания? Объясняю. Есть такая таблица прерываний. Она начинается с самого начала оперативки (с адреса 00000h) и занимает 1024 байта. Там для каждого прерывания храниться вектор (адрес обработчика). Таким образом команду
INT n

можно заменить чем-то вроде

XOR AX, AX
MOV ES, AX
MOV DI, 4*n
CALL FAR [ES:DI]

Вот только сбиваются ES и AX, чего как правило делать нельзя.

Что мы можем получить от прерываний
Мы можем получить доступ к функциям BIOS'а. Они доступны через прерывания
  • $10 - работа с монитором. Текстовые и графические режимы. Основные возможности: установка режима, вывод строк, изменение цветов пикселей...
  • $11 - возвращает список периферии. Довольно стрёмный способ сбора информации о железе надо заметить.
  • $12 - возвращает объём памяти без EMA. Т. е. 1 МБ.
  • $13 - работа с дисками на уровне читать/записать сектор, отформатировать дорожку и т. д.
  • $14 - работа с COM'иками. Инициализация, пересылка данных.
  • $15 - "расширенный сервис AT". Тут есть много всякого в том числе и "Delay", только не всегда это всё работает.
  • $16 - клава. Самый минимум, зато стабильно работает. Как показывает практика, это единственное прерывание, которое не заменяется при инициализации Windows. Более того она им ещё и пользуется.
  • $17 - принтер. Тоже примитив: инициализация, печать.
  • $18 - раньше тут хранился встроенный Basic. Что там сейчас, я не знаю.
  • $19 - перезагрузка. Из под Windows не потянет конечно, но, если вызвать его из под чистого DOS'а - результат гарантирован.
  • $1A - работа с CMOS часами, таймером и т. д.
  • $1B - это прерывание вызывается самим BIOS'ом, когда тот обнаруживает нажатие Ctrl-Break. Изначально вектор этого прерывания указывает на команду IRET, но при загрузке DOS ставит на него свой обработчик, который меняет переменные DOS'а и чего-то там ещё.

Ещё вектора прерываний указывают на некоторые таблицы BIOS'а. Например таблицы параметров FDD, HDD, таблицы символов... И т.д.

Есть ещё аппаратные прерывания. Их не вызывают, они сами приходят smile.gif Они генерируются, как реакция на сигналы от периферии. Например $08 генерируется 18.2 раза в секунду по сигналу от таймера, $09 - реакция на нажатие кнопочки на клаве.
Самая крупная и, наверно, многофункциональная, группа прерываний, это прерывания установленные операционной системой. Они располагаются в диапазоне $20-$2F. И вот что они позволяют:
  • $20 - Завершить программу. Есть одна трудность, связанная с использованием этого INT'а. Он предполагает, что CS хранит адрес PSP, т.е. имеет то значение, в который его установила OS при запуске программы. Воспользовавшись этим прерыванием из процедуры с дальним вариантом вызова, в которой CS возможно и не тот, мы можем не получить ожидаемого результата. Рекомендуемый вариант: заменит вызов INT 20h на вызов ф-ции 4Ch от INT 21h.
  • $21 - "сервис DOS". Тут целая куча функций на все случаи жизни. И файловый ввод/вывод, и консольный, и локальные настройки, и много чего другого. В том числе функции, которые ещё и Windows подгружает.
  • $22 - хранит "адрес возврата из программы". Именно сюда передастся управление, когда ваша программа завершится. Не стоит вызывать его непосредственно - DOS и сам умеет.
  • $23 - вот именно его и вызывает DOS запеленговав Ctrl-Break. Сам он в зависимости от установок либо завершит программу либо забьёт на всё. При желании можно изменить этот вектор на свою процедуру и самому решать что делать. Кстати в модуле CRT, есть переменная CheckBreak, вроде, которая определяет реакцию программы на Ctrl-Break (нагло игнорируя всякие там DOS'ы).
  • $24 - вызывается DOS'ом как реакция на что-то критическое. Например монитор отвалился. На практике ни разу не сталкивался smile.gif
  • $25, $26 - прерывания-заподлисты. Они хорошо умеют читать/писать сектора на диски (за исключением FAT'а и Boot'а), но сбивают все регистры и, что самое важное оставляют лишнее слово в стеке. Так что, вызвав эти прерывания не надо забывать вставлять что-то типа POP AX.
  • $27 - то, что в Паскале назвали Keep. Завершает программу оставляя её в памяти (TSR - Terminate And Stay Resident). Яркий пример такого "вандализма" - KeyRus. Есть более гибкая функция для таких целей - 31h от INT 21h
  • $28 - презент от мелко-мягких. Не документированное прерывание, которое вызывается DOS'ом во время ввода/вывода, а точнее во время простоя. Т. е. получив вызов функции ввода строки с клавы, DOS начинает её вводить, а пока юзверь сидит и ищет кнопку, DOS методично вызывает это прерывание.
  • $2E - ещё одна такая же. Но если с той хоть что-то ясно, то с этой вообще ничего. Она выполняет команду DOS'а типа "SET PATH=C:\", но как узнать выполнилась ли она мне не известно. А ещё она сбивает SS и SP, что меня вообще шокирует. Короче, не тронь - не пахнет.
  • $2F - "мультиплексор". Сюда могут вешаться любые программы и предоставлять свой сервис. Таким образом работают и SHARE, и ASSIGN и что-то там ещё.
Вот так.
Осталась последняя группа прерываний - те, которые устанавливаются by ПО. Например INT 33h предоставляет возможность работы с мышью, если запущен *Mouse.Com. Точнее это он перехватывает это прерывание и предоставляет вам какие-то возможности.

Ну вот, хотел перейти к практике, а уже столько теории впаял...
Давайте тогда хотя бы две функции напишем, которые практически ОЧЕНЬ нужны, а создав модуль с ними, мы получаем возможность не подключать CRT только ради них (ну не нравится мне CRT - глючит он, а патчи искать как правило влом):

Function KeyPressed: Boolean; Assembler;
Asm
 MOV AH, 1 { Номер функции прерывания как правило передаётся
через AH, по крайней мере в DOS'е и BIOS'е }
 INT 16h { Работа с клавой }

{ Если в буфере клавы чего-то есть, то это чего-то возвращается в AX,
но из буфера не стирается, а если нету, то устанавливается флаг ZF }
 JNZ @@1
 XOR AL, AL {Не нажата. Надо вернуть False, т. е. 0 в AL}
 JMP @@2
@@1:
 MOV AL, 1 {Нажата. Вернём True - 1 в AL}
@@2:
End;

Function ReadKey:Word;Assembler;
{
Да вот такой вот ReadKey - не Char, а Word.
Отличие в том, что для "обычной" клавиши ReadKey=Word(Key),
а для клавиш типа F1, стрелок и т. п. ReadKey=$??00.
Мне так удобнее...
}
Asm
 XOR AH,AH {Функция №0. Если в буфере клавы что-то есть, то
читает оттуда, иначе ждёт нажатия}
 INT 16h
 OR AL,AL {Если в AL попал 0, то в AH - дополнительный код,
 т. е. результат уже подготовлен}
 JZ @@1
 XOR AH,AH {Иначе обнулим AH на всякий случай, чтобы подогнать
значение из AL до слова}
@@1:
End;

Как видите, пара строк, а CRT из-за этих двух функций подключать уже не надо. А ведь частенько при экспериментах с графикой нужно что-то типа ReadKey, чтоб паузу сделать и посмотреть, что получается.
 К началу страницы 
+ Ответить 

Сообщений в этой теме
BlackShadow   Азы (встроенный ассемблер)   7.05.2004 20:01
Dark   ОШИБКА!!! :rolleyes: LOOP СНАЧАЛА …   10.05.2004 6:27
Dark   [center]Флаги В архитектуре компьютера существует…   10.05.2004 7:24
Dark   Вот некоторый пример :) uses crt; (* Программа п…   10.05.2004 17:10
BlackShadow   Продолжим ликбез :) [center]Стек Сейчас я вот во…   10.05.2004 18:05
BlackShadow   Голова болит, работа достала... Ну как тут не вспо…   11.05.2004 18:32
BlackShadow   [color=red]Битовые операции Начнём с логико-арифм…   12.05.2004 19:00
FreeMan   я читаю. кстати, вот пара процедур включения, выкл…   13.05.2004 0:15
BlackShadow   По документации, ты конечно прав... Но вот только …   13.05.2004 1:16
BlackShadow   Теория это хорошо, но перейдём к [center]Практика …   13.05.2004 19:25
Dark   Хых, как всегда ты на шаг впереди ;) конеечно, у т…   13.05.2004 22:45
BlackShadow   Я бы посоветовал Procedure Input(Var s:String);Ass…   14.05.2004 0:30
BlackShadow   [b]Текстовый ввод/вывод Всем прекрасно известны т…   17.05.2004 19:56
Dark   Продолжим Секция функций для работы с файлами и …   9.10.2004 2:57
Dark   Оки, тады продолжаем [b]Работа с диском (DOS функ…   16.10.2004 5:36


 Ответить  Открыть новую тему 
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0

 





- Текстовая версия 25.04.2024 11:03
500Gb HDD, 6Gb RAM, 2 Cores, 7 EUR в месяц — такие хостинги правда бывают
Связь с администрацией: bu_gen в домене octagram.name