Ассемблер Это просто! Учимся программировать

       

Программка для практики


Хотите увидеть всю мощь Ассемблера?

Давайте усовершенствуем программу с . (1) CSEG segment (2) assume CS:CSEG, DS:CSEG, ES:CSEG, SS:CSEG (3) org 100h (4) Start: (5) mov ax,0B800h (6) mov es,ax (7) mov al,1 (8) mov ah,31 (9) mov cx,254 (10) Next_screen: (11) mov di,0 (12) call Out_chars (13) inc al (14) loop Next_screen (15) mov ah,10h (16) int 16h (17) int 20h (18) Out_chars proc (19) mov dx,cx (20) mov cx,2000 (21) Next_face: (22) mov es:[di],ax (23) add di,2 (24) loop Next_face (25) mov cx,dx (26) ret (27) Out_chars endp (28) CSEG ends (29) end Start

Не устали?

Будем разбираться... Хотя, собственно, ничего сложного нет.

Итак, строки (1) - (8), (15) - (17) и (28) - (29) опускаем. Вопросов по ним быть не должно.

В строке (9) заносим в CX число 254, указывающее на то, сколько раз будет выполняться основной цикл. Строки (10) и (14) - "голова" и "хвост" нашего основного цикла соответственно. DI будет меняться в процедуре, поэтому нам необходимо будет его постоянно аннулировать (11). В строке (12) вызываем процедуру, которая заполнит экран кодом символа, который находится в AL (в первом случае - это 01 "рожица"). Все! Теперь экран заполнен кодом 01. При этом DI будет равно 2001 (поэтому нам и нужно его обнулять). Далее увеличим на единицу код, который находится в AL (теперь AL содержит 02 - тоже "рожица", но немного другого вида) (строка (13)), уменьшим счетчик на 1 и перейдем к заполнению экрана кодом 02 (строка 14). И так 254 раза.

Теперь процедура.

В строке (19) сохраним CX (просто перенесем его в DX), т.к. он будет изменен во вложенном цикле ниже. Строки (21) и (24) - "голова" и "хвост" вложенного цикла, который будет выполняться 2000 раз (надо же заполнить экран полностью) (см. строку (20), в которой загружаем счетчик в CX). Все! Осталось только восстановить CX (строка (25)) и выйти из подпрограммы (26).

Итак, у нас здесь два цикла: основной и вложенный (так мы их назвали). Основной выполняется 254 раза, а вложенный - 2000 раз.

Если у Вас Pentium, то, боюсь, что вы не успеете заметить вывод всех символов. Очень быстро работает, хотя это далеко и не оптимальный алгоритм (кто знаком с языком - подтвердят мои слова).

Для исследования данного примера я рекомендую вам воспользоваться отладчиком. Т.к. у многих есть CV и AFD, то вкратце еще раз объясню, какие кнопки давить.

CodeView:

F8 - пошаговое выполнение команд (с заходом в подпрограммы);

F10 - выполнение команды за один шаг (без захода в подпрограммы).

AFD:

F1 - пошаговое выполнение команд (с заходом в подпрограммы и прерывания);

F2 - выполнение команды за один шаг (без захода в подпрограммы и прерывания).

Ну, поэксперементируйте с клавишами. Принцип прост.

Удачного программирования!


Прежде чем перейти к программе, рассмотрим новый оператор:



Оператор Перевод Применение Процессор
NOP No OPerand - нет операнда Ничего не делает 8086

Этот оператор делает то, что ничего не делает, но занимает один байт. Его обычно используют для резервирования места либо для того, чтобы "забить" ненужный код, когда исходник на Ассемблере отсутствует. Например, программа перед стартом проверяет версию MS-DOS. Версия, которая установлена на вашем компьютере, не соответствует требуемой программой. Для этого данным оператором "забивают" участок кода, который проверяет версию ОС.

Все это позволяет сделать Hacker's View, который можно взять на моем сайте:

Запомните машинный код данной комманды: 90h

