4858

Основы микропрограммирования на языке Ассемблера. Лабораторные работы

Лабораторная работа

Информатика, кибернетика и программирование

Создание первой программы на языке Ассемблера Программирование арифметических операций Работа со строками Написание собственного обработчика прерывания Связь подпрограмм на Ассемблере с программами на языке высокого уровня Лабораторная работа №1 Со...

Русский

2012-11-28

322.5 KB

239 чел.

Создание первой программы на языке Ассемблера

Программирование арифметических операций

Работа со строками

Написание собственного обработчика прерывания

Связь подпрограмм на Ассемблере с программами на языке высокого уровня


Лабораторная работа №1 «Создание первой программы на языке Ассемблера»

Целью лабораторной работы является получение знаний о структуре и основных правилах записи  программ на языке Ассемблера для ЭВМ типа IBM PC, а также приобретение практических навыков по составлению простейших программ на языке Ассемблера, их выполнению и отладке.

Теоретическая часть

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

Формат команды языка Ассемблера в общем случае содержит до 4-х полей:

[метка:] команда [поле операндов] [;комментарий]

Обязательным является только поле команды. По крайней мере, один пробел между полями обязателен. Максимальная длина строки - 132 символа.

Метка используется для указания адреса команды и служит для присвоения символического имени первому байту области памяти, в котором будет записан машинный эквивалент команды. Метки могут содержать:

- буквы от A до Z, причем Ассемблер не делает разницы между строчными и прописными буквами;

- цифры от 0 до 9;

- спец.символы: '?', '.'(только первый символ), '@'(коммерческое эт), '$', '_'.

Первым символом в метке должна быть буква или специальный символ. Максимальная длина метки - 31 символ.

Поле команды содержит имя команды, например, MOV - команда пересылки, ADD - команда сложения и другие.

Операнды определяют:

- начальные значения данных;

- элементы, над которыми выполняется действие.

Пример:

MOV AX, 5; команда MOV заносит в регистр AX значение 5.

В поле операнда, если оно присутствует, может быть один или два операнда. Между собой операнды отделяются запятой. В командах с двумя операндами первый представляет собой приемник, а второй - источник. Источник никогда не изменяется, приемник изменяется почти всегда.

Комментарий начинается с символа ';' в любом месте программы. Все символы справа от символа ';' до конца строки воспринимаются Ассемблером как комментарий.

Директивы могут иметь до 4-х полей:

[метка] директива [операнд] [;комментарий]

Наиболее употребительные директивы можно разделить на две группы: директивы данных и директивы управления листингом. Директивы данных в свою очередь делятся на пять групп:

- директивы определения идентификаторов;

- директивы определения данных;

- директивы внешних ссылок;

- директивы определения сегмента/процедуры;

Существует две директивы определения идентификаторов: EQU и '='. EQU присваивает имя выражению постоянно, а '=' - временно и его можно переопределить.

Существуют следующие директивы определения данных:

- DB - определить байт;

- DD - определить двойное слово;

- DW - определить слово;

- DT - определить 10 байт.

Обычно DB и DW резервируют память под переменные, а DD и DT - под адреса. В любом случае можно указать начальное значение или знак '?' только для резервирования памяти. Операция DUP позволяет повторять одно и тоже значение несколько раз.

Пример:

PEREM DB 4 DUP(?); директива резервирует четыре байта в памяти под переменную PEREM.

Если перечислять значения переменной через запятую, то тем самым определяется таблица данных.

Пример:

TABL DW 3,4,5 ; директива выделяет область памяти с именем TABL длиной три слова

  ;и заносит в первое слово значение 3, во второе значение 4, в третье слово - значение 5.

Директивы определения сегмента делят исходную программу на сегменты. В программе на языке Ассемблера возможно 4 вида сегментов:

- сегмент данных;

- сегмент стека;

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

- дополнительный сегмент.

По крайней мере один сегмент - сегмент команд - должен присутствовать в каждой программе.

Существует две директивы определения сегмента: SEGMENT и ENDS, а также директива ASSUME, которая устанавливает для Ассемблера соответствие между конкретными сегментами и сегментными pегистpами.

Пример:

DATASG SEGMENT PARA 'DATA' ;директива определяет начало сегмента с именем DATASG, а ;директива

DATASG ENDS ;конец этого сегмента. 

Директива

ASSUME CS:CODESG,DS:DATASG

указывает Ассемблеру, что сегмент CODESG будет адресоваться с помощью сегментного регистра CS, а сегмент DATASG - с помощью сегментного регистра DS.

Директивы PROC и ENDP определяют начало и конец процедуры. Процедура может иметь атрибут дистанции NEAR или FAR. Процедура с атрибутом NEAR может быть вызвана только из того сегмента команд, в котором она определена, а процедура с атрибутом FAR может быть вызвана и из другого сегмента команд. Например, директива

FUN PROC NEAR

определяет начало процедуры FUN с атрибутом дистанции NEAR, а

директива

FUN ENDP

конец этой процедуры. Программа на Ассемблере имеет атрибут FAR.

Директивы внешних ссылок PUBLIC и EXTRN делают возможным использование переменных и процедур, определенных в разных файлах.

Директива управления трансляцией END отмечает конец исходного модуля, обязательно присутствует в каждом модуле.

Директивы управления листингом PAGE и TITLE могут быть использованы для определения формата вывода распечаток программ и выдачи заголовков.

Таким образом, типовая структура программы на языке Ассемблера для ЭВМ типа IBM PC выглядит следующим образом:

TITLE ;заголовок программы

PAGE 60,132 ; определение сегмента стека

STACKSG SEGMENT PARA STACK 'STACK'

DB 64 DUP(?) ; область стека, не менее 32 слов

STACKSG ENDS ; конец сегмента стека

DATASG SEGMENT PARA 'DATA' ; определение сегмента данных

; здесь можно поместить, если необходимо, директивы определения

DATASG ENDS ;конец сегмента данных

; Определение сегмента команд (основная программа)

CODESG SEGMENT PARA 'CODE'

ASSUME CS:CODESG, DS:DATASG, SS:STACKSG

ENTRY PROC FAR

; Инициализировать программу

PUSH DS ; сохранить в стеке адрес возврата

SUB AX,AX ; обнулить регистр AX

PUSH AX ; занести в стек нулевое смещение для адреса возврата

; Инициализировать адрес сегмента данных

MOV AX,DATASG ; занести адрес сегмента

MOV DS,AX ; данных в регистр DS

; Программа

...

POP AX;

POP DS; восстановили регистры AX и DS

RET ; вернутся в MS-DOS

ENTRY ENDP

CODESG ENDS

END ENTRY

В данном фрагменте показана группа команд PUSH DS ... MOV DS,AX, которая является стандартной для программ на Ассемблере и обеспечивает возврат управления в MS-DOS после выполнения программы. Команда RET обеспечивает выход из программы и передачу управления MS-DOS.

Практическая часть

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

  1.  Постановка задачи (точное и подробное описание функциональности будущей программы, а также описание всех входных и выходных данных и способа их передачи программе);
  2.  Разработка алгоритма программы (построение блок-схемы, текстовое или математическое описание решения);
  3.  Формализация алгоритма (запись алгоритма на языке программирования). Создание текстового файла программы с расширением .asm (например, my.asm). Отсутствие среды разработки позволяет программисту самостоятельно выбрать текстовый редактор для написания кода программы. Для этой цели подойдет любой текстовый редактор с нумерацией строк, например Блокнот или редактор, встроенный в оболочку FAR-менеджер.
  4.  Трансляция программы. Трансляция - процесс перевода программы из текстового вида в машинный код. При использовании транслятора фирмы Borland необходимо выполнить команду:

      tasm my.asm

т.е. запускаем транслятор tasm и передаем с командной строки имя файла, содержащего программу. Если программа имеет синтаксические ошибки, транслятор выдаст сообщение об ошибке с указанием номера строки и описанием для каждой ошибки (нужно вернуться на этап №3 и исправить синтаксические ошибки). В случае успешной трансляции будет создан файл, содержащий объектный код программы my.obj, который ещё не является исполняемым модулем.

  1.  Компоновка программы. Компоновка - создание из файла объектного кода исполняемого модуля.

      tlink my.obj

В качестве параметра компоновщик tlink принимает имя файла содержащего объектный код программы (в нашем случае - my.obj). В случае успешной компоновки будет создан исполняемый модуль my.exe

  1.  Запуск и тестирование исполняемого модуля программы. На данном этапе необходимо проверить, соответствует ли написанная программа постановке задачи, выполненной на этапе №1. Неправильная работа программы говорит об алгоритмической ошибке (семантическая ошибка), поэтому для успешного её устранения следует вернуться на этап разработки алгоритма (этап №2).

Написание первой программы на языке ассемблера

  1.  Постановка задачи. Написать программу, которая выводит на экран строчку "Привет!".
  2.  Разработка алгоритма программы. Алгоритм линейный, разработки не требует.
  3.  Формализация (запись) алгоритма
    В текстовом редакторе создаем файл privet.asm и записываем в него следующий код (без номеров строк) :

Вариант 1:

1

   

data segment

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

2

 

       

mes db 'Привет!$'

;строка для вывода на экран. '$' - признак конца строки

3

 

data ends

;конец сегмента данных

4

 

code segment

;начало сегмента кода

5

 

start:

;метка start - начало нашей программы

6

 

 

assume cs:code, ds: data    

;директива компилятора

7

 

 

mov ax, data

;настройка сегмента данных

8

 

 

mov ds, ax

9

 

 

mov ah, 9

;функция №9 прерывания 21h  - вывод строки на экран

10

 

 

lea dx, mes

;берём адрес строки

11

 

 

int 21h

;вызов прерывания для вывода строки

12

 

 

mov ax, 4c00h

;функция завершения программы

13

 

 

int 21h

;завершаем программу

14

 

code ends

;конец сегмента кода

15

 

end start

;конец программы с точкой входа start

  1.  Компиляция программы
    tasm privet.asm
  2.  Компоновка программы
    tlink privet.obj
  3.  Запуск и тестирование
    privet.exe 

Переход на новую строку

Для организации перехода на новую строку достаточно вывести на экран символы перевода строки и возврата каретки (CR/LF). Эти символы имеют коды 10 и 13. Если в нашей программе необходимо после вывода строки перейти на новую, то для этого достаточно переписать вторую строку программы:

