Iphone
shpora.me - незаменимый помощник для студентов и школьников, который позволяет быстро создавать и получать доступ к шпаргалкам или другим заметкам с любых устройств. В любое время. Абсолютно бесплатно. Зарегистрироватся | Войти

* данный блок не отображается зарегистрированым пользователям и на мобильных устройствах

Системне -andy

ЛАБОРАТОРНА РОБОТА №1

 

ТЕМА: Системи числення.

МЕТА: Навчитись представляти числа в двійковій, вісімковій, шістнадцятко вій та двійково-десятковій системах числення; навчитись переводити числа із однієї системи числення в іншу.

 

  1. Теоретичні відомості

1.1 Що таке система числення і які бувають системи числення

Системою числення називають сукупність прийомів запису чисел. Розрізнюють позиційні і непозиційні системи числення.

Непозиційні системи числення

Прикладом непозиційної системи числення є так звані римські цифри. У цій системі смисл кожного символу не залежить від місця, на якому він стоїть. Так запис LXXX позначає число 80. Символ X має значення 10 неза- лежно від його місця у запису.

Позиційні системи числення

У позиційній системі числення значення цифри в зображенні числа залежить від її положення (позиції) у послідовності цифр, що зображують число. Наприклад, запис 5237 у позиційній системі числення означає, що це число містить 7 одиниць, 3 десятки, 2 сотні і 5 тисяч, тобто 5237 - це скорочене позначення виразу:

5 · 103 + 2 · 102 + 3 · 102 + 7 · 100

Число 10, що присутнє у кожному доданкові, називають основою системи числення, а саму систему десятковоюсистемою числення. Зверніть увагу, що для запису числа в десятковій системі ми використовуємо рівно десять цифр,  які називають алфавітом системи  числення

0, 1, 2, 3, 4, 5, 6, 7, 8, 9.

Цифра (символ), що позначає основу, тобто у даному разі число «десять», відсутнє. За принципом позиційної системи це число позначається одиницею в наступній позиції. Для того, щоб підкреслити, що число задане саме у десятковій системі пишуть (5237)10.

Ми користуємось десятковою системою з цілком зрозумілих причин - на руках у людини десять пальців. Ми звикли до неї, і ніколи свідомо не підкреслюємо значення основи. Але немає ніяких перешкод побудувати систему числення, якщо за основу взяти будь-яке інше натуральне число. Візьмемо, наприклад, за основу позиційної системи число 8, тоді запис (123)8буде означати  вираз:

1 · 82 + 2 · 81 + 3 · 80

Якщо виконати арифметичні дії, то отримаємо число  64 + 16 + 3 = 83. Тобто:

(123)8 = (83)10.

Нагадаємо, що у вісімковій системі для запису чисел використовуються тільки 8 цифр: 0, 1, 2, 3, 4, 5, 6, 7 і наступні числа у цій системі будуть позначатися таким чином: 10, 11, 12, 13, 14, 15, 16, 17, 20, 21, 22, 23, 24, 25, 26, 27, 30 ... і т.д.

Зверніть увагу, що в будь-якій системі число рівне основі має вигляд 10, тому множення (ділення) на основу зводиться до перенесення коми, яка розділяє цілу і дробову частину на одну позицію праворуч (ліворуч):

(12)8 · (8)10 = (12)8 ·  (10)8 = (120)8

З числами у вісімковій системі числення всі арифметичні операції виконуються за тими ж правилами, що і в десятковій системі.

1.2 Шістнадцяткова система числення

В комп’ютерних технологіях широко використовується шістнадцяткова система числення. Певна річ, що треба мати 16 символів для позначення цифр. Перші десять цифр можна запозичити з десяткової системи числення, а що до решти, то їх домовилися позначати  великими латинськими літерами:

10 - A,  11 - B,  12 - C,  13 - D,  14 - E,  15 -  F.

Таким чином запис (2CF) 16 буде означати вираз

2 · 162 + 12 · 161 + 15 · 160 = (944)10

1.3 Двійкова і вісімкова системи числення

Окрім шістнадцяткової системи в комп’ютерних технологіях використовуються двійкова, а також вісімкова системи числення, які як і шістнадцяткова система мають основою степені  двійки.

Алфавіт двійкової системи складається з двох цифр: 0, 1. Ці цифри мають спеціальну назву біт від англійсь- кого «binary digit». Запис вигляду  (101101)2 означає вираз:

1·25 + 0·24 + 1·23 + 1·22 + 0·21 + 1·20 = (45)10

Нижче у таблиці 1 подані перші шістнадцять цілих чисел, записаних у різних системах числення.

Таблиця 1 – Числа, записані у різних системах числення

Десятковасистема

Двійковасистема

Вісімковасистема

Шістнадцятковасистема

0

0

0

0

1

1

1

1

2

10

2

2

3

11

3

3

4

100

4

4

5

101

5

5

6

110

6

6

7

111

7

7

8

1000

10

8

9

1001

11

9

10

1010

12

A

11

1011

13

B

12

1100

14

C

13

1101

15

D

14

1110

16

E

15

1111

17

F

 

 

Зверніть увагу, що для запису чисел таблиці у двійковій системі знадобилося не більше ніж чотири біта.

Дріб у двійковій системі записується за тими ж правилами, що і десятковий дріб, але при підрахунку значення треба використовувати від’ємні степені двійки. Запис (0,1101)2 означає:

1·2-1 + 1·2-2 + 0·2-3 + 1·2-4 = 1·0,5 + 1·0,25 + 0·0,125 + 1·0,0625 = (0,8125)10

 

 

2. Приклад виконання завдання

2.1 Число 483 (в десятковій системі числення) перевести в двійкову систему числення

48310 → ?2

Виконання:

Перевірка:

Ваговий коефіцієнт

28

27

26

25

24

23

22

21

20

Число

1

1

1

1

0

0

0

1

1

 

 

1·256 + 1·128 + 1·64 + 1·32 + 1·2 + 1 = 48310

 

Відповідь: 48310 → 1111000112 – цей результат необхідно вписати в таблицю 4.

 

2.2 Число 483 (в десятковій системі числення) перевести в вісімкову систему числення

48310 → ?8

Виконання: Розбиваємо двійкове число на тріади справа наліво і зображаємо кожну тріаду відповідною вісімковою цифрою (див. таблицю 1):

 

48310 → 1111000112

111.100.0112 → 7438

 

Перевірка:

7438 → 7·82+ 4·81 + 3·80 = 7·64 + 4·8 + 3 = 48310

 

Відповідь: 48310 → 7438 – цей результат необхідно вписати в таблицю 4.

 

2.3 Число 483 (в десятковій системі числення) перевести в шістнадцяткову систему числення

48310 → ?16

Виконання: Розбиваємо двійкове число на тетради (четвірки) справа наліво і кожну тетраду зображаємо відповідною шістнадцятковою цифрою (див. таблицю 1):

 

48310 → 1111000112

0001.1110.0011 → 1E316

 

Перевірка:

1E316 → 1·162 + 14·161 + 3·160 = 256 + 224 + 3 = 48310

 

Відповідь: 48310 → 1E316 – цей результат необхідно вписати в таблицю 4.

 

2.4 Число 483 (в десятковій системі числення) перевести в двійково-десяткову систему числення

48310 → ?2-10

Виконання: Кожну десяткову цифру зображаємо відповідною двійковою тетрадою (див. таблицю 1):

4           8          3

0100     1000 0011

Відповідь: 48310 → 100100000112-10 – цей результат необхідно вписати в таблицю 4.

ТЕМА: Написання простої програми на мові Assembler.

МЕТА: Ознайомитись з процесом написання та відлагодження програми на мові Асемблер. Вивчити основи оформлення типових програм на асемблері.

 

1. Теоретичні відомості

1.1 Структура програми на мові асемблер

При вивченні будь-якої мови програмування традиційним є написання програми, що виводить на екран повідомлення Hello, world!. Текст такої програми на мові Assembler:

 

MODEL TINY

STACK 256

DATASEG

Hellostr DB 'Hello, world!'

CODESEG

start:

mov ax,@data

mov ds,ax

mov bx,1

mov cx,21

mov dx,offset Hellostr

mov ah,40h

int 21h

mov ah, 04Ch

int 21h

end start

 

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

Зверніть увагу на DATASEG і CODESEG. У програмі є місце, де зберігаються дані і де зберігається код. І ці місця потрібно розділяти. Директива DATASEG вказує на те що далі будуть йти дані а директива CODESEG що тепер почнуться команди процесора. Це важливий поділ даних і команд процесора. Коли програма завантажується в пам'ять то операційній системі потрібно знати куди поставити вказівник для виконання команди. Саме директива CODESEG і вказує при складанні, де це місце буде знаходитись. Те ж саме і для DATASEG.

 

 

 

 

1.2 Переривання

В програмі ми використовували команду INT:

mov ah,40h

int 21h

mov ah, 04Ch

int 21h

Int - це скорочення слова Interrupt що на українську переводитися як переривання. Виглядає ця команда так:

 

Int номер

 

Тобто у кожного переривання є номер. І все таки, що це таке? З'явилося поняття переривання разом із створенням ЕОМ. Тоді стояло завдання про спільну роботу процесора і повільних зовнішніх пристроїв. Хорошим прикладом може служити клавіатура. Коли користувач натисне клавішу не відомо. Це може трапитися в будь-який момент, от коли він натискає клавішу процесору повідомляється що потрібно обробити цю дію і отримати код клавіші, яка натиснута.

Можна зробити висновок, що переривання народжують зовнішні пристрої. Але ми ж з Вами використовували переривання в програмі. Звичайно, крім отримання інформації від пристрою, цими пристроями потрібно ще й керувати. Пристрої повільні і крім іншого ще потрібно буде дочекатися закінчення виконання операції. Це теж реалізується за допомогою переривань тільки викликаємо їх ми з програми. Отже, переривання бувають двох типів:

         програмні;

         апаратні.

Пристроїв всяких багато - клавіатура, монітор, дисковод і так далі. Якщо не користуватися перериваннями, то операційна система повинна постійно опитувати пристрої, чи натиснута клавіша, чи потрібно вивести дані на монітор і так далі. Набагато простіше обумовити деякий механізм на який і буде звертатися увага операційної системи і процесора на необхідність проведення деяких дій.

Отже, давайте подивимося на все це в динаміці. Ваша програма щось виконує. У цей момент натискається клавіша. В програмі повинна бути перервана. І це буде зроблено, управління буде передано спеціальним кодом (процедура обробки переривання), а потім Ваша програма буде виконуватися далі.

Те ж саме коли ми викликаємо переривання для виведення символів на монітор (int 21h 04Ch) то самі генеруємо переривання. Навіщо? У цей момент може відбуватися зчитування з дисковода або вивід інших символів на екран. Через те що переривання можуть наступати одночасно, існує пріоритет їх обробки. Є переривання, які будуть виконуватися в будь-якому випадку навіть якщо йде обробка іншого переривання.

Процедура обробки переривання це програма. Питання в тому тільки де вона зберігається. Базова обробка переривання зберігається в BIOS і в самих мікросхемах. Але використовувати їх досить важко. Уявіть, для того, що б записати файл потрібно завести двигун дисковода, встановити головку в потрібному місці, дати команду перейти в той сектор прочитати таблицю файлів, перевірити чи там немає файлу і так далі. Всі ці завдання полегшує операційна система, яка надає Вам переривання більш високо рівня. Використовуючи ці переривання, Ви можете одним заходом створити файл, наприклад. Розрізняють переривання за номерами:

21h - переривання DOS

13h - переривання BIOS

От коли Ви викликаєте переривання (INT) Ви вказуєте ще й номер (21h) тобто, хто буде виконувати цю дію.

Знову повертаємося до нашого коду програми "HelloWord":

mov ah, 40h

int 21h

mov ah, 04Ch

int 21h

Як бачите одне і теж переривання викликаються два рази. У першому випадку виводиться рядок у другому закривається програма. Номер переривання один і той самий, так як же вдалося розібратися DOS що до чого? Справа в тому, що одне переривання може виконувати багато функцій. Наприклад виведення на екран і завершення програми. Просто викликати INT 21h мало ще потрібно вказати що Ви конкретно хочете зробити. Ось це вказується в регістрі AX. В регістрі AX вказується що потрібно зробити, тобто яку дію. До речі цей регістр ділитися на дві частини (AX: AH + AL).

 

mov ah, 40h ; функція виведення рядка

mov ah, 04Ch ; функція завершення програми

 

А тепер все разом:

mov ah, 40h ; будемо виводити рядок

int 21h ; вивести

mov ah, 04Ch ; будемо закривати програму

int 21h ; закрити

 

Функція є у всіх переривань 10,13,21 і так далі.

 

1.3 Регістри

Регістри це спеціальні комірки пам'яті. Це найголовніше. Вся їх перевага у тому, що звернення до регістрів проводитися значно швидше ніж до оперативної пам'яті ПК. Саме з цієї причини регістри використовуються для команд процесора. Звідти процесору зручно і швидко отримувати інформацію. Якщо говорити про ПК з типом процесорів 286 то розмір регістру 16 біт. Кожен регістр має ім'я і своє призначення. Вони бувають наступні за типами.

         регістри загального призначення: AX, BX, CX, DX, BP, SI, DI, SP

         cегментні регістри: CS, DS, SS, ES

         лічильник команд IP

         регістр прапорів Flags

Кожне ім'я регістра має певне значення:

A - accumulator акумулятор;

B - base база;

C - counter лічильник;

D - data дані;

BP - base pointer вказівник бази;

SI - source index індекс джерела;

DI - destination index індекс приймача;

SP - stack pointer вказівник стека;

CS - code segment сегмент команд;

DS - data segment сегмент даних;

SS - stack segment сегмент стека;

ES - extra segment додатковий сегмент;

IP - instruction pointer лічильник команд.

 

Регістри AX, BX, CX і DX дозволяють нам звертатися не до регістру а до старшого і молодшого байта:

AX: AH, AL

BX: BH, DL

DX: DH, DL

CX: CH, CL

 

В програмі HelloWord ми використовували AX для задання функції переривання:

 

mov ah, 40h

int 21h

mov ah, 04Ch

int 21h

 

При цьому використовували тільки частину регістра, а точніше старший байт:

         H high старший

         L low молодший

Чому ми не використовували, скажімо, комірки пам'яті? Тому що є правила, де що повинно зберігатися при виклику переривання. Точніше що і в якому регістрі повинно знаходитися. Правила, ці описані в документації. Ну, наприклад наша остання функція описана так:

Int 21H Функція 4CH

AH = 4CH

AL = код повернення

Повернення немає.

 

Припиняє процес і передає операційній системі код повернення.

На даний момент Ви повинні розуміти що для більшості операцій використовуються регістри, а для переривань конкретно є специфікації, в яких написано що і в якому регістрі повинно знаходиться.

 

 

 

 

1.4 Команда MOV

Нам кілька разів зустрілася команда MOV:

mov bx, 1

mov cx, 21

mov dx, offset Hellostr

mov ah, 40h

 

Сенс команди MOV полягає в приміщенні з одного місця в інше:

 

MOV одержувач, передавач

Дивимося:

mov bx, 1 ; помістити в bx 1

mov cx, 21 ; помістити в cx 21

mov dx, offset Hellostr ; помістити в dx зміщення рядка

mov ah, 40h ; помістити в ah число 40h

Для вказівки сегменту даних використовується регістр DS. Тобто цей регістр повинен вказувати на початок даних в нашій програмі або на сегмент DATASEG. В програмі було так:

mov ax, @ data

mov ds, ax

По-перше, що таке @ data - це ідентифікатор DATASEG при компіляції і збірки програми на місце цього слова буде поставлено реальне зміщення сегмента, в якому знаходяться дані. Адже до збірки програми ми цього не знаємо. І реально це число стане відоме тільки при складанні.

А чому ми поміщаємо спочатку в регістр AX? Те ж все просто є правило - Ви не можете безпосередньо змінювати вміст регістрів CS, DS, SS. Тобто ми не можемо написати так:

mov ds, 12345

Ми можемо змінити цей регістр тільки використовуючи інші регістри. Отже, все разом:

mov ax, @ data ; в регістр помістити AX зміщення для даних

mov ds, ax ; встановити регістр DS рівний AX тобто тепер там зсув.

Отже, у нас є регістр DS, в який ми не можемо безпосередньо отримати доступ, а тільки використовуючи інші регістри. Цей регістр вказує на дані.

Отже, ми знаємо, що регістр DS вказує на сегмент даних, але ж даних може бути багато, наприклад, багато рядків, ось для цього і використовується адресація сегменту - зміщення. Взагалі подібна адресація була створена у зв'язку з необхідністю адресації до більшого діапазону ніж дозволяє число 16 біт. У нашому випадку нам потрібно ще й отримати вказівник на дані. І ми це робили.

DATASEG

Hellostr DB 'Hello world!

CODESEG

start:

......

mov dx, offset Hellostr

Ми використовували offset - ця команда вміє обчислювати те як зміщені певні дані щодо сегмента даних. У нашому випадку рядок. При компонуванні програми в це місце буде поставлено конкретне число.

А ось комбінація DS - зміщення (ці числа) будуть вказувати нам на місце розташування рядка. Після того як регістр DS виставлений нас більше нічого не хвилює. Ми просто вказуємо щодо нього зміщення у даному випадку (001С) і отримуємо першу літеру нашого рядка. Ось саме з'ясуванням цього числа і займається offset.

Отже, у наших даних є поняття зсув, яке вираховується щодо регістра DS. Для цього є команда offset:

Offset ім'я_змінної

 

1.5 Процес асемблювання та компонування програми з використанням TASM (NASM)

 

Рисунок 1 - Алгоритм створення програми в системі Turbo Assembler

 

На рис.1 зображено повний цикл створення програми в системі Turbo Assembler, який включає ряд етапів:

1.      Редагування. Можна виконувати з допомогою будь-якого текстового редактора, який записує документ в ASCII вигляді. Текст програми потрібно зберегти у файлі з розширенням .ASM. При цьому слід звернути увагу на кодування кириличних символів, яке використовує даний редактор. Адже програма для ОС DOS використовує кодування відмінне від того, що використовується ОС Windows, і тому всі повідомлення можуть вивестися ієрогліфами.

2.      Асемблювання. На даному етапі текст програми перетворюється в проміжну форму, яка називається об'єктним модулем. Для асемблювання необхідно в командній стрічці DOS ввести команду:

 

TASM filename.asm

 

Програму буде асембльовано у файл filename.obj. На екрані буде повідомлення, подібне до наступного:

Turbo Assembler Version 4.1 Copyright (c) 1988, 1996 Borland International

Assembling file: filename.asm

Error messages: None

Warning messages: None

Passes: 1

Remaining memory: 417k

 

Якщо текст програми введено правильно, то не повинно бути ніяких повідомлень про помилки чи попередження. Якщо такі повідомлення отримуються, то вони зявляються на екрані разом з номерами помилкових стрічок програми. Необхідно перевірити текст та внести необхідні виправлення. Обєктний файл не створюється, якщо компілятор виявив помилки в програмі.

3.      Компонування. Один чи кілька обєктних модулів, а також бібліотеки звязуються в один виконуваний файл. На цьому етапі створення програм використовується компонувальник TLINK. Необхідно ввести команду:

 

TLINK filename.obj

 

В результаті виконання цієї дії буде створено файл filename.exe для запуску і виконання якого потрібно набрати в командній стрічці:

 

filename.exe

ТЕМА: Зчитування клавіатури. Команди порівняння. Умовні та безумовні переходи.

МЕТА: Вивчення можливостей зчитування клавіатури, використання команд порівняння і умовних та безумовних переходів в мові програмування Асемблер.

 

1. Теоретичні відомості

1.1 Приклад програми зчитування символів з клавіатури

 

MODEL SMALL

STACK 100h       

DATASEG

     Question DB 'ARE YOU A STUDENT? - [Y/N] $'

     GreetingStudent DB ' HELLO STUDENT!$'

     GreetingTeacher DB ' HELLO TEACHER!$'

CODESEG                     

START:   

mov   ax,@Data

mov   ds,ax                ; встановити регістр DS так, щоб він вказував на сегмент даних

mov   dx,OFFSET Question             ; посилання на повідомлення-запитання

mov   ah,09h            ; функція DOS виводу стрічки

int      21h                 

mov   ah,01h             ; отримати дані вводу одного символа

int      21h

cmp   al,'Y'                ; велика буква Y

jz       IsStudent          ; так, студент

cmp   al,'y'                 ; маленька буква y

jnz    IsTeacher        ; ні, не студент

IsStudent:

mov   dx,OFFSET GreetingStudent ; вказує на привітання "HELLO STUDENT"

jmp    DisplayGreeting

IsTeacher:

mov   dx,OFFSET GreetingTeacher ; вказує на привітання "HELLO TEACHER"

DisplayGreeting:

mov   ah,09h            ; функція DOS виводу повідомлення

int      21h                  ; вивести відповідне повідомлення

mov   ah,04ch                       ; функція DOS завершення програми

int      21h                  ; завершити програму

END START

Таким чином ми додали в програму два дуже важливих нових інструменти: можливість введення і прийняття рішень. Ця програма запитує у вас, чи ви студент, зчитуючи відповідь (один символ) з клавіатури. Якщо такою відповіддю буде буква Y у верхньому або нижньому регістрі (що означає відповідь ТАК), то програма виводить повідомлення "Привіт студент!", в іншому випадку виводиться повідомлення "Привіт викладач!". У даній програмі є всі основні елементи корисної програми: введення інформації зовнішнього середовища, обробка даних і прийняття рішення.

 

1.2 Функція отримання символу (INT 21h 01H)

Ми з Вами вже виводили символи. Але їх можна отримувати. Для цього нам потрібно знати функцію.  Це функція 01h:

 

 AH = 01H     

 Вивід символу

 AL = 8 бітний код числа

 

1.3 Функція 09h виведення рядків

В лабораторній роботі №2 в програмі "HelloWord" ми вже виводили рядок символів на екран монітора, правда було трохи незручно в плані того, що потрібно вказувати кількість символів в рядку. Існує інша функція:

 

 Int 21H функція 09H

 AH = 09H

 DS: DX сегмент зсув.

 

 Вся користь цієї функції полягає в тому, що не потрібно вказувати кількість символів.  Але для цього рядок потрібно правильно оформити.  В кінці рядка потрібно поставити символ $ це якраз і служить обмежувачем рядка.  Наприклад:

 

 MODEL TINY

 STACK 100h           

 DATASEG

           Hellostr DB 'Hello First Step Site $'

CODESEG              

 start:

           mov ax, @ data

           mov ds, ax

           mov dx, offset Hellostr              

           mov ah, 09h

           int 21h

           mov ah, 04Ch

           int 21h

 end start      

 

1.3 Регістр прапорів

Фізичний зміст регістра прапорів- такий же, як і у інших регістрів (32 біта).

Eflags        Flags

А от призначення - спеціальне. У цей регістр не можна просто записати значення командою MOV. У нього навіть немає імені. Він змінюється по-іншому. І він сильно відрізняється від першого спеціального регістра - EIP (вказівника поточної інструкції).

Регістри загального призначення програміст може сприймати як сховище:

·         Числа (адреси або значення).

·         Чисел (по тетрадам, байтам, словам тощо).

·         Бітової інформації.

Регістр EIP (або IP) можна вважати лише як сховище числа, і воно завжди означає адресу.

А ось регістр прапорів – тільки як сховище бітової інформації!

Зрозуміло, що з точки зору комп'ютера будь-яка інформація бітова.  Однак з точки зору програміста, навіть низькорівневого, тобто числа, які можна ділити, множити і проводити з ними складні обчислення. У цих числах важливіше ціле, ніж кожен розряд окремо.

І є такі байти, кожен біт яких означає самостійне явище. Стан процесора, результат порівняння і багато іншого. Звичайно, ці байти теж можуть виглядати як hex число, але воно, навпаки, в цілому вигляді не має явного сенсу.

Регістр прапорів - це якраз 32 біта, важливих окремо один від одного.

 

15

Регістри-індекси і вказівники

0

SP

Вказівник стека

BP

Вказівник бази

SI

Індексний регістр джерела

DI

Індексний регістр призначення

 

 

15

Сегментні регістри

0

CS

Регістр сегмента кода

DS

Регістр сегмента даних

SS

Регістр сегмента стека

ES

Регістр додаткового сегмента

 

 

 

15

Вказівник команд і прапори

0

 

 

IP

Регістр сегмента кода

 

 

 

 

 

 

Прапори

 

 

 

 

O

D

I

T

S

Z

 

A

 

P

 

C

 

15

 

 

 

11

10

9

8

7

6

 

4

 

2

 

0

 

                                                                         

За винятком одного парного прапора кожен біт обізвали прапором.

У всіх прапорів є імена.

Регістр прапорів містить дев'ять прапорів:

СF - прапор переносу,

PF - прапор парності (паритету),

AF - прапор додаткового перенесення

ZF - прапор нуля

SF - прапор знака

TF - прапор пастки

IF - прапор дозволу переривання

DF - прапор напрямку

OF - прапор переповнення

Прапор перенесення - індикує перенесення одиниці із старшого розряду або позичання одиниці цим розрядом при арифметичних операціях над 8 - і 16-розрядними числами. За наявності переносу або позичання прапор перенесення встановлюється в стан логічної одиниці. Цей прапор робить можливою багатобайтову і багатослівну арифметику. Команди циклічного зсуву можуть змінювати значення прапора переносу. Є команди безпосередньої установки (STC) і скидання (CLC) прапора переносу.

Прапор парності (паритету) - індикує парне число одиниць у 8-розрядному числі або в молодшому байті 16-розрядного. Цей прапор корисний при тестуванні пам'яті і при контролі правильності передачі даних.

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

Прапор нуля - отримує значення логічної одиниці при утворенні всіх нульових бітів у байті або в слові.

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

Прапор пастки - використовується для реалізації покрокового режиму роботи. При встановленому прапорі Т мікропроцесор виробляє сигнал внутрішнього переривання після виконання кожної команди.

Прапор дозволу переривання - використовується для дозволу або заборони зовнішнього переривання, що надходить по лінії INTR. На немасковані зовнішні переривання і на програмні переривання прапор не впливає. Є команди безпосередньої установки (STI) і скидання (CLI) прапора переривання.

Прапор напрямку - використовується зазвичай разом із строковими командами. При значенні логічної одиниці прапора зміна адрес у цих командах здійснюється від старших до молодших, при значенні логічного нуля – від молодших до старших. Команда STD встановлює прапор напрямку в одиничне значення, а команда CLD - у нульове.

В даному прикладі для нас цікаві далеко не всі прапори.

Найголовніші для програміста біт-прапори.

ZF (Zero Flag - прапор нуля)

Розповідати про цей біт, перераховуючи всі команди, які його змінюють або дивляться, - марно, так як це буде половина команд Асемблера. Прапор нуля вмикається, коли в результаті дії команди змінна обнуляється, і вимикається, коли змінна приймає значення відмінне від 0. Але так роблять не всі команди (mov не змінює прапори, арифметичні і деякі інші команди - змінюють прапори).

Найцікавіше, що прапор нуля перемикається при порівнянні змінних.

 

1.4 Команда CMP

Команда CMP  походить від англ.  слова compare – порівнювати.

Формат: CMP (операнд призначення), (операнд-джерело)

Команда СМР (порівняння) віднімає операнд-джерело з операнда призначення, не змінюючи при цьому значення операндів. Операнди можуть бути байтовими або двобайтовими числами. Хоча значення операндів не змінюються, значення прапорів оновлюються, що може бути враховано в наступних командах умовного переходу. Команда CMP впливає на прапори AF, CF, OF, PF, SF і ZF. При збігу значень операндів встановлюється прапор ZF. Прапор перенесення встановлюється, якщо операнд призначення менший ніж операнд-джерело.

В нашому прикладі ми зіткнулися з тим випадком, коли для нас важливий лише прапор ZF.

Насправді все елементарно. Наприклад, щодо прапора нуля (ZF) дія команди виглядає так:  MOV AH, 10h; для наочності присвоїмо AH значення 10h

 CMP AH, 8  ; ZF = 0

 CMP AH, 10h          ; ZF = 1

 CMP AH, 0A0h       ; ZF = 0

 

1.5 Команда умовного переходу JNZ(JNE)

Команда умовного переходу JNZ (JNE) походить від англ. слів Jmp if Not Zero - стрибнути якщо не нуль.

Формат:

JNZ мітка

Дія Якщо ZF = 0, то дія схожа на JMP (зміна EIP на вказану адресу, тобто перехід куди сказали).

Якщо ZF = 1, то дія як у NOP (зміна EIP на адресу наступної команди, тобто нічого не відбувається).

JZ (JE) - команда протилежної дії.

 

1.6 Умовні та безумовні переходи

Безумовний перехід - це перехід, який виконується завжди. Безумовний перехід здійснюється за допомогою командиJMP. У цієї команди є один операнд, який може бути безпосередньою адресою (міткою), регістром або осередком пам'яті, яка містить адресу. Існують також «далекі» переходи - між сегментами, проте тут ми їх розглядати поки не будемо.

Мітка – це набір з символів латинського алфавіту, цифр та/або символу нижнього підкреслення («_») який починається з символу і закінчується двокрапкою («:»).

JMP   mitka               ; Перехід на мітку

JMP   bx                    ; Перехід за адресою в BX

JMP   word [bx]        ; Перехід за адресою, що міститься в пам'яті за адресою в BX

Умовний перехід здійснюється, якщо виконується певна умова, задана прапорами процесора (крім однієї команди, яка перевіряє CX на рівність нулю). Стан прапорів змінюється після виконання арифметичних, логічних і деяких інших команд. Якщо умова не виконується, то управління переходить до наступної команди.

Існує багато команд для різних умовних переходів. Також для деяких команд є синоніми (наприклад, JZ і JE - це одне й те саме). Для наочності всі команди умовних переходів наведені в таблиці:

 

Команда

Перехід, якщо

Умова переходу

JZ / JE

нуль або дорівнює

ZF = 1

JNZ / JNE

НЕ нуль або не дорівнює

ZF = 0

JC / JNAE / JB

є переповнення / не вище і не дорівнює / нижче

CF = 1

JNC / JAE / JNB

немає переповнювання / вище або дорівнює / не нижче

CF = 0

JP

число одиничних біт парне

PF = 1

JNP

число одиничних біт непарне

PF = 0

JS

знак дорівнює 1

SF = 1

JNS

знак дорівнює 0

SF = 0

JO

є переповнення

OF = 1

JNO

немає переповнювання

OF = 0

JA / JNBE

вище / не нижче і не дорівнює

CF = 0 і ZF = 0

JNA / JBE

не вище / нижче або дорівнює

CF = 1 або ZF = 1

JG / JNLE

більше / не менше й не дорівнює

ZF = 0 і SF = OF

JGE / JNL

більше або дорівнює / не менше

SF = OF

JL / JNGE

менше / не більше й не дорівнює

SF ≠ OF

JLE / JNG

менше або дорівнює / не більше

ZF = 1 або SF ≠ OF

JCXZ

вміст CX дорівнює нулю

CX = 0

 

 

У всіх цих команд один операнд - ім'я мітки для переходу. Зверніть увагу, що деякі команди застосовуються для беззнакових чисел, а інші - для чисел із знаком. Порівняння «вище» і «нижче» відносяться до беззнакових чисел, а «більше» і «менше» - до чисел із знаком. Для беззнакових чисел ознакою переповнення буде прапор CF, а відповідними командами переходу JC і JNC. Для чисел із знаком про переповнення можна судити за станом прапора OF, тому їм відповідають команди переходу JO і JNO. Команди переходів не змінюють значення прапорів.

ТЕМА: Арифметичні операції в мові програмування Assembler.

МЕТА: Вивчити основні команди мови програмування Асемблер для здійснення арифметичних операцій та навчитись використовувати їх в процесі написання програм.

 

1. Теоретичні відомості

1.1 Приклад програми

MODEL SMALL

STACK 100h     

DATASEG

   Set_X   DB       13,10,'X = $'

   Result   DB       13,10,'Y = $'

   error_ db "incorrect number$"

   buff    db 6,7 Dup(?)   

CODESEG                    

start:

   mov      ax,@Data

    mov                ds,ax             ; встановити регістр DS таким чином, щоб він вказував на сегмент даних

    mov                dx,OFFSET Set_X                    ; посилання на повідомлення-запитання

    mov                ah,09h                                    ; функція DOS виводу стрічки

    int       21h                 

;************************************************************************************

;********* Зчитування числа з клавіатури і перетворення його в цифрове значення ************

;********                                             Результат в регістрі AX                                  *********

;************************************************************************************

    mov    ah,0ah

    xor     di,di

    mov    dx,offset buff                            ; адреса буфера

    int       21h                                          ; зчитуємо стрічку

    mov    dl,0ah

    mov    ah,02

    int       21h                                          ; виводимо перевід стрічки

 ; обробляємо вміст буфера

    mov    si,offset buff+2                                     ; берем аддрес початку стрічки

    cmp    byte ptr [si],"-"                                    ; якщо перший символ мінус

    jnz      ii1

    mov    di,1                                          ; встановлюємо прапор

    inc      si                                              ; і пропускаєм його

ii1:

    xor     ax,ax

    mov    bx,10                                      

ii2:

    mov    cl,[si]                                       ; берем символ з буфера

    cmp    cl,0dh                                                  ; перевіряємо чи він не останній

    jz        endin

; якщо символ не останній, то перевіряємо його на правильність

    cmp    cl,'0'                                         ; якщо введений невірний символ <0

    jl         er

    cmp    cl,'9'                                         ; якщо введений невірний символ >9

    ja        er

    sub     cl,'0'                                         ; робимо з символу число

    mul     bx                                            ; перемножуємо на 10

    add     ax,cx                                        ; додаємо до інших

    inc      si                                              ; вказівник на наступний символ

    jmp     ii2                                            ; повторюємо

er:   ; якщо була помилка, то виводимо повідомлення про це і виходимо

    mov    dx, offset error_

    mov    ah,09

    int       21h

    int       20h

   ; все символи з буфера оброблені число знаходиться в ax

endin:

    cmp    di,1                                          ; якщо встановлений прапорець, то

    jnz      ii3

    neg     ax                                            ; робим число від’ємним

ii3:

;************************************************************************************

;**************** Завершення функції зчитування і розпізнавання числа ********************

;************************************************************************************

   add      ax,1

   mov      bx,2

   mul      bx

   xchg     cx,ax

 

   mov      dx,OFFSET Result       ; вказує на

   mov      ah,09h            ; функція DOS виводу повідомлення

   int        21h                  ; вивести відповідне повідомлення

   xchg     cx,ax

;************************************************************************************

;************* Початок функції перетворення числа в стрічку і виводу її на екран ************

;************************************************************************************

; Перевіряємо число на знак

   test      ax, ax

   jns       oi1

; Якщо воно від'ємне, виведемо мінус і залишимо його модуль.

   mov     cx, ax

   mov                 ah, 02h

   mov                 dl, '-'

   int        21h

   mov     ax, cx

   neg     ax

; Кількість цифр будемо тримати в CX.

oi1: 

    xor                 cx, cx

    mov     bx, 10             ; основа сс. 10 для десяткової і т.п.

oi2:

    xor                 dx,dx

    div     bx

; Ділимо число на основу сс. В залишку виходить остання цифра.

; Відразу виводити її неможна, тому збережемо її в стек.

    push    dx

    inc     cx

; А з остачею повторюємо те ж саме, відділяючи від нього чергову

; цифру справа, поки не залишиться нуль, що означає, що далі

; зліва лише нулі.

    test    ax, ax

    jnz     oi2

; Тепер приступимо до виводу.

    mov                ah, 02h

oi3:

    pop     dx

; Беремо чергову цифру, переводимо її в символ и виводимо.

    add                 dl, '0'

    int       21h

; Повторимо рівно стільки разів, скільки цифр нарахували.

    loop    oi3

;************************************************************************************

;************ Завершення функції перетворення числа в стрічку і виводу її на екран ***********

;************************************************************************************

    mov                ah,04ch                       ; функція DOS завершення програми

    int       21h                  ; завершити програму

