Все про Assembler українською мовою на сайті net.kr.ua

 

:: Меню ::

Головна
Введення
Архітектура реального режиму
Основи програмування
Команди і алгоритми
Розширені можливості сучасних мікропроцесорів
Система команд процесорів Intel
Книга для гостей
Контакти
Добавити у вибране

:: Друзі ::

 
 

:: Лічильники ::

=

 

 

 

 

fff00e50

Сегментна структура програм

Як було показано вище, звернення до пам'яті здійснюється виключно за допомогою сегментів - логічних утворень, що накладаються на будь-які ділянки фізичного адресного простору. Початкова адреса сегменту, що ділиться на 16, тобто без молодшої шістнадцятиричної цифри, заноситься в один з сегментних регістрів; після цього ми дістаємо доступ до ділянки пам'яті, що починається із заданої сегментної адреси.
Яким чином поняття сегментів пам'яті відбивається на структурі програми? Слід відмітити, що структура програми визначається, з одного боку, архітектурою процесора (якщо звернення до пам'яті можливо тільки за допомогою сегментів, то і програма, мабуть, повинна складатися з сегментів), а з іншої - особливостями тієї операційної системи, під управлінням якої ця програма виконуватиметься. Нарешті, на структуру програми впливають також і правила роботи вибраного транслятора - різні транслятори пред'являють декілька вимоги, що розрізняються, до початкового тексту програми. При підготовці цієї книги для трансляції і відладки прикладів програм використовувався пакет TASM 5.0 корпорацій Borland International; він зручний, зокрема, наявністю наочного багатовіконного відладчика. Питання цей, проте, не принциповий, і читач може для відладки прикладів, приведених в книзі, скористатися будь-яким асемблером, ознайомившись заздалегідь з його описом.
У справжньому розділі ми на простому прикладі розглянемо особливості сегментної адресації і роль регістрів процесора у виконанні прикладної програми. Проте для того, щоб програма була працездатна, нам доведеться включити в неї ряд елементів, що не мають прямого відношення до даних питань, але необхідних для її правильного функціонування. До таких елементів, зокрема, відноситься виклик функцій DOS. Привівши повний текст програми, ми дамо короткі пояснення.

Приклад 1-1. Проста програма з трьома сегментами


;Вкажемо відповідність сегментних регістрів сегментам


assume Cs:code,ds:data


;Опишемо сегмент команд


code segment ;Откроем сегмент команд


begin: mov Ax,data ;Настроим DS


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


Виведемо на екран рядок тексту


mov Ан,09h ;Функция DOS виводу на екран


mov Dx,offset msg ;Адрес рядка, що виводиться


int 21h ;Вызов DOS


;Завершимо програму


mov Ax,4c00h ;Функция DOS завершення програми


int 21h ;Вызов DOS


code ends ;Закроем сегмент команд


;Опишемо сегмент даних


data segment ;Откроем сегмент даних


msg db "Програма працює!$' ;Выводимая рядок


data ends ;Закроем сегмент даних


;Опишемо сегмент стека


stk segment stack ;Откроем сегмент стека


db 256 dup (?) ;Отводим під стек 256 байт


stk ends ;Закроем сегмент стека


end begin ;Конец тексту з точкою входу

