→ Пошук по сайту       Увійти / Зареєструватися
Знання Асемблер Основи програмування

Переходи

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

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

Безумовні переходи здійснюються за допомогою команди jmp, яка може використовуватися в 5 різновидах. Перехід може бути:

  • прямим коротким (у межах -128... + 127 байтів);
  • прямим ближнім (в межах поточного сегменту команд):
  • прямим дальнім (у інший сегмент команд);
  • непрямим ближнім (в межах поточного сегменту команд через осередок з адресою переходу);
  • непрямим дальнім (у інший сегмент команд через осередок з адресою переходу).
  • Розглянемо послідовно структуру програм з переходами різного вигляду.

Прямий короткий (short) перехід. Прямим називається перехід, в команді якого в явній формі указується мітка, на яку потрібно перейти. Зрозуміло, ця мітка має бути присутньою в тому ж програмному сегменті, при цьому помічена нею команда може знаходитися як до, так і після команди jmp. Гідність команди короткого переходу полягає в тому, що вона займає лише 2 байт пам'яті: у першому байті записується код операції (Ebh), в другому - зсув до точки переходу. Відстань до точки переходу відлічується від чергової команди, тобто команди, наступної за командою jmp. Оскільки потрібно забезпечити перехід як вперед, так і назад, зсув розглядається, як число із знаком і, отже, перехід може бути здійснений максимум на 127 байт вперед або 128 байт назад. Прямий короткий перехід оформляється таким чином:

code segment
.
jmp short go ;Код ЄВ dd

go:
.
code ends  

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

tasm /m2 p,p,p  

то описувач short можна опустити, оскільки асемблер сам визначить, що відстань до точки переходу укладається в короткий перехід, навіть якщо влучна go розташована після рядка з командою jmp. При використанні транслятора MASM вказівка описувача short обов'язково (якщо влучна go розташована після команди jmp). Тут виявляються незначні відмінності асемблерів різних розробників.

У коментарі вказаний код команди; dd (від displacement, зсув) позначає байт із зсувом до точки переходу від команди, наступної за командою jmp.

При виконанні команди прямого короткого переходу процесор додає значення байта dd до молодшого байта поточного значення покажчика команд IP (який, як вже мовилося, завжди указує на команду, наступну за виконуваною). В результаті в IP виявляється адреса точки переходу, а пропозиції, що знаходяться між командою jmp і точкою переходу, не виконуються. Між іншим, конструкція з прямим переходом вперед часто використовується для того, щоб обійти дані, які по якихось причинах бажано розмістити в сегменті команд.

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

code segment
.
jmp go ;Код Е9 dddd
.
go:
.
code ends  

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

При виконанні команди прямого ближнього переходу процесор повинен додати значення слова dddd до поточного значення покажчика команд IP і сформувати тим самим адресу точки переходу. Що є зсувом ddddl Яка це величина, із знаком або без знаку? Якщо розглядати зсув як величину без знаку, то перехід буде можливий тільки вперед, що, звичайно, невірно. Якщо ж зсув є величиною із знаком, то перехід можливий не більш, ніж на півсегменту вперед або на півсегменту назад, що теж невірно. Насправді, розглядаючи обчислення адреси точки переходу, слід мати на увазі явище обертання, суть якого можна коротко виразити такими співвідношеннями:

Ffffh+0001h=0000h

0000h-0001h=ffffh  

Якщо послідовно збільшувати вміст якого-небудь регістра або елементу пам'яті, то, досягнувши верхньої можливої межі Ffffh, число "перевалить" через цей кордон, стане рівним нулю і продовжить наростати в області малих позитивних чисел (1, 2, 3, і так далі). Так само, якщо послідовно зменшувати деяке позитивне число, то воно, досягнувши нуля, перейде в область негативних (або, що те ж саме, великих беззнакових) чисел, проходячи значення 2, 1, 0, Ffffh, Fffeh і так далі.

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

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

Прямій дальній (far), або міжсегментний перехід. Цей перехід дозволяє передати управління в будь-яку крапку будь-якого сегменту. При цьому, очевидно, передбачається, що програмний комплекс включає декілька сегментів команд. Команда дальнього переходу має довжину 5 байт і включає, окрім коди операції Eah, ще і повна адреса точки переходу, тобто сегментна адреса і зсув. Транслятору треба повідомити, що цей перехід - дальній (за умовчанням команда jmp транслюється, як команда ближнього переходу). Це робиться за допомогою описувача far ptr, указуваного перед ім'ям крапки переходу.

codel segment

assume CS: codel ;Сообщим транслятору, що це сегмент команд
.
jmp far ptr go ;Код EA dddd ssss
.
codel ends

code2 segment

assume CS : code2 ; Повідомимо транслятор, що це сегмент команд
.
gо:
.
code2 ends 

Влучна go знаходиться в іншому сегменті команд цієї двохсегментної програми. У коді команди ssss - сегментна адреса сегменту code2, а dddd - зсув точки переходу go в сегменті команд code2.

Відмітимо, що за наявності в програмі декількох сегментів команд, кожен з них необхідно передувати директивою асемблера assume Сs:імя_сегмента, яка повідомляє транслятор про те, що почався черговий сегмент команд. Це допоможе транслятору правильно обробляти адреси влучний, що зустрічаються в цьому сегменті.