end start

Дана програма зчитує число X з клавіатури, виконує обчислення за формулою: Y = (X+1)*2Після цього виводить результат обчислення – число Y на екран.

 

Таблиця 1 – Команди арифметичних операцій мови Асемблер

Додавання

ADD

Додавання байта або слова

ADC

Додавання байта або слова з розрядом переносу

INC

Збільшення байта або слова на 1

AAA

Корекція додавання неупакованих десяткових чисел

DAA

Корекція додавання упакованих десяткових чисел

Віднімання

SUB

Віднімання байта або слова

SBB

Віднімання байта або слова з розрядом переносу

DEC

Зменшення байта або слова на 1

NEG

Інверсія байта або слова

CMP

Порівняння байта або слова

AAS

Корекція віднімання неупакованих десяткових чисел

DAS

Корекція віднімання упакованих десяткових чисел

Множення

MUL

Множення беззнакового байта або слова

IMUL

Цілочисельне множення байта або слова

AAM

Корекція множення неупакованих десяткових чисел

Ділення

DIV

Ділення беззнакового байта або слова

IDIV

Цілочисельне ділення байта або слова

AAD

Корекція ділення неупакованих десяткових чисел

CWB

Перетворення байта в слово

CWD

Перетворення слова в подвійне слово

 

 

1.2 Команди додавання

ADD (операнд призначення), (операнд-джерело)

Сума двох операндів, які можуть бути байтами або словами, поміщається в операнд призначення. Обидва операнда можуть бути знаковими або беззнаковими числами. Команда ADD змінює значення прапорів AF, CF, OF, PF, SF і ZF.

ADC (операнд призначення), (операнд-джерело)

Команда ADC (підсумовування з урахуванням розряду переносу) додає операнди, які можуть бути байтами або словами, і додає 1, якщо встановлено розряд переносу, результат поміщається в операнд призначення. Обидва операнда можуть бути знаковими або беззнаковими числами. Команда ADD змінює значення прапорів AF, CF, OF, PF, SF і ZF. Так як команда ADC враховує значення розряду переносу від попередньої операції, це може бути використано для організації додавання чисел довільної розрядності.

INC (операнд призначення)

Команда INC (інкремент) додає одиницю до операнду призначення. Операнд може бути байтом або словом і трактується як беззнакове двійкове число. Команда INC змінює значення прапорів AF, OF, PF, SF і ZF; значення прапора CF ця команда не змінює.

ААА

Команда ААА (корекція додавання неупакованих десяткових чисел) приводить вміст регістра AL до виду правильного неупакованого десяткового числа, старший напівбайт при цьому обнуляється. Команда ААА змінює значення прапорів FC і AC; вміст прапорів OF, PF, SF і ZF після виконання команди ААА невизначено.

DAA

Команда DAA (десяткова корекція додавання) приводить вміст регістра AL до виду правильного упакованого десяткового числа після попередньої команди додавання. Команда DAA змінює значення прапорів AF, CF, PF, SF і ZF; вміст прапора OF після виконання команди DAA не визначено.

Для додавання двох чисел призначена команда ADD.  Вона працює як з числами зі знаком, так і з числами без знаку (це особливість додаткового коду).

Операнди повинні мати однаковий розмір (не можна складати 16 - і 8-бітове значення).  Результат поміщається на місце першого операнда. Загалом, ці правила справедливі для більшості команд.

Після виконання команди змінюються прапори, за якими можна визначити характеристики результату:

Прапор CF встановлюється, якщо при додаванні відбулося перенесення із старшого розряду.  Для беззнакових чисел це означатиме, що сталося переповнення і результат вийшов некоректним.

Прапор OF позначає переповнення для чисел із знаком.

Прапор SF дорівнює знакової биту результату (природно, для чисел зі знаком, а для беззнакових він дорівнює старшому биту і особливо сенсу не має).

Прапор ZF встановлюється, якщо результат дорівнює 0.

Прапор PF - ознака парності, дорівнює 1, якщо результат містить непарне число одиниць.

Приклади:

add ax, 5       ; AX = AX + 5

add dx, cx      ; DX = DX + CX

 add dx, cl     ; Помилка: різний розмір операндів.

 

1.3 Команди віднімання

SUB (операнд призначення), (операнд-джерело)

Вміст операнда-джерела віднімається з вмісту операнда призначення, і результат поміщається в операнд призначення.Операнди можуть бути знаковими або беззнаковими, двійковими або десятковими (див. команди AAS і DAS), однобайтовими або двобайтовими числами. Команда SUB змінює значення прапорів AF, CF, OF, PF, SF і ZF.

SBB (операнд призначення), (операнд-джерело)

Команда SBB (віднімання з урахуванням позичання) віднімає вміст операнда-джерела від вмісту операнда призначення, потім віднімає від результату 1, якщо був встановлений прапор перенесення CF. Результат поміщається на місце операнда призначення.

Операнди можуть бути знаковими або беззнаковими, двійковими або десятковими (див. команди AAS і DAS), однобайтовими або двобайтовими числами. Команда SBB змінює значення прапорів AF, CF, OF, PF, SF і ZF. Команда SBB може бути використана для організації віднімання мультибайтних чисел.

DEC (операнд призначення)

Команда DEC (декремент) віднімає одиницю з операнда призначення, який може бути одно-або двобайтовим. Команда DEC змінює вміст прапорів AF, OF, PF, SF і ZF. Вміст прапора CF при цьому не змінюється.

NEG (операнд призначення)

Команда NEG (інверсія) віднімає операнд призначення, який може бути байтом або словом 0 і поміщає результат в операнд призначення. Така форма двійкового доповнення числа придатна для інверсії знакових цілих чисел. Якщо операнд нульовий, його знак не змінюється. Спроба застосувати команду NEG до байтового числа - 128 або до двобайтового числа - 32 768 не призводить до зміни значення операнда, але встановлює прапор OF. Команда NEG впливає на прапори AF, CF, OF, PF, SF і ZF. Прапор CF завжди встановлений за винятком випадку, коли операнд дорівнює нулю, коли цей прапор скинутий.

CMP (операнд призначення), (операнд-джерело)

Команда СМР (порівняння) віднімає операнд-джерело з операнда призначення, не змінюючи при цьому значення операндів. Операнди можуть бути байтовими або двобайтовими числами. Хоча значення операндів на змінюються, значення прапорів оновлюються, що може бути враховано в наступних командах умовного переходу. Команда CMP впливає на прапори AF, CF, OF, PF, SF і ZF. При збігу значень операндів зводиться прапор ZF. Прапор перенесення зводиться, якщо операнд призначення менший за операнда-джерела.

AAS

Команда AAS (корекція віднімання неупакованих десяткових чисел) коригує результат попереднього вирахування двох правильних упакованих десяткових чисел. Операндом призначення в команді віднімання повинен бути регістр AL. Команда AAS призводить значення в AL до виду правильного неупакованого десяткового числа; старший напівбайт при цьому обнуляється. AAS впливає на прапори AF і CF; Значення прапорів OF, PF, SF і ZF після виконання команди AAS невизначено.

DAS

Команда DAS (десяткова корекція вирахування) коригує результат попереднього вирахування двох правильних упакованих десяткових чисел. Операндом призначення в команді віднімання повинен бути регістр AL. Команда DAS призводить значення в AL до виду двох правильних упакованих десяткових чисел. Команда DAS впливає на прапори AF і CF. Значення прапорів OF, PF, SF і ZF після виконання команди DAS невизначено.

 

Віднімання виконується за допомогою команди SUB. Результат також поміщається на місце першого операнда і знову ж виставляються прапори. Єдина різниця в тому, що відбувається віднімання, а не додавання.

Насправді віднімання в процесорі реалізовано за допомогою додавання.  Процесор змінює знак другого операнда на протилежний, а потім складає два числа. Якщо вам необхідно в програмі поміняти знак числа на протилежний, можна використовувати команду NEG.  У цієї команди всього один операнд.

Приклади:

sub si, dx         ; SI = SI - DX

neg ax              ; AX =-AX

Дуже часто в програмах використовується операція додавання або віднімання одиниці.  Додаток одиниці називається інкрементом, а віднімання - декрементом.  Для цих операцій існують спеціальні команди процесора: INC і DEC .  Зверніть увагу, що ці команди не змінюють значення прапора CF.

 

1.4 Команди множення

MUL (операнд-джерело)

Команда MUL (множення) виконує беззнакове множення операнда-джерела і вмісту акумулятора. Якщо операнд-джерело однобайтовий, здійснюється множення на вміст регістра AL, а двобайтовий результат повертається регістрах AH і AL. Якщо операнд-джерело двобайтовий, здійснюється множення на вміст регістра AX, а чотирьохбайтовий результат повертається в парі регістрів DX і AX.

Операнди розглядаються як беззнакові двійкові числа. Якщо старша половина результату (регістр AH при однобайтовому множенні і DX при двобайтовому множенні) встановлюються прапори CF і OF, в іншому випадку ці прапори скидаються.

Якщо після виконання множення встановлені прапори CF і OF, це говорить про наявність значущих цифр результату в регістрі AH або DX. Вміст прапорів AF, PF SF і ZF після виконання команди множення невизначено.

IMUL (операнд-джерело)

Команда IMUL (цілочисельне множення) виконує знакове множення операнда-джерела і вмісту акумулятора. Якщо операнд-джерело однобайтовий, здійснюється множення на вміст регістра AL, а двобайтовий результат повертається в регістрах AH і AL. Якщо операнд-джерело двобайтовий, здійснюється множення на вміст регістра AX, а чотирьохбайтовий результат повертається в парі регістрів DX і AX. Операнди розглядаються як беззнакові двійкові числа. Якщо старша половина результату (регістр AH при однобайтовому множенні і DX при двобайтовому множенні) встановлюються прапори CF і OF, в іншому випадку ці прапори додаються. Якщо після виконання множення встановлені прапори CF і OF, це говорить про наявність значущих цифр результату в регістрі AH або DX. Вміст прапорів AF, PF SF і ZF після виконання команди цілочисельного множення невизначено.

ААМ

Команда ААМ (корекція множення неупакованих десяткових чисел) призводить результат попереднього множення до двох правильно упакованих десяткових чисел. Для отримання правильного результату після виконання корекції старші напівбайти операндів які перемножуються повинні бути нульовими, а молодші повинні бути правильними двійково-десятковими цифрами.

Команда ААМ впливає на прапори PF, SF і ZF. Вміст прапорів AF, CF і OF після виконання команди ААМ невизначено.

Множення чисел без знаку

Для множення чисел без знаку призначена команда MUL. У цієї команди тільки один операнд - другий множник, який повинен знаходитися в регістрі або в пам'яті. Розташування першого множника і результату задається неявно і залежить від розміру операнда:

Розмір операнда

Множник

Результат

Байт

AL

AX

Слово

AX

DX: AX

 

Відмінність множення від додавання і віднімання в тому, що розрядність результату виходить в 2 рази більшою, ніж розрядність співмножників. Також і в десятковій системі - наприклад, перемножуючи двозначне число на двозначне, ми можемо отримати в результаті максимум чотиризначне. Запис «DX: AX» означає, що старше слово результату буде знаходитися в DX, а молодше - в AX. Приклади:

mul bl; AX = AL * BL
mul ax; DX: AX = AX * AX 

Якщо старша частина результату дорівнює нулю, то прапори CF і ОF будуть мати нульове значення. У цьому випадку старшу частину результату можна відкинути. Це властивість можна використовувати в програмі, якщо результат повинен бути такого ж розміру, як множники.

Множення чисел із знаком

Для множення чисел зі знаком призначена команда IMUL . Ця команда має три форми, що розрізняються кількістю операндів:

·         З одним операндом - форма, аналогічна команді MUL. Як операнд вказується множник. Розташування іншого множника і результату визначається за таблицею.

·         З двома операндами - вказуються два множника. Результат записується на місце першого множника. Старша частина результату в цьому випадку ігнорується. До речі, ця форма команди не працює з операндами розміром 1 байт.

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

Приклади:

imul cl; AX = AL * CL
imul si; DX: AX = AX * SI
imul bx, ax; BX = BX * AX
imul cx, - 5; CX = CX * (-5)

CF = OF = 0, якщо твір поміщається в молодшій половині результату, інакше CF = OF = 1. Для другої і третьої форми команди CF = OF = 1 означає, що сталося переповнення.

 

1.5 Команди ділення

DIV (операнд-джерело)

Команда DIV (ділення) виконує беззнакове ділення вмісту акумулятора (і його розширення) на операнд-джерело. Якщо операнд-джерело однобайтовий, здійснюється ділення двобайтового діленого, розташованого в регістрах AH і AL.Однобайтове ділене виходить в регістрі AL, а однобайтовий залишок - в регістрі AH. Якщо операнд-джерело двобайтовий, здійснюється розподіл чотирьохбайтового діленого, розташованого в регістрах DX і AX. Двухбайтове ділене при цьому виходить в регістр AX, а двобайтовий залишок - в регістрі DX. Якщо значення діленого перевищує розрядність акумулятора (0FFh для однобайтового ділення і 0FFFFh - для двобайтового) або виконується спроба поділу на нуль, генерується переривання типу 0, а ділене і залишок залишаються невизначеними. Вміст прапорів AF, CF, OF, PF, SF і ZF після виконання команди DIV невизначено.

IDIV (операнд-джерело)

Команда IDIV (цілочисельне ділення) виконує знакове ділення вмісту акумулятора (і його розширення) на операнд-джерело. Якщо операнд-джерело однобайтовий, здійснюється ділення двухбайтового діленого, розташованого в регістрах AH і AL. Однобайтове ділене розміщується в регістрі AL, а однобайтовий залишок - в регістрі AH. Для бйтового цілочисельного ділення позитивне ділене не може бути більше значення +127 (7Fh), а негативне не може бути меншим за -127 (81h). Якщо операнд-джерело двобайтовий, здійснюється ділення чотирьохбайтового діленого, розташованого в регістрах DX і AX. Двухбайтовое ділене при цьому розміщується в регістрі AX, а двобайтовий залишок - в регістрі DX. Для двобайтового цілочисельного ділення позитивне ділене не може бути більше значення +32767 (7FFFh), а негативне не може бути меншим за значення -32767 (8001H). Якщо ділене позитивне і перевищує максимум або негативне і менше мінімуму, генерується переривання типу 0, а ділене і залишок залишаються невизначеними. Окремим випадком такої події є спроба поділу на нуль. Вміст прапорів AF, CF, OF, PF, SF і ZF після виконання команди IDIV невизначено.

AAD

Команда AAD (корекція ділення неупакованих десяткових чисел) модифікує вміст регістра AL перед виконанням ділення так, щоб при виконанні ділення в діленому вийшло правильне неупаковане десяткове число. Для отримання правильного результату після виконання ділення вміст регістра AH має бути нульовим. Команда ААD впливає на прапори PF, SF і ZF. Вміст прапорів AF, CF і OF після виконання команди ААD невизначено.

CBW

Команда CBW (перетворення байта в слово) розширює знак байта в регістрі AL на весь регістр АХ. Команда CBW не вплине на прапори. Команда CBW може бути використана для отримання двохбайтового діленого з однобайтового перед виконанням команди поділу.

CWD

Команда CWD (перетворення слова в подвійне слово) розширює знак слова в регістрі AХ на пару регістрів АХ і DX.CWD Команда не впливає на прапори. Команда CWD може бути використана для отримання чотирьохбайтового діленого з двухбайтового перед виконанням команди поділу.

 

 

Ділення чисел без знаку

Ділення цілих двійкових чисел - це завжди ділення з залишком! За аналогією з множенням, розмір дільника, діленого та залишку повинен бути в 2 рази менше розміру діленого. Ділення чисел без знаку здійснюється за допомогою команди DIV . У цієї команди один операнд - дільник, який повинен знаходитися в регістрі або в пам'яті. Розташування діленого, дільника та залишку задається неявно і залежить від розміру операнда:

Розмір операнда 
(Дільника)

Ділене

Приватна

Залишок

Байт

AX

AL

AH

Слово

DX: AX

AX

DX

 

При виконанні команди DIV може виникнути переривання:

·         якщо дільник дорівнює нулю;

·         якщо ділене не поміщається у відведену під нього розрядну сітку (наприклад, якщо при розподілі слова на байтдільник більше 255).

Приклади:

div cl; AL = AX / CL, залишок у AH
div di; AX = DX: AX / DI, залишок в DX 

Ділення чисел із знаком

Для ділення чисел зі знаком призначена команда IDIV. Єдиним операндом є дільник. Розташування діленого і приватного визначається також, як для команди DIV. Ця команда теж генерує переривання при діленні на нуль чи занадто великому приватному.

1.6 Команда XCHG

Однією із  задач в мові програмування Асемблер може бути задача поміняти місцями значення в регістрах. Вирішити це завдання можна використовуючи оператори MOV та додаткову комірку пам'яті, ось так:

 

 mov ax, 20

 mov cx, 30

         

 mov bx, ax

 mov ax, cx

 mov cx, bx

 

Виходить, що нам потрібно три команди.  Команда XCHG дозволяє зробити обмін однією командою. Обмін може проводиться між регістрами або регістрами і пам'яттю.

Наприклад:

      mov ax, 20

      mov cx, 30

      XCHG ax, cx; поміняти місцями

Наявність цієї команди процесора дуже зручно, оскільки дозволяє оптимізувати операції пов'язані з необхідністю великої кількості замін. Наприклад, при сортуванні.  Реально зменшується і розмір програми.

ТЕМА: Використання змінних, стеку та підпрограм в мові програмування Асемблер.

МЕТА: Вивчити правила оголошення та використання змінних в мові програмування Асемблер та навчитись застосовувати стек та підпрограми.

 

1. Теоретичні відомості

1.1 Змінні в програмі на асемблері

Змінна - це місце в пам'яті яке має ім'я і тип. Створюючи програму на асемблері нам потрібно буде визначати наші дані. Дані зазвичай визначаються в сегменті даних. TASM підтримує ряд директив які допоможуть нам виділити іменоване місце під змінні. Наприклад:

db - 1 байт

dw - 2 байти

dd - 4 байти.

 

MODEL SMALL

STACK 256

DATASEG

Data1 DB 31h ; виділити один байт з вмістом 31h

CODESEG

Start:

mov ax, @ data ; установка в ds адpеси

mov ds, ax ; сегменту даних

mov ax, OFFSET Data1 ; де знаходитися змінна

Exit:

mov ah, 04Ch ; функція DOS виходу з програми

mov al, 0h ; код повернення

int 21h ; Виклик DOS зупинка програми

End Start

 

Так само цю змінну можна поміщати в регістр ось так наприклад:

Start:

mov ax, @ data ; установка в ds адpеса

mov ds, ax ; сегменту даних

mov ax, OFFSET Data1 ; де знаходитися змінна

mov al, Data1 ; поміщаємо в регістр AX (AL)

Exit:

 

Можна і зворотну операцію здійснити - помістити дані з регістра в змінну:

Start:

mov ax, @ data ; встановлення в ds адpеси

mov ds, ax ; сегменту даних

mov ax, OFFSET Data1 ; де знаходитися змінна

mov al, Data1 ; поміщаємо в регістр AX (AL)

inc al ; збільшити на одиницю

mov Data1, al ; в пам'ять

Exit:

 

 

1.2 Стек

Стек - це спеціальна область пам'яті. Адресацією в цій області управляє регістр SP або вказівник стека. Використовується ця пам'ять в основному для тимчасового зберігання вмісту регістрів. Саме тимчасового. Найголовніше зрозуміти, як стек працює. А працює він за принципом перший прийшов останній пішов - LIFO (Last In - First Out або останнім прийшов - першим пішов). Давайте уявимо. Хтось ставить книгу на стіл. Потім зверху ще одну книгу. Перша книга внизу. Що б витягти мені спочатку потрібно зняти верхню і тільки після цього я отримаю доступ до першої.

Зазвичай стек використовується для збереження адрес повернення і передачі аргументів під час виклику процедур (про процедури піде мова далі), також у ньому виділяється пам'ять для локальних змінних. Крім того, в стеку можна тимчасово зберігати значення регістрів.

Стек є невід'ємною частиною архітектури процесора і підтримується на апаратному рівні: у процесорі є спеціальні регістри (SS, BP, SP) і команди для роботи зі стеком.

Схема організації стека в процесорі 8086 показана на малюнку:

Стек розташовується в оперативній пам'яті в сегменті стека, і тому адресується щодо сегментного регістра SS. Шириною стека називається розмір елементів, які можна поміщати в нього або витягувати. У нашому випадку ширина стека дорівнює двом байтам або 16 бітам. Регістр SP (покажчик стека) містить адресу останнього доданого елемента. Ця адреса також називається вершиною стека. Протилежний кінець стека називається дном.

Дно стека знаходиться у верхніх адресах пам'яті. При додаванні нових елементів у стек значення регістра SP зменшується, тобто стек росте в бік молодших адрес.

Для стека існують всього дві основні операції:

         додавання елемента на вершину стека (PUSH);

         витягування елемента з вершини стека (POP);

 

 

 

 

 

 

1.2.1 Додавання елемента в стек командою PUSH

Виконується командою PUSH. У цієї команди один операнд, який може бути безпосереднім значенням, 16-бітним регістром (у тому числі сегмент) або 16-бітної змінної в пам'яті. Команда працює наступним чином:

1.      Значення в регістрі SP зменшується на 2 (так як ширина стека - 16 біт або 2 байти);

2.      Операнд поміщається в пам'ять за адресою в SP.

Приклади:

push -5 ;Помістити -5 в стек

push ax ;Помістити AX в стек

push ds ;Помістити DS в стек

push x ;Помістити x в стек (x обявлений як слово)

Існують ще 2 команди для додавання в стек. Команда PUSHF поміщає в стек вміст регістра прапорів. Команда PUSHA поміщає в стек вміст всіх регістрів загального призначення в наступному порядку: АХ, СХ, DX, ВХ, SP, BP, SI, DI (значення DI буде на вершині стека). Значення SP поміщається те, яке було до виконання команди. Обидві ці команди не мають операндів.

 

1.2.2 Витягування елемента з стека командою POP

Виконується командою POP. У цієї команди також є один операнд, який може бути 16-бітним регістром (у тому числі сегментним, але крім CS) або 16-бітною змінною в пам'яті. Команда працює наступним чином:

1.      Операнд читається з пам'яті за адресою в SP;

2.      Значення в регістрі SP збільшується на 2.

Зверніть увагу, що витягнутий зі стеку елемент не обнуляється і не затирається в пам'яті, а просто залишається як сміття. Він буде перезаписаний при поміщенні нового значення в стек.

Приклади:

pop cx ;Помістити значення з стека в CX

pop es ;Помістити значення з стека в ES

pop x ;Помістити значення з стека в змінну x

Відповідно, є ще 2 команди. POPF поміщає значення з вершини стека в регістр прапорів. POPA відновлює з стека всі регістри загального призначення (але при цьому значення для SP ігнорується).

 

 

1.3 Процедури

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

 

1.3.1 Команди CALL і RET

Для роботи з процедурами призначені команди CALL і RET. За допомогою команди CALL виконується виклик процедури. Ця команда працює майже так само, як команда безумовного переходу (JMP), але з однією відмінністю - одночасно в стек зберігається поточне значення регістра IP. Це дозволяє потім повернутися до того місця в коді, звідки була викликана процедура. В якості операнда вказується адреса переходу, яка може бути безпосереднім значенням (міткою), 16-розрядним регістром (крім сегментних) або коміркою пам'яті, яка містить адресу.

Повернення з процедури виконується командою RET. Ця команда відновлює значення з вершини стека в регістр IP. Таким чином, виконання програми триває з команди, яка є наступною відразу після команди CALL. Зазвичай код процедури закінчується цією командою. Команди CALL і RET не змінюють значення прапорів (крім деяких особливих випадків у захищеному режимі). Невеликий приклад різних способів виклику процедури:

 

mov ax,myproc

mov bx,myproc_addr

 

call myproc ;Виклик процедури (адреса перехода мітка myproc)

call ax ;Виклик процедури по адресі в AX

call [myproc_addr] ;Виклик процедури по адресі в змінній

 

mov ax,4C00h ;\

int 21h ;/ Завершення програми

 

;----------------------------------------------------------------------

;Процедура, яка нічого не виконує

myproc:

nop ;пуста команда

ret ;Вихід з процедури

;----------------------------------------------------------------------

myproc_addr dw myproc ;Змінна с адресом процедури

 

1.3.2 Близький та далекий виклик процедур

Існує два типи викликів процедур. Близьким називається виклик процедури, яка знаходиться в поточному сегменті коду. Далекий виклик - це виклик процедури в іншому сегменті. Відповідно існують 2 види команди RET - для ближнього і далекого повернення. Компілятор ТASM автоматично визначає потрібний тип машинної команди, тому в більшості випадків не потрібно про це турбуватися.

1.3.3 Передача параметрів

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

1.3.4 Значення, що повертається

Крім передачі параметрів часто потрібно отримати якесь значення з процедури. Наприклад, якщо процедура щось обчислює, хотілося б отримати результат обчислення. А якщо процедура щось робить, то корисно дізнатися, завершилася дія успішно чи виникла помилка. Існують різні способи повернення значення з процедури, але найбільш часто використовуваний - це помістити значення в один з регістрів. Зазвичай для цієї мети використовують регістри AL і AX. Хоча ви можете робити так, як вам більше подобається.

1.3.5 Збереження регістрів

Хорошим прийомом є збереження регістрів, які процедура змінює в ході свого виконання. Це дозволяє викликати процедуру з будь-якої частини коду і не турбуватися, що значення в регістрах будуть зіпсовані. Зазвичай регістри зберігаються в стеку за допомогою команди PUSH, а перед поверненням з процедури відновлюються командою POP. Природно, що відновлювати їх треба в зворотному порядку. Приблизно ось так:

 

myproc:

push bx ;Збереження регістрів

push cx

push si

... ;Код процедури

pop si ;Відновлення регістрів

pop cx

pop bx

ret ; Відновлення з процедури

ТЕМА: Команди роботи з бітами. Логічні операції в мові програмування Assembler.

МЕТА: Вивчити основні команди для роботи з бітами мови програмування Асемблер для здійснення логічних операцій та навчитись використовувати їх в процесі написання програм.

 

1. Теоретичні відомості

1.1 Вивід на екран числа в двійковій формі

Даний код здійснює вивід двобайтового числа в двійковій системі числення. Число, яке потрібно вивести, повинно бути в регістрі АХ.

mov bx,ax

mov cx,16

ob1:

shl bx,1

jc ob2

 

mov dl,'0'

jmp ob3

 

ob2:

mov dl,'1'

ob3:

mov ah,2

int 21h

loop ob1

 

1.2 Логічні операції

Таблиця 1 Команди мови Асемблер для роботи з бітами

Логічні операції

NOT

Інверсія байти або слова

AND

Операція "І" над байтами або словами

OR

Операція "АБО" над байтами або словами

XOR

Операція "ВИКЛЮЧАЮЧЕ АБО" над байтами або словами

TEST

Перевірка байта або слова

Команди зсуву

SHL / SAL

Логічний / арифметичний зсув вліво байта або слова

SHR

Логічний зсув вправо байта або слова

SAR

Арифметичний зсув вправо байта або слова

Команди циклічного зсуву

ROL

Циклічний зсув вліво байта або слова

ROR

Циклічний зсув вправо байта або слова

RCL

Циклічний зсув вліво байта або слова через розряд переносу

RCR

Циклічний зсув вправо байта або слова через розряд переносу

 

 

Логічні операції виконуються порозрядно, тобто окремо для кожного біта операндів. В результаті виконання змінюються прапори. У програмах ці операції часто використовуються для скидання, установки або інверсії окремих бітів двійкових чисел.

 

1.2.1 Логічне І

Якщо обидва біти рівні 1, то результат дорівнює 1, інакше результат дорівнює 0.

AND

0

1

0

0

0

1

0

1

 

 

Для виконання операції логічного І призначена команда AND. У цієї команди 2 операнда, результат поміщається на місце першого операнда. Часто ця команда використовується для обнулення певних бітів числа. При цьому другий операнд називають маскою. Обнуляються ті біти операнда, які в масці рівні 0, значення інших бітів зберігаються.

Приклади:

and ax, bx ; AX = AX & BX

and cl, 11111110b ; Обнулення молодшого біта CL

and dl, 00001111b ; Обнулення старшої тетради DL

 

Ще одне використання цієї команди - швидке обчислення залишку від ділення на ступінь 2. Наприклад, так можна обчислити залишок від ділення на 8:

and ax, 111b ; AX = залишок від ділення AX на 8

 

1.2.2 Логічне АБО

Якщо хоча б один з бітів дорівнює 1, то результат дорівнює 1, інакше результат дорівнює 0.

OR

0

1

0

0

1

1

1

1

 

 

Логічне АБО обчислюється за допомогою команди OR. У цієї команди теж 2 операнда, і результат поміщається на місце першого. Часто це команда використовується для встановлення в 1 певних бітів числа. Якщо біт маски дорівнює 1, то біт результату буде дорівнювати 1, інші біти збережуть свої значення.

Приклади:

or al, dl ; AL = AL | DL

or bl, 10000000b ; Встановити знаковий біт BL

or cl, 00100101b ; Встановити біти 0,2,5 CL

 

1.2.3 Логічне НЕ (інверсія)

Кожен біт операнда змінює своє значення на протилежне (0 → 1, 1 → 0). Операція виконується за допомогою команди NOT. У цієї команди є тільки один операнд. Результат поміщається на місце операнда. Ця команда не змінює значення прапорів.

Приклад:

not bx ; Інверсія байта за адресою в BX

 

1.2.4 Логічне ВИКЛЮЧАЮЧЕ АБО (сума за модулем два)

Якщо біти мають однакове значення, то результат дорівнює 0, інакше результат дорівнює 1.

XOR

0

1

0

0

1

1

1

0

 

 

Виключаючим АБО ця операція називається тому, що результат дорівнює 1, якщо один біт дорівнює 1 або інший дорівнює 1, а випадок, коли обидва рівні 1, виключається. Ще ця операція нагадує додавання, але в межах одного біта, без переносу. 1 + 1 = 10, але перенесення в інший розряд ігнорується і виходить 0, звідси назва сума за модулем 2. Для виконання цієї операції призначена команда XOR. У команди два операнди, результат поміщається на місце першого. Команду можна використовувати для інверсії певних бітів операнда. Інвертуються ті біти, які в масці рівні 1, останні зберігають своє значення.

Приклади:

xor si, di ; SI = SI ^ DI

xor al, 11110000b ; Інверсія старшої тетради AL

xor bp, 8000h ; Інверсія знакового біта BP

 

Позначення операції в коментарі до першого рядка використовується в багатьох мовах високого рівня (наприклад C, C + +, Java і т.д.). Часто XOR використовують для обнулення регістрів. Якщо операнди рівні, то результат операції завжди дорівнює 0. Такий спосіб обнулення працює швидше і, на відміну від команди MOV, не містить безпосереднього операнда, тому команда виходить коротшою (і не містить нульових байтів):

mov bx, 0 ; Ця команда займає 3 байти

xor bx, bx ; А ця - всього 2

 

1.3 Лінійний зсув

Зсув - це особлива операція процесора, яка дозволяє реалізувати різні перетворення даних, працювати з окремими бітами, а також швидко виконувати множення і ділення чисел на степінь 2.

1.3.1 Логічний зсув вправо

Логічний зсув завжди виконується без урахування знакового біта. Для логічного зсуву вправо призначена команда SHR. У цієї команди два операнда. Перший операнд являє собою значення яке потрібно зсувати і на його місце записується результат операції. Другий операнд вказує, на скільки біт потрібно здійснити зсув. Цим операндом може бути або безпосереднє значення, або регістр CL. Схема виконання операції показана на малюнку:

Всі біти операнда зсуваються вправо (від старших бітів до молодших). Зсунутий біт стає значенням прапора CF. Старший біт отримує нульове значення. Ця операція повторюється кілька разів, якщо другий операнд більший за одиницю. Логічний зсув вправо можна використовувати для поділу цілих чисел без знаку на ступінь 2, причому зсув працює швидше, ніж команда ділення DIV.

Приклади:

shr ax, 1 ; Логічний зсув AX на 1 біт вправо

shr byte bx, cl ; Логічний зсув байта за адресою BX на СL біт вправо

shr cl, 4 ; CL = CL / 16 (для числа без знака)

 

1.3.2 Арифметичний зсув вправо

Арифметичний зсув відрізняється від логічного тим, що він не змінює значення старшого біта, і призначений для чисел із знаком. Арифметичний зсув вправо виконується командою SAR. У цієї команди теж 2 операнда, аналогічно команді SHR. Схема виконання операції показана на малюнку:

Зсунутий біт стає значенням прапора CF. Знаковий біт не змінюється. При зсуві на 1 біт скидається прапор OF. Цю команду можна використовувати для поділу цілих чисел із знаком на ступінь 2 (зверніть увагу, що округлення завжди в бік меншого числа, тому для негативних чисел результат буде відрізнятися від результату ділення за допомогою команди IDIV).

Приклади:

sar bx, 1 ; Арифметичний зсув BX на 1 біт вправо

sar di, cl ; Арифметичний зсув DI на CL біт вправо

sar x, 3 ; x = x / 8 (x - 8-бітове значення зі знаком)

 

1.3.3 Логічний і арифметичний зсув вліво

Логічний зсув вліво виконується командою SHL, а арифметичний - командою SAL. Однак, насправді це просто синоніми для однієї і тієї ж машинної команди. Зсув вліво однаковий для чисел із знаком і чисел без знаку. У команди 2 операнда, аналогічно командам SHR і SAR. Схема цієї операції показана на малюнку:

Старший біт стає значенням прапора CF, а молодший отримує нульове значення. За допомогою зсуву вліво можна швидко множити числа на ступінь 2. Але будьте уважні, щоб не отримати в результаті переповнення. Якщо при зсуві на 1 біт змінюється значення старшого біта, то встановлюється прапор OF.

Приклади:

shl dx,1 ; Зсув DX на 1 біт вліво

sal dx,1 ; Те ж саме

shl ax,cl ; Зсув AX на CL біт вліво

sal x,2 ; x = x * 4

 

1.4 Циклічний зсув

Циклічний зсув відрізняється від лінійного тим, що висунуті з одного кінця біти висуваються з іншого боку, тобто рухаються по кільцю. У процесора x86 існує 2 види циклічного зсуву: простий і через прапор переносу (CF). У всіх команд, які розглядаються в цій частині навчального курсу, по 2 операнда, таких же, як у команд лінійного зсуву. Перший операнд значення, яке потрібно зсувати і місце для запису результату. Другий операнд - лічильник зсувів, який може знаходиться в регістрі CL або вказуватися безпосередньо.

 

1.4.1 Простий циклічний зсув

Циклічний зсув вправо виконується командою ROR, а вліво - командою ROL. Схема роботи цих команд представлена на малюнку (на прикладі 8-бітного операнда):

Циклічний зсув вправо (ROR)

Циклічний зсув вліво (ROL)

Значення останнього висунутого біта копіюється в прапор CF. Для зсуву на 1 біт встановлюється прапор OF, якщо в результаті зсуву змінюється знаковий біт операнда.

Приклади:

rol bl, 1 ; Циклічний зсув BL на 1 біт вліво

ror word si, 5 ; Циклічний зсув слова за адресою в SI на 5 біт вправо

rol ax, cl ; Циклічний зсув AX на CL біт вліво

 

1.4.2 Циклічний зсув через прапор переносу

Відмінність від простого циклічного зсуву в тому, що прапор CF бере участь у зсуві нарівні з бітами операнда. При зсуві на 1 біт, який зсувається, поміщається в CF, а значення CF зсувається в операнд з іншого боку. При зсуві на кілька біт ця операція повторюється багато разів. Циклічний зсув через прапор перенесення виконується командами RCR (вправо) і RCL (вліво).

Знову ж для зсуву на 1 біт встановлюється прапор OF, якщо в результаті зсуву змінюється знаковий біт операнда.

Приклади:

rcr dh, 3 ; Цикл. зсув DH на 3 біти вправо через прапор CF

