Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Ассемблер? Это просто! Учимся программировать Выпуск ╧ 007


Служба Рассылок Городского Кота

Здравствуйте, уважаемые любители Ассемблера!


Выпуск N 007

Взрыв в переходе на Пушкинской площади 8 августа 2000 года потряс не только Москву, но и всю Россию. Где будет следующий теракт? Наши власти очередной раз показали свое бессилие. Будут ли найдены виновные? Учитывая результаты предыдущих расследований, думаю, что нет. Террористический акт потрясает своей жестокостью, бесчеловечностью и слабостью. Именно жестокостью, так как только жестокий человек способен убивать ни в чем не повинных людей. Именно бесчеловечностью, так как только нелюдь способен тщательным образом подготавливаться и предвкушать наступления долгожданного момента. Именно слабостью, так как только слабый человек способен выстрелить в спину. Подонки, искалечившие десятки судеб, принесшие горя сотням людей не могут называться людьми. Кому и что в итоге террористы доказали? Устрашили народ? Думаю, что это им удалось. Что тут говорить: наше государство не способно защитить своих граждан. Друзья мои! Давайте будем внимательными не только к себе, но и к окружающим нас людям, ибо только так нам удастся вернуть все то, что мы утратили за последние годы...


Хотел бы сказать по этому поводу...


Сегодня в нашей рассылке:


Информация для вновь подписавшихся

Пожалуйста, возьмите предыдущие выпуски на сайте:

www.oleg77.newmail.ru/Assembler

Напишите мне с просьбой выслать: oleg77@online.ru

Постарайтесь прежде перекачать с сайта. Если ничего не получится, то пишите мне.

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

Благодарю всех, кто подписался.


Ваши письма

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

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

Давайте так и сделаем. Вирус будем изучать нерезидентный (резидент уже есть), а в процессе написания оболочки рассмотрим команды 486 процессора, Viewer, графическую заставку.

Хотел бы поблагодарить всех, кто помог мне с материалом по форматам графических файлов.

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


Электронные адреса подписчиков

(опубликовываю по вашей просьбе):

ser@avtograd.ru Сергей
yoshi@beep.ru Yoshi
sab@sab.ru Сергей
e1korn@mail.ru Борис
ILUH_1@mail.ru Николай
kam_andrey@mail.ru Андрей
artem1977@mail.ru Артем
philich1@mtu-net.ru Александр
mzelenkin@operamail.com Миша
rosstrax@tsl.ru Владимир
dprof@skynet.kharkov.com ScAlAn
albor@gala.net Albor

Результаты голосования.

Команды и инструкции 486-Pentium 21
Viewer для просмотра графики 10
Безобидный вирус 34
Графическая игра 14
Дизассемблер 9
Графическая заставка с музыкой 16
Локальная сеть 19
Оболочка типа Нортон Коммандер 33
Резидент 27

Как видите, результаты не сильно изменились. А вообще, я очень разочарован. Проголосовало всего около двух процентов! Наверное, остальным все равно что мы будем изучать.........

Первая тройка: Оболочка, Вирус, Резидент. Из этих трех программ нам нужно выбрать одну, которой мы будем уделять наибольшее внимание (но не оставим в стороне остальные две). У меня на сайте проводится голосование по этому поводу.


Стек/стэк (stack)

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

Итак, решение. Вот программа:

...

(1) mov sp,offset Lab_1

(2) mov ax,9090h

(3) push ax

(4) int 20h

(5) Lab_1:

(6) mov ah,9

(7) mov dx,offset Mess

(8) int 21h

(9) int 20h

...

Что же все-таки происходит.

Как мы узнали из прошлого выпуска, команда NOP имеет код 90h. Посмотрите на строку (2). В ней мы загружаем в AX два числа 90h, т.е. два оператора NOP.