Слід відмітити, що при введенні початкового тексту програми з клавіатури можна використовувати як прописні, так і рядкові букви; транслятор сприймає, наприклад, рядки MOV Ax,data і mov ах.data однаково. Проте за допомогою відповідних ключів можна змусити транслятор розрізняти прописні і рядкові букви в окремих елементах пропозицій. У справжній книзі в текстах програм і при описі операторів мови в основному використовуються рядкові букви, за винятком позначень регістрів, які для наочності виділені прописними буквами.
Пропозиції мови асемблера можуть містити коментарі, які відділяються від пропозиції мови знаком крапки з комою (;). При необхідності коментар може займати цілу рядок (теж, природно, що починається із знаку ";"). Оскільки в мові асемблера немає знаку завершення коментаря, коментар не можна вставляти всередину пропозиції мови, як це допустимо робити в багатьох мовах високого рівня. Кожна пропозиція мови асемблера, навіть найкоротше, повинна займати окремий рядок тексту.
У програмі 1-1 описано три сегменти: сегмент команд з ім'ям code, сегмент даних з ім'ям data і сегмент стека з ім'ям stk. Опис кожного сегменту починається з ключового слова segment, що передує деяким ім'ям, і закінчується ключовим словом end, перед яким указується те ж ім'я, щоб транслятор знав, який саме сегмент ми хочемо закінчити. Імена сегментів вибираються цілком довільно. Текст програми закінчується директивою асемблера end, що завершує трансляцію. У якості операнда цієї директиви указується точка входу в програму; у нашому випадку це влучна begin.
Порядок опису сегментів в програмі, як правило, не має значення. Часто програму починають з сегменту даних, це декілька полегшує читання програми, і в деяких випадках усуває можливі неоднозначності в інтерпретації команд, що посилаються на дані, які ще не описані. Ми на початку програми розташували сегмент команд, за ним - сегмент даних і в кінці - сегмент стека; такий порядок надає деякі зручності при відладці програми. Важливо тільки розуміти, що в оперативну пам'ять комп'ютера сегменти потраплять в тому ж порядку, в якому вони описані в програмі (якщо спеціальними засобами асемблера не задати інший порядок завантаження сегментів в пам'ять).
Сегменти вводяться в програму за допомогою директив асемблера segment і ends. Що таке директива асемблера? У тексті програми зустрічаються ключові слова двох типів: команди процесора (mov, int) і директиви транслятора (в даному випадку терміни "транслятор" і "асемблер" є синонімами, позначаючи програму, що перетворює початковий текст, написаний на мові асемблера, в коди, які при виконанні програми сприйматимуться процесором). До директив асемблера відносяться позначення почала і кінця сегментів segment і ends; ключові слова, що описують тип використовуваних даних (db, dup); спеціальні описувачі сегментів ніби stack і так далі Директиви служать для передачі транслятору службової інформації, якою він користується в процесі трансляції програми. Проте до складу здійснимої програми, що складається з машинних код, ці рядки не потраплять, оскільки процесору, що виконує програму, вони не потрібні. Іншими словами, оператори типу segment і ends не транслюються в машинні коди, а використовуються лише самим асемблером на етапі трансляції програми. З цим питанням ми ще зіткнемося при розгляді лістингів програм.
Ще одна директива асемблера використовується в першій пропозиції програми:
assume Cs:code,ds:data
Тут встановлюється відповідність сегменту code сегментному регістру CS і сегменту data сегментному регістру DS. Перше оголошення говорить про те, що сегмент code є сегментом команд, і що зустрічаються в цьому сегменті мітки належать саме цьому сегменту, що допомагає асемблеру правильно транслювати команди переходів. У нашій програмі влучний немає, і цю частину пропозиції можна було б опустити, проте в складніших програмах вона необхідна (при використанні транслятора MASM ця частина оголошення необхідна в будь-якій, навіть найпростішій програмі).
Друге оголошення допомагає транслятору правильно обробляти пропозиції, в яких проводиться звернення до полів даних сегменту data. Вище вже наголошувалося, що для звернення до пам'яті процесору необхідно мати дві адреси, що становлять: сегментна адреса і зсув. Сегментна адреса завжди знаходиться в сегментному регістрі. Проте в процесорі два сегментні регістри даних, DS і ES, і для звернення до пам'яті можна використовувати будь-який з них. Зрозуміло, процесор при виконанні команди повинен знати, з якого саме регістра він повинен витягувати сегментну адресу, тому команди звернення до пам'яті через регістри DS або ES кодуються по-різному. Оголошуючи відповідність сегменту data регістру DS, ми пропонуємо транслятору використовувати варіант кодування через регістр DS.
Проте звідси зовсім не витікає, що до моменту виконання команди із зверненням до пам'яті в регістрі DS міститиметься сегментна адреса необхідного сегменту. Більш того, можна гарантувати, що потрібної адреси в сегментному регістрі не буде. Директива assume впливає тільки на кодування команд, але зовсім не на вміст сегментних регістрів. Тому практично будь-яка програма повинна починатися з пропозицій, в яких в сегментний регістр, використовуваний для адресації до сегменту даних (як правило, це регістр DS) заноситься сегментна адреса цього сегменту. Так зроблено і в нашому прикладі за допомогою двох команд

mov Ax,data ;Настроим DS


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

з яких починається наша програма. Спочатку значення імені data (тобто адреса сегменту data) завантажується командою mov в регістр загального призначення процесора АХ, а потім з регістра АХ переноситься в регістр DS. Така двоступінчата операція потрібна тому, що процесор через деякі особливості своєї архітектури не може виконати команду безпосереднього завантаження адреси в сегментний регістр. Доводиться користуватися регістром АХ як "перевалочний пункт".
Помістивши в регістр DS сегментну адресу сегменту даних, ми дістали можливість звертатися до полів цього сегменту. Оскільки в програмі може бути декілька сегментів даних, операційна система не може самостійно визначити необхідне значення DS, і ініціалізувати його доводиться "уручну".
Призначенням програми 1-1 передбачається вивід на екран текстового рядка "Програма працює!", описаною в сегменті даних. Наступні пропозиції програми якраз і виконують цю операцію. Робиться це не безпосередньо, а шляхом звернення до службових програм операційної системи MS-DOS, яку ми скорочено надалі називатимемо просто DOS. Річ у тому, що у складі команд процесора і, відповідно, операторів мови асемблера немає команд виведення даних на екран (як і команд введення з клавіатури, записи у файл на диску і так далі). Вивід навіть одного символу на екран насправді є досить складною операцією, для виконання якої потрібна довга послідовність команд процесора. Звичайно, цю послідовність команд можна було б включити в нашу програму, проте набагато простіше звернутися за допомогою до операційної системи. До складу DOS входить велика кількість програм, що здійснюють стандартні і часто потрібні функції, - вивід на екран і введення з клавіатури, запис у файл і читання з файлу, читання або установка поточного часу, виділення або звільнення пам'яті і багато інших.
Для того, щоб звернутися до DOS, треба завантажити в регістр загального призначення АН номер необхідної функції, в інші регістри - початкові дані для виконання цієї функції, після чого виконати команду hit 21h (int - від interrupt, переривання), яка передасть управління DOS. Вивід на екран рядка тексту можна здійснити функцією 09h, яка вимагає, щоб в регістрах Ds:dx містилася повна адреса рядка, що виводився. Регістр DS ми вже ініціалізували, залишилося помістити в регістр DX відносну адресу рядка, яка асоціюється з ім'ям поля даних msg. Довжину рядка, що виводиться, указувати немає необхідності, оскільки функція 09h DOS виводить на екран рядок від вказаної адреси до символу долара, який ми передбачливо включили в рядок, що виводився. Заповнивши всі потрібні для конкретної функції регістри, можна виконати команду int 21h, яка здійснить виклик DOS.
Як завершити виконувану програму? Насправді завершення програми - це досить складна послідовність операцій, в яку входить, зокрема, звільнення пам'яті, зайнятою програмою, що завершилася, а також виклик тієї системної програми (конкретно - командного процесора COMMAND.COM), яка виведе на екран запит DOS, і чекатиме введення наступних команд оператора. Всі ці дії виконує функція DOS з номером 4ch. Ця функція припускає, що в регістрі AL знаходиться код завершення нашої програми, який вона передасть DOS. Якщо програма завершилася успішно, код завершення має дорівнювати 0, тому ми в одній пропозиції mov Ax,4c00h завантажуємо в АН 4ch, а в AL - 0, і викликаємо D'OS вже знайомій нам командою int 21h.
Для того, щоб виконати пробний прогін приведеної програми, її необхідно спочатку відтранслювати і ськомпоновать. Хай початковий текст програми зберігається у файлі з ім'ям P.ASM. Трансляція здійснюється викликом асемблера TASM.EXE за допомогою наступної .команди DOS;

tasm /z/zi/n p/p,p

Ключ /z вирішує вивід на екран рядків початкового тексту програми, в яких асемблер виявив помилки (без цього ключа пошук помилок довелося б проводити по лістингу трансляції).
Ключ /zi управляє включенням в об'єктний файл інформації, не потрібної при виконанні програми, але використовуваною відладчиком.
Ключ /n пригнічує вивід в лістинг переліку символічних позначень в програмі, від чого декілька зменшується інформативність
лістингу, але скорочується його розмір.
Параметри, що стоять далі, визначають імена файлів: початкового (P.ASM), об'єктного (P.OBJ) і лістингу (P.LST). За бажання можна в рядку виклику транслятора вказати повні імена файлів з їх розширеннями, проте необхідності в цьому немає, оскільки за умовчанням транслятор використовує саме вказані вище розширення.
Рядок виклику компонувальника має наступний вигляд:

tlink /x/v p,p

Ключ /х пригнічує утворення лістингу компоновки, який зазвичай не потрібний.
Ключ /v передає в завантажувальний файл інформацію, використовувану відладчиком. Параметри, що стоять далі, позначають імена модулів: об'єктного (Р.Ои) і завантажувального (Р.Ехе).
Оскільки при вивченні цієї книги вам доведеться написати і відладити велику кількість програм, доцільно створити командний файл (з ім'ям, наприклад, А.Ват), що автоматизує виконання однотипних операцій трансляції і компоновки. Текст командного файлу в простому варіанті може бути таким (у припущенні, що шлях до каталога з пакетом TASM був вказаний в параметрі команди PATH):

tasm /z/zi/n p,p,p


tlink /х/v р,р

Запуск підготовленої програми Р.Ехе здійснюється командою .р.ехе або просто
При завантаженні програми сегменти розміщуються в пам'яті, як показано на мал. 1.9.

Мал. 1.9. Образ програми в пам'яті.

Образ програми в пам'яті починається з сегменту префікса програми (Program Segment Prefics, PSP), що утворюється і заповнюваного системою. PSP завжди має розмір 256 байт; він містить таблиці і поля даних, використовувані системою в процесі виконання програми. Услід за PSP розташовуються сегменти програми в тому порядку, як вони оголошені в програмі. Сегментні регістри автоматично ініціалізувалися таким чином: ES і DS указують на початок PSP (що дає можливість, зберігши їх вміст, звертатися потім в програмі до PSP), CS - на початок сегменту команд, а SS - на початок сегменту стека. У покажчик команд IP завантажується відносна адреса точки входу в програму (з операнда директиви end), а в покажчик стека SP - величина, рівна оголошеному розміру стека, внаслідок чого покажчик стека указує на кінець стека (точніше, на перше слово за його межами).
Таким чином, після завантаження програми в пам'ять адресуються виявляються всі сегменти, окрім сегменту даних. Ініціалізація регістра DS в перших рядках програми дозволяє зробити тим, що адресуються і цей сегмент.
Малюнок 1.9 ще раз підкреслює найважливішу особливість архітектури процесорів Intel: адреса будь-якого елементу пам'яті складається з двох слів, одне з яких визначає розташування в пам'яті відповідного сегменту, а інше - зсув в межах цього сегменту. Сенс сегментної частини адреси, що зберігається завжди в одному з сегментних регістрів, в реальному і захищеному режимі різний; у МП 86 сегментна частина адреси, після множення її на 16, визначає фізична адреса початку сегменту в пам'яті.
Звідси витікає, що сегмент завжди починається з адреси, кратної 16, тобто на межі 16-байтового блоку пам'яті (параграфа). Сегментну адресу можна розглядати, як номер параграфа, з якого починається даний сегмент. Розмір сегменту визначається що об'ємом містяться в нім даних, але ніколи не може перевищувати величину 64 Кбайт, що визначається максимально можливою велічиной зсуви.
Сегментна адреса сегменту команд зберігається в регістрі CS, а зсув до байта, що адресується, - в покажчику команд IP. Як вже наголошувалося, після завантаження програми в IP заноситься зсув першої команди програми; процесор, прочитавши її з пам'яті, збільшує вміст IP точно на довжину цієї команди (команди процесорів Intel можуть мати довжину від 1 6 байт), внаслідок чого IP указує на другу команду програми. Виконавши першу команду, процесор прочитує з пам'яті другу, знову збільшуючи значення IP. В результаті в IP завжди знаходиться зсув чергової команди, тобто команди, наступної за виконуваною. Описаний алгоритм порушується тільки при виконанні команд переходів, викликів підпрограм і обслуговування переривань.
Сегментна адреса сегменту даних зазвичай зберігається в регістрі DS, а зсув може знаходиться в одному з регістрів загального призначення, наприклад, у ВХ або SI. Проте в МП 86 два сегментні регістри даних - DS і ES. Додатковий сегментний регістр ES часто використовується для звернення до полів даних, що не входять в програму, наприклад до відеобуфера або системних осередків. Проте при необхідності його можна набудувати і на один з сегментів програми. Зокрема, якщо програма працює з великим об'ємом даних, для них можна передбачити два сегменти і звертатися до одного з них через регістр DS, а до іншого - через ES.

-

:: Наша кнопка ::

Отримати код:

Підтримайте наш сайт і розмістіть нашу кнопку на своєму ресурсі.


:: Популярне ::

-


:: Посилання ::

-


 

 

 


Copyright © net.kr.ua, 2019-2025 (assem.us)