rcl byte bx, cl ; Цикл. зсув байта за адресою в BX на CL біт вліво через прапор CF

rcl dx, 1 ; Цикл. зсув DX на 1 біт вліво через прапор CF

 

1.4.3 Порівняння бітів TEST

TEST працює точно так само як і логічна операція AND. Одна різниця що результат не зберігається, але зате виставляються прапори для подальшого умовного переходу. Дана властивість дозволяє використовувати TEST для перевірки прапорів. Отже, як поведе себе TEST. А ось так. Уявімо якесь число в двійковій системі:

00000100 (4 в десятковому форматі)

Хочемо перевірити це число на установку цього біта в 1, виходить що потрібно написати:

test 4,4

А далі умовний перехід наприклад JNZ. Перехід буде, якщо одиниці збігаються. Але справа в тому, що цей біт встановлений не тільки у 4, але ще і у:

5 000000101

7 000000111

12 00001100

І у всіх випадках команда TEST повинна працювати однаково, оскільки встановлено цей біт.

Лекція 1 Засоби та задачі системного програмування

 

1..

2..

 

  1. Засоби та задачі системного програмування

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

В даному курсі системного програмування розглядається методологія розробки програмного забезпечення з використанням мови програмування низького рівня Асемблер.

Спектр завдань, які краще всього вирішувати з використанням мови Асемблер:

  будь-які програми, що вимагають мінімального розміру і максимальної швидкодії;

  драйвери і взагалі все, що безпосередньо працює з апаратурою;

  ядра ОС, сервери DPMI і взагалі системні програми, що працюють в захищеному режимі;

  програми для захисту інформації, злому цього захисту і захисту від таких зломів.

Список насправді не вичерпаний. На мові Асемблер (або на мові високого рівня, але з використанням вставок програмного коду мови Асемблер) можна створювати багато програм, які зазвичай створюються на мові високого рівня з неминучим програшем в швидкодії програми і компактності вихідного коду.

 

  1. Переваги та недоліки низькорівневих середовищ розробки програм

До переваг використання мови Асемблер можна віднести:

  1. Максимальна гнучкість і максимальний доступ до ресурсів компютера і операційної системи (ОС). На мові Асемблер можна реалізувати взаємодію з апаратними засобами значно простіше, ніж на мовах високого рівня, де доводиться використовувати різні доповнення. Наприклад, для створення драйвера або резидентної програми, постає необхідність у використанні мінімальних ресурсів. На мові Асемблер є можливість залишити в пам'яті тільки те, що нам буде потрібно після ініціалізації програми. При створенні аналогічних програм за допомогою мов програмування високого рівня, досить часто обєм використовуваної оперативної памяті більший в десятки разів. Причиною цього є велика надлишковість скомпільованого коду в пам'яті залишається дуже багато вже непотрібних даних.
  2. Компактність вихідного коду і можливості його ручної оптимізації, обмежені лише можливостями процесора. Крім того, вставки коду на мові Асемблер та ручна оптимізація початкових модулів дозволяють збільшити швидкість виконання розроблюваних програм.

 

До недоліків використання мови Асемблер можна віднести:

  1. Трудомісткість розробки. Дійсно, трудомісткість розробки програм на Асемблері в кілька разів вища, ніж на мовах високого рівня. Там, де реально записується одна стрічка (щоб, наприклад, привласнити змінній значення виразу), на мові Асемблер доводиться писати декілька (а іноді і десятки, в залежності від виразу). Але на практиці, розробники програмного забезпечення часто ускладнюють собі роботу, реалізовуючи наново в кожній програмі одні і ті ж алгоритми, наприклад, вводу/виводу, замість того, щоб реалізувати їх у вигляді макросів у файлі або підпрограм в бібліотеці. Крім того, є готові бібліотеки, але вони не стандартизовані і разом з компіляторами не розповсюджуються..
  2. Складність розуміння початкових текстів значно вища, ніж для початкових текстів на мовах високого рівня.
  3. Непереносимість. Програми на Асемблері дійсно не переносяться з однієї апаратної платформи на іншу. І не повинні переноситись. Програмне забезпечення, створене з використанням мови Асемблер, призначене для найбільш ефективного використання особливостей конкретних апаратних платформ (архітектур) та для забезпечення максимально можливої якості роботи.

Крім того, сучасні середовища розробки часто не гарантують переносимість не тільки між апаратними платформами, але і між різними ОС, тому що для збільшення ефективності програмування і вихідного коду до мови вносяться системно-залежні розширення під конкретну ОС. Програма, що використовує ці розширення, очевидно, відразу ж втрачає переносимість.

Загалом, доцільність та обгрунтування використання будь-якого середовища розробки і, як наслідок, мови програмування визначається вимогами до розроблюваного програмного продукту та наявними засобами (інструментами) в конкретній мові для реалізації цих вимог.

Лекція 2 Структура обчислювальної системи. Системні шини

 

  1. Структура обчислювальної системи з точки зору програмування.
  2. Принципи обміну даними в цифрових системах. Системні шини.

 

  1. Структура обчислювальної системи з точки зору програмування

На рис. 1.1 показана спрощена структурна схема обчислювальної системи на прикладі персонального комп'ютера типу IBM РС.

Центральний процесор (ЦП) спільно з блоком пам'яті забезпечує виконання програм.

Зв'язок між процесором і пам'яттю здійснюється через системну магістраль, яку утворюють 3 шини: даних, адреси і керування. Керування операціями запису і читання з пам'яті здійснюється сигналамиMEMW (Memory Write) і MEMR (Memory Read) шини керування. Пристрої вводу/виводу (ПВВ) забезпечують зв'язок з периферійними пристроями, виконуючи функції вводу, виводу і відображення інформації (клавіатура, монітор, принтер, миша і ін.), забезпечують довготривале зберігання програм і даних (накопичувачі на магнітних дисках). Взаємодія з ПВВ також здійснюється через системну магістраль. ПВВ підключаються до системної магістралі через інтерфейсні блоки (ІБ) або адаптери,кожен з яких має в своєму складі набір пристроїв (найчастіше це регістри), що називаються портами вводу/виводу (ВВ), через які ЦП і пам'ять взаємодіють з ПВВ.

Адресний простір пам'яті і портів ВВ є суміщеним, тому керування операціями запису і читання з портів ВВ здійснюється спеціальними сигналами IOW (Input/output Write) і IOR (Input/output Read) шини керування.

 

2.     Принципи обміну даними в цифрових системах. Системні шини

На рис. 1.2 показана узагальнена структурна схема типової мікропроцесорної системи. Всі обчислення і логічні операції виконуються блоком центрального процесора, або ЦПУ (Central ProcessingUnitабо CPU). У ньому передбачені: невелике число внутрішніх елементів пам'яті (регістрів),високочастотний тактовий генератор (ТГ), блок керування (БК) і арифметико-логічний пристрій.

 

Pict1

Рисунок 1.2 Узагальнена структурна схема типової мікропроцесорної системи

 

Центральний процесор вставляється в спеціальне гніздо, розташоване на материнській платі, і електрично з'єднується з іншими пристроями комп'ютера за допомогою великої кількості виводів. Більша частина цих виводів призначена для підключення до шин даних, керування і адреси комп'ютерної системи.

При виконанні програми всі команди і оброблювані дані зберігаються в оперативній пам'яті (ОЗП).Центральний процесор генерує команди звернення до блоку оперативної пам'яті, у відповідь на які, останній або видає на шину даних вміст запитаного елементу оперативної пам'яті, або записує вміст шини даних в заданий за допомогою шини адреси елемент пам'яті.

Шина (bus) є групою паралельних провідників, за допомогою яких дані передаються від одного пристрою комп'ютерної системи до іншого. Зазвичай системна шина комп'ютера складається з трьох різних шин: шини даних, шини керування і шини адреси. Шина даних (data bus) використовується для обміну команд і даних між ЦП і оперативною пам'яттю, а також між пристроями вводу/виводу і ОЗП. Пошині керування (control bus) передаються спеціальні сигнали, що синхронізують роботу всіх пристроїв, підключених до системної шини. Шина адреси (address bus) використовується для вказівки адреси елементу пам'яті в ОЗП, до якої у даний момент відбувається звернення з боку ЦП або пристроїв вводу/виводу.

Тактовий генератор, або генератор тактових (прямокутних) імпульсів постійної частоти, є джерелом синхронізації для внутрішніх команд, що виконуються процесором, та інших компонентів системи. У теорії електронно-обчислювальних машин розрізняють два поняття: машинний такт і машинний цикл.

Машинний такт відповідає одному періоду імпульсів тактового генератора і є основною одиницею вимірювання часу виконання команд процесором.

Машинний цикл складається з декількох машинних тактів і відповідає часу виконання однієї команди. Наприклад, машинний цикл команди вибірки операнда з пам'яті може складатися з одного-двохмашинних тактів. На рис. 1.3 зображений один період генератора тактових імпульсів, відповідних одному машинному такту. Зверніть увагу, що один машинний такт відповідає періоду часу, що пройшов між двома задніми фронтами тактових імпульсів.

Pict2

Рисунок 1.3 Машинний такт

 

Тривалість машинного такту обернено пропорційна частоті тактового генератора, яка вимірюється в кількості коливань в секунду, або герцах (Гц). Наприклад, якщо тактовий генератор виробляє за 1с 1 млрд. імпульсів (тобто працює на частоті 1 ГГц), тривалість машинного такту відповідатиме одній мільярдній частині секунди, тобто 1 наносекунді (нс).

Для виконання однієї машинної команди, як правило, потрібно від одного до декількох машинних тактів. Деяким командам, наприклад таким, як команда множення в процесорі 8088, потрібно близько 50 машинних тактів. Часто при виконанні команд звернення до пам'яті доводиться вводити декілька холостих тактів, які називають режимом очікування (waitstates). Так відбувається тому, що ЦП, системна шина і мікросхеми пам'яті мають різну швидкодію, тобто працюють на різних тактових частотах. Слід зазначити, що останнім часом при розробці комп'ютерних систем намітилася тенденція відходу від використання загального джерела синхронізації і перехід на асинхронний режим роботи деяких компонентів системи, зокрема блоку оперативної пам'яті і пристроїв вводу/виводу.

Блок керування визначає послідовність мікрокоманд, що виконуються при обробці машинних команд.

Арифметико-логічний пристрій безпосередньо виконує арифметичні операції, такі як додавання, віднімання, а також логічні операції, такі як І, АБО і НЕ.

Лекція 3 Архітектура процесорів сімейства Intel x86

 

  1. Архітектура процесорів сімейства Intel x86. Регістри.
  2. Конвеєрна організація роботи процесора. Процесори суперскалярної архітектури.

3.     Будова процесорів сімейства IA-32.

 

  1. Архітектура процесорів сімейства Intel x86. Регістри

Мікропроцесор (МП) сімейства Intel x86 фірми Intel має 16-розрядну внутрішню архітектуру (рис. 1.4). Саме такою є розрядність шини даних і всіх регістрів, в яких зберігаються дані і адреси. Шина адреси має розрядність 20 біт, що відповідає об'єму адресного простору 220 = 1 Мбайт. Для того щоб за допомогою 16-розрядних регістрів можна було звертатися в будь-яку точку адресного простору, в МП передбачена так звана сегментна адресація, що реалізовується за допомогою чотирьох сегментних регістрів (рис. 1.5).

 

Pict3

 

Рисунок 1.4 Структура процесора 8086

 

