9. Приложение

9.1. Регистр EFLAGS

В этой таблице описан формат регистра флагов для процессоров i80386 и i80486 (регистр флагов процессора i80286 называется FLAGS и представляет собой младшее 16-разрядное слово регистра EFLAGS):

Номер бита Назначение
0 - CF Флаг переноса
1 - 1 Зарезервировано и равно 1
2 - PF Флаг чётности
3 - 0 Зарезервировано и равно 0
4 - AF Флаг вспомогательного переноса
5 - 0 Зарезервировано и равно 0
6 - ZF Флаг нуля
7 - SF Флаг знака
8 - TF Флаг ловушки
9 - IF Флаг разрешения прерываний
10 - DF Флаг направления
11 - OF Флаг переполнения
12-13 - IOPL Уровень привилегий ввода/вывода
14 - NT Флаг вложенной задачи
15 - 0 Зарезервировано и равно 0
16 - RF Флаг возобновления (только i80386 и i80486)
17 - VM Флаг режима виртуального процессора 8086 (только i80386 и i80486)
18 - AC Флаг проверки выравнивания (только i80486)
19-31 - 0 Зарезервировано и равно 0

9.2. Управляющие регистры процессора i80386

Регистр Назначение
CR0 Регистр состояния процессора
CR1 Зарезервирован
CR2 Линейный адрес отказа страницы
CR3 Базовый адрес каталога страницы

9.3. Формат регистра CR0 процессора i80386

Номер бита Назначение
0 - PE Включение защищённого режима работы процессора.
1 - MP Присутствие сопроцессора.
2 - EM Эмуляция сопроцессора.
3 - TS Переключение задачи.
4 - ET Тип сопроцессора - i80287 или i80387.
5-14 Зарезервировано
15 - PG Включение механизма трансляции страниц

9.4. Формат регистра CR0 процессора i80486

Номер бита Назначение
0 - PE Включение защищённого режима работы процессора.
1 - MP Присутствие сопроцессора.
2 - EM Эмуляция сопроцессора.
3 - TS Переключение задачи.
4 - ET Тип сопроцессора - i80287 или i80387.
5 - NE Числовая ошибка. Разрешает обработку ошибок при операциях с плавающей точкой.
6-15 Зарезервировано
16 - WP Защита записи. При установке этого бита страницы пользователя защищены от записи в режиме супервизора.
17 Зарезервировано
18 - AM Бит маски выравнивания. Этот бит разрешает или запрещает контроль выравнивания операндов команд в памяти. Контроль выравнивания разрешён только для программ, работающих в третьем кольце, при условии что установлен бит AM.
19-28 Зарезервировано
29 - NW Разрешение сквозной записи. Используется в механизме управления кэшированием.
30 - CD Запрещение кэширования. Если этот бит установлен, внутреннее кэширование запрещено.
31 - PG Включение механизма трансляции страниц

9.5. Формат регистра CR3 процессора i80486

Номер бита Назначение
0-2 Зарезервировано
3 - PWT Прозрачность записи на уровне страниц. Используется для управления записью во внешний кэш.
4 - PCD Запрещение кэширования на уровне страниц. Используется для управления работой внешнего кэша.
5-31 - PBDR Базовый регистр каталога страниц

9.6. Системные команды процессоров i80286/i80386/i80486

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

Как правило, системные команды могут использовать только те программы, которые выполняются в нулевом привилегированном кольце.

ARPL Коррекция поля привилегий инициатора запроса в селекторе

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

Первый операнд команды - 16-разрядный регистр или слово памяти, содержащие значение проверяемого селектора. Второй операнд - регистр, в который записано содержимое CS прикладной программы.

Если команда не изменяла уровень привилегий, в регистре FLAGS (EFLAGS для процессоров i80386 и i80486) устанавливается флаг нуля. В противном случае этот флаг сбрасывается.

Пример использования команды:

mov     dx, cs
mov     ax, TESTED_SELECTOR
arpl    dx, ax




CLTS Сброс флага TS переключения задачи в регистре CR0

Каждый раз при переключении задачи флаг TS устанавливается в 1. Команда CLTS позволяет сбросить этот флаг.

LAR Загрузка байта прав доступа

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

В процессорах i80386 и i80486 команда LAR использует в качестве первого операнда 32-разрядный регистр. Кроме байта прав доступа в этот регистр заносятся биты типа сегмента (9-11), DPL (14), бит присутствия (15), бит дробности (23).

LGDT Загрузка регистра GDTR

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

LIDT Загрузка регистра IDTR

Команда выполняет инициализацию регистра IDTR, указывающего расположение в памяти и размер дескрипторной таблицы прерываний.

LLDT Загрузка регистра LDTR

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

LMSW Загрузка слова состояния процессора

С помощью этой команды можно выполнить загрузку младшего слова регистра CR0 из регистра - операнда команды.

Эта команда может использоваться для переключения процессора в защищённый режим. Обратного переключения эта команда не обеспечивает (даже для процессоров i80386 и i80486).

LSL Загрузка предела сегмента

