Вопрос № 121103: Помогите разобрать программу по блокам.Что происходит в этих блоках?Если можно по пунктах, потому что это моя экзаменацыонная программа, а у меня преподаватель спрашивает по каждой строчке.<p><fieldset style='background-color:#EFEFEF; width:80%; bord...Вопрос № 121126: Помогите разобрать программу по блокам.Что происходит в этих блоках?Если можно по пунктах, потому что это моя экзаменацыонная программа, а у меня
преподаватель спрашивает по каждой строчке.
Это уже последняя.Помогите завтра экзамен...
Вопрос № 121.103
Помогите разобрать программу по блокам.Что происходит в этих блоках?Если можно по пунктах, потому что это моя экзаменацыонная программа, а у меня преподаватель спрашивает по каждой строчке.
Отвечает: Евгений Иванов
Здравствуйте, Балковая Наталья Юриевна!
.model tiny - модель программы. tiny - это код и данные вместе в одном сегменте не более 64 Кб, вызовы ближние.
LOCALS @@ - использовать местные (локальные) переменные в подпрограммах, с префиксом @@ .code - объявление сегмента кода (упрощенная директива) .386 - использовать команды процессора не выше 386 при ассемблировании org 100h - адресовать код, начиная с адреса 256 start: - метка кода, называется start mov ax, 3 - вызов функции БИОС,
конкретно - установка видеорежима номер 3 int 10h
push num - записываем в стек число по адресу num lea dx, str_dec - записываем в регистр dx адрес переменной str_dec push dx - записывем его в стек CALL toDEC - вызываем подпрограмму toDEC, которая вероятно, будет преобразовывать число в строку mov ah, 9 - для того чтобы его вывести на экран данной функцией ДОС номер 9 int 21h
CALL crlf - вызов подпрограммы вероятно для р
исовки перевода строки
push num lea dx, str_bin push dx CALL toBIN mov ah, 9 int 21h - опять вызываем подпрограмму с двумя параметрами - здесь видимо рисовка двоичного представления числа CALL crlf
push num lea dx, str_hex push dx CALL toHEX mov ah, 9 int 21h int 20h - шестнадцатеричного представления числа выводим toDEC proc - объявление подпрограммы push bp - взятие стековых переменных - инициализация регистра BP для этого mov
bp, sp push ax - сохраняем регистры чтобы их не испортить во время работы подпрограммы push bx push cx push dx push di mov ax, [bp+6] - это собственно взятие переданной в подпрограмму переменной mov di, [bp+4] cld - сбрасываем флаг D для того чтобы указать что команды для работы со строками работали по увеличению xor cx, cx - очищаем регистр CX, cx=0 mov bx, 10 @@1: xor dx, dx div bx - делим
на 10 число в AX push dx - заносим остаток в регистре DX в стек (запоминаем) inc cx or ax, ax jnz @@1 - делим на 10 до тех пор, пока все число не поделим (пока не возьмем в стек все символы числа) @@2: pop ax add al, '0' stosb LOOP @@2 - собственно выводим число, прибавляя к каждому числу код нуля для превращения его в цифру, видимую на экране mov al, '$' stosb pop di - восстанавливаем регистры pop dx pop cx pop bx pop ax pop bp ret 4 - выходим
из подпрограммы с очисткой стека toDEC endp
toBIN proc push bp mov bp, sp push ax push bx push cx push di cld mov bx, [bp+6] mov di, [bp+4] mov cx, 16 - будем выводить 16 бит числа на экран в виде 0 и 1 @@1: mov al, '0' rol bx, 1 - сдвигаем младший бит в флаг C jnc @@2 - если бит установлен, увеличиваем 0 - делаем 1 inc al @@2: stosb - выводим в строку LOOP @@1 mov al, '$' stosb pop di pop cx pop bx pop ax pop bp ret 4 toBIN endp
toHEX proc push bp mov bp, sp push ax push bx push cx push di push si mov si, [bp+6] ; copy of number mov di, [bp+4] mov bx, 0F000h ; mask - готовим маску для взятия каждого блока бит по 4 mov cx, 12 ; shifts - количество сдвигов этого блока до границы младшего разряда cld @@1: mov ax, si and ax, bx shr ax, cl - берем число,
маскируем и двигаем справо до границы регистра
; magical sequence cmp al, 10 sbb al, 69h das - преобразуем в 16-ый символ (от 0 до F) stosb shr bx, 4 - готовим сдвиги для следующего 4-битного блока sub cx, 4 or bx, bx jnz @@1
mov al, '$' stosb pop si pop di pop cx pop bx pop ax pop bp ret 4 toHEX endp
CRLF proc push ax - выв
одим коды 13 и 10 на экран - это перевод каретки (курсора) для вывода на новой строке mov al, 13 int 29h mov al, 10 int 29h pop ax ret CRLF endp
;============= num dw 1234h - число для тестового вывода
str_dec db 32 dup ('0') - строки получившиеся во время работы для вывода str_bin db 32 dup (0) str_hex db 32 dup (0) end start
--------- Знания - сила
Ответ отправил: Евгений Иванов (статус: Профессор) Россия, Москва Тел.: +79168790776 Адрес: ул. Тверская WWW:Eugene Ivanov ICQ: 214889076 ---- Ответ отправлен: 01.02.2008, 00:29 Оценка за ответ: 5
Вопрос № 121.126
Помогите разобрать программу по блокам.Что происходит в этих блоках?Если можно по пунктах, потому что это моя экзаменацыонная программа, а у меня преподаватель спрашивает по каждой строчке.
Это уже последняя.Помогите завтра экзамен
Отвечает: Олег Владимирович
Здравствуйте, Балковая Наталья Юрьевна!
Строка MASM выбирает один из двух режимов ассемблера. Борландовский ассемблер поддерживает как синтаксис микрософта (MASM), так и свой (IDEAL).
Директива MODEL выбирает модель памяти. В данном случае создаётся COM-файл, поэтому модель выбрана tiny. Она характеризуется тем, что данные и код хранятся вместе в одном сегменте размером 64Кбайт. Под стэк отводится область в конце сегмента, то есть стэк прирастает навстречу данным - от адресов FFFFh вниз. Допустимы также варианты small, medium, compact, large, huge, flat - но они не подходят для com-файлов. small используется для задания двух отдельных сегментов - один под данные, один под код. medium имеет
один сегмент данных и несколько сегментов кода. В модели compact - один сегмент кода, зато любое кол-во сегментов данных. В модели large любое кол-во сегментов кода и данных. Модель huge отличается от flat лишь тем, что размер сегментов может превышать 64К. В модели flat используется один большой сегмент, где хранятся и код, и данные.
Директива .186 написана с ошибкой - точка должна быть ровно одна. Директива выбирает самый младший тип процессора, в данном случае 80186, на котором может выполняться программа. Без неё тоже, кстати, работает. Просто иногда используются инструкции, которых не было на 8086, и ассемблер отказывается компилировать такой код без указания типа процессора.
Директива CODESEG указывает начало сегмента кода - единственного сегмента в этой программе. Сегмент закончится только в конце, директивой end. Эта же директива end заканчивает файл и указывает так называемую точку входа в программу - ту метку, с которой начнётся выполнение. В случае COM-файла программа обязана начинаться в единственном сегменте по смещению 100h от начала сегмента. В связи с этим следующая строка ORG 100h как раз указывает, что следующий код должен быть помещён по смещению 100h, и - вот она,
точка входа, метка start:.
Переход в 3-ий текстовый режим приводит к тому, что экран очищается, курсор перемещается в левый верхний угол, задаётся 16-цветный режим 80х25 символов. Переход осуществляется вызовом прерывания BIOS командой INT 10h, которая ожидает в AH номер функции установки видеорежима, т.е. 0, а в AL - номер видеорежима, поэтому и заносим нужные значения командой MOV AX,3.
Далее, просим пользователя ввести число, т.е. выдаём приглашение к вводу, т.е. выводим строку, пользуясь функцией DOS с номером 9. Функция DOS вызывается прерыванием INT 21h. Она ожидает номер функции в AH, поэтому, дабы поместить нужный нам номер функции, применяем MOV AH,9. Кроме того, DX должен указывать на строку, которую надо вывести, причём строка должна заканчиваться на '$'. Вот для этого и заносим в регистр DX смещение (ближний указатель) на строку.
Теперь готовимся вводить число. Число будет вводиться по цифрам, так что его придётся где-то накапливать, для чего подготавливаем DI, занося в него 0 командой XOR DI,DI. XOR - побитовая команда исключающее ИЛИ, в результате в 1 установлены только те биты, которые различны у операндов. А т.к. операнды одинаковые, то все биты станут равными нулю, что равносильно команде MOV DI,0.
Далее смотрим на логику: если есть число 234, сохранённое в DI, и пользователь вводит очередную цифру 5, то в DI надо получить 2345, т.е. умножить DI на 10 и прибавить введённую цифру.
Так что делаем: получаем вводимую цифру, пользуясь функцией DOS с номером 7 (MOV AH,7 - выбор функции и INT 21h - её вызов). Она дождётся нажатия клавиши и занесёт её ASCII-код в AL.
При этом пользователь не обязательно введёт цифру. Ленивый пользователь закончит ввод, нажав Enter, чей код считается равным 0Dh. Поэтому делаем проверку: сравниваем полученный код с 0Dh (CMP AL, 13) и, если действительно нажали Enter, т.е. AL==13h переходим на обработку числа - на метку StopNumber (JZ StopNumber, эквивалентно JE StopNumber). Далее, вредные пользователи вводят нецифровые символы. Все цифры имеют коды 30h - 39h, соответствующие символам '0' - '9'. Вот и проверим, сравним введённый код с 30h и
39h: если меньше 30h (CMP AL, 30h и JB NoNumber) или больше 39h (CMP AL, 39h и JA NoNumber), то ввели не цифру, и этот символ можно проигнорировать, прыгнув на вызов функции 7, которая получит очередной символ.
Функция 7 только получала введённый символ, но не отображала его на экране. Пользователь может испугаться, если всё, что он вводит, куда-то пропадает. Так что надо вывести введённую цифру (всё нецифровое ведь уже отсеяли). Для этого используем прерывание DOS - INT 29h, которое ожидает в AL код символа, который нужно вывести. При этом почему-то боимся за сохранность регистра AX, сохраняя его в стэке перед вызовом (PUSH AX) и восстанавливая после (POP AX), хотя мне не удалось воспроизвести ситуацию, при которой
прерывание 29h изменило бы этот регистр, поэтому эти две команды можно безболезненно репрессировать.
Теперь, наконец, накапливаем число в DI - умножим его на 10 и прибавим только что введённую цифру. В AL хранится код цифры, а не цифра. Но коды цифр подобраны так, что младшие 4 бита содержат саму цифру, поэтому AND AX, 0Fh, которое обнулит все биты AX, кроме 4 младших, переведёт символы '0' - '9' в соответствующие цифры 0 - 9. Ну, хорошо, теперь вычисляем DI*10+AX. Умножение делаем инструкцией MUL BX, но она умножает операнд на AX, сохраняя 32-битный результат в паре регистров DX:AX, в DX - старшая половина,
в AX - младшая. Поэтому множитель надо поместить в AX, а он находится в DI. Но AX содержит прибавляемую цифру, её потерять нельзя. Поэтому просто меняем их местами инструкцией XCHG DI,AX, после которой накапливаемое число окажется в AX, а очередная цифра - в DI. Теперь можно умножать на 10 (MOV BX, 10 и MUL BX - просто MUL 10 нельзя - такую команду не предусмотрели) и результат умножения (AX, старшую часть игнорируем) сложить с очередной цифрой (DI) инструкцией ADD DI,AX
, которая поместит результат в DI, где мы и накапливаем вводимое число. Далее повторяем процедуру получения цифры, переходя на метку NoNumber (JMP SHORT NoNumber). Директива SHORT используется для того, чтоб указать, что переход короткий, т.е. что прыжок происходит на расстояние менее 128 байт, т.е. от команды JMP до целевой метки менее 128 байт, это позволяет сгенерировать более компактный код. По умолчанию используется NEAR, т.е. переход в пределах сегмента. В условных же переходах (вроде JZ) возможен только
короткий переход, т.е. там подразумевается как раз SHORT. (Конечно, в 80386 недоразумение исправили, там уже можно прыгать на любое расстояние, но тогда нужна директива .386)
Итак, из цикла ввода числа пользователь может выйти, лишь нажав Enter. При этом управление передаётся на метку StopNumber. Здесь мы задаёмся целью вывести число в различных системах счисления. Так как это похожие операции, их можно оформить в виде подпрограммы, принимающей два параметра - выводимое число в AX и систему счисления в SI. Так что командами MOV AX, DI и MOV SI, 2 мы подготавливаем параметры, а командой CALL OutNumber вызываем подпрограмму.
Теперь внимательно. При вызове подпрограммы в стэке сохраняется адрес возврата - ссылка на инструкцию, на которую перейдёт управление после выполнения подпрограммы. CALL OutNumber сохранит в стэке адрес следующей инстркуции, MOV AX, DI, чтоб при завершении подпрограммы по команде RET управление передалось именно на эту инструкцию (Команда RET извлекает из стэка адрес возврата и передаёт туда управление). Ну, хорошо, с тремя вызовами всё ясно - подготовка параметров (каждый раз нужно, т.к. подпрограмма изменяет
AX) и вызов. Но вот команды MOV AX,DI и MOV SI,16 готовят параметры для очередного вызова подпрограммы... а вызова нет. Но управление и так передаётся подпрограмме, ведь далее идёт метка OutNumber. Но, ведь никакого адреса возврата в стэке не сохранилось - CALL-то не было, а RET в конце подпрограммы есть. Куда перейдёт управление после завершения подпрограммы? Так вот, в начале, ещё при загрузке COM-программы, операционная система сохраняет в стэке ноль. Вот RET и извле
чёт этот ноль и передаст туда управление. По адресу 0 операционная система размещает команду INT 20h - вызов прерывания DOS, которое завершает программу.
Теперь разберёмся, что происходит внутри подпрограммы. Она должна вывести строку вроде "10 system: 2345", т.е. два числа и строку "system:". Так вот, OutNumber сохраняет переданные ей параметры, т.к. использует эти регистры, организует перевод строки (выводит символы возврата каретки - 13 и перехода на новую строку - 10, пользуясь уже встречавшимся прерыванием 29h), выводит основание системы счисления в десятичном виде (MOV AX, SI; MOV SI, 10; CALL isNumber), строку "system:" функцией
9, которую тоже где-то уже видели, и, наконец, само число в нужной системе счисления при помощи isNumber.
...продолжение в приложении...
Приложение:
--------- Факультет ПМ-ПУ - лучший в СПбГУ!
Ответ отправил: Олег Владимирович (статус: Студент)
Ответ отправлен: 01.02.2008, 03:23