В SP заносим адрес метки Lab_1 (1). Теперь стек находится внутри нашей программы, а точнее вершина стека на метке Lab_1. Стоит отметить, что сама метка памяти не занимает! Она нужна только для MASM/TASM, которые, дойдя до строки (5), запомнят адрес (смещение) метки в программе (109h). После выполнения строки (1), посмотрите в отладчике какое число находится в SP, а также на код, который расположен по этому адресу (чуть ниже). Мы увидим, что по адресу 109h находится команда mov ah,9, т.е. нет нашей метки в памяти!

Теперь внимание! В строке (3) мы толкаем в стек два числа 90h, т.е. две команды NOP (чувствуете, как просто в Ассемблере заносятся коды программы "на лету"?). Что происходит дальше? Стек, как вы уже знаете, "растет" снизу вверх (помещая в стек число, SP не увеличивается, как кажется, а уменьшается).

Еще раз внимание! Команда int 20h занимает два байта. Это можно наблюдать в отладчике или в Hacker's View. Машинный код int 20h равен 0CDh, 20h. Теперь вы знаете, как вручную набрать com-программу в редакторе. Если есть редактор, позволяющий смотреть шестнадцатеричные числа (например, Volcov Commander), то введите в создаваемом файле CD и 20. Сохраните файл как com (например, prog.com). Можете его смело запускать. Он не зависнет!

Вернемся. Понятно, что int 20h занимает два байта. Командой push ax (3) мы толкаем в стек два оператора NOP, которые ложатся поверх int 20h, затирая выход из программы (стек-то у нас внутри нашего кода!).

Все. Далее компьютер выполняет не выход в DOS, а два оператора NOP, которые ничего не делают!

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

Давайте теперь рассмотрим, почему отладчики CodeView, AFD, Turbo Debugger работают некорректно.

Вдумайтесь в следующую фразу: вышеперечисленные отладчики используют стек НАШЕЙ программы. Грубо говоря, отладчик, загрузив к себе в память нашу программу, использует тот же стек (те же регистры SS:SP), что и наша программа. Отладчику ведь тоже надо хранить где-то свои данные (например, после выполнения одной команды нашей программы, запомнить адрес следующей. Одно дело на экране, но другое - в стеке отладчика, который является нашим!). Поэтому, переместив стек в область кода программы, мы тем самым переносим стек отладчика, который, сохраняя свои переменные (а их более двух байт; мы ведь хотим поместить только 9090h), затирает наш код! Уфф! Понятно, дорогие мои?

Стоит отметить такой момент: на компьютере 486 DX2-80 этот номер почему-то не работает: программа выходит, не выводя сообщение на экран. Однако, запустив ее под отладчиком SoftIce, программа работает верно! В чем может быть дело - под вопросом. Мне также пришло письмо от одного из подписчиков, который также сообщил о том, что программа ничего не выводит на экран. Как оказалось, процессор у него AMD 486 DX4-100. Странно очень... Я пробовал запустить эту программу на Pentium-150 под Win98, а также на Pentium-120 под Win95. Все работало верно!

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


Новые операторы.

Итак, дорогие мои, вы уже умеете многое. Например, ожидание клавиши от пользователя, используя функцию 10h прерывания 16h. Теперь настало время научиться проверять, какую же клавишу нажал пользователь.

Для этого рассмотрим несколько новых операторов.

Оператор Перевод Применение Процессор
CMP приемник, источник compare - сравнить Сравнение 8086

Я уже не раз упоминал в выпусках, что после вызова функции 10h прерывания 16h, в AX помещается код клавиши, которую нажал пользователь. Как же нам проверить?

Вот пример:

...

(1) mov ah,10h

(2) int 16h

(3) cmp al,'Y'

(4) jz Yes_key

...

(5) Yes_key:

...

Строки (1) - (2) нас уже не интересуют, а вот (3) - (4) мы рассмотрим.