Команда имеет два операнда. Граница сегмента, селектор которого используется в качестве второго операнда (задаётся в регистре), загружается в регистр, указанный в качестве первого операнда.

LTR Загрузка регистра задачи

Команда предназначена для загрузки регистра TR - регистра задачи. Загрузка этого регистра не приводит к переключению задачи.

MOV Загрузка системных регистров

Для процессоров i80386 и i80486 в качестве операндов обычной команды MOV допустимо (на нулевом уровне привилегий) указывать системные регистры - CR0, CR2, CR3, DR0, DR1, DR2, DR3, DR6, DR7, TR6, TR7. Команда MOV может быть использована процессорами i80386 и i80486 для возврата процессора из защищённого режима в реальный.

SGDT Запись в память содержимого регистра GDTR

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

SIDT Записать в память содержимое регистра IDTR

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

SLDT Записать в память содержимое регистра LDTR

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

SMSW Записать слова состояния процессора

Команда записывает в память или 16-битовый регистр младшее слово регистра CR0 и может быть использована в системных отладчиках.

STR Запись регистра задачи

Команда записывает текущее содержимое регистра задачи TR в 16-разрядную ячейку памяти или 16-разрядный регистр. Может использоваться в системных отладчиках.

VERR Проверить сегмент на возможность чтения

VERW Проверить сегмент на возможность записи

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

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

9.7. Недокументированная команда LOADALL

Оказывается, для процессора i80286 существует способ получения доступа к расширенной памяти, не переключаясь в защищённый режим. Для этого может быть использована недокументированная команда LOADALL, имеющая код 0F05h (команда не имеет операндов). Эта команда не описана в справочниках по процессору i80286, информация о ней поставляется фирмой Intel по запросу. Те сведения о команде LOADALL, которые приведены в нашей книге, получены по электронной почте из BBS и могут быть использованы только для расширения вашего кругозора и для оценки полезности этой команды в ваших разработках.

Команда LOADALL первоначально была задумана фирмой Intel как тестовая. Однако оказалось, что она пригодна и для обращения к расширенной памяти в реальном режиме. Широко известный драйвер расширенной памяти HIMEM.SYS обращается в область адресов выше первого мегабайта именно с помощью команды LOADALL (а не переключаясь в защищённый режим и возвращаясь обратно, как это можно было бы предположить).

Команда LOADALL сокращает время, требуемое драйверу HIMEM.SYS на доступ к расширенной памяти, так как время на переключение в защищённый режим и обратное переключение достаточно велико по сравнению с временем, необходимым на копирование данных из основной памяти в расширенную или обратно.

Другое применение команды - драйвер электронного диска Microsoft RAMDRIVE.SYS и блок совместимости операционной системы Microsoft OS/2 версии 1.x.

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

Как мы уже говорили, команда LOADALL не имеет операндов. Регистры загружаются из буфера, который имеет длину 102 байта и должен быть подготовлен в области памяти с физическим адресом 00800h.

Формат буфера представлен в следующей таблице:

Таблица 16. Формат буфера для команды LOADALL.

Адрес Регистры процессора
800h-805h Не используется
806h-807h Слово состояния процессора MSW (Machine Status Word)
808h-815h Не используется
816h-817h Регистр задачи TR (Task Register)
818h-819h Регистр флагов
81Ah-81Bh Регистр IP (Instruction Pointer)
81Ch-81Dh Селектор LDT (Local Descriptor Table)
81Eh-81Fh Регистр DS (Data Segment Selector)
820h-821h Регистр SS (Stack Segment Selector)
822h-823h Регистр CS (Code Segment Selector)
824h-825h Регистр ES (Extra Segment Selector)
826h-827h Регистр DI (Destination Index)
818h-829h Регистр SI (Source Index)
82Ah-82Bh Регистр BP (Base Pointer)
82Ch-82Dh Регистр SP (Stack Pointer)
82Eh-82Fh Регистр BX (Data Register BX)
830h-831h Регистр DX (Data Register DX)
832h-833h Регистр CX (Data Register CX)
834h-835h Регистр AX (Accumulator)
836h-83Bh Кэш дескриптора ES
83Ch-841h Кэш дескриптора CS
842h-847h Кэш дескриптора SS
848h-84Dh Кеш дескриптора DS
84Eh-853h Регистр GDTR (Global Descriptor Table Register)
854h-859h Кэш дескриптора LDT
85Ah-85Fh Регистр IDTR (Interrupt Descriptor Table Register)
860h-865h Кэш дескриптора TSS (Task State Segment)

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

Формат кэша дескриптора приведён в следующей таблице:

Таблица 17. Формат кэша дескриптора.

Смещение поля Назначение поля
0-2 24-битовый базовый адрес сегмента
3 Байт доступа, его формат полностью аналогичен формату байта доступа дескриптора, за исключением бита присутствия. На месте этого бита находится бит VALID. Если этот бит сброшен в 0, при попытке использовать дескриптор для адресации памяти произойдёт исключение 13 с кодом ошибки 0.
4-5 16-битовый предел сегмента

Можно предложить следующий алгоритм использования команды LOADALL:

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