mes2 db 'Выводим строку и переходим на новую...', 10, 13, '$'

Переход на новую строку можно выполнить и до вывода сообщения на экран:

             mes3 db 10, 13, 'Выводим с новой строки...$'

Вариант 2:

  1.  

text          segment  

;Начало сегмента команд

  1.  

assume   CS:text,DS:data        

;Сегментный регистр CS будет указывать на сегмент команд, а сегментный регистр DS - на сегмент данных

  1.  

start:      mov AX,data

;Адрес сегмента данных сначала загрузим в АХ,

  1.  

               mov DS,AX

;а затем перенесем из АХ в DS

  1.  

               mov AH,09h

;Функция MS-DOS 9h вывода на экран

  1.  

               mov DX,offset mesg

;Адрес выводимого сообщения должен быть в DX

  1.  

               int 21h

;Вызов MS-DOS

  1.  

               mov AH,4Ch

;Функция 4Ch завершения программы

  1.  

               mov AL, 0

;Код 0 успешного завершения

  1.  

               int 21h

;Вызов MS-DOS

  1.  

text         ends

;Конец сегмента команд

  1.  

data        segment

;Начало сегмента данных

  1.  

mesg      db 'Hello world!$'

;Выводимый текст

  1.  

data        ends

;Конец сегмента данных

  1.  

stk           segment stack

;Начало сегмента стека

  1.  

               db 256 dup (0)

;Резервируем 256 байт для стека

  1.  

stk           ends

;Конец сегмента стека

  1.  

end start 

;Конец текста программы с точкой входа start

Задание

  1.  Написать программу, которая выводит одно под другим следующие сообщения:

Привет!

Меня зовут ФИО студента!

Я с удовольствием буду изучать Ассемблер!

  1.  Сравните и проанализируйте исходные тексты программ Вариант 1 и Вариант 2.

Контрольные вопросы

  1.  Перечислите, из каких структурных компонентов состоит программа на языке Ассемблера.
  2.  Как выполняются трансляция и компоновка программы на Ассемблере.
  3.  Каким образом в ассемблере объявляются константы и переменные?
  4.  От чего зависит размер поля, отводимого под размещаемые данные?
  5.  С какой целью используется служебное слово DUP?

Лабораторная работа №2 «Программирование арифметических операций»

Цель работы – изучить форматы и правила работы с командами сложения, умножения, вычитания и деления микропроцессоров семейства х86.

Теоретическая часть

Форматы арифметических данных.

Двоичные числа могут иметь 8 или 16 битов и могут быть со знаком или без знака. У числа без знака все 8 или 16 битов представляют его значение. Следовательно, двоичные числа без знака могут принимать значения от 0 до 255 (8-битовые) или до 65535 (16-битовые). У числа со знаком старший бит (7 или 15) указывает его знак, а остальные биты содержат значение числа. Следовательно, числа со знаком могут принимать значения от -128 до 127 (8-битовые) или от -32768 до 32767 (16-битовые).

Десятичные числа

Микропроцессор 8x86 хранит десятичные числа в виде последовательностей байтов без знака в упакованном или неупакованном формате. Каждый байт упакованного десятичного числа содержит две цифры в двоично-десятичном коде BCD (binary-coded decimal). При этом код старшей цифры числа занимает четыре старших бита байта. Следовательно, один упакованный десятичный байт может содержать значения от 00 до 99.

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

Как же микропроцессор 8x86 узнает, с каким видом данных он имеет дело? Пусть требуется сложить два байта. Как он определяет, какие числа они представляют (двоичные числа со знаком, двоичные числа без знака, упакованные десятичные числа или неупакованные десятичные числа)? На самом деле микропроцессор 8x86 об этом совершенно не заботится и трактует все операнды только как двоичные числа.

Это хорошо в том случае, когда Ваши операнды и в самом деле являются двоичными числами, но если они оказались десятичными, то результаты, конечно, будут ошибочными. Для компенсации таких ошибок микропроцессор 8x86 имеет группу команд коррекции, которые обеспечивают получение правильного результата после выполнения операций над десятичными числами. Эти команды будут обсуждаться далее.

Хранение чисел в памяти

Как уже упоминалось, микропроцессор 8x86 хранит 16-битовые числа в порядке, противоположном естественному представлению, а именно он хранит младшие биты числа в байте с меньшим адресом. Например, при запоминании числа 1234Н в ячейке по имени NUM он размещает 34Н по адресу NUM, a 12H — по адресу NUM+1. При чтении изображения (или дампа) содержимого памяти учитывайте эту схему свертки байтов. Запомните фразу: "младший байт — младший адрес, старший байт — старший адрес".

Команды сложения.

Команда сложения ADD и команда сложения с добавлением переноса ADC.

Команды ADD (add - сложить) и ADC (add with carry - сложить с переносом) могут складывать как 8-, так и 16-битовые операнды. Команда ADD складывает содержимое операнда-источника и операнда-приемника и помещает результат в операнд-приемник. В символической нотации ее действия можно описать как

приемник = приемник + источник

Команда ADC делает то же, что и команда ADD, но при сложении использует также флаг переноса CF, что можно записать следующим образом:

приемник = приемник + источник + перенос

Перенос при сложении двоичных чисел аналогичен переносу при сложении десятичных чисел в столбик. Например, пои сложении

  98

+ 13

  79

190

возникает два переноса: сложение единиц вызывает добавление 2 к десяткам, а сложение десятков и перенос из столбца единиц вызывает другой перенос, а именно числа 1 в столбец сотен. Перенос возникает тогда, когда сумма цифр столбца в нем не помещается.

Аналогичным образом возникает перенос, когда ЭВМ складывает двоичные числа: если сумма не помещается в операнде-приемнике, то генерируется перенос. Как известно, 8-битовый регистр может содержать значения без знака в диапазоне от 0 до 255. Если мы, например, выполним двоичное сложение чисел 250 и 10, то получим

  1111 1010    (двоичное представление числа 250)

+ 0000 1010   (двоичное представление числа 10)

                             1 0000 01000   (ответ: десятичное значение 260)

Результат верен, но занимает 9 двоичных битов! Если при выполнении этой операции мы использовали 8-битовые регистры, то младшие 8 битов будут занесены в регистр-приемник, а девятый бит - во флаг переноса CF.

Теперь Вам нетрудно понять, почему микропроцессор 8x86 имеет две разные команды сложения. Одна из них (ADD) может складывать значения, представляемые байтами или словами, а также младшие части значений повышенной точности. Другая команда (ADC) используется для сложения старших частей значений повышенной точности.

Например, команда

ADD AX,CX

складывает 16-битовые значения регистров АХ и СХ и возвращает результат в регистр АХ. Если Ваши операнды имеют длину более 16 битов, то можно воспользоваться последовательностью команд вида

ADD АХ,СХ     ; Сначала сложить младшие 16 битов, а затем

ADC BX,DX     ; старшие 16 битов

которая складывает 32-битовое число, находящееся в регистрах СХ и DX, с 32-битовым числом, находящимся в регистрах АХ и ВХ. Использованная здесь команда ADC добавляет к (DX)+(BX) любой перенос от сложения (СХ)+(АХ).

Вы можете также добавлять находящийся в памяти операнд к регистру и наоборот или добавлять непосредственный операнд к регистру или операнду, находящемуся в памяти. Приведем несколько примеров:

ADD AX,MEM_WORD   ;Добавить значение ячейки памяти к регистру

ADD MEM_WORD,AX   ;или наоборот

ADD АL,10    ;Добавить константу к регистру

ADD MEM_BYTE,OFH   ;или к ячейке памяти

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

Команды ADD и ADC могут воздействовать на шесть флагов:

Флаг переноса CF равен 1, если результат сложения не помещается в операнде-приемнике; в противном случае он равен 0.

Флаг четности PF равен 1, если результат имеет четное число битов со значением 1; в противном случае он равен 0.

Вспомогательный флаг переноса AF равен 1, если результат сложения десятичных чисел требует коррекции; в противном случае он равен 0.

Флаг нуля ZF равен 1, если результат равен 0; в противном случае он равен 0.

Флаг знака SF равен 1, если результат отрицателен (старший бит равен 1); в противном случае он равен 0.

Флаг переполнения OF равен 1, если сложение двух чисел одного знака (оба положительные или оба отрицательные) приводит к результату, который превышает диапазон допустимых значений приемника в обратном коде, а сам приемник при этом меняет знак. В противном случае флаг OF равен 0.

Флаги SF и OF имеют смысл только при сложении чисел со знаком, а флаг AF -только при сложении десятичных чисел.

Микропроцессор 8x86 имеет команды, которые проверяют флаги и на основе результатов проверки принимают решение о том, куда передать управление. Например, при отрицательном результате (SF=1) должна исполняться одна группа команд, а при положительном (SF=0) — другая. Эти команды "принятия решения" будут обсуждаться ниже.

Коррекция результата сложения для представления в кодах ASCII и в упакованном десятичном формате (команды AAA и DAA).

Как уже упоминалось, при выполнении сложения микропроцессор 8x86 рассматривает операнды как двоичные числа. Что же произойдет, если они будут двоично-десятичными кодами чисел (кратко десятичными или BCD-числами)? Разберемся в этом на примере. При сложении упакованных BCD-чисел 26 и 55 микропроцессор 8x86 выполнит следующее двоичное сложение:

    00100110 (BCD-число 26)

  +01010101 (BCD-число 55)

    01111011 (??)

Вместо правильного значения (BCD-число 81) мы получим результат, у которого старшая цифра 7, а младшая - шестнадцатеричная цифра В. Означает ли это, что нельзя складывать десятичные числа? Нет, это означает лишь то, что результат должен быть скорректирован для представления в десятичной форме.

Коррекция результата сложения десятичных чисел осуществляется командами ААА (ASCII adjust for addition - скорректировать результат сложения для представления в кодах ASCII) и DAA (Decimal adjust for addition - скорректировать сложение для представления в десятичной форме). В них не требуется наличия операнда: предполагается, что корректируемое значение находится в регистре AL.