Даю голову на отсечение, что ни один язык высокого уровня не позволяет сделать того, что может наша программа: (1) CSEG segment (2) assume cs:CSEG, es:CSEG, ds:CSEG, ss:CSEG (3) org 100h (4) Begin: (5) mov sp,offset Lab_1 (6) mov ax,9090h (7) push ax (8) int 20h (9) Lab_1: (10) mov ah,9 (11) mov dx,offset Mess (12) int 21h (13) int 20h (14) Mess db 'А все-таки она выводится!$' (15) CSEG ends (16) end Begin

То, что вы видите - обман зрения. На первый взгляд, программа что-то делает с регистром SP, а затем выходит. Строки (9) - (12) вообще не будут работать. Но это глубокое заблуждение!

Попробуйте запустить ее под отладчиком. Вы увидите, что CodeView, TurboDebuger, AFD будут сообщать какую-то ерунду (непонятные операторы, сообщения типа "программа завершилась", хотя строка не выведена и пр.). Но, если запустить ее просто из ДОС, то строка появится на экране, т.е. программа будет работать корректно!

Данный пример - типичный случай "заламывания рук" отладчикам (но не SoftIce!). И вы уже можете это делать!

Вывод один: указанные выше отладчики используют стек пользовательской программы (что это значит - думаю, вы разберетесь).

Ваша задача: разобрать "по полочкам" программу. Почему так происходит? Почему строка выводится? Почему отладчик работает неверно? И пр. и пр. Вопросов море. И вам предстоит дать ответ на них.

Разобравшись с программой самостоятельно, вы почувствуете Силу и Неограниченные Возможности Ассемблера. И это правда! Не спешите писать мне письмо с просьбой помочь. Будет не интересно! Пробуйте разобраться сами! Не бойтесь эксперементировать! Компьютер будет часто зависать, но это не главное! Ваши мучения приведут вас к Истине! Я сам проходил когда-то через это...




Думаю, что будет интереснее давать вам какую-нибудь программку, а описание к ней в следующей главе.

Сегодня мы рассмотрим такую программу:

CSEG segment assume cs:CSEG, ds:CSEG, es:CSEG, ss:CSEG org 100h Begin: call Wait_key cmp al,27 je Quit_prog cmp al,0 je Begin call Out_char jmp Begin Quit_prog: mov al,32 call Out_char int 20h ; === Подпрограммы === ; --- Wait_key --- Wait_key proc mov ah,10h int 16h ret Wait_key endp ; --- Out_char --- Out_char proc push cx push ax push es push ax mov ax,0B800h mov es,ax mov di,0 mov cx,2000 pop ax mov ah,31 Next_sym: mov es:[di],ax inc di inc di loop Next_sym pop es pop ax pop cx ret Out_char endp CSEG ends end Begin

Внимательно набирайте программу! Если что-то не работает, то ищите опечатку.

Стоит отметить, что в Ассемблере после точки с запятой идет комментарий, который будет опускаться MASM/TASM при ассемблировании.

Пример комментария:

; это комментарий

mov ah,9 ; это комментарий




Ой! Тут столько объяснять придется. Давайте поступим как в первой главе: кое-что мы рассмотрим позже.

Итак, вот образец чтения файла (до 64000 байт) в память, а, точнее, в наш сегмент (это и будет программкой для практики): CSEG segment assume cs:CSEG, ds:CSEG, es:CSEG, ss:CSEG org 100h ;начало Begin: mov ax,3D00h mov dx,offset File_name int 21h jc Error_file mov Handle,ax mov bx,ax mov ah,3Fh mov cx,0FF00h mov dx,offset Buffer int 21h mov ah,3Eh mov bx,Handle int 21h mov dx,offset Mess_ok Out_prog: mov ah,9 int 21h int 20h Error_file: mov dx,offset Mess_error jmp Out_prog ;конец Handle dw 0 Mess_ok db 'Файл загружен в память! Смотрите в отладчике!$' Mess_error db 'Не удалось открыть (найти) файл 'File_name db 'c:\msdos.sys',0,'!$' Buffer equ $ CSEG ends end Begin

