← Октябрь 2000 → | ||||||
1
|
||||||
---|---|---|---|---|---|---|
3
|
4
|
5
|
6
|
7
|
8
|
|
9
|
11
|
12
|
13
|
14
|
15
|
|
16
|
17
|
19
|
20
|
21
|
22
|
|
23
|
24
|
25
|
27
|
28
|
29
|
|
30
|
31
|
За последние 60 дней ни разу не выходила
Сайт рассылки:
http://rusfaq.ru
Открыта:
28-06-2000
Статистика
0 за неделю
Ассемблер? Это просто! Учимся программировать Выпуск N 012
Кваса. Снова кваса глоток хлебану. Знаю, что рассылку сейчас получу. Нужно побыстрее мне в Сеть лишь войти. Чтоб там новый выпуск найти. ________ Где ж ты моя, долгожданная, где? Слюни текут уже по бороде. Вот ты! Ща тебя я прочту. Что у нас сегодня? Пожалуйста, возьмите предыдущие выпуски по одному из следующих адресов: http://www.oleg77.newmail.ru (здесь также можно найти все необходимое программное обеспечение для работы с Ассемблером). http://www.subscribe.ru/archive/comp.prog.assembler Если у Вас нет доступа в Сеть, то напишите мне письмо с просьбоей выслать тот или иной выпуск либо все вместе: assembler@beep.ru?Subject=Выпуски Друзья мои! У нас существенно изменился внешний вид сайта. В этом мне помог BoB, за что ему огромное спасибо, а также море благодарности. Теперь там есть Гостевая книга, где вы можете лично оставить свое впечатление, пожелание, критику и прочее. Появился также раздел писем, которые вы можете отправлять сами на сайт (например, решение программы, мысли, заметки и прочее). Единственная проблема: закачать все это дело на сайт www.oleg77.newmail.ru . Думаю, что на этой неделе я все сделаю... Если вам интересно, то можете попозже посмотреть одним глазком. Все ваши отклики принимаются тут: assembler@beep.ru?Subject=Сайтик Я уже не раз писал, что ко мне очень много приходит писем. Если бы в сутках было 124 часа, то я, возможно, смог бы всем ответить. Я прошу прощения, если кто-то не получил ответ. У меня действительно нет времени отвечать на все письма. Так как рассылка уже выходит относительно давно, то у меня сложилась определенная система ответа на ваши письма. Итак, в первую очередь я отвечаю на письма:
Во вторую очередь я отвечаю на следующие письма:
Я НЕ отвечаю на письма такого плана:
Подобные письма приходят пачками. Я просто физически не могу ответь на все. Еще не забывайте, что у меня работа, учеба, сын и куча проблем. Пожалуйста, не обижайтесь, если я кому-нибудь не отвечу. Единственное, что могу посоветовать: купите книгу по DOS и читайте. Ничего сложного там нет. Еще один момент. Т.к. я пользуюсь бесплатными ящиками (beep.ru, hotmail.ru), которые не несут ответственность за предоставляемые услуги, то, возможно, что ваше письмо не дойдет до меня. Если я не отвечу в течение 3 дней, то вышлите его повторно, пожалуйста. ______________ ВНИМАНИЕ! У меня довольно-таки много адресов находится в моей адресной книге Outlook'а. Не исключено, что у меня может появиться вирус, который я сразу и не замечу. Пожалуйста, не открывайте письма, которые могут прийти от моего имени с таким содержанием в поле "Тема" (или примерно таким): "Вышлите мне стольник и получите лимон!" "Все вы козлы, а я - Д'Артаньян!" "Посмотри мои фотки в проложении!" "Предлагаю крутую работу за сумасшедшие деньги! Подробности в приложении." и прочее. Я таких писем не отправляю и их не читаю, чего и вам советую. Если же все-таки к вам пришло подобное письмо от моего имени, то, пожалуйста, НЕМЕДЛЕННО сообщите мне об этом. Буду очень признателен. _____________ Я получил письмо от одного из подписчиков на позапрошлой неделе, который указал на ошибку в выпуске N 009 (когда читали сами себя в память). Эх, какую я допустил досадную логическую ошибку! А заметил-то всего один подписчик, который мне долго объяснял ее суть. Ай-да, молодец, Серега! Итак, вот оно, это письмо: Здравствуйте, Олег! Простите за назойливость, но.... Боюсь, что я не очень правильно поставил вопрос. Попробую сначала. Thursday, October 05, 2000, 1:24:51 PM, you wrote: КО> | Open_File proc КО> | cmp Handle, 0FFFFh ;Если в Hanlde
не 0FFFFh (см. ниже) то снова
открывать не надо. Именно об этом эффекте я и говорил. А ведь после открытия файла Handle точно не FFFF (по крайней мере в дебаггере). А вот перед закрытием - FFFF. То есть остается предположить, что когда мы перечитываем файл, Handle становится FFFF. Почему так - я пытался описать раньше. Best regards, Sergey ______________ Вот еще одно предложение: Привет, Олег! Хочу подсказать одну нуууууу очень хорошую книгу: П.Абель "Програмирование на языке Ассемблера". Можешь стянуть ее по адресу www.chat.ru/~rusdoc По моему она в 2 раза лучше Жордейна. Я в свое время на ней учился. ______________ Ну и еще одно письмецо. Нужна помощь одному из наших подписчиков. Может, кто подскажет чего: И вот еще - обращаюсь с просьбой о помощи (хотелось бы чтобы оно было опубликовано в рассылке)! Мне срочно нужно описание BIOS (Сетапа) как и что там делать - от этого зависит моя "жизни". Дело в том , что наш системный администратор "слинял" а на свое место некого не поставил! И Все проблемы свалились на меня! solomaxa@rambler.ru Теперь к делу. Возьмите, пожалуйста, файл здесь: http://www.oleg77.newmail.ru/Assembler/Programs/Lessons/Resid12.rar Если у вас нет выхода в Сеть, то напишите мне письмо с просьбой выслать. Я также включу ваш адрес в базу данных. Затем перед выходом очередной рассылки вы получите этот файл по почте. Сегодня рассмотрим один из вариантов применения резидента. Сперва посмотрите файлы resid12.asm и test12.asm. Что делает программа? Сначала загрузим файл resid12.com в память. Затем запустим файл test12.com. Что мы видим? Resid12.com оставляет в памяти процедуру, которая выводит на экран строку ASCIIZ (строка, заканчивающаяся символом 0). Причем, процедура Int_10h_proc "вешается" на 10h прерывание (это прерывание BIOS (ПЗУ), как вы уже знаете). Нечто похожее мы рассматривали в позапрошлом выпуске. В данном случае мы как бы добавляем еще одну функцию (88h) к прерыванию 10h. Для того, чтобы она отработала, нам нужно вызвать 10h прерывание, а в регистры загрузить необходимые числа (данные). Это: AH=88h - номер нашей функции; DS:SI = адрес строки, которую нужно вывести на экран (DS - сегмент, SI - смещение). Обратите еще раз внимание, что мы в DS (сегмент) ничего не загружаем, только в SI (смещение). Если вы забыли, то напомню: при старте любого *.com-файла, сегментные регистры CS, DS, ES, SS равны нашему единственному сегменту, в который загрузилась программа. Сегмент может быть любым. Все зависит от того, сколько резидентных программ загружено. Итак, давайте подробно рассмотрим программу resid12.asm. Сперва переходим на метку инициализации. При инициализации обычно настраиваются необходимые регистры, перехватываются прерывания и пр. Т.е. происходит "подготовительный процесс". Перво-наперво проверим, загружен ли наш резидент в память или нет. Как это делается? Т.к. мы перехватили прерывание, то можем из него сделать отклик. Вот мы и делаем. Вызываем 10h прерывание, занеся в регистр AX "магическое число": 8899h (можно любое другое. Главное - чтобы не конфликтовало с какой-нибудь функцией данного прерывания). Понятно, что в AH заносится 88h, а в AL - 99h. Функции 88h у 10h прерывания не существует (не дошли пока разработчики до этого числа). Это можно с уверенностью предположить на 99,9%. Т.к. такой функции не существует, то произойдет немедленный выход из 10h прерывания (регистры не меняются!), что легко проверить в отладчике. Следовательно, вызвав 10h прерывание с числом 8899h в AX и получив в ответ 8899h в том же AX, мы уверены, что наш резидент еще не загружен. Сейчас все будет понятно. Если мы уже находимся в памяти, перехватив 10h прерывание, то прежде всего проверим, вызывют ли его с числом 8899h в AX. Если так, то нужно будет послать какой-нибудь ответ. Какой? В нашем примере мы меняем местами содержимое регистров AH и AL и немедленно выходим из прерывания. Проще говоря, возвращаемя в нашу программу, которая в свою очередь проверяет, вернулось ли число 8899h или 9988h (помните, что если нашего резидента нет в памяти, то вернется 8899h в AX. BIOS регистры не изменит, т.к. такой функции просто нет!). Если же вернулось 9988h, то значит мы в памяти! Т.е. кусочек нашей процедуры Int_10h_proc поменял местами регистры (а кто еще может это сделать?). Смотрите первые строки процедуры (часть нашего резидента): pushf ;сохраним флаги cmp ax,8899h ;проверим на "магическое число" jne Next_test ;если не оно, то работаем дальше xchg ah,al ;иначе меняем местами AH/AL popf ;восстановим флаги iret ;и выйдем Итак, новая инструкция: xchg
Пример: mov ax,10h mov bx,15h xchg ax,bx Теперь AX=15h, BX=10h. Все элементарно! Мы сейчас рассматриваем одну из самых сложных тем: механизм работы прерываний. Я понимаю, что для многих из вас это очень трудно (особенно для тех, кто не знаком ни с одним языком программирования). Поэтому я постараюсь как можно проще объяснить принцип работы прерываний: Как уже вам известно, сегментный регистр CS (Code Segemt - сегмент кода) всегда содержит номер сегмента, в котором находится наша программа, а IP (Instruction Pointer - указатель инструкций) - смещение. Допустим, вектор (адрес) 10h прерывания находится по адресу 0010:0400h, а наша программа загрузилась по адресу 1234:0100h. Тогда сразу после загрузки: CS:IP = 1234:0100h 1234:0100 mov ax,8899h --- теперь (после выполнения данной инструкции) CS:IP=1234:0103h 1234:0103 int 10h --- теперь CS:IP = сегменту/смещению адреса (вектора) 10h прерывания, т.е. 0010:0400h. 1234:0105 mov bx,10 Остальные регистны не изменятся (кроме SP, т.к. перед вызовом прерывания компьютер кладет в стек адрес следующей команды, чтобы потом на нее вернуться. Примерно, как при вызове процедуры, с той лишь разницей, что при вызове процедуры в стек кладется только смещение (а зачем сегмент, если и так в одном сегменте с программой находится процедура?). При вызове же прерывания менятеся как сегмент (CS), так и смещение (IP), которые компьютер и кладет в стек. Поэтому инструкция выхода из подпрограммы и из прерывания разные: ret и iret соответственно. Ret достает со стека только слово (два байта - смещение - IP), а iret достает еще и сегмент (CS+IP)). Что дальше? А дальше все просто: выполняется 10h прерывание до тех пор, пока не встретится инструкция iret, которая вытащит из стека сегмент/смещение инструкции на которую нужно вернуться, в нашем случае - это 1234h (CS - сегмент) и 0105h (IP - смещение). Проще говоря, в CS:IP загружаются из стека данные значения, при этом SP увеличивается на 4 байта (стек-то снизу вверх растет!). Очень просто все смотрится на практике. Попробуйте запустить наш резидент (который скачали) в отладчика. Внимательно следите за состоянием регистров CS:IP. Вы заметите, что они постоянно меняются. Обратите внимание, какие значения принимают они при вызове прерываний (для этого нужно зайти внутрь прерываний; в AFD - F1, в CV - F8). Ну что, сложно? Теперь рассмотрим некоторые новые инструкции, встречающиеся в нашем резиденте: stos, lods, rep. Это очень мощные и быстрые команды Ассемблера. Они предназначены для работы с массивами данных (строки, данные любого типа, числа и пр.). Инструкция lods загружает в регистр AX/AL число, которое находится по адресу, указанному в регистрах DS:SI, при этом SI увеличивается на 1 или на 2. Почему я написал AX/AL? Дело в том, что эта инструкция имеет две разновидности: lodsb и lodsw. Lodsb (B - byte) загружает в AL, а stosw (W - word - слово) - в AX. Пример: ... mov si,offset String ;SI указывает на начало String, т.е. на '1' lodsb ;теперь в AL - символ '1' (31h); SI=SI+1, т.е. SI теперь указывает не на '1', а на '2' lodsw ;теперь AX содержит '32' (3332h); SI=SI+2, SI указывает на '45' ... String db '12345' Поняли принцип? Если последний символ в инструкции lods 'b' - то загружается один байт в AL, и SI увеличивается на 1. Если же последний символ 'w', то загружается два байта (слово) в AX, и SI увеличивается на два. Здесь возникает два вопроса: 1. Почему не загружаем ничего в DS? 2. Почему после команды lodsw в AX содержится '32', а не '23', что было бы вполне логичней? Когда указывается число в кавычках ('32'), то это значит, что оно занимает два байта. Т.е. один байт - '3', а второй - '2'. Превый вопрос уже ясен для многих: если String находится в том же сегменте, в котором наша программа, и он не менялся в процессе работы, то в него ничего загружать не надо (в нем и так находится нужный сегмент. Ведь при запуске *-com программы все сегментные регистры равны нашему единственному сегменту). Второй вопрос заслуживает подробного рассмотрения. Дело в том, что компьютере двух- и более байтовые числа храняться в обратном порядке. Путаницы не возникает, если к двухбайтовому числу (к слову) мы обращаемся как к двухбайтовому, а к четырехбайтовому (к двойному слову) мы обращаемся как к четырехбайтовому. Вот примеры: Handle dw 1234h --- изначально присваиваем переменной значение 1234h. В памяти это число расположится в таком порядке: 3412h. Проверьте в отладчике... mov ax,Handle --- AX=1234h mov al,byte ptr Handle --- AL=34h mov al,byte ptr Handle+1 --- AL=12h Byte ptr, как вы уже знаете, обозначает то, что мы хотим загрузить один байт с переменной двухбайтового типа (Handle dw 1234h - DW - Define Word - определить слово (два байта)). Но повторю еще раз: путаницы, как правило, не возникает. И вы поймете это скоро. Исходя из вышесказанного, рассмотрим, почему мы при сохранении вектора прерывания 10h заносим вначале смещение, а затем сегмент, хотя логичнее было бы наоборот: ... mov ax,3510h ;получим адрес (вектор) 10h прерывания. int 21h ;теперь ES содержит сегмент, а BX - смещение... mov word ptr Int_10h_vect,bx ;сохраним сперва смещение mov word ptr Int_10h_vect+2,es ;а затем сегмент, учитывая, что данные храняться в обратном порядке ... Int_10h_vect dd ? ;переменная на два слова (четыре байта) Word ptr указывает на то, что нужно занести слово в переменную Int_10h_vect. Обратите внимание, что данная переменная имеет тип DD - Define Double word - определить двойное слово. Но мы-то заносим одно слово (ES или BX)! Для этого и указываем ассемблеру на то, что заносится только слово, иначе он "не поймет". Уфф!!! Перекусим и поедем дальше... Выяснили, что команда lodsb загружает в AL однобайтовое число, находящееся по адресу DS (сегиент):SI (смещение). В принципе, данная команда аналогична следующей паре инструкций: mov al,ds:[si] inc si (или add si,1) только работает она гораздо быстрее, да и занимает меньше байт. По аналогии: команда lodsw загружает в AX двухбайтовое число, расположенное также по адресу DS:SI. Она эквивалентна паре инструкций: mov ax,ds:[si] add si,2 Как правило такие команды (lodsb / lodsw) работают в цикле для чтения значений из строки (или другого массива данных). Массив данных - цепочка символов, расположенных последовательно друг за другом. Например, следующая строка является своего рода массивом данных: Data_array db 'Это массив данных' Рассмотрим еще пару подобных команд, которые используются в нашем резиденте: stosb / stosw. Stosb заносит однобайтовое число из AL по адресу ES:DI, а stosw - двухбайтовое число по тому же адресу. Пример: mov si,offset Data_array mov ax,2030h stosw ... Data_array dw ? ;теперь в этой переменной находится число 2030h что равносильно команде: mov Data_array,2030h Например, занесем в верхний левый угол экрана нашу известную "рожицу", используя данные команды. Вот как это выглядит: ... mov ax,0B800h mov es,ax mov di,0 mov ah,07h ;атрибут символа (белый на черном) mov al,01h ;(сам символ - "рожица") stosw ;заносим, что эквивалентно: mov es:[di],ax Насколько мы знаем, видеобуфер имеет следующую структуру: символ:атрибут символ:атрибут и т.д. Если внимательно присмотреться, то можно заметить, что в AH мы загружаем атрибут, а в AL - символ. Получается, что сперва заносится атрибут (т.к. AH - старшая (левая) половинка), а затем символ. Что-то не так... Стоит вспомнить с сегодняшнего урока, что в компьютере двух и более байтовые данные (а AX - два байта) хранятся в обратном порядке. Получится, что при переносе AX в сегмент видеобуфера, символ и атрибут поменяются местами! Понятно это? Далее. Рассмотрим т.н. префикс rep. Что он делает? Допустим, нам надо очистить экран. Это можно сделать, используя команду stosw (просто забьем экран пробелами): ... mov ax,0B800h mov es,ax mov di,0 ;как обычно с верхнего левого угла mov cx,2000 ;80х25=2000 символов на экране mov ax,0720h ;07 - атрибут, 20h - пробел Next_sym: ;заносим посимвольно stosw loop Next_sym ;теперь экран чистый Неуклюже немного. Да и работать будет не так быстро, хотя на глаз и не заметно. Попробуем воспользоваться префиксом rep. Вот, что у нас получится: ... mov ax,0B800h mov es,ax mov di,0 mov cx,2000 ;CX - counter - счетчик mov ax,0720h ;атрибуты / символ rep stosw ;выводим пробел столько раз, сколько указано в CX. ... Все! Экран чистый. Очистка происходит мгновенно. Можете попробовать... После выполнения последней команды (rep stosw) CX будет содержать 0. REP (REPeat - повтор) от CX неразделимы, как LOOP от CX! Подведем итог: одна команда stosw запишет только два символа, находящихся в AX по адресу ES:DI. команда rep stosw запишет два символа, находящихся в AX по адресу ES:DI столько раз, сколько находится в регистре CX. Просто? Да элементарно! Пробуйте в отладчике. Там все видно, как на ладони... Еще покажу вам одну маленькую "фишку". Для того, чтобы обнулить регистр, можно воспользоваться такой командой, которая выполняется быстрее: xor ax,ax ;равносильно mov ax,0 А можно и так: sub ax,ax ;вычтем из AX AX. Например, AX=23. Что будет, если мы сделаем так: AX = AX - AX? Если правильно посчитал мой калькулятор, то будет 0. Обычно эти команды (xor ax,ax / sub ax,ax) выполняются быстрее mov ax,0. Поэтому не удевляйтесь, если где-то в программе встретите нечто подобное. XOR - исключающее ИЛИ. Это логическая команда. Подобные инструкции мы рассмотрим позже. Пока что вам нужно уяснить, что XOR'ом и SUB'ом можно аннулировать регистры. Ну что? Загрузил я вас сегодня? Думаю, что вирус мы рассмотрим на следующей неделе. Нескучной вам ночи!!! P.S. Домашнее задание. Исследовать программы RESID12.ASM и TEST12.ASM в отладчике (лучше в AFD) "от корки до корки". С уважением,
|
http://subscribe.ru/
E-mail: ask@subscribe.ru |
В избранное | ||