что делает резидентная программа
Резидентная программа
Резидентная программа (или TSR-программа, от англ. Terminate and Stay Resident — «завершиться и остаться резидентной») — в операционной системе MS-DOS программа, вернувшая управление оболочке операционной системы (command.com), либо надстройке над операционной системой (Norton Commander и т. п.), но оставшаяся в оперативной памяти персонального компьютера. Резидентная программа активизируется каждый раз при возникновении прерывания, вектор которого эта программа изменила на адрес одной из своих процедур.
При работе с MS-DOS резидентные программы широко использовались для достижения различных целей (например, русификаторы клавиатуры, программы доступа к локальной сети, менеджеры отложенной печати, вирусы).
По способу инициализации и вызова операционной системой резидентные программы необходимо отличать от «настоящих» драйверов MS-DOS, встраиваемых операционной системой в своё ядро во время загрузки. В эпоху многозадачных ОС резидентными иногда называют программы, загруженные постоянно и работающие в фоновом режиме. Но применение этого термина некорректно по отношению к многозадачным ОС.
Содержание
Основные понятия
Резидентные программы могут переключать на себя обработку прерываний, например, связанных с выводом на печать или с обращением к клавиатуре и т. д.
Такие программы тоже обычно запускаются через файл autoexec.bat или при необходимости. Они перехватывают прерывания, предназначенные для работы с клавиатурой. Как только пользователь нажимает заранее определенную комбинацию клавиш, резидентная программа активизируется. Поверх имеющегося на экране изображения выводится диалоговое окно резидентной программы.
Иногда резидентные программы используют вместо загружаемых драйверов для обслуживания нестандартной аппаратуры. В этом случае резидентная программа может встроить свой обработчик, через который все прикладные программы смогут обращаются к аппаратуре.
Аналогично работают резидентные модули некоторых систем управления базами данных (СУБД). Прикладная программа посылает запросы к базе данных через прерывание, устанавливаемое при запуске такой СУБД.
На резидентные программы накладываются многочисленные ограничения, затрудняющие работу программиста.
Например, резидентным программам не разрешается использовать прерывания MS-DOS, когда вздумается. Это связано с тем, что MS-DOS с самого начала проектировалась как однозадачная операционная система, поэтому функции прерываний MS-DOS не обладают свойством реентерабельности (повторной входимости).
Представьте себе такую ситуацию.
Пусть обычная программа вызвала какую-либо функцию прерывания MS-DOS, на выполнение которой требуется относительно много времени (например, запись на диск).
Так как пользователь может активизировать резидентную программу в любой момент, то если не принять специальных мер предосторожности, возможен повторный вызов той же самой функции, обработка которой еще не завершена. В этом случае мы получим повторный вызов функции MS-DOS, который недопустим из-за того, что функции MS-DOS не реентерабельны.
Функции BIOS также далеко не все реентерабельны. Резидентная программа может смело вызывать разве лишь прерывание INT 16h (которое предназначено для работы с клавиатурой). Если резидентной программе нужно вывести что-нибудь на экран, то вместо прерывания INT 10h следует выполнить непосредственную запись символов и их атрибутов в видеопамять.
Без принятия специальных мер предосторожности резидентная программа не может вызывать многие функции библиотеки транслятора, так как последние вызывают прерывания MS-DOS. Например, функция malloc вызывает прерывание MS-DOS для определения размера свободной памяти в системе.
Для использования прерывания INT 27h сегментный регистр CS должен указывать на PSP программы. При этом в регистр DX следует записать смещение последнего байта программы плюс один байт.
Нетрудно заметить, что этот способ больше всего подходит для com-программ, так как с помощью прерывания INT 27h невозможно оставить в памяти резидентной программу длиннее 64 Кбайт.
Для того чтобы оставить резидентной в памяти программу, размер которой превышает 64 Кбайт, вы можете использовать только последний метод. Но не стоит увлекаться большими резидентными программами, так как занимаемая ими память нужна другим программам.
Структура резидентной программы
Функции секции инициализации заключаются в следующем
Инициализация резидентной программы
Для использования прерывания 27h сегментный регистр CS должен указывать на PSP программы, а в регистре DX должно быть записано смещение последнего байта программы плюс один байт. Нетрудно заметить, что этот способ остаться резидентной больше всего подходит для программ в формате COM. Вы не сможете оставить резидентной программу длиннее 64 килобайт.
Но не стоит увлекаться длинными TSR-программами, так как обычно освободить память, занимаемую ставшей уже ненужной резидентной программой, можно только с помощью перезагрузки операционной системы.
Решение пpоблемы повтоpного запуска
Нужно опpеделить, была уже запущена TSR или нет. Возможно несколько ваpиантантов опpеделения запуска TSR:
Достоинство: Шиpокое использование. Недостаток: Набоp сигнатуpы достаточно огpаничен (сигнатуpа может случайно совпасть). Надежность меньше, чем у 2-го метода.
Взаимодействие новых и стаpых обpаботчиков пpерываний (ISR).
Возвpат осуществляется из стаpого обpаботчика. Возникает цепочка между обpаботчиками пpеpываний. Недостаток: Часто бывает необходимо, чтобы новые функции выполнялись после стаpых. По этой схеме это невозможно.
Уpовни сложности TSR и взаимодействие новых ISR дpуг с дpугом.
В зависимости от взаимодействия новых ISR выделяют pазличные уpовни сложности.
Путеводитель по написанию вирусов: 3. Резидентные вирусы
Если вы достигли эту строку и до сих пор живы, у вас есть будущие в мире под название вирусная сцена :).
Здесь начинается интересный для чтения (вам) и написания (мне) материал.
Что же такое резидентная программа?
Резидентная программа выполняется как нормальная программа, но она оставляет в памяти порцию себя, которая не освобождается после окончания программы. Резидентные программы (TSR = Terminate and Stay Resident) обычно замещают некоторые прерывания и помещают свои собственные обработчики, чтобы те выполняли определенные задачи. Как мы можем использовать резидентную программу? Мы можем использовать ее для хакинга (воровать пароли), для наших классных утилит… все зависит от вашего воображения. И, конечно, я не забыл… можно делать РЕЗИДЕHТHЫЕ ВИРУСЫ :).
Что может вам дать TSR-вирус?
TSR — не лучший способ вызывать вирусы, которые собираются быть резидентными. Представьте, что вы запустили что-нибудь и это возвратилось в DOS. Hет. Мы не можем прервать выполнение и стать резидентными! Пользователь поймет, что здесь что-то не то. Мы должны возвратить управление основной программе и стать резидентными :). TSR — всего лишь аббревиатура (неверно употребляемая, я должен добавить). Резидентные вирусы могут предложить нам новые возможности. Мы можем сделать наши вирусы быстрее распространяющимися, незаметными… Мы можем лечить файл, если обнаружена попытка открыть/прочитать файл (представьте, AV’шники ничего не обнаружат), мы можем перехватывать функции, используемые антивирусами, чтобы одурачить их, мы можем вычитать размер вируса, чтобы провести неопытного пользователя (хе-хе… и опытного тоже) ;).
В наши дни нет никаких причин, чтобы делать вирусы времени выполнения. Они медленны, легко обнаруживаются и они УСТАРЕЛИ :). Давайте посмотрим на небольшой пример резидентной программы.
Этот маленький пример не может быть использован для написания вируса. Почему? INT 27h, после помещения программы в память, прерывает ее выполнение. Это все равно, что поместить код в память и вызывать iNT 20h, или что вы там используете для завершения выполнения текущей программы.
И тогда… Что мы можем использовать для создания вируса?
Алгоритм TSR вирусов
Мы можем следовать этим шагам (воображение весьма полезно для создания вирусов…) :).
Проверка на резидентность
Когда мы пишем резидентную программу, мы должны сделать по крайней мере одну проверку, чтобы убедиться, что наша программа еще не находится в памяти. Обычно используется специальная функция, которая, когда мы вызываем ее, возвращает нам определенное значение (мы его задаем сами), или, если программа не установлена в память, она возвращает в AL 00h.
Давайте посмотрим на пример:
Если программа уже установлена в память, мы восстанавливаем зараженный файл носителя и передаем контроль оригинальной программе. Если программа не установлена, мы идем и устанавливаем ее. Обработчик INT 21h для этого вируса будет выглядеть так:
Резервирование памяти путем модификации MCB
Hаиболее часто используемый способ резервирования памяти — это MCB (Memory Control Block). Есть два пути, чтобы сделать это: через ДОС или сделать это HАПРЯМУЮ. Перед тем, как рассмотреть, что из себя представляет каждый способ, давайте посмотрим, что такое MCB.
MCB создается ДОСом для каждого управляющего блока, используемого программой. Длина блока — один параграф (16 байтов), и он всегда находится до зарезервированной памяти. Мы можем узнать местоположение MCB нашей программы, вычтя от сегмента кода 1 (CS-1), если это COM-файл, и DS — если EXE (помните, что в EXE CS <> DS). Вы можете посмотреть структуру MCB в главе о структурах (уже должны были видеть).
Использование DOS для модифицирования MCB
Метод, который я использовал в моем первом вирусе, Antichrist Suрerstar, очень прост и эффективен. Во-первых, мы делаем запрос ДОСу, используя функцию INT 21h для всей памяти (BX=FFFFh), что является невозможным значением. Эта функция увидит, что мы просим слишком много памяти, поэтому она поместит в BX всю память, которую мы можем использовать. Поэтому мы вычитаем от этого значения размер кода нашего вируса в параграфах (((size+15)/16)+1), а затем снова вызываем досовскую функцию 48h, с размером код в параграфах в BX. В AX будет возвращен сегмент зарезервированного блока, поэтому мы помещаем его в ES, уменьшаем значение AX и помещаем новое значение в DS. В нем у нас теперь находится MCB, которым мы можем манипулировать. Мы должны поместить в DS:[0] байт «Z» или «M» (в зависимости от ваших нужд, смотри структуру MCB), а в DS:[1] слово 0008, чтобы сказать DOS, что этот блок не нужно перезаписывать.
После некоторого количества теории будет неплохо посмотреть кое-какой код. Что-то вроде нижеследующего скорректирует MCB под ваши нужды:
Достаточно просто и эффективно… Тем не менее, это только манипуляции с памятью, а не перемещение вашего кода в память. Это очень просто. Hо мы увидим это в дальнейшем.
Прямая модификация MCB
Этот метод делает то же самое, но путь, которым мы достигаем нашей цели, другой. Есть одна вещь, которая делает его лучше, чем вышеизложенный метод: сторожевая резидентная AV-собака не скажет ничего относительно манипуляций с памятью, так как мы не используем никаких прерываний :).
Первое, что мы делаем, это помещаем DS в AX (потому что мы не можем делать подобного pода вещи с сегментами), мы уменьшаем его на 1, а потом снова помещаем в DS. Теперь DS указывает на MCB. Если вы помните структуру MCB, по смещению 3 находится количество текущей памяти в параграфах. Поэтому нам нужно вычесть от этого значения количество памяти, которые нам потребуется. Мы используем BX (почему нет?) ;). Если мы взглянем назад, то вспомним, что MCB на 16 байт выше PSP. Все смещения PSP сдвинуты на 16 (10h) байтов. Hам нужно изменить значение TOM, который находится по смещению 2 в PSP, но мы сейчас не указываем на PSP, мы указываем на MCB. Что мы можем сделать? Вместо использования смещения 2, мы используем 12h (2+16=18=12h). Мы вычитаем требуемое количеств памяти в параграфах (помните, размер вируса+15, деленный на 16). Hовое значение по этому смещению теперь является новым сегментом нашей программы, и мы должны поместить его в соответствующий регистр. Мы используем дополнительный сегмент (ES). Hо мы не можем просто сделать mov (из-за ограничений возможных действий с сегментными регистрами). Мы должны использовать временный регистр. AX прекрасно подойдет для наших целей. Теперь мы помечаем [ES:[0] «Z» (потому что мы используем DS как обработчик сегмента), и ES:1 помечаем 8.
После теории (как обычно, весьма скучной) будет неплохо привести немного кода.
Помещение вируса в память
Это самая простая вещь в написании резидентных вирусов. Если вы знаете, для чего мы можем использовать инструкцию MOVSB (и, конечно, MOVSW, MOVSD…), вы увидите, как это просто. Все, что мы должны сделать — это установить, что именно и как много нам нужно поместить в память. Это довольно просто. Hачало данных, которые нужно переместить, магическим образом равно дельта-смещению, поэтом если оно находится в BP, все, что мы должны сделать, это поместить в SI содержимое BP. И мы должны поместить размер вируса в байтах в CX (или в словах, если мы будем использовать MOVSW). Заметьте, что DI должно быть равно 0, чего мы добьемся с помощью xor di, di (оптимизированный способ сделать mov di, 0). Давайте посмотрим код…
После помещения вируса в память, мы должны модифицировать по крайней мере одно прерывание для того, чтобы выполнять заражение. Обычно это INT 21h, но если наш вирус бутовый, то мы также должны перехватить INT 13h. То есть от наших потребностей зависит то, какие прерывания мы будем перехватывать. Есть два пути перехвата прерываний: используя ДОС или прямой перехват. Мы должны учитывать следующее при создании собственного обработчика:
Мы можем сделать другую вещь. Мы можем перенаправить другое прерывание, сделав так, что оно будет указывать на старый INT 21h. Хороший выбором может стать INT 03h: это хороший прием против отладчиков, делает наш код немного меньше (INT 03h кодируется как CCh, то есть занимает только один байт, в то время как обычные прерывания кодируются как CDh XX, где XX — это шестнадцатеричное значение прерывания). Таким образом мы можем забыть о всех проблемах вызовов перехваченных функций. Когда мы готовы вернуть контроль первоначальному INT 21h.
Перехват процедур, используя DOS
Мы должны получить первоначальный вектор прерывания до того, как поместим наш вектоp. Это можно сделать с помощью функции 35h INT 21h.
После вызова она возвратит нам следующие значения :
После вызова этой функции мы сохраняем ES:BX в переменной в нашем коде для последующего использования и устанавливаем новый обработчик прерывания. Функция, которую мы должны использовать — 25 INT 21h. Вот ее параметры:
Давайте посмотрим пример перехвата прерывания, используя DOS:
Прямой перехват прерываний
Если мы забудем о DOS’е, мы выиграем несколько вещей, о которых я говорил ранее (в разделе о прямой модификации MCB). Вы помните структуру таблицы прерываний? Она начинается в 0000:0000 и продолжается до 0000:0400h. Здесь находятся все прерывания, которые мы можем использовать, от INT 00h до INT FFh. Давайте посмотрим немного кода:
Последние слова о pезидентности
Конечно, это не мои последние слова. Я еще много расскажу о заражении и еще о многом, но я предполагаю, что теперь вы знаете, как сделать резидентный вирус. Все, что излагается в остальных разделах этого документа, будет реализовываться в виде резидентных вирусов. Коенчно, если скажу, что что-то предназначается для вирусов времени выполнения, не кричите! 🙂
В конце этого урока я помещу пример полностью рабочего резидентного вируса. Мы снова используем G2. Это ламерский резидентный COM-инфектор.
Извините. Я знаю, я чертовски ленив. Вы можете считать, что это ламерский подход. Может быть. Hо подумайте о том, что пока я делаю этот документ, я пишу несколько вирусов и делаю кое-что для журнала DDT, поэтому у меня нет достаточного количество времени для создания собственных достойных вирусов для этого туториала. Эй! Никто не платит мне за это, вы знаете? 🙂
Иллюстрированный самоучитель по Assembler
Резидентные программы
Большой класс программ, обеспечивающих функционирование вычислительной системы (драйверы устройств, оболочки DOS, русификаторы, интерактивные справочники и др.), должны постоянно находиться в памяти и мгновенно реагировать на запросы пользователя, или на какие-то события, происходящие в вычислительной системе. Такие программы носят названия программ, резидентных в памяти (Terminate and Stay Resident, TSR), или просто резидентных программ. Сделать резидентной можно как программу типа.СОМ, так и программу типа .ЕХЕ, однако поскольку резидентная программа должна быть максимально компактной, чаще всего в качестве резидентных используют программы типа .СОМ.
Программы, предназначенные для загрузки и оставления в памяти, обычно состоят из двух частей (секций) – инициализирующей и рабочей (резидентной). В тексте программы резидентная секция размещается в начале, инициализирующая – за ней.
При первом вызове программа загружается в память целиком и управление передается секции инициализации, которая заполняет или модифицирует векторы прерываний, настраивает программу на конкретные условия работы (возможно, исходя из параметров, переданных программе при ее вызове) и с помощью прерывания DOS Int 21h с функцией 31h завершает программу, оставляя в памяти ее резидентную часть. Размер резидентной части программы (в параграфах) передается DOS в регистре DX. Указывать при этом сегментный адрес программы нет необходимости, так как он известен DOS. Для определения размера резидентной секции ее можно завершить предложением вида:
Где main – смещение начала программы, а при вызове функции 31h в регистр DX заслать результат вычисления выражения (rcssLze+10Fh)/16.
Разность S – main представляет собой размер главной процедуры. Однако перед главной процедурой размещается префикс программы, имеющий размер 100h байт, который тоже надо оставить в памяти. Далее, при целочисленном делении отбрасывается остаток, т.е. происходит округление результата в сторону уменьшения. Для компенсации этого дефекта можно прибавить к делимому число 15 = Fh. Деление всего этого выражения на 16 даст требуемый размер резидентной части программы в параграфах (возможно, с небольшим кусочком секции инициализации величиной до 15 байт).
Функция 31h, закрепив за резидентной программой необходимую для ее функционирования память, передает управление командному процессору COMMAND.СОМ, и вычислительная система переходит, таким образом, в исходное состояние. Наличие программы, резидентной в памяти, никак не отражается на ходе вычислительного процесса за исключением того, что уменьшается объем свободной памяти. Одновременно может быть загружено несколько резидентных программ.
Для того, чтобы активизировать резидентную программу, ей надо как-то передать управление и, возможно, параметры. Как правило, активизация резидентной программы осуществляется с помощью механизма прерываний.
Кроме того, специально для взаимодействия с резидентными программами в DOS предусмотрено мультиплексное прерывание 2Fh.
Рассмотрим типичную структуру резидентной программы и системные средства оставления ее в памяти. Как уже отмечалось, резидентные программы чаще всего пишутся в формате .СОМ:
При первом запуске программы с клавиатуры управление передается на начато процедуры main (первый байт после префикса программы). Командой jmp осуществляется переход на секцию инициализации, в которой, в частности, подготавливаются условия для дальнейшей активизации программы уже в резидентном состоянии. Последними строками секции инициализации вызывается функция 31h, которая выполняет завершение программы с оставлением в памяти указанной ее части. С целью экономии памяти секция инициализации располагается в конце программы и отбрасывается при ее завершении.
MS-DOS для программиста
5. Резидентные программы
Что это такое резидентная программа?
Обычно после завершения очередной программы MS-DOS освобождает место в памяти, которое занимала программа, чтобы загрузить на это место новую. Однако есть способ оставить программу в памяти и после ее завершения. Такая программа и будет резидентной, т. е. постоянно присутствующей в памяти.
Резидентные программы могут переключать на себя обработку прерываний, например, связанных с выводом на печать или с обращением к клавиатуре и т. д.
Другой пример использования резидентных программ: резидентные калькуляторы, справочные базы данных или интегрированные системы, наподобие Borland SideKick.
Такие программы тоже обычно запускаются через файл autoexec.bat или при необходимости. Они перехватывают прерывания, предназначенные для работы с клавиатурой. Как только пользователь нажимает заранее определенную комбинацию клавиш, резидентная программа активизируется. Поверх имеющегося на экране изображения выводится диалоговое окно резидентной программы.
Иногда резидентные программы используют вместо загружаемых драйверов для обслуживания нестандартной аппаратуры. В этом случае резидентная программа может встроить свой обработчик, через который все прикладные программы смогут обращаются к аппаратуре.
Аналогично работают резидентные модули некоторых систем управления базами данных (СУБД). Прикладная программа посылает запросы к базе данных через прерывание, устанавливаемое при запуске такой СУБД.
Вы, наверное, слышали, что составить правильно работающую резидентную программу не так просто. Мы полностью с этим согласны. Причин много, главная заключается в том, что фирма Microsoft не предоставила в распоряжение программистов всю необходимую для этого информацию.
На резидентные программы накладываются многочисленные ограничения, затрудняющие работу программиста.
Например, резидентным программам не разрешается использовать прерывания MS-DOS, когда вздумается. Это связано с тем, что MS-DOS с самого начала проектировалась как однозадачная операционная система, поэтому функции прерываний MS-DOS не обладают свойством реентерабельности (повторной входимости).
Представьте себе такую ситуацию.
Пусть обычная программа вызвала какую-либо функцию прерывания MS-DOS, на выполнение которой требуется относительно много времени (например, запись на диск).
Так как пользователь может активизировать резидентную программу в любой момент, то если не принять специальных мер предосторожности, возможен повторный вызов той же самой функции, обработка которой еще не завершена. В этом случае мы получим повторный вызов функции MS-DOS, который недопустим из-за того, что функции MS-DOS не реентерабельны.
Функции BIOS также далеко не все реентерабельны. Резидентная программа может смело вызывать разве лишь прерывание INT 16h (которое предназначено для работы с клавиатурой). Если резидентной программе нужно вывести что-нибудь на экран, то вместо прерывания INT 10h следует выполнить непосредственную запись символов и их атрибутов в видеопамять.
Без принятия специальных мер предосторожности резидентная программа не может вызывать многие функции библиотеки транслятора, так как последние вызывают прерывания MS-DOS. Например, функция malloc вызывает прерывание MS-DOS для определения размера свободной памяти в системе.
Могут возникнуть трудности и с использованием арифметических действий с числами в формате плавающей запятой. Это происходит потому, что функция _dos_keep восстанавливает прерывания, использовавшиеся для эмуляции арифметики с плавающей запятой и для работы с арифметическим сопроцессором.
Как оставить программу резидентной в памяти?
Для использования прерывания INT 27h сегментный регистр CS должен указывать на PSP программы. При этом в регистр DX следует записать смещение последнего байта программы плюс один байт.
Нетрудно заметить, что этот способ больше всего подходит для com-программ, так как с помощью прерывания INT 27h невозможно оставить в памяти резидентной программу длиннее 64 Кбайт.
Для того чтобы оставить резидентной в памяти программу, размер которой превышает 64 Кбайт, вы можете использовать только последний метод. Но не стоит увлекаться большими резидентными программами, так как занимаемая ими память нужна другим программам.
Библиотека функций системы разработки Borland C++ содержит в своем составе специальную функцию, предназначенную для оставления программы резидентной в памяти.
Нужно внимательно следить за размером оставляемой резидентной части программы. Если указать функции _dos_keep неправильный размер, то резидентная программа может оказаться разрушенной программой, загруженной следом за ней. Все это, в конечном счете, приведет к разрушению операционной системы и к необходимости ее перезапуска.
5.1. Резидентная программа TSRDEMO
В этом разделе мы приведем исходные тексты программы TSRDEMO, которую вы можете использовать в качестве заготовки для вашей собственной разработки.
Программа TSRDEMO может работать в MS-DOS версии 4.0 и более старших версий. Мы проверили ее в среде MS-DOS версии 6.22.
Наша резидентная программа выполняет все необходимые действия, включая защиту от повторного запуска и удаление себя из оперативной памяти. Функция, которая получает управление при активизации резидентной программы, может использовать функции прерываний MS-DOS и стандартные функции библиотеки транслятора. Все это возможно благодаря правильному выбору момента для активизации программы, предварительному сохранению некоторых системных областей данных и резервированию памяти для областей стека и кучи.
Если программу TSRDEMO запустить без параметров в первый раз, она выведет сообщение и останется резидентной в памяти. При повторной попытке запуска программа выведет сообщение о том, что она уже активна. Для того чтобы деинсталлировать программу (выгрузить ее из памяти), достаточно указать при запуске параметр u.
В ответ на любой другой параметр на экран выводится инструкция по запуску программы и список комбинаций клавиш, которые используются для ее активизации.
Когда программа TSRDEMO загружена в память, в правом верхнем углу экрана мигают символы «*» и «+». Соответствующие функции демонстрируют способ непосредственной записи кодов символов и атрибутов в видеопамять.
При запуске программы TSRDEMO управление получает функция main, исходный текст которой приведен ниже:
Прежде всего, функция main проверяет параметры, переданные программе через командную строку.
Если указан параметр u, вызывается функция unload, которая удаляет резидентную программу из памяти или отключает ее (при невозможности удаления). Если же указан любой другой параметр, функция main выводит на экран инструкцию по запуску программы. В обоих случаях вслед за этим работа программы завершается.
В том случае, когда при запуске программы TSRDEMO пользователь не указал никаких параметров, функция main предпринимает попытку инициализировать программу и оставить ее резидентной в памяти. Перед этим она вызывает функцию tsrloaded, проверяющую, нет ли в памяти копии программы TSRDEMO, загруженной ранее. Попытки повторной загрузки блокируются с выводом соответствующего сообщения на экран.
Если программа запускается впервые, функция main вызывает функцию tsrinit. Последняя выполняет все необходимые инициализирующие действия, такие, например, как встраивание обработчиков аппаратных и программных прерываний.
5.2. Инициализация резидентной программы
Отложим на время описание способа, с помощью которого резидентная программа может выгрузить себя из памяти, и займемся более детально процессом инициализации.
На первом этапе инициализации наша резидентная программа резервирует память для области кучи (heap) и стека. Это необходимо, если функция, получающая управление при активизации программы, работает со стандартными функциями из библиотеки транслятора (например, открывает файлы или потоки ввода/вывода, заказывает области памяти функцией malloc и т. п.).
Рис. 5.1. Расположение сегментов в модели памяти small
Какие из этих сегментов потребуются программе, после того как она останется резидентной в памяти?
Если резидентная программа составлена на языке программирования С, то наибольшие трудности возникают при определении необходимого размера сегмента данных.
Вслед за сегментом _BSS в памяти расположена область HEAP, которая иногда называется просто кучей. Из этой области (доступ к которой возможен с помощью регистра DS) происходит динамическое выделение памяти по явному или неявному запросу программы. Заметим, что вызов многих функций стандартной библиотеки С приводит к неявному выделению памяти из области HEAP.
Механизм выделения памяти из области HEAP имеет одну особенность: если программа заказала для себя блок памяти из этой области (например, функцией malloc), а затем освободила его, этот блок памяти не будет возвращен MS-DOS до завершения работы программы. Когда программе опять потребуется блок памяти, по возможности он будет выделен из области HEAP без обращения к соответствующей функции прерывания MS-DOS.
Что же касается стека, то, как известно, он адресуется с помощью регистров SS:SP. Содержимое регистра SS после запуска программы в модели памяти small равно содержимому регистра DS, так как для этой модели памяти стек расположен в сегменте данных.
В отличие от области данных HEAP, которая растет в направлении к концу памяти (в сторону больших адресов), стек растет в направлении к началу памяти. На рис. 5.1 это показано при помощи стрелок.
Вслед за областью стека в памяти расположена область FAR HEAP. Блоки памяти из этой области выделяются в тех случаях, когда программа заказывает их такой функцией, как farmalloc.
Для того чтобы при активизации резидентная программа могла вызывать стандартные функции библиотеки транслятора С, необходимо зарезервировать достаточное количество памяти для областей HEAP и стека. Кроме того, при активизации необходимо правильно загрузить регистры SS:SP, чтобы они указывали на нижнюю границу блока памяти, выделенного резидентной программой для стека.
Взгляните на последние две строки нашей программы TSRDEMO:
Функция tsrinit
Функция tsrinit выполняет все действия, необходимые для инициализации резидентной программы.
Прежде всего, эта функция резервирует область памяти для стека, который будет использован в момент активизации резидентной программы.
Вот соответствующий фрагмент кода:
Вернувшись к рис. 5.1, заметим, что перед стеком должна располагаться область памяти HEAP. Она нужна для работы функций динамического выделения памяти (и не только для этих функций). Поэтому область стека резидентной программы должна быть расположена ниже, как это показано на рис. 5.1.
На самом деле вызывая функцию malloc в первый раз, мы резервируем память для области HEAP. После второго вызова функции malloc будет выделена область памяти размером STACK_SIZE, расположенная сразу после области HEAP. Такая область подходит для стека резидентной программы.
Так как стек растет в направлении к началу памяти, мы запоминаем в переменной tsr_stack адрес нижней границы полученной области (точнее говоря, компоненту смещения адреса стека, пригодную для загрузки в регистр SP).
Когда резидентная программа активизируется, регистры SS:SP будут загружены так, чтобы указывать на выделенную нами область стека. При этом перед стеком будет расположена область памяти, имеющая размер HEAP_RESERVED. Она будет выполнять роль области HEAP, обеспечивая возможность использования стандартных функций библиотеки С.
Далее функция tsrinit сохраняет старые значения ряда векторов прерываний и устанавливает новые обработчики для этих прерываний.
В завершение функция освобождает блок памяти, выделенный для переменных среды в сегменте PSP :
Флаг InDos
Искомый адрес возвращается в регистрах ES:BX.
Для выяснения расположения флага обработчика критических ошибок в MS-DOS новых версий вы должны использовать недокументированную функцию 5Dh прерывания INT 21h:
Эта функция вернет указатель на флаг обработчика критических ошибок в регистрах DS:SI.
5.3. Установка обработчиков прерываний
Обработчики всех прерываний, за исключением прерывания INT 13h, составлены на языке С. Так как прерывание INT 13h возвращает результат выполнения операций в регистре флагов, соответствующий обработчик удобнее составить на языке ассемблера.
Расскажем подробно о том, какие вектора прерываний мы переопределяем и зачем.
Прерывание INT 1Ch
Прерывание INT 1Ch является программным и вызывается обработчиком аппаратного прерывания от таймера INT 08h приблизительно 18,2 раза в секунду.
Наша программа использует это прерывание, для того чтобы выводить в правом верхнем углу экрана мигающие символы «*» и «+». Такое мигание нужно только для индикации активности резидентной программы. Если индикация не требуется, вы можете не изменять соответствующий вектор прерывания и не предусматривать обработчик для этого прерывания.
Наш обработчик прерывания INT 1Ch называется new_int1c и имеет следующий вид:
Массив screen содержит дальние указатели на массив целых чисел, определенные следующим образом:
Прерывание INT 2Fh
Во-первых, этот обработчик используется для предотвращения повторной загрузки резидентной программы в память.
Во-вторых, он нужен для выгрузки резидентной программы из памяти.
Исходный текст обработчика прерывания INT 2Fh представлен ниже:
Вызов прерывания INT 2Fh происходит при запуске нашей программы, а также и при запуске других резидентных программ. Регистр AH при этом должен содержать идентификатор резидентной программы. Для программы TSRDEMO мы выбрали идентификатор FFh, хотя никто не сможет гарантировать, что такой идентификатор уже не используется другой программой. Для надежности вы можете указать дополнительный идентификатор, например, в виде текстовой строки, адрес которой передается через другие регистры. Мы не стали этого делать для сокращения объема листинга программы.
Итак, если при вызове прерывания INT 2Fh регистр AH содержит значение FFh, наш обработчик прерывания считает, что вызов выполняет программа TSRDEMO.
В этом случае он дополнительно проверяет содержимое регистра AL, в котором находится код выполняемой функции.
Если этот код равен 0, прерывание INT 2Fh вызвано для проверки наличия программы TSRDEMO в памяти. В ответ обработчик возвращает в регистре AX значение 00FFh. Это и есть признак того, что программа TSRDEMO уже загружена в память и ее повторная загрузка невозможна.
Если же код равен 1, программа TSRDEMO была запущена с параметром u для выгрузки своей копии из памяти.
В этом случае обработчик прерывания INT 2Fh сохраняет в глобальной переменной ExitAddress адрес завершения, переданный обработчику в регистрах BX:DX. Затем проверяется флаг tsr_already_active, который установлен, если резидентная программа активна и ее в данный момент нельзя выгружать из памяти.
Далее обработчик разрешает аппаратные прерывания, которые были запрещены перед вызовом прерывания INT 2Fh для выгрузки, и предпринимает попытку выгрузить резидентную программу, вызывая функцию tsr_exit.
Заметим, что после выгрузки резидентной программы функция tsr_exit передает управление MS-DOS, а не возвращает его обратно в программу.
Подробнее процесс выгрузки резидентной программы будет рассмотрен позже.
Прерывание INT 09h
Аппаратное прерывание от клавиатуры INT 09h обрабатывается для того чтобы обнаружить комбинацию клавиш, предназначенную для активизации резидентной программы:
Когда программа уже активна, установлен флаг tsr_already_active. В этом случае наш обработчик прерывания передает управление по цепочке, вызывая старый обработчик.
Для корректной обработки аппаратного прерывания необходимо выдать в контроллер прерывания соответствующую команду, разрешающую обработку других прерываний. Эту задачу решают несколько команд, оформленных как asm-вставки в исходный текст обработчика.
Прерывание INT 08h
Наша программа использует несколько возможностей для своей активизации. В частности, обработчик аппаратного прерывания таймера INT 08h периодически проверяет возможность активизации:
Прежде всего, проверяется флаг tsr_already_active. Этот флаг устанавливается, когда резидентная программа уже активизирована. Проверка флага активизации позволяет избежать повторной активизации, которая может привести к катастрофе.
Активизация не выполняется также и в том случае, если установлен флаг InDos или unsafe_flag (последний устанавливается, если вызван обработчик прерывания INT 13h ).
Если активизация возможна, сбрасываются флаги popup_while_dos_busy и устанавливается флаг tsr_already_active. Затем вызывается старый обработчик прерывания INT 08h и разрешаются прерывания.
Далее вызывается функция activate_tsr, которая выполняет все действия, необходимые для активизации. Она будет описана отдельно. После возвращения управления из этой функции флаг tsr_already_active сбрасывается.
В том случае, когда активизация резидентной программы невозможна, наш обработчик прерывания INT 08h вызывает старый обработчик и возвращает управление прерванной программе.
Прерывание INT 28h
Прерывание INT 28h вызывается MS-DOS, когда она ожидает ввод данных от клавиатуры или, иными словами, ничем особенным не занята. Поэтому обработчик прерывания INT 28h может активизировать резидентную программу, если на это есть запрос от пользователя.
Приведем исходный текст обработчика прерывания INT 28h :
Обработчик прерывания INT 28h содержит счетчик рекурсивных вызовов int_28_in_progress, который анализируется при активизации резидентной программы функцией activate_tsr.
В первом случае вызывается функция DosBusy:
Эта функция проверяет содержимое флагов InDos и флага обработки критической ошибки. Если MS-DOS не выполняет обработку критической ошибки и если прерываемая программа не вызывает функцию MS-DOS, можно выполнять активизацию. В этом случае функция DosBusy возвращает значение 0.
При рекурсивных вызовах функций MS-DOS значение, хранящееся в этом байте, увеличивается на 1. Активизация резидентной программы допускается только в том случае, когда в этом байте находится значение 1, т. е. когда нет рекурсии.
Прерывание INT 13h
Исходный текст обработчика прерывания INT 13h представлен ниже:
Для обеспечения возможности адресации глобальной переменной unsafe_flag регистр DS устанавливается на сегмент данных программы TSRDEMO.
Адрес старого обработчика прерывания INT 13h находится в переменной old_int13, куда он записывается функцией get_int_13. Эта функция вызывается из функции tsrinit. Ее исходный текст вы найдете в листинге 5.3 (см. ниже).
5.4. Активизация резидентной программы
Когда обработчик прерываний INT 08h или INT 28h обнаруживает, что есть запрос на активизацию и, кроме того, возможность активизации, вызывается функция activate_tsr. Она и выполняет всю необходимую работу.
Переключение стека
Прежде всего, функция activate_tsr переключает стек при помощи функции set_stack. Эта функция составлена на языке ассемблера и приведена в листинге 5.3.
Резидентная программа во время активизации должна пользоваться собственным стеком, память для которого была отведена на этапе инициализации, а не стеком прерванной программы. Размер стека прерванной программы в общем случае неизвестен, поэтому резидентная программа может разрушить важные структуры, записывая в него свои данные.
Переключение PSP
На следующем этапе активизации резидентная программа сохраняет PSP прерванной программы и устанавливает свой PSP. Если этого не сделать, текущий PSP не будет соответствовать текущей выполняемой программе (в данном случае программе TSRDEMO).
Для получения адреса PSP прерванной программы вызывается функция GetPSP.
Переключение DTA
Сохранение расширенной информации об ошибке
Если при выполнении какой-либо функции прерывания INT 21h возникает ошибка, в соответствующую структуру памяти записывается расширенная информация, позволяющая определить причины ее возникновения.
Функция SetExtErr восстанавливает расширенную информацию об ошибке при помощи функции 5D0Ah того же самого прерывания.
Удаление содержимого буфера клавиатуры
Для удаления содержимого буфера клавиатуры используется функция _bios_keybrd. Она вызывается в цикле до тех пор, пока буфер клавиатуры не окажется пуст:
Вызов функции application
Теперь можно делать то, для чего предназначена резидентная программа. В данном случае, программа TSRDEMO вызывает функцию application, которая копирует содержимое видеопамяти в создаваемый текстовый файл.
Перед вызовом функции application мы выполнили все необходимые действия по переключению активной программы, сохранению структур данных. Кроме того, мы зарезервировали память для области HEAP и стека. Поэтому функция application может вызывать практически любые функции стандартной библиотеки, в том числе предназначенные для динамического выделения памяти и для работы с потоками данных.
Обратное переключение
5.5. Выгрузка резидентной программы из памяти
Напомним, что для выгрузки программы TSRDEMO из памяти ее нужно запустить с параметром u.
Что происходит в этом случае?
Функция main вызывает функцию unload. Та, в свою очередь, пытается выполнить удаление копии программы TSRDEMO из памяти, вызывая для этого функцию tsrunload. В зависимости от успеха этой операции функция unload выводит на экран соответствующее сообщение.
Функция tsrunload составлена на языке ассемблера. Ее исходный текст вы сможете найти в листинге 5.3.
Кратко о том, что делает эта функция.
Полученный адрес сохраняется в глобальной переменной ExitAddress, вслед за чем вызывается функция tsr_exit.
Эта функция устанавливает стек, зарезервированный для работы при активизации программы, вслед за чем пытается восстановить переназначенные при установке векторы прерываний.
Для восстановления используется функция RestoreIntVect, которая проверяет, не были ли переназначены векторы прерывания еще раз, например, другой резидентной программой, запущенной после программы TSRDEMO.