Команда LOADALL может выполняться в защищённом режиме в нулевом приоритетном кольце. Но, к сожалению, эту команду нельзя использовать для переключения процессора из защищённого в реальный режим.

Процессор i80387 также имеет команду LOADALL, но её код и выполняемые функции другие.

9.8. Утилита MEMOSCOP

Для определения активных интерфейсов с защищённым режимом можно использовать предлагаемую утилиту MEMOSCOP. Эта утилита проверяет присутствие всех уровней поддержки программ, работающих в защищённом режиме или с расширенной памятью - от BIOS до DPMI.

*MEMOSCOP*, © Frolov A.V., 1992
--------------------------------------------------------
Файл memoscop.c

#include <stdio.h>
#include <stdlib.h>
#include <dos.h>

void main(void) {

        extern int getcpu(void);
        unsigned cpu_type, ver;
        unsigned err;
        char ver_hi, ver_lo, verems;
        unsigned hostdata_seg, hostdata_size, dpmi_flags;
        void (far *pm_entry)();

        union REGS regs;
        struct SREGS segregs;


        printf("\n*MemoScop* v 1.0, © Frolov A.V., 1992\n"
                 "---------------------------------------\n");

// Определяем тип центрального процессора. Если программа
// работает на процессоре i8086, завершаем выполнение,
// так как в этом случае интерфейсы с защищенным режимом
// недоступны.

        printf("Тип процессора: 80%d\n", (cpu_type = getcpu()));
        if(cpu_type == 86) {
                printf("\nНа этом процессоре нам работать не интересно...");
                exit(0);
        }

// Определяем размер доступной через прерывание INT 15h
// расширенной памяти.

        printf("\n------------------------ Уровень BIOS -------------------------\n");

        regs.h.ah = 0x88;
        int86(0x15, &regs, &regs);
        printf("Размер расширенной памяти, доступной через INT 15:"
        " \t%d Кбайт\n", regs.x.ax);

// Проверяем, установлен ли драйвер HIMEM.SYS,
// если установлен, выводим его версию.

        printf("\n------------------------ Уровень XMM --------------------------\n");


        if (XMM_Installed()) {
                printf("Установлен драйвер HIMEM.SYS");
                ver = XMM_Version();
                printf(", версия: %4X, изменения: %4X\n",
                        (short)ver, (short)(ver >> 16));
                printf("Размер свободной расширенной памяти, доступной через XMM:"
                                         " %ld Кбайт\n", (long)XMM_QueryLargestFree());
                printf("Общий размер расширенной памяти, доступной через XMM:"
                                         " \t  %ld Кбайт\n", (long)XMM_QueryTotalFree());

        }
        else printf("\nДрайвер HIMEM.SYS не установлен.");


        printf("\n------------------------ Уровень EMS/VCPI ----------------------\n");

// Проверяем наличие драйвера EMS/VCPI

        if(ems_init()) printf("Драйвер EMS/VCPI не загружен\n");
        else {
                printf("Драйвер EMS/VCPI загружен, ");

// Выводим номер версии драйвера

                if((err = ems_ver(&verems)) != 0) {
                        printf("\nОшибка %02.2X при определении версии EMM", err);
                        exit(-1);
                }
                printf("версия EMM: %02.2X", verems);

// Определяем присутствие VCPI и его версию

                if(vcpi_ver(&ver_hi, &ver_lo) != 0) {
                        printf("\nДрайвер EMM не поддерживает VCPI\n");
                        exit(-1);
                }
                printf("\nВерсия VCPI: %02.2X.%02.2X\n", ver_hi, ver_lo);
        }


        printf("\n------------------------ Уровень DPMI --------------------------");


// Проверяем доступность и параметры сервера DPMI

        regs.x.ax = 0x1687;
        int86x(0x2F, &regs, &regs, &segregs);
        if(regs.x.ax != 0) {
                printf("\nСервер DPMI не активен"); exit(-1);
        }

// Определяем версию сервера DPMI

        printf("\nВерсия сервера DPMI: \t\t\t%d.%d\n",
                regs.h.dh, regs.h.dl);

// Определяем тип процессора

        printf("Тип процессора:\t\t\t\t");
        if(regs.h.cl == 2) printf("80286");
        else if(regs.h.cl == 3) printf("80386");
        else if(regs.h.cl == 4) printf("80486");

// Определяем возможность работы с 32-разрядными
// программами

        dpmi_flags = regs.x.bx;
        printf("\nПоддержка 32-разрядных программ:\t");
        if(dpmi_flags && 1) printf("ПРИСУТСТВУЕТ");
        else printf("ОТСУТСТВУЕТ");

// Определяем размер области памяти для сервера DPMI

        hostdata_size = regs.x.si;
        printf("\nРазмер памяти для сервера DPMI:\t\t%d байт",
                hostdata_size * 16);

// Определяем адрес точки входа в защищённый режим

        FP_SEG(pm_entry) = segregs.es;
        FP_OFF(pm_entry) = regs.x.di;
        printf("\nАдрес точки входа в защищённый режим: \t%Fp\n",
                pm_entry);

        getch();

}