Освоївши застосування команд дальніх переходів, ми дістали можливість створювати програми будь-якої довжини. Дійсно, передбачивши в кінці кожного програмного сегменту команду дальнього переходу на почато наступний, ми можемо включити в програмний комплекс будь-яке число сегментів по 64 Кбайт. Єдине обмеження - щоб вони всі помістилися в пам'яті. Насправді так, звичайно, не роблять. Розумніше додаткові сегменти команд заповнити підпрограмами і викликати їх з основного сегменту (або один з одного) в міру необхідності. Проте і в цьому випадку команди викликів підпрограм мають бути дальніми. Різновиди підпрограм і команд їх виклику будуть розглянуті нижче.

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

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

code segment
.
jmp Ds:go_addr ;Код FF 26 dddd
.
go: ; Крапка переходу
.
code ends

data segment
.
go_addr dw go ;Адреса переходу (слово)
.
data ends  

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

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

mov go_addr, offset gol

mov go_addr, offset go2

mov go_addr, offset go3  

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

Асемблер допускає різні форми опису непрямого переходу через осередок сегменту даних:

jmp Ds:go_addr

jmp word ptr go_addr
jmp go_addr   

У першому варіанті, використаному в приведеному вище фрагменті, вказано, через який сегментний регістр належить звертатися до осередку go_addr, що містить адресу переходу. Тут допустима заміна сегменту, якщо сегмент з осередком go_addr адресується через інший сегментний регістр, наприклад, ES або CS.

У другому варіанті підкреслюється, що перехід здійснюється через осередок розміром в одне слово і, отже, є ближнім. Осередок go_addr міг бути оголошена за допомогою директиви dd і містити повну двухсловний адресу переходу, потрібну для реалізації дальнього переходу. Проте нею можна скористатися і для ближнього переходу. Описувач word ptr перед ім'ям осередку з адресою переходу засташшет транслятор рахувати, що вона має розмір 1 слово (незалежно від того, як вона була оголошена), і що перехід, отже, є ближнім.

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

jmp go_addr 

було вже відомо, що є ім'ям go_addr. Цього можна добитися двома способами. Перший - розташувати сегмент даних до сегменту команд, а не після, як в приведеному вище прикладі. Другий - змусити транслятор обробляти початковий текст програми не один раз, як він це робить за умовчанням, а декілька. Число проходів для транслятора TASM можна задати при його виклику за допомогою ключа /m#, де # - необхідне число проходів. У нашому випадку досить два проходи.

У приведених прикладах адреса поля пам'яті з адресою точки переходу задавалася безпосередньо в коді команди непрямого переходу. Проте цю адресу можна задати і в одному з регістрів загального призначення (ВХ, SI або DI). Для приведеного вище прикладу непрямого переходу в точку go, адреса якої знаходиться в осередку go_addr в сегменті даних, перс-хід з використанням непрямої регістрової адресації може виглядати таким чином:

mov BX, offset go_addr ;У ВХ зсув поля з адресою переходу

jmp [BX] ;Переход у точку gо 

Особливо великі можливості надає методика непрямого переходу з використанням базово-індексної адресації через пари регістрів, наприклад, [BX][SI] або [BX][DI]. Цей спосіб зручний в тих випадках, коли є ряд альтернативних точок переходу, вибір яких залежить від деяких умов. В цьому випадку в сегменті даних створюється не одне поле з адресою, а таблиця адрес переходів. У базовий регістр ВХ завантажується адреса цієї таблиці, а в один з індексних регістрів - визначений тим або іншим способом індекс в цій таблиці. Перехід здійснюється в крапку, відповідну заданому індексу. Структура програми, що використовує таку методику, виглядає таким чином:

code segment

mov BX, off set go_tbl ;Загружаем у ВХ базова адреса таблиці

mov SI, 4 ;Вычисленное якимсь

;образом зсув в таблиці

jmp [BX] [SI] ;Если індекс =4, перехід в точку goз
.
gol: ;1-я точка переходу
.
gо2 : ;2-я точка переходу
.
gоз: ;3-я точка переходу
.
code ends

data segment

go_tbl label word ;Таблица адрес переходів

gol_addr dw gol ;Адрес першою альтернативною
;точки переходу

go2_addr dw go2 ;Адрес другою альтернативною
;точки переходу

go3_addr dw доз ;Адрес третьою альтернативною
;точки переходу

data ends 

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

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

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

mov BX, off set gol jmp BX  

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

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

codel segment

assume Cs:codel,ds:data
.
jmp Ds:go_addr ; Код FF 2e dddd
.
codel ends

code2 segment

assume Cs:code2
.
go: ;Точка переходу в іншому сегменті команд
.
code2 ends

data segment
.
go_addrdd go ;Двухсловный адреса точки переходу
.
data ends 

Точка переходу go знаходиться в іншому сегменті команд цієї двохсегментної програми. У коді команди dddd позначає відносну адресу слова go_addr в сегменті даних. Осередок go_addr оголошується директивою dd (define double, визначити подвійне слово) і містить двухсловний адресу точки переходу; у першому слові міститься зсув go в сегменті команд codel, в другому слові сегментна адреса codel. Обидва компоненти адреси переходу можуть бути обчислені і поміщені в осередок go_addr по ходу виконання програми.

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

jmp Ds:go_addr ;Возможна заміна сегменту

jmp dword ptr go_addr ;Если поле go_addr оголошене
;операторами dw

jmp go_addr ;Характеристики осередки винні
;бути відомі 

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

mov Bx,offset go_addr

jmp [BX]  

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

загрузка...
Сторінки, близькі за змістом