Команда ААА преобразует содержимое регистра AL в правильную неупакованную десятичную цифру в младших четырех битах регистра AL (и заполняет нулями старшие четыре бита). Она используется в следующем контексте:

ADD AL,BL     ;Сложить неупакованные числа, находящиеся в AL и BL 

ААА                ; и преобразовать результат в неупакованное число

Если результат превышает 9, то команда ААА добавляет 1 к содержимому регистра АН (чтобы учесть избыточную цифру) и полагает флаг CF равным 1; в противном случае она обнуляет флаг CF. Кроме того, команда ААА изменяет состояние флага AF и оставляет значения флагов PF, ZF, SF и OF неопределенными. Но так как в данном случае только флаг CF имеет смысл, то считайте значения остальных флагов уничтоженными.

Команда DAA преобразует содержимое регистра AL в две правильные упакованные десятичные цифры. Она используется в следующем контексте:

ADD AL,BL          ;Сложить упакованные BCD-числа в AL и BL

DAA                    ; и преобразовать результат в упакованное число

Если результат превышает предельное значение для упакованных BCD-чисел (99), то команда DAA добавляет 1 к содержимому регистра АН и полагает флаг CF равным 1. Кроме того, команда DAA изменяет состояния флагов PF, AF, ZF и CF и оставляет значение флага OF неопределенным. Но так как в данном случае только флаг CF имеет смысл, то считайте остальные пять флагов уничтоженными.

Команда приращения значения приемника на единицу

Команда INC (increment - прирастить) добавляет 1 к содержимому регистра или ячейки памяти, но в отличие от команды ADD не воздействует на флаг переноса CF. Команда INC удобна для приращения значений счетчиков в циклах команд. Ее можно использовать и для приращения значения индексного регистра или указателя при доступе к последовательно расположенным ячейкам памяти.

Приведем несколько примеров:

INC CX         ;Прирастить значение 16-битового

INC AL              ;или 8-битового регистра

INC MEM_ВYТЕ       ;Прирастить значение байта

INC MEM_WORD[BX]                  ;или слова памяти

Как ни странно, приращение значения 8-битового регистра отнимает у микропроцессора 8x86 больше времени, чем приращение значения 16-битового регистра. Это вызвано тем, что разработчики фирмы Intel предполагали, что программисты будут чаще пользоваться счетчиками размером в слово, а не байт, и предусмотрели специальную однобайтовую версию команды INC для 16-битовых регистров.

Команды вычитания. Выполнение вычитания микропроцессором 8086.

Внутри микропроцессора 8x86, как и любого другого микропроцессора общего назначения, нет устройства вычитания. Однако он имеет устройство сложения (сумматор) и может вычитать числа путем сложения. Чтобы понять, как можно вычитать путем сложения, посмотрим, как вычесть 7 из 10. В начальной школе учат записывать это как

10-7,

но в старших классах (скажем, в курсе алгебры) учат и другому способу записи:

10+(-7).

Первым способом (непосредственное вычитание) вычитание может быть выполнено микропроцессором, имеющим устройство вычитания. Так как микропроцессор 8x86 его не имеет, то он вычитает в два приема. Сначала он меняет знак у вычитаемого (у второго числа), т.е. обращает его, а затем складывает уменьшаемое и обращенное вычитаемое. Так как микропроцессор 8x86 оперирует двоичными числами, то обращение знака числа производится путем так называемого дополнения до двух.

Чтобы выполнить дополнение до двух, берется исходная форма двоичного числа и значение каждого его бита обращается (каждый 0 заменяется на 1, а 1 — на 0), а затем к полученному числу добавляется 1.

Применяя это к нашему примеру, получаем 8-битовые представления чисел 10 и 7: 00001010В и 00000111В соответственно. Затем дополним двоичное представление 7 до двух:

1111 1000 (обратить все биты)

 +_____1 (добавить 1)

 1111 1001 (дополнение до двух числа 7, или - 7).

Теперь операция вычитания примет следующий вид:

    0000 1010 (10)

   +1111 1001 (-7)

    0000 0011 (Ответ: 3)

Таким образом, получаем правильный ответ!

Так как микропроцессор 8x86 выполняет дополнение до двух автоматически, то эта операция, возможно, понадобится в редких случаях, однако позже рассмотрим команду NEG, посредством которой также можно выполнить дополнение до двух.

Команда вычитания SUB и вычитания с заемом SBB.

Команды SUB (substract - вычесть) и SBB (substract with borrow - вычесть с заемом) аналогичны соответственно командам сложения ADD и ADC, только при вычитании флаг переноса CF действует как признак заема. Команда SUB вычитает операнд-источник из операнда-приемника и возвращает результат в операнд-приемник, т.е.

                                                     приемник = приемник — источник

Команда SBB делает то же самое, но дополнительно вычитает значение флага переноса CF:

приемник = приемник - источник - перенос

Как и в случае сложения, команды вычитания выполняют две отдельные функции. Первая команда SUB вычитает числа размером в байт или слово, а также младшие биты чисел повышенной точности. Другая команда SBB вычитает старшие биты чисел повышенной точности. Например, команда

SUB AХ,СХ

вычитает содержимое регистра СХ из содержимого регистра АХ и возвращает результат в регистр АХ.

Если размеры операндов превышают 16 битов, то пользуйтесь последовательностью команд вида

SUB AX,BX    ;Вычесть младшие 16 битов,

SBB BX,DX    ; а затем — старшие 16 битов

Здесь мы вычитаем 32-битовое число, помещенное в регистры СХ и DX, из 32-битового числа, помещенного в регистры АХ и ВХ. При вычитании содержимого регистра DX из содержимого регистра ВХ команда SBB учитывает возможность заема при выполнении первого вычитания.

Можно вычитать из содержимого регистра содержимое ячейки памяти (и наоборот) или вычитать из содержимого регистра либо ячейки памяти непосредственное значение. Ниже приведены примеры допустимых команд:

SUB AХ,MEM_WORD      ; Вычесть из регистра содержимое ячейки памяти

SUB MEM_WORD[BX],AХ   ; или наоборот

SUB AL,10                               ; Вычесть константу из регистра

SUB MEM_BYTE,OFH      ; или из ячейки памяти

Нельзя непосредственно вычесть значение одной ячейки из другой или использовать непосредственное значение как приемник.

Команды SUB и SBB могут воздействовать на шесть флагов следующим образом:

Флаг переноса CF равен 1, если требуется заем; в противном случае он равен 0.

Флаг четности PF равен 1, если результат вычитания имеет четное число битов со значением 1; в противном случае он равен 0.

Вспомогательный флаг переноса AF равен 1, если результат вычитания десятичных чисел требует коррекции; в противном случае он равен 0.

Флаг нуля ZF равен 1, если результат равен 0; в противном случае он равен 0.

Флаг знака SF равен 1, если результат отрицателен (старший бит равен 1); в противном случае он равен 0.

Флаг переполнения OF равен 1, если при вычитании чисел, имеющих разные знаки, результат превышает диапазон значений приемника в обратном коде, а сам приемник изменяет знак; в противном случае флаг OF равен 0.

Флаги SF и OF имеют смысл только при вычитании чисел со знаком, а флаг AF – только при вычитании десятичных чисел.

 Коррекция результата вычитания для представления в кодах ASCII и в упакованном десятичном формате (команды AAS и DAS).

При вычитании, как и при сложении, микропроцессор 8x86 рассматривает операнды как двоичные числа. Поэтому вычитание чисел, представленных в двоично-десятичном коде (BCD-чисел), может привести к неправильным результатам. Предположим, например, что надо вычесть BCD-число 26 из BCD-числа 55. Микропроцессор 8x86 выполнит двоичное вычитание следующим образом: дополнит до двух двоично-десятичное представление числа 26, а затем выполнит сложение:

        0101 0101 (BCD-число 55)

+ 1101 1010 (дополнение до двух BCD-числа 26)

     1 0010 1111 (??).

Вместо правильного значения (BCD-числа 29) мы получили результат, у которого старшая цифра 2, младшая цифра - шестнадцатеричная цифра F, и при этом бит переноса равен 1. Конечно, этот результат требует коррекции.

Коррекция результата вычитания двух десятичных чисел осуществляется командами AAS (ASCII adjust for substraction - скорректировать вычитание для представления в кодах ASCII) и DAS (Decimal adjust for substraction - скорректировать вычитание для представления в десятичной форме). При их исполнении предполагается, что корректируемое число находится в регистре AL.

Команда AAS преобразует содержимое регистра AL в правильную неупакованную десятичную цифру в младших четырех битах регистра AL (и обнуляет старшие четыре бита). Она используется в следующем контексте:

SUB AL,BL    ; Вычесть BCD-число (содержимое BL) из AL 

AAS               ; и преобразовать результат в неупакованное число

Если результат превышает 9, то команда AAS вычитает 1 из содержимого регистра АН и полагает флаг CF равным 1, в противном случае она обнуляет флаг CF. Кроме того, команда AAS изменяет состояние флага AF и оставляет значения флагов PF, ZF, SF и OF неопределенными. Но так как в данном случае только флаг CF имеет смысл, то считайте значения остальных флагов уничтоженными.

Команда DAS преобразует содержимое регистра AL в две правильные упакованные десятичные цифры. Она используется в следующем контексте:

SUB AL,BL     ;Вычесть упакованное BCD-число(содержимое) BL из AL

DAS                ;и преобразовать результат в упакованное число

Если результат превышает предельное значение для упакованных BCD-чисел (99), то команда DAS вычитает 1 из содержимого регистра АН и полагает флаг CF равным 1; в противном случае она обнуляет флаг CF. Кроме того, команда DAS изменяет состояния флагов PF, AF, ZF и SF, а значение флага OF оставляет неопределенным. Но так как в данном случае только флаг CF имеет смысл, то считайте остальные упомянутые флаги уничтоженными.

Команда уменьшения содержимого приемника на единицу DEC

Команда DEC (decrement - уменьшить) вычитает 1 из содержимого регистра или ячейки памяти, но при этом (в отличие от команды SUB) не воздействует на флаг переноса CF. Команда DEC часто используется в циклах для уменьшения значения счетчика до тех пор, пока оно не станет нулевым или отрицательным. Ее можно использовать также для уменьшения значения индексного регистра или указателя при доступе к последовательно расположенным ячейкам памяти.