/**
*.Name         ems_init
*.Title        Функция проверяет установку драйвера EMS
*
*.Descr        Эта функция проверяет наличие драйвера EMS
*
*.Proto        int ems_init(void);
*
*.Params       Не используются
*
*.Return       0 - драйвер EMS установлен;
*              1 - драйвер EMS не установлен.
*
*.Sample       ems_test.c
**/

int ems_init(void) {

        void (_interrupt _far *EMS_driver_adr)(void);
        char _far *EMS_driver_name;
        char test_name[8];
        int i;

        EMS_driver_adr = _dos_getvect(0x67);

        FP_SEG(EMS_driver_name) = FP_SEG (EMS_driver_adr);
        FP_OFF(EMS_driver_name) = 10;

        for(i=0; i<8; i++) test_name[i] = EMS_driver_name[i];

        if(strncmp(test_name, "EMMXXXX0", 8) == 0) return(0);
        else return(1);

}

/**
*.Name         ems_ver
*.Title        Определение версии драйвера EMS
*
*.Descr        Эта функция возвращает номер версии
*              драйвера EMS в двоично-десятичном формате.
*
*.Proto        int ems_ver(char *ver);
*
*.Params       char *ver - указатель на байт, в который
*                 будет записан номер версии.
*
*.Return       Номер версии драйвера EMS в формате BCD
*
*.Sample       ems_test.c
**/

int ems_ver(char *ver) {

        union REGS reg;

        reg.x.ax = 0x4600;
        int86(0x67, &reg, &reg);

        *ver = reg.h.al;
        return(reg.h.ah);
}

int vcpi_ver(char *ver_hi, char *ver_lo) {

        union REGS reg;

        reg.x.ax = 0xDE00;
        int86(0x67, &reg, &reg);

        *ver_hi = reg.h.bh;
        *ver_lo = reg.h.bl;
        return(reg.h.ah);
}




Исходные тексты функций, вызываемых утилитой MEMOSCOP приведены ниже:

*MEMOSCOP*, © Frolov A.V., 1992
--------------------------------------------------------
Файл cpu.asm


DEAL

MODEL SMALL

P386

PUBLIC  _getcpu

CODESEG

PROC    _getcpu  NEAR

; --------------------------------------------------------
; Пытаемся установить старшую тетраду регистра флагов в 0.
; Если программа работает на процессоре 8086, в этом
; байте все биты будут установлены в 1
; --------------------------------------------------------

        mov     dx, 86      ; Тип процессора - 8086

        pushf
        pop     bx
        and     bh,0Fh
        push    bx
        popf
        pushf
        pop     ax
        and     ah,0F0h
        cmp     ah,0F0h
        je      cpu_end

; --------------------------------------------------------
; Для теста на процессор 80286 пытаемся установить старшую
; тетраду регистра флагов в единицы. Если программа
; выполняется на процессоре 80286, эти биты останутся
; равными нулю
; --------------------------------------------------------

        mov     dx, 286             ; Тип процессора - 80286

        or      bh,0F0h
        push    bx
        popf
        pushf
        pop     ax
        test    ah,0F0h
        jz      cpu_end

; --------------------------------------------------------
; Для того, чтобы отличить процессор 80386 от процессора
; 80486, используем бит 18 регистра флагов EFLAGS.
; Если программа может изменить состояние этого бита,
; она выполняется на процессоре 80486. В противном случае
; используется процессор 80386.
; --------------------------------------------------------

; Созраняем указатель стека

        mov     edx,esp

; Выравниваем указатель стека для предотвращения
; исключения при установке флага AC

        and     esp,not 3

; Копируем регистр EFLAGS в регистр EAX

        pushfd
        pop     eax

; Сохраняем начальное значение регистра EFLAGS

        mov     ecx,eax

; Переключаем флаг AC

        xor     eax,40000H

; Пытаемся записать измененное значение обратно в регистр
; EFLAGS

        push     eax
        popfd

; Копируем регистр EFLAGS в регисрр EAX

        pushfd
        pop     eax

; Сравниваем старое и новое значения бита AC

        xor     eax,ecx
        shr     eax,18
        and     eax,1
        push    ecx

; Восстанавливаем регситр EFLAGS

        popfd

; Восстанавливаем указатель стека

        mov     esp,edx

; Теперь если программа выполняется на процессоре 80386,
; регистр AX содержит значение 0, а если на процессоре
; 80486 - значение 1.

        mov     dx,386              ; Тип процессора - 80386

        test    ax,ax
        jz      cpu_end

        mov     ax,486              ; Тип процессора - 80486

cpu_end:
        mov     ax, dx
        ret
ENDP    _getcpu

END




Для работы с функциями драйвера HIMEM.SYS используется интерфейс, описанный нами в томе 2 "Библиотеки системного программсита":

*MEMOSCOP*, © Frolov A.V., 1992
--------------------------------------------------------
Файл xmmc.asm


; Это интерфейсный модуль для вызова функций
; XMS из Си. Текст программы рассчитан на
; модель памяти Small.

          .model small,c
          .DATA

; В этом месте будет храниться адрес
; управляющей функции XMM

XMM_Control  dd   ?

          .CODE