Виконавча 20-розрядна адреса будь-якого елементу пам'яті обчислюється ЦП шляхом сумування початкової адреси області пам'яті (сегменту пам'яті), в якій знаходиться ця комірка, із зсувом (offset) в ній (у байтах) від початку сегменту. Розмір сегменту може знаходитися в межах 0 байт - 64 Кбайт.

Початкову адресу сегменту пам'яті зазвичай називають сегментною адресою, зсув (зміщення) в сегменті пам'яті - відносною адресою. Сегментна адреса без 4 молодших нульових бітів, тобто кратна 16, зберігається у одному з сегментних регістрів. При обчисленні виконавчої адреси ЦП множить вміст сегментної адреси на 16 і додає до отриманого 20-розрядного коду 16-розрядний вміст регістра, в якому зберігається відносна адреса Таким чином, повна адреса елементу пам'яті може бути записаний у вигляді:

 

SSSSh:OOOOh,

 

де SSSSh - сегментна, а OOOOh - відносна адреса комірки в шістнадцятковій формі запису.

 

Pict4

Рисунок 1.5 Сегментна адресація

 

Сегменти жорстко не прив'язуються до певних адрес памяті і можуть частково або повністю перекриватися. Ділянка пам'яті розміром 16 байт називається параграфом. Адреса початку сегменту завжди вирівняна на межу параграфа.

Якщо розглядати тільки режим реальної адресації пам'яті, або просто реальний режим, внутрішня архітектура МП фірми Intel практично співпадає. Розглянемо структуру МП сімейства Intel на прикладі процесора 8086.

Процесор 8086 містить (рис. 1.4):

  чотири сегментні регістри;

  вісім програмно-доступних 16-розрядних регістрів;

  вказівник команд;

  регістр прапорів (ознак).

 

Сегментні регістри CS (code segment), DS (data segment), ES (enhanced segment) і SS (stack segment) забезпечують адресацію чотирьох сегментів (сегменту коду, основного і додаткового сегментів даних, а також сегменту стека).

Регістри загального призначення АХ, ВХ, СХ і DX використовуються для зберігання даних або адрес, результатів виконання логічних або арифметичних операцій Ці регістри допускають незалежне звернення до своїх старших (АН, ВН, СН, DH) або молодших (AL, BL, CL, DL) частин. При виборі регістрів перевага завжди віддається регістру АХ (або його частинам АН і AL), оскільки багато команд виконуються в цьому випадку швидше і займають менше місця в пам'яті. Деякі команди використовують регістри загального призначення неявним чином. Так, наприклад, команди циклів використовують СХ як лічильник циклів; команди множення і ділення як операнди використовують вміст АХ і DX; команди вводу/виводу як буферні регістри можуть використовувати тільки АХ або AL, а як регістр адреси DX і т.д.

Основне призначення регістрів SI (Source Index) і DI (Destination Index) зберігати індекси (зсуви) щодо деяких базових адрес масивів при вибірці операндів з пам'яті. Адреса бази при цьому може знаходитися в регістрі ВР або ВХ. Спеціальні рядкові команди не явним чином використовують регістриSI і DI як вказівники в оброблюваних рядках. При необхідності обидва індексні регістри можуть використовуватися в якості регістрів загального призначення.

Регістр ВР (Base Pointer) служить вказівником бази при роботі із стеком, але можевикористовуватись і в якості регістрів загального призначення. Регістр SP (Stack Pointer) використовується як вказівник вершини стека при виконанні команд, що працюють із стеком.

Стек - це область пам'яті, організована таким чином, що 16-розрядні дані завантажуються в неї послідовно, а при читанні витягуються в зворотному порядку. Стек заповнюється від низу до верху, а витягання вмісту стека проводиться зверху (з вершини стека) в порядку черги. В результаті стек можна назвати FILO-памяттю, що працює по принципу "першим увійшов, останнім вийшов" - "first in, last out". Для звичайно організованої пам'яті (пам'яті з довільним доступом) при вводі і виводі даних необхідно вказувати адреси комірок, до яких відбувається звернення. Для стека достатньо простих команд поміщення в стек і завантаження із стека. Стек використовується для тимчасового зберігання даних, для передачі параметрів підпрограмам, що викликаються, а також для збереження адрес повернення при виклику підпрограм і обробників переривань.

Вказівник команд IP (Instruction Pointer) виконує функцію програмного лічильника. Його вміст є відносною адресою команди, наступної за виконуваною. Регістр IP програмно недоступний. Нарощування адреси в ньому здійснює ЦП із врахуванням довжини поточної команди. Команди передачі керування змінюють вміст IP, забезпечуючи тим самим перехід в потрібні точки програми (рис. 1.6).

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

 

Ознаки результату:

S (Sign) - знак результату, рівний старшому біту результату операції;

Z (Zero) - ознака нульового результату;

Р (Parity) - ознака парності результату;

C (Carry) - прапор перенесення; встановлюється, якщо при додаванні (відніманні) виникає перенесення (позичання) із старшого розряду результату; при зсувах CF зберігає значення біта, що висувається; служить індикатором помилки при зверненні до системних функцій;

A (Auxiliary) - прапор додаткового перенесення; встановлюється, якщо виникає перенесення (позичання) з третього біта в четвертий; використовується в операціях над упакованими двійково-десятковими цифрами;

O (Overflow) - прапор переповнення; встановлюється при отриманні результату, що знаходиться за межами допустимого діапазону значень.

 

Прапори керування:

D (Direction) - прапор напряму; визначає напрям обробки рядків даних;

DF = 0 - рух від молодших адрес до старших, вміст індексних регістрів після обробки кожного елементу рядка збільшується;

DF = 1 - рух від старших адрес до молодших, вміст індексних регістрів після обробки кожного елементу рядка зменшується;

I (Interrupt) - прапор переривання; встановлюється, коли треба дозволити ЦП обробляти запити переривань від ПВВ;

Т (Trap) - прапор трасування; при TF = 1 після виконання кожної команди генерується внутрішнє переривання процесора; використовується відладчиками.

 

Програма завжди розташовується в сегменті, визначеному регістром CS. Значення CS визначається операційною системою автоматично. Область даних за замовчуванням знаходиться в сегменті, визначеному регістром DS. Вона може знаходитися і в одному з сегментів, що адресуються регістрами CS,ES або SS, проте цей факт повинен бути відзначений в програмі наявністю префікса заміни сегменту,наприклад:

CS:Flagenable - вміст елементу пам'яті Flagenable, що знаходиться в сегменті коду;

ES:[ВХ-2] - вміст елементу пам'яті, розташованої в додатковому сегменті даних. При цьому, її відносна адреса рівна вмісту ВХ, зменшеному на 2.

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

 

  1. Конвеєрна організація роботи процесора. Процесори суперскалярної архітектури

Поняття конвеєрної обробки в процесорі тісно повязано з питанням циклу виконання команди.

Під циклом виконання команди ми маємо на увазі послідовність дій, що здійснюються процесором при виконанні однієї машинної команди. Наприклад, при виконанні команди, в якій використовується операнд, розташований в пам'яті, процесор повинен спочатку визначити адресу операнда, помістити її на шину адреси, дочекатися, поки значення операнда з'явиться на шині даних, і т. д..

Перед виконанням програма повинна бути завантажена в оперативну пам'ять комп'ютера. Спрощена схема циклу виконання команди показана на рис. 1.7. На цьому рисунку під терміном лічильник команд(ЛК) мається на увазі регістр, в якому міститься адреса наступної по порядку виконуваної команди. Чергакоманд - це область надоперативної пам'яті усередині мікропроцесора, в яку поміщається одна або декілька команд безпосередньо перед їх виконанням. При виконанні кожної машинної команди процесор повинен виконати як мінімум три основні операції: вибірка, декодування і виконання. Якщо в команді використовується операнд, розташований в пам'яті, процесору потрібно виконати ще дві додаткові операції: вибірку операнда з пам'яті і запис результату в пам'ять. Іншими словами, при виконанні команди, пов'язаної із зверненням до пам'яті, процесор повинен виконати як мінімум п'ять операцій, перерахованих нижче.

 

Pict5

Рисунок 1.7 Спрощена схема циклу виконання команди

 

1.  Вибірка команди. Блок управління витягує команду з пам'яті, копіює її у внутрішню пам'ять мікропроцесора і збільшує значення лічильника команд на довжину цієї команди.

2.  Декодування команди. Блок керування визначає тип виконуваної команди, пересилає вказані в ній операнди в АЛП і генерує електричні сигнали управління АЛП, відповідно до типу виконуваної операції.

3.  Вибірка операндів. Якщо в команді використовується операнд, розташований в пам'яті, блок керування ініціює операцію по його вибірці з пам'яті.

4.  Виконання команди. АЛП виконує вказану в команді операцію, зберігає отриманий результат в заданому місці і оновлює стан прапорів, за значенням яких програма може робити висновок про результат виконання команди.

5.  Запис результату в пам'ять. Якщо результат виконання команди повинен бути збережений в пам'яті, блок керування ініціює операцію збереження даних в пам'яті.

 

Кожна операція в циклі виконання команди триває як мінімум один період тактового генератора, який називається машинним тактом. Проте це зовсім не означає, що процесор перед початком виконання наступної команди повинен дочекатися закінчення виконання всіх етапів попередньої команди. Він може виконувати їх паралельно. Така методика називається конвеєрною обробкою. Починаючи з Intel 386, всі процесори сімейства IA-32 підтримують шестиступінчасту обробку команд, а конвеєрна обробка була вперше застосована в процесорі Intel 486. Всі шість етапів виконання команди, а також вузли процесора, які це забезпечують, включають:

  1. Модуль шинного інтерфейсу забезпечує доступ до пам'яті і виконує всі операції по вводу і виводу даних.
  2. Модуль попередньої вибірки команд отримує потік машинних команд від модуля шинного інтерфейсу і поміщає їх у внутрішню область пам'яті процесора, яка називається чергою команд.
  3. Модуль декодування команди виконує вибірку машинної команди з черги, її декодування і перетворення в послідовність мікрокоманд.
  4. Модуль виконання забезпечує виконання послідовності мікрокоманд, отриманих від модуля декодування.
  5. Модуль сегментації перетворює логічні адреси в лінійні адреси і виконує перевірки, пов'язані із захистом пам'яті.
  6. Модуль сторінкової організації перетворює лінійні адреси у фізичні адреси пам'яті, виконує перевірки адрес, пов'язані із захистом сторінок пам'яті, а також веде список сторінок, до яких недавно здійснювався доступ.

 

Приклад. Припустимо, що кожен етап виконання команди в процесорі триває рівно 1 машинний такт. На рис. 1.8 показана матриця шестиступінчастого виконання команд в процесорі, що не підтримує режим конвеєрної обробки. Подібний режим виконання команд був реалізований в процесорах фірми Intel до появи моделі Intel 486.

Pict6

Рисунок 1.8 Шестиступінчатий цикл виконання команди в процесорі,

який не підтримує режим конвеєрної обробки

 

Як тільки завершується етап Е6 виконання команди К-1, починається виконання етапу Е1 команди К-2. При цьому для виконання двох команд К-1 і К-2 потрібно 12 машинних тактів. Іншими словами, якщо цикл виконання команди складається з етапів, то для виконання послідовності з команд буде потрібно машинних тактів. Завдяки рис. 1.8 стає очевидно, що подібний центральний процесор працює вкрай неефективно, оскільки за 1 машинний такт виконується тільки одна шоста частина команди.

В той же час, якщо в процесорі підтримується режим конвеєрної обробки, то, як показано на рис. 1.9,вже на другому машинному такті процесор може приступити до етапу Е1 виконання нової команди. При цьому попередня команда знаходитиметься на етапі Е2 свого виконання. Таким чином, конвеєрна обробка дозволяє сумістити виконання двох машинних команд в часі. На рис. 1.9 показаний процес виконання двох команд К-1 і К-2 в конвеєрі. Як тільки процесор переходить до етапу Е2 виконання команди К1, починається виконання етапу Е1 команди К-2. Внаслідок цього для виконання 2 машинних команд потрібний вже не 12, а всього лише 7 машинних тактів. При повному завантаженні конвеєра, в даний момент часу працюють всі 6 його ступенів.

 

Pict7

Рисунок 1.9 Шестиступінчатий цикл виконання команди в процесорі,

який підтримує режим конвеєрної обробки

Взагалі кажучи, якщо цикл виконання команди складається з етапів, то для виконання послідовності з команд буде потрібно машинних тактів. Таким чином, тоді як в процесорі, що не підтримує режим конвеєрної обробки, 2 команди виконуються за 12 машинних тактів, при використанні конвеєра процесор може виконати за той же самий час вже 7 команд.

Процесор, побудований по суперскалярній архітектурі, має 2 (або більше) конвеєри для виконання команд. Це дозволяє одночасно виконувати 2 (або більше) команди.

Щоб краще зрозуміти доцільність застосування суперскалярної архітектури в процесорі, розглянемо попередній приклад конвеєрної обробки, в якому ми для спрощення припускали, що етап виконання команди (Е4) триває всього 1 машинний такт. А що ж відбудеться, якщо етап виконання команди Е4 триває 2 машинних такти? Тоді в роботі конвеєра виникнуть збої, як показано на рис. 1.10. Процесор не зможе перейти до фази виконання Е4 команди К-2, поки він повністю не завершить фазу виконання команди К-1. В результаті цикл виконання команди К-2 збільшиться на 1 машинний такт, тобто на час очікування звільнення конвеєра на етапі Е4. По міру надходження на конвеєр додаткових команд, деякі його ступені працюватимуть вхолосту (на рис. 1.10 вони виділені сірим кольором).

Взагалі кажучи, якщо цикл виконання команди складається з етапів (причому для виконання одного з етапів потрібно 2 машинних такти), то для виконання послідовності з команд буде потрібно машинних тактів.

При використанні процесора з суперскалярною архітектурою, на етапі виконання можуть знаходитися відразу декілька команд. Таким чином, при використанні конвеєрів, відразу  команд може одночасно знаходитися на етапі виконання в одному і тому ж машинному такті. У процесорі Intel Pentiumбули застосовані 2 конвеєри. Таким чином, він став першим процесором сімейства IA-32, побудованим по суперскалярній архітектурі. У процесорі Pentium Pro вперше було застосовано 3 конвеєри.

Pict8

Рисунок 1.10 Цикл виконання команди на одному конвеєрі

 

Продовжимо розгляд нашого прикладу шестиступінчастого конвеєра і введемо в нього ще один (тобто другий) конвеєр. Як і раніше ми припускатимемо, що фаза виконання команди Е4 триває 2 машинних такти. Як показано на рис. 1.11, команда з непарним номером поступає на -конвеєр, а команда з парним номером - на -конвеєр. Подібний підхід дозволяє ліквідовувати простої в роботі конвеєра і виконати п команд за машинних тактів.

Pict9

Рисунок 1.11 Принцип роботи шестиступінчатого конвеєра процесора з суперскалярною архітектурою

 

3.     Будова процесорів сімейства IA-32

Під сімейством (архітектурою) IA-32 ми маємо на увазі сімейство процесорів фірми Intel, родоначальником якого є процесор Intel 386. У це сімейство входить також процесори Pentium 4. Не дивлячись на те, що з моменту випуску процесора Intel 386 швидкодія процесорів і їх внутрішня структура істотно змінилися, для розробника програмного забезпечення ці відмінності не мають особливого значення, оскільки всі вони приховані стандартом архітектури IА-32. Таким чином, з погляду розробника програмного забезпечення, архітектура процесорів IА-32 по суті не змінилася з моменту випуску процесора lntel 386, якщо не рахувати введення набору високопродуктивних команд для підтримки мультимедійних застосувань.

Процесори сімейства IА-32 можуть працювати в одному з трьох основних режимів:

  реальної адресації (Real-address mode);

  захищеному (Protected mode);

  керування системою (System Management mode).

Крім того, існує ще один - віртуальний режим роботи (Virtual-8086 mode), або режим емуляції процесора 8086, який є різновидом захищеного режиму.

 

Захищений режим. Це основний режим роботи, в якому для розробника доступні всі команди, режими адресації і можливості процесора. При цьому, кожній програмі виділяється ізольована область пам'яті, яку називають сегментом (segment). В процесі виконання програми, ЦП відстежує всі її звернення до пам'яті і присікає всі спроби звернення за межі виділених програмі сегментів.

Віртуальний режим. При роботі ЦП в захищеному режимі, він може безпосередньо виконувати програми, написані для реального режиму адресації процесора 8086. Таким чином, стає можливим завантаження і виконання програм, написаних для системи MS DOS в безпечному багатозадачному оточенні. Іншими словами, навіть якщо програма в процесі виконання, в результаті помилки або збоюзависне, це ніяк не вплине на інші програми, які виконуються в даний момент на комп'ютері. Саме тому,даний режим роботи часто називають режимом емуляції віртуального процесора 8086, хоча насправді цей режим відноситься до захищеного режиму роботи процесора.

Реальний режим адресації. У цьому режимі повністю повторюється робота процесора Intel 8086 і додається декілька нових можливостей, наприклад команди переходу в інші режими роботи. Реальний режим адресації використовувався в операційних системах Windows 95/98/2000/XP/Vista/7/Server у разі, коли застосуванню MS DOS потрібно було надати повний контроль над апаратним забезпеченням комп'ютера. При виконанні початкового завантаження по сигналу скидання (Reset) всі процесори фірмиIntel сімейства IA-32 автоматично переходять в реальний режим адресації. Після цього операційна система комп'ютера може перемкнути процесор в необхідний режим роботи.

Режим керування системою. Даний режим роботи процесора часто позначають абревіатурою SSM(System Management mode). Він дозволяє надати операційній системі комп'ютера механізм для виконання таких функцій, як перехід комп'ютера в режим енергозбереження і відновлення працездатності системи після збою. Ці функції зазвичай використовуються виробниками комп'ютера і материнських плат длявстановлення потрібних режимів роботи обладнання.

 

При роботі в захищеному режимі процесори сімейства IA-32 можуть адресувати до 4 Гбайт оперативної пам'яті. Такий діапазон адрес визначається розрядністю внутрішніх регістрів процесора. Оскільки регістри 32-розрядні, в них можуть зберігатися значення від 0 до 232-1. У реальному режимі адресації процесор може адресувати до 1 Мбайт оперативної пам'яті. Якщо процесор працює в захищеному режимі, він може одночасно виконувати декілька програм у віртуальному режимі адресації 8086 процесора. При цьому кожній програмі відводиться ізольована область віртуальної пам'яті розміром 1 Мбайт.

 

Основні елементи процесора архітектури IA-32:

1.     Програмні регістри

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

На рис. 1.13 зображена структура основних програмних регістрів (program execution registers)процесора сімейства IA-32 і їх назви, визначені фахівцями фірми Intel. Існує 8 регістрів загального призначення, 6 сегментних регістрів, регістр стану процесора, або регістр прапорів (EFLAGS), і регістр покажчика команд (EIP).

 

Регістри загального призначення. Ці регістри використовуються в основному для виконання арифметичних операцій і пересилки даних. Як показано на рис. 1.14, до кожного регістра загального призначення можна звернутися як до 32-розрядного або як до 16-розрядного регістра.

До деяких 16-розрядних регістрів можна звертатися як до двох 8-розрядних регістрів. Наприклад, регістр ЕАХ є 32-розрядним, проте його молодші 16 розрядів знаходяться в регістрі АХ. Старші 8 розрядіврегістра АХ знаходяться в регістрі АН, а молодші 8 розрядів - в регістрі AL.

У табл. 1.1 показані особливості звернення до інших регістрів загального призначення, які ми умовно назвали основними.

До регістрів загального призначення, які не вказані в табл. 1.1, можна звертатися або як до 32-розрядних, або як до 16 розрядних регістрів, як показано в табл. 1.2. Вони не підтримують можливість звернення до молодших і старших байтів своєї 16-розрядної частини, як це було при розгляді прикладу з регістром ЕАХ. 16-розрядні частини цих регістрів зазвичай використовуються тільки при написанні програм для реального режиму адресації.

 

 

Таблиця 1.1 - Звернення до основних регістрів загального призначення

32-розрядний регістр

16- розрядний регістр

8- розрядний регістр (старший байт)

8- розрядний регістр (молодший байт)

ЕАХ

АХ

АН

AL

ЕВХ

ВХ

ВН

BL

ЕСХ

СХ

СН

CL

EDX

DX

DH

DL

 

 

Таблиця 1.2 - Звернення до додаткових регістрів загального призначення

32-розрядний регістр

16- розрядний регістр

ESI

SI

EDI

DI

ЕВР

ВР

ESP

SP

 

Особливості використання регістрів. При виконанні команд процесором, частина регістрів загального призначення мають особливе значення.

Вміст регістра ЕАХ автоматично використовується при виконанні команд множення і ділення. Оскільки цей регістр зазвичай пов'язаний з виконанням арифметичних команд, його часто називаютьрозширеним регістром акумулятора (extended accumulator).

Регістр ЕСХ автоматично використовується процесором як лічильник циклу.

За допомогою регістра ESP відбувається звернення до даних, що зберігаються в стеку. Стек - це системна область пам'яті, звернення до якої здійснюється за принципом FILO. Цей регістр зазвичай ніколи не використовується для виконання звичайних арифметичних операцій і команд пересилки даних. Його часто називають розширеним регістром вказівника стеку (extended stack pointer).

Регістри ESI і EDI зазвичай використовують для команд високошвидкісної пересилки даних з однієї ділянки пам'яті в іншій. Тому їх іноді називають розширеними індексними регістрами джерела іодержувача даних (extended source index і extended destination index).

Регістр EBP зазвичай використовується в мовах програмування високого рівня для звернення до параметрів функції і для посилань на локальні змінні, розміщені в стеку. Він не повинен використовуватися для виконання звичайних арифметичних операцій або для переміщення даних, за винятком випадків застосування особливих методик програмування досвідченими розробниками програмного забезпечення. Його часто називають розширеним регістром вказівника стекового фрейму (extended frame pointer).

Сегментні регістри. Ці регістри використовуються як базові при зверненні до заздалегідь розподілених областей оперативної пам'яті, які називаються сегментами. Існує три типи сегментів і, відповідно, сегментних регістрів:

  коду (CS), в них зберігаються тільки команди процесора, тобто машинний код програми;

  даних (DS, ES, FS і GS), в них зберігаються області пам'яті, що виділяються під змінні програми;

  стеку (SS), в них зберігається системна область пам'яті, яка називається стеком, в якій розподіляються локальні (тимчасові) змінні програми і параметри, які передаються функціям при їх виклику.

Регістр вказівника команд. У регістрі EIP, який також називають регістром вказівника команд, зберігається адреса наступної виконуваної команди. У процесорі є декілька команд, які впливають на вміст цього регістра. Зміна адреси, що зберігається в регістрі EIP, викликає передачу керування на нову ділянку програми.

Регістр прапорів EFLAGS. Кожен біт цього регістра відповідає або за особливості виконання деяких команд ЦП, або відображає результат виконання команд блоком АЛП процесора. Для аналізу бітів цього регістра передбачені спеціальні команди процесора.

Говорять, що прапор встановлений, коли значення відповідного йому біта регістра EFLAGS рівне 1, і що прапор скинутий, коли значення його біта рівне 0.

Прапори керування. Стан бітів регістра EFLAGS, які відповідають керуючим прапорам, програміст може змінити за допомогою спеціальних команд процесора. Ці прапори керують процесом виконання деяких команд ЦП. Як приклад можна привести прапори управління напрямом пересилки даних (Direction)і перериванням (Interrupt).

Прапори стану. Ці прапори відображають результат виконання арифметичної або логічної команди ЦП. До них відносяться:

Прапор перенесення (Carry flag, або CF) встановлюється у випадку, якщо при виконанні беззнакової арифметичної операції виходить число, розрядність якого перевищує розрядність виділеного для нього поля результату.

Прапор переповнення (Overflow flag, або OF) встановлюється у випадку, якщо при виконанні арифметичної операції із знаком виходить число, розрядність якого перевищує розрядність виділеного для нього поля результату.

Прапор знаку (Sign flag, або SF) встановлюється, якщо при виконанні арифметичної або логічної операції виходить негативне число (тобто старший біт результату рівний 1).

Прапор нуля (Zero flag, або ZF) встановлюється, якщо при виконанні арифметичної або логічної операції виходить число, рівне нулю (тобто всі біти результату рівні 0).

Прапор службового перенесення (Auxiliary Carry, або AF) встановлюється, якщо при виконанні арифметичної операції з 8-розрядним операндом відбувається перенесення з третього біта в четвертий.

Прапор парності (Parity flag, або PF) встановлюється у випадку, якщо в результаті виконання арифметичної або логічної операції виходить число, що містить парну кількість одиничних бітів.

 

2.     Математичний співпроцесор

Сімейство процесорів IA-32 містить так званий модуль операцій з плаваючою крапкою (Floating-pointUnit, або FPU), який використовується виключно для швидкого виконання цього типу операцій. У процесорах Intel 386 цей блок був реалізований у вигляді окремої мікросхеми математичного співпроцесора, яка позначалася як Intel 387. Проте починаючи з процесорів Intel 486 математичний співпроцесор почав знаходитися на одному кристалі з основним процессором.

У модулі FPU міститься 8 внутрішніх регістрів для зберігання даних з плаваючою комою, які називаються ST(0), ST(1), ST(2), ST(3), ST (4), ST(5), ST(6) і ST (7). Решта регістрів, що виконують функції керування і зберігають вказівники, показані на рис. 1.15.

 

3.     Розширений набір регістрів для роботи з мультимедійними додатками, який включає:

  вісім 64-розрядних регістрів, що використовуються в так званих ММХ-командах;

  вісім 128-розрядних ХММ регістрів, що використовуються при виконанні потокової обробки даних (SIMD-операций), тобто коли за допомогою однієї машинної команди можна виконати одну і ту ж операцію над декількома даними (Single-instruction, Multiple-data, або SIMD).

 

Pict10

Рисунок 1.15 Регістри математичного співпроцесора

 

У перших процесорах фірми Intel, які встановлювалися в персональні комп'ютери типу IBM-PC, була застосована ідеологія так званого повного набору команд (Complete-instruction-set Computing, або CISC).У системі команд процесорів Intel були передбачені досить розвинені методи адресації даних, а також можливості виконання дуже складних високорівневих операцій. Логіка розробників була зрозумілою: чим складніша і розвиненіша система команд процесора, тим ефективніше працюватимуть програми, скомпільовані з мови високого рівня.

Проте основним недоліком архітектури CISC було те, що на декодування і виконання складних машинних команд було потрібно досить багато машинних тактів. Для виконання таких команд використовувалася спеціальна інтерпретуюча мікропрограма, зашита в ядро ЦП. Оскільки в перших процесорах була реалізована архітектура з повним набором команд (CISC), для сумісності на рівні машинних кодів потрібно було забезпечити її підтримку у всіх подальших версіях цих процесорів. В результаті програми, написані для перших ПК типу IBM-PC можуть виконуватися без зміни на сучасних процесорах Pentium 4.

Проте при проектуванні високошвидкісних процесорів зазвичай застосовується повністю протилежний описаному вище підхід - мова йде про так звану архітектуру з скороченим набором команд (Reduced Instruction Set, або RISC). Подібні процесори підтримують відносно невелике число коротких і простих команд, які можна дуже швидко декодувати і виконати. На відміну від використання мікропрограмного інтерпретатора для декодування і виконання CISC-команд, в RISC-процесорах цю функцію виконують спеціальні електронні схеми.

Процесори архітектури RISC знаходять своє застосування у сфері цифрової обробки даних, системах високопродуктивних обчислень, різноманітних засобах звязку. Архітектура CISC найчастіше використовується для реалізації універсальних процесорів, що володіють широким набором можливостей і є основними обчислювальними елементами сучасних персональних компютерів.

Лекція 4 Система переривань. Таблиця векторів. Система вводу-виводу

 

1.     Система переривань. Виклик переривання. Таблиця векторів переривань.

2.     Система вводу-виводу.

 

1.     Система переривань. Виклик переривання. Таблиця векторів переривань.

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

Зовнішні переривання викликаються зовнішніми по відношенню до мікропроцесора подіями. Внутрішні переривання виникають всередині мікропроцесора під час обчислювального процесу.

 

Рисунок 1 Підсистема переривань

 

Із рис. 1 видно, що МП має два фізичних контакти INTR і NMI, на які поступають сигнали від зовнішніх пристроїв. Вхід INTR (INTerrupt Request) призначений для фіксації запитів від різних периферійних пристроїв. Вхід NMI (Non Maskable Interrupt) немасковане переривання використовується для невідкладної обробки деяких подій або катастрофічних помилок (не може бути заборонене програмами).

 

Внутрішні переривання виникають по наступних причинах:

  ненормальний внутрішній стан МП, що виник під час обробки деякої команди програми. Такі події називають винятками. Розробниками Intel визначено наступний вектор винятків:

  1. Ділення на нуль
  2. Один крок (використовується відладчиками)
  3. NMI перивання
  4. Точка розриву (використовується відладчиками)
  5. INTO інструкція виявила переповнення
  6. BOUND інструкція виявила порушення діапазону
  7. Неправильний opcode
  8. Розширення процесора не доступне
  9. Виняток подвійного дефекту виявлено
  10. Вихід за межі процесорного розширення сегменту
  11. Недопустимий сегмент стану задачі
  12. Відсутній сегмент
  13. Вихід за межі сегменту стека
  14. Помилка загального захисту
  15. Помилка сторінки
  16. Зарезервовано
  17. Помилка з плаваючою крапкою
  18. Перевірка вирівнювання (коли включена, то генерується виняток коли операнд не відповідно вирівняний)

Винятки 0-5 визначені в 8086, 6-13 добавлені в 80286, 14-17 добавлені в 80386 і пізніших МП.

  обробка машинної команди int xx (програмні переривання). Це плановані переривання, які обробляє ОС, BIOS або програма обробки переривань розробника.

 

У загальному випадку система переривань це сукупність програмних і апаратних засобів, що реалізовують механізм переривань.

До апаратних засобів системи переривань відносяться:

  1. Виводи МП:

  INTR вивід для вхідного сигналу зовнішнього переривання. На цей вхід поступає вхідний сигнал від мікросхеми контролера переривань 8259A;

  INTA вивід МП для вихідного сигналу підтвердження отримання сигналу переривання МП. Цей вихідний сигнал поступає на однойменний вхід INTA мікросхеми контролера переривань 8259А;

  NMI вивід МП для вхідного сигналу немаскованого переривання.

  1. Мікросхема програмованого контролера переривань 8259А. Вона призначена для фіксації сигналів переривань від восьми різних зовнішніх пристроїв.
  2. Зовнішні пристрої: таймер, клавіатура, магнітні диски.

До програмних засобів системи переривань реального режиму відносяться:

  таблиця векторів переривань. В цій таблиці містяться вказівники на процедури обробки відповідних переривань;

  наступні прапори в регістрі прапорів :

  • IF (Interrupt Flag) прапор переривань. Призначений для так званого маскування (заборони) апаратних переривань, тобто переривань по входу INTR. Якщо IF=1, то МП обробляє зовнішні переривання, а якщо IF=0, то МП ігнорує сигнали на вході INTR;
  • TF (Trace Flag) прапор трасування. Якщо TF=1, то МП переходить у режим покомандної роботи;
  • Машинні команди МП: intint0iretclisti.

 

Програмування контролера переривань i8259A

Контролер переривань може працювати в одному із чотирьох режимів:

  1. FNN (Fully Nested Mode) режим вкладених переривань. В цьому режимі кожному входу irg0 irg7 присвоюється фіксоване значення пріоритету (irq0 . найвищий пріоритет). Пріоритет визначає право переривання менш пріоритетного переривання більш пріоритетним.
  2. ARM (Automatic Rotation Mode) режим циклічної обробки переривань. Пріоритети змінюються по принципу: останнє обслужене переривання отримує найвищий пріоритет.
  3. SRM (Specific Rotation Mode) режим адресованих пріоритетів. Система або програміст самостійно можуть назначити перериванню найвищий пріоритет.
  4. PM (Polling Mode) режим опитування. В цьому режимі процесору заборонено переривати роботу МП при перериванні від зовнішніх пристроїв. Ініціатором обробки переривань стає не саме переривання, а МП у визначені ним моменти часу.

 

Програмування контролера переривань здійснюється через адресний простір вводу/виводу з використанням двох 8-бітових портів з адресами 20h і 21h. Керування здійснюється шляхом посилання в певній послідовності у ці порти наказів двох типів:

  ICW (Initialization Control Word) керуюче слово ініціалізації. Таких слів чотири:

  • ICW1 (визначення особливостей послідовності наказів);
  • ICW2 (визначення базової адреси);
  • ICW3 (звязок контролерів);
  • ICW4 (додаткові особливості обробки переривань).

  OCW (Operation Control Word) операційне керуюче слово. Таких слів три:

  • OCW1 (керування регістром масок) ;
  • OCW2 (керування пріоритетом);
  • OCW3 (загальне керування контролером).

 

Реальний режим роботи контролера

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

  простір оперативної памяті ділиться на сегменти по 64 Кб. Сегменти у памяті можуть перериватися;

  сторінкове перетворення адреси заборонено, а фізична адреса дорівнює лінійній і формується як сума двох складових:

  • 16-розрядної ефективної адреси, яка є сумою трьох складових: бази, зміщення і індексу;
  • отримання 20-розрядний результату шляхом зсуву вмісту сегментного регістру на 4-ри розряди вліво;

  максимальне значення фізичного адресу h, тобто 1 Мб;

  схема розподілу оперативної памяті фіксована:

  • в діапазоні 00000h - 003FFh знаходиться таблиця вектора переривань. Вона має 256 векторів переривань (вказівників на програми) розміром 4 байти;
  • в діапазоні адресів 00400h - 006FFh, зразу за таблицею векторів переривань, розміщується область памяті з даними, що забезпечують роботу BIOS і MS-DOS;
  • з адреси 0B8000h розміщується область відео памяті, в якій формується зображення для екрану.

 

Обробка переривань у реальному режимі роботи МП

Обробка переривань (як зовнішніх, так і внутрішніх) проводиться в три етапи:

  1. Зупинка виконання поточної програми.
  2. Перехід до програми обробки переривань.
  3. Повернення керування перерваній програмі.

 

Перший етап повинен забезпечити тимчасове припинення виконання поточної програми. Кожна програма займає своє окреме місце в ОП.

Всі програми використовують регістри МП, в тому числі і регістр прапорів. Тому їх потрібно зберігати. При виникненні переривань, МП автоматично зберігає регістри CS, IP, EFLAGS/FLAGS. ПараCS:IP містить адресу команди, з якої необхідно продовжити виконання програми, після обробки переривання. Збереження вмісту інших регістрів покладено на розробника. Найбільш зручним місцем для зберігання регістрів є стек. В кінці першого етапу, після запису в стек регістрів CS, IP, EFLAGS/FLAGS, МП скидає біт прапора переривань IF в регістрі flags. Цим самим запобігається можливість вкладених переривань по входу INTR і псування регістрів початкової програми.

Після того як будуть виконані необхідні дії по збереженню контексту, обробник апаратних переривань може дозволити вкладені переривання командою sti. Команда sti встановлює прапор IF=1 і таким чином дає дозвіл на переривання.

На другому етапі визначається джерело переривань і викликається відповідна програма обробки. В реальному режимі МП допускається від 0 до 255 джерел переривань. Кількість джерел обмежена розміром таблиці вектора переривань.

 

Таблиця векторів переривань

Таблиця вектора переривань є посередником між джерелом переривання і процедурою обробки. Кожний елемент таблиці займає 4 байти і має наступну структуру:

  1-е слово елемента значення зміщення початку процедури обробки переривання (n) від початку кодового сегменту;

  2-е слово елемента значення базової адреси сегменту, де знаходиться процедура обробки переривання.

Визначити адресу, за якою знаходиться вектор переривань з номером n, можна наступним чином:зміщення_елементу_таблиці = n *4 .

МП на другому етапі виконує наступні дії:

  1. По номеру джерела переривань шляхом множення на 4 визначає зміщення в таблиці
  2. переривань.
  3. Поміщує перші два байти по вичисленому адресу в регістр IP.
  4. Поміщує другі два байти по вичисленому адресу в регістр CS.
  5. Передає керування по адресу, визначеному парою CS:IP.

 

Після цього виконується сама програма обробки переривань. Вона, в свою чергу може бути перервана. У цьому випадку етапи 1 і 2 будуть повторені для нового переривання.

Набір дій третього етапу полягає у відновленні контексту перерваної програми. Тут є дії, які виконуються МП автоматично, і дії які виконує програміст. Програміст вказує необхідні дії по відновленню регістрів і очистці стека. Цю частину коду необхідно захистити від можливого спотворення вмісту регістрів (в результаті нового апаратного переривання) за допомогою команди cli.

Останні команди у процедурі обробки переривань . stiiret:

  •   дозволити апаратні переривання по входу INTR;
  •   видобути послідовно три слова із стека і помістити їх, відповідно, в регістри IP, CS,FLAGS.

В результаті, керування передається черговій команді перерваної програми. Апаратні переривання можуть бути ініціалізовані програмно, командою int n, де n номер апаратного переривання згідно таблиці векторів переривань. При цьому МП також скидає прапор IF, але не формує сигнал INTA.

Лекція 5 Принципи організації памяті. Види адресації

 

1.     Принципи організації памяті. Моделі памяті. Види адресації. Поняття сегменту.

2.     Сторінкова організація памяті та файл підкачки. Абсолютна та відносна адреси. Переваги та недоліки сегментної організації памяті.

 

 

1.     Принципи організації памяті. Моделі памяті. Види адресації. Поняття сегменту.

У сімействі процесорів IA-32 вибір методу звернення до пам'яті визначається режимом роботи процесора.

У реальному режимі процесор може звертатися тільки до першого мегабайта пам'яті, адреси якого знаходяться в діапазоні від 00000 до FFFFF в шістнадцятковому записі. При цьому процесор працює воднопрограмному режимі (тобто в заданий момент часу він може виконувати тільки одну програму). Проте, при цьому він може у будь-який момент перервати її виконання і перемкнутися на процедуру обробки запиту (його називають перериванням), що поступив від одного з периферійних пристроїв.

Будь-якій програмі, яку виконує у цей момент процесор, дозволений доступ без обмеження до будь-яких областей пам'яті, що знаходяться в межах першого мегабайта: до ОЗП - по читанню і запису, а до ПЗП, зрозуміло, тільки по читанню. Реальний режим роботи процесора використовується в операційній системі MS DOS, а також в системах класу Windows при завантаженні в режимі емуляції MS DOS.

У захищеному режимі процесор може одночасно виконувати декілька програм. При цьому, кожному процесу (тобто програмі, що виконується) може бути призначено до 4 Гбайт оперативної пам'яті. Щоб запобігти взаємному впливу виконуваних програм, їм виділяються ізольовані ділянки пам'яті (тобто код і дані програм знаходяться у взаємно несуміжних сегментах). У захищеному режимі працюють такі ОС, якMS Windows, Unix, Linux, MacOS.

У віртуальному режимі адресації процесора 8086, останній насправді працює в захищеному режимі. Для кожного завдання створюється власна віртуальна машина, якою виділяється ізольована область пам'яті розміром 1 Мбайт, і повністю емулюється робота процесора 80x86 в реальному режимі адресації. Наприклад, в операційних системах Windows 2000 і ХР віртуальна машина процесора 8086 створюється кожного разу при завантаженні користувачем вікна командного інтерпретатора (сеансу MS DOS). При цьому одночасно можна завантажити досить багато таких вікон, причому програми, що виконуються в них, не впливатимуть одна на одну.

В ряді випадків, частина програм, створених для системи MS DOS і реального режиму адресації, безпосередньо взаємодіють з апаратним забезпеченням комп'ютера. Тому можливо, що вони не працюватимуть в середовищі ОС Windows 2000 і ХР.

 

 

Реальний режим адресації

У реальному режимі процесор може звертатися тільки до перших 1 048 576 байтів (1 Мбайт) ОЗП, оскільки при цьому він використовує тільки 20 молодших розрядів шини адреси. Отже, діапазон адрес пам'яті, виражених в шістнадцятковому представленні, складатиме від 00000 до FFFFF. Основна проблема, з якою зіткнулися інженери фірми Intel, полягала в тому, що за допомогою 16-розрядних регістрів процесор 8086 не міг безпосередньо працювати з 20-розрядними адресами оперативної пам'яті. Тому була реалізована спеціальна схема адресації, яку назвали сегментацією пам'яті. Суть її полягала в тому, що весь доступний адресний простір розділявся на блоки розміром 64 Кбайт, які називалисясегментами (рис. 1.16).

Як аналогія тут доречно привести великий багатоповерховий будинок, в якому сегменти позначатимуть номер поверху. Відвідувач може піднятися на ліфті на потрібний йому поверх, пройти по коридору і знайти потрібну йому кімнату по вказаному зсуву (offset), який можна трактувати як відстань в метрах від ліфта до дверей кімнати.

Тут варто звернути увагу на те, що молодша шістнадцяткова цифра в адресі кожного сегменту рівна нулю. Іншими словами, адреса будь-якого сегменту завжди буде кратна 16 байтам. А раз так, при записі адреси сегменту останню цифру можна опустити. Таким чином, якщо, наприклад, вказано 16-розрядне сегментне значення С000, воно відповідатиме сегменту, розташованому в пам'яті починаючи з адреси С0000.

На рис. 1.16 зображений також вміст сегменту, що починається з адреси 80000. Для звернення до будь-якого байта цього сегменту потрібне 16-розрядне зміщення (його значення може знаходитися в межах від 0000 до FFFF), яке потрібно буде додати до базової адреси сегменту. Як приклад розглянута адреса байта, заданий у формі сегмент-зміщення: 8000:0250. Така форма запису означає, що потрібний нам байт розташований із зміщенням 0250 від початку сегменту, розташованого за адресою 80000. Лінійна адреса виглядатиме наступним чином: 80250h.

Pict11

 

Рисунок 1.16 Сегментна організація памяті в реальному режимі

адресації процесора 8086

 

Обчислення 20-розрядної лінійної адреси

По суті, адреса елементу пам'яті - це звичайне число, що вказує її порядковий номер відносно початку пам'яті, тобто нульової адреси. Як вже було сказано, в реальному режимі лінійна (тобтоабсолютна) адреса має довжину 20 біт, а її значення може знаходитися в діапазоні від 00000 до FFFFF в шістнадцятковому записі. Проте в самих 16-розрядних програмах безпосередньо оперувати лінійними адресами не можна. Тому абсолютні адреси елементів пам'яті задаються в них у вигляді двох 16-розрядних чисел, що визначають адресу у формі сегмент-зміщення таким чином:

  16-розрядна адреса початку поміщається в один з шести сегментних регістрів (CS, DS, ES,SS, FS або GS), який явним або неявним чином указується при виконанні кожної команди;

  програми безпосередньо оперують тільки 16-розрядним вказаним відносно початку сегменту.

Адреси, задані в програмах у формі сегмент-зміщення, автоматично перетворюються ЦП в 20-розрядні лінійні адреси в процесі виконання команди.

 

Приклад. Припустимо, що адреса деякої змінної, заданої в шістнадцятковому вигляді і у формісегмент-зміщення, рівна 08F1:0100. При обчисленні лінійної адреси ЦП повинен помножити сегментну частину адреси на 10h і додати до отриманого результату зміщення, як показано нижче:

08F1 * 10 = 0 8 F 1 0 (Лінійна адреса початку сегменту)

 

До адреси початку сегменту: 0 8 F 1 0

Додаємо зсув: 0 1 0 0

Отримуємо лінійну адресу: 0 9 0 1 0

 

У типовій програмі, написаній для процесорів сімейства IA-32, як правило, є три сегменти: коду, даних і стеку. При завантаженні програми, їх базові сегментні адреси поміщаються в регістри CSDS і SS, відповідно. У трьох регістрах ES, що залишилися, FS і GS програма може зберігати вказівники на додаткові сегменти.

 

Захищений режим

Це самий розвинутий режим роботи процесора, в якому можна реалізувати всі його можливості,придумані виробниками. При роботі в захищеному режимі, кожній програмі може бути виділений блок пам'яті розміром до 4 Гбайт, адреси якого в шістнадцятковому форматі можуть мінятися від 00000000 доFFFFFFFF. При цьому говорять, що програмі виділяється лінійний адресний простір (flat address space),який розробники компілятора Microsoft Assembler назвали лінійною моделлю пам'яті (flat memory model).З точки зору розробника програмного забезпечення, лінійна модель найбільш проста в використанні, оскільки для зберігання адреси будь-якої змінної або команди достатньо одного 32-розрядного цілого числа. Ця ілюзія простоти багато в чому досягається за рахунок того, що частину роботи по реалізаціївбудованих можливостей процесора виконує операційна система. У захищеному режимі в сегментних регістрах (CS, DS, SS, ES, FS, GS) зберігаються не 16-розрядні базові адреси сегментів, а вказівники на дескриптори сегменту (segment descriptor), розташовані в одній із системних таблиць дескрипторів (descriptor table). За інформацією, що знаходиться в дескрипторі, операційна система визначає лінійні адреси сегментів програми.

У типовій програмі, написаній для захищеного режиму, як правило, є три сегменти: коду, даних і стеку, інформація про яких зберігається в трьох перерахованих нижче сегментних регістрах:

  1. У регістрі CS зберігається вказівник на дескриптор сегменту коду програми.
  2. У регістрі DS зберігається вказівник на дескриптор сегменту даних програми.
  3. У регістрі SS зберігається вказівник на дескриптор сегменту стеку програми.

 

Лінійно-сегментна модель пам'яті

У цій моделі (flat segmentation model) дескриптори всіх сегментів вказують на один і той самий сегмент пам'яті, який відповідає всьому 32-розрядному фізичному адресному простору комп'ютера. При цьому, для кожної програми, операційна система створює в своїх таблицях всього два дескриптори - один для сегменту коду, а інший для сегменту даних.

Як вже було сказано, в захищеному режимі кожен сегмент визначається за допомогою відповідногодескриптора - 64-розрядного числа, що зберігається в спеціальній системній таблиці, яка називаєтьсятаблицею глобальних дескрипторів (Global Descriptor Table, або GDT).

На рис. 1.17 показаний вміст дескриптора сегменту, в полі базової адреси (base address) якого зберігається вказівник на перший доступний байт оперативної пам'яті комп'ютера, що має нульову адресу (00000000). Значення поля, що визначає межу сегменту (segment limit), може свідчити (але не завжди) про розмір фізичної пам'яті, встановленої в комп'ютері. В даному випадку значення поля межі сегменту рівне 4000. Біти поля доступу (access field) дескриптора сегменту визначають спосіб використання сегменту.

Pict12

 

Рисунок 1.17 Ілюстрація лінійно-сегментної моделі памяті

 

Багатосегментна модель пам'яті

При використанні багатосегментної моделі пам'яті для кожної програми виділяється власна таблиця сегментних дескрипторів, яка називається таблицею локальних дескрипторів (Local Descriptor Table, абоLDT). При цьому з'являється можливість для кожного процесу створити власний набір сегментів, які ніяк не перетинаються з сегментами інших процесів, навіть якщо значення їх дескрипторів, що знаходяться в сегментних регістрах, співпадають. В результаті цього, кожен сегмент знаходиться в ізольованому адресному просторі.

На рис. 1.18 показано, що кожен елемент таблиці локальних дескрипторів визначає різні сегменти пам'яті. У кожному дескрипторі сегменту вказується його точна довжина. Наприклад, сегмент, що починається з адреси 3000, має довжину 2000 байтів в шістнадцятковому значенні, оскільки значення поля дескриптора, що визначає межу сегменту, рівне 0002, а 0002 х 1000 = 2000. Аналогічно, довжина сегменту, що починається з адреси 8000, рівна А000.

Pict13

Рисунок 1.18 Ілюстрація багатосегментної моделі памяті

 

2.     Сторінкова організація памяті та файл підкачки. Абсолютна та відносна адреси. Переваги та недоліки сегментної організації памяті.

У процесорах сімейства IA-32 підтримується одна дуже важлива можливість, яка називаєтьсясторінковою організацією пам'яті (paging). Вона дозволяє розділити сегмент на блоки пам'яті розміром 4096 байтів, які називаються сторінками (page). В результаті можна легко зробити так, щоб сумарний об'єм оперативної пам'яті, використовуваної у всіх програмах, що виконуються на комп'ютері, перевищував об'єм реальної (тобто фізичної) пам'яті комп'ютера. Саме тому сторінкова організація пам'яті дуже часто називається віртуальною пам'яттю (virtual memory). Працездатність системи віртуальної пам'яті забезпечує спеціальна програма, що є частиною операційної системи, яка називається диспетчеромвіртуальної пам'яті (virtual memory manager).

Сторінкова організація пам'яті дуже добре вирішує проблему всіх розробників апаратного і програмного забезпечення - проблему браку пам'яті.

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

При виконанні програми, ділянки її оперативної пам'яті (або сторінки), які не використовуються в даний момент, можна зберегти на диску. Говорять, що частина завдання витіснена (swapped) на диск. У оперативній пам'яті комп'ютера є сенс зберігати тільки ті сторінки, до яких процесор активно звертається, наприклад, виконує деякий програмний код. Якщо ж процесор повинен звернутися до сторінки пам'яті, яка зараз витіснена на диск, відбувається системна помилка (або переривання) через відсутність сторінки (pagefault). Обробкою цієї помилки займається диспетчер віртуальної пам'яті операційної системи, який знаходить на диску сторінку, що містить потрібний код або дані, і завантажує її у вільну ділянку оперативної пам'яті.

Лекція 6 Програмування на алгоритмічній мові Асемблер

 

1.     Програмування на алгоритмічній мові Асемблер. Основні поняття. Основні частини програми на мові Асемблер.

2.     Формат команд мови Асемблер. Підготовка початкової асемблерної програми в EXE або COM -форматах. Компіляція. Відлагодження.

 

1.     Програмування на алгоритмічній мові Асемблер. Основні поняття. Основні частини програми на мові Асемблер.

 

На рис. 1 зображено повний цикл створення програми в системі Turbo Assembler, який включає ряд етапів:

  •   . Можна виконувати з допомогою будь-якого текстового редактора, який записує документ в ASCII вигляді. Текст програми потрібно зберегти у файлі з розширенням .ASM.

  асемблювання. На даному етапі текст програми перетворюється в проміжну форму, яка називається об'єктним модулем.

Рисунок 1 - Цикл створення програми в системі Turbo Assembler

 

Якщо текст програми введено правильно, то не повинно бути ніяких повідомлень про помилки чи попередження. Якщо такі повідомлення отримуються, то вони з'являються на екрані разом з номерами помилкових стрічок програми. Необхідно перевірити текст та внести необхідні виправлення. Об'єктний файл не створюється, якщо компілятор виявив помилки в програмі.

  компонування. Один чи кілька об'єктних модулів, а також бібліотеки зв'язуються в один виконуваний файл. На цьому етапі створення програм використовується компонувальник TLINK.

 

Приклад програми:

 

TITLE EXASM1 Приклад регістрових операцій

;-----------------------------------------------------

STACKSG SEGMENT PARA 'Stack'

DB 12 DUP('STACKSEG')

STACKSG ENDS

;-----------------------------------------------------

CODESG SEGMENT PARA 'Code'

BEGIN PROC FAR

ASSUME CS:CODESG, DS:NOTHING, SS:STACKSG

PUSH DS

SUB AX, AX

PUSH AX

MOV AX, DATASG ; Якщо є сегмент даних

MOV DS, AX

MOV AX, 0123h

ADD AX, 0025h

MOV BX, AX

ADD BX, AX

MOV CX, BX

SUB CX, AX

SUB AX, AX

NOP

RET

BEGIN ENDP

CODESG ENDS

END BEGIN

 

2.      Формат команд мови Асемблер. Підготовка початкової асемблерної програми в EXE або COM -форматах. Компіляція. Відлагодження.

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

Рисунок х.х Загальний формат команди на мові Асемблер

Розрізняють наступні режими адресації даних в командах:

  •        - дані довжиною 8 або 16 біт є частиною команди;

       пряма - 16-розрядна виконавча адреса даних є частиною команди;

       регістрова - 8- або 16-розрядні дані знаходяться у визначеному командою відповідно 8-розрядному або 16-розрядному регістрі;

       регістрова непряма - виконавча (ефективна) адреса ЕА знаходиться в одному з регістрів ВХ, SI,DI;

       регістрова відносна - виконавча адреса, рівна сумі вмісту одного з регістрів ВР, ВХ, SI, DI і 8- або 16-розрядного зміщення;

       базова індексна - виконавча адреса, рівна сумі вмісту одного з базових регістрів ВР, ВХ і одного з індексних регістрів SI, DI;

       відносна базова індексна - виконавча адреса, рівна сумі вмісту одного з базових регістрів ВР, ВХ, одного з індексних регістрів SI, DI і 8-або 16-розрядного зміщення

 

Крім того, розрізняють наступні режими адресації переходів:

        внутрішньосегментна пряма - виконавча адреса переходу рівна сумі поточного вмісту покажчика команд IP і 8- або 16-розрядного зміщення; допустима в командах умовного і безумовного переходів, але в першому випадку може використовуватися тільки 8-розрядне зміщення;

        внутрішньосегментна непряма - виконавча адреса переходу являє собою вміст регістра або елементу пам'яті, які вказуються в будь-якому режимі (окрім безпосередньої) адресації даних; вміст IP замінюється виконавчою адресою переходу; допустимо тільки в командах безумовного переходу;

        міжсегментна пряма - замінює вміст IP однією частиною, а вміст CS іншою частиною команди;

        міжсегментна непряма - замінює вміст IP і CS вмістом двох суміжних 16-розрядних елементів пам'яті, які визначаються в будь-якому режимі адресації даних, окрім безпосереднього і регістрового.

Міжсегментний перехід може бути тільки безумовним.

 

В операційній системі DOS виконувані програми можуть мати формат СОМ або ЕХЕ. Відмінності:

  розмір програми. Програма в форматі ЕХЕ може мати довільний розмір, тоді як розмір СОМ-програми обмежений розміром одного сегменту і не перевищує 64К. Розмір СОМ-файлу завжди менший, ніж розмір відповідного ЕХЕ-файлу, причина - наявність в ЕХЕ-файлі спеціального 512-байтового заголовку.

  сегмент стеку. В ЕХЕ-програмі визначається сегмент стеку, а СОМ-програма генерує його автоматично (для такої програми стек повинен бути опущений).

  сегмент даних. В ЕХЕ-програмі зазвичай визначається сегмент даних і регістр DS ініціалізується адресою цього сегменту. В СОМ-програмі всі дані повинні бути визначені в сегменті коду.

  •   . Операційна система (ОС) DOS має чотири вимоги до ініціалізації EXE-програми. Для цього потрібно:
    1. Вказати які сегментні регістри повинні відповідати сегментам.
    2. Зберегти в стеку значення, що вказує на початкову адресу виконання програми.
    3. Записати в стек нуль.
    4. Завантажити в регістр DS адресу сегменту даних.

 

Компіляція (трансляція) програми здійснюється з використанням спеціалізованих компонентів і полягає у перетворенні створеного програмного коду в послідовність мікрокоманд для їх подальшого виконання центральним процесором. Варто зауважити, що компілятори (транслятори) є не універсальними програмними компонентами. Дуже часто розробники створюють для своїх процесорів власні набори засобів для компіляції (трансляції), які орієнтовані на використання для їх конкретних архітектур, наприклад: IA-32, EM64T, AMD64, 
IA-64. Застосування невідповідних компіляторів для розробки програм як правило не дає змоги використати особливості архітектурних та технологічних рішень конкретного процесора (групи процесорів), що може відобразитись на ефективності роботи створених програм.

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

Лекція 7 Типи даних. Визначення даних. Директиви

 

1.     Типи даних. Команди. Псевдокоманди. Визначення даних.

2.     Директиви. Класичні та спрощені директиви сегментації.

 

1.     Типи даних. Команди. Псевдокоманди. Визначення даних

У MASM визначено декілька внутрішніх типів даних, значення яких можуть бути привласнені змінним, або вони можуть бути результатом виконання виразу. Наприклад, в змінній типу DWORD можна зберегти будь-яке 32-розрядне ціле значення. Проте на деякі типи накладаються жорсткіші обмеження. Наприклад, змінній типу REAL4 можна привласнити тільки дійсну константу.

Представлені в табл. 2.1 типів даних відносяться до цілочисельних значень, за винятком останніх трьох. При описі цих трьох типів використовується абревіатура "IEEE", яка означає, що ці типи даних відповідають стандарту представлення дійсних чисел, прийнятому відділенням інформатики Інституту інженерів по електротехніці і електроніці (IEEE).

 

Таблиця 2.1 Внутрішні типи даних

Тип

Опис

BYTE

8-розрядне беззнакове ціле

SBYTE

8- розрядне знакове ціле

WORD

16- розрядне беззнакове ціле (в режимі реальної адресації може використовуватись для збереження ближнього вказівника)

SWORD

16- розрядне знакове ціле

DWORD

32- розрядне беззнакове ціле (в захищеному режимі може використовуватись для збереження ближнього вказівника)

SDWORD

32- розрядне знакове ціле

FWORD

48- розрядне ціле (в захищеному режимі може використовуватись для збереження дальнього вказівника)

QWORD

64- розрядне ціле

TBYTE

80- розрядне (10-байтне) ціле

REAL4

32- розрядне (4-байтне) короткое дійсне, відповідає формату IEEE

REAL8

64- розрядне (8-байтне) довге дійсне, відповідає формату IEEE

REAL10

80-разрядное (10-байтне) розширене дійсне, відповідає формату IEEE

 

Оператор визначення даних

За допомогою оператора визначення даних в програмі резервується область пам'яті відповідної довжини для розміщення змінної. При необхідності цій змінній можна призначити ім'я. Оператори визначення даних використовуються в програмі на асемблері для створення змінних, типи яких перелічені в табл. 2.1.

Синтаксис оператора наступний:

Ініціалізатори. При визначенні даних повинен бути вказаний хоч би один ініціалізатор, навіть якщо змінною не призначається якогось конкретного значення (в цьому випадку значення ініціалізатора рівне ?).

Всі додаткові ініціалізатори перераховуються через кому. Для цілочисельних типів данихініціалізатор є цілочисельною константою або виразом, значення якого відповідає розміру визначених даних (BYTE, WORD, і т. д.).

Незалежно від використовуваного формату чисел, всі ініціалізатори автоматично перетворюються Асемблером в двійкову форму. Іншими словами, в результаті компіляції ініціалізаторів 00110010b, 32h і 50d буде отримане однакове двійкове значення.

 

Визначення змінних типу BYTE і SBYTE

Директиви BYTE (визначає беззнаковий байт) і SBYTE (визначає знаковий байт) використовуються в операторах визначення даних, за допомогою яких в програмі виділяється пам'ять під одну або декілька знакових або беззнакових змінних довжиною 8 біт. Кожен ініціалізатор повинен бути або 8-розрядним цілочисельним виразом або символьною константою.

Наприклад:

 

valuel BYTE 'a' ; Символьна константа

value2 BYTE 0 ; Найменше беззнакове байтове значення

value3 BYTE 255 ; Найбільше беззнакове байтове значення

value4 SBYTE -128 ; Найменше знакове байтове значення

value5 SBYTE +127 ; Найбільше знакове байтове значення

 

Для наочності ключові слова BYTE і SBYTE виділені прописними буквами, але можуть бути записані і рядковими буквами.

Щоб залишити змінну неініціалізованою (тобто при виділенні під неї пам'яті, але не привласнювати їй ніякого значення), замість ініціалізатора використовується знак питання. Така форма запису припускає, що значення даної змінної буде призначено під час виконання програми за допомогою спеціальних команд процесора. Ось приклад:

value6 BYTE ?

 

Імена змінних. Насправді ім'я змінної є міткою, значення якої відповідає зсуву (зміщенню) даної змінної відносно початку сегменту, в якому вона розташована. Наприклад, припустимо, що змінна valuel розташована в сегменті даних із зсувом 0 і займає один байт пам'яті. Тоді змінна value2 розташовуватиметься в сегменті із зсувом 1:

.data

valuel BYTE 10h

value2 BYTE 20h

 

Директива DB. У попередніх версіях MASM для визначення байта даних використовувалася директива DB. У нинішній версії компілятора MASM ви можете як і раніше використовувати цю директиву, але тоді компілятор не зможе відрізнити, до якого типу (знакового або беззнакового) відноситься ваша змінна:

 

vall DB 255 ; Беззнакове байтове значення

val2 DB -128 ; Знакове байтове значення

 

Множинна ініціалізація

Якщо в одному і тому ж операторові визначення даних використовується декілька ініціалізаторів, то привласнена цьому операторові мітка відноситься тільки до першого байта даних. У приведеному нижче прикладі мається на увазі, що мітці list відповідає зсув 0. Тоді значення 10 розташовується із зсувом 0 щодо сегменту даних, значення 2 0 - із зсувом 1, 3 0 - із зсувом 2 і 4 0 - із зсувом 3:

 

. data

list BYTE 10,20,30,40

 

На рис. 2.3 ця послідовність байтів показана наочно разом з відповідним значенням зсуву.

Мітки потрібні не для всіх операторів визначення даних. Наприклад, якщо нам потрібно визначити безперервний масив байтів, що починається із змінної list, то додаткові оператори визначення даних можуть бути введені в подальших рядках програми:

 

list BYTE 10,20,30,40

BYTE 50,60,70,80

BYTE 81,82,83,84

 

У одному операторові визначення даних можуть використовуватися ініціалізатори, задані в різних системах числення. Крім того, можуть використовуватися разом як символи, так і рядкові константи. У наведеному нижче прикладі списки listl і list2 еквівалентні:

 

listl BYTE 10,32,41h,00100010b

list2 BYTE Oah,20h,'a',22h

 

Визначення рядків

Щоб визначити в програмі текстовий рядок, потрібно стрічку, яка її формує вкласти в лапки. Найчастіше в програмах використовуються так звані нуль-завершення (null-terminated) рядки, або рядки, що закінчуються нульовим байтом, тобто байтом, значення якого рівне двійковому нулю. Цей тип рядків використовується в таких популярних мовах програмування, як C/С++ і Java, а також передається як параметри функцій системи Microsoft Windows. Нижче наведений приклад нуль-завершеного рядка:

 

greetingl BYTE "Добрий день!",0

 

Кожен символ даного рядка займає один байт пам'яті. До рядків символів не відноситься правило, згідно якому значення окремих байтів ініціалізатора розділяються між собою комами.

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

 

greetingl BYTE "Вас вітає демо-версія програми шифрування"

BYTE "створена.",0Dh,0Ah

BYTE "Якщо ви внесете зміни до цієї програми "

BYTE "будь ласка, пришліть мені її копію.",0Dh,0Ah,0

 

Шістнадцяткові значення байтів 0dh і 0ah, вказані в даному прикладі, називаються символами кінця рядка і скорочено позначаються CR/LF. При їх посиланні на стандартний пристрій виводу, курсор монітора автоматично переходитиме в першу позицію наступного рядка.

У MASM передбачена можливість розділення одного довгого оператора програми на декілька стрічок. Для цього в місці розриву поточної стрічки оператора ставиться спеціальний знак продовження - символ зворотної косої межі (\). Іншими словами, якщо оператор не поміщається в одній стрічці початкового коду, то в кінці поточної стрічки ставиться символ \ і набір коду продовжується з наступної стрічки програми. Наприклад, наведені нижче два оператори визначення даних еквівалентні:

 

greetingl BYTE " Вас вітає демо-версія програми шифрування"

і

greetingl \ BYTE " Вас вітає демо-версія програми шифрування"

 

Використання оператора DUP

Оператор DUP використовується для створення змінних, які містять значення байтів, що повторюються. Як лічильник байтів використовується константний вираз. Цим оператором зазвичай користуються при виділенні пам'яті під рядок символів або масив, які можуть бути ініціалізовані або ні. Наприклад:

 

BYTE 20 DUP(O) ; 20 байтів, всі рівно нулю

BYTE 20 DUP(?) ; 20 байтів, значення яких не визначене

BYTE 4 DUP("stack ") ; 20 bytes: "stack stack stack stack "

 

Визначення змінних типу WORD і SWORD

За допомогою директив WORD (визначити слово) і SWORD (визначити слово із знаком) в програмах виділяється пам'ять для зберігання 16-розрядних цілих значень. Наприклад:

 

wordl WORD 65535 ; Найбільше беззнакове значення

word2 SWORD -32768 ; Найменше знакове значення

word3 WORD ? ; Неініціалізоване беззнакове значення

 

У колишніх версіях асемблера для визначення як знакових, так і беззнакових 16-розрядних цілих змінних використовувалася директива DW. У новій версії MASM також є можливість користуватися цією директивою:

 

val1 DW 65535 ; Беззнакове

val2 DW -32768 ; Знакове

 

Масив слів. Для створення масиву 16-розрядних слів можна скористатися або оператором DUP, або явно перерахувати значення кожного елементу масиву через кому. Ось приклад масиву слів, що містить певні значення:

 

mylist WORD 1,2,3,4,5

 

На рис. 3.4 ця послідовність слів показана наочно разом з відповідним значенням зсуву. Передбачається, що змінна mylist розташовується із зсувом 0. В даному випадку значення зсуву кожного елементу масиву збільшується на 2 (тобто на розмір елементу масиву в байтах).

 

Для виділення пам'яті під масив слів зручно користуватися оператором DUP:

 

array WORD 5 DUP(?) ; Масив з 5 неініціалізованих слів

 

Визначення змінних типу DWORD і SDWORD

За допомогою директив DWORD (визначити подвійне слово) і SDWORD (визначити подвійне слово із знаком) в програмах виділяється пам'ять для зберігання 32-розрядних цілих значень. Наприклад:

 

vall DWORD 12345678b ; Беззнакове

val2 SDWORD -2147483648 ; Знакове

val3 DWORD 20 DUP(?) ; Неініціалізований масив беззнакових чисел

 

У колишніх версіях компілятора асемблера для визначення як знакових, так і беззнакових 32-розрядних цілих змінних використовувалася директива DD. У новій версії MASM ви також можете користуватися цією директивою:

 

val1 DD 12345678h ; Беззнакове

val2 DD -2147483648 ; Знакове

 

Масив подвійних слівДля створення масиву 32-розрядних слів можна скористатися або оператором DUP, або явно перерахувати значення кожного елементу масиву через кому. Ось приклад масиву слів, що містить певні значення:

 

mylist DWORD 1,2,3,4,5

 

На рис. 3.5 ця послідовність подвійних слів показана наочно разом з відповідним значенням зсуву. Передбачається, що змінна mylist розташовується із зсувом 0. В даному випадку значення зсуву кожного елементу масиву збільшується на 4 (тобто на розмір елементу масиву в байтах).

 

Визначення змінних типу QWORD

За допомогою директиви QWORD (визначити збільшене учетверо слово) в програмах виділяється пам'ять для зберігання 32-розрядних цілих значень. Наприклад:

 

quadl QWORD 1234567812345678h

 

Крім того, для визначення збільшеного учетверо слова в програмах можна використовувати застарілу директиву DQ:

 

quadl DQ 1234567812345678h

 

Визначення змінних типу TBYTE

За допомогою директиви TBYTE (визначити 10 байтів) в програмах виділяється пам'ять для зберігання 80-розрядних цілих значень. Цей тип даних в основному використовується для зберігання десяткових упакованих цілих чисел (двійково-кодованих цілих чисел). Для роботи з цими числами використовується спеціальний набір команд математичного співпроцесора. Ось приклад визначення:

 

vail TBYTE 1000000000123456789ah

 

Крім того, для визначення десятибайтової змінної в програмах можна використовувати застарілу директиву DT:

 

vail DT 1000000000123456789ah

 

Визначення змінних дійсного типу

Директива REAL4 визначає в програмі 4-байтову змінну дійсного типу одинарної точності. Директива REAL8 визначає 8-байтову змінну дійсного типу подвійної точності, а REAL10 - 10-байтову змінну дійсного типу розширеної точності. Після кожної з директив необхідно вказати один або декілька ініціалізаторів, значення яких повинне відповідати довжині ділянки пам'яті, що виділяється, під змінну:

 

rvall Real4 -2.1
rval2 Real8 3.2Е-260
rval3 Real10 4.6E+4096
 

У попередніх версіях компілятора асемблера для визначення дійсних чисел використовувалися директиви DD, DQ і DT. Їх можна використовувати і в сучасній версії асемблера:

 

rvall DD -1.2

rval2 DQ 3.2E-260

rval3 DT 4.6E+4096

 

Прямий і зворотний порядок проходження байтів

У процесорах фірми Intel при вибірці і зберіганні даних в пам'яті використовується так званий прямий порядок проходження байтів (little endian order). Це означає, що молодший байт змінної зберігається в пам'яті за меншою адресою. Байти змінної, що залишилися, зберігаються в подальших елементах пам'яті в порядку зростання їх старшинства.

Як приклад розглянемо подвійне слово, значення якого рівне 12345678h. Припустимо, що воно зберігається в пам'яті із зсувом 0. Тоді значення 78h зберігатиметься в першому байті із зсувом 0, 56h- в другому байті із зсувом 1, 34h - в третьому байті із зсувом 2, 12h - в четвертому байті із зсувом 3, як показано на рис. 3.6.

У деяких типах процесорів використовується зворотний (bigendian) порядок проходження байтів. При цьому старший байт змінної зберігається за молодшою адресою, як показано на рис. 3.7.

 

 

2.     Директиви. Класичні та спрощені директиви сегментації.

Директива .DATA? використовується для оголошення в програмі блоку пам'яті, що містить неініціалізовані дані. Цією можливістю часто користуються, коли в програмі потрібно зарезервувати великий блок неініціалізованих даних, оскільки вона дозволяє скоротити розмір виконуваного файлу, що отримується після асемблювання і компоновки. Ось приклад коду, що ефективно використовує дискову пам'ять для зберігання виконуваного файлу:

 

.data

smallarray DWORD 10 DUP(0) ; Довжина 40 байтів

 

.data?

bigarray DWORD 5000 DUP(?); Довжина 20000 байтів

 

А ось приклад невдалого оголошення змінних, в результаті якого розмір виконуваного модуля перевищуватиме 20000 байтів:

 

.data

Smallarray DWORD 10 DUP(0) ; Довжина 40 байтів

bigarray DWORD 5000 DUP(?) ; Довжина 20000 байтів

 

Перемішування коду і данихАсемблер дозволяє при написанні програми швидко переходити з сегменту коду в сегмент даних і навпаки. Цей засіб зручно застосовувати у разі, коли по мірі написання програми необхідно оголосити локальну змінну, яка використовуватиметься тільки в межах деякої частини вашої програми. У приведеному нижче прикладі ми оголосили локальну змінну temp, розмістивши оператори її визначення прямо в коді програми:

 

.code

mov eax,ebx

.data

temp DWORD ?

.code

mov temp,eax

 

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

 

Символічні константи

Ідентифікатор мови асемблера (або символ), якому поставлений у відповідність цілочисельний вираз або текстовий рядок, називається символічною константою (symbolic constant) або визначенням символу (symbol definition). На відміну від змінних, для яких під час оголошення асемблер резервує пам'ять в програмі, при визначенні символічної константи пам'ять не виділяється. Символічні константи використовуються тільки під час компіляції програми, їх не можна змінити під час виконання програми.

 

Директива привласнення

Директива привласнення (=) пов'язує символічне ім'я з цілочисельним виразом. Синтаксис наступний:

 

ім'я = вираз

 

Як правило, значенням виразу є 32-розрядне ціле число. Всі імена замінюються відповідними виразами на етапі асемблювання програми, точніше, під час її першої фази - обробки початкового тексту програми препроцесором. Наприклад, якщо препроцесор зустрічає в програмі наступні рядки

 

COUNT = 500

mov ах,count

 

він замінить їх на наступну команду:

 

mov ах,500

 

Насправді, на перший погляд зовсім не обов'язково спочатку визначати символ COUNT, а потім використовувати його в команді MOV, якщо можна відразу вказати в цій команді літерал 500. Проте, при використанні символів програми стають зрозумілішими і їх легко супроводжувати. Припустимо, що символ COUNT використовується в деякій програмі в десяти місцях. Якщо через деякий час знадобиться збільшити його значення 600, це завжди можна буде легко зробити, відредагувавши всього один рядок коду:

 

COUNT = 600

 

Після асемблювання програми всі значення цього символу автоматично заміняться на число 600. А якби в програмі не використовувався цей символ, розобникові довелося б вручну шукати в початковому коді число 500 і замінювати його на 600.

Визначення коду клавіш. Символи часто використовуються в програмі для позначення коду важливих клавіш. Наприклад, десяткове число 27 відповідає ASCII-коду клавіші <Esc>:

 

Esc_key = 27

 

Визначивши такий символ і потім використовуючи його в програмі замість безпосередньо заданого значення, ми тим самим підвищуємо її читабельність. Порівняємо команду:

 

mov al,esc_key ; Хороший стиль програмування

з цією:

mov al,27 ; Поганий стиль програмування

 

Використання в операторі DUP. Як відомо, для вказівки розміру резервованої пам'яті в операторові DUP використовується значення лічильника. Для зручності супроводу такої програми його значення потрібно задавати у вигляді символічної константи. Припустимо, що значення символу COUNT вже визначене в програмі. Тоді ним можна скористатися в наведеному нижче операторові визначення даних:

 

array DWORD COUNT DUP(O)

Перевизначення символівЯкщо значення символу визначене за допомогою директиви привласнення (=), його можна перевизначити в програмі стільки раз, скільки це потрібно. У наступному прикладі показано, як асемблер інтерпретуватиме значення символу COUNT після кожного його перевизначення.

 

COUNT = 5

mov a1, COUNT ; AL = 5

COUNT =10

mov al, COUNT ; AL = 10

COUNT = 100

mov al, COUNT ; AL = 100

 

Зміна значення символу, такого як COUNT, ніяк не впливає на порядок виконання команд процесором. Вона впливає тільки на значення, підставлювані в команди початкової програми препроцесором асемблера.

 

Визначення розміру масивів і рядків

При використанні в програмі масивів і рядків, нам часто потрібно знати їх розмір. У приведеному нижче прикладі ми створили символічну константу Listsize і вручну привласнили їй значення, рівне кількості байтів масиву list.

 

list BYTE 10,20,30,40

Listsize = 4

 

Проте такий код не можна назвати прикладом хорошого стилю програмування, оскільки його важко згодом модифікувати і супроводжувати. При зміні кількості байтів в масиві List потрібно буде відповідним чином змінити значення символічної константи Listsize, інакше програма працюватиме некоректно. Для усунення проблеми, потрібно зробити так, щоб в програмі автоматично обчислювалося значення символічної константи Listsize.

У MASM можна визначити зсув поточного оператора програми відносно початку сегменту. Для цього використовується оператор $, який повертає поточне значення лічильника команд. У наведеному нижче прикладі значення символічної константи Listsize обчислюється автоматично компілятором шляхом віднімання від поточного значення лічильника команд ($) зсуву змінної List:

 

List BYTE 10,20,30,40

Listsize = ($ - List)

 

В даному прикладі важливо, щоб оператор обчислення значення Listsize розташовувався відразу за масивом List. Наприклад, в наступному прикладі значення символічної константи Listsize буде більше розміру списку List, оскільки після нього розташована область пам'яті, в якій розміщується змінна Var2:

 

List BYTE 10,20,30,40

Var2 BYTE 20 DUP(?)

Listsize = ($ - List)

 

Довжину рядків дуже незручно обчислювати вручну. Тому розумно, щоб цю роботу здійснювала сама програма, як показано в наступному прикладі:

 

mystring BYTE "Це довгий рядок, в якому "

BYTE "може міститися довільне "

BYTE "кількість символів."

mystring_len = ($ - mystring)

 

Масиви слів і подвійних слівЯкщо кожен елемент масиву є 16-розрядним словом, то щоб визначити кількість елементів такого масиву, необхідно обчислену загальну довжину масиву в байтах розділити на 2 (тобто на довжину елементу масиву):

 

List WORD 1000h,2000h,3000h,4000h

Listsize = ($ - List) / 2

 

Аналогічно, якщо кожен елемент масиву є 32-розрядним подвійним словом, то загальну довжину масиву в байтах потрібно розділити на 4:

 

List DWORD l0000000h,20000000b,30000000h,40000000b

Listsize = ($ - List) / 4

 

Директива EQU

Ця директива використовується для призначення символічного імені цілочисельному виразу або довільному текстовому рядку. Існує три формати директиви EQU:

 

ім'я EQU вираз

ім'я ЕQU символ

ім'я EQU <текст>

 

У першому випадку значення виразу повинне мати цілий тип і знаходитися в допустимих межах. У другому випадку символ повинен бути визначений раніше за допомогою директиви привласнення (=) або іншої директиви EQU. У третьому випадку між кутовими дужками <. . . > може знаходитися довільний текст.

Якщо після визначення символу з вказаним ім'ям воно зустрінеться компілятору в програмі, то замість цього символу буде підставлено відповідне йому цілочисельне значення або текст.

Директиви EQU використовуються у разі визначення символів, яким не обов'язково повинне відповідати цілочисельне значення. Наприклад, за допомогою цієї директиви можна визначити дійсну константу:

 

PI EQU <3.141592б>

 

Приклад. Символ можна легко пов'язати з текстовим рядком, а потім на основі цього символу створити змінну в програмі:

 

presskey EQU <"для продовження натисніть будь-яку клавішу...",0>

.data

prompt BYTE presskey

 

Приклад. Припустимо, що нам потрібно визначити символічну константу, значення якої рівне кількості комірок в матриці розмірності 10x10. Ми можемо визначити в програмі дві символічні константи двома різними способами. Значення першої з них буде цілим числом, а другий - текстовим виразом. Потім обидва цих символи можна використовувати в операторах визначення даних:

 

matrixl EQU 10 * 10

matrix2 EQU <10 * 10>

.data

Ml WORD matrixl

M2 WORD matrix2

 

При цьому асемблер створить два різних оператора визначення даних для змінних M1 і М2. Спочатку він обчислить значення символічної константи matrixl, а потім привласнить її значення змінної M1. У другому випадку він просто скопіює текст, відповідний символу matrix2, в оператор визначення даних ддя другої змінної М2:

Ml WORD 100

М2 WORD 10 * 10

 

Неможливість перевизначенняДиректива EQU відрізняється від директиви привласнення (=) тим, що визначений з її допомогою символ не можна перевизначити в одному і тому ж початковому файлі. На перший погляд це є недоліком. Проте даний недолік може обернутися перевагою, оскільки ви не зможете випадково змінити значення одного разу певного символу.

 

Директива TEXTEQU

Ця директива вперше з'явилася в шостій версії MASM. По суті, вона дуже схожа на директиву EQU і створює так званий текстовий макрос (text macro). Існує три формати директиви TEXTEQU:

 

ім'я TEXTEQU <текст>

ім'я TEXTEQU текстовий__макрос

ім'я TEXTEQU %константний_вираз

 

У першому випадку символу привласнюється вказаний в кутових дужках 
<. . .> текстовий рядок. У другому випадку - значення певного текстового макросу. У третьому випадку - символічній константі привласнюється значення цілочисельного виразу.

У приведеному нижче прикладі змінній promptl привласнюється значення текстового макросу continuemsg:

 

continuemsg TEXTEQU <"хотіте продовжити (Y/n)?">

.data

promptl BYTE continuemsg

 

При визначенні текстових макросів можна використовувати значення інших текстових макросів. У наведеному нижче прикладі символу count привласнюється значення цілочисельного виразу, в якому використовується символ rowsize. Потім визначається символ move, значення якого рівне mov. Після цього визначається символ setupal на основі символів move і count:

 

rowsize = 5

count TEXTEQU %(rowsize * 2) ; Теж саме, що і count TEXTEQU <10>

move TEXTEQU <mov>

setupal TEXTEQU <move al,count> ; Теж саме, що і setupal TEXTEQU <mov al,10>

 

Символ, визначений за допомогою директиви TEXTEQU, можна перевизначити в програмі у будь-який момент. Цим вона відрізняється від директиви EQU.

Лекція 8 Команди пересилки даних

 

Команда MOV

Команда MOV копіює дані з операнда-джерела в операнд-одержувач. Вона відноситься до групи команд пересилки даних (data transfer) і використовується в будь-якій програмі. Команда MOV є двомісною (тобто має два операнди): перший операнд визначає одержувача даних (destination), а другий - джерело даних (source):

 

MOV одержувач, ДЖЕРЕЛО

 

При виконанні цієї команди змінюється вміст операнда-одержувача, а вміст операнда-джерела не міняється. Принцип пересилки даних справа наліво відповідає прийнятому в операторах привласнення мов високого рівня, таких як C++ або Java:

 

одержувач = ДЖЕРЕЛО;

 

Практично у всіх командах асемблера операнд-одержувач знаходиться зліва, а операнд- джерелj - справа.

У команді MOV можуть використовуватися самі різні операнди. Крім того, необхідно враховувати наступні правила і обмеження:

  1. Обидва операнди повинні мати однакову довжину.
  2. Як один з операндів обов'язково повинен використовуватися регістр (тобто пересилки типу "пам'ять-пам'ять" в команді MOV не підтримуються).
  3. Як одержувач не можна вказувати регістри CS, EIP і IP.
  4. Не можна переслати безпосередньо задане значення в сегментний регістр.

Нижче приведені варіанти використання команди MOV з різними операндами (окрім сегментних регістрів):

 

MOV reg,reg

MOV mem,reg

MOV reg,mem

MOV mem, imm

MOV reg,imm

 

Сегментні регістри в команді MOV зазвичай використовуються тільки в програмах, написаних для реального або віртуального режимів роботи процесора. При цьому можуть існувати наступні її форми (слід враховувати, що регістр CS не можна вказувати як одержувач даних):

 

MOV r/m16, sreg

MOV sreg,r16

 

Пересилка типу "пам'ять-пам'ять". За допомогою однієї команди MOV не можна безпосередньо переслати операнд з однієї області пам'яті в іншу. Тому спочатку потрібно завантажити початкове значення в один з регістрів загального призначення, а потім переслати його в потрібне місце пам'яті:

 

.data

varl DWORD 12345678h

var2 DWORD ?

. code

mov eax,varl

mov var2,eax

 

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

 

Команди розширення цілих чисел

 

Копіювання меншого по довжині значення в змінну більшої довжини

Вище ми вже відзначали, при спробі переслати за допомогою команди MOV ціле число, довжина якого не збігається з довжиною одержувача даних, асемблер згенерує повідомлення про помилку. Проте в програмах досить часто потрібно переслати менше по довжині значення у велику змінну або регістр. Як приклад припустимо, що нам потрібно завантажити 16-розрядне беззнакове значення, що зберігається в змінній count, в 32-розрядний регістр ЕСХ. Найпростіше рішення цієї задачі полягає в тому, що спочатку потрібно обнулити регістр ЕСХ, а потім записати 16-розрядне значення змінної count в регістр СХ:

 

.data

count WORD 16

.code

mov ecx,0

mov cx,count

 

А якщо нам потрібно вирішити аналогічну задачу, тільки для цілих чисел із знаком? Наприклад, що робити, якщо потрібно завантажити в регістр ЕСХ негативне значення -16? Якщо ми застосуємо традиційний підхід, отримаємо наступне:

 

.data

signedval SWORD -16 ; FFF0h (-16)

.code

mov ecx,0

mov cx,signedval ; ECX = 0000FFF0h (+65520)

 

Зверніть увагу, що в даному випадку в регістр ЕСХ завантажиться значення 0000FFF0h (+65520), яке зовсім відрізняється від того, що потрібне нам, тобто FFFFFFF0h (-16). Іншими словами, для отримання правильного результату нам потрібно було не обнуляти регістр ЕСХ, а записати в нього значення FFFFFFFFh, і тільки тоді записати в регістр СХ змінну signedval. Правильний код буде таким:

 

mov ecx,0FFFFFFFFh

mov cx,signedval ; ЕСХ = FFFFFFF0h (-16)

 

Вище ми описали проблему, яка виникає при завантаженні цілих чисел із знаком в регістр, розмір якого перевищує довжину числа. Для її вирішення спочатку потрібно проаналізувати знак числа і залежно від результату занести в регістр або 0, або -1.

Команда MOVZX

Команда MOVZX (Move With Zero-extend, або Перемістити і доповнити нулями) копіює вміст початкового операнда в більший за розміром регістр одержувача даних. При цьому, біти регістра-одержувача, що залишилися невизначеними (як правило, старші 16 або 24 біта) скидаються в нуль. Ця команда використовується тільки при роботі з беззнаковими цілими числами. Існує три варіанти командиmovzx:

 

MOVZX rl6, r/m8

MOVZX r32, r/m8

MOVZX r32, r/ml6

 

Рис..1. Ілюстрація роботи команди MOVZX

 

У приведеному нижче прикладі використовуються всі три варіанти команди MOVZX з різними розмірами операндів.

 

mov bx,0a69bh

movzx eax,bx ; ЕАХ = 0000a69bh

movzx edx,bl ; EDX = 0000009bh

movzx ex, bl ; СХ = 009bh

 

А в наступному прикладі як початковий операнд використовуються змінні різної довжини, розташовані в пам'яті, але отриманий результат буде ідентичний попередньому прикладу.

 

.data

bytel BYTE 9bh

wordl WORD 0a69bh

.code

movzx eax,wordl ; EAX = 0000a69bh

movzx edx, bytel ; EDX = 0000009bh

movzx cx, bytel ; CX = 009bh

 

Команда MOVSX

Команда MOVSX (Move With Sign-extendабо Перемістити і доповнити знаком) копіює вміст початкового операнда в більший за розміром регістр одержувача даних, так як і команда MOVZX. При цьому біти регістра-одержувача, що залишилися невизначеними (як правило, старші 16 або 24 бsтb) заповнюються значенням знакового біта початкового операнда. Ця команда використовується тільки при роботі із знаковими цілими числами. Існує три варіанти команди MOVSX:

MOVSX r16, r/m8

MOVSZ r32, r/m8

MOVSX r32, r/ml6

При завантаженні меншого за розміром операнда в більший за розміром регістр за допомогою команди MOVSX, знаковий розряд початкового операнда дублюється (тобто переноситься або розширюється) у всі старші біти регістра-одержувача. Наприклад, при завантаженні 8-розрядного значення 10001111b в 16-розрядний регістр, воно буде поміщено в молодших 8 бітів цього регістра. Потім, як показано на рис.2, старший біт початкового операнда переноситься у всі старші розряди регістра-одержувача.

 

 

Рис. 2. Ілюстрація роботи команди MOVSX

 

У наведеному нижче прикладі використовуються всі три варіанти команди MOVSX з різними розмірами операндів.

 

mov bx,0a69bh

movsx eax,bx ; EAX = Ffffa69bh

movsx edx,bl ; EDX = Ffffff9bh

movsx cx,bl ; CX = Ff9bh

 

Команди LAHF і SAHF

Команда LAHF (Load Status Flags Into АН, або завантажити прапори стану в регістр АН) дозволяєзавантажити в регістр АН молодший байт регістра прапорів EFLAGS. При цьому в регістр АН копіюються наступні прапори стану: SF (прапор знаку), ZF (прапор нуля), AF (прапор службового перенесення), PF(прапор парності) і CF (прапор перенесення). За допомогою цієї команди можна легко зберегти вміст регістра прапорів в змінній для подальшого аналізу:

 

.data

saveflags BYTE ?

.code

lahf ; Завантажити прапори в регістр АН

mov saveflags, ah ; Зберегти прапори в змінній

 

Команда SAHF (Store АН Into Status Flagsабо записати регістр АН в прапори) поміщає вміст регістра АН в молодший байт регістра прапорів EFLAGS. Наприклад, ви можете відновити збережене раніше в змінній значення прапорів:

 

mov ah,saveflags ; Завантажимо в регістр АН збережене

; ранішезначення регістра прапорів
 

sahf ; Скопіюємо його в молодший байт регістра EFLAGS

 

 

 

Команда XCHG

Команда XCHG (Exchange Dataабо Обмін даними) дозволяє обміняти вміст двох операндів. Існує три варіанти команди XCHG:

 

XCHG reg,reg

XCHG reg,mem

XCHG mem,reg

 

Для операндів команди XCHG потрібно дотримувати ті ж правила і обмеження, що і для операндів команди MOV, за виключення того, що операнди команди XCHG не можуть бути безпосередньо заданими значеннями.

Команда XCHG часто використовується в програмах сортування елементів масивів, оскільки дозволяє дуже швидко поміняти місцями два елементи. Ось декілька прикладів використання командиXCHG:

 

xchg ах,bx ; Обмін вмісту 16-розрядних регістрів

xchg ah,al ; Обмін вмісту 8-розрядних регістрів

xchg varl,bx ; Обмін вмісту 16-розрядного операнда

; у пам'яті і регістра ВХ

xchg eax,ebx ; Обмін вмісту 32-розрядних регістрів

 

Щоб поміняти вміст двох змінних, розташованих в пам'яті, необхідно скористатися проміжним регістром і двома додатковими командами:

 

.data

vall DWORD 1

val2 DWORD 2

.code

mov eax,vall

xchg eax,val2

mov vall,eax

 

Операнди з безпосередньо заданим зсувом

При заданні операнда команди, до імені змінної можна додавати зсув. Така конструкція називається операндом з безпосередньо заданим зсувом. Вона використовується в програмі для доступу до елементів пам'яті, яким не була призначена мітка.

Розглянемо масив байтів, якому привласнена мітка arrayb:

 

arrayb BYTE 10h,20h,30h,40h,50h

 

Якщо вказати змінну arrayb як джерело даних в команді MOV, то в результаті буде вибрано значення першого байта цього масиву:

 

mov al,arrayb ; AL = l0h

 

Щоб звернутися до другого байта масиву, потрібно до зсуву, відповідного змінною arrayb, додати одиницю:

 

mov al,[arrayb+1] ; AL = 20h

 

Для звернення до третього байта масиву потрібно додати 2:

mov al,[arrayb+2] ; AL = 30h

 

При збільшенні константи до зсуву змінної, наприклад arrayb+1, виходить так званий адресний вираз. Воно обчислюється асемблером при визначенні поточної адреси (effective addressоперанда. Якщо помістити адресний вираз в квадратні дужки, ми тим самим явно вкажемо асемблеру, що мається на увазі операція звернення до пам'яті за вказаною в команді адресою операнда. Проте при використанні компілятора MASM квадратні дужки можна опустити:

 

mov al,arrayb+l

 

Перевірка виходу за межу масиву. В MASM немає вбудованих засобів перевірки виходу поточної адреси операнда за межу масиву. Тому при виконанні приведеної нижче команди асемблер не видасть повідомлення про помилку, а процесор просто завантажить в регістр AL байт, що не відноситься до масивуarrayb:

 

mov al,[arrayb+20] ; AL = ??

 

Виявити подібні помилки не так-то просто! Тому при роботі з масивами потрібно завжди перевіряти, чи не виходить поточна адреса операнда за межі меж масиву.

 

Масиви слів і подвійних слів. При використанні масивів 16-розрядних довжина елементу такого масиву складає 2 байти. Тому при переході від одного елементу масиву до іншого поточний зсув необхідно збільшувати на 2. У приведеному нижче прикладі для звернення до другого елементу масиву ми додали до зсуву arrayw число 2:

.data

arrayw WORD 100h,200h,300h

.code

mov ах,[arrayw] ; AX = l00h

mov ах,[arrayw+2] ; AX = 200h

 

Аналогічно, при роботі з масивом подвійних слів зсув між його сусідніми елементами складає 4 байти:

 

.data

arrayd DWORD 10000h,20000h

.code

mov eax,[arrayd] ; EAX = l0000h

mov eax,[arrayd+4] ; EAX = 20000h

Лекція 9 Арифметичні команди

 

Додавання і віднімання

Команди цілочисельного додавання і віднімання відносяться до групи базових команд, що виконуються процесором. До них відносяться команди: INC (increment, або інкремент)DEC (decrement, або декремент), ADD, SUB і NEC (negate, або заперечення).

 

Команди INC і DEC

Команди INC (increment, або інкремент) і DEC (decrementабо декремент), відповідно, додають або віднімають одиницю з вказаного одномісного операнда. Синтаксис цих команд наступний:

 

INC reg/mem

DEC reg/mem

 

Ось декілька прикладів використання цих команд:

 

.data

mydword DWORD l000h

.code

inc mydword ; mydword = 0000l00lh

mov ebx,mydword

dec ebx ; EBX = 0000l000h

 

Команда ADD

Команда ADD додає операнд-джерело до операнда-одержувача даних. Довжини операндів мають бути рівні. Синтаксис команди ADD наступний:

 

ADD одержувач, джерело

 

При додаванні, значення початкового операнда не змінюється, а отримана сума записується на місце операнда-одержувача даних. Для операндів команди ADD потрібно дотримувати ті ж правила іобмеження, що і для операндів команди MOV. Нижче приведений короткий фрагмент коду, в якому використовуються команди 32-розрядного цілочисельного додавання:

 

.data

varl DWORD l0000h

var2 DWORD 20000h

.code

mov eax,varl

add eax,var2 ; EAX = 30000h

 

Прапори. Команда ADD змінює стан наступних прапорів: CF (прапор перенесення), ZF (прапор нуля), SF (прапор знаку), OF (прапор переповнювання), AF (прапор службового перенесення), PF (прапор парності). Ці прапори використовуються для аналізу отриманого в результаті виконання команди додавання значення.

Команда SUB

Команда SUB віднімає операнд-джерело з операнда-одержувача даних. Для операндів команди SUBпотрібно дотримувати ті ж правила і обмеження, що і для операндів команд ADD і MOV. Синтаксис команди SUB наступний:

 

SUB одержувач, джерело

 

Нижче приведений короткий фрагмент коду, в якому використовуються команди 32-розрядного цілочисельного віднімання:

 

.data

varl DWORD 30000h

var2 DWORD l0000h

.code

mov eax,varl

sub eax,var2 ; EAX = 20000h

 

При виконанні команди віднімання процесор замінює її на команду додавання, інвертуючи при цьому значення початкового операнда. Наприклад, замість операції 4 1 виконується операція 4 + (-1). Нагадаємо, що для представлення відємних чисел в процесорах Intel використовується двійковий додатковий код. Тому -1 представляється у вигляді двійкового числа 11111111b (рис.3).

Рис. 3. Ілюстрація принципу виконання команди віднімання в процесорі

В процесі виконання команди додавання з негативним числом відбувається перенесення старшого біта числа, проте при виконанні знакових цілочисельних арифметичних операцій в процесорі біт перенесення ігнорується.

Прапори. Команда SUB змінює стан наступних прапорів: CF (прапор перенесення), ZF (прапор нуля), SF (прапор знаку), OF (прапор переповнювання), AF (прапор службового перенесення), PF (прапор парності). Ці прапори використовуються для аналізу отриманого в результаті виконання команди віднімання значення.

 

Команда NEG

Команда NEG змінює знак числа на протилежний, конвертуючи число в двійковий додатковий код. Формати використання цієї команди наступні:

 

NEG reg

NEG mem

 

Нагадаємо, що для знаходження двійкового додаткового коду числа, потрібно інвертувати значення всіх бітів початкового двійкового числа і до отриманого результату додати одиницю.

Прапори. Команда NEG змінює стан наступних прапорів: CF (прапор перенесення), ZF (прапор нуля), SF (прапор знаку), OF (прапор переповнювання), AF (прапор службового перенесення), PF (прапор парності). Ці прапори використовуються для аналізу отриманого в результаті виконання команди значення.

 

Реалізація арифметичних виразів

Після вивчення команд ADD, SUB і NEG ми можемо приступити до програмування арифметичних виразів на мові асемблера, в яких використовуються операції складання, віднімання і заперечення. Іншими словами, зараз ми емулюватимемо роботу компілятора мови високого рівня, такого як C++, яку він виконує при трансляції в машинний код приведеного нижче виразу:

 

Rval = -xval + (Yval - Zval);

 

В даному випадку ми скористаємося наступними 32-розрядними змінними:

 

.data

Rval SDWORD ?

Xval SDWORD 26

Yval SDWORD 30

Zval SDWORD 40

 

При виконанні трансляції арифметичного виразу зручно спочатку обчислити значення всіх його членів, а потім додати їх. Перш за все, інвертуємо копію змінної Xval:


mov eax,xval ; Перший член: -xval

neg eax ; EAX = -26

 

Потім завантажимо в регістр змінну Yval і віднімемо з неї змінну Zval:


mov ebx,yval ; Другий член: (Yval - Zval)

sub ebx,zval ; EBX = -10

 

І нарешті, додаємо значення двох членів арифметичного виразу, які знаходяться в регістрах ЕАХ і ЕВХ:

 

add eax,ebx ; результат збережемо в змінній Rval:

mov Rval,eax ; Rval = -36

 

Прапори, що встановлюються арифметичними командами

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

Прапори нуля і знаку (ZF і SF)

Прапор ZF встановлюється, якщо в результаті виконання арифметичної команди виходить нульове значення, наприклад:

 

mov ecx,1

sub есх,1 ; ЕСХ = 0, ZF = 1

mov eax,0ffffffffh

inc eax ; EAX = 0, ZF = 1

inc eax ; EAX = 1, ZF = 0

 

Прапор SF встановлюється, якщо в результаті виконання арифметичної команди виходить негативне значення, наприклад:

 

mov ecx,0

sub ecx,l ; ЕСХ = -1, SF = 1

add ecx,2 ; ЕСХ = 1, SF = 0

 

Прапор перенесення (операції з беззнаковими цілими числами)

Прапор CF має важливе значення при виконанні процесором арифметичних операцій з беззнаковими цілими числами. Даний прапор встановлюється у випадку, якщо результат виконання такої операції дуже великий (або дуже малий) і тому він не поміщається у виділений для нього простір операнда - приймача даних. Наприклад, в результаті виконання приведеної нижче команди ADD буде встановлений прапор перенесення, оскільки отримана сума не поміщається в 8-розрядний регістр:

 

mov al,0ffh

add al,1 ; CF = 1, AL = 00

 

Команда MUL

Команда mul використовується для множення 8-, 16- і 32-розрядних беззнакових цілих чисел, що знаходяться в одному з регістрів загального призначення або в пам'яті, з операндом, розташованим врегістрі АL, АХ або ЕАХ:

 

mul r/m8

mul r/m16

mul r/m32

 

Команда mul має всього один операнд, що є множником. У табл. 7.1 вказано, в яких регістрах розміщується множене і результат, залежно від розміру множника.

 

Таблиця 7.1 - Розташування множеного і результату, залежно від розміру множника

Множене

Множник

Результат

AL

r/m8

AX

AX

r/m16

DX:AX

EAX

r/m32

EDX:EAX

 

 

Щоб при виконанні операції множення не виникло переповнення, розмір результату повинен в два рази перевищувати розміри множеного і множника. На рис. 7.1 показаний процес множення вмісту регістра ЕАХ на 32-розрядний множник.

 

Рисунок 7.1 Виконання операції множення над 32-розрядними числами

 

В результаті виконання команди mul встановлюються два прапори: перенесення CF і переповненняOF, якщо значення старшої половини результату не рівне нулю. Прапорець CF зазвичай використовується при аналізі результатів виконання арифметичних команд з цілими числами без знаку. Наприклад, при множенні регістра АХ на 16-розрядний операнд, результат зберігається в парі регістрів DХ: АХ. При цьому, якщо регістр DХ не рівний нулю, буде встановлений прапор перенесення CF.

Приклад 1. У приведеному нижче фрагменті програми виконується множення 8-розрядних цілих чисел без знаку (5*10h), внаслідок чого виходить 16-розрядне число 0050h, яке розміщується в регістріАХ:

 

mov al,5h

mov bl,10h

mul bl ;CF=0

 

В даному випадку прапор перенесення CF не встановлюється, оскільки регістр АН (старша половина результату) рівний нулю.

Приклад 2. У приведеному нижче фрагменті програми виконується множення 16-розрядних цілих чисел без знаку (0100h х 2000h), внаслідок чого виходить 32-розрядне число 00200000h, яке розміщується в регістрах DX: АХ:

.data

val1 WORD 2000h

val2 WORD 0100h

.code

mov ах,val1

mul val2 ;CF=1

В даному випадку прапорець CF встановлюється, оскільки регістр DX не рівний нулю.

 

Приклад 3. У приведеному нижче фрагменті програми виконується множення 32-розрядних цілих чисел без знаку (12345h х 1000h), внаслідок чого виходить 64-розрядне число 0000000012345000h, яке розміщується в регістрах ЕDX: ЕАХ:

 

mov еах,12345h

mov ebx,1000h

mul ebx ;CF=0

 

Тут прапор перенесення CF не встановлюється, оскільки регістр EDX рівний нулю.

 

Команда IMUL

Команда imul призначена для множення цілих чисел із знаком. Синтаксис команди аналогічний до синтаксису команди mul. Відмінність полягає в тому, що при множенні зберігається знак результату.

В результаті виконання команди imul встановлюються два прапорці: перенесення CF і переповненняOF, якщо значення старшої половини результату не є розширенням знакового розряду, взятим з молодшої частини результату. Прапорець OF використовується при аналізі результатів виконання арифметичних команд з цілими числами із знаком.

 

Приклад 1. У приведеному нижче фрагменті програми виконується множення 8-розрядних цілих чисел із знаком (-4x4), внаслідок чого виходить 16-розрядне число FFF0h (-16), яке розміщується в регістрі АХ:

 

mov al,-4

mov bl,4

imul bl ;AX=FFF0h, OF=0

 

Оскільки вміст регістру AH є знаковим розширенням регістру AL, то прапорець переповнення OF не встановлюється.

 

Приклад 2. У приведеному нижче фрагменті програми виконується множення 16-розрядних цілих чисел із знаком (-48*4), внаслідок чого виходить 32-розрядне число 000000C0h (+192), яке розміщується в регістрах DX: АХ:

 

mov ах,48

mov bx,4

imul bx ;DX:AX=000000C0h,OF=0

 

В даному випадку, вміст регістру DX є знаковим розширенням регістру AX, тому прапорець переповнення OF не встановлюється.

 

Приклад 3. У приведеному нижче фрагменті програми виконується множення 32-розрядних цілих чисел із знаком (4823424 *-423), внаслідок чого виходить 64-розрядне число FFFFFFFF86635D80h, яке розміщується в регістрах ЕDX: ЕАХ:

 

mov еах,+4823424

mov ebx,-423

imul ebx ;EDX:ЕАХ=FFFFFFFF86635D80h, OF=0

 

В даному випадку, вміст регістру DX є знаковим розширенням регістру AX, тому прапорець переповнення OF не встановлюється.

 

 

Команда DIV

Команда DIV використовується для ділення на 8-, 16- і 32-розрядне беззнакове ціле число, що знаходиться в одному з регістрів загального призначення або в пам'яті, операнда, розташованого в регістрах АХ, DX:АХ або EDX:ЕАХ:

DIV r/m8

DIV г/ml6

DIV r/m32

 

Команда DIV має всього один операнд, що є дільником. У табл. 7.2 вказано, в яких регістрах розміщується ділене, дільник, частка і остача, залежно від розміру множника.

 

Таблиця 7.2 - Розташування операндів команди DIV

Ділене

Дільник

Частка

Остача

 

АХ

r/т8

AL

АН

 

DX:AX

r/ml6

АХ

DX

 

EDX:ЕАХ

r/т32

ЕАХ

EDX

 

 

Рисунок 7.2 Виконання операції ділення на 32-розрядне число

 

На рис. 7.2 показаний процес ділення 64-розрядного числа, що знаходиться в регістрах ЕDХ : ЕАХ,на 32-розрядний дільник.

 

Приклад 1. У приведеному нижче фрагменті програми виконується ділення на 8-розрядне ціле число без знаку (83h/2), внаслідок чого виходить 8-розрядна частка 41h і остача 1, які розміщуються в регістрахAL і АН:

 

mov ах,0083h ; Ділене

mov bl,2 ; Дільник

div bl ; AL=41h, AH=01h

 

Приклад 2. У приведеному нижче фрагменті програми виконується ділення на 16-розрядне ціле число без знаку (8003h / 100h), внаслідок чого виходить 16-розрядна частка 80h і остача 3, які розміщуються в регістрах АХ і DX. Оскільки в регістрі DX міститься старша частина діленого, то перед виконанням команди DIV потрібно його обнулити:

 

mov dx, 0 ; Обнулимо старшу частину діленого

mov ах,8003h ; Завантажимо молодшу частину діленого

mov сх,100h ; Дільник

div сх ; АХ=0080h, DX=0003h

 

Приклад 3. У приведеному нижче фрагменті програми виконується ділення на 32-розрядне ціле число без знаку. При цьому ділене розміщується в пам'яті у вигляді 64-розрядного числа типу QWORD:

 

 

.data

dividend QWORD 0000000800300020h

divisor DWORD 00000100h

 

.code

mov edx,DWORD PTR dividend + 4 ; Завантажимо старше

 ; подвійне слово діленого

mov еах,DWORD PTR dividend ; Завантажимо молодше

 ; подвійне слово діленого

div divisor ; ЕАХ=08003000h

 ; EDX=00000020h

 

Ділення цілих чисел із знаком

 

Команди CBW, CWD, CDQ

Перш ніж розглянути команди ділення цілих чисел із знаком, ми повинні познайомитися з трьома командами, за допомогою яких можна розширити довжину цілого числа із знаком (тобто розповсюдити знаковий розряд на його старшу половину).

Команда CBW (Convert Byte to Word, або перетворити байт в слово) дозволяє розширити знаковий розряд з регістра AL в регістр АН. В результаті знак початкового числа зберігається:

 

.data

byteVal SBYTE -101 ; 9Bh

.code

 mov al,byteVal ; AL = 9Bh

cbw ; AX = FF9Bh

 

Як видно з цього прикладу, числа 9Bh і FF9Bh рівні - 101. Різниця полягає тільки в кількості займаних ними розрядів.

Команда CWD (Convert Word to Doubleword, або перетворити слово в подвійне слово) розширює знаковий біт з регістра АХ в регістр DX:

 

.data

wordVal SWORD -101 ; FF9Bh

.code

mov ах,wordVal ; AX = FF9Bh

cwd ; DX:AX = FFFFFF9Bh

 

Команда CDQ (Convert Doubleword to Quadword, або перетворити подвійне слово в збільшене учетверо слово) розширює знаковий біт з регістра ЕАХ в регістр EDX:

 

.data

dwordVal SDWORD -101 ; FFFFFF9Bh

.code

mov eax,dwordVal

cdq ; EDX:EAX = FFFFFFFFFFFFFF9Bh

 

 

 

Команда IDIV

Команда IDIV дозволяє виконати ділення цілих чисел із знаком. Вона має ті ж формати операнда, що і команда DI V. При діленні на 8-розрядне число, перед виконанням команди IDIVпотрібно розширити знак діленого в регістр АН за допомогою команди CBW. У приведеному нижче прикладі виконується ділення числа -48 / 5. Після виконання команди IDIV в регістрі ALзнаходитиметься частка, рівна -9, а в регістрі АН - остача, рівна -3:

.data

byteVal SBYTE -48

.code

mov al,byteVal ; Ділене

cbw ; Розширимо знак регістра AL в АН

 mov bl,5 ; Дільник

 idiv bl ; AL = -9, AH = -3

 

Аналогічно, при виконанні ділення на 16-розрядне число, необхідно спочатку розширити знак регістра АХ в регістр DX. У приведеному нижче прикладі ділиться число - 5000 / 256.

 

.data

wordVal SWORD -5000

.code

 mov ах,wordVal ; Молодша частина діленого

cwd ; Розширимо знак АХ в DX

mov bx,256 ; Дільник

idiv bx ; Частка: AX = -19

 ; Залишок: DX = -136

 

Аналогічно, при виконанні ділення на 32-розрядне число, необхідно спочатку розширити знак регістра ЕАХ в регістр EDX. У приведеному нижче прикладі ділиться число -50000 на 256:

 

.data

dwordVal SDWORD -50000

.code

 mov eax,dwordVal ; Молодша частина діленого

cdq ; Розширимо знак ЕАХ в EDX

mov ebx,256 ; Дільник

idiv ebx ; Частка: ЕАХ = -195

; Залишок: EDX = -80

 

Після виконання обох команд DIV і IDIV арифметичні прапори стану процесора залишаються в невизначеному стані.

 

Переповнення при діленні

Якщо при виконанні команди ділення виходить частка, розмір якої перевищує розмір операнда, що виділяється для її розміщення, виникає ситуація переповнення при діленні. Це приводить до переривання роботи процесора і завершення роботи поточної програми. Наприклад, при виконанні приведеної нижче послідовності команд виникає ситуація переповнення при діленні, оскільки частка (100h) не може бути розміщена в регістрі AL:

 

mov ах,1000h

mov bl,10h

div bl ; В AL не можна розмістити число 100h

 

Помилка ділення на нуль:

 

mov ах,dividend

mov bl,0

div bl

 

Додавання і віднімання чисел з довільною точністю

Додавання і віднімання чисел з довільною точністю дозволяє виконувати арифметичні операції над числами з будь-якою розрядністю. Припустимо, що потрібно написати програму додавання двох 128-розрядних цілих чисел. На мові асемблера вона вирішується дуже просто завдяки наявності двох команд додавання з перенесенням ADC і віднімання з запозиченням SBB.

 

Команда ADC

Команда ADC (ADd with Carry) додає початковий операнд з операндом одержувача даних і додає до результату значення прапора перенесення (тобто число 0 або 1 залежно від стану прапора CF). Формат операндів команди ADC повністю співпадає з командою MOV:

 

ADC reg, reg

ADC mem, reg

ADC reg, mem

ADC mem, imm

ADC reg, imm

Наприклад, в приведеному нижче фрагменті коду додаються значення двох 8-розрядних цілих чиселFFh і FFh, а отримана сума, рівна 0lFEh, поміщається в пару регістрів DL: AL:

 

mov dl,0

mov al,0FFh

add al,0FFh ; AL = FE, CF = 1

adc dl,0 ; DL = 01

 

Аналогічно, в наступному прикладі додаються два 32-розрядні цілі числа FFFFFFFFh і FFFFFFFFh, а отримана в результаті 64-розрядна сума 
0000000lFFFFFFFEh поміщається в пару регістрів EDX: ЕАХ:

 

mov edx, 0

mov еах, 0FFFFFFFFh

add еах, OFFFFFFFFh

adc edx,0

 

Команда SBB

Команда SBB (SuBtract with Borrow) віднімає початковий операнд з операнда одержувача даних і віднімає з отриманого результату значення прапора перенесення (тобто число 0 або 1, залежно від стану прапора CF). Формат операндів команди SBB такий же, як і у команди ADC.

У приведеному нижче прикладі виконується віднімання одиниці з 64-розрядного числа, початкове значення якого рівне 000000010000000h (воно знаходиться в парі регістрів EDX: ЕАХ). Спочатку віднімається одиниця з молодшого подвійного слова, в результаті цього встановлюється прапор CF. Потім із старшого подвійного слова віднімається значення прапора перенесення CF:

 

mov edx,1 ; Старше подвійне слово

mov еах,0 ; Молодше подвійне слово

sub еах,1 ; Віднімемо одиницю з молодшого

; подвійного слова

sbb edx,0 ; Віднімемо прапор перенесення з

; старшого подвійного слова

 

В результаті ми набудемо 64-розрядного значення різниці, що знаходиться в регістрах EDX:ЕАХ:00000000FFFFFFFFh.

 

Арифметичні операції з упакованими десятковими числами і ASCII-рядками

Існує можливість виконувати арифметичні операції над числами, заданими у вигляді АSСII-рядків.

Припустимо, що в деякій програмі потрібно додати два числа, які ввів користувач. Нижче приведений варіант діалогу користувача з програмою, під час якого він ввів два числа: 3402 і 1256.

 

Введіть перше число: 3402

Введіть друге число: 1256

Сума рівна: 4658

 

Поставлену задачу можна вирішити двома способами.

1.  Перетворити обидва числа в двійкову форму і додати їх, потім перетворити суму в числовий ASCII-рядок і відобразити її на екрані.

2.  Безпосередньо скласти числа в ASCII-форматі, послідовно підсумувавши один з одним кожну пару чисел ASCII-рядка, тобто 2+6, 0+5, 4+2 і 3+1. При цьому сума виходить у форматі ASCII-рядка, тому її можна відразу вивести на екран.

Для вирішення завдання другим способом нам знадобляться спеціальні команди, які дозволяють скоректувати значення суми після додавання кожної пари чисел ASCII-рядка. Тому в системі команд процесорів Intel передбачено чотири команди, призначені для підтримки виконання операцій додавання, віднімання, множення і ділення ASCII-чисел, а також неупакованих десяткових чисел (табл. 7.3).

 

Таблиця 7.3 - Команди для підтримки арифметичних операцій над ASCII-числами

Команда

Опис

AAA (ASCI I Adjust After Addition)

Корекція після додавання ASCII-чисел

AAS (ASCII Adjust After Subtraction)

Корекція після віднімання ASCII-чисел

AAM (ASCII Adjust After Multiplication)

Корекція після множення ASCII-чисел

AAD (ASCII Adjust Before Division)

Корекція перед діленням ASCII-чисел

 

Неупаковані десяткові числа і формат ASCI.

Десяткові числа, представлені в неупакованому форматі, відрізняються від формату ASCII тільки значенням старших 4 бітів. У першому випадку значення бітів рівні нулю, а в другому 0011b. На рис. 7.20показаний приклад представлення десяткового числа 3402 в обох форматах. Значення всіх цифр приведені в шістнадцятковому вигляді.

 

 

Рис.7.3 - Приклад представлення десяткових чисел в різних форматах

 

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

При виконанні операцій додавання і віднімання десяткових чисел операнди можуть бути представлені або у форматі ASCII, або в неупакованому вигляді. Проте для множення і ділення може використовуватися тільки неупакований формат.

 

Команда AAA

Команда AAA (ASCII Adjust After Addition) коректує значення двійкової суми, отриманої після виконання команд ADD або ADC над десятковими неупакованими числами. В результаті сума, що знаходиться в регістрі AL, завжди відповідатиме представленню чисел у форматі ASCII. У приведеному нижче прикладі показано, як можна додати дві ASCII-цифри 8 і 2 і за допомогою команди AAA отримати коректну суму в десятковому упакованому форматі. Варто звернути увагу, що перед додаванням нам потрібно обнулити вміст регістра АН. За допомогою останньої команди значення неупакованої суми перетворюється в ASCII-рядок:

 

mov ah,0

mov al,8 ; AX = 0038h

add al,2 ; AX = 006Ah

aaa ; AX = 0100h в результаті корекції

; суми за допомогою команди AAA

or ах,3030h ; АХ = 3130h або '10' в ASCII-коді

 

Команда AAS

Команда AAS (ASCH Adjust After Subtraction) використовується після команд SUB або SBB за допомогою яких одне неупаковане десяткове число віднімається з другого числа, і результат поміщається в регістр AL. В результаті різниця, що знаходиться в регістрі AL, завжди відповідатиме представленню чисел у форматі ASCII. Варто звернути увагу, що коректування результату за допомогою команди AASпотрібне тільки у разі отримання негативного числа. Наприклад, в приведеному нижче фрагменті кодиASCII-число 9 віднімається від числа 8:

 

.data

val1 BYTE '8'

val2 BYTE '9'

.code

mov ah,0

mov al,vall ; AX = 0038h

sub al,val2 ; AX = 00FFh

aas ; AX = FF09h

pushf ; Збережемо в стеку значення прапора CF

or al, 30h ; AX = FF39h

popf ; Відновлений CF

 

Після виконання команди SUB в регістрі АХ знаходилося число 00FFh. Команда AAS змінила значення регістра AL на число 09h, відняла одиницю з регістра АН (в результаті він став рівний OFFh) і встановила прапор перенесення CF. Отриманий в регістрі АХ результат після виконання команди AASпотрібно прокоментувати, оскільки на перший погляд вийшло щось не те. Результат, який повинен бути рівний -1, має шістнадцяткове представлення FF09, тобто доповнення числа -1 до десяти.

 

Команда AAM

Команда AAM (ASCII Adjust After Multiplication) призначена для коректування результату множення неупакованих десяткових чисел за допомогою команди MUL. Варто звернути увагу, що множити числа у форматі ASCII не можна! Щоб отримати правильний результат, старші чотири біта кожного десяткового числа повинні бути рівні нулю. У приведеному нижче фрагменті коду множиться число 5 на 6, а потім за допомогою команди ААМ коректується результат, що знаходиться в регістрі АХ. У результаті в регістріАХ ми отримаємо число 0300h, яке є представленням числа 30 в десятковому неупакованому форматі.

 

.data

AscVal BYTE 05h,06h

.code

mov bl,ascVal ; Завантажимо перший операнд

mov al,ascVal+l ; Завантажимо другий операнд

mul bl ; AX = 00lEh

aam ; AX = 0300h

 

 

Команда AAD

Команда AAD (ASCII Adjust Before Division) призначена для коректування діленого, представленого в десятковому неупакованому форматі в регістрі АХ, перед виконанням команди ділення. У приведеному нижче фрагменті програми десяткове неупаковане число 37 ділиться на 5. Спочатку за допомогою команди AAD число 0307h перетворюється в число 0025h. Після виконання команди DIV в регістрі ALбуде знаходитися приватне, рівне 07h, а в регістрі АН залишок, рівний 02h:

 

.data

quotient BYTE ?

remainder BYTE ?

.code

mov ах, 0307h ; Ділене

aad ; AX = 0025h

mov bl,5 ; Дільник

div bl ; AX = 0207h

mov quotient,al

mov remainder,ah

ЛЕКЦІЯ 10 Булеві операції і команди порівняння

 

У системі команд процесорів сімейства IA-32 передбачені команди AND, OR, XOR, NOT, TEST іВTn, виконуючі перераховані вище булеві операції між байтами, словами і подвійними словами (табл.1).

 

Таблиця 1- Логічні команди процесора

Команда

Опис

AND

Виконує операцію логічного І між двома операндами

OR

Виконує операцію логічного АБО між двома операндами

XOR

Виконує операцію виключаючого АБО між двома операндами

NOT

Виконує операцію логічного заперечення (НЕ) єдиного операнда

TEST

Виконує операцію логічного І між двома операндами, встановлює відповідні прапори стану процесора, але результат операції не записується замість операнда одержувача даних

ВТ, BTC, BTR,BTS

Копіює біт операнда одержувача, номер п якого заданий в початковому операнді, в прапор перенесення (CF), а потім, залежно від команди, тестує, інвертує, скидає або встановлює цей же біт операнда одержувача

 

Прапори стану процесора

Кожна команда, описана вище, впливає на стан прапорів процесора.

  Прапор нуля (Zero flag, або ZF) встановлюється, якщо при виконанні арифметичної або логічної операції виходить число, рівне нулю (тобто всі біти результату рівні 0).

  Прапор перенесення (Carry flag, або CF) встановлюється у випадку, якщо при виконанні беззнакової арифметичної операції виходить число, розрядність якого перевищує розрядність виділеного для нього поля результату.

  Прапор знаку (Sign flag, або SF) встановлюється, якщо при виконанні арифметичної або логічної операції виходить негативне число (тобто старший біт результату рівний 1).

  Прапор переповнення (Overflow flag, або OF) встановлюється у випадку, якщо при виконанні арифметичної операції із знаком виходить число, розрядність якого перевищує розрядність виділеного для нього поля результату.

  Прапор парності (Parity flag, або PF) встановлюється у випадку, якщо в результаті виконання арифметичної або логічної операції виходить число, що містить парну кількість одиничних бітів.

 

Команда AND

Команда AND виконує операцію логічного І між відповідними парами бітів операндів команди і поміщає результат на місце операнда одержувача даних:

 

AND одержувач, джерело

Існують наступні варіанти команди AND:

 

AND reg,reg

AND reg,mem

AND reg,imm

AND mem,reg

AND mem,imm

 

Команда AND може працювати з 8-, 16- або 32-розрядними операндами, причому довжина у обох операндів повинна бути однаковою. При виконанні операції порозрядного логічного І значення результату буде рівне 1 тільки в тому випадку, якщо обидва біта пари рівні 1. У табл.2 приведена таблиця істинності для операції логічного І.

 

Таблиця 2 - Таблиця істинності для операції логічного І

X

Y

X AND Y

0

0

0

0

1

0

1

0

0

1

1

1

 

 

Команда AND зазвичай використовується для скидання окремих бітів двійкового числа (наприклад, прапорів стану процесора) по заданій масці. Якщо біт маски рівний 1, значення відповідного розряду числа не змінюється (в цьому випадку говорять, що розряд замаскований), а якщо рівний 0 те скидається. Як приклад на рис. 1 показано, як можна скинути чотири старших біта 8-розрядного двійкового числа.

Рис. 1. Скидання бітів по масці за допомогою команди AND

 

Для виконання цієї операції можна скористатися двома командами:

 

mov al,00111011b

and al,00001111b

 

В даному випадку корисна інформація знаходиться в чотирьох молодших бітах числа, а значення чотирьох старших бітів для нас не має особливого значення. В результаті маскування ми виділяємо значення окремих бітів числа і поміщаємо їх в регістр AL.

Прапори. Команда AND завжди скидає прапори переповнення (OF) і перенесення (CF). Крім того, вона встановлює значення прапорів знаку (SF), нуля (ZF) і парності (PF) відповідно до значення результату.

 

Перетворення рядкових латинських букв в прописні

Команда AND дозволяє дуже просто перетворити рядкові латинські букви в прописні. Дійсно, порівнявши двійкові ASCII-коды прописною А і рядковою а, можна відмітити, що вони відрізняються тільки значенням 5-го розряду:

 

01100001 = 61h ('а')

01000001 = 41h (A)

 

Решта символів впорядкована в алфавітному порядку, але для них виконується те ж правило. Отже, якщо значення маски вибрати рівним 11011111b, то при виконанні команди AND ми скинемо тільки значення 5-го біта числа, залишивши решту всіх біт без змін. У приведеному нижче прикладі всі символи, що знаходяться в масиві array перетворюються до верхнього регістра:

 

.data

 array BYTE 50 DUP(?)

.code

mov ecx, LENGTHOF array

mov esi, OFFSET array

LI:

 AND byte ptr [esi],11011111b ; Скинемо 5-й біт

 inc esi

 loop LI

 

 

Команда OR

Команда OR виконує операцію логічного АБО між відповідними парами бітів операндів команди і поміщає результат на місце операнда одержувача даних:

 

OR одержувач, ДЖЕРЕЛО

 

В команді OR використовуються аналогічні команді AND типи операндів:

 

OR reg,reg

OR reg,mem

OR reg,imm

OR mem,reg

OR mem,imm

 

Команда OR може працювати з 8-, 16- або 32-розрядними операндами, причому довжина у обох операндів повинна бути однаковою. При виконанні операції порозрядного логічного АБО значення результату буде рівне 1, якщо хоч би один з бітів пари операндів рівний 1. У табл. 3 приведена таблиця істинності для операції логічного АБО.

 

Таблиця 3. Таблиця істинності для операції логічного АБО

X

Y

X OR Y

0

0

0

0

1

1

1

0

1

1

1

1

 

Команда OR зазвичай використовується для установки в одиницю окремих бітів двійкового числа (наприклад, прапорів стану процесора) по заданій масці. Якщо біт маски рівний 0, значення відповідного розряду числа не змінюється, а якщо рівний 1 те встановлюється в 1. Як приклад на рис. 2 показано, як можна встановити чотири молодших біта 8-розрядного двійкового числа, вибравши як маску число 0Fh.Значення старших бітів числа при цьому не міняється.

За допомогою команди OR можна перетворити двійкове число, значення якого знаходиться в діапазоні від 0 до 9 в ASCII рядку. Для цього потрібно встановити в одиницю біті 4 і 5. Наприклад, якщо в регістрі AL знаходиться число 05h, то щоб перетворити його у відповідний ASCII-код, потрібно виконати операцію OR регістра АL з числом 30h. В результаті вийде число 35h, яке відповідає ASCII-коду цифри 5 (рис. 3).

 

На мові асемблера подібне перетворення можна записати так:

 

mov dl,5

or dl,30h

 

Прапори. Команда OR завжди скидає прапори переповнювання (OF) і перенесення (CF). Крім того, вона встановлює значення прапорів знаку (SF), нуля (ZF) і парності (PF) відповідно до значення результату. Наприклад, за допомогою команди OR можна визначити, яке значення знаходиться в регістрі (негативне, позитивне або нуль). Для цього спочатку потрібно виконати команду OR, вказавши як операнди один і той же регістр, наприклад:

 

or а1,а1

 

а потім проаналізувати значення прапорів, як показано в табл. 4.

 

Таблиця 4. Визначення значення числа по прапорах стану процесора

Прапор нуля (ZF)

Прапор знаку (SF)

Значення числа

0

0

Більше нуля

1

0

Рівно нулю

0

1

Менше нуля

 

 

 

Команда XOR

Команда XOR виконує операцію ВИКЛЮЧАЮЧЕ АБО між відповідними парами бітів операндів команди і поміщає результат на місце операнда одержувача даних:

 

XOR одержувач, ДЖЕРЕЛО

 

В команді XOR використовуються аналогічні командам AND і OR типи операндів:

 

XOR reg,reg

XOR reg,mem

XOR reg,imm

XOR mem,reg

XOR mem,imm

 

Команда XOR може працювати з 8-, 16- або 32-розрядними операндами, причому довжина у обох операндів повинна бути однаковою. При виконанні операції порозрядного ВИКЛЮЧАЮЧОГО АБО значення результату буде рівне 1, якщо значення бітів пари операндів різні, і 0 якщо значення бітів рівні.

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

Операція XOR володіє властивістю реверсивності якщо її виконати двічі з одним і тим же операндом, то значення результату інвертується. Якщо двічі підряд виконати операцію XOR між бітами Хта Y, то в результаті вийде початкове значення біту X".

Властивість реверсивності операції XOR дозволяє використовувати її для виконання простого шифрування даних.

Прапори. Команда XOR завжди скидає прапори переповнювання (OF) і перенесення (CF). Крім того, вона встановлює значення прапорів знаку (SF), нуля (ZF) і парності (PF) відповідно до значення результату.

Перевірка прапора парності (PF). Прапор парності дозволяє дізнатися, яка кількість одиничних бітів (парне або непарне) міститься в молодшому байті результату виконання логічної або арифметичної команди. Якщо цей прапор встановлений, значить, в результаті вийшла парна кількість одиничних бітів, а якщо скинутий, то непарна. Кількість одиничних бітів можна перевірити, не міняючи значення результату. Для цього спочатку потрібно виконати команду XOR з нульовим значенням (тобто з числом, всі біти якого рівні нулю), а потім перевірити прапор парності:

 

mov а1,10110101b ; Число містить непарну (5) кількість

; одиничних бітів. Тому

xor al,0 ; прапор парності (PF) не встановлюється

mov а1,11001100b ; Число містить парну (4) кількість

; одиничних бітів. Тому

xor al,0 ; прапор парності (PF) встановлюється

 

У відладчиках часто для позначення парної кількості одиниць в отриманому результаті використовується абревіатура РЕ (тобто Parity Even), а для непарного РО (тобто Parity Odd).

 

Парність в 16-розрядних словах. Вище ми вже говорили про те, що прапор парності PFвстановлюється залежно від кількості одиниць, що містяться в молодших восьми розрядах результату. Для виконання контролю по парності 16-розрядних операндів, потрібно виконати команду XOR між старшим і молодшим байтами цього числа:

 

 mov ах,64Clh ; 0110 0100 1100 0001

 хоr ah,al ; Прапор парності встановлюється

 

Таким чином, 16-розрядний операнд розбивається на 2 групи по 8 бітів. При виконанні командиXOR одиничні біти, що знаходяться у відповідних позиціях двох 8-розрядних операндів, не враховуватимуться, оскільки відповідний біт результату рівний нулю. Ця команда видаляє з результату будь-які пересічні одиничні біти двох 8-розрядних операндів і додає в результат непересічні одиничні біти. Отже, парність отриманого нами 8-розрядного операнда буде така ж, як і парність початкового 16-розрядного числа.

А якщо нам потрібно оцінити парність 32-розрядного числа? Тоді, пронумерувавши його байти, відповідно, В0, В,, В2 і В3, парність можна визначити по наступній формулі: В0 XOR В1, XOR В2 XORВ3.

 

Команда NOT

Команда NOT дозволяє виконати інверсію всіх бітів операнда, внаслідок чого виходить зворотний код числа. У команді допускаються наступні типи операндів:

 

NOT reg

NOT mem

Наприклад, зворотний код числа F0h рівний 0Fh:

 

mov al, 11110000b

not al ; AL = 00001111b

 

Прапори. Команда NOT не змінює прапори процесора.

 

Команда TEST

Команда TEST виконує операцію порозрядного логічного І між відповідними парами бітів операндів і, залежно від отриманого результату, встановлює прапори стану процесора. При цьому, на відміну від команди AND, значення операнда одержувача даних не змінюється. У команді TESTвикористовуються аналогічні команді AND типи операндів. Зазвичай команда TEST застосовується для аналізу значення окремих бітів числа по масці.

 

Приклад: тестування декількох бітів.

За допомогою команди TEST можна визначити стан відразу декількох бітів числа. Припустимо, ми хочемо дізнатися, чи встановлений нульовий і третій біти регістра AL. Для цього можна скористатися такою командою:

 

 test al,00001001b ; Тестуємо біти 0 і 3

 

Як показано в приведених нижче прикладах, прапор нуля ZF буде встановлений тільки в тому випадку, якщо всі тестовані біти скинуті:

 

00100101 <- Початкове значення

00001001 <- Маска

00000001 <- Результат: ZF = 0

00100100 <- Початкове значення

00001001 <- Маска

00000000 <- Результат: ZF = 1

 

Прапори. Команда TEST завжди скидає прапори переповнювання (OF) і перенесення (CF). Крім того, вона встановлює значення прапорів знаку (SF), нуля (ZF) і парності (PF) відповідно до значення результату виконання операції логічного І (як і команда AND).

 

Команда СМР

Команда СМР віднімає початковий операнд з операнда одержувача даних і, залежно від отриманого результату, встановлює прапори стану процесора. При цьому, на відміну від команди SUB, значення операнда одержувача даних не змінюється.

 

CMP одержувач, ДЖЕРЕЛО

 

У команді СМР використовуються аналогічні команді AND типи операндів.

Прапори. Команда СМР змінює стан наступних прапорів: CF (прапор перенесення), ZF (прапор нуля), SF (прапор знаку), OF (прапор переповнювання), AF (прапор службового перенесення), PF(прапор парності). Вони встановлюються залежно від значення, яке було б отримано в результаті застосування команди SUB. Наприклад, як показано в табл. 6 після виконання команди СМР, за станом прапорів нуля (ZF) і перенесення (CF) можна судити про величини порівнюваних між собою беззнакових операндів.

 

Таблиця 6. Стан прапорів після порівняння беззнакових операндів за допомогою команди СМР

Значення операндів

ZF

CF

одержувач<джерело

0

1

одержувач>джерело

0

0

одержувач=джерело

1

0

 

 

Якщо порівнюються два операнди із знаком, то, окрім прапорів OF і ZF, потрібно враховувати ще і прапор знаку (SF), як показано в табл. 7.

 

 

Таблиця 7. Стан прапорів після порівняння операндів із знаком за допомогою команди СМР

Значення операндів

Стан прапорів

одержувач<джерело

SF <> OF і ZF = 0

одержувач>джерело

SF = OF і ZF = 0

одержувач=джерело

ZF = 1

 

 

Команда СМР дуже важлива, оскільки вона використовується практично у всіх основних умовних логічних конструкціях. Якщо після команди СМР помістити команду умовного переходу, то отримана конструкція на мові асемблера буде аналогічна операторові IF мови високого рівня.

Приклади. Розглянемо три фрагменти коду, в яких продемонстрований вплив команди СМР на прапори стану процесора. При порівнянні числа 5, що знаходиться в регістрі ЕАХ, з числом 10, встановлюється прапор перенесення CF, оскільки при відніманні числа 10 з 5 відбувається позика одиниці:

 

mov еах, 5

 cmp еах,10 ; CF = 1

 

При порівнянні вмісту регістрів еах і есх, в яких містяться однакові числа 1000, встановлюється прапор нуля (ZF), оскільки в результаті віднімання цих чисел виходить нульове значення:

 

mov еах,1000

mov есх,1000

 cmp есх,еах ; ZF = 1

 

При порівнянні числа 105 з нулем обидва прапори ZF і CF скидаються, оскільки число 105 більше нуля:

 

mov esi,105

 cmp esi,0 ; ZF = 0 і CF = 0

 

Встановлення і скидання окремих прапорів стану процесора

Як найпростіше встановити або скинути прапор нуля (ZF), знаку (SF), перенесення (CF) і переповнювання (OF)? Для цього існують декілька простих способів, і більшість з них змінює значення операнда одержувача даних. Щоб встановити прапор нуля (ZF), можна виконати команду AND знульовим операндом, а для скидання цього прапора команду OR з одиничним операндом, як показано нижче:

and al,0 ; Прапор ZF встановлюється

or al,l ; Прапор ZF скидається

 

Для встановлення прапора знаку (SF) можна виконати команду OR, у якої старший біт операнда встановлений в 1, а для скидання цього прапора команду AND, у якої старший біт операнда скинутий в 0, як показано нижче:

 

or al,80h ; Прапор SF встановлюється

and al,7Fh ; Прапор SF скидається

 

Для встановлення і скидання прапора перенесення (CF) передбачені спеціальні команди: STC (SETCarry flag) і CLC (CLear Carry flag):

 

stc ; Прапор CF встановлюється

clc ; Прапор CF скидається

 

Щоб встановити прапор переповнювання (OF), потрібно додати два позитивні числа так, щоб в результаті вийшла негативна сума. Для скидання цього прапора досить виконати команду OR з нульовим операндом, як показано нижче:

 

 mov al,7Fh ; al= +127

 inc al ; al=80h (-128), OF=1

 or al,0 ; прапорець OF скидається

 

Лекція 11 Команди простого, арифметичного і циклічного зсуву

 

Однією з відмітних особливостей мови асемблера є підтримка засобів роботи з окремими бітами, включаючи побітові логічні команди і команди зсуву. Операція зсуву означає переміщення всіх бітів операнда управо або вліво на одну або декілька позицій. У табл. 7.1 перераховані всі команди зсуву; при їх виконанні змінюється стан прапорів переповнювання OF і перенесення CF.

 

Таблиця 7.1 Команди зсуву

Команда

Опис

SHL

Зсув вліво

SHR

Зсув вправо

SAL

Зсув арифметичний вліво

SAR

Зсув арифметичний управо

ROL

Циклічний зсув вліво

ROR

Циклічний зсув управо

RCL

Циклічний зсув вліво через прапор перенесення

RCR

Циклічний зсув управо через прапор перенесення

SHLD

Зсув вліво подвоєний

SHRD

Зсув управо подвоєний

 

 

Логічні і арифметичні зсуви

Операція зсуву бітів цілого числа може виконуватися двома способами. Перший називаєтьсялогічним зсувом, при якому висунута позиція бітового розряду заповнюється нулем. На рис. 7.1 продемонстрована операція логічного зсуву байта на один розряд вправо. В результаті, біту 7 привласнено нульове значення.

 

Рис. 7.1. Ілюстрація операції логічного зсуву байта

вправо на один розряд

 

Наприклад, при виконанні логічного зсув управо на один розряд байта, значення якого рівне11001111, отримаємо число 01100111.

Другий тип зсуву називається арифметичним. Під час його виконання висунута позиція бітового розряду заповнюється первинним значенням знакового розряду, як показано на рис. 7.2.

 

Рис. 7.2. Ілюстрація операції арифметичного зсуву байта

вправо на один розряд

Наприклад, байт, значення якого рівне 11001111, має одиницю в знаковому розряді. При виконанні арифметичного зсуву його вправо на один розряд отримаємо число 11100111.

 

Команда SHL

Команда SHL (SHift Left) виконує логічне зсув вліво операнда одержувача даних на кількість розрядів, вказаних в початковому (тобто другому) операнді. При цьому молодші "висунуті" розряди заповнюються нулями. Старший розряд числа переходить в прапор перенесення CF, а біт, який до цього знаходився в прапорі перенесення, втрачається (рис. 7.3).

 

РИС. 7.3. Ілюстрація операції логічного зсуву байта вліво

на один розряд (команда SHL)

 

Перший операнд команди SHL визначає число, яке буде зсунуте, а другий кількість розрядів, на які проводиться зсув:

 

SHL операнд, лічильник

 

Нижче приведені допустимі формати операндів команди SHL:

 

SHL reg, imm8

SHL mem, imm8

SHL reg, CL

SHL mem, CL

 

У процесорах Intel 8086/8088 як лічильник в командах зсуву можна було вказувати безпосередньо задане значення imm8рівне тільки одиниці. Проте починаючи з моделі процесора Intel 80286 це обмеження було зняте. Тепер на місці imm8 можна указувати будь-яке 8-розрядне ціле число. Останні два формати операндів команд зсуву, в яких лічильник розрядів зсуву указується в регістрі CL, працюють на будь-яких моделях процесорів фірми Intel (як старих, так і нових). Приведені вище формати операндів справедливі також і для інших команд зсуву, таких як SHR, SAL, SAR, ROR, ROL, RCR І RCL.

 

Приклад. В наведеному нижче фрагменті коду вміст регістра BL зміщується вліво на один розряд. При цьому старший біт поміщається в прапор CF, а самий молодший біт обнуляється:

 

mov bl, 8Fh ; bl = 10001111b

shl bl,1 ; bl = 00011110b CF = 1

 

Швидке множення. Найчастіше команда SHL використовується для виконання швидкого множення деякого числа на число, кратне 2. Насправді, зсув двійкового числа вліво на n розрядів означає його множення на 2n. Наприклад, в результаті зсув числа на один розряд вліво виходить число 10, тобто добуток 5x2 (рис. 7.4):

 

mov dl,5

shl dl,1

Рис. 7.4. Ілюстрація швидкого виконання операції множення

 

Команда SHR

Команда SHR (SHift Right) виконує логічний зсув вправо операнда одержувача даних на кількість розрядів, вказаних в початковому (тобто другому) операнді. При цьому старші "висунуті" розряди заповнюються нулями. Молодший розряд числа поміщається в прапор перенесення CF, а біт, який до цього знаходився в прапорі перенесення, втрачається (рис. 7.5).

 

Рис. 7.5. Ілюстрація операції логічного зсув байта управо на один розряд

 

У команді SHR використовуються такі ж формати операндів, як і в команді SHL. У наведеному нижче фрагменті коди значення молодшого біта регістра AL, рівне нулю, поміщається в прапор перенесення CF, а старший біт регістра AL обнуляється:

 

 mov al,0D0h ; AL = 11010000b

 shr al,l ; AL = 01101000b, CF = 0

 

Швидке ділення. Як відомо, зсув двійкового числа вліво на n розрядів приводить до його множення на 2n. Отже, зсув числа вправо на n розрядів повинен приводити до його ділення на 2nНаприклад, в результаті зсуву вправо числа 32 на один розряд (тобто на 21) отримуємо число 16 (рис. 7.6):

 

mov dl,32

shr dl,1

Рис. 7.6. Ілюстрація швидкого виконання операції ділення

 

У наступному прикладі число 64 ділиться на 8 (тобто 23):

 

mov al,01000000b ; AL = 64

shr al,3 ; Ділимо на 8, AL = 00001000b

 

Ділення чисел із знаком шляхом зсув управо виконується командою SAR, оскільки вона зберігає значення знакового розряду.

 

Команди SAL і SAR

Команда SAL (Shift Arithmetic Left, або арифметичний зсув вліво) повністю еквівалента командіSHL, оскільки при зсуві вліво значення знакового розряду не зберігається. Команда SAR (Shift ArithmeticRight) виконує арифметичний зсув управо операнда одержувача даних на кількість розрядів, вказаних в початковому (тобто другому) операнді. При цьому старші "висунуті" розряди заповнюються колишнім значенням знакового розряду. Молодший розряд числа поміщається в прапор перенесення CF, а біт, який до цього знаходився в прапорі перенесення, втрачається (рис. 7.7).

 

Рис. 7.7. Ілюстрація операції арифметичного зсув байта управо

на один розряд (команда SAR)

 

У командах SAL і SAR використовуються такі ж формати операндів, як і в командах SHL і SHR.Перший операнд визначає зсунуте число, а другий кількість розрядів, на які проводиться зсув:

 

SAL операнд, лічильник

SAR операнд, лічильник

 

У наступному прикладі показано, як в результаті арифметичного зсуву регістра AL вправо на один розряд виконується дублювання знакового біта. Тому до і після виконання операції зсуву в регістрі ALзалишається негативне число:

 

mov аl, 0F0h ; АL = 11110000H (-16)

 sar аl,1 ; АL = 11111000h (-8), CF = 0

 

Швидке ділення чисел із знаком. Команда SARвикористовується для виконання швидкої операції ділення деякого числа на число, кратне 2n. У приведеному нижче прикладі число -128 ділиться на 8 (тобто 23). Результат рівний -16:

 

mov dl, -128 DL = 10000000b (-128)

sar dl, 3 ; DL = 11110000b (-16)

 

 

Команда ROL

Команда ROL (ROtate Left) циклічно зсуває кожен біт операнда одержувача даних вліво на кількість розрядів, вказаних в початковому (тобто другому) операнді. При цьому старший біт числа копіюється в молодший біт, а також в прапор перенесення CF (рис. 7.8). У команді ROL використовуються такі ж формати операндів, як і в команді SHL.

 

Рис. 7.8. Ілюстрація операції циклічного зсуву байта вліво

на один розряд (команда ROL)

 

Циклічний зсув відрізняється від простого зсуву тим, що в результаті його виконання значення бітів числа не втрачаються, а просто переміщаються по кругу: старший біт поміщається на місце молодшого, молодшого на місце біта 1, потім біт 1 на місце біта 2 і т.д. У приведеному нижче прикладі значення старшого біта копіюється в молодший біт і в прапор перенесення CF:

 

mov al,40h ; AL = 01000000b

rol al,l ; AL = 10000000b CF = 0

rol al,l ; AL = 00000001b CF = 1

rol al,l ; AL = 00000010b CF = 0

 

Команду ROL можна використовувати для обміну старшого (біти 4-7) і молодшого (біти 0-3) півбайтів числа. Наприклад, в результаті циклічного зсув вліво числа 26h отримаємо число 62h:

 

mov а1,26h

rol al,4 ; AL = 62h

 

Команда ROR

Команда ROR (ROtate Right) циклічно зсуває кожен біт операнда одержувача даних вправо на кількість розрядів, вказаних в початковому (тобто другому) операнді. При цьому молодший біт числа копіюється в старший біт, а також в прапор перенесення CF (рис. 7.9). У команді ROR використовуютьсятакі ж формати операндів, як і в команді SHR.

Рис. 7.9. Ілюстрація операції циклічного зсуву байта вправо

на один розряд (команда ROR)

 

В наведеному нижче прикладі значення молодшого біту числа копіюється в старший біт і в прапор переносу CF.

 

mov al,01h ; AL = 00000001b

ror al,l ; AL = 10000000b, CF = 1

ror al,l ; AL = 01000000b, CF = 0

 

Команди RCL і RCR

Команда RCL (Rotate Carry Left) циклічно зсуває через прапор перенесення кожен біт операнда одержувача даних вліво на кількість розрядів, вказаних в початковому (тобто другому) операнді. При цьому значення прапора перенесення CF поміщається на місце самого молодшого біта, а самий старший (знаковий) біт числа поміщається в прапор перенесення CF (рис. 7.10).

 

Рис. 7.10. Ілюстрація операції циклічного зсув вліво байта через прапор

перенесення на один розряд (команда RCL)

 

Якщо вважати прапор перенесення CF додатковим розрядом числа, розташованим перед знаковим розрядом, то тоді команда RCL нічим не відрізняється від команди циклічного зсуву вліво ROL, за винятком того, що вона виконується над 9-розрядним операндом.

У наведеному нижче прикладі команда CLC скидає значення прапора перенесення CF. За допомогою першої команди RCL старший біт регістра BL копіюється в прапор перенесення CF, після чоговсі біти цього регістра циклічно зсуваються вліво на один розряд. Друга команда RCL переміщає прапорCF в молодший розряд регістра BL і циклічно зсуває всі біти цього регістра на один розряд вліво:

 

сlс ; CF = 0

mov bl,88h ; CF, BL = 0 10001000b

rcl bl,l ; CF, BL = 1 00010000b

rcl bl,l ; CF, BL = 0 00100001b

 

Витягання значення прапора перенесення CF.Команду RCL можна використовувати для відновлення значення біта, який був раніше висунутий в прапор перенесення CF. У наведеному нижче прикладі виконується перевірка значення молодшого біта змінної testval шляхом його зсуву в прапор CF за допомогою команди SHR. Подальша команда RCLвідновлює первинне значення цього біта.

.data

testval BYTE 01101010b

.code

shr testval,1 ; Зсунемо молодший біт в прапор CF

jc quit ; Завершити роботу, якщо CF =1

rcl testval,1 ; Інакше відновити первинне

; значення числа

 

Команда RCR

Команда RCR (Rotate Carry Right) циклічно зсуває через прапор перенесення кожен біт операнда одержувача даних вправо на кількість розрядів, вказаних в початковому (тобто другому) операнді. При цьому значення прапора перенесення CF поміщається на місце самого старшого (тобто знакового) біта, а самий молодший біт числа поміщається в прапор перенесення CF (рис. 7.11).

 

Рис. 7.11. Ілюстрація операції циклічного зсуву вправо байта через прапор перенесення на один розряд (команда RCR)

Як і у випадку з командою RCL, ми представили операнд у вигляді 9-розрядного двійкового цілого числа, в якому прапор перенесення CF розташовується правіше самого молодшого розряду.

У приведеному нижче прикладі спочатку за допомогою команди STC встановлюється прапорперенесення CF. Потім за допомогою команди RCR він поміщається в самий старший біт регістра АН, а значення його молодшого біта переноситься у прапор CF:

 

stc ; CF = 1

mov ah,10h ; CF, AH = 00010000 1

rcr ah,l ; CF, AH = 10001000 0

 

Команди SHLD і SHRD

Команди SHLD і SHRD з'явилися тільки в процесорі Intel 386. На відміну від розглянутих вищекоманд зсуву, у цих команд не два, а три операнди. Команда SHLD (SHift Left Double, або зсув вліво подвоєний) виконує логічний зсув вліво операнда одержувача даних на кількість розрядів, вказаних в третьому операнді. Розряди операнда одержувача даних, що звільнилися в результаті зсуву, заповнюються старшими бітами початкового (тобто другого) операнда. При цьому значення початкового операнда не змінюється, але міняється стан прапорів знаку SF, нуля ZF, службового перенесення AF, парності PF і перенесення CF. Синтаксис команди SHLD наступний:

 

SHLD одержувач, джерело, лічильник

 

Команда SHRD (SHift Right Double, або зсув вправо подвоєний) виконує логічний зсув вправо операнда одержувача даних на кількість розрядів, вказаних в третьому операнді. Розряди операнда одержувача даних, що звільнилися в результаті зсуву, заповнюються молодшими бітами початкового (тобто другого) операнда. Синтаксис команди SHLD наступний:

 

SHRD одержувач, джерело, лічильник

 

Команди SHLD і SHRD мають однаковий формат операндів, описаний нижче. Операнд-одержувачданих може розташовуватися або в пам'яті, або в регістрі. Початковий операнд може знаходитися тільки в регістрі. Як лічильник може бути заданий або регістр CL, або 8-розрядна константа:

 

SHLD reg16, reg16, CL/imm8

SHLD meml6, reg16, CL/imm8

SHLD reg32, reg32, CL/imm8

SHLD mem32, reg32, CL/imm8

 

Приклад 1. В наведеному нижче фрагменті коду 16-розрядна змінна wval зсувається на 4 біти вліво. Звільнені молодші чотири розряди змінної wval заповнюються чотирма старшими розрядами регістра АХ (рис. 7.12):

 

.data

wval WORD 9BA6h

.code

mov ах, 0AC36h

shld wval, ax,4 ; wval = BA6Ah

 

Рис. 7.12. Схема виконання подвоєного зсуву вліво

 

Приклад 2. У наведеному нижче фрагменті коду 16-розрядний регістр АХ зсувається на 4 біти вправо. Звільнені старші чотири розряди регістра АХ заповнюються чотирма молодшими розрядами регістра DX (рис. 7.13):

 

mov ах, 234В11

mov dx, 7654h

shld ах,dx,4 ; АХ = 4234h

 

Рис. 7.13. Схема виконання подвоєного зсуву вправо

 

Команди SHLD і SHRD часто використовуються для виконання різних операцій з растровими зображеннями, коли необхідно зсунути вліво або вправо групу бітів для перепозиціонування картинки на екрані. Крім того, дані команди можна з успіхом застосовувати в застосуваннях для шифрування даних, алгоритм роботи яких побудований на операціях зсуву групи бітів. Нарешті, ці дві команди можуть використовуватися при виконанні операції швидкого множення або ділення цілих чисел з дуже великою розрядністю.

Лекція 12 Команди передачі керування (умовного та безумовного переходу)

 

 

1.     Команди передачі керування (умовного та безумовного переходу). Формат, умови переходу, звязок із прапорцями процесора. Взаємодія з командами CMP та SUB.

2.     Організація циклів.

 

1.     Команди передачі керування (умовного та безумовного переходу). Формат, умови переходу, звязок із прапорцями процесора. Взаємодія з командами CMP та SUB.

 

Команди передачі керування

Команди переходів можна розділити на три групи:

  1. Команди безумовної передачі керування:

  команди безумовного переходу;

  виклик процедури і повернення із процедури;

  виклик програмних переривань і повернення із програмних переривань.

  1. Команди умовної передачі керування:

  команди переходу по результату команди порівняння;

  команди переходу по стану певного прапора;

  команди переходу по вмісту регістра X/CX;

  1. Команди керування циклом:

  команда організації циклу із лічильником ECX/CX;

  команда організації циклу із лічильником  з можливістю дострокового виходу із циклу по додатковій умові.

Щоб позначити місце куди необхідно передати керування, використовується мітка. Мітка - це символічне імя, що позначає певну комірку памяті, призначену для використання в якості операнду в командах передачі керування. Мітці асемблер присвоює три атрибути:

  •   я сегменту коду, де ця мітка описана;

  зміщення - віддаль в байтах від початку сегменту, де описана мітка;

  тип мітки, або атрибут віддалі (near - перехід на мітку можливий тільки в межах сегменту, far - перехід на мітку можливий в результаті міжсегментної передачі керування).

 

Мітку можна описати двома способами:

  оператором : ( символічне_ім.я : [ команда ассемблера ] );

  •   символічне_ім.я label: [ команда ассемблера ] ).

За допомогою оператора : можна визначити тільки мітку типу near. Ця мітка може використовуватися в якості операнду в командах умовного jcc і безумовного jmpcall переходу, які повинні бути у тому ж сегменті що і мітка.

 

Директива label визначає мітки типу far. Мітка може бути тільки одного типу - near або far. Для того, щоб зробити видимим імя мітки ззовні, застосовується директива public:

public m _far ; зробити мітку видимою для зовнішніх програм

.

m_far label far ; визначення мітки дального типу

m_near: ; визначення мітки ближнього типу

 

Другий випадок застосування директиви label - це організація доступу до однієї області пам.яті, як до області, що містить дані різних типів:

mas_b label byte

mas_w dw 15 dup (?)

...

mov mas_b+10, al ; записати в 11 байт із al

mov mas_w, ax ; записати в перше слово області mas_w із ах

До міток має також пряме відношення лічильник адреси команд (зміщення поточної команди відносно початку сегмента). Для роботи із цим лічильником є дві можливості:

  використання міток, атрибуту зміщення яких транслятор присвоює значення лічильника адреси тієї команди, перед якою вони появились;

  застосування спеціального символу $ для позначення лічильника адреси команд. Цей символ дозволяє у будь-якому місці програми використовувати числове значення лічильника адреси.

 

.data

Str_Mes db .Testova stroka.

Len_mes=$-Str_Mes ; Len_mes дорівнює довжині строчки Str_Mes.

 

Для встановлення значення лічильника адреса в абсолютне значення використовується директива:

 

ORG вираз

 

Наприклад: org 100h. Цю директиву завжди використовують при створенні виконуваного файлу з типом .com для резервування префікса програмного сегменту величною 256 байт (100h).

 

Команди безумовного переходу

 

jmp [модифікатор] адреса_переходу безумовний перехід без зберігання інформації про точку повернення.

 

Модифікатор:

  near ptr прямий перехід на мітку всередині поточного сегмента коду. Модифікується тільки регістрEIP/IP або вираз, що використовує символ $.

  far ptr прямий перехід на мітку у другому сегменті коду. Адреса переходу складається із 16-бітного селектора і 16/32-бітного зміщення, які завантажуються у регістри CS і IP/EIP відповідно.

  word ptr непрямий перехід на мітку в середині поточного сегменту коду. Модифікується значенням зміщення із памяті або регістра тільки EIP/IP. Розмір зміщення 16 або 32 біти.

  dword ptr непрямий перехід на мітку у другому сегменті коду. Модифікується значенням тільки із памяті CS і EIP/IP.

Команда jmp має декілька кодів машинних команд, що відрізняються дальністю переходу і заданням адреси. Адреса переходу може бути внутрішньосегментною або міжсегментною.

Внутрішньосегментний перехід змінює тільки вміст регістрів EIP/IP і має три варіанти:

  прямий короткий;

  •   
  •   

Прямий короткий перехід застосовується, коли віддаль від команди jmp до адреси переходу не більше -128 або +127 байтів. У цьому випадку формується коротка 2-х байтова машинна команда. Якщо адреса переходу розміщена до команди jmp, то формується коротка команда без додаткових вказівок. У випадку коли адреса переходу знаходиться після команди jmp, необхідно використовувати модифікаторshort ptr:

 

jmp short ptr

... ; не більш 127 байтів

m1:

 

Прямий внутрішньосегментний перехід формує 3-х байтну машинну команду, що дозволяє здійснювати переходи в межах 64Кбайт відносно наступної за jmp команди:

m1:

... ; віддаль більш 128 байт і менше 64 Кбайт.

jmp m1

 

Непрямий внутрішньосегментний перехід задає не саму адресу, а місце де вона знаходиться:

 

lea bx,m1

jmp bx ; адреса переходу в регістрі bx

...

m1

addr dw m1

...

jmp addr ; адреса переходу в комірці памяті

...

m1

 

Міжсегментний перехід має другий формат машинної команди. При між сегментному переході, крім регістра EIP/IP модифікується також регістр CS. Міжсегментний перехід підтримує два варіанти команд: прямого і непрямого переходу.

Прямий міжсегментний перехід команда має довжину 5 байтів, із яких два байти зміщення і два байти відводяться на адресу сегменту. Приклад:

 

Seg1 segment

jmp far ptr m2 ; модифікатор far обов.язковий

m1 label far

seg1 ends

seg2 segment

.

m2 label far

jmp m1

 

Якщо команда переходу зустрічається до описання мітки, то в команді jmp необхідно вказати модифікатор far ptr. Без вказівки модифікатора транслятор формує 3-х байтну команду.

Непрямий міжсегментний перехід має в якості операнду адресу в області памяті, де міститься сегментна складова і зміщення. Приклад:

 

addr dd m1 ; в полі addr знаходиться адрес сегмента мітки і зміщення

code1 segment

.

jmp m1

 

Варіантом непрямого міжсегментного переходу є непрямий регістровий між сегментний перехід. У цьому переході адреса вказується в регістрі. Це дає можливість програмувати динамічні переходи. Приклад:

 

data segment

addr_m1 dd m1 ; в полі addr_m1 значення зміщення і адрес мітки m1

data ends

code_1 segment

lea bx,addr_m1

jmp dword ptr[bx]

code_1 ends

code_2 segment

m1 label far

mov ax,bx

code_2 segment

 

Таким чином, модифікатори short ptrnear ptr і word ptr застосовуються для організації внутрішньосегментних переходів, а far ptr і dword ptr для міжсегментних.

 

Умовні переходи

Є 18 команд умовного переходу, які дозволяють перевірити:

  відношення між операндами із знаком (більше-менше);

  відношення між операндами без знака (вище-нижче);

  стан арифметичних прапорів ().

 

Синтаксис команд умовного переходу:

jcc мітка_переходу

 

Значення абревіатури в назві команді jcc наведено в табл. 1. Мітка переходу може знаходитися тільки в межах поточного сегменту. Для того, щоб прийняти рішення, куди буде передано керування, необхідно сформувати умову. Джерелом такої умови можуть бути:

  будь-яка команда, що змінює стан арифметичних прапорів;

  команда порівняння cmp, що порівнює значення двох операндів;

  стан регістру .

 

Команда порівняння cmp

Команда cmp віднімає операнди і порівнює прапори:

cmp операнд_1, операнд_2

 

Прапори, які встановлює команда команда cmp, можна аналізувати спеціальними командами умовного переходу із табл. 1. Оскільки команди умовного переходу не змінюють прапорів, то після однієї команди cmp може слідувати декілька команд умовного переходу.

 

Таблиця 1 Перелік команд умовного переходу для команди cmp

Типи

операндів

 

Мнемокод

Критерій умовного переходу

Значення

прапорів для

переходу

Будь-які

JE

операнд_1 = операнд_2

ZF=1

Будь-які

JNE

операнд_1 <> операнд_2

ZF=0

 

Із знаком

JL/JNGE

операнд_1 < операнд_2

SF<>OF

Із знаком

JLE/JNG

операнд_1 <= операнд_2

SF<>OF АБО ZF=1

Із знаком

JG/JNLE

операнд_1 > операнд_2

SF=OF І ZF=0

Із знаком

JGE/JNL

операнд_1 => операнд_2

SF=OF

Без знаку

JB/JNAE

операнд_1 < операнд_2

CF=1

Без знаку

JBE/JNA

операнд_1 <= операнд_2

CF=1 АБО ZF=1

Без знаку

JA/JNBE

операнд_1 > операнд_2

CF=0 І ZF=0

Без знаку

JAE/JNB

операнд_1 => операнд_2

CF=0

 

 

* Примітка. g-більше, l-менше, а-вище, b-нижче

 

Команди умовного переходу і прапори

Мнемонічне позначення деяких команд умовного переходу відображає назву прапора, з яким вони працюють. Перед позначенням прапора може стояти символ n. Якщо символа n немає, то перевіряється стан прапора і, якщо він дорівнює 1, робиться перехід на мітку. Якщо символ n присутній, то перехід на мітку здійснюється при стані прапора 0. Мнемокоди команд і прапори наведено в табл. 2.

 

Таблиця 2 Команди умовного переходу і прапори

Назва прапора

Номер біта

в eflags/flags

Команди умовного переходу

Значення прапора для переходу

Прапор переносу CF

1

JC

CF=1

Прапор парності PF

2

JP

PF=1

Прапор нуля ZF

6

JZ

ZF=1

 
 

продовження таблиці 2

Назва прапора

Номер біта

в eflags/flags

Команди умовного переходу

Значення прапора для переходу

Прапор знаку SF

7

JS

SF=1

Прапор переповнення OF

11

JO

OF=1

Прапор переносу CF

1

JNC

CF=1

Прапор парності PF

2

JNP

PF=1

Прапор нуля ZF

6

JNZ

ZF=1

Прапор знаку SF

7

JNS

SF=1

Прапор переповнення OF

11

JNO

OF=1

 

 

Команди умовного переходу і регістр ECX/CX

Регістр ECX/CX виконує роль лічильника в командах керування циклами і при роботі з ланцюжками символів. З цим регістром повязана команда умовного переходу:

 

jcxz мітка_переходу (jump if cx is zero)- перехід якщо cx ноль.

 

jecxz мітка_переходу (jump Equal cx is zero) - перехід якщо ecx ноль.

 

Команда jcxz/jecxz може адресувати тільки короткі переходи на -128 або +127 байт від наступної за нею команди.

 

2.     Організація циклів

Для полегшення програмування циклів є три команди:

  1. loop мітка_переходу (Loop) повторити цикл. Команда дозволяє організувати цикли, аналогічні до циклів for у мовах високого рівня. Робота команди полягає у наступному:

  декремент регістра ;

  порівняння регістра  з нулем;

  якщо ()>0, то керування передається на мітку переходу;

  якщо ()=0, то керування передається на наступну після loop команду.

  1. loope/loopz мітка_переходу (Loop till CX <> 0 або ZF=0) повторити цикл, поки CX<>0 або ZF=0. Робота команди полягає у наступному:

  декремент регістра ;

  порівняння регістра з нулем;

  аналіз стану прапора нуля 

  якщо ()>0 і =1, то керування передається на мітку переходу;

  якщо ()=0 або =1, то керування передається на наступну після loop команду.

  1. loopne/loopnz мітка_переходу (Loop till CX <> 0 або ZF=1) повторити цикл, поки CX<>0 або ZF=1. Робота команди полягає у наступному:

  декремент регістра ;

  порівняння регістра з нулем;

  аналіз стану прапора нуля 

  якщо ()>0 і =0, то керування передається на мітку переходу;

  якщо ()=0 або =1, то керування передається на наступну після loop команду.

Команди loope/loopz і loopne/loopnz дозволяють достроково вийти із циклу на основі аналізу прапораZF.

Недолік команд організації циклів looploope/loopzloopne/loopnz в тому, що вони реалізовують тільки короткі переходи від -128 до +127. Для роботи із довгими циклами необхідно використовувати команди умовного переходу і команду jmp.

Лекція 13 Процедури. Визначення і використання процедур. Макроси.

 

1.     Процедури. Визначення і використання процедур. Виклик, порядок використання та повернення з процедури.

2.     Макроси, спосіб оголошення та виклику.

 

 

1.     Процедури. Визначення і використання процедур. Виклик, порядок використання та повернення з процедури.

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

Основні вимоги до процедур:

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

  при виході з процедур повернення завжди проводиться до команди, що знаходиться, після виклику, незалежно від того, де знаходився виклик, - ця дія здійснюється автоматично при виконанні командиRET, при цьому адреса повернення (одне слово у разі ближньої і два слова у разі дальньої процедури) витягується із стека;

  вміст використовуваних процедурою регістрів необхідно запам'ятовувати до їх зміни, а перед поверненням з процедури відновлювати - ці дії (звичайно здійснюються командами PUSH і POP) зобов'язаний виконувати програміст;

  процедура повинна мати засоби взаємодії з програмою, що викликала її.

Змінні, які передаються процедурі викликаючою програмою, називаються параметрами. Коли вхідний параметр має довжину, рівну байту або слову, простіше передавати сам параметр, інакше простіше передавати його адресу. Проте для уніфікації зазвичай застосовують передачу адрес параметрів (покажчиків), щоб розробник процедури завжди точно знав, як передається необхідна йому інформація. Є два основні способи передачі параметрів процедурі: побудова таблиць (або масиву), що містить адреси параметрів, і передача адреси цієї таблиці через регістр або включення адрес параметрів в стек.

Обмеження тіла підпрограми директивами PROC і ENDP дозволяє створювати локальні мітки, що є корисним при використанні в різних підпрограмах однакових міток. Всі локальні мітки повинні починатися з символів "@@". Всі такі мітки будуть локалізовані в підпрограмі, якщо перед її командами буде вказана директива LOCALS.

 

Визначення процедури

Нестрого процедуру можна визначити як іменований блок команд, що закінчується оператором повернення. Для оголошення процедури використовуються директиви PROC і ENDP. При оголошенні процедурі повинне бути призначене ім'я, яке є одним з дозволених ідентифікаторів.

При створенні будь-яких процедур, що не є стартовими, потрібно в їх кінці розмістити команду RET. В результаті процесор поверне управління команді, наступній за тією, яка викликала цю процедуру:

 

sample PROC

ret

sample ENDP

 

До особливого типу процедур відноситься так звана стартова процедура програми, якій призначено ім'я main, оскільки вона повинна завершуватися не командою RET, а оператором exit. Насправді exit - це не вбудований оператор асемблера, а символ, визначений за допомогою директиви EQU:

 

exit EQU <INVOKE Exitprocess,0>

 

Замість нього компілятор підставляє виклик функції Exitprocess системи Windows, яка і завершує виконання програми:

 

INVOKE Exitprocess,0

 

Приклад: сумування трьох цілих чисел

Створимо процедуру Sumof, що обчислює суму трьох 32-розрядних чисел. Припустимо, що перед викликом процедури значення цих чисел ми повинні помістити в регістри ЕАХ, ЕВХ і ЕСХ, а сума повертається в регістрі ЕАХ:

 

Sumof PROC

add eax,ebx

add eax,ecx

ret Sumof ENDP

 

Документування процедур

Хорошим стилем програмування вважається використання в програмі коротких і зрозумілих коментарів, які дозволяють програмістові швидко розібратися в її суті. Нижче приведено декілька рекомендацій з приводу інформації, яка повинна бути розміщена на початку кожної процедури:

  1. Опис всіх функцій, що виконуються процедурою.
  2. Список вхідних параметрів і опис їх значень. Якщо який-небудь з параметрів має особливий тип, його потрібно також вказати. Зазвичай вхідні параметри указуються після ключового словаПередається (Receives).
  3. Список повертаних процедурою значень, вказаних після ключового слова Повертається (Returns).
  4. Перелік особливих вимог (якщо такі є), які повинні бути задоволені перед викликом процедури. Вони називаються вхідними умовами і указуються після ключового слова Потрібний (Requires).Наприклад, для процедури, яка креслить на екрані пряму лінію, однією з вхідних умов є робота відеоадаптера в графічному режимі.
  5. Вибрані нами ключові слова, такі як Передається (Receives), Повертається (Returns) і Потрібний (Requires), є такими, що рекомендуються. Замість них програмісти можуть вибрати та інші, не менш зрозумілі описувачі.

Врахувавши наведені вище зауваження, задокументуємо процедуру Sumof:

 

Sumof PROC

; Обчислює і повертає суму трьох 32-розрядних цілих чисел.

; Передається: три числа в регістрах ЕАХ, ЕВХ, ЕСХ.

; Числа можуть бути як із знаком, так і без нього.

; Повертається: сума в регістрі ЕАХ, а також прапори стану

; (перенесення, переповнювання і ін.)

add eax,ebx

add eax,ecx

ret Sumof ENDP

 

Команди CALL і RET

Команда CALL призначена для передачі управління процедурі, адреса якої указується як параметр. При цьому процесор починає виконувати команду, розташовану за вказаною адресою. Щоб повернути управління команді, розташованій відразу за CALL, в процедурі використовується команда RET. Строго кажучи, команда CALL поміщає в стек поточне значення лічильника команд, який на фазі виконання команди CALL містить адресу наступної команди, а потім завантажує в лічильник команд вказану адресу процедури. При поверненні з процедури (тобто при виконанні в ній команди RET), адреса повернення завантажується із стека в лічильник команд. Нагадаємо, що процесор завжди виконує команду, адреса якої указується в регістрі EIP, тобто в лічильнику команд. У 16-розрядному режимі роботи як лічильник команд використовується регістр IP.

 

Приклад виклику і повернення з процедури

Припустимо, що в процедурі main по зсуву 00000020h розташована команда CALL. У 32-розрядному режимі довжина цієї команди складає 5 байтів. Тому наступна команда (у нашому случаєmov) буде розташована із зсувом 00000025h:

 

main PROC ;EIP=00000020h

call Mysub ;EIP=00000025h

mov eax,ebx

 

Далі, припустимо, що перша команда процедури Mysub розташована із зсувом 00000040h:

 

Mysub PROC ;EIP=00000040h

mov eax,edx

ret Mysub

ENDP

 

При виконанні команди CALL в стек поміщається адреса наступної за нею команди (в даному випадку 00000025h), після чого в регістр EIP завантажується адреса процедури Mysub, як показано на рис. 5.10.

Після цього процесор починає виконувати послідовність команд процедури Mysub, поки в ній не зустрінеться команда RET. При виконанні команди RET вміст стеку, на який вказує регістр ESP,копіюється в регістр EIP. В результаті процесор після команди RET виконуватиме не наступну за нею команду, а команду, що знаходиться за адресою 00000025h. А це якраз команда, розташована слідом за командою CALL, яка викликала дану процедуру, як показано на рис. 5.11.

 

Вкладені виклики процедур

Вкладений виклик процедури відбувається, коли викликана процедура викликає ще одну процедуру до того, як управління буде передано викликаній процедурі. Припустимо, що з процедури mainвикликається процедура Subl. При виконанні процедури Subl викликається процедура Sub2, яка у свою чергу викликає процедуру Sub3. Цей процес показаний на рис. 5.12.

При виконанні команди RET в кінці процедури Sub3, в лічильник команд буде занесено поточний вміст вершини стека, на яке указує регістр ESP. В результаті процесор продовжить виконання команд процедури Sub2 і передасть управління команді, наступній за командою виклику процедури Sub3 (call Sub3). На рис. 5.13 показаний вміст стека перед поверненням з процедури Sub3.

Після повернення в процедуру Sub2 регістр ESP указуватиме на наступний елемент в стеку. На рис. 5.14 показаний вміст стека перед поверненням з процедури Sub2.

І нарешті, при поверненні з процедури Subl із стека в покажчик команд завантажиться адреса команди, наступної за call Subl. В результаті процесор відновить виконання послідовності команд процедури main. На рис. 5.15 показаний вміст стека перед поверненням з процедури Subl.

 

 

 

 

 

 

 

 

 

 

Як видно з описаного прикладу, стек може використовуватися як якийсь пристрій для нагадування інформації. З його допомогою дуже легко реалізується режим вкладеного виклику процедур. А взагалі кажучи, стекові структури часто використовуються у випадках, коли програма повинна виконати певні команди в заданому порядку.

 

Локальні і глобальні мітки

У MASM за умовчанням мітки коду (тобто ідентифікатори, після яких вказана одна двокрапка) мають локальну область видимості. Це означає, що ними можна користуватися тільки усередині поточної процедури. Тим самим в програмі запобігається випадковий перехід за допомогою команди JMP або LOOPна мітку, розташовану за межами поточної процедури. Проте хоч і не часто, але зустрічаються випадки, коли в програмі потрібно перейти на мітку, розташовану поза поточною процедурою. Для цього потрібно оголосити глобальну мітку, поставивши після ідентифікатора дві двокрапки, як показано нижче:

 

Globallabel::

 

У приведеному нижче фрагменті програми, команда переходу з процедури main на мітку L2 викличе появу повідомлення про помилку, оскільки мітка L2 є локальною для процедури sub2. Перехід же з процедури sub2 на мітку L1 коректний, оскільки L1 визначена як глобальна мітка:

 

main PROC

jmp L2 ; Помилка!

L1:: ; Глобальна мітка

exit main ENDP

sub2 PROC

L2 : ; Локальна мітка

jmp L1 ; Дозволено!

ret sub2

ENDP

 

Передача параметрів процедурам через регістри

При написанні процедури, яка виконує одну із стандартних дій, наприклад, таку як підсумовування елементів цілочисельного масиву, не має сенсу використовувати в ній конкретні імена змінних. Річ у тому, що тоді дана процедура зможе обробляти тільки елементи одного масиву з вказаним ім'ям. Набагато краще при виклику процедури передати їй як параметри адреса масиву і кількість його елементів. Називатимемо ці параметри аргументами або вхідними параметрами. У мові асемблера для передачі параметрів процедурам часто використовуються регістри загального призначення.

Раніше ми створили просту процедуру Sumof, яка обчислює суму трьох чисел, що знаходяться в регістрах ЕАХ, ЕВХ і ЕСХ. Перед викликом цієї процедури з процедури main нам потрібно завантажити відповідні значення в регістри ЕАХ, ЕВХ і ЕСХ:

 

.data

thesum DWORD ?

.code

main PROC

mov eax,10000h ; Перший аргумент

mov ebx,20000h ; Другий аргумент

mov ecx,30000h ; Третій аргумент

call Sumof ; EAX = (ЕЛХ + EBX + ECX)

mov thesum,eax ; Збережемо суму в змінній

 

Після виклику процедури Sumof за допомогою команди CALL в регістрі ЕАХ знаходитиметься шукана сума трьох чисел, яку ми можемо зберегти в змінній для подальшого використання.

 

Приклад: сумування елементів масиву цілих чисел

При програмуванні на мові високого рівня, такому як С++ або Java, часто доводиться в циклі підраховувати суму елементів масиву цілих чисел. Подібне завдання можна дуже легко реалізувати на мові асемблера. Для цього усередині циклу використовуватимемо регістри, а не змінні.

Створимо процедуру під ім'ям Arraysum, якій із викликаючої програми передаватимуться два параметри: покажчик на масив 32-розрядних цілих чисел і кількість елементів в цьому масиві. Суму елементів масиву процедура повертатиме в регістрі ЕАХ:

 

Arraysum PROC

; Обчислює суму елем. масиву 32-розрядних цілих чисел

; Передається: ESI = адреса масиву

; ЕСХ = кількість елементів масиву

; Повертається: ЕАХ = сума елементів масиву

push esi ; Збережемо значення регістрів ESI і ЕСХ

push ecx

mov eax,0 ; Обнулимо значення суми

L1:

add eax,[esi] ; Додамо черговий елемент масиву

add esi,4 ; Обчислимо адресу наступного елементу масиву

loop L1 ; Повторюваний цикл для всіх елементів масиву

pop ecx ; Відновимо значення

; регістрів EST. і ЕСХ

pop esi

ret ; Повернемо суму в регістрі ЕЛХ

Arraysum ENDP

 

В цій процедурі немає посилань на конкретний масив або змінну, що містить число його елементів. В результаті вона може використовуватися для обчислення суми елементів будь-якого масиву 32-розрядний цілих чисел. Ви також повинні намагатися (там де це можливо) створювати універсальні і такі, що легко адаптуються процедури.

Виклик процедури ArraysumУ приведеному нижче прикладі при виклику процедури Arraysum в регістрі ESI їй передається адреса масиву array, а в регістрі ЕСХ - кількість елементів цього масиву. Повертане процедурою значення зберігається в змінній thesum:

 

.data

array DWORD l0000h,20000h,30000h,40000h,50000h

thesum DWORD ?

.code

main PROC

mov esi,offset array ; Завантажимо в ESI адресу масиву

mov ecx,lengthof array ; Завантажимо в ЕСХ число елементів

; масиву

call Arraysum ; Обчислимо суму елементів масиву

mov thesum,eax ; Збережемо суму в змінній

Блок-схеми програм

Для представлення логіки роботи програми в графічному вигляді широко використовуються блок-схеми її алгоритму. На них у вигляді різних символів зображаються логічні кроки програми. Символи сполучені один з одним лініями із стрілками, які позначають порядок виконання кроків програми. На рис. 5.16 показані основні елементи блок-схеми.

 

 

Для вказівки напряму галуження блок-схеми, до елементу, що позначає вибір умови, додаються текстові примітки, такі як Так і ні. Стрілки умови можуть виходити з цього елементу блок-схеми в будь-якому зручному напрямі (тобто не обов'язково так, як показано на рис. 5.16). У кожному елементі блок-схеми, що позначає дію програми, може знаходитися одна або декілька зв'язаних один з одним команд. Причому синтаксис цих команд не обов'язково повинен відповідати синтаксису операторів мови високого рівня або мови асемблера. По суті він може бути довільним, але зрозумілим читачеві. Наприклад, на рис. 5.17 показано, як можна позначити на блок-схемі команду додавання 1 до регістра ЕСХ.

 

 

Зобразимо блок-схему алгоритму попередньо розглянутої процедури Arraysum (рис. 5.18). Для зображення команди LOOP ми скористалися символом вибору умови. Річ у тому, що команда LOOPвиконує перехід по вказаній мітці залежно від значення регістра ЕСХ. Для наочності ми помістили в блок-схему оригінальний лістинг процедури.

Збереження і відновлення регістрів

На початку процедури Arraysum значення регістрів ЕСХ і ESI зберігаються в стеку, а при поверненні з неї - відновлюються із стека. Це один з прикладів хорошого стилю програмування. Подібна практика повинна використовуватися для тих процедур, в яких змінюються значення регістрів. Якщо при написанні процедур ви зберігатимете значення регістрів, що модифікуються в них, то тим самим полегшите подальшу відладку програм. При цьому в викликаючій програмі не потрібно буде відстежувати, які з регістрів "зіпсувала" програма, що викликалася.

 

Оператор USES

Оператор USES, вказаний відразу після директиви PROC, дозволяє перерахувати імена всіх регістрів, значення яких змінюється в процедурі. При його обробці компілятор асемблера виконує дві речі. По-перше, на початку процедури автоматично генерується послідовність команд PUSH, за допомогою яких в стеку зберігаються значення регістрів, вказаних в операторові USES. По-друге, при виході з процедури (точніше, перед кожною командою RET, якщо в процедурі їх декілька) автоматично відновлюються значення цих регістрів. Оператор USES указується відразу за ключовим словом PROC. Список регістрів слідує відразу за ключовим словом USES, при цьому імена регістрів розділяються пропуском або символом табуляції (не комою!).

 

Arraysum PROC USES esi ecx

mov eax, 0 ; Обнулимо значення суми

LI:

add eax [esi] ; Додамо черговий елемент масиву

add esi,4 ; Обчислимо адресу наступного ел. масиву

loop L1 ; Повторюваний цикл для всіх

; елементів масиву

ret ; Повернемо суму в регістрі ЕАХ

Arraysum ENDP

 

В результаті асемблер згенерує приведений нижче код.

 

Arraysum PROC

push esi

push ecx

mov eax,0 ; Обнулимо значення суми

LI:

add eax [esi] ; Додамо черговий елемент масиву

add esi,4 ; Обчислимо адресу наступного

; елементу масиву

loop LI ; Повторюваний цикл для всіх елем. масиву

pop ecx

pop esi

ret

Arraysum ENDP

 

Виключення з правила. Існує одне важливе виключення з сформульованого вище правила про збереження в стеку регістрів, що модифікувалися в процедурі. Воно відноситься до тих регістрів, в яких в процедуру, що викликала, повертаються значення. Очевидно, що в подібних випадках нам немає чого зберігати, а потім відновлювати значення таких регістрів. Наприклад, якби в процедурі Sumof ми б зберегли значення регістра ЕАХ, що модифікувався, то повертане процедурою в цьому регістрі значення суми було б втрачене:

 

Sumof PROC ; Обчислення суми трьох цілих чисел

push еах ; Збережемо регістр ЕАХ

add eax,ebx ; Обчислимо суму трьох чисел

add еах,ecx ; що знаходяться в регістрах ЕАХ,ЕВХ,ECX

pop еах ; Увага! Значення суми загублене!
ret

Sumof Endp

 

Використання процедур при розробці програм

Будь-яка програма, окрім найпростішої, складається з деякої кількості команд, що виконують різні завдання, обумовлені алгоритмом. Можна, звичайно, написати одну велику програму, весь код якої складається з однієї процедури. Проте через деякий час ви зрозумієте, що розібратися в ній, а тим більше відладити код, стає все важчим і важчим. Інтуїція нам підказує, що одну велику програму потрібно розділити на частини, кожна з яких вирішуватиме якусь окрему задачу, і оформити ці фрагменти коди у вигляді окремих процедур. Причому ці процедури можуть знаходитися як в одному, так і в декількох початкових файлах, що містять програмний код.

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

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

  1. Одне складне завдання можна легко розділити на ряд простих підзадач.
  2. Програму набагато легше написати, відладити і супроводжувати, якщо кожну процедуру можна протестувати незалежно від інших.
  3. Використання низхідного підходу до проектування дозволяє побачити існуючі взаємозв'язки між процедурами.
  4. Коли створена загальна структура проекту, ви легко можете зосередитися на вирішенні конкретних завдань, а також написанні коду, що реалізовує кожну процедуру.

 

2.     Макроси, спосіб оголошення та виклику

Макроси - підпрограми генерації. Ділянки тексту програми, що повторюються можна оформити як макроозначення (опис макросу). Якщо в тексті програми зустрічається виклик макросу (макрокоманда), відбувається генерація всіх його операторів. Відмінність макросу від звичайної підпрограми полягає в тому, що, якщо в початковому тексті буде декілька викликів макросу в об'єктному модулі, його тіло буде повторено стільки ж раз. Тіло ж звичайної підпрограми записується в об'єктний модуль тільки один раз незалежно від кількості викликів. З іншого боку, тіло макросу може бути різним залежно від тих аргументів, з якими він викликаний, звичайна підпрограма таку властивість забезпечити не може.

Макрос повинен бути описаний до виклику. Опис макросу починається рядком з ім'ям макроозначення і директивою MACRO із списком формальних аргументів. Закінчується макроозначення директивою ENDM.

Як фактичні аргументи можуть виступати будь-які конструкції асемблера, допустимі для даної команди. За наявності в тілі макроозначення мітки, вони повинні бути оголошені локальними за допомогою директиви LOCAL.

Опис макросу має вигляд:

 

Ім'я MACRO Список_формальних_аргументів

Директиви і команди

ENDM

 

Тексти макроозначень можна включати безпосередньо в текст програми або поміщати в макробібліотеку. Макроозначення записуються у файл макробібліотеки в такому ж вигляді, як і в текст програми. Вміст макробібліотеки можна включити в програму за допомогою директиви INCLUDE, що має наступний формат: INCLUDE імя_файла.

 

 

 

Приклад:

 

;==== mymacro.inc - макробібліотека. ===============================

;==== Wrmem - запис в кожен 8-розрядний елемент таблиці Table =====

;==== розміром Msize < 257 її власної адреси. =================

;==== Формальні аргументи - відносна адреса і розмір таблиці. =

 

Wrmem MACRO Table, Msize

LOCAL Nextbyte

pushf

push ex di es

eld

mov ex, Msize

les di, Table

xor ах, ах

Nextbyte:

stosb

inc al

loop Nextbyte

pop es di ex

popf

ENDM

 

;==== Delay - програмна затримка. ================================

.==== Формальний аргумент - величина затримки. =====================

 

Delay MACRO Time

LOCAL Ext, Inn

push ex

mov ex, Time

Ext:

push ex

xor ex, ex

Inn:

loop Inn

pop ex

loop Ext

pop ex

ENDM

 

;==== myprog.asm - основна програма. =============================

 

.MODEL small

INCLUDE mymacro.inc

.DATA

Buf DB 100 dup (?)

Bsize = $ - Buf

.CODE

Begin:

Wrmem Buf, Bsize

Delay 200h

Delay WORD PTR [bx]

END Begin

Лекція 14 Функції DOS та BIOS

 

Текстовий режим роботи дисплея

Більшість програм вимагають виведення даних у зручному форматі на екран. Всі необхідні екранні операції можна виконати використовуючи команду INT 10H, яка передає управління безпосередньо в BIOS і потім повертає управління в перервану програму для продовження роботи. Функція, яку слід виконати, і інші параметри необхідні для її виконання, передається в BIOS через регістри.

Таким чином, перед викликом переривання необхідно у відповідні регістри завантажити необхідні дані. Наприклад, щоб вивести на екран символ,

необхідно вказати номер функції, яка виводить символ, номер відео сторінки, на яку буде виведено символ, і ASCII код цього символу. Кожна команда вимагає свої параметри, всі вони будуть описані нижче.

 

Встановлення відеорежиму

Встановлення відеорежиму для виконуваної в поточний момент програми здійснюється c допомогою функції 00h програмного переривання BIOS INT 10H. Ця функція дозволяє перемикати кольоровий монітор в текстовий або графічний режим. Вміст регістра AL у момент виклику переривання визначає відеорежим, який буде встановлений після виконання операції, і може приймати такі значення:

 

00h - 40 x 25 чорно-білий текстовий режим;

01h - 40 x 25 стандартний 16-колірний текстовий режим;

02h - 80 x 25 чорно-білий текстовий режим;

03h - 80 x 25 стандартний 16- колірний текстовий режим;

04h - 320 x 200 стандартний 4- колірний графічний режим;

05h - 320 x 200 чорно-білий графічний режим;

06h - 640 x 200 чорно-білий графічний режим;

07h - 80 x 25 чорно-білий стандартний монохромний;

0Dh - 320 x 200 16-колірний графічний режим (EGA);

0Eh - 640 x 200 16-колірний графічний режим (EGA);

0Fh - 640 x 350 чорно-білий графічний режим (EGA);

10h - 640 x 350 64- колірний графічний режим (EGA).

 

Приклад 1. Встановити графічний режим 320 x 200, 4 кольори.

 

mov ah, 00h ; Вказуємо номер функції

; Вибору графічного Режиму

mov al, 04h ; Номер режиму 320x200, 4 кольори

int 10h ; Виклик переривання

 

Встановлення курсору

Екран можна представити у вигляді двовимірного простору з адресованими позиціями, в будь-яку з яких може бути встановлений курсор. Звичайний відеомонітор, наприклад, має 25 рядків (Нумерованих від 0 до 24) і 80 стовпчиків (нумерованих від 0 до 79).

 

Приклад 2. Встановити курсор на 5-й рядок і 12-й стовпець.

 

mov ah, 02h ; Функція встановлення курсору

mov bh, 00h ; Відео сторінка 0

mov dh, 05h ; Номер рядка 5

mov dl, 0Ch ; Номер стовпця 12

int 10h ; Виклик переривання

 

Значення 02h в регістрі AH, вказує на виконання функції встановлення курсору. Значення рядка та стовпця повинні бути, відповідно, в регістрах DH і DL. Номер відео сторінки у регістрі BH (звичайно 0, для графічного режиму). Для встановлення рядка та стовпця можна також використовувати одну команду MOV, з безпосереднім багатобайтовим значенням:

 

mov dx, 050Ch

 

Для того щоб зробити курсор невидимим, необхідно встановити його на 25-й рядок.

 

Переміщення (прокрутка) вікна вгору / вниз

Для переміщення (прокрутки) вікна вгору і вниз використовуються функції з номерами, відповідно, 06h і 07h. Також ці функції можна використовувати для очищення всього екрану або певної його області. У регістрі AH вказується номер функції (06h або 07h, в залежності від того куди переміщувати), в AL - кількість рядків, які буде прокручено (якщо 0, то вікно буде очищено). У CH, CL - рядок, колонка верхнього лівого кута вікна (вважаючи від 0). У DH, DL - рядок, колонка нижнього правого кута вікна. У BH відео атрибут, використовуваний для порожніх рядків (07h - нормальний атрибут чорно-білий).

 

Приклад 3. Виконати очищення всього екрану

mov ah, 06h ; Функція прокручування вікна

mov al, 00h ; Очищення

mov bh, 07h ; Чорно-білий

mov cx, 0000h ; Завантаження лівої верхньої

; Позиції екрану

mov dx, 184Fh ; Права нижня позиція

int 10h ; Виклик переривання

 

У цьому прикладі використано вказаний вище спосіб завантаження двобайтового значення.

 

Вивід символу

Для виведення одного символу на екран можуть використовуватися дві функції: 0Ah і 0Eh. Обидві операції виводять вказаний символ від позиції курсору. Відмінність даних функцій в тому, що перша при цьому не змінює положення курсору, а друга переміщує його слідом за виводом символів. Якщо виводити 5 символів функцією 0Ah, вони будуть відображатися на одному і тому ж місці, затираючи собою попередній, а якщо використовувати функцію 0Eh то при кожному виведенні, курсор буде переміщатися, і в результаті символи відобразяться один за одним, зліва направо.

При використанні функції 0Ah, перед викликом переривання, необхідно в регістрі AH вказати номер функції (0Ah), в AL ASCII код символу, в BH - номер відео сторінки, в CX - кількість разів.

Вивід на екран послідовності різних символів вимагає організації циклу. При використанні функції 0Eh, вміст регістрів AL і BH такий, як і в разі використання функції 0Ah. У регістр AH завантажуємо 0Eh, а в BL - колір символу в графічному режимі.

 

Приклад 4. Вивести довільний символ в центр екрану

 

; Перед висновком необхідно встановити

; курсор у центр екрану по прикладу 2

mov ah, 0Ah ; Номер функції

mov al, 03h ; У AL код символу Черви

mov bh, 00h ; відеосторінок 0

mov cx, 01h ; Один раз

int 10h ; Виклик переривання

 

Вивід символу з атрибутом

Функція 09h. Ця функція аналогічна до функції 0Ah, описаної вище, з тією відмінністю, що при використанні цієї функції існує можливість вказати атрибут символів, що виводяться.

Атрибут вказується в регістрі BL. Вміст інших регістрів аналогічно операції 0Ah.

 

Приклад 5. Вивести на екран 5 символів сердечко з миготінням та у інвертованому вигляді.

mov ah, 09h; Вказуємо номер функції

mov al, 03h; У AL код символу Черви

mov bh, 00h; Номер сторінки

mov bl, 0F0h; Атрибут (миготіння, інверсія)

mov cx, 05h; 5 разів

int 10h; Виклик переривання

 

Вивід символьного рядка

Операція 13h переривання 10h BIOS дозволяє виводити на екран символьні рядки з встановленням атрибутів і переміщенням курсора. Перед його викликом, в AH записується номер операції (13h). У AL записується номер підфункції, який може приймати значення від 0 до 3 включно, опис наводиться нижче. BH номер відеосторінки. BL - атрибут символів (для підфункцій 0 і 1). CX - довжина рядка, що виводиться. DH і DL - координати початку рядка, відповідно X і Y. ES: BP - вказівник на рядок.

Опис підфункцій:

0 - використовувати атрибут і не переміщати курсор;

1 - використовувати атрибут і перемістити курсор;

2 - вивести символ, потім атрибут і не переміщати курсор;

3 - вивести символ, потім атрибут і перемістити курсор.

Приклад 6. Вивести в лівий верхній кут екрану довільний рядок не переміщаючи курсор.

 

str db 'Привіт!' ; Місце в пам'яті для рядка

...

; Тут, якщо це необхідно, виконуємо

; очищення екрану (за прикладом 3)

mov ah, 13h ; Номер функції

mov al, 00h ; У AL номер підфункції - без

; Переміщення курсору

mov bh, 00h ; Номер сторінки

lea bp, str ; Вказівник на рядок в ES: BP

mov cx, 07h ; Довжина рядка

mov dx, 0000h ; Координати лівого верхнього кута

int 10h ; Виклик переривання

 

Читання символу з атрибутом

Операція 08h переривання 10h повертає атрибут і ASCII код символу (у текстовому режимі), на якому встановлений курсор на зазначеній відео сторінці. Перед викликом переривання необхідно в AH вказати номер операції, в BH - номер відеосторінки. Після повернення з переривання AH буде містити атрибут символу, а AL - ASCII код символу.

 

Приклад 7. Визначити ASCII код символу, розташованого в центрі екрану.

 

; Спочатку, за прикладом 2,

; необхідно встановити курсор у центр екрану.

 

mov ah, 08h ; Вказуємо номер функції

mov bh, 00h ; Сторінка 0

int 10h ; Виклик переривання

; Після повернення з переривання в AL

; буде міститися ASCII код символу.

 

Для відображення символу в нормальному вигляді, яким він представлений на клавіатурі, необхідно знати його ASCII код.

Таблиця ASCII - це набір умовно закодованих текстових і керуючих символів. Молодші 128 елементів набору ASCII формально визначені. Кодові значення вище 127 (7fH) інтерпретуються різним чином на різних комп'ютерах,

принтерах і т.п.

Лекція 15 Програмування доступу до клавіатури. Робота з відеосистемою.

 

Ввід інформації з клавіатури

Ввід інформації з клавіатури - один з основних способів взаємодії з комп'ютером IBM PC. DOS забезпечує ряд функцій, за допомогою яких програма на асемблері може обробляти натиснення клавіш.

Можливо одним з найбільш простих способів отримання символів клавіш є функція "Вводу з клавіатури", тобто функція DOS 01h. Функції DOS викликаються шляхом поміщення номера функції в регістр AH і виконання команди INT 21h. Наступний набраний на клавіатурі символ повертається в регістрі AL.

Наприклад, коли виконується код:

 

mov ah,1

int 21h

 

операційна система DOS поміщає наступний набраний на клавіатурі символ в регістр AL. Відмітимо, що якщо клавіша не натиснута, операційна система DOS чекатиме, коли вона буде натиснута. Тому для виконання даної функції може бути потрібно невизначений час.

 

Вивід символів на екран

Якщо натиснення клавіш означають взаємодію користувача з програмним забезпеченням, то екран є доповненням.

Функція DOS 02h забезпечує прямий спосіб виводу символу на екран. Для цього потрібно просто помістити значення функції 02h в регістр AH і символ, що виводиться, в регістр DL, а потім викликати переривання INT 21h. Наступний код відображає кожен введений символ на екрані:

 

mov ah,1

int 21h ; отримати код наступної нажатої клавіші

mov ah,2

mov dl,al ; перемістити символ для виводу з AL в DL

int 21h ; вивести його на екран

 

Є також ряд інших функцій для зчитування і виводу символів і рядків символів. При цьому, варто зауважити, що DOS виконує тільки посимвольний або порядковий ввід/вивід.

Для завершення програми є декілька функцій DOS, але найбільш відомим методом є виконання функції DOS з номером 4Ch (або 76 для тих, хто віддає перевагу десятковому вигляду).

Знаючи це, можна тепер написати повну програму відображення символів:

 

DOSSEG

.MODEL SMALL

.STACK 100h

.DATA

.CODE

EhcoLoop:

mov ah,1 ; функція DOS введення з клавіатури

int 21h ; отримати наступну клавішу

cmp al,13 ; це клавіша ENTER?

jz EchoDone ; так, виконуємо процедуру виходу

mov dl,al ; помістити символ в DL

mov ah,2 ; функція DOS висновку на екран

int 21h ; вивести на екран символ

jnz EchoLoop ; відобразити наступний символ

EchoDone:

mov ah,4ch ; функція DOS завершення програми

int 21h ; завершити програму

END

 

В результаті виконання програми, кожен символ, що вводиться користувачем, виведеться двічі: один раз, коли він відображається DOS при введенні з клавіатури, і другий раз, коли він відображається програмою. Важливим моментом тут є те, що всі дії: читання символів клавіш, вивід символів на екран і завершення програми виконуються за допомогою функцій DOS.

Лекція 16 Програмування зовнішніх пристроїв

 

Асемблер допускає програмування на високому, середньому і низьких рівнях. В першому випадку суть програмування полягає в підготовці даних, необхідних для роботи системних функцій DOS, з подальшим викликом цих функцій. Особливістю такого методу програмування є простота написання інаглядність початкового тексту. Для підвищення швидкості роботи програми в операціях вводу/виводу можна використовувати пряме звернення до функцій BIOS (середній рівень). Низький рівень програмування має на увазі пряме звернення до портів вводу/виводу і прямий доступ до пам'яті. Такий стиль програмування вимагає хорошого знання принципів роботи пристроїв персонального комп'ютера і виправданий при високих вимогах до швидкодії програми, або при роботі з нестандартними пристроями вводу/виводу.

Звернення до функцій DOS і BIOS здійснюється командами INT за єдиним правилом. Перед командою виклику системної функції в регістр АН завантажується номер функції, в інші (строго визначені для даної функції) регістри завантажуються необхідні параметри. Потім виконується одна з команд:

INT 21h - виклик диспетчера DOS;

INT 10h - виклик драйвера екрану BIOS;

INT 13h - виклик драйвера жорсткого диска BIOS;

INT 16h - виклик драйвера клавіатури BIOS.

 

Код завершення після роботи системної функції зазвичай повертається в прапорі CF: CF = 0 - функція виконалася успішно, CF = 1 - відбулася помилка. У останньому випадку повертається ще і код помилки (зазвичай в регістрі АХ).

 

Робота з файлами

Файл на диску розглядається як послідовність байтів, пронумерованих, починаючи з нуля. При цьому можливий як послідовний, так і прямий доступ до кожного блоку байту. Номер байта у файлі, до якого відбувається звернення, визначається вмістом файлового покажчика.

Специфікація файлу - рядок символів, що містить ім'я диска, шлях до файлу і ім'я файлу. Ознакою кінця рядка є нульовий байт.

Відкриваючи файл, DOS формує унікальний 16-розрядний код, використовуваний надалі для посилань на даний файл. Цей код називають номером або дескриптором файлу. Дескриптор - адреса системної області, де зберігається інформація про відкритий файл.

Файлові функції, що використовують дескриптори, можна використовувати для вводу/виводу через деякі стандартні ПВВ комп'ютера. При цьому останнім відповідають зумовлені дескриптори, зокрема :

0 - стандартний ввід;

1 - стандартний вивід;

2 - стандартна помилка (виведення діагностичних повідомлень);

4 - стандартний принтер.

Таким чином, використовуючи файлові функції DOS, введення з клавіатури можна здійснювати через дескриптор 0, вивід на екран - через дескриптори 1 і 2, вивід на принтер - через дескриптор 4. Стандартне введення і вивід засобами DOS можна перенаправити на будь-який пристрій або у файл.

 

Деякі функції DOS, призначені для роботи з файлами:

 

3CH - Створення файлу

Створює файл. Якщо файл із заданим ім'ям вже існує, то він усікається до нульової довжини і розглядається як знов створений.

 

При виклику:

AH = 3Ch;

СХ - атрибути файлу (можуть комбінуватися):

0 - без атрибутів

1 - тільки для читання

2 - прихований

3 - системний,

8 - мітка тому,

32- атрибуту архіву;

DS: DX - адреса специфікації файлу, записана у форматі ASCIIZ.

При поверненні:

АХ - дескриптор.

 

3DH - Відкриття файлу

Відкриває файл із заданим ім'ям. Повертає дескриптор для подальших операцій над файлом. Встановлює байтовий покажчик на початок файлу.

 

При виклику:

AH = 3Dh;

AL - режим доступу:

0 - читання

1 - запис

2 - читання і запис;

DS: DX - адреса специфікації файлу, записана у форматі ASCIIZ.

Коли до коду режиму додане 80h, дескриптор успадковується дочірним процесом.

При поверненні:

АХ - дескриптор.

 

3EH - Закриття файлу

Закриває файл і звільняє дескриптор.

 

При виклику:

AH = 3Eh;

BX - дескриптор.

 

3FH - Читання з файлу або пристрою

Читає дані (починаючи з байта, на який встановлений покажчик) з файлу або пристрою в буфер користувача і модифікує покажчик.

 

При виклику:
AH = 3Fh;

ВХ - дескриптор;

СХ - запрошуване число байтів, що пересилаються;

DS: DX - адреса буфера користувача.

При поверненні: АХ - реальне число прочитаних байтів, яке може опинитися меншезаданого в СХ при виклику унаслідок досягнення кінця файлу.

 

40H - Запис у файл або пристроїв

Записує групу підряд розташованих байтів з буфера користувача у файл починаючи з позиції, на яку встановлений покажчик. В процесі запису модифікує покажчик. Якщо при виклику СХ = 0, довжина файлу встановлюється відповідно до текучого положення покажчика.

 

При виклику:

AH = 40h;

ВХ -дескриптор;

СХ - запрошуване число байтів, що пересилаються;

DS: DX - адреса буфера користувача.

При поверненні:

AX - реальне число переданих байтів, яке може опинитися менше, задан* в СХ при виклику, якщо диск заповнений.

 

41H - Видалення файлу

 

При виклику:

AH = 41h;

DS: DX - адреса специфікації файлу, записана у форматі ASCIIZ.

 

42H - Встановлення файлового вказівника

Встановлює покажчик на будь-який байт файлу для виконання подальших операцій читання або запису. Використовується при реалізації прямого доступу до файлу, так як при послідовному доступі до файлу операція переміщення покажчика не потрібна. Може використовуватися для визначення довжини файлу.

 

При виклику:

AH = 42h;

AL - режим установки покажчика:

0 - абсолютний зсув від початку файлу

1 - знаковий зсув від поточного положення покажчика

2 - знаковий зсув від кінця файлу;

ВХ - дескриптор;

СХ - старші розряди зсуву;

DX - молодші розряди зсуву.

При поверненні: СХ - старші розряди поверненого значення покажчика; DX - молодші розряди поверненого значення покажчика.

 

4EH - Знаходження першого файлу

Шукає перший файл, відповідний заданому шаблону.

 

При виклику:

АН = 4eh;

СХ - атрибути файлу (можуть комбінуватися):

0 - без атрибутів

1 - тільки для читання

2 - прихований

3 - системний,

8 - мітка тому,

16 - каталог

32 - атрибут архіву;

DS : DX - адреса специфікації файлу, записаною у форматі ASCIIZ.

При поверненні:

Ім'я і розширення поміщаються в байти DTA (Disk Transfer Area) із зсувомlEh2Ah.

 

Примітка:

Область обміну з диском DTA знаходиться в PSP по зсуву 80h, ця службова структура DOS використовується при роботі з файлами.

 

 

4FH - Знаходження наступного файлу

Шукає наступний файл, після того, як функція 4Eh знайшла перший файл, відповідний заданому шаблону. Якщо потрібні всі такі файли, функція 4Fh виконується до отримання при поверненні CF=1.

 

При виклику:

АН =4Fh.

При поверненні:

Ім'я і розширення поміщаються в байти DTA із зсувом 1Eh...2Ah.

 

56H - Перейменування файлу

Перейменовує файл або переміщає його в інший каталог.

 

При виклику:

АН = 56h;

DS: DX - адреса поточної специфікації, записаної у форматі ASCIIZ;

ЕS: DI - адреса нової специфікації, записаної у форматі ASCIIZ .

Коди найбільш поширених помилок:

1 - неправильний номер функції або підфункції;

2 - файл не знайдений;

3 - шлях до файлу не знайдений;

4 - багато відкритих файлів;

5 - немає доступу до файлу (неприпустима операція, каталог повний, помилка устаткування і ін.);

6 - неправильний дескриптор;

12 - неправильний код доступу;

17 - невідповідний пристрій;

18 - більше немає файлів;

80 - файл вже існує.

 


Математические формулы. Шпаргалка для ЕГЭ с математики

Формулы сокращенного умножения

(а+b)2 = a2 + 2ab + b2

(а-b)2 = a2 – 2ab + b2

a2 – b2 = (a-b)(a+b)

a3 – b3 = (a-b)( a2 + ab + b2)

a3 + b3 = (a+b)( a2 – ab + b2)

(a + b)3 = a3 + 3a2b+ 3ab2+ b3

(a – b)3 = a3 – 3a2b+ 3ab2- b3

Свойства степеней

a0 = 1 (a≠0)

am/n = (a≥0, n ε N, m ε N)

a- r = 1/ a r (a>0, r ε Q)

m...

законы диалектики

Основные законы диалектики.

1)Закон единства и борьбы противоположностей.

Этот закон является «ядром» диалектики, т.к. определяет источник развития, отвечает на вопрос, почему оно происходит.

Содержание закона: источник движения и развития мира находится в нем самом, в порождаемых им противоречиях.

Противоречие – это взаимодействие противоположных сторон, свойств и тенденций в составе той или иной системы или между системами. Диалектическое противоречие есть только там, где...

Политология. Универсальная шпаргалка

перейти к оглавлению

1. Место политологии среди гуманитарных наук

Политология развивается в тесном взаимодействии с другими гуманитарными науками. Их всех объединяет общий объект исследования — жизнь общества во всем многообразии ее конкретных проявлений.

Сегодня невозможно изучать сложные политические процессы, не учитывая взаимодействие общественных (гуманитарных) наук.

1) Политология тесно связана с экономикой. Экономика дает соответствующее обоснование реализации экономических...

Идеология

1.Идеология как социальный феномен, её сущность. Содержание идеологииСоциально-исторической системой представлений о мире стала идеология как система рационально- логического обоснования поведения людей, их ценностей, норм взаимоотношений, целей и т.д. Идеология как явление во многом сходна с религией и с наукой. От науки она восприняла доказательность и логичность своих постулатов, но, в отличие от науки, идеология призвана давать оценку явлениям действительности (что хорошо, что...