Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
 iakovlev.org 
 Books
  Краткое описание
 Linux
 W. R. Стивенс TCP 
 W. R. Стивенс IPC 
 A.Rubini-J.Corbet 
 K. Bauer 
 Gary V. Vaughan 
 Д Вилер 
 В. Сталлинг 
 Pramode C.E. 
 Steve Pate 
 William Gropp 
 K.A.Robbins 
 С Бекман 
 Р Стивенс 
 Ethereal 
 Cluster 
 Languages
 C
 Perl
 M.Pilgrim 
 А.Фролов 
 Mendel Cooper 
 М Перри 
 Kernel
 C.S. Rodriguez 
 Robert Love 
 Daniel Bovet 
 Д Джеф 
 Максвелл 
 G. Kroah-Hartman 
 B. Hansen 
NEWS
Последние статьи :
  Тренажёр 16.01   
  Эльбрус 05.12   
  Алгоритмы 12.04   
  Rust 07.11   
  Go 25.12   
  EXT4 10.11   
  FS benchmark 15.09   
  Сетунь 23.07   
  Trees 25.06   
  Apache 03.02   
 
TOP 20
 MINIX...3057 
 Solaris...2933 
 LD...2904 
 Linux Kernel 2.6...2470 
 William Gropp...2180 
 Rodriguez 6...2011 
 C++ Templates 3...1945 
 Trees...1937 
 Kamran Husain...1865 
 Secure Programming for Li...1792 
 Максвелл 5...1710 
 DevFS...1693 
 Part 3...1682 
 Stein-MacEachern-> Час...1632 
 Go Web ...1624 
 Ethreal 4...1618 
 Arrays...1607 
 Стивенс 9...1603 
 Максвелл 1...1592 
 FAQ...1538 
 
  01.01.2024 : 3621733 посещений 

iakovlev.org

9. ПРИЛОЖЕНИЕ

9.1. Регистр EFLAGS

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

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

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

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

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

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

9.8. Утилита MEMOSCOP

9.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-224-битовый базовый адрес сегмента
3Байт доступа, его формат полностью аналогичен формату байта доступа дескриптора, за исключением бита присутствия. На месте этого бита находится бит VALID. Если этот бит сброшен в 0, при попытке использовать дескриптор для адресации памяти произойдёт исключение 13 с кодом ошибки 0.
4-516-битовый предел сегмента

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

  • Запретитите прерывания.
  • Сохраните где-нибудь в буфере программы область памяти, начинающуюся с адреса 00800h и имеющую длину 102 байта.
  • Заполните буфер для команды LOADALL необходимыми значениями для всех загружаемых регистров. Базовый адрес в области кэша дескриптора сегмента данных должен указывать на необходимый вам участок расширенной памяти.
  • Выполните команду LOADALL. Сегмент данных теперь будет указывать на область расширенной памяти.
  • Выполните запись или чтение области расширенной памяти.
  • Восстановите базовый адрес сегмента данных в кэше дескриптора данных в буфре, расположенном по адресу 00800h.
  • Выполните команду 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, поэтому указанным недостатком можно пренебречь. К тому же следует учесть резкое увеличение стойкости инсталлятора, работающего в защищённом режиме, к попыткам взлома.
Оставьте свой комментарий !

Ваше имя:
Комментарий:
Оба поля являются обязательными

 Автор  Комментарий к данной статье