; Макроопределения для выполнения соглашения об
; использовании регистров в процедурах Си

c_begin macro
                         push bp
                         mov  bp,sp
                         push si
                         push di
                  endm

c_end   macro
                         pop  di
                         pop  si
                         mov  sp,bp
                         pop  bp
                         ret
                  endm

; Все процедуры должны быть public

        public XMM_Installed
        public XMM_Version
        public XMM_RequestHMA
        public XMM_ReleaseHMA
        public XMM_GlobalEnableA20
        public XMM_GlobalDisableA20
        public XMM_EnableA20
        public XMM_DisableA20
        public XMM_QueryA20
        public XMM_QueryLargestFree
        public XMM_QueryTotalFree
        public XMM_AllocateExtended
        public XMM_FreeExtended
        public XMM_MoveExtended
        public XMM_LockExtended
        public XMM_UnLockExtended
        public XMM_GetHandleLength
        public XMM_GetHandleInfo
        public XMM_ReallocateExtended
        public XMM_RequestUMB
        public XMM_ReleaseUMB

;**
;.Name         XMM_Installed
;.Title        Получение адреса управляющей функции
;
;.Descr        Эта функция проверяет наличие драйвера
;              HIMEM.SYS и в случае его присуствия
;              запоминает адрес управляющей функции.
;
;.Proto        unsigned XMM_Installed(void);
;
;.Params       Не используются
;
;.Return       0 - драйвер HIMEM.SYS не установлен;
;              1 - драйвер HIMEM.SYS установлен.
;
;.Sample       xms_test.c
;**

XMM_Installed proc near
                         c_begin

                         mov  ax, 4300h
          int  2fh
          cmp  al, 80h
                         jne  NotInstalled

                         mov  ax, 4310h
          int  2fh
          mov  word ptr [XMM_Control], bx
          mov  word ptr [XMM_Control+2], es
                         mov  ax,1
                         jmp  Installed

NotInstalled:
                         mov  ax, 0
Installed:
                         c_end
XMM_Installed endp

;**
;.Name         XMM_Version
;.Title        Определение версии драйвера HIMEM.SYS
;
;.Descr        Эта функция определяет версию драйвера
;              HIMEM.SYS
;
;.Proto        long  XMM_Version(void);
;
;.Params       Не используются
;
;.Return       Номер версии в младших 16 битах,
;              номер изменений - в старших 16 битах
;              возвращаемого значения
;
;.Sample       xms_test.c
;**

XMM_Version proc near
          push si
                         push di
                         xor  ah,ah
                         call [XMM_Control]
                         mov  dx, bx
          pop  di
          pop  si
          ret
XMM_Version endp

;**
;.Name         XMM_RequestHMA
;.Title        Запросить область HMA
;
;.Descr        Эта функция пытается зарезервировать для
;              программы область HMA
;
;.Proto        long  XMM_RequestHMA(unsigned space);
;
;.Params       space - размер требуемой области для
;                      TSR-программы или драйвера,
;                      0xffff для прикладной программы;
;
;.Return       < 0 - область HMA не назначена программе,
;                    код ошибки находится в старшем байте.
;              0L  - область HMA назначена программе.
;
;.Sample       xms_test.c
;**

XMM_RequestHMA proc near
                         c_begin
                         mov  ah, 1
                         mov  dx, [bp+4]
          call [XMM_Control]
          xor  dx, dx
          dec  ax
                         jz   @success
          mov  dh, bl
@success:
                         c_end
XMM_RequestHMA endp


;**
;.Name         XMM_ReleaseHMA
;.Title        Освободить область HMA
;
;.Descr        Эта функция пытается освободить
;              область HMA
;
;.Proto        long  XMM_ReleaseHMA(void);
;
;.Params       Не используются
;
;.Return       < 0 - область HMA не освобождена,
;                    код ошибки находится в старшем байте.
;              0L - область HMA освобождена.
;
;.Sample       xms_test.c
;**

XMM_ReleaseHMA proc near
                         c_begin
          mov  ah, 2
          call [XMM_Control]
          xor  dx, dx
          dec  ax
                         jz   @success1
          mov  dh, bl
@success1:
                         c_end
XMM_ReleaseHMA endp

;**
;.Name         XMM_GlobalEnableA20
;.Title        Глобальное разрешение линии A20
;
;.Descr        Эта функция разрешает программе, получившей
;              доступ к области HMA использовать линию A20
;
;.Proto        long  XMM_GlobalEnableA20(void);
;
;.Params       Не используются
;
;.Return       < 0 - линия A20 не включена,
;                    код ошибки находится в старшем байте.
;              0L  - линия A20 включена.
;
;.Sample       xms_test.c
;**

XMM_GlobalEnableA20 proc near
                         c_begin
                         mov  ah, 3
          call [XMM_Control]
          xor  dx, dx
          dec  ax
                         jz   @success2
          mov  dh, bl
@success2:
                         c_end
XMM_GlobalEnableA20 endp