Из этого примера вы узнаете очень много. Еще раз хочу сказать: пользуйтесь отладчиком!




Давайте поподробнее рассмотрим работу с файлами. Вот пример:

CSEG segment assume cs:CSEG, ds:CSEG, es:CSEG, ss:CSEG org 100h ; -------------Содержание------------ Begin: mov dx,offset File_name call Open_file jc Error_file ; -------------- Открыли файл ----------- mov bx,ax mov ah,3Fh mov cx,offset Finish-100h mov dx,offset Begin int 21h ; ------------- Прочитали файл ---------------- call Close_file ; ------------ Выводим сообщение -------------- mov ah,9 mov dx,offset Mess_ok int 21h ret ; ---------- Не смогли найти файл ----------------- Error_file: mov ah,2 mov dl,7 int 21h ret ; ======= Процедуры пошли... ========== ; --- Открытие файла --- Open_file proc cmp Handle,0FFFFh jne Quit_open mov ax,3D00h int 21h mov Handle,ax ret Quit_open: stc ret Handle dw 0FFFFh Open_file endp ; --- Закрытие файла --- Close_file proc cmp Handle,0FFFFh je No_close mov ah,3Eh mov bx,Handle int 21h mov Handle,0FFFFh No_close: ret Close_file endp ; ===== Данные ====== File_name db 'less009.com',0 Mess_ok db 'Все нормально!', 0Ah, 0Dh, '$' Finish equ $ CSEG ends end Begin

ВНИМАНИЕ: этот файл нужно сохранить как less009.asm!

Вот работы вам на целую неделю!!!

Сложно. Очень сложно... Да и отладчик работать не будет... Что же делать-то? Ищите, эксперементируйте, пробуйте все варианты... Здесь много чего интересного!




Сегодня рассмотрим одну небольшую программку, которая выводит на экран сообщение, и ждет, когда пользователь нажмет любую клавишу. После чего возвращается в DOS.

Управлять клавиатурой позволяет прерывание 16h. Это прерывание BIOS (ПЗУ), а не MS-DOS (как 21h). Его можно вызывать даже до загрузки операционной системы, в то время, как прерывание 21h доступно только после загрузки IO.SYS / MSDOS.SYS (т.е. определенной части ОС MS-DOS).

Чтобы остановить программу до нажатия любой клавиши следует вызвать функцию 10h прерывания 16h. Вот как это выглядит (после символов ";" идет комментарий):

mov ah,10h ; в AH всегда указывается номер функции int 16h ; вызываем прерывание 16h - сервис работы с клавиатурой BIOS (ПЗУ)

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

Следующая программа выводит на экран сообщение и ждет нажатия любой клавиши (равнозначна команде "pause" в *.bat файлах):

(01) CSEG segment (02) org 100h (03) Start: (04) (05) mov ah,9 (06) mov dx,offset String (07) int 21h (08) (09) mov ah,10h (10) int 16h (11) (12) int 20h (13) (14) String db 'Нажмите любую клавишу...$' (15) CSEG ends (16) end Start

Строки с номерами (01), (02) и (15) пока опускаем. В строках (05) - (07), как Вы уже знаете, выводим на экран сообщение. Затем (строки (09) - (10)) ждем нажатия клавиши. И, наконец, строка (12) выходит из нашей программы.

Мы уже знаем команды INC, ADD и SUB. Можно поэкспериментировать с вызовом прерывания. Например, так:

mov ah,0Fh

inc ah

int 16h

Это позволит Вам лучше запомнить новые операторы.

В следующей главе рассмотрим двоичную систему счисления, основы сегментации памяти и сегментные регистры. Напишем интересную программку.




Теперь интересная программка для практики, которая выводит в верхний левый угол экрана веселую рожицу на синем фоне (данная программа будет работать только на цветных мониторах CGA, EGA, VGA, SVGA):

(01) CSEG segment (02) org 100h (03) _beg: (04) mov ax,0B800h (05) mov es,ax (06) mov di,0 (07) (08) mov ah,31 (09) mov al,1 (10) mov es:[di],ax (11) (12) mov ah,10h (13) int 16h (14) (15) int 20h (16) (17) CSEG ends (18) end _beg

