Калашников.ru - Ассемблер? Это просто!.. (Выпуск № 008)

InterReklama Advertising

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

Выпуск N 008

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

Ваши письма;

Программа из прошлого выпуска;

Немного теории;

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

Ваши письма

Спасибо всем, кто высказал свое мнение по поводу терракта. Я на сайте разместил некоторые ваши письма.

Еще хотел бы обратить ваше внимание на такой момент: рассылки могут приходить с большим опозданием (2-4 дня). Может даже случиться так, что рассылка не дойдет вовсе, если сервер Subscribe.ru не сможет отправить письмо в течение суток.

Выпуски выходят в субботу - понедельник. Если до пятницы вы не получите очередной номер - пишите мне, либо перекачайте с архива:

www.subscribe.ru/archive/comp.prog.assembler.

Более того, случается и такое, что Subscribe.ru отправляет рассылку кому-нибудь два раза. Даже не знаю, с чем это связано. Прошлый раз была моя вина в том, что рассылка вышла в двойном экземпляре. Первый раз, когда я пытался отправить, произошла ошибка. Я подумал, что выпуск не выйдет и переслал его. Однако, я ошибался... В итоге, все получили две копии рассылки.

Я, к сожалению, тут бессилен что-либо сделать...

_______________________

Еще несколько моментов.

Друзья мои! Я просто физически не могу ответить на все ваши вопросы. Для этого мне нужно было бы забросить рассылку, работу и рефераты и отвечать только на ваши письма. Очень много вопросов поступает по поводу того, что программу из выпуска набрал, а она не работает. Что делать?

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

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

_______________________

Чат. Он, конечно, далек от совершенства. Наблюдаются некоторые ошибки. Например, невозможно ввести e-mail, если он содержит более 15 знаков либо числа (0-9). Вводите какое-нибудь другое имя. Например:

oleg@mail.ru

dimon@pisem.net

и т.п.

_______________________

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

_______________________

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

В 486 процессорах есть ошибка в схемотехнике, приводящая к тому, что при записи в память по адресу большему текущего IP, но меньшему длины очереди предвыборки команд, содержимое очереди не перечитывается, а выполняется то, что уже было считано процессором. Поэтому процессор никак не реагирует на изменения в коде. Эта ошибка была исправлена только в Пентиуме.

Прислал Alex.

_______

Мне кажется, я могу дать объяснение данному "феномену" :) Дело в том, что у процессоров 80486 работа механизма кэширования отлична от механизма реализованного в процессорах Pentium. Попробую разъяснить.

Дело в том, что процессор, при выполнении команд, считывает их в кэш. И на выполнение команды поступают уже из кэша. Так вот. Секрет в том, что при изменении участка кода, который уже закэшировал процессор 486, не обновляет содержимое кэша!!! Т.е. на 486 процессорах возможен вариант затирания NOP-ами всей программы, и, тем не менее, она выполнится. В случае с Pentium-ами, ситуация выглядит более правильной. При изменении участка памяти, который уже закэширован, процессор сбрасывает содержимое кэша и заново считывает данные из памяти. Поэтому пример работает на Pentium и не работает на 486.

Остается один момент: почему на 486 под SoftIce программа-таки да выполняется? Тут дело в том, что когда отладчик прерывает выполнение программы, то меняются значения всех сегментных регистров. И процессор принудительно сбрасывает кэш, так как его содержимое ему уже не нужно. А так как кэш сбрасывается, то его новое содержимое уже будет отражать текущее содержимое памяти, и, следовательно, программа выполняется по командам, которые лежат в памяти. А там как раз лежат именно нами прописанные NOP-ы.

Прислал Slava V.

_______________________

Вот еще одно интересное письмо, на которое стоит дать ответ в рассылке:

Здравствуйте Олег,

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

Зачем нужен Ассемблер? Что он может делать, что не сможет сделать любой другой язык?

Ведь это неинтересно изучать то, что не понимаешь!

Прислал Роман.

И в самом деле, друзья! Я ведь даже и словом не обмолвился о том, чем же Ассемблер лучше других языков, какие его плюсы и какие минусы? Полагая, что многие уже догадались, я, тем не менее, перечислю достоинства и недостатки Ассемблера:

Плюсы:

Программа, написанная на Ассемблере, максимально быстро работает (в 50-200 раз быстрее Бейсика и в 3-20 раз быстрее С++);

Код программы максимально компактен;

Позволяет сделать то, что ни один язык высокого уровня не способен сделать.

Минусы:

Больше времени затрачивается на написание программы;

Код длиннее, чем в других языках;

Сложнее любых других языков.

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