Приведем несколько примеров:

DEC CX                                ;Уменьшить знамение 16-битового

DEC AL                                ; или 8-битового регистра

DEC MEM_BYTE               ;Уменьшить значение байта

DEC MEM_WORD[BX]    ;или слова памяти

Команда обращения знака NEG.

Команда NEG вычитает значение операнда-приемника из нулевого значения и тем самым формирует его дополнение до двух. Команда NEG оказывает на флаг то же действие, что и команда SUB. Но поскольку один из операндов равен 0, то можно точнее описать условия изменения состояний флагов. Итак, при исполнении команды NEG флаги изменяются следующим образом:

Флаг переноса CF и флаг знака SF равны 1, если операнд представляет собой ненулевое положительное число; в противном случае они равны 0.

Флаг четности PF равен 1, если результат имеет четное число битов, равных 1; в противном случае он равен 0.

Флаг нуля ZF равен 1, если операнд равен 0; в противном случае он равен 0.

Флаг переполнения OF равен 1, если операнд-байт имеет значение 80Н или операнд-слово имеет значение 8000Н; в противном случае он равен 0.

Команда NEG полезна для вычитания значения регистра или ячейки памяти из непосредственного значения. Например, Вам нужно вычесть значение регистра AL из 100. Так как непосредственное значение не может служить приемником, то команда SUB 100, AL недопустима. В качестве альтернативы можно обратить знак содержимого регистра AL и добавить к нему 100:

NEG AL 

ADD AL,100

Команда расширения знака.

Существуют две команды, позволяющие выполнять операции над смешанными данными за счет удвоения размера операнда со знаком. Команда CBW (convert byte to word — преобразовать байт в слово) воспроизводит 7-й бит регистра AL во всех битах регистра AH.

Команда CWD (convert word to double word — преобразовать слово в двойное слово) воспроизводит 15-й бит регистра AX во всех битах регистра DX.

Таким образом, команда CBW позволяет сложить байт и слово, вычесть слово из байта и т. д. Аналогично, команда CWD позволяет разделить слово на слово.

Приведем несколько примеров:

CBW   ;Сложить байт в AL со словом в BX

ADD AX,BX

CBW   ;Умножить байт в AL на слово в BX

IMUL BX

CWD   ;Разделить слово в AX на слово в BX

IDIV BX

Команды умножения чисел без знака MUL и целого умножения чисел со знаком IMUL.

Команда MUL (multiply – умножить) умножает числа без знака, a IMUL (integer multiply – умножить целые числа) – числа со знаком. Обе команды могут умножать как байты, так и слова.

Эти команды имеют формат:

MUL источник

IMUL источник

где источник - регистр общего назначения или ячейка памяти размером в байт или слово. В качестве второго операнда команды MUL и IMUL используют содержимое регистра AL (при операциях над байтами) или регистра АХ (при операциях над словами). Произведение имеет двойной размер и возвращается следующим образом:

Умножение байтов возвращает 16-битовое произведение в регистрах АН (старший байт) и AL (младший байт).

Умножение слов возвращает 32-битовое произведение в регистрах DX (старшее слово) и АХ (младшее слово).

По завершении исполнения этих команд флаги переноса CF и переполнения OF показывают, какая часть произведения существенна для последующих операций. После исполнения команды MUL флаги CF и OF равны 0, если старшая половина произведения равна 0; в противном случае оба этих флага равны 1. После исполнения команды IMUL флаги CF и OF равны 0, если старшая половина произведения представляет собой лишь расширение знака младшей половины. В противном случае они равны 1.

Приведем несколько примеров умножения:

MUL ВХ                       ;Умножить ВХ на АХ без знака

MUL MEM_BYTE.      ;Умножить содержимое ячейки памяти на AL без знака

IMUL DL                      ;Умножить DL на AL со знаком

IMUL MEM WORD    ;Умножить содержимое ячейки памяти на АХ со знакам

Команды MUL и IMUL не позволяют в качестве операнда использовать непосредственное значение. Такое значение-перед умножением надо, загрузить в регистр или в ячейку памяти. Например, в результате исполнения команд

MOV  DX,10

MUL  DX

содержимое регистра АХ будет умножено на 10.

Коррекция результатов умножения для представления в кодах ASCII (команда AAM)

Команда ААМ (ASCII adjust for multiplication - скорректировать умножение для представления в кодах ASCII) преобразует результат предшествующего умножения байтов в два правильных неупакованных десятичных операнда. Она считает, что произведение двойного размера находится в регистрах АН и AL, и возвращает неупакованные операнды в регистрах АН и AL. Чтобы команда ААМ работала правильно, исходные множимое и множитель должны быть правильными неупакованными байтами.

Для выполнения преобразования команда ААМ делит значение регистра AL на 10 и запоминает частное и остаток в регистрах АН и AL соответственно. Кроме того, она модифицирует флаг четности. PF, флаг нуля ZF и флаг знака SF в зависимости от полученного значения регистра AL. Состояние флага переноса CF, вспомогательного флага AF и флага переполнения становятся неопределенными.

Рассмотрим действие команды 'ААМ на примере. Пусть регистр AL содержит 9 (0000 1001В), а регистр BL — 7 (0000 0111В). Команда

MUL BL

умножит значение регистра AL на значение регистра BL и возвратит 16-битовый результат в регистрах АН и AL. В нашем случае она возвратит 0 в регистре АН и 00111111В (десятичное 63) в регистре AL. Следующая за ней команда

AAM

поделит значение регистра AL на 10 и возвратит частное 0000 0110В в регистре АН, а остаток 0000 0011В в регистре AL. Тем самым мы получаем правильный результат: BCD-число 63 в неупакованном формате. У микропроцессора 8x86 нет команды умножения упакованных десятичных чисел. Для выполнения этой операции сначала распакуйте эти числа, перемножьте их и воспользуйтесь командой ААМ, а затем упакуйте результат.

Команда деления числа без знака DIV и деления числа со знаком IDIV

Имея две отдельные команды умножения, микропроцессор 8x86 имеет и две отдельные команды деления. Команда DIV (divide - разделить) выполняет деление чисел без знака, а команда IDIV (integer divide - разделить целые числа) выполняет деление чисел со знаком. Эти команды имеют формат

DIV источник

IDIV источник

где источник - делитель размером в байт или слово, находящийся в регистре общего назначения или в ячейке памяти. Делимое должно иметь двойной размер; оно извлекается из регистров АН и AL (при делении на 8-битовое число) или из регистров DX и АХ (при делении на 16-битовое число). Результаты возвращаются следующим образом:

Если операнд-источник представляет собой байт, то частное возвращается в регистре AL, а остаток в регистре АН.

Если операнд-источник представляет собой слово, то частное возвращается в регистре АХ, а остаток - в регистре DX.

Обе команды оставляют состояние флагов неопределенными, но если частное не помещается в регистре-приемнике (AL или АХ), то микропроцессор 8x86 сообщает Вам об этом весьма драматическим образом: он генерирует прерывание типа 0 (деление на 0).

Переполнение результата деления возникает при следующих условиях:

1. Делитель равен 0.

2. При делении байтов без знака делимое по меньшей мере в 256 раз превышает делитель.

3. При делении слов без знака делимое по меньшей мере в 65 536 раз превышает делитель.

4. При делении байтов со знаком частное лежит вне диапазона от -128 до +127.

5. При делении слов со знаком частное лежит вне диапазона от -32768 до 32767. Приведем несколько типичных примеров операций деления:

DIV BX                        ;Разделить DX:AX на ВХ , без знака

DIV MEM_BYTE        ;Разделить AH:AL на байт памяти, без знака

IDIV DL                       ;Разделить АН:AL на DL со знаком

IDIV MEM_WORD    ;Разделить DX:AX на слово памяти, со знаком

Команды DIV и IDIV не позволяют прямо разделить на непосредственное значение; его надо предварительно загрузить в регистр или ячейку памяти. Например, команды

MOV  ВХ,20

DIV  ВХ

разделят объединенное содержимое регистров DX и АХ на 20.

Команда коррекции деления для представления в кодах ASCII (команда AAO)

Все ранее описанные команды десятичной коррекции (ААА, DAA, AAS, DAS и ААМ) выполняли действия над результатом операции. В противоположность им команда AAD (ASCII adjust for division - скорректировать деление для представления в кодах ASCII) должна исполняться непосредственно перед операцией деления.

Команда ADD преобразует неупакованное делимое в двоичное значение и загружает его в регистр AL. Для этого она умножает старшую цифру делимого (содержимое регистра АН) на 10 и добавляет полученный результат к младшей цифре, находящейся в регистре AL. Затем она обнуляет содержимое регистра АН.

Приведем типичный пример применения команды AAD:

AAD            ; Скорректировать неупакованное делимое в АН:АL,

DIV BL         ; а затем выполнить деление

Задание

Организовать два массива по 5 элементов каждый. Выполнить поэлементно операции сложения, умножения, вычитания и деления. Каждый раз результат выводить в новый массив.

Контрольные вопросы

  1.  Формат и правила применения команд ADD и ADC.
  2.  Воздействие на флаги команд ADD и ADC.
  3.  Формат и правила применения команд SUB и SBB.
  4.  Воздействие на флаги команд SUB и SBB.
  5.  Команды обращения знака и расширения знака.
  6.  Формат и правила применения команд MUL и IMUL.
  7.  Воздействие на флаги команд MUL и IMUL.
  8.  Формат и правила применения команд DIV и IDIV.
  9.  Воздействие на флаги команд DIV и IDIV.
  10.  Правила применения команд AAO и AAM.

Лабораторная работа №3 «Работа со строками»

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

Теоретическая часть

В ассемблере под строкой понимается последовательность соседних байтов или слов.  В связи с этим  все строковые команды имеют две разновидности - для работы со строками из байтов (в мнемонику операций входит буква B) и для работы со строками из слов (в мнемонику входит W).

Команды обработки строк предоставляют возможности выполнения пяти основных операций, называемых примитивами, которые обрабатывают строку по одному элементу (байту или слову) за прием (таблица 1). Эти примитивы (пересылка, сравнение/сканирование, загрузка и сохранение) представлены в таблице. Каждый примитив представлен тремя разными командами. В первом формате обработка байт или слов определяется неявно типом операнда (операндов). Второй и третий форматы явно указывают операцию над байтами или словами.