;**
;.Name         XMM_GlobalDisableA20
;.Title        Глобальное запрещение линии A20
;
;.Descr        Эта функция запрещает программе, получившей
;              доступ к области HMA использовать линию A20
;
;.Proto        long  XMM_GlobalDisableA20(void);
;
;.Params       Не используются
;
;.Return       < 0 - линия A20 не выключена,
;                    код ошибки находится в старшем байте.
;              0L  - линия A20 выключена.
;
;.Sample       xms_test.c
;**

XMM_GlobalDisableA20 proc near
                         c_begin
                         mov  ah, 4
          call [XMM_Control]
          xor  dx, dx
          dec  ax
                         jz   @success3
          mov  dh, bl
@success3:
                         c_end
XMM_GlobalDisableA20 endp

;**
;.Name         XMM_EnableA20
;.Title        Локальное разрешение линии A20
;
;.Descr        Эта функция разрешает программе управлять
;              областью расширенной памяти.
;
;.Proto        long  XMM_EnableA20(void);
;
;.Params       Не используются
;
;.Return       < 0 - линия A20 не включена,
;                    код ошибки находится в старшем байте.
;              0L  - линия A20 включена.
;
;.Sample       xms_test.c
;**

XMM_EnableA20 proc near
                         c_begin
          mov  ah, 5
          call [XMM_Control]
          xor  dx, dx
          dec  ax
                         jz   @success4
          mov  dh, bl
@success4:
                         c_end
XMM_EnableA20 endp

;**
;.Name         XMM_DisableA20
;.Title        Локальное запрещение линии A20
;
;.Descr        Эта функция запрещает программе управлять
;              областью расширенной памяти.
;
;.Proto        long  XMM_DisableA20(void);
;
;.Params       Не используются
;
;.Return       < 0 - линия A20 не выключена,
;                    код ошибки находится в старшем байте.
;              0L  - линия A20 выключена.
;
;.Sample       xms_test.c
;**

XMM_DisableA20 proc near
                         c_begin
          mov  ah, 6
          call [XMM_Control]
          xor  dx, dx
          dec  ax
                         jz   @success5
          mov  dh, bl
@success5:
                         c_end
XMM_DisableA20 endp

;**
;.Name         XMM_QueryA20
;.Title        Проверить состояние линии A20
;
;.Descr        Эта функция проверяет доступность
;              линии A20
;
;.Proto        long  XMM_QueryA20(void);
;
;.Params       Не используются
;
;.Return       < 0 - ошибка,
;                    код ошибки находится в старшем байте.
;              0L  - линия A20 выключена,
;              1L  - линия A20 включена.
;
;.Sample       xms_test.c
;**

XMM_QueryA20 proc near
                         c_begin
          mov  ah, 7
          call [XMM_Control]
          xor  dx, dx
          or   ax, ax
                         jnz  @success6
          mov  dh, bl
@success6:
                         c_end
XMM_QueryA20 endp

;**
;.Name         XMM_QueryLargestFree
;.Title        Определить максимальный размер блока
;
;.Descr        Эта функция возвращает размер максимального
;              непрерывного блока расширенной памяти,
;              который доступен программе.
;
;.Proto        long  XMM_QueryLargestFree(void);
;
;.Params       Не используются
;
;.Return       < 0 - ошибка,
;                    код ошибки находится в старшем байте.
;              >= 0 - размер блока.
;
;.Sample       xms_test.c
;**

XMM_QueryLargestFree proc near
                         c_begin
          mov  ah, 8
          call [XMM_Control]
          xor  dx, dx
          or   ax, ax
                         jnz  @success7
          mov  dh, bl
@success7:
                         c_end
XMM_QueryLargestFree endp

;**
;.Name         XMM_QueryTotalFree
;.Title        Определить размер расширенной памяти
;
;.Descr        Эта функция возвращает размер
;              всей имеющейся расширенной памяти.
;
;.Proto        long  XMM_QueryTotalFree(void);
;
;.Params       Не используются
;
;.Return       < 0 - ошибка,
;                    код ошибки находится в старшем байте.
;              >= 0 - размер расширенной памяти.
;
;.Sample       xms_test.c
;**

XMM_QueryTotalFree proc near
                         c_begin
                         mov  ah, 8
          call [XMM_Control]
          or   ax, ax
          mov  ax, dx
                         mov  dx, 0
                         jnz  @success8
          mov  dh, bl
@success8:
                         c_end
XMM_QueryTotalFree endp

;**
;.Name         XMM_AllocateExtended
;.Title        Запросить блок расширенной памяти
;
;.Descr        Эта функция выделяет программе блок
;              расширенной памяти, в случае успеха
;              возвращает индекс полученного блока.
;
;.Proto        long XMM_AllocateExtended(unsigned space);
;
;.Params       space - размер требуемого блока памяти
;                      в килобайтах;
;
;.Return       < 0 - блок не распределен,
;                    код ошибки находится в старшем байте.
;              > 0L  - младший байт содержит индекс
;                      полученного блока памяти.
;
;.Sample       xms_test.c
;**


XMM_AllocateExtended proc near
                         c_begin
                         mov  ah, 9
                         mov  dx,  [bp+4]
          call [XMM_Control]
          or   ax, ax
          mov  ax, dx
                         mov  dx, 0
                         jnz  @success9
          mov  dh, bl