Что же происходит? На строке (2) компьютер остановится и не продолжит работу до тех пор, пока мы не нажмем какую-нибудь клавишу. После этого прерывание 10h поместит в AX код клавиши, и выполнение программы продолжится. В строке (3) мы проверяем, нажата ли клавиша 'Y'. Обратите внимание на запись:

cmp al,'Y'

Мы можем даже не знать ASCII-код клавиши. Для этого в кавычках просто указываем саму клавишу. Ассемблер при ассемблировании сам заменит ее на соответствующий код. Если вы запустите программу под отладчиком, то увидите, что код будет не

cmp al,'Y'

а

cmp al,59h

Теперь мы знаем код клавиши 'Y'. Естественно, можно записать и cmp al,59h. Программа будет работать правильно.

Т.о., мы узнали, что в Ассемблере можно записывать не только двоичные, десятичные и шестнадцатеричные числа, но и просто символы. Например:

mov al,'2'

mov dh,'d'

В результате AL будет содержать 32h, а DH - 64h. Все просто!

Смотрим дальше. В строке (4) наша программа перейдет на метку 'Yes_key' (5), если нажата клавиша 'Y'.

Оператор JZ (Jump if Zero - переход если флаг нуля установлен) выполняет переход на указанную метку, если флаг нуля установлен в единицу.

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

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

mov ax,15

cmp ax,15

jz Our_lab

mov ah,3

Our_lab:

...

В данном случае флаг нуля будет установлен в единицу, и мы сможем перейти на указанную метку (Our_lab). Команда mov ah,3 не будет выполняться.

Второй пример:

mov ax,15

cmp ax,16

jz Our_lab

mov ah,3

Our_lab:

...

В данном случае на метку мы не перейдем, так как команда сравнения (cmp ax,16) не будет верна. Выполнится инструкция mov ah,3 (и, естественно, все, что идет после данной инструкции).

Все просто! Не так ли?

Допустим, нам нужно подождать, пока пользователь нажмет какую-нибудь клавишу. И, если это 'ф' или 'Ф', то перейти на указанную метку. В противном случае, запросить клавишу снова. Обратите внимание, что коды ЗАГЛАВНЫХ и строчных букв различаются! Вот что мы сделаем:

Next_key:

mov ah,10h

int 16h

cmp al,'ф'

jz F_pressed

cmp al,'Ф'

jz F_pressed

jmp Next_key

F_pressed:

mov ah,9

mov dx,offset Mess

int 21h

int 20h

Mess db 'Ура! Вы нажали Ф или ф!!!$'

...

Все элементарно!

Стоит отметить еще такой момент: команды jz и je (Jump if Equal - переход, если равно) выполняют одну и ту же функцию. Т.е. можно записать и так:

...

int 16h

cmp al,'Ф'

je F_pressed

...

Команда je переведется ассемблером в jz. Это легко проверить, если после ассемблирования запустить программу под отладчиком.

Разницы между je и jz нет никакой!

Итак, теперь вы умеете производить проверку нажатой клавиши.

Теперь совсем немного терминов.

Команда jmp (которую вы уже знаете "на зубок") назвается командой безусловного перехода. Т.е. при любом условии компьютер перейдет на указанную метку. Для тех, кто знаком немного с Бейсиком: эта команда эквивалентна GOTO.

goto 20 - переход на строку 20.

Команды вида je и jz - условный переход. Т.е. компьютер перейдет на метку только, если условие выполняется (в нашем случае - если нажата клавиша, которую мы проверяем: cmp al,'Ф'). В Бейсике это выглядит примерно так:

if Key = "Ф" then goto 20

Я все время говорю, что компьютер сделает то-то или то-то. На самом деле, правильнее будет сказать, что процессор сделает то-то. Мы посылаем команды не компьютеру (компьютер - это общее понятие), а напрямую процессору, который и выполняет нашу похоть.

Теперь рассмотрим таблицу некоторых кодов символов расширенного ASCII.