Многие операторы Вы уже знаете. Поэтому я буду объяснять только новые. В данном примере, для вывода символа на экран, мы используем метод прямого отображения в видеобуфер. Что это такое - будет подробней рассмотрено в последующих главах.

В строках (04) и (05) загружаем в сегментный регистр ES число 0B800h, которое соответствует сегменту дисплея в текстовом режиме (запомните его!). В строке (06) загружаем в регистр DI нуль. Это будет смещение относительно сегмента 0B800h. В строках (08) и (09) в регистр AH заносится атрибут символа (31 - ярко-белый символ на синем фоне) и в AL - ASCII-код символа (01 - это рожица). В строке (10) заносим по адресу 0B800:0000h (т.е. первый символ в первой строке дисплея - верхний левый угол) атрибут и ASCII-код символа (31 и 01 соответственно) (сможете разобраться?).

Обратите внимание на запись регистров в строке (10). Квадратные скобки ( [ ] ) указывают на то, что надо загрузить число не в регистр, а по адресу, который содержится в этом регистре (в данном случае, как уже отмечалось, - это 0B800:0000h).

Можете поэкспериментировать с данным примером. Только не меняйте пока строки (04) и (05). Сегментный регистр должен быть ES (можно, конечно, и DS, но тогда надо быть осторожным). Более подробно данный метод рассмотрим позже. Сейчас нам из него нужно понять принцип сегментации на практике.

Это интересно.

Следует отметить, что вывод символа прямым отображением в видеобуфер является самым быстрым. Выполнение команды в строке (10) занимает 3 - 5 тактов. Т.о. на Pentium-100Mhz можно за секунду вывести 20 миллионов(!) символов или чуть меньше точек на экран! Если бы все программисты (а особенно Microsoft) выводили бы символы или точки на экран методом прямого отображения в видеобуфер на Ассемблере, то программы бы работали чрезвычайно быстро... Я думаю, Вы представляете...




Усовершенствуем из предыдущей главы, которая выводила в верхний левый угол "рожицу" прямым отображением в видеобуфер (1) CSEG segment (2) org 100h (3) Begin: (4) mov ax,0B800h (5) mov es,ax (6) mov di,0 (7) mov al,1 (8) mov ah,31 (9) mov cx,2000 (10) (11) Next_face:

(12) mov es:[di],ax (13) add di,2 (14) loop Next_face (15) (16) mov ah,10h (17) int 16h (18) int 20h (19) CSEG ends (20) end Begin

Уфф! Длинная получилась. Прежде чем читать описание программы, попробуйте сами разобраться, что в итоге получится. Поверьте, это принесет вам пользу. Все ведь очень просто!

Теперь описание программы.

Строки с (1) по (10) и с (15) по (20) вы уже знаете. Объясню только новое.

Строка (11) - это метка, как бы "голова" нашего цикла. Строка (14) - "хвост" цикла. Все, что находится в пределах строк (10) - (14), является циклом. Сам цикл будет повторяться 2000 раз, для чего мы и заносим в CX число 2000 (строка (9)).

В строке (12) записываем в видеобуфер (0B800:DI) число в AX (это символ (строка (7) и атрибут (строка (8)). Итак, первый символ занесли. Что делаем дальше?

Дальше увеличиваем регистр DI на 2 для того чтобы перейти к адресу следующего символа. Почему на 2? Дело в том, что в видеобуфере один символ занимает 2 байта: сам символ и его атрибут. Т.к. символ у нас в AL, а атрибут в AH, и мы загрузили уже эти два байта в строке (12), то и увеличиваем DI (смещение) на 2.

DI теперь указывает на адрес для следующего символа. Осталось уменьшить счетчик (CX) на 1 и повторить. Что мы, собственно, и делаем в строке (14).

Все! Обратите внимание на скорость вывода символов при запуске программы.

Еще раз напоминаю: пожалуйста, печатайте все программы сами! Так вы быстрее освоите Ассемблер!



Содержание раздела