@success9:
                         c_end
XMM_AllocateExtended endp

;**
;.Name         XMM_FreeExtended
;.Title        Освободить блок расширенной памяти
;
;.Descr        Эта функция освобождает блок
;              расширенной памяти, полученный функцией
;              XMM_AllocateExtended().
;
;.Proto        long XMM_FreeExtended(unsigned handle);
;
;.Params       handle - индекс освобождаемого блока памяти;
;
;.Return       < 0 - блок не распределен,
;                    код ошибки находится в старшем байте.
;              0L  - блок освобожден.
;
;.Sample       xms_test.c
;**

XMM_FreeExtended proc near
                         c_begin
          mov  ah, 0Ah
                         mov  dx, [bp+4]
          call [XMM_Control]
          xor  dx, dx
          dec  ax
                         jz   @successA
          mov  dh, bl
@successA:
                         c_end
XMM_FreeExtended endp

;**
;.Name         XMM_MoveExtended
;.Title        Копировать блок расширенной памяти
;
;.Descr        Эта функция копирует блок
;              расширенной памяти, используя структуру
;              struct XMM_Move:
;
;                 struct   XMM_Move {
;                    unsigned long  Length;
;                    unsigned short SourceHandle;
;                    unsigned long  SourceOffset;
;                    unsigned short DestHandle;
;                    unsigned long  DestOffset;
;                 };
;
;.Proto        long  XMM_MoveExtended(struct
;                       XMM_Move *move_descr);
;
;.Params       struct XMM_Move *move_descr -
;                 указатель на структуру, описывающую
;                 что, откуда и куда надо копировать.
;
;.Return       < 0 - ошибка при копировании,
;                    код ошибки находится в старшем байте.
;              0L  - блок скопирован успешно.
;
;.Sample       xms_test.c
;**

XMM_MoveExtended proc near
                         c_begin
          mov  ah, 0Bh
                         mov  si, [bp+4];
          call [XMM_Control]
          xor  dx, dx
          dec  ax
                         jz   @successB
          mov  dh, bl
@successB:
                         c_end
XMM_MoveExtended endp

;**
;.Name         XMM_LockExtended
;.Title        Заблокировать блок расширенной памяти
;
;.Descr        Эта функция блокирует блок расширенной
;              памяти и возвращает 31 разряд его
;              физического адреса.
;
;.Proto        long XMM_LockExtended(unsigned handle);
;
;.Params       handle - индекс блокируемого блока памяти;
;
;.Return       < 0 - блок не заблокирован,
;                    код ошибки находится в старшем байте.
;              > 0L  - блок заблокирован, функция
;                      возвращает физический адрес блока
;                      памяти.
;
;.Sample       xms_test.c
;**

XMM_LockExtended proc near
                         c_begin
          mov  ah, 0Ch
                         mov  dx, [bp+4]
          call [XMM_Control]
                         xchg ax, bx
                         dec  bx
          jz   XMML_Success
          mov  dh, al
XMML_Success:
                         c_end
XMM_LockExtended endp

;**
;.Name         XMM_UnLockExtended
;.Title        Разблокировать блок расширенной памяти
;
;.Descr        Эта функция разблокирует блок расширенной
;              памяти.
;
;.Proto        long XMM_UnLockExtended(unsigned handle);
;
;.Params       handle - индекс блока памяти;
;
;.Return       < 0 - блок не разблокирован,
;                    код ошибки находится в старшем байте.
;              0L  - блок разблокирован.
;
;.Sample       xms_test.c
;**

XMM_UnLockExtended proc near
                         c_begin
          mov  ah, 0Dh
                         mov  dx, [bp+4]
          call [XMM_Control]
          xor  dx, dx
          dec  ax
                         jz   @successC
          mov  dh, bl
@successC:
                         c_end
XMM_UnLockExtended endp

;**
;.Name         XMM_GetHandleLength
;.Title        Получить длину блока расширенной памяти
;
;.Descr        Эта функция возвращает длину блока
;              расширенной памяти по его индексу.
;
;.Proto        long XMM_GetHandleLength(unsigned handle);
;
;.Params       handle - индекс блока памяти;
;
;.Return       < 0 - произошла ошибка,
;                    код ошибки находится в старшем байте.
;              > 0L  - длина блока в килобайтах.
;
;.Sample       xms_test.c
;**

XMM_GetHandleLength proc near
                         c_begin
          mov  ah, 0Eh
                         mov  dx, [bp+4]
          call [XMM_Control]
          or   ax, ax
          mov  ax, dx
                         mov  dx, 0
                         jnz  @successD
          mov  dh, bl
@successD:
                         c_end
XMM_GetHandleLength endp

;**
;.Name         XMM_GetHandleInfo
;.Title        Получить информацию о блоке расширенной памяти
;
;.Descr        Эта функция возвращает общее
;              количество индексов в системе и
;              содержимое счетчика блокирования для
;              заданного индекса.
;
;.Proto        long XMM_GetHandleInfo(unsigned handle);
;
;.Params       handle - индекс блока памяти;
;
;.Return       < 0 - произошла ошибка,
;                    код ошибки находится в старшем байте.
;              > 0L  - младший байт - общее количество
;                      индексов в системе;
;                      старший байт - счетчик блокирования.
;
;.Sample       xms_test.c
;**