Что такое расширенный ASCII? Я говорил, что код клавиши возвращается в AX. Но в приведенных выше примерах мы проверяли почему-то клавишу в AL:

cml al,'Y'

Дело в том, что кроме символов (А..Я / A..Z) и цифр (0..1) существуют еще клавиши F1-F12. Мы не можем записать так, проверяя, нажата ли клавиша F1..F12:

cmp al,'F1'

Ассемблер выдаст ошибку, потому что 'F1' - два байта, а регистр AL (как уже общеизвестно) может хранить / проверять только один байт. Для этого используются расширенные коды ASCII. При этом, если мы на запрос нашей программы нажмем F1, то в AL помещается 0, а в AH - расширенный код.

Вот таблица в помощь:

F1..F10 Alt-F1..F10 Shift-F1..F10 Ctrl-F1..F10
3Bh..44h 68h..71h 54h..5Dh 5Eh..67h

Думаю, что из таблицы понятно, что F1 имеет код 3Bh, F2 - 3Ch, F3 - 3Dh и так далее...

Вот кусок программы, в которой мы проверим нажатие комбинации клавиш Shift-F4 (комбинация клавиш - в данном случае нужно нажать Shift и, не отпуская его, F4):

...

(1) No_ext:

(2) mov ah,10h

(3) int 16h

(4) cmp al,0

(5) jnz No_ext

(6) cmp al,57h

(7) je Shift_f4

(8) jmp No_ext

(9) Shift_f4:

...

Мы уже выяснили, что при нажатии на клавиши типа F1, Alt+F1 и т.п. в AL помещается 0, а в AH - расширенный код. В строке (4) мы это дело проверяем. В строке (5) переходим на метку No_ext, если пользователь нажал клавишу, код которой не расширенный (например: A, ф, <пробел>, <Enter> и т.п.). Т.е. мы как бы просто проигнорируем нажатую клавишу и "попросим" пользователя нажать другую.

В строке (6) проверяем, нажата ли именно комбинация клавиш Shift+F4 (не трудно высчитать ее код из приведенной выше таблицы). Если же пользователь нажал Shift+F4, то, как не трудно догадаться, программа перейдет на метку Shift_f4 (9) (ее, естественно, можно было назвать по другому). Если пользователь нажал какую-либо иную клавишу, имеющую расширенный код, то программа опять-таки вернется на метку No_ext (8).

Резюмируем: программа продолжит работу, только если пользователь нажмет комбинацию клавиш Shift+F4.

Думаю, что вопросов быть не должно...

Вот еще одна таблица с кодами (но не расширенными; т.е. проверять нужно AL, а не AH!) часто используемых клавиш (в последствии рассмотрим остальные коды ASCII):

Enter ESC Space Tab
0Dh 1Bh 20h 09h

Все!


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

Я все размышляю: какую бы программку вам дать для изучения? Думаю, что будет интереснее давать вам какую-нибудь программку, а описание к ней в следующем выпуске. Так вы в течение недели "поломаете голову", а затем, в следующем выпуске, узнаете, правы были или нет. Как вам это нравится? oleg77@online.ru?Subject=Чушь!

Ваши варианты, умозаключения, выводы, решения по поводу программы вы можете посылать мне по почте с пометкой "Решение": oleg77@online.ru?Subject=Решение

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

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

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


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 ; это комментарий

Удачного изучения!

С уважением,

Калашников Олег ( oleg77@online.ru?Subject=Ассемблер: )


Немного рекламы.

MASM 6.13 теперь есть на моем сайте. В нем также:

masm.exe

ml.exe

link.exe

codeview.exe

pwb.exe

а также примеры, помощь, документация.

Архив занимает 4 Мб, но его вполне достаточно для написания программ на Ассемблере.

Очень просто создать com-файл:

ml.exe prog.asm /AT

и все!

www.oleg77.newmail.ru

___________



http://subscribe.ru/
E-mail: ask@subscribe.ru

В избранное