Введення в мову асемблера. Особливі ситуації режиму V86
Асемблери MASM, TASM і WASM відрізняються між собою. Однак створення простих програм для них практично не має відмінностей, за винятком самого ассемблирования і компонування.
Отже, наша перша програма для MASM, TASM і WASM, яка виводить англійську букву «A» в поточній позиції курсору, тобто в лівому верхньому кутку екрану:
Model tiny .code ORG 100h start: MOV AH, 2 MOV DL, 41h INT 21h INT 20h END start Цей текст можна набрати в будь-якому простому текстовому редакторі - наприклад в блокнот (NotePad) від WINDOWS (але не в Word і не в іншому «крутому»). Однак я рекомендую «просунутий» текстовий редактор з підсвічуванням синтаксису, наприклад, PSPad (див. Розділ). Потім зберігаємо цей файл з расшіреніем.asm, наприклад, в папці MYPROG. Назвемо файл atest. Отже, ми отримали: C: \\ MYPROG \\ atest.asm.
ПРИМІТКА
Зверніть увагу, що в першій команді ми записали 2 замість 02h. MASM, TASM і WASM, як і Emu8086, допускають такі «вольності». Хоча можна написати 02h - помилки не буде.
Пояснення до програми:
.model tiny - 1-ша рядок. Діректіва.model визначає модель пам'яті для конкретного типу файлів. У нашому випадку це файл з розширенням COM, тому вибираємо модель tiny, в якій об'єднані сегменти коду, даних, і стека. Модель tiny призначена для створення файлів типу СОМ.
.code - 2-а рядок. Ця директива починає сегмент коду.
ORG 100h - 3-й рядок. Ця команда встановлює значення програмного лічильника в 100h, тому що при завантаженні СОМ-файлу в пам'ять, DOS виділяє під блок даних PSP перші 256 байт (десяткове число 256 одно шістнадцятиричним 100h). Код програми розташовується тільки після цього блоку. Всі програми, які компілюються у файли типу СОМ, повинні починатися з цієї директиви.
start: MOV AH, 02h - 4-й рядок. Мітка start розташовується перед першою командою в програмі і буде використовуватися в директиві END, щоб вказати, з якої команди починається програма. Інструкція MOV поміщає значення другого операнда в перший операнд. Тобто значення 02h поміщається в регістр АН. Для чого це робиться? 02h - це ДОСовскіх функція, яка виводить символ на екран. Ми пишемо програму для DOS, тому використовуємо команди цієї операційної системи (ОС). А записуємо ми цю функцію (а точніше її номер) саме в регістр АН, тому що переривання 21h використовує саме цей регістр.
MOV DL, 41h - 5-й рядок. Код символу «A» заноситься в регістр DL. Код символу «A» за стандартом ASCII - це число 41h.
INT 21h - 6-й рядок. Це і є те саме переривання 21h - команда, яка викликає системну функцію DOS, задану в регістрі АН (в нашому прикладі це функція 02h). Команда INT 21h - основний засіб взаємодії програм з ОС.
INT 20h - 7-й рядок. Це переривання, яке повідомляє операційній системі про вихід з програми, і про передачу управління консольного додатку. У тому випадку, якщо програма вже відкомпільована і запущена з ОС, команда INT 20h поверне нас в ОС (наприклад, в DOS).
END start - 8-й рядок. Директива END завершує програму, одночасно вказуючи, з якою мітки повинно починатися її виконання.
INT 3 |
Виклик переривання 3 (#BP, точка зупинки) |
8086 |
int 3 |
|
CD ib |
INT imm8 |
виклик переривання imm8 |
8086 |
int 13 |
INTO |
Виклик переривання 4 (#OF, переповнення), якщо EFLAGS.OF \u003d 1 |
8086 |
into |
опис:
команда INT 3 призначена для генерації переривання 3 і за описом операції ідентична команді INT n за винятком того, що номер переривання тут не присутній безпосередньо в коді операції, але неявно задається рівним 3.
Дане переривання призначене для використання отладчиком, який розміщує спеціальну однобайтном команду INT 3 (Код CCh) замість першого байта команд або замість однобайтовим команд.
Існує другий спосіб виклику даного переривання за допомогою двухбайтное коду INT 3 (код CD03h). Однак даний метод на практиці не застосовується, все асемблери x86 за замовчуванням інтерпретують мнемоніку INT 3 як однобайтном команду з кодом CCh (але це не виключає можливість ручного програмування двухбайтное коду). Крім розміру коду, відрізняється і процес обробки одно- та двобайтових команд INT 3. Переривання, сгенерированное однобайтном командою в режимі EV86 (CR4.VME \u003d 1), що не піддається перенаправлення по карті перенаправлення переривань (як це описано для Режиму 2, Режиму 3, Режиму 5) і завжди обробляється обробником захищеного режиму через дескриптор в таблиці IDT. Крім того, в режимі V86 для даного переривання не провадиться перевірок поля IOPL і, відповідно, не може бути згенерована помилка загального захисту #GP коли EFLAGS.IOPL< 3, то есть однобайтная команда не является IOPL-чувствительной .
операція:
Представлений тут алгоритм описує не тільки поведінку процесора при виконанні команди INT 3 зовнішнього переривання або генерації особливої \u200b\u200bситуації.
THEN GOTO REAL-ADDRESS-MODE;
IF (EFLAGS.VM \u003d 1 AND EFLAGS.IOPL< 3 AND
(CR4.VME \u003d 0 OR CR4.VME \u003d 1 AND IRB [n] \u003d 1)
) (* IRB [n] - біт, що відповідає перериванню n в карті перенаправлення переривань *)
#GP (0); (Програмне переривання INT n в режимі: (1) V86 при EFLAGS.IOPL< 3, (2) EV86 Режим 2 *)
ELSE (* Захищений режим або режим V86 / EV86 *)
IF (EFLAGS.VM \u003d 1 AND CR4.VME \u003d 1 AND
(INT n) AND IRB [n] \u003d 0)
ELSE GOTO PROTECTED-MODE; (* Апаратні переривання, особливі ситуації; програмні переривання INT n в режимі: (1) Захищений режим, (2) V86 при EFLAGS.IOPL \u003d 3, (3) EV86 Режим 1 або Режим 4 *)
REAL-ADDRES-MODE:
IF ((Номер переривання * 4) + 3 виходить за межі сегмента при зверненні до таблиці векторів переривань IVT) THEN #GP; FI;
IF (В стеці немає місця для 6 байт) THEN #SS; FI;
EFLAGS.IF \u003d 0; (* Скидання прапора переривань *)
EFLAGS.TF \u003d 0; (* Скидання прапора пастки *)
EFLAGS.AC \u003d 0; (* Скидання прапора режиму контролю вирівнювання *)
CS \u003d IVT [Номер переривання * 4] .selector;
EIP \u003d IVT [Номер переривання * 4] .offset AND 0x0000FFFFh;
(* Продовження роботи в режимі реальної адресації ... *)
EV86-MODE: (* CR0.PE \u003d 1, EFLAGS.VM \u003d 1, CR4.VME \u003d 1, Режим EV86 - програмне переривання INT n, при IRB [n] \u003d 0 - Режим 3 або Режим 5 *)
IF (В стеці завдання V86 немає місця для 6 байт) THEN #SS (0); FI;
tempFLAGS \u003d FLAGS;
tempFLAGS.NT \u003d 0;
THEN EFLAGS.IF \u003d 0; (* Скидання прапора дозволу переривань *)
tempFLAGS.IF \u003d EFLAGS.VIF;
EFLAGS.VIF \u003d 0; (* Скидання віртуального прапора переривань *)
EFLAGS.TF \u003d 0; (* Скидання прапора пастки *)
Push (tempFLAGS);
(* В стік не заносяться коди помилок *)
CS \u003d IVT_V86 [Номер переривання * 4] .selector; (* Таблиця векторів переривань IVT_V86 розташовується на початку адресного простору завдання V86 *)
EIP \u003d IVT_V86 [Номер переривання * 4] .offset AND 0x0000FFFFh; (* Старші 16-біт регістра EIP обнуляються *)
(* Продовження роботи в режимі EV86 ... *)
PROTECTED-MODE: (* CR0.PE \u003d 1, Апаратні переривання, особливі ситуації; програмні переривання INT n в режимі: (1) Захищений режим, (2) V86 при EFLAGS.IOPL \u003d 3, (3) EV86 Режим 1 або Режим 4 *)
IF ((Номер переривання * 8) + 7 не потрапляє в межі таблиці IDT) THEN #GP (номер переривання * 8 + 2 + EXT); FI;
(* Тут і далі в параметрах коду помилки доданок +2 означає установку біта IDT коду помилки, а доданок + EXT - означає установку біта EXT коду помилки відповідно до того, чи було що викликало помилку переривання програмним EXT \u003d 0 або зовнішнім EXT \u003d 1 * )
Байт AR дескриптора повинен задавати шлюз переривання, шлюз пастки або шлюз завдання, інакше #GP (номер переривання * 8 + 2 + EXT);
IF (Програмне переривання або особлива ситуація) (* Тобто один з випадків INT n, INT 3, INT01, BOUND або INTO *)
IF (CPL\u003e DPL шлюзу)
#GP (номер переривання * 8 + 2); (* CR0.PE \u003d 1, DPL шлюзу< CPL, программное прерывание *)
Шлюз повинен бути присутнім, інакше #NP (номер переривання * 8 + 2 + EXT);
IF (Шлюз завдання)
THEN GOTO TASK-GATE;
GOTO TRAP-OR-INT-GATE; (* CR0.PE \u003d 1, шлюз переривання або пастки *)
TRAP-OR-INT-GATE: (* Захищений режим або режим V86 / EV86, шлюз пастки або переривання *)
Перевірка нового селектора CS, заданого в дескрипторі шлюзу, і відповідного йому дескриптора з LDT чи GDT:
Селектор повинен бути не нульовим, інакше #GP (EXT);
Індекс селектора повинен потрапляти в межі таблиці дескрипторів, інакше #GP (селектор + EXT);
Обраний дескриптор повинен бути дескриптором сегмента коду, інакше #GP (селектор + EXT);
Сегмент повинен бути присутнім (P \u003d 1), інакше #NP (селектор + EXT);
IF (Кодовий сегмент неузгоджений) AND (DPL сегмента коду< CPL)
IF EFLAGS.VM \u003d 0
THEN GOTO INT-TO-INTER-PRIV; (* CR0.PE \u003d 1, EFLAGS.VM \u003d 0, шлюз переривання або пастки, неузгоджений кодовий сегмент, DPL сегмента коду< CPL *)
ELSE (* EFLAGS.VM \u003d 1 *)
IF (DPL нового сегмента коду ≠ 0) THEN #GP (Селектор кодового сегмента + EXT); FI;
GOTO INT-FROM-V86-MODE;(* CR0.PE \u003d 1, EFLAGS.VM \u003d 1, шлюз переривання або пастки, DPL сегмента коду \u003d 0, CPL \u003d 3 *)
ELSE (* CR0.PE \u003d 1, шлюз переривання або пастки, узгоджений кодовий сегмент або неузгоджений кодовий сегмент з DPL \u003d CPL *)
IF EFLAGS.VM \u003d 1 THEN #GP (Селектор кодового сегмента + EXT); FI;
IF ((Кодовий сегмент узгоджений) OR (DPL сегмента коду \u003d CPL))
THEN GOTO INT-TO-INTRA-PRIV; (* CR0.PE \u003d 1, шлюз переривання або пастки, DPL сегмента коду ≤ CPL для узгодженого сегмента, DPL сегмента коду \u003d CPL для неузгодженого сегмента *)
ELSE #GP (Селектор кодового сегмента + EXT); (* DPL\u003e CPL для узгодженого сегмента або DPL ≠ CPL для неузгодженого сегмента *)
INT-TO-INTER-PRIV: (* Захищений режим, шлюз переривання або пастки, неузгоджений кодовий сегмент, DPL сегмента коду< CPL *)
IF (поточний TSS 32-бітний)
TSSstackAddress \u003d (new code segment DPL * 8) + 4
IF ((TSSstackAddress + 5)\u003e Межа TSS) (* (TSSstackAddress + 7)\u003e
NewSS \u003d [База TSS + TSSstackAddress + 4]; (* Завантажується 2 байта *)
(* Завантажується 4 байта *)
ELSE (* Поточний TSS 16-бітний *)
TSSstackAddress \u003d (new code segment DPL * 4) + 2
IF ((TSSstackAddress + 3)\u003e Межа TSS) (* (TSSstackAddress + 4)\u003e Межа TSS - для деяких моделей процесорів *)
THEN #TS (Селектор поточного TSS + EXT);
NewESP \u003d [База TSS + TSSstackAddress]; (* Завантажується 2 байта *)
NewSS \u003d [База TSS + TSSstackAddress + 2]; (* Завантажується 2 байта *)
RPL селектора має дорівнювати DPL нового кодового сегмента, інакше #TS (SS селектор + EXT);
DPL стекового сегмента має дорівнювати DPL нового кодового сегмента, інакше #TS (SS селектор + EXT);
IF (32-бітний шлюз)
THEN Новий стек повинен мати місце для 20 байт (24 байт, якщо є код помилки), інакше #SS (EXT)
ELSE Новий стек повинен мати місце для 10 байт (12 байт, якщо є код помилки), інакше #SS (EXT)
SS: ESP \u003d TSS (NewSS: NewESP); (* Завантаження нових значення SS і eSP з TSS *)
IF (32-бітний шлюз)
THEN
ELSE CS: IP \u003d Gate (SELECTOR: OFFSET);
Завантажити дескриптор SS в приховану частину регістра SS;
IF (32-бітний шлюз)
Push (Довгий покажчик на старий стек - SS: ESP);
Push (Довгий покажчик на точку повернення - CS: EIP); (* 3 слова доповнюються до 4 *)
Push (Код помилки);
Push (Довгий покажчик на старий стек - SS: SP); (* 2 слова *)
Push (Довгий покажчик на точку повернення - CS: IP); (* 2 слова *)
Push (Код помилки);
CPL \u003d DPL нового кодового сегмента;
IF (Шлюз переривання) THEN EFLAGS.IF \u003d 0 FI; (* Скинути прапор переривання *)
EFLAGS.RF \u003d 0;
(* Продовження роботи в захищеному режимі на рівні з великими привілеями ... *)
INT-FROM-V86-MODE: (* Режим V86 / EV86, шлюз переривання або пастки, DPL \u003d 0, CPL \u003d 3 *)
(* Поточний TSS завжди 32-бітний в режимі V86 *)
IF (Межа TSS< 9) (* Межа TSS< 11 - для некоторых моделей процессоров *)
THEN #TS (Селектор поточного TSS + EXT);
NewSS \u003d [База TSS + 8]; (* Завантажується 2 байта *)
NewESP \u003d [База TSS + 4]; (* Завантажується 4 байта *)
Перевірка селектора нового стекового сегмента NewSS і відповідного йому дескриптора з LDT чи GDT:
Селектор повинен бути не нульовим, інакше #TS (EXT);
Індекс селектора повинен потрапляти в межі таблиці дескрипторів, інакше #TS (SS селектор + EXT);
RPL селектора має дорівнювати нулю, інакше #TS (SS селектор + EXT);
DPL стекового сегмента має дорівнювати нулю, інакше #TS (SS селектор + EXT);
Дескриптор повинен мати формат дескриптора сегмента даних, дозволеного для запису (W \u003d 1), інакше #TS (SS селектор + EXT);
Сегмент повинен бути присутнім (P \u003d 1), інакше #SS (SS селектор + EXT);
IF (32-бітний шлюз)
Новий стек повинен мати місце для 36 байт (40 байт, якщо є код помилки), інакше #SS (EXT)
Новий покажчик інструкції повинен потрапляти в межі нового кодового сегмента, інакше #GP (EXT); (* Покажчик інструкції визначається значенням поля OFFSET з дескриптора шлюзу *)
TempEflags \u003d EFLAGS;
EFLAGS.VM \u003d 0; (* Процесор виходить з режиму V86 для обробки переривання в захищеному режимі *)
IF (Шлюз переривання)
THEN EFLAGS.IF \u003d 0;
CPL \u003d 0; (* Перемикання на нульовий рівень привілеїв *)
SS: ESP \u003d TSS (NewSS: NewESP); (* Завантажити значення SS0 і ESP0 з TSS *)
Push (GS);
Push (FS); (* Розширюється до двох слів *)
Push (DS); (* Розширюється до двох слів *)
Push (ES); (* Розширюється до двох слів *)
GS \u003d 0; (* Сегментні регістри обнуляються. Неприпустимо подальше використання нульових селектор в захищеному режимі *)
Push (TempSS); (* Розширюється до двох слів *)
Push (TempEflags);
Push (CS); (* Розширюється до двох слів *)
Push (Код помилки); (* Якщо присутній, 4 байта *)
CS: EIP \u003d Gate (SELECTOR: OFFSET); (* Завантажити селектор: зміщення з дескриптора 32-бітного шлюзу *)
ELSE (* 16-бітний шлюз *)
Новий стек повинен мати місце для 18 байт (20 байт, якщо є код помилки), інакше #SS (EXT)
(* Збереження в стеці 16-бітних значень регістрів відбувається аналогічно 32-бітного шлюзу *)
(* Повернення з переривання командою IRET назад в режим V86 з 16-бітного сегмента буде неможливий, так як прапор VM не збережеться в стеку і не відновиться з образу EFLAGS при поверненні *)
(* Продовження роботи в захищеному режимі на нульовому рівні привілеїв ... *)
INT-TO-INTRA-PRIV: (* CR0.PE \u003d 1, DPL \u003d CPL або узгоджений сегмент з DPL ≤ CPL *)
IF (32-бітний шлюз)
THEN Новий стек повинен мати місце для 12 байт (16 байт, якщо є код помилки), інакше #SS (EXT)
ELSE Новий стек повинен мати місце для 6 байт (8 байт, якщо є код помилки), інакше #SS (EXT)
Новий покажчик інструкції повинен потрапляти в межі нового кодового сегмента, інакше #GP (EXT);
IF (32-бітний шлюз)
Push (Довгий покажчик на точку повернення); (* 3 слова доповнюються до 4 *)
CS: EIP \u003d Gate (SELECTOR: OFFSET); (* Завантажити селектор: зміщення з дескриптора 32-бітного шлюзу *)
Push (Код помилки); (* Якщо присутній, 4 байта *)
Push (Довгий покажчик на точку повернення); (* 2 слова *)
CS: IP \u003d Gate (SELECTOR: OFFSET); (* Завантажити селектор: зміщення з дескриптора 16-бітного шлюзу *)
Push (Код помилки); (* Якщо присутній, 2 байта *)
Завантажити дескриптор CS в приховану частину регістра CS;
IF (Шлюз переривання) THEN EFLAGS.IF \u003d 0; FI;
(* Продовження роботи в захищеному режимі без зміни рівня привілеїв ... *)
TASK-GATE: (* CR0.PE \u003d 1, шлюз завдання *)
Перевірка селектора TSS з дескриптора шлюзу завдання:
Селектор повинен задавати GDT (біт TI \u003d 0), інакше #GP (TSS селектор + EXT);
Індекс селектора повинен потрапляти в межі таблиці GDT, інакше #GP (TSS селектор + EXT);
Перевірка відповідного заданої селектору дескриптора TSS:
Дескриптор TSS повинен мати тип вільного TSS (TYPE.B \u003d 0), інакше #GP (TSS селектор + EXT);
TSS повинен бути присутнім (P \u003d 1), інакше #NP (TSS селектор + EXT);
SWITCH-TASKS (С вкладенням) в TSS; (* Тут "З вкладенням" означає той факт, що при ініціалізації контексту нового завдання буде встановлений прапор EFLAGS.NT \u003d 1, а в поле LINK її сегмента TSS буде скопійований селектор TSS переривається (старої) завдання - см. Адресація і багатозадачність: Засоби підтримки мультизадачности *)
IF (Переривання є особливою ситуацію з кодом помилки)
У стеці має бути місце для коду помилки, інакше #SS (EXT);
Push (код помилки);
Покажчик інструкції EIP повинен потрапляти в межі сегмента CS, інакше #GP (EXT);
(* Завантаження контексту нового завдання супроводжується додатковими перевірками як це описано в розділі Адресація і багатозадачність: Засоби підтримки мультизадачности *)
(* Продовження роботи в контексті нового завдання ... *)
Особливі ситуації захищеного режиму:
INT 3, Але і під час вступу будь-якого зовнішнього переривання або генерації особливої \u200b\u200bситуації. біт EXT в коді помилки зовнішнього переривання).
- дескриптор вектору (Індексу) переривання, чи не знаходиться в межах таблиці дескрипторів переривань (IDT);
- дескриптор, відповідний оброблюваного вектору (Індексу) переривання, не є дескриптором шлюзу пастки, шлюзу переривання або шлюзу завдання;
- має місце програмне переривання або програмна особлива ситуація (тобто один з випадків: INT n, INT 3, INT01, BOUND або INTO) і при цьому поточний рівень привілеїв (CPL) завдання більше рівня привілеїв(DPL) дескриптора шлюзу з таблиці IDT -
- селектор сегмента в відповідному оброблюваного вектору (Індексу) переривання дескрипторі шлюзу пастки, шлюзу переривання або шлюзу завдання є нульовим селектором;
- індекс селектора сегмента дескриптора шлюзу пастки або дескриптора шлюзу переривання НЕ потрапляє в межі відповідної таблиці дескрипторів;
- індекс селектора TSS з відповідного переривання дескриптора шлюзу завдань не потрапляє в межі глобальної таблиці дескрипторів (GDT);
- дескриптор нового кодового сегмента не є дескриптором сегмента коду;
- (DPL) нового узгодженого кодового сегмента більше поточного уровеня привілеїв (CPL) завдання -
- рівень привілеїв дескриптора (DPL) нового неузгодженого кодового сегмента не дорівнює поточний рівень привілеїв (CPL) завдання -
- селектор TSS з відповідного переривання дескриптора шлюзу завдання вказує на локальну таблицю дескрипторів (LDT);
- дескриптор TSS нового завдання відзначений як зайнятий- TYPE.B ≠ 1.
- адреса, за якою має зчитуватися нове значення для покажчика стека (SS: eSP), виходить за межі сегмента TSS;
- селектор нового сегмента стека є нульовим селектором;
- індекс селектора нового сегмента стека не влучає у межі відповідної таблиці дескрипторів;
- запитуваний рівень привілеїв (RPL) селектора нового сегмента стека НЕ \u200b\u200bдорівнює (DPL) нового сегмента коду -
- рівень привілеїв дескриптора (DPL) нового сегмента стека НЕ \u200b\u200bдорівнює рівню привілеїв дескриптора (DPL) нового сегмента коду -
- новий стековий сегмент не є сегментом даних доступним для запису -
- що відповідає перериванню дескриптор шлюзу пастки, шлюзу переривання, шлюзу завдання або дескриптор TSS відзначений як неприсутність (Біт P дескриптора скинутий);
- новий сегмент коду не присутній (біт P дескриптора сегмента скинутий).
- при записі в стек (в тому числі в новий стек, якщо мало місце переключення стека) значень адреси повернення, покажчика стека, прапорів або коду помилки просходит вихід за допустиму межу стекового сегмента;
- новий сегмент стека не присутній (біт P дескриптора сегмента скинутий).
Особливі ситуації режиму реальної адресації:
Представлений тут перелік особливих ситуацій характеризує поведінку процесора не тільки при виконанні команди INT 3, Але і під час вступу будь-якого зовнішнього переривання або генерації особливої \u200b\u200bситуації.
Особливі ситуації режиму V86:
Представлений тут перелік особливих ситуацій характеризує поведінку процесора не тільки при виконанні команди INT 3, Але і під час вступу будь-якого зовнішнього переривання або генерації особливої \u200b\u200bситуації. біт EXT в коді помилки використовується для індикації зовнішнього по відношенню до перерваної програми події (
Всі функції DOS викликаються за допомогою переривання 21h (в десятковій нотації 33). Перша версія DOS містила 42 функції. У другій до них додано ще 33 функції, які зберігаються у всіх наступних версіях. Вибір конкретної функції здійснюється шляхом запису відповідного номера в регістр AH.
Функція 02: висновок одного символу на екран
Для виведення одного символу на екран ПК використовується
функція 02 переривання 21h:
mov DL,<код выводимого символа>
Виведений символ висвічується в позиції курсора (що б там не було записано), після чого курсор зсувається на одну позицію вправо. Якщо курсор знаходився в кінці рядка екрану, то він переміщається на початок наступного рядка, а якщо курсор був в кінці останнього рядка екрану, то вміст екрану зсувається на один рядок вгору, а внизу з'являється порожній рядок, в започаткована ще і встановлюється курсор.
Особливим чином здійснюється висновок символів з кодами 7, 8, 9, 10 (0Ah) і 13 (0Dh). Символ з кодом 7 (bell, дзвінок) на екрані не висвітлюється (і курсор не зрушується), а викликає звуковий сигнал. Символ з кодом 8 (backspase, крок назад) повертає курсор на одну позицію вліво, якщо тільки він не був в самій лівій позиції рядка. Символ з кодом 9 (tab, табуляція) зміщує курсор вправо на найближчу позицію, кратну 8. Символ з кодом 10 (line feed, переклад рядка) переміщує курсор в наступний рядок екрана, залишаючи його в тій же колонці. Символ з кодом 13 (carrige returne, повернення каретки) встановлює курсор на початок поточного рядка; висновок поспіль символів з кодами 13 та 10 означає переклад курсору на початок наступного рядка.
Функція 9: вивести рядок на екран дисплея
Для виведення на екран рядки (послідовності символів) можна, звичайно, використовувати функцію 02, проте зробити це можна і за один прийом за допомогою функції 09 переривання 21h:
DS: DX: \u003d початкова адреса рядка
Перед зверненням до цієї функції в регістр DS повинен бути поміщений номер того сегмента пам'яті, в якому знаходиться виводиться рядок, а в регістр DX - зміщення рядки всередині цього сегмента. При цьому в кінці рядка повинен знаходитися символ $ (код 24h), який є ознакою кінця рядка і сам не виводиться.
Хоча ця функція може виявитися набагато зручніше функцій побайтового виведення на екран (функція 2 і 6), вона має той недолік, що цілком звичайний символ $ використовується як обмежувач рядка. Це ще один побічний продукт сумісності з CP / M.
Розширені функції операційної системи DOS в якості обмежувача рядка використовують CHR $ (0). Це відповідає угодам, прийнятим в операційній системі UNIX і мовою програмування Сі.
Серед функцій DOS немає такої, яка виводить числа. Таку операцію, якщо треба, доводиться реалізовувати на основі розглянутих функцій.
Функція 4Ch: завершення програми
Завершивши всі свої дії, програма повинна повернути управління операційній системі, щоб користувач міг продовжити роботу на ПК. Таке повернення реалізується функцією 4Ch переривання 21h, яку поміщають в кінці програми:
mov AL,<код завершения>
Кожна програма, взагалі кажучи, зобов'язана повідомити, успішно чи ні вона завершила свою роботу. Справа в тому, що будь-яка програма викликається з якоїсь іншої програми (наприклад, з операційної системи), і іноді отримала програмі, щоб правильно продовжити роботу, треба знати, чи виконала викликана програма все, що треба, або вона пропрацювала з помилкою. Така інформація передається у вигляді коду завершення програми (деякого цілого числа), який повинен бути нульовим, якщо програма пропрацювала правильно, і ненульовим (яким саме - обмовляється в кожному випадку особливо) в іншому випадку. (Дізнатися код завершення викликаної програми можна за допомогою функції 4Dh переривання 21h.) Буде потрібно цей код чи ні, програма все одно повинна видати його.
Що таке Асемблер
Асемблер - низькорівневий мову програмування. Для кожного процесора існує свій асемблер. Програмуючи на асемблері ви безпосередньо працюєте з апаратурою комп'ютера. Оригінальний текст на мові асемблера складається з команд (мнемонік), які після компіляції перетворюються в коди команд процесора.
Розробка програм на асемблері - дуже важка штука. Натомість витраченого часу ви отримуєте ефективну програму. Програми на асемблері пишуть, коли важливий кожен такт процесора. На асемблері ви даєте конкретні команди процесора і ніякого зайвого сміття. Цим і досягається висока швидкість виконання вашої програми.
Щоб грамотно використовувати асемблер необхідно знати програмну модель мікропроцесорної системи. З точки зору програміста мікропроцесорна система складається з:
- мікропроцесора
- пам'яті
- Пристроїв введення / виводу.
Програмна модель добре описана в літературі.
синтаксис Ассемблера
Загальний формат рядка програми на асемблері
<Метка>: <Оператор> <Операнды> ; <Комментарий>
Поле мітки. Мітка може складатися з символів і знаків підкреслення. Мітки використовуються в операціях умовного і безумовного переходу.
Поле оператора. У цьому полі міститься мнемоніка команди. наприклад мнемоніка mov
Поле операндів. Операнди можуть бути присутніми тільки якщо присутній Оператор (поле оператора). Операндів може не бути, а може бути кілька. Операндами можуть бути дані, над якими необхідно виконати якісь дії (переслати, скласти і т.д.).
Поле коментаря. Коментар потрібен для словесного супроводу програми. Все, що стоїть за символом ; вважається коментарем.
Перша програма на мові Асемблера
У цій статті буде використовуватися асемблер для i80x86 процесора і використовуватися такі програми та
- TASM - Borland Turbo Assembler - компілятор
- TLINK - Borland Turbo Linker - редактор зв'язків (компоновщик)
Якщо бути конкретним, то Tasm 2.0.
За традицією наша перша програма буде виводити рядок "Hello world!" на екран.
файл sample.asm
Model small; Модель памяті.stack 100h; Установка розміру стека.data; Початок сегмента даних програми HelloMsg DB "Hello World!", 13,10, "$" .code; Початок сегмента коду mov ax, @ DATA; Пересилаємо адреса сегмента даних в регістр AX mov ds, ax; Установка регістра DS на сегмент даних mov ah, 09h; DOS функція виведення рядка на екран mov dx, offset HelloMsg; Задаємо зміщення до початку рядка int 21h; Виводимо рядок mov ax, 4C00h; DOS функція виходу з програми int 21h; Вихід з програми end
Як ви могли помітити, що програма розділена на сегменти: сегмент даних, сегмент коду і є ще стековий сегмент.
Розглянемо все по порядку.
Діректіва.model small задає модель пам'яті. Модель small - це 1 сегмент для коду, 1 сегмент для даних і стека тобто дані і стек знаходяться в одному сегменті. Бувають і інші моделі пам'яті, наприклад: tiny, medium, compact. Залежно від обраної вами моделі пам'яті сегменти вашої програми можуть перекриватися або можуть мати окремі сегменти в пам'яті.
Діректіва.stack 100h задає розмір стека. Стек необхідний для збереження деякою інформацією з подальшим її відновленням. Зокрема стек використовується при переривання. У цьому випадку вміст регістра прапорів FLAGS, регістра CS і регістра IP зберігаються в стеці. Далі йде виконання перериває програми, а потім йде відновлення значень цих регістрів.
- Регістр прапорів FLAGS містить ознаки, які формуються після виконання команди процесором.
- Регістр CS (Code Segment) містить адресу сегмента коду.
- Регістр IP (Instruction Pointer) - покажчик команд. Він містить адресу команди, яка належна виконатися наступного (Адреса щодо сегмента коду CS).
більш докладний опис виходить за рамки простої статті.
Діректіва.data визначає початок сегмента даних вашої програми. У сегменті даних визначаються "змінні" тобто йде резервування пам'яті під необхідні дані. После.data йде рядок
HelloMsg DB "Hello World!", 13,10, "$"
Тут HelloMsg - це символьне ім'я, яке відповідає початку рядка "Hello World!" (Без лапок). Тобто це адреса першого символу нашої рядки щодо сегмента даних. Директива DB (Define Byte) визначає область пам'яті доступну по-байтних. 13,10 - коди символів новий рядок і Повернення каретки, а символ $ необхідний для коректної роботи DOS функції 09h. Отже, наша рядок буде займати в пам'яті 15 байт.
Діректіва.code визначає початок сегмента коду (CS - Code Segment) програми. Далі йдуть рядки програми містять мнемоніки команд.
Розповім про команду mov.
mov<приёмник>, <источник>
Команда mov - команда пересилання. Вона пересилає вміст джерела в приймач. Пересилання можуть бути регістр-регістр, регістр-пам'ять, пам'ять-регістр, а ось пересилання пам'ять-пам'ять немає тобто все проходить через регістри процесора.
Щоб працювати з даними необхідно налаштувати регістр сегмента даних. Налаштування полягає в тому, що ми записуємо адресу сегмента даних @DATA в регістр DS (Data Segment). Безпосередньо записати адресу в цей регістр не можна - така архітектура, тому ми використовуємо регістр AX. У AX ми записуємо адресу сегмента коду
а потім пересилаємо вміст регістра AX в регістр DS.
Після цього регістр DS буде містити адресу початку сегмента даних. За адресою DS: 0000h міститиметься символ H. Я припускаю, що ви знаєте про сегментах і зсувах.
Адреса складається з двох складових<Сегмент>:<Смещение>, Де Сегмент це 2 байта і зміщення - 2 байта. Виходить 4 байта для доступу до будь-якої комірки пам'яті.
mov ah, 09h
mov dx, offset HelloMsg
int 21h
Тут ми в регістр AH записуємо число 09h - номер функції 21-го переривання, яка виводить рядок на екран.
У наступному рядку ми в регістр DX записуємо адресу (збентеження) до початку нашої рядки.
Далі ми викликаємо переривання 21h - це переривання функцій DOS. Переривання - коли виконується програма переривається і починає виконуватися перериває програма. За номером переривання визначається адреса підпрограми DOS, яка виводить рядок символів на екран.
У вас напевно виникне питання: А чому ми записуємо номер функції 09h в регістр AH? І чому зміщення до рядка записуємо в регістр DX?
Відповідь проста: для кожної функції визначені конкретні регістри, які містять вхідні дані для цієї функції. Подивитися які регістри потрібні конкретних функцій ви можете в help "е.
mov ax, 4C00h
int 21h
mov ax, 4C00h - пересилаємо номер функції в регістр AX. Функція 4C00h - вихід з програми.
int 21h - виконуємо переривання (власне виходимо)
end - кінець програми.
Після директиви end компілятор все ігнорує, тому можете там писати все, що завгодно :)
Якщо ви дочитали до кінця, то ви герой!
Майко Г.В. Асемблер для IBM PC: - М .: "Бізнес-Інформ", "Сирин» 1999 р - 212 с.