Наибольший эффект (я бы сказал, оптимальный вариант) достигается при комбинировании двух языков: Pascal+Assembler или C+Assembler. Это особенно становится актуальным при программировании под Windows.

Кстати, о Windows. После выхода предыдущего выпуска ко мне стало приходить много писем с просьбой быстрее перейти к Win32. Я не спорю, это интересней на первый взгляд. Стоит отметить, что программирование под Win32 на Ассемблере мало чем отличается от программирования на языках высокого уровня. Если в DOS скорость работы зависит от использования языка и профессионализма программиста, то в Win скорость зависит только от операционной системы.

Многие мне возразят: зачем, мол, учитывать скорость, считать такты, оптимизировать программы, уменьшать код, если и так большинство людей используют Pentium-200 (32Мб / 6Гб) и выше, где плюс-минус 1000 тактов на глаз не заметно?

Вот, что я могу сказать. Дома у меня, к сожалению, пока стоит 486DX2-80. Программа WinAmp (кажется, такое название) не успевает проигрывать MP3-файлы (задержки большие). Но другая программа (Xing Mpeg) воспроизводит их прекрасно! Алгоритмы разные. В любом случае хочется, чтобы ваша любимая игра или программа работала быстрее...

В связи с тем, что многие очень хотят научится писать программы под Windows, я провожу на сайте голосование по этому поводу:

Как будем изучать Ассемблер:

1. Только DOS;

2. Быстро DOS, а затем Win

3. Сразу Win.

Несколько комментариев.

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

1. Оболочка (на которую, судя по всему, будем делать акцент, хотя голосование еще продолжается);

2. Вирус;

3. Резидент.

Думаю, что изучить только DOS будет не интересно. По крайней мере, наш курс будет неполным без Win.

Изучать сразу Win будет очень тяжело для тех, кто вообще не знаком с Ассемблером.

На мой взгляд оптимальный вариант 2: Быстро DOS, а затем Win.

Тем не менее, выбор за вами. Те, кто уже посетил сайт и проголосовал (всего 39 человек), считают именно второй вариант лучшим. Но, прошу вас, голосуйте еще! Уроки-то вы строите, а не я.

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

прерывания;

работа с файлами и дисками;

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

немного графики;

резиденты;

структура DOS в общих чертах;

переменные;

способы адресации;

строковые операции;

управление флагами;

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

клавиатура;

мышь;

принтер;

управление памятью (UMB, HMA, EMS, XMS);

загрузка и выполнение программ;

командные параметры и переменные среды;

программирование на уровне портов ввода-вывода;

и пр.

Много? Но зато как интересно!!!

Если большинством голосов победит второй вариант (быстро DOS, а затем Win), то все это мы пройдем быстрее (в одном выпуске, где это возможно, рассмотрим 2-3 темы).

Здесь есть несколько "но":

1. Вам нужно будет запастись программой-ассемблером для Win. В принципе, я помещу "голые" файлы на сайт, но этого будет недостаточно. Я рекомендую найти и скачать MASM 6.14 вот здесь: http://win32asm.newmail.ru. Хотя это не к спеху...

2. У меня нет опыта программирования под Win. Поэтому изучать будем вместе. Можно создать хороший форум, где будут обсуждаться вопросы, проблемы и пр. Получится у нас что-то вроде клуба программистов на Ассемблере. Как вы думаете? Можно присылать мне пожелания по следующему адресу.

oleg77@online.ru?Subject=Клуб программистов

___________________

Еще хотел бы сказать по поводу Интернета. В офисе, где я работаю в настоящий момент, в Интернет можно выйти только по модему. Со следующей недели я буду находиться в другом офисе, где Интернет будет по световому кабелю и бесплатно для меня. Я буду висеть в сети с 10:00 до 19:00 по Московскому времени (если куда-нибудь не отлучусь на время - работа все-таки). В связи с этим сообщаю вам номер своей ICQ: 68951340. Обращаю ваше внимание на то, что пока никакие программы не настроены. На установку и настройку может уйти несколько дней. Так что доступ ко мне может быть не с понедельника. Вероятно, и e-mail поменяется. Но я об этом сообщу немедленно, хотя текущий будет еще действовать около месяца после перехода на новый.

Может случиться так, что я не смогу выйти на связь в течение нескольких дней. Причиной может служить следующее: попал в аварию, сломал нос, вывихнул ухо и т.п., лежу дома, пью лекарство. Хотя самое страшное, что может произойти (боюсь подумать!) - отключение от Интернет...

Вот, вроде бы все, что хотел сказать.

Программа из прошлого выпуска.