XMM_GetHandleInfo proc near
                         c_begin
          mov  ah, 0Eh
                         mov  dx, [bp+4]
          call [XMM_Control]
          mov  dx, bx
          or   ax, ax
          mov  ax, dx
                         mov  dx, 0
                         jnz  @successE
          mov  dh, bl
@successE:
                         c_end
XMM_GetHandleInfo endp

;**
;.Name         XMM_ReallocateExtended
;.Title        Изменить размер блока расширенной памяти
;
;.Descr        Эта функция изменяет размер выделенного
;              блока расширенной памяти.
;
;.Proto        long XMM_ReallocateExtended(unsigned handle,
;                 unsigned new_size);
;
;.Params       handle - индекс блока памяти;
;              new_size - новый размер блока памяти
;                      в килобайтах;
;
;.Return       < 0 - блок не распределен,
;                    код ошибки находится в старшем байте.
;              > 0L  - младший байт содержит индекс
;                      полученного блока памяти.
;
;.Sample       xms_test.c
;**

XMM_ReallocateExtended proc near
                         c_begin
          mov  ah, 0Fh
                         mov  dx, [bp+4]
                         mov  bx, [bp+6]
          call [XMM_Control]
          xor  dx, dx
          dec  ax
                         jz   @successF
          mov  dh, bl
@successF:
                         c_end
XMM_ReallocateExtended endp

;**
;.Name         XMM_RequestUMB
;.Title        Запросить область UMB
;
;.Descr        Эта функция пытается зарезервировать для
;              программы область UMB
;
;.Proto        long  XMM_RequestUMB(unsigned space);
;
;.Params       space - размер требуемой области
;                      в параграфах;
;
;.Return       < 0 - область UMB не назначена программе,
;                    код ошибки находится в старшем байте;
;                    максимальный размер доступного блока
;                    в младшем слове (16 разрядов);
;              > 0L  - область UMB назначена программе,
;                    младшее слово содержит сегмент блока
;                    UMB, старший - размер выделенного
;                    блока UMB.
;
;.Sample       xms_test.c
;**

XMM_RequestUMB proc near
                         c_begin
          mov  ah, 10h
                         mov  dx, [bp+4]
          call [XMM_Control]
                         xchg bx, ax
                         dec  bx
          jz   RUMB_Success
                         xchg ax, dx
                         mov  dh, dl
RUMB_Success:
                         c_end
XMM_RequestUMB endp

;**
;.Name         XMM_ReleaseUMB
;.Title        Освободить область UMB
;
;.Descr        Эта функция пытается освободить
;              область UMB
;
;.Proto        long  XMM_ReleaseUMB(unsigned segment);
;
;.Params       segment - сегмент освобождаемого блока UMB*
;
;.Return       < 0 - область UMB не освобождена,
;                    код ошибки находится в старшем байте.
;              0L - область UMB освобождена.
;
;.Sample       xms_test.c
;**

XMM_ReleaseUMB proc near
                         c_begin
          mov  ah, 11h
                         mov  dx, [bp+4]
          call [XMM_Control]
          xor  dx, dx
          dec  ax
                         jz   @success10
          mov  dh, bl
@success10:
                         c_end
XMM_ReleaseUMB endp

                         END




9.9. Защита программ от отладки

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

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

Защищённый режим работы процессора открывает перед вами новую возможность. Возьмите любую программу, приведённую в этой книге и попытайтесь запустить её под управлением какого-либо отладчика (например, попробуйте Turbo Debugger или Code View). Всё будет хорошо до тех пор, пока ваша программа не попытается загрузить регистр IDTR при помощи команды LIDT. После выполнения этой команды отладчик зависает и единственное средство вновь оживить компьютер - нажать на кнопку сброса, расположенную на системном блоке.

Причина очевидна - изменились расположение и формат дескрипторной таблицы прерываний. Она подготовлена для работы в защищённом режиме, но отладчик работает в реальном режиме. Поэтому обработка всех прерываний, в том числе и от клавиатуры, невозможна.

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

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

Далее процессор можно вернуть в реальный режим и продолжить процесс инсталляции.

Находясь в защищённом режиме, вы можете читать и писать сектора дискеты только используя уровень портов ввода/вывода контроллера флоппи-диска. Программирование контроллера флоппи-диска описано в третьей книге первого тома "Библиотеки системного программиста". Обрабатывать прерывания в защищённом режиме вы уже умеете.

Очевидный недостаток применения защищённого режима при организации защиты от копирования заключается в необходимости использования процессоров i80286, i80386 или i80486. Это означает, что указанный метод непригоден для компьютеров IBM PC/XT, использующих процессор i8086 или i8088.

Однако в последнее время большинство программных комплексов требует наличия в компьютере по крайней мере процессора i80286, поэтому указанным недостатком можно пренебречь. К тому же следует учесть резкое увеличение стойкости инсталлятора, работающего в защищённом режиме, к попыткам взлома.