Контакти

Введення в мову асемблера. Особливі ситуації режиму 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.) Буде потрібно цей код чи ні, програма все одно повинна видати його.

Що таке Асемблер

Асемблер - низькорівневий мову програмування. Для кожного процесора існує свій асемблер. Програмуючи на асемблері ви безпосередньо працюєте з апаратурою комп'ютера. Оригінальний текст на мові асемблера складається з команд (мнемонік), які після компіляції перетворюються в коди команд процесора.

Розробка програм на асемблері - дуже важка штука. Натомість витраченого часу ви отримуєте ефективну програму. Програми на асемблері пишуть, коли важливий кожен такт процесора. На асемблері ви даєте конкретні команди процесора і ніякого зайвого сміття. Цим і досягається висока швидкість виконання вашої програми.

Щоб грамотно використовувати асемблер необхідно знати програмну модель мікропроцесорної системи. З точки зору програміста мікропроцесорна система складається з:

  1. мікропроцесора
  2. пам'яті
  3. Пристроїв введення / виводу.

Програмна модель добре описана в літературі.

синтаксис Ассемблера

Загальний формат рядка програми на асемблері

<Метка>: <Оператор> <Операнды> ; <Комментарий>

Поле мітки. Мітка може складатися з символів і знаків підкреслення. Мітки використовуються в операціях умовного і безумовного переходу.

Поле оператора. У цьому полі міститься мнемоніка команди. наприклад мнемоніка 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 с.



Сподобалася стаття? поділіться їй