Спасибо всем, кто прислал мне свои варианты решения! Некоторые я поместил на сайт. Хотел бы попросить: указывайте, пожалуйста, в письме, стоит ли опубликовывать ваш e-mail.

В принципе, ничего сложно в ней не было. Единственное, на что я хотел обратить ваше внимание, это на перевод шестнадцатеричных чисел в десятичные.

Совет такой: необходимо запомнить (или запомнится само со временем) некоторые частоиспользуемые шестнадцатеричные числа и десятичные:

20h - 32

100h - 256

1Bh- 27

21h - 33

и пр.

...

(1) call Wait_key ;ждем клавишу...

(2) cmp al,27 ;это ESC?

(3) je Quit_prog ;если да - то на метку Quit_prog (quit - выход; prog (program) - программа)

(4) cmp al,0 ;код клавиши расширенный? (F1-F12 и т.п.)

(5) je Begin ;да - повторим запрос...

(6) call Out_char ;вызываем процедуру вывода нажатой клавиши на экран

(7) jmp Begin ;ждем дальше....

(8) Quit_prog: ;метка, на которую придет программа в случае нажатия ESC

(9) mov al,32 ;помещаем в AL <пробел>

(10) call Out_char ;вызываем процедуру вывода символа в AL (в данном случае - пробела). Здесь мы как бы "обманываем" процедуру Out_char, которая нужна для вывода нажатого символа на экран. Мы симулируем нажатие клавиши пробел и вызываем процедуру. Подумайте над этим...

(11) int 20h ;выходим...

(12) ...

(13) ; --- Out_char --- ;процедура (комментарий)

(14) Out_char proc ;начало

(15) push cx ;сохраним все регистры, которые будут изменены подпрограммой...

(16) push ax ;...сделаем это для того, чтобы в последствии не было путаницы

(17) push es ;сохраним сегментный регистр

(18) push ax ;сохраним AX, т.к. в нем код нажатой клавиши...

(19) mov ax,0B800h ;установим ES на сегмент видеобуфера

(20) mov es,ax

(21) mov di,0 ;DI - первый символ первой строки

(22) mov cx,2000 ;выводим 2000 символов (80 символов в строке * 25 строк)

(23) pop ax ;восстановим код клавиши (см. строку 18)...

(24) mov ah,31 ;цвет символа

(25) Next_sym: ;метка для цикла

(26) mov es:[di],ax ;заносим код клавиши и ее цвет (цвет всегда 31)

(27) inc di ;увеличиваем указатель на 2 (первый байт - символ, второй байт - цвет)

(28) inc di

(29) loop Next_sym ;обработка следующего символа

(30) pop es ;восстановим сохраненные регистры и выровним стек

(31) pop ax

(32) pop cx

(33) ret ;вернемся из процедуры

(34) Out_char endp

...

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

Программа делает следующее:

ждет от пользователя клавиши;

если это расширенный ASCII (F1-F12, стрелки), то игнорирует ее;

если это не расширенный ASCII (A-Z, 0-9 и т.п.) - заполнить экран данным символом;

если нажимаем ESC (27 или 1Bh), то заполнить экран пробелами (mov al,32) и выйти.

Нич-чего сложного...

Немного теории.

Пришло время рассмотреть работу с файлами.

Для того, чтобы прочитать содержимое файла необходимо вначале открыть его. Это позволяет сделать функция 3Dh прерывания 21h:

Функция 3Dh прерывания 21h - открытие файла:

Вход: AH = 3DhAL = тип открытия (00 - только чтение, 01 - только запись, 02 - чтение / запись)

DS:DX = адрес ASCII-строки с именем файла

Выход: AX - номер файлаJC - ошибка

Итак, на входе AL должен содержать тип открытия (что мы будем делать с файлом: только прочитаем в память, только запишем что-нибудь или будем делать и то и другое). Естественно, при открытии файла для чтения / записи (AL=3) мы не обязаны прочитать его, а затем что-то записать. Можно просто записать, можно просто прочитать, а можно вообще ничего не делать.

Однако, следует иметь в виду, что если мы попытаемся открыть файл с атрибутом "Только чтение" ("read-only") для записи (AL=2) или для чтения / записи (AL=3), то функция вернет ошибку.

Следующий код открывает файл для чтения / записи:

...

mov ax,3D02h

mov dx,offset File_name

int 21h

...

File_name db 'command.com',0

Обратите внимание, что мы в AX загружаем сразу два числа: 3Dh и 02h. Это будет работать быстрее, если бы мы делали так:

mov ah,3Dh

mov al,02h

Строку

File_name db ....