Микропроцессор х86 может исполнять только те команды обработки строк, которые не имеют операндов. При трансляции программы Ассемблер всегда преобразует команду с операндами в одну из команд без операндов. При этом микропроцессор предполагает, что строка-приемник находится в дополнительном сегменте, а строка - источник - в сегменте  данных. Процессор адресует  строку-приемник через регистр  DI, а строку-источник - через  регистр SI.- Так как команды манипулирования строками предназначены для действий над группой элементов,  то они автоматически модифицируют указатели для адресации следующего элемента строки. Бит  флага направления DF в регистре флагов  определяет, будут  значения  регистров SI и DI увеличены  или уменьшены по  завершении  команды манипулирования.  Если DF=O, то значения регистров SI и DI увеличиваются после исполнения каждой команды; DF=1 - значения уменьшаются.

Состоянием флага DF можно управлять с помощью команды CLD -сбросить флаг направления,  которая обнуляет его, и команды STD -установить флаг направления, которая устанавливает DF=1.

Можно сделать так, чтобы одна команда обработки строк обрабатывала группу последовательных элементов  памяти. Для этого перед  ней надо указать префикс повторения. Число повторений навлекается из регистра СХ.

     Пример:

     MOV  СХ, 500                                   ;  команда будет повторена 500 paз.

     REP MOVS DEST, SOURCE

Остальные префиксы  используют при "решении" о продолжении или прекращении повторении  флаг нуля ZF. Следовательно, они применимы только к командам сравнения строк и поиска значения в строке, которые   воздействуют на флаг ZF. Префикс REPE (повторять, пока не равно), имеющий синоним  REPZ (повторять, пока не нуль), повторяет команду,  пока  флаг ZP равен 1 и значение CХ не равно 0.

Команды обработки строк     Таблица 1.

МНЕМОКОД

ФОРМАТ

ФЛАГИ

Префиксы

повторения

REP

REPE / REPZ

REPNE / REPNZ

REP

REPE

REPNE

OF DF IF TF SF ZF AF PF CF

--   --   --   --   --   --   --   --   --

--   --   --   --   --   --   --   --   --

--   --   --   --   --   --   --   --   --

Пересылка

MOVS

MOVSB

MOVSW

MOVS строка-приемник ,

строка-источник

MOVSB

MOVSW

--   --   --   --   --   --   --   --   --

--   --   --   --   --   --   --   --   --

--   --   --   --   --   --   --   --   --

Сравнение

CMPS

CMPSB

CMPSW

CMPS строка-приемник ,

строка-источник

CMPSB

CMPSW

+   --   --   --   +    +   +    +   +

+   --   --   --   +    +   +    +   +

+   --   --   --   +    +   +    +   +

Сканирование

SCAS

SCASB

SCASW

SCAS строка-приемник

SCASB

SCASW

+   --   --   --   +    +   +    +   +

+   --   --   --   +    +   +    +   +

+   --   --   --   +    +   +    +   +

Загрузка и

сохранение

LODS

LODSB

LODSW

STOS

STOSB

STOSW

LODS строка-приемник

LODSB

LODSW

STOS строка-приемник

STOSB

STOSW

--   --   --   --   --   --   --   --   --

--   --   --   --   --   --   --   --   --

--   --   --   --   --   --   --   --   --

--   --   --   --   --   --   --   --   --

--   --   --   --   --   --   --   --   --

--   --   --   --   --   --   --   --   --

     -- означает сохранение флага, + - его изменение.

     Пример:                                      .

     MOV СХ 100                                     ;сравниваются элементы строк до тех пор,

      REPNE CMPS   DEST, SOURCE        ;пока соответствующие элементы не равны; либо пока не

                                                                   ;просматриваются 100 элементов.

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

      Пример:                       

      LEA SI,ES:HERE      ; копируем байт из строки HERE в строку THERE,

      LEA DI,ES;THERE   ; обе  строки  находятся в дополнительном  сегменте.

      MOVSB

Чтобы скопировать строки в сегменте данных, надо загрузить в регистр дополнительного сегмента ES значение, равное содержимому регистра данных DS, после этого при исполнении команды MOVS микропроцессор будет считать, что копирует (как обычно) строку из сегмента данных в дополнительный сегмент.

Задание

  1.  Написать программу  формирования  сжатой строки символов. Сжатие заключается в удалении пробелов из  исходной  строки  при просмотре ее слева направо.
  2.  Написать программу формирования сжатой строки символов. Сжатие заключается в удалении пробелов из исходной строки при просмотре ее справа налево.
  3.  Написать  программу выделения из исходной строки подстроки символов заданной длины с указанного номера позиции.
  4.  Написать программу, определяющую номер позиции, с которой  начинается  первое слева вхождение заданной конфигурации  символов в исходную строку.
  5.  Написать программу формирования строки из исходной путем заданного числа повторений исходной строки.
  6.  Написать программу, выполняющую следующую функцию. Заданы  две строки. Проверить вхождение каждого символа строки 1 в строку 2. Если какой-либо (первый слева) символ строки 1 не представлен в строке 2, то фиксируется номер позиции этого символа в строке 1.
  7.  Написать  программу, которая  бы  инвертировала  исходную строку.
  8.  Написать  программу, находящую максимальный и минимальный символы в исходной строке.
  9.  Написать программу, заменяющую все десятичные цифры  в исходной строке на заданный символ.
  10.  Написать программу, удаляющую из исходной строки повторные  вхождения  заданного символа.
  11.  Написать программу, удаляющую пробелы в конце исходной строки.
  12.  Написать программу, удаляющую из исходной строки заданную конфигурацию символов.

Контрольные вопросы

  1.  Примитивы. 
  2.  Префиксы повторения. 
  3.  Команды пересылки строк.
  4.  Замена сегмента (префикс замены сегмента).
  5.  Команды сравнения строк.
  6.  Команды сканирования строк.
  7.  Команды загрузки и сохранения строки.
  8.  Команды условной передачи управления.

Лабораторная работа №4 «Написание собственного обработчика прерывания»

Цель работы: ознакомиться с системой обработки прерываний микропроцессора в защищенном режиме. Научиться создавать собственные обработчики аппаратных прерываний.

Теоретическая часть

Ключевыми элементами обработки прерываний в защищенном режиме являются таблица дескрипторов прерываний (IDT – Interrupt Descriptor Table) и связанный с ней системный 48-разрядный регистр idtr. В старших 32 битах этого регистра содержится базовый адрес таблицы IDT в пространстве линейных адресов. В младших 16 битах содержится лимит таблицы IDT. При возникновении прерывания от источника с номером N микропроцессор, находясь в защищенном режиме, выполняет следующие действия (в самом общем виде):

  1.  Определяет местонахождение таблицы IDT, адрес и размер которой содержатся в регистре idtr.
    1.  Складывает значение адреса, по которому размещена таблица IDT, и значение N8. По данному смещению в таблице IDT должен находиться 8-байтовый дескриптор, определяющий местоположение процедуры обработки прерывания.
      1.  Переключается на процедуру обработки прерывания.

Прерывания и исключения в защищенном режиме можно разделить на три группы – сбои (fault), ловушки (trap), аварийные завершения (abort).

Сбой (ошибка, отказ) – прерывание или исключение, при возникновении которого в стек записываются содержимое регистров cs:eip/ip, указывающее на команду, вызвавшую данное прерывание. Это позволяет, получив доступ к сегменту кода, исправить ошибочную команду в обработчике прерывания и, вернув управление программе, повторно выполнить команду, вызвавшую ошибку.

Ловушка – прерывание или исключение, при возникновении которого в стек записываются содержимое регистров cs:eip/ip, указывающее на команду, следующую за командой, вызвавшей данное прерывание. Так же как и в случае ошибок, возможен рестарт команды. Для этого необходимо лишь исправить в обработчике прерывания соответствующие код или данные, послужившие источником ошибки. После этого перед возвратом управления нужно скорректировать значение eip/ip в стеке на длину команды, вызвавшей данное прерывание. Механизм ловушек похож на механизм прерываний, но не во всем. Если прерывание типа ловушки возникло в команде передачи управления, то содержимое пары cs:eip/ip в стеке будет отражать результат выполнения этой команды.

Аварийное завершение – прерывание, при котором информация о месте его возникновения недоступна или не полна, и поэтому рестарт практически невозможен, если только данная ситуация не была запланирована заранее.

Микропроцессор жестко определяет, какие прерывания являются ошибками, какие ловушками и авариями, и соответствующие им обработчики будут отличаться алгоритмами работы. Некоторые прерывания при своем возникновении дополнительно генерируют и записывают в стек так называемый код ошибки (EC – Error Code). Этот код в дальнейшем может быть использован для установления источника прерывания. Код ошибки, если он генерируется, записывается в стек вслед за содержимым регистров eflags, cs и eip.

Таблица IDT представляет совокупность 8-байтовых дескрипторов. Ее отличия от таблиц локальных и глобальных дескрипторов (LDT – Local Descriptor Table и GDT – Global Descriptor Table) заключается в следующем:

1) нулевой дескриптор, в отличие от таблицы GDT, используется; он описывает шлюз для программы обработки исключительной ситуации 0 (ошибка деления);

2) дескрипторы в таблицы IDT строго упорядочены в соответствии с номерами прерываний;

3) размерность таблицы IDT – не более 256 элементов размером по восемь байт, по числу возможных источников прерываний. В отдельных случаях есть смысл описывать все 256 дескрипторов этой таблицы, формируя для неиспользуемых номеров прерываний шлюзы-заглушки. Это позволяет корректно обрабатывать все прерывания, даже если они не планируются к использованию в данной задаче. Если этого не сделать, то при незапланированном прерывании с номером, превышающим пределы IDT для данной задачи, будет возникать исключительная ситуация с номером 13 нарушения общей защиты - #GP (General Protection);

4) при обработке запросов на прерывания микропроцессор всегда определяет местоположение таблицы IDT по полю базового адреса в регистре idtr, и это он делает независимо от режима, в котором работает.

В реальном режиме содержимое поля базового адреса в регистре idtr равно нулю, поэтому работа с таблицами векторов прерываний выполняется без проблем. Из всего этого следует, что возможно произвольное размещение в памяти этой таблицы не только в защищенном, но и в реальном режиме.

Дескрипторы в таблице IDT обычно называются шлюзами. Шлюзы предназначены для указания точки входа в программу обработки прерывания. Формат шлюза изображен на рис. 1.

Рис.1. Структура дескриптора таблицы IDT

Бит P (Present) – бит присутствия. Если P=1, то сегмент, на который указывает данный шлюз, присутствует в оперативной памяти; P=0 сегмент находится на диске.

Двухбитовое поле DPL (Descriptor Privilege Level) – поле уровня привилегий дескриптора.

Поле TYPE определяет тип шлюза:

0100 – 16-разрядный шлюз вызова (Call Gate -286);

0101 – шлюз задачи 16- или 32-разрядной задачи;

0110 – 16-разрядный шлюз прерывания (Interrupt Gate -286);

0111 – 16-разрядный шлюз ловушки (Trap Gate -286);

1100 – 32-разрядный шлюз вызова (Call Gate -386+);

1101 – зарезервирован;

1110 – 32-разрядный шлюз прерывания (Interrupt Gate-386+);

1111 – 32-разрядный шлюз ловушки (Trap Gate-386+);

Поле Word Count используется только в шлюзах вызовов и определяет число слов, передаваемых из стека вызывающего процесса, автоматически копируемых в стек вызываемой процедуры.

Поле Destination Selector для шлюзов вызова, прерываний и ловушек задает селектор целевого сегмента кода, а для шлюза задачи – селектор целевого сегмента состояния задачи (TSS – Task Status Segment).

Поле Destination Offset задает смещение (адрес) точки входа в целевом сегменте.

При возникновении прерывания, которому соответствует шлюз прерывания, микропроцессор выполняет те же действия, что и для шлюза ловушки, но с одним отличием. Когда микропроцессор передает управление обработчику прерывания через шлюз прерывания, то он сбрасывает флаг прерывания в регистре eflags в ноль, запрещая тем самым обработку аппаратных прерываний.15

Когда микропроцессор при выполнении прерывания встречает в таблице IDT шлюз задачи, то это означает, что он должен осуществить переход на особый объект – задачу, которая будет осуществлять обработку прерывания. При этом состояние старой задачи, т.е. содержимое всех, связанных с этой задачей регистров сохраняется в сегменте состояния задачи – TSS. В общем случае, для того чтобы сделать возможным обработку прерываний в защищенном режиме, необходимо выполнить следующие действия:

1) инициализировать таблицу IDT;

2) составить процедуры обработчиков прерываний;

3) запретить аппаратные прерывания;

4) перепрограммировать контроллер прерываний i8259A;

5) загрузить регистр idtr адресом и размером таблицы IDT;

6) перейти в защищенный режим;

7) разрешить обработку прерываний

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

1) запретить обработку аппаратных прерываний;

2) выполнить обратное перепрограммирование контроллера прерываний;

3) перейти в реальный режим работы микропроцессора;

4) разрешить обработку аппаратных прерываний.

Для формирования таблицы IDT в целом, необходимо определиться с написанием отдельных ее дескрипторов – шлюзов. Для описания дескриптора таблицы IDT можно использовать шаблон в виде структуры:

Descr_idt struc

offs_1 dw 0; - младшая часть адреса смещения обработчика прерывания

sel dw 30h ; селектор на сегмент команд в таблице GDT

no_use db 0

type_attr db 8eh; по умолчанию шлюз прерывания

offs_2 dw 0 ; старшая часть адреса смещения обработчика прерывания

Ends

После описания дескриптора таблицы IDT можно приступать к формированию самой таблицы. Это можно делать как в отдельном сегменте, так и в сегменте данных. Но лучше, чтобы не загромождать сегмент данных, оформлять таблицу IDT в виде отдельного сегмента.

idt_seg SEGMENTpara public 'data' use16

int00h descr_idt <dummy,,,,>

REPT 2

descr_idt <dummy,,,,>

ENDM

int03h descr_idt <int_03h,,,,>

descr_idt <dummy,,,,>16

int05h descr_idt <int_05h,,,,>

REPT 7

descr_idt <dummy_err,,,,>

ENDM

int0dh descr_idt <int_0dh,,,,>

REPT 3

descr_idt <dummy,,,,>

ENDM

int11h descr_idt <dummy_err,,,,>

REPT 14

descr_idt <dummy,,,,>

ENDM

int20h descr_idt <new_08h,,,,>

int21h descr_idt <sirena,38h,,,>

REPT 222

descr_idt <dummy,,,,>

ENDM

idt_size=$-int00h-1

idt_seg ENDS

В этом сегменте полностью описана таблица дескрипторов прерываний. При этом все дескрипторы инициализированы именем процедур обработки прерываний. Основная часть дескрипторов имеет в качестве обработчика процедуру dummy, т.е. они будут обрабатываться одной программой. Для некоторых прерываний введены уникальные имена; эти прерывания должны иметь свои процедуры обработки в программе.

Расположение и порядок следования процедур обработки прерывания в памяти может быть произвольным. Обязательным является только помещение их адресов в соответствующие дескрипторы. Аналогично таблице GDT, нужно сообщить микропроцессору, где располагается таблица IDT. Загрузка регистра idtr осуществляется по команде lidt. Более подробную информацию об обработке прерываний в защищенном режиме можно получить в [1-3].

Задание

Перейти в защищенный режим работы микропроцессора и в соответствии с номером варианта задания написать программу. При каждом вызове процедуры обработчика запроса на обслуживание прерывания обрабатывать только один элемент массива или выводить один элемент числовой последовательности.

1. Перехватив прерывание от таймера, вывести на экран последовательность квадратов натуральных чисел. Заполнение прекратить, когда числа выйдут за пределы 16-разрядной сетки.

2. Перехватив прерывание от таймера, вывести на экран последовательность чисел Фибоначчи 1,1,2,3,5,8,13,21… . Заполнение прекратить, когда числа выйдут за пределы 16-разрядной сетки.

3. Перехватив прерывание от таймера, вывести последовательность чисел 0,1,2,…,255 на экран.

4. Дан массив A из 10 однобайтовых чисел. Перехватив прерывание от таймера, вывести на экран со сдвигом на один разряд влево только числа, содержащие четное количество единиц.

5. Дан массив A из 10 однобайтовых чисел. Перехватив прерывание от таймера, вывести на экран со сдвигом на один разряд вправо только числа, содержащие нечетное количество единиц.

6. Дан массив A из 10 однобайтных чисел. Перехватив прерывание от таймера, вывести из массива А на экран все числа, большие 05h и меньшие 20h.

7. Перехватив прерывание от таймера, вывести на экран последовательность степеней числа 2. Вывод прекратить, когда числа выйдут за пределы 16-разрядной сетки.

8. Дан массив A из 10 однобайтовых чисел. Перехватив прерывание от таймера, проверить есть ли в нем число 10h, если оно есть, выполнить сложение всех чисел массива А и вывести сумму на экран.

9. Дан массив A из 10 однобайтовых чисел. Перехватив прерывание от таймера, вывести на экран все числа большие 20h и подсчитать сумму таких чисел и также вывести ее на экран.

10. Перехватив прерывание от таймера, вывести на экран последовательность степеней числа 3. Вывод прекратить, когда числа выйдут за пределы 16-разрядной сетки.

11. Перехватив прерывания от таймера, вывести последовательность факториалов натуральных чисел. Заполнение прекратить, когда числа выйдут за пределы 16-разрядной сетки.

12. Перехватив прерывание от таймера, вывести на экран члены следующей последовательности 1+2+3+…+n, прекратить, когда числа выйдут за пределы 16-разрядной сетки.

Контрольные вопросы

1. Что такое прерывание?

2. Вектор прерывания.

3. Прерывание типа 21.

4. Функции для работы с клавиатурой.

5. Функции для работы с дисплеем.

Лабораторная работа №5 «Связь подпрограмм на Ассемблере с программами

на языке высокого уровня» 

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

Теоретическая часть

Существуют следующие формы комбинирования программ на языках высокого уровня (ЯВУ) с программами на языке Ассемблера:

  •  на этапе компиляции — вставка в программу ассемблерных фрагментов (англ. inline assembler) с помощью специальных директив языка (в частности, данный способ поддерживается языком программирования Си), в том числе написание функций на языке ассемблера. Способ удобен для несложных преобразований данных, но полноценного ассемблерного кода, с данными и подпрограммами, включая подпрограммы с множеством входов и выходов, не поддерживаемых высокоуровневыми языками, с помощью него сделать нельзя.
  •  на этапе компоновки, или раздельной компиляции. Для взаимодействия скомпонованных модулей достаточно, чтобы связующие функции (определённые в одних модулях и использующиеся в других) поддерживали нужные соглашения вызова (англ. calling conventions) и типы данных. Написанные же отдельные модули могут быть на любых языках, в том числе и на ассемблере.

Взаимодействие языков Си и Ассемблера

Первое требование, которое необходимо выполнить при совместной загрузке объектных файлов созданных Ассемблером и компилятором языка Си, состоит в том, что все эти объектные файлы должны иметь одинаковый формат, тот, который нужен загрузчику.

Другое требование к взаимодействию состоит в том, что функцию на языке Ассемблера надо кодировать так, чтобы выполнялись соглашения по вызову функций, используемые компилятором языка Си.

Чтобы программа на языке Си (вызывающая функция) могла вызвать подпрограмму на языке Ассемблера, обменяться с ней данными, и соответствующим образом получить управление обратно,  должны быть выполнены следующие шаги:

  1.  Программа должна сохранить адрес команды, с которой будет продолжено ее исполнение после завершения вызова подпрограммы. Затем программа передает управление подпрограмме.  Программе  и подпрограмме нужно придерживаться таких соглашений об использовании машинных регистров,  чтобы подпрограмма не уничтожала регистровых значений программы (иногда называемых средой).
  2.  После завершения подпрограмме необходимо возвратить управление по адресу, сохраненному программой.
  3.  Программа и подпрограмма должны придерживаться общих соглашений о передаче данных из программы в подпрограмму.
  4.  Должны быть установлены также соглашения о возвращении данных из подпрограммы в программу. Обычно для этого используется стек. Микропроцессор 8088 фирмы INTEL имеет отдельный регистр сегмента, называемый регистр сегмента стека SS.В дополнение к нему имеется регистр управления стека SP. При исполнении определенных команд этот регистр обеспечивает использование в качестве стека той области памяти, которая адресуется с помощью регистра SS.