можно помещать где угодно. Главное, чтобы она не перемешивалась с кодом. Я это уже объяснял, но, тем не менее, ко мне приходят вопросы по этому поводу. Например, нельзя делать так:

...

mov ax,3D02h

mov dx,offset File_name

File_name db 'command.com',0

int 21h

...

В данном случае, программа сассемблируется без ошибок, но зависнет при запуске. Процессор распознает 'command.com' как набор инструкций, а не строку символов. Скорее всего, это не имеет никакой логики...

Имя файла можно указывать даже так:

File_name db 'C:\ASSM\command.com',0

ПРОПИСНЫЕ и строчные символы значения не имеют. Можно записать и так:

My_file db 'a:\myfile.doc',0

Если диск и путь к файлу опущен, то программа будет искать файл в текущем каталоге.

Теперь о том, что возвращает функция. Вот код:

...

mov ax,3D00h

mov dx,offset Just_file

int 21h

...

Just_file db 'file',0

Допустим, в текущем каталоге файла "file" не было найдено. Тогда, функция 3Dh устанавливает в единицу флаг переноса (помните схожую ситуацию с флагом нуля из прошлых выпусков?). Если же файл все-таки найден и успешно открыт, то флаг переноса устанавливается в нуль.

Для проверки состояния флага переноса используется оператор JC (Jump if Carry - переход, если установлен флаг переноса) и JNC (Jump if Not Carry - переход, если флаг переноса не установлен):

...

int 21h

jc Error

Ok:

....

Error:

...

или так:

...

int 21h

jnc Ok

Error:

...

Ok:

...

Естественно, вместо меток Ok и Error (ошибка) можно задавать любые другие имена.

Вы уже можете сделать вывод, что JC и JNC - команды условного перехода.

Все функции прерывания 21h устанавливают в единицу флаг переноса, если произошла ошибка и сбрасывают его, если ошибки не было.

Вот полный пример открытия файла:

...

mov ax,3D00h

mov dx,offset File_name

int 21h

jc Bad_file

mov dx,offset Mess1

Quit_prog:

mov ah,9

int 21h

int 20h

Bad_file:

mov dx,offset Mess2

jmp Quit_prog

Далее. При успешном открытии файла в AX возвращается уникальный идентификационный номер файла. В дальнейшем, при обращении к данному файлу, будет указываться не его имя, а этот номер. После вызова функции 3Dh сохраните номер файла!

После того, как мы закончили работу с файлом (записали или прочитали что-нибудь), его необходимо закрыть функцией 3Eh прерывания 21h:

Функция 3Eh прерывания 21h - закрытие файла:

Вход: AH = 3EhBX - номер файла

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

Не забывайте закрывать файл!

mov ah,3Eh

mov bx,Handle

int 21h

Файл закрыт.

Обратите внимание на запись mov bx,Handle. Здесь Handle - это переменная, в которую необходимо будет занести номер файла после открытия. Переменные мы подробно рассмотрим в следующих выпусках, а сейчас коснемся только того, как создать переменную Handle. Вот пример:

Handle dw 0

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

...

mov ax,3D00h

mov dx,offset File_name

int 21h

jc Error

mov Handle,ax

; файл открыт успешно...

mov ah,3Eh

mov bx, Handle

int 21h

;файл закрыт

Error:

int 20h

...

Handle dw 0

...

__________

Для чтения информации из файла используется функция 3Fh, а для записи в файл - 40h. При этом BX должен содержать тот самый номер файла (Handle), CX - количество читаемых или записываемых байт, DS:DX - адрес буфера для чтения / записи.

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

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

Итак, вот образец чтения файла (до 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

Из этого примера вы узнаете очень много. Еще раз хочу сказать: пользуйтесь отладчиком! Смотрите адреса, которые загружаются в DX. Там будет много чего интересного!!!

Ваши решения принимаются здесь: oleg77@online.ru?Subject=Решение

В течение следующей недели они будут помещаться на сайт. Пожалуйста, указывайте также, хотите ли вы, чтобы на сайте был опубликован ваш e-mail.

До встречи, друзья мои!

С уважением,

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

Сайт: www.Kalashnikoff.ru

[Следующий выпуск] [На главную страницу]

u="u496.71.spylog.com";d=document;nv=navigator;na=nv.appName;p=1; bv=Math.round(parseFloat(nv.appVersion)*100); n=(na.substring(0,2)=="Mi")?0:1;rn=Math.random();z="p="+p+"&rn="+rn;y=""; y+=""; y+=""; y+=""; d.write(y);if(!n) { d.write("

Соседние файлы в папке Выпуски