Компилятор языка Си генерирует соответствующий код, обеспечивающий резервирование памяти для такого стека и присваивание адреса этой области регистрам SS и SP, в качестве начальных значений. Этот стек будем называть стеком времени исполнения. На IBM PC регистру SP в качестве начального значения присваивается адрес последней ячейки сегмента стека, содержимое регистра SP убывает при помещении объектов в стек.

Передача данных подпрограмме

Существуют два способа обмена данными между вызывающей функцией и подпрограммой на языке Ассемблера:

  •  использование глобальных данных;
  •  использование аргументов.

Использование глобальных данных

Подпрограмма на языке Ассемблера может иметь доступ к глобальным данным, определенным в модуле на языке Си с использованием внешнего класса хранения данных. В подпрограмме, необходимо определить сегмент данных и использовать для ссылки на внешние объекты псевдооператор EXTERN.

     Пример 1.  Подпрограмма изменяет значение внешнего объекта VAL, при этом внешние объекты записываются прописными буквами.

/* Содержание модуля TEST1.C */

extern  “Cvoid second ( );      /* объявить внешнюю подпрограмму */

int  val;                     /* определить внешний объект данных */

main ( )

{ second ( );

printf ("значение VAL = % d\n", val);

}

;Содержание модуля SECONDSМ

DGROUP GROUP DATA                                     ; сегмент данных подпрограммы

DATA SEGMENT WORD PUBLIC ‘DATA’        ; должен быть

ASSUME DS:DGROUP                      ; в том же сегменте,  что и данные программы на языке Си

; Псевдооператор GROUP служит для указания Ассемблеру на то, что этот сегмент надо поместить

; вместе в один сегмент с данными программы на Си.

EXTRN _VAL:WORD                        ;  объявить  _VAL как глобальный объект целого типа

DATA ENDS

PGROUP GROUP PROG                             ; сегмент команд надо поместить вместе с командами других модулей

       ; в один блок памяти размером 64 К

PROG SEGMENT WORD PUBLIC ‘PROG’

ASSUME CS: PGROUP

PUBLIC _SECOND                              ; сделать это имя глобальным

_SECOND PROC NEAR                                        ; малая модель распределения памяти

MOV _VAL, 4                                     ; модифицировать глобальную переменную

RET                                                                 ; вернуться в вызывающую программу

_SECOND ENDP                                                  ; конец программы

_TEXT ENDS                                                        ; конец сегмента

END                                                                     ; конец файла

Чтобы тестировать эту программу, надо откомпилировать модуль на языке Си,  откомпилировать подпрограмму с помощью Ассемблера, загрузить исполняемую программу и вызвать ее.

Использование аргументов функции

В языке Си аргументы  передаются по значению, т.е. вызываемая подпрограмма или функция получает копию каждого аргумента. Значения аргументов копируются в стек времени исполнения.

     Пример 2.

      а1, а2, аЗ - целые значения.

     Вызов third (al, а2, а3) преобразуется компилятором Си в последовательность команд МП 8088:

                PUSH аЗ 

                PUSH а2

                PUSH a1

                CALL THIRD

     Замечания

  1.  Это общепринятый подход, зависящий от реализации компилятора.
  2.  Аргументы помещаются в стек в порядке, обратном тому, в котором они указаны при вызове функции. Ячейка  с  адресом  возврата  имеет адрес [SP]. Копии аргументов  имеют адреса [SP]+2,  [SP]+4, [SP]+6.

Микропроцессоры семейства х86 не позволяют использовать регистр указателя стека в команде MOV для извлечения значений, находящихся по этим адресам. В ней следует использовать другой регистр указателя. Обычно для этих целей служит регистр ВР (регистр указателя базы). Значение этого регистра часто называют указателем фрейма. Поэтому одной из первых команд любой подпрограммы, которой необходимо адресоваться к аргументам, является помещение в регистр ВР значения (SP). Перед этим необходимо сохранить (ВР) в стеке, а затем воспользоваться регистром ВР в подпрограмме, а непосредственно перед возвратом в вызывающую функцию нужно восстановить исходное значение ВР.

     Пример 3.

     Рассмотрим подпрограмму, которая получает три целых аргумента, суммирует их, а затем возвращает полученный результат через внешнюю переменную VAL:

Организация доступа к аргументам функции: 

/* Содержание модуля TEST2.C */        

            extern  “C” void sum( );

              int val;

            main ( )

           {   int x = 10 ;

                sum  (x, 20, 20+5) ;

                printf ("успешное возвращение из подпрограммы  val = %d\n", val) ;

            }

  1.  Аргументами вызова могут быть идентификаторы, константа или выражения. Каждое выражение вычисляется, а его значение помещается в стек командой PUSH.  Однако порядок вычисления таких выражений не оговорен в Си.
  2.  В языке Си можно передавать такие объекты, как слово, символы, длинные целые значения, значения с плавающей точкой одинарной и двойной точности, а также (если это обеспечивается компилятором) целые структуры.

В этом случае размер части стека,  занимаемой каждым аргументом, зависит от его размера. Однако при передаче аргументов компиляторы Си выполняют некоторые упрощения:

  1.  объекты типов char, short, int занимают в стеке по одному слову; 
  2.  объекты типа long занимают по два слова;
  3.  объекты типа float, double передаются в формате объектов типа double и требуют в стеке по 4 слова;
  4.  если компилятор обеспечивает передачу структур по значению, то в стеке требуется столько байтов, чтобы в них поместились все элементы структуры. При этом число выделяемых байтов округляется до целого, кратного размеру слова;
  5.  для указателя требуется одно или два слова в зависимости от используемой модели распределения памяти.

Наряду с возвращением значений через глобальные объекты подпрограмма на языке Ассемблера может возвращать их через аргументы вызова или как значение подпрограммы. Чтобы вызывающая программа могла воспринять возвращаемую сумму как значение имени подпрограммы, нужно следующим образом модифицировать модуль TEST2.C (пример 3)

     /* содержание модуля TEST3.C*/

     extern  “C” void sum ( );

     main ( )

     { inl val, x = 10;            

             val = sum (x, 20, 20+5) ;

             printf (" Успешное возвращение из подпрограммы , val =% d\n , val) ;

     }

Вызывающая функция и подпрограмма должны придерживаться определенных соглашений относительно возвращения значения через имя подпрограммы. Многие компиляторы языка Си рассчитаны на то, что это значение запомнено в одном или нескольких регистрах.

Общепринятые соглашения по использованию этих ресурсов на IBM PC  показаны ниже в таблице. Чтобы в соответствии с этими соглашениями модифицировать подпрограмму sum, из программы примера 3 надо удалить ссылки на внешний объект VAL м оставить вычисленный ею результат в регистре АХ.

Замечание. Результат остался в AX, программа на Си, получив управление, извлечет этот результат из AX.

Регистры, используемые для возвращения значений

Тип возвращаемого значения

Используемые регистры

целый (16 битов)

длинный целый (32 бита)

вещественный с двойной точностью (64 бита)

AX

AX, BX или DX, AX

AX, BX, CX, DX

Примечание. Это соглашение может быть своим у каждого компилятора.

Использование аргументов

Если подпрограмма должна возвращать несколько значений, то она может использовать для этой цели свои аргументы. В этом случае передается адрес аргумента, т.е. передается аргумент по адресу.

Подпрограмму sum модифицируем:  теперь у нее четыре аргумента и результат будет возвращаться через последний из них.  Теперь её вызов имеет следующий вид:

                            sum (x, 20, 20+5, & val);

Вызов функций на языке Си из программ на языке Ассемблера

Чаще всего приходится вызывать подпрограммы на языке Ассемблера из функций на Си. Однако можно вызывать функции на языке Си из программ на языке Ассемблера. Для этого надо объявить имя функции на языке Си в программе на языке Ассемблера, используя псевдооператор EXTRN, который должен следовать за псевдооператором SESMENT.

Если имя функции на языке Си – myfunc, то для моделей распределения памяти с малыми кодами ее объявление, будет иметь вид:

                            EXTRN _MYFUNCT:NEAR

Чтобы вызвать функцию на языке Си, её каждый аргумент нужно поместить в стек, начиная с последнего аргумента, а затем вызвать функцию с помощью команды CALL. После возвращения из функции на языке Си программа на языке Ассемблера должна очистить стек, удалив из него все ранее помещенные аргументы. Для этого можно с помощью команды POP извлечь их один за другим. Но легче всего просто увеличить содержимое указателя стека на целое значение, .равное числу байтов, ранее помещенных в стек.

     Пример 4.      Вызов функции на языке Си из программы на языке Ассемблера.

     PGROUP      GROUP  PROG

     PROG  SEGMENT  WOPRD  PUBLIC  ‘PROG’

                            EXTERN _MYFUNCT: NEAR                   ; имя функции на Си

                            ASSUME CS:PGROUP

                            PUBLIC ASMFUNCT

ASMFUNCT  PROC NEAR    

    . . . . . . . . . .

     PUSH AX                   ; Предполагается, что значения аргументов

     PUSH  BX                  ;  находятся в регистрах АХ,  ВХ,  CХ

     PUSH СХ

     CALL _MYFUNCT     ;  Вызвать функцию

     ADD SP,6                   ; Очистить стек от значений аргументов

    . . . . . . . . . .

Если данные определены внутри программы на языке Ассемблера, то их можно сделать доступными функциям на языке Си, объявив их общедоступными с помощью псевдооператора PUBLIC в сегменте данных программы на языке Ассемблера и внешними с помощью служебного слова extern в функции на языке Си.

Использование локальных данных

Функции на языке Си используют стек времени исполнения и для других целей: они выделяют в нем память для автоматических и рабочих объектов данных. Это делается путем уменьшения содержимого указателя стека на число байтов, занимаемых объектом данного типа. Например, если в функции определены три целых объекта с классом хранения automatic, то после команды сохранения значения указателя базы при входе в функцию компилятор языка Си должен генерировать команду: SUB SP, 6. Эта команда выделит в стеке для последующего использования шесть байтов, доступ к которым можно организовать путем относительной адресации по регистру указателя базы. Если компилятор инициализирует регистр указателя базы до выделения памяти в стеке, то смещения адресов автоматических переменных по отношению к значению регистра указателя базы будет отрицательным. Перед возвращением в вызывающую программу надо очистить стек, восстановив содержимое указателя стека.

Пример. Выделение и освобождение памяти в стеке.

     Вариант 1

     PUSH BP         ; сохранить (ВР) при вызове

     MOV BP, SP   ; установить новое значение ВР

     SUB SP, n        ; выделить n байтов

     . . . . . . . . .

     MOV SP, BP   ; освободить память

     POP ВР           ; восстановить ВР

     RET

    Вариант 2

     PUSH  BP         ;  сохранить (BP) при вызове  

    SUB SP, n         ; выделить n байтов

    MOV BP, SP     ;  установить новое  (BP)

    . . . . . . . . .

    ADD SP, n        ; освободить память

    POP ВР           ; восстановить ВР

    RET

Этими же методами выделения рабочей памяти можно воспользоваться и в подпрограммах на языке Ассемблера.

Замечание. В первом варианте указатель базы устанавливается до выделения в стеке памяти для локальных данных, а во второмпосле выделения памяти. В первом варианте указатель базы продолжает показывать на ту ячейку стека, на которую показывал указатель стека до выделения пакета для локальных данных. Поэтому перед выполнением команды RET стек можно очистить от этих данных, присвоив указателю стека значение указателя базы.

Вызов функций операционной системы MS-DOS из программы на языке Си

В операционной системе MS-DOS предусмотрено много служебных функций, которыми можно воспользоваться в прикладной программе. Некоторые из них обеспечивают операции обмена данными с клавиатурой и экраном, диском и другими периферийными устройствами. Чтобы воспользоваться такой функцией, необходимо вызвать прерывание микропроцессора, которое передаст управление операционной системе MS-DOS. Многие компиляторы языка Си не обеспечивают непосредственное кодирование команды прерывания в программе на языке Си. В этом случае для доступа к операционной системе MS-DOS необходимо воспользоваться подпрограммой на языке Ассемблера. Некоторые компиляторы дают возможность указывать команду ASM, позволяющую осуществить прямое кодирование команд языка Ассемблера в программе на языке Си.

                  Задание

  1.  Найти всех соседей заданного символа в исходной строке. Первый и последний символ считать соседями.
  2.  Подсчитать количество символов, у которых равные соседи и исходной строке. Первый и последний символы считать соседями.
  3.  Переставить в обратном порядке все символы между первым и последним вхождением  заданного символа в исходной строке, если заданный символ встречается в строке не менее двух раз.
  4.  В исходную строку вставить после заданного символа все символы, предшествующие ему. Оставшуюся часть строки оставить без изменения.
  5.  В исходную строку вставить после заданного символа все символы, предшествующие заданному в обратном порядке. Оставшуюся часть строки оставить без изменения.
  6.  В последней строке символы, следующие за заданным символом, переписать в обратном порядке. 
  7.  Образовать строку, повторив  фрагмент  исходной строки с заданной позиции данной длины требуемое число раз.
  8.  Образовать строку из исходной, повторив i и элемент 1 раз, 1+1-й элемент   1+1 раз, 1+2 и элемент - 1.2 раза.
  9.  В исходной строке фрагмент с заданной позиции заданной длины повторить требуемое число раз. Остальные символы строки оставить без изменения.
  10.  Часть строки, следующую за первым вхождением заданного символа переписать в обратном порядке заданное число раз.
  11.  Часть строки, предшествующую первому вхождению заданного символа, переписать в обратном порядке заданное число раз.
  12.  В исходной строке указанное число символов, начиная с заданной позиции, переписать в конец строки.

Контрольные вопросы

  1.  Каким образом осуществляется взаимодействие языков Си и Ассемблера?
  2.  Как происходит передача управления в подпрограмму на Ассемблере и обратно?
  3.  Каким образом осуществляется возвращение значений из подпрограммы на Ассемблере?
  4.  Как осуществляется вызов функций на языке Си из программ на языке Ассемблера?
  5.  Каким образом используются локальные данные?

СПИСОК ЛИТЕРАТУРЫ

  1.  Assembler: Учебник для вузов. 2-е изд. Юров В. И., 2-е издание, 2010 год, 637с.
  2.  Assembler: Практикум. 2-е изд. Юров В. И., 2-е издание, 2007 год, 400 с.
  3.  Абель П. Язык ассемблера для IBM PC и программирования: Пер.с англ.-М.:Высшая шк.2006 г.- 336 с.
  4.  Пильщиков В.Н. Программирование на языке ассемблер.: Диалог-МИФИ, 2005 г. -288 с.
  5.  Пирогов В.Ю. Ассемблер и дезассемблирование. Издательство: БХВ-Петербург, 2006 г., 458 с.
  6.  Панов А.А. Ассемблер. Экспресс-курс. Издательство: БХВ-Петербург, 2006 г., 260 с.
  7.  Искусство программирования на ассемблере. Лекции и упражнения: Голубь Н.Г. – 2-е изд. испр. и доп. – СПб.: ООО «ДиаСофтЮП». 2002г., – 656с.


ПРИЛОЖЕНИЕ 1

РАБОТА С ОТЛАДЧИКОМ Turbo Debugger (TD)

В процессе отладки часто бывает так, что программа, успешно пройдя этап трансляции и компоновки, либо работает не так, как ожидалось, либо вообще не работает. Это означает, что с точки зрения правил языка программирования, программа написана правильно (в ней нет синтаксических ошибок), но алгоритм ее в чем-то неверен. Для отладки такой программы следует воспользоваться программой-отладчиком. Для этого необходимо загрузить программу под управлением отладчика, введя с клавиатуры в командной строке (TD prog.exe ). На экране появится информационное окно отладчика, показанное на рис.1.

Рис.1. Информационное окно отладчика TD

При работе с отладчиком возможны следующие действия:

  1.  В ответ на сообщение "Program has no symbol table" следует нажать клавишу <Enter>.
    1.  Для того, чтобы увеличить размер кадра до размеров экрана, необходимо нажать клавишу <F5>.
    2.  В режиме пошаговой отладки выполнить инициализацию программы и сегмента данных (<F7> или <F8> - выполнение одной команды).
    3.  Для перехода в зону данных следует воспользоваться клавишами <Tab> или <Shift>-<Tab>.
    4.  Нажать <Ctrl>-<G> и ввести в поле ввода начальный адрес интересуемой области памяти (сегмента данных) DS:0. В зоне появится содержимое сегмента данных.
    5.  Используя <Tab> или <Shift>-<Tab> , перейти в зону кода.
    6.  Выполнить программу в пошаговом режиме.
    7.  Для выхода из отладчика в MS-DOS, нажать <Alt>-<X>.
    8.  Если в процессе работы программа осуществляет вывод на экран, то просмотреть его можно, нажав на <Alt>-<F5>.


 

А также другие работы, которые могут Вас заинтересовать

67554. А-ПРЕДСТАВЛЕНИЕ КВАНТОВОЙ МЕХАНИКИ 642 KB
  Здесь предполагается, что спектр оператора - невырожденный. Если есть вырождение, то нужен еще один индекс, связанный с необходимостью введения по крайней мере еще одного оператора, коммутирующего с . Тогда строим базис из общих собственных векторов операторов и (см. лекцию 2):
67555. СООТНОШЕНИЯ НЕОПРЕДЕЛЕННОСТЕЙ 611.5 KB
  Зависимость от времени можно ввести в квантовую механику разными способами. До сих пор мы пользовались картиной Шредингера в которой считается что всю зависимость от времени несут векторы состояния волновые функции а в операторы наблюдаемых она может входить лишь в исключительных...
67556. ЗАКОНЫ СОХРАНЕНИЯ 488.5 KB
  В картине Шредингера затруднительно сразу сказать, что такое сохраняющаяся физическая величина, так как операторы наблюдаемых обычно вообще от времени не зависят. Приходится исхитряться (см. ниже). А в картине Гейзенберга все ясно.
67557. НОРМИРОВКА В НЕПРЕРЫВНОМ СПЕКТРЕ 299 KB
  Классическому инфинитному движению отвечают состояния с обобщенными волновыми функциями которые нельзя нормировать а энергетический спектр является непрерывным. Возникает проблема нормировки волновых функций непрерывного спектра. Реально же на самом деле спектр всегда является дискретным так как...
67558. ГАРМОНИЧЕСКИЙ ОСЦИЛЛЯТОР 773 KB
  Мы получили, что волновые функции стационарных состояний осциллятора являются или четными или нечетными. Оказывается, этот результат можно было предсказать заранее, не решая задачу. Сделаем в этой связи отступление, которое представляет и значительный самостоятельный интерес.
67559. КОГЕРЕНТНЫЕ СОСТОЯНИЯ 390.5 KB
  Доказательство основывается на математическом результате, что всякий эрмитов оператор с конечным следом (такие операторы называются ядерными) имеет чисто дискретный спектр. Ставим задачу на собственные значения...
67560. ОРБИТАЛЬНЫЙ МОМЕНТ ИМПУЛЬСА 637 KB
  Дальше мы намерены перейти к анализу движения частицы в центральном поле. Как и в классической физике, здесь очень важную роль играет момент импульса. Но в квантовой механике бывает два момента импульса - связанный с движением частицы и имеющий классический аналог, и не связанный с движением частицы...
67561. МАТРИЦЫ ОПЕРАТОРОВ МОМЕНТА ИМПУЛЬСА 738 KB
  Мы хотим найти матрицы спиновых операторов в явном виде. Для этого решим сначала более общую задачу - найдем матрицы операторов момента и, которые удовлетворяют коммутационным соотношениям...
67562. КВАЗИКЛАССИЧЕСКОЕ ПРИБЛИЖЕНИЕ 363 KB
  В квантовой механике уравнение Шредингера для сколько-нибудь реалистических систем невозможно решить точно, в квадратурах. Поэтому здесь создано большое количество приближенных методов исследования. Мощнейший из них - теорию возмущений - мы рассмотрим позже.