Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
iakovlev.org

PROTECTED MODE

Сегментация

Сегмент - блок памяти,который имеет фиксированный базовый адрес и длину. Любая ссылка на ячейку памяти требует, чтобы в ней было значение сегмента и значение смещения. Смещение подразумевается относительно базового адреса сегмента. Давайте разберемся,как интерпретируется значение сегмента в режиме 386.

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

Селектор имеет длину в 2 байта и состоит из 3-х различных полей.

 Bits  0..1       Request Privilege level        (можно просто установить в 0)
 
 Bit   2          Table Indicator        0 = Global Descriptor Table
                                         1 = Local Descriptor Table
 
 Bits 3..15      индекс , указывающий на дескриптор таблицы
 

Глобальная таблица дескрипторов (GDT)

GDT - таблица дескрипторов,которая хранится в памяти. Адрес этой таблицы грузится в специальный регистр, называемый global descriptor table register - GDTR. Наличие GDT для загрузки защищенного режима обязательно- в ней мы определяем все сегменты.

Каждый дескриптор включает в себя комплексную информацию о сегменте- фактически это краткое описание сегмента. Длина дескриптора - 64 бита,в нем много различных полей. Индекс , загружаемый в биты 3..15 сегментного регистра, определяет загружаемый сегмент.

Пример:
Предположим,у вас есть сегмент данных. И вы хотите загрузить из него адрес 012345h. Дескриптор этого сегмента данных хранится в GDT и его индекс - 6.
Решение:
Мы берем любой из следующих регистров (SS,DS,FS,GS,ES) и грузим в него 6-й селектор GDT. Затем уже нужно загрузить с его помощью загрузить требуемый адрес.
Один из регистров (FS,DS,GS,SS,CS or ES) должен быть загружен с учетом следующих 3-х полей:

         Request Privilege level ( Bits 0..1 ) = 0  (практически всегда это ноль)
         Table Indicator ( bit 2 )  = 0  
         Index  ( bits 3..15 ) = 6
 
              
      mov  ax,0000000110000b      ;выбираем регист DS 
      mov  ds,ax          
      mov  byte ptr DS:[ 012345h ],0     ; используем косвенную адресацию DS
 
 
Архитектура 386 - многозадачная. Для управления многозадачностью дескрипторы бывают нескольких типов. Есть 2 группы дескрипторов :
1) CODE/DATA дескрипторы для кода и для данных.
2) SYSTEM дескрипторы , используемые для многозадачности.

Формат дескрипторов кода и данных

 BITS                 описание
 ----------------------------------------------------------------
 0..15                SEGMENT LIMIT 0...15
 16..39               SEGMENT BASE 0..23
 40                  (A)   бит доступа
 41..43              (TYPE) 0 = смотрите ниже
 44                  (0)  0 = code/data descriptor 1 = system descriptor
 45..46              (DPL) Descriptor Privilege level
 47                  (P)  Segment Present bit
 48..50              SEGMENT LIMIT 16..19
 51..52              (AVL)   2 bits available for the OS
 53                  zero for future processors
 54                  Default Operation size used by code descriptors only
 55                  Granularly:  1 = segment limit = limit field *1000h
                                 0 = segment limit = limit field
 56..63              SEGMENT BASE 24..31      
 
 формат поля TYPE
           bit  2    Executable (E)     0 = сегмент данных
                1    Expansion Direction (ED) 0 = Expand up
                                              1 = Expand up
                0    Writeable (W)       W = 0 сегмент данных только на чтение
                                         W = 1 сегмент данных R/W     
 
 
           bit  2    Executable (E)  1 = кодовый сегмент
                1    Conforming (C)   ( ? )
                0    Readable (R)        R = 0 кодовый сегмент на выполнение
                                         R = 1 кодовый сегмент на чтение
 
Если вы загрузите неверный селектор в сегментный регистр, произойдет General Protection exception( interrupt 13 ). В защищенном режиме 13-е исключение может быть использовано в специальных целях.

Линейные адреса - физические адреса

Трансляция адресов происходит в segmentation unit. segmentation unit берет информацию из дескрипторных таблиц, селекторов,смещений и получает 32-битный линейный адрес. Это вычисление происходит следующим образом:
Линейный адрес = базовый адрес сегмента + смещение.
Базовый адрес сегмента берется в битах 16..39 дескриптора GDT. Индекс сегментного регистра ( биты 3..15) вычисляет этот дескриптор.

Пример:
MOV EAX, ES:[EDX*8+012345678h]
EDX = 100h
ES=селектор,указывающий на дескриптор с базовым адресом = 02000000h.
Линейный адрес = 2000000h + ( 100h*8 + 012345678h ) = 014345E78h

В архитектуре 386 есть второй модуль управления памятью - Paging Unit. Линейный адрес - это еще не физический адрес. Он должен быть обработан с помощью Paging Unit.
paging unit бьет линейный адрес на 3 части:

 bits 0..12         смещение страницы
 bits 13..23        указатель на страницу в page table
 bits 24..31        указатель на другую таблицу - page directory table, 
 				   в которой лежит ссылка на page table
 
page table состоит из 1024 4-х байтовых строк. В каждой из них лежит физический адрес 4-килобайтной страницы. page directory также состоит из 1024 double word строк. Каждая строка хранит физический адрес page table. Физический базовый адрес самой DIRECTORY TABLE лежит в CR3. Доступ к paging unit может быть разрешен/запрещен с помощью бита 31 CR0. Если пэйджинг задисэблен,то линейный адрес становится равным физическому.

 
             32-битные линейные адпеса , получаемые на выходе segmentation unit
   -------------------------------------------------------------------------
   |   BITS  0..12             |    BITS  13..23       |    BITS 24..31     |
   |                           |                       |                    |
   -------------------------------------------------------------------------
                |                                  |                   |
                |------------------------          |                   |
                                         |         |                   |
                                         |         |                   |
                                         |         |                   |
         A Page in memory (4KB)          |         |                   |
      --------------------------         |         |                   |    
      |                        |         |         |                   |
      |                        |         |         |                   |
      |                        |         |         |                   |
      |                        |         |         |                   |
      |                        |         |         |                   |
      |                        |         |         |                   |
      |                        |         |         |                   |
      |                        | <-------          |                   |
      |                        |                   |                   |
      |                        |                   |                   |
      |                        |                   |                   |
 |---> -------------------------                   |                   |
 |                                                 |                   |
 |                                                 |                   |
 |                                                 |                   |
 |            PAGE  TABLE          Offset          |                   |
 |    --------------------------                   |                   |
 |    |                        |    4092           |                   |
 |    --------------------------                   |                   |
 |    .                        .                   |                   |
 |    .                        .                   |                   |
 |    .                        .                   |                   |
 |    --------------------------                   |                   |
 |    |                        |     12            |                   |
 |    --------------------------                   |                   |
 |---<|   address of page      |     8   <---------                    |
      --------------------------                                       |    
      |                        |     4                                 |
      --------------------------                                       |
      |                        |     0                                 |
 |--> --------------------------                                       |
 |                                                                     |
 |                                                                     |
 |                                                                     |
 |                                                                     |
 |                                                                     |
 |         DIRECTORY TABLE          Offset                             |
 |    --------------------------                                       |
 |    |                        |    4092                               |
 |    --------------------------                                       |
 |    .                        .                                       |
 |    .                        .                                       |
 |    .                        .                                       |
 |    --------------------------                                       |
 |---<| address of page table  |     12  <-----------------------------
      --------------------------                                       
      |                        |     8   
      --------------------------                   
      |                        |     4             
      --------------------------                   
      |                        |     0             
 |--> --------------------------                   
 |
 |
 |                           
 |         ----------------------------------------------
 --------<-|               CR3                          |
           ----------------------------------------------
 
 

Global Descriptor Table Register (GDTR)

Содержимое его есть физический адрес Global Descriptor Table (GDT). GDTR - 48-битный регистр. Нижние 2 байта определяют т.н. LIMIT, или размер GDT в байтах. Значение LIMIT меньше на единицу размера этой таблицы. Если например LIMIT=00FFh , длина таблицы - 256 байт. LIMIT = 16 бит, поэтому размер GDT ограничен 65,536 байтами. Старшие 4 байта GDTR - BASE, определяют базовый физический адрес GDT.

Interrupt Descriptor Table Register (IDTR) :

Регистр IDTR определяет таблицу в физической памяти-Interrupt Descriptor Table (IDT).
IDTR также 48-битный. Нижние 2 байта - LIMIT. IDT также ограничена в 65,536 байт. Но есть ограничение : архитектура 386 поддерживает 256 прерываний. Верхние 3 байта IDTR - BASE , это базовый физический адрес IDT. Дескрипторы IDT еще называются гейтами , имеют длину в 8 байт.

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

Контрольные реистры : Защищенный режим поддерживается с помощью 4-х системных контрольных регистра:

         31          23             15             7           0
         -------------------------------------------------------
         | Page Directory Base Register (PDBR)|     Reserved   | CR3
         -------------------------------------------------------
         |                Page Fault Linear Address            | CR2
         -------------------------------------------------------
         |                       RESERVED                      | CR1
         -------------------------------------------------------
         |P|                                         |R|T|E|M|P| CR0
         |G|                     RESERVED            | |S|M|P|E|
         -------------------------------------------------------
 
Нижние 5 бит в CR0 - контрольные флаги. Регистры CR0,CR2,CR3 могут быть использованы для управления пэйджинга. 4 бита CR0 - PE, MP, EM , R - контролируют конфигурацию защищенного режима. Пятый бит TS - бит статуса. Эти биты можно модифицировать.
Бит PE - protected-mode enable (PE) - определяет режим процессора - либо реальный , либо защищенный. По умолчанию PE=0, или реальный режим. Для входа в защищенный режим PE переключается в 1. Для возвращения в реальный режим PE переключается в 0.
Пэйджинг включается путем переключения бита PG регистра CR0 в 1.

Task Register (TR): Хранит 16-битный селектор. При загрузке системы в него грузится стартовый селектор. В дальнейшем,когда происходик task switch,его значение меняется автоматически. При этом инициализируется дескриптор task state segment (TSS),который помещается в стек задачи. Этот дескриптор описывает область памяти,которая хранит информацию,необходимую для инициализации задачи, такую,как значения пользовательских регистров.

В сегментные регистры грузятся селекторы:

         15                        8                     2     0
         -------------------------------------------------------
         |           INDEX                              |TI|RPL|
         -------------------------------------------------------
                                 SELECTOR
 
       Bits Name                             Function
       1-0  Requested        уровень привилегий сегмента
            Privilege
            Level (RPL)
 
       2    Table Indicator  TI = 0 Global Descriptor Table (GDT)
            (TI)
                             TI = 1  Local Descriptor Table (LDT)
 
      15-3  INDEX            индекс дескриптора в таблице 
 
 
 
 

Protected-Mode system control instruction set

 Instruction                     Description                              Mode
 LGDT S      Загрузка регистра GDT. S specifies      						Both
             the memory location that contains the first byte of the
             6 bytes to be loaded into the GDTR.
 
 SGDT D      Сохраняет значение LGDT.                    D specifies        Both
             the memory location that gets the first of the six bytes
             to be stored from the GDTR.
 
 LIDT S      Загрузка регистра IDT.                         S specifies      Both
             the memory location that contains the first byte of the
             6 bytes to be loaded into the IDTR.
 
 SIDT D      Store the interrupt descriptor table register. D specifies   Both
             the memory location that gets the first of the six bytes
             to be stored from the IDTR.
 
 LMSW S      Load the machine status word. S is an operand to specify     Both
             the word to be loaded into MSW.
 
 SMSW D      Store the machine status word. D is an operand to specify    Both
             the word location or register where the MSW is to be
             saved.
 
 LLDT S      Load the local descriptor table register. S specifies the  
 Protec      operand to specify a word to be loaded into the LDTR.
 
 SLDT D      Store the local descriptor table register. D is an operand 
 Protec      to specify the word location where the LDTR is to be saved.
 
 LTR S       Load the task register. S is an operand to specify a word  
 Protec      to be loaded into TR (Task Register).
 
 STR D       Store the task register. D is an operand to specify the    
 Protec      word location where the TR is to be stored.
 
 LAR D,S     Load access rights byte. S specifies the selector for the  
 Protec      descriptor whose access byte is loaded into the upper byte
             of the D operand. The low byte specified by D is cleared.
             The zero flag is set if the loading completes successfully;
             otherwise it is cleared.
 
 LSL R16,S   Load segment limit. S specifies the selector for the       
 Protec      descriptor whose limit word is loaded into the word
             register operand R16. The zero flag is set if the
             loading completes successfully; otherwise it is cleared.
 
 ARPL D,R16  Adjust RPL field of the selector. D specifies the selector 
 Protec      whose RPL field is increased to match the PRL field in the
             register. The zero flag is set if successful;otherwise it
             is cleared.
 
 VERR S      Verify read access. S specifies the selector for the       Protec
             segment to be verified for read operation, If successful
             the zero flag is set; otherwise it is reset.
 
 VERW S      Verify write access. S specifies the selector for the      
 Protec      segment to be verified for write operation, If successful
             the zero flag is set; otherwise it is reset.
 
 CLTS        Clear task switched flag.                                  
 Protec
 Примеры :
 

LGDT [INIT_GDTR]
Loads the GDTR with the base and limit pointed to by address INIT_GDTR to create a global descriptor table in memory. This instruction is meant to be used during system initialisation and before switching the 80386DX to the protected mode. Once loaded the current contents of the GDTR can be saved in memory by executing the store global table (SGDT) instruction. SGDT [SAVE_GDTR] The instruction load machine status word (LMSW) and store machine status word (SMSW) are provided to load and store the contents of the machine status word (MSW), respectively. These are the instructions that are used to switch the 80386DX from real to protected mode. To do this we must set the least significant bit in the MSW to 1. This can be done by first reading the contents of the machine word , modifying the LSB (PE), and then writing the modified value back into the MSW part of CR0. The instruction sequence that follows will switch an 80386DX operating in real mode to protected mode: SMSW AX ;read from the MSW OR AX,1 ;modify the PE bit LMSW AX ;write to the MSW Solution 1.1 : Each descriptor takes up eight bytes; therefore, a 4096-byte table can hold : Descriptors = 4096/8 = 512 Solution 1.2 : The maximum number of interrupt descriptors than can be used in an 80386DX microcomputer system is 256. Therefore, the maximum table size in bytes is : IDT (size) = 8*256 = 1000h bytes LIMIT = 1000h-1 = 0FFFh (We start from zero) Solution 1.3 : From the values of the base and limit, we find that the table is located in the address range IDT (start) = 00011000h IDT ( end ) = 000111FFh The last descriptor in this table takes up the eight bytes of the memory from address 000111F8h through 000111FFh. Следующий код проверяет версию процессора. Для этого оттестируем флаговый регистр. В 80386, флаговые биты 12-14 используются для I/O Privilege Level (IOPL) и Nested Task (NT), поэтому проверим , можно ли их модифицировать - а значит , можем ли мы переключаться между защищенным и реальным режимами: ; checks for a 386 no386e db 'Sorry, at least a 80386 is needed!',13,10,'$' proc check_processor pushf ; save flags xor ah,ah ; clear high byte push ax ; push AX onto the stack popf ; pop this value into the flag register pushf ; push flags onto the stack pop ax ; ...and get flags into AX and ah,0f0h ; try to set the high nibble cmp ah,0f0h ; the high nibble is never 0f0h on a je no386 ; 80386! mov ah,70h ; now try to set NT and IOPL push ax popf pushf pop ax and ah,70h ; if they couldn't be modified, there jz no386 ; is no 80386 installed popf ; restore the flags ret ; ...and return no386: ; if there isn't a 80386, put a msg mov dx,offset no386e ; and exit jmp err16exit endp check_processor ; exits with a msg ; In: DS:DX - pointer to msg proc err16exit mov ah,9 ; select DOS' print string function int 21h ; do it mov ax,4cffh ; exit with 0ffh as exit code int 21h ; good bye... endp err16exit Следующую вещь,которую мы проверим - в каком режиме мы находимся. Посмотрим на Control Register 0: бит 0 в случае Real Mode, иначе - PM. ; checks if we are running in Real Mode nrme db 'You are currently running in V86 mode!',13,10,'$' proc check_mode mov eax,cr0 ; get CR0 to EAX and al,1 ; check if PM bit is set jnz not_real_mode ; yes, it is, so exit ret ; no, it isn't, return not_real_mode: mov dx,offset nrme jmp err16exit endp check_mode При переключении в Protected Mode, вместо MOV CR0,EAX можно использовать LMSW AX. Для выхода из PM ее использовать нельзя. ТАБЛИЦЫ И ДЕСКРИПТОРЫ GDT - Global Descriptor Table. Эта таблица хранит дескрипторы. В Real Mode, сегмент ограничен 64kb и его базовый адрес кратен 16. В Protected Mode базовый адрес сегмента может быть производным. Мы не будем использовать в своем примере LDT. Базовый формат регистров : Segment Descriptor ------------------ 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00 [ Base address 31:24 ] [G] [D] [0] [AVL] [Sg. length 19:16] [P] [DPL] [DT] [ Type ] [ Base address 23:16 ] [ Base address 15:00 ] [ Segment length 15:00 ] Размер дескриптора равен 4*16=64 bits. ; contains a segment descriptor struc segment_descriptor seg_length0_15 dw ? ; low word of the segment length base_addr0_15 dw ? ; low word of base address base_addr16_23 db ? ; low byte of high word of base addr. flags db ? ; segment type and misc. flags access db ? ; highest nibble of segment length ; and access flags base_addr24_31 db ? ; highest byte of base address ends segment_descriptor С помощью этой структуры можно управлять GDT и LDT. Формат IDT отличается: 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00 [ Offset 31:16 ] [P] [ DPL ] [0] [1] [1] [1] [0] [0] [0] [0] [0] [0] [0] [0] [0] [ Selector 00:15 ] [ Offset 00:15 ] То же для IDT на асм-е: ; contains an interrupt descriptor struc interrupt_descriptor offset0_15 dw ? ; low word of handler offset selector0_15 dw ? ; segment selector zero_byte db 0 ; unused in this descriptor format flags db ? ; flag-byte offset16_31 dw ? ; high word of handler offset ends interrupt_descriptor Таблица описания дескриптора: Bit name Meaning ------------------------------------------------------------------------------- G Granularity. G=0 => 1 Byte granularity G=1 => 4k granularity This bit specifies the granularity of the segment. If the bit is clear, the length field in the descriptor reflects the real length of the segment in bytes. If the bit is set, you have to multiply the length field in the descriptor by 4096 to get the real length in bytes. D Default Operand Size. D=0 => 16 bit operands D=1 => 32 bit operands This bit specifies the default operand size which has to be used by special opcodes (like REP xxx). If the bit is clear, the default operand size is 16 bit and the processor behaves similar to a 80286. If the bit is set, the default operand size is 32 bit. D=0 does not mean you can't use 32 bit instructions, it only affects the default operand sizes. AVL Available for System. This bit is not used in 80286/80386/80486 machines. If somebody has information about how it is used on Pentium machines, please mail me. For now, better keep it to zero to keep compatibility. However, if your program only runs on machines lower than Pentium, you can use it as a mark for your own software or whatever. P Presence. P=0 => segment is not present (or invalid) P=1 => segment is present and valid With this bit you can easily implement a virtual memory manager (VMM). If the application wants to allocate more memory than available, save the least used segment (determined with help of the A bit) to disk, then clear the P bit in its descriptor. The next access to that segment will be followed by a General Protection Fault. Catch the fault, reload the segment into memory and set its P bit. Done. The processor checks only the P bit before generating the General Protection Fault, so if P is set to zero, the rest of the descriptor is available to keep information for your VMM. DPL Descriptor Privilege Level. 0 <= DPL <= 3 The DPL bits contain the Descriptor Privilege Level. The Privilege Level has a range from 0 (highest privilege level) to 3 (lowest privilege). If a program tries to access a segment with a higher privilege level than its own, the processor will generate a General Protection Fault. REMARK: Every time I speak of Privilege Levels in this text, I mean that HIGH Privilege Level is a LOW number in DPL, LOW Privilege Level is a HIGH number in DPL. Example: segment 1: DPL=1 \ -> segment 1 is more privileged segment 2: DPL=3 / DT Descriptor Type. DT=0 => System Descriptor (System-Segment or Gate) DT=1 => Application Descriptor (Data or Code) If this bit is clear, the Descriptor describes a segment that is (a) available for the System Software, or (b) a Gate-Descriptor. Type Segment type. These four bits select the segment type. Bit 3 2 1 0 Type Description Name T E W A 0 0 0 0 Data read-only 0 0 0 1 Data read-only, accessed 0 0 1 0 Data read/write 0 0 1 1 Data read/write, accessed 0 1 0 0 Data read-only, expand down 0 1 0 1 Data read-only, exp. down, acc. 0 1 1 0 Data read-write, expansion down 0 1 1 1 Data read-write, exp. down, acc. 1 0 0 0 Code exec-only --------------------------------------------------------------- Name T C R A 1 0 0 1 Code exec-only, accessed 1 0 1 0 Code exec-read 1 0 1 1 Code exec-read, accessed 1 1 0 0 Code exec-only, conforming 1 1 0 1 Code exec-only, conf., acc. 1 1 1 0 Code exec-read, conforming 1 1 1 1 Code exec-read, conf., acc. Read-only means that you are only allowed to read this segment. Read-write means that you can read and write from/to the segment. Exec-only segments can only be executed, but no read-access is allowed. Exec-read segments can be read and executed. Unlike as in Real Mode, you aren't allowed to use self-modifying code. A way around this can be found a few lines ahead in this text. The Accessed bit is set everytime a program tried to access this segment _and_ the bit isn't already set. If you want to figure out which segment to swap (the famous VMM example), increase a counter if the A bit is set and then clear this bit. The segment with the lowest counter position can safely be swapped out. BUT WATCH OUT: If the A bit is set, a program might run in this segment! Swapping these segments may hurt! Expansion Direction is a weird thing. If the bit is clear, the Expansion Direction is upwards, that means the segment grows upwards. To grow it, increase the length. You are allowed to access every address that is 0 <= Address <= Limit Limit means the actual length of the segment. If the segments Granularity is set to 0, the limit is equal to the length. But if Granularity is set to 1, you first have to multiply the length by 4096 (4k) to get the length. If the bit is set however, welcome to hell. Now the Expansion Direction is _downwards_, that means to grow the segment, you'll have to _decrease_ the Length. You are allowed to access every address that is G=0 : Limit-1 <= Address <= 0ffffh G=1 : Limit-1 <= Address <= 0ffffffffh. Because of the 4G Wrap-Around, these addresses are just the ones that would cause a General Protection Fault if E would be zero. Conforming means that a segment with C=1 can call another segment with a lower or equal Privilege Level. The Current Privilege Level however isn't changed! If you call directly from a segment with C=0 (and not through a Task-Gate) to a segment that has another Privilege Level than the Current Privilege Level, a General Protection Fault follows. Формат селектора : 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00 [ Pointer into a Descriptor Table ] TI [ RPL ] RPL - Requested Privilege Level. TI : TI=0 - GDT, TI=1 - LDT. Теперь проинициализируем 2 сегмента - сегмент данных и сегмент кода : segment code16 para public use16 ; <- the 16-bit code and data segment assume cs:code16, ds:code16 ends code16 segment code32 para public use32 ; <- the 32-bit code and data segment assume cs:code32, ds:code32 ends code32 Здесь 2 кодовых сегмента. В кодовый сегмент как известно нельзя писать. Создадим композитный сегмент. Для этого в GDT создадим дескриптор , который будет указывать на кодовый сегмент После этого в регистр CS мы загрузим кодовый селектор , а в DS, ES, FS , GS загрузим селектор данных. ; GDT data gdt_reg dw gdt_size,0,0 dummy_dscr segment_descriptor <0,0,0,0,0> code32_dscr segment_descriptor <0ffffh,0,0,9ah,0cfh,0> data32_dscr segment_descriptor <0ffffh,0,0,92h,0cfh,0> core32_dscr segment_descriptor <0ffffh,0,0,92h,0cfh,0> code16_dscr segment_descriptor <0ffffh,0,0,9ah,0,0> data16_dscr segment_descriptor <0ffffh,0,0,92h,0,0> gdt_size=$-(offset dummy_dscr) Загрузим GDT : lgdt [fword ds:gdt_reg] Теперь IDT: lidt [fword ds:idt_reg] В нашей GDT имеем 6 дескрипторов : 32-bit кодовый (4G size, 32-bit operands, code type), 32-bit данных (4G size, 32-bit operands, data type), 32-bit кодовый (4G size, 32-bit operands, data type), 16-bit кодовый (64k size, 16-bit operands, code type) и 16-bit данных (64k size, 16-bit operands, data type). Нулевой дескриптор нужен в частности для дебага защищенного режима. Если происходит попытка загрузить неправильный дескриптор, будет сгенерирован General Protection Fault и загружен этот самый нулевой дескриптор. Запрещаем прерывания : cli переключаемся в Protected Mode: mov eax,cr0 or al,1 mov cr0,eax (или LMSW AX) Далее : db 0eah dw offset start32, code32_idx 0EAh - это opcode для JMP FAR. Это необходимо для того , чтобы загрузить CS дескриптором PM. И стартует Protected Mode с метки START32. Грузим остальные сегментные регистры и вызываем MAIN. Нужно также включить A20 . 1. Для того чтобы вернуться назад в Real Mode из 32-bit: db 0eah dw offset real_mode_proc, 0 , segment_selector ^^^ 2. You can use _every_ register as an index. mov eax,[edx] 3. You can use displacements _and_ factors in pointers. mov eax,[ecx*8+edx] All factors have to be powers of 2. 4. Sometimes this feature can be used for quick multiplies: lea eax,[eax*8+eax] -> EAX=EAX*9 5. IMUL accepts immediates: imul ecx,5 -> ECX=ECX*5 6. IMUL accepts immediates _and_ a register: imul eax,ecx,5 -> EAX=ECX*5 7. Extended form of SHR/SHL to shift across registers: shrd eax,edx,5 shld eax,edx,5 In this example, the 5 bits that become free in EAX will be filled with bits of EDX. EDX is not modified. 8. This feature can be used to get rid of one MOV instruction: Assume ECX contains a linear address that you want to convert into a segment:offset notation. Normally, you would use something like this: mov eax,ecx shr eax,4 ; EAX now contains segment and ecx,0fh ; ECX now contains offset With the SHRD instruction, you can modify it to shld eax,ecx,28 ; EAX now contains segment and ecx,0fh ; ECX now contains offset REMEMBER: The CPU only uses the 5 lower bits of the shift factor, so shld eax,ecx,32 will _not_ copy ECX to EAX!!! 9. You can push immediates: push 12345 ИСКЛЮЧЕНИЯ Список Faults, Traps , Exceptions. No. Name Type Error Code Cause ---------------------------------------------------------------------------- 0 Division by Fault no Someone tried to Zero divide by zero. Same as in Real Mode 1 Single Step Trap,Fault no This interrupt is called after each instruction if the Trap Flag is set 2 Non Maskable Abort no Heavy hardware failure. Interrupt (NMI) Same as in Real Mode. 3 Breakpoint Trap no Used for debugging purposes. Called by special INT3 opcode. 4 Overflow Trap no Called if INTO is executed and the Overflow Bit is set. 5 Bound Range Fault no BOUND failed Exceeded 6 Invalid Opcode Fault no CPU found an invalid opcode. Same as in Real Mode. 7 Coprocessor Fault no Called if CPU tries to Not Available execute ESC or WAIT and EM bit is clear. 8 Double Fault Abort yes (always 0) An exception occured while another exception handler is active. 9 Coprocessor Abort no The middle operand Segment Overrun of a FPU instruction can't be accessed. Dunno what this should be, i486 doesn't has this exception any more. 10 Invalid TSS Fault yes Tried to switch to a task with an invalid TSS. 11 Segment not Fault yes Someone tried to access Present a segment that had it's Present bit clear. 12 Stack Exception Fault yes Called if stack exceeds it's limits or if selector for SS is invalid 13 General Fault yes Someone tried to access Protection Fault invalid, protected or not-present data. 14 Page Fault Fault yes Called if paging is enabled and and an access to an invalid, protected or not-present page occured. 16 Coprocessor Fault no The FPU saw that it Error was doing something totally wrong... :) 17 Arrangement ???? ?? This exception occurs Error only if AC=1, AM=1 and CPL=3. If memory isn't accessed at integral addresses, EXCP17 is generated. (see table below) 0..255 Software Trap no If you call one of Interrupts these interrupts from your program (INT xx), they are handled like Traps. Faults are documented and recoverable errors. The return address for the IRET instruction (CS:EIP) points to the opcode that caused the Fault. Some Faults have an error code on the stack (see below). To solve the problem, the handler only has to read in the failed opcode and react on it. Traps are interrupts that are caused by your program (INT xx instruction) or by a debugging mechanism (INT3 or Trap Mode). An error code is never generated. CS:EIP points to the opcode following the one that caused the Trap. Aborts are only caused if the system tables (GDT, IDT, LDT) are invalid or if there was a hardware failure. They don't allow you to return to your program, nor there's an error code. Формат error code: 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00 [ Reserved ] [ Selector ] TI IDT EXT Bit name Meaning ------------------------------------------------------------------ Selector Selector of segment where the error occured TI Table Indicator (applies only if IDT=0) TI=0 - Selector from GDT TI=1 - Selector from LDT IDT Interrupt Descriptor IDT=0 - No Interrupt Gate IDT=1 - Selector from IDT EXT External EXT=0 - Program caused Exception EXT=1 - Exception caused by external event Arrangement Check Errors Data Type Address has to be dividable by ---------------------------------------------------------------------- Word 2 Double Word 4 Floating Point, Single Precision 4 Floating Point, Double Precision 8 Floating Point, Extended Precision 8 Selector 2 48-Bit Farpointer 4 Contents of GDTR and IDTR (48 Bits) 4 32-Bit Farpointer 2 32-Bit Address 4 Bitstrings 4 FPU Environment Blocks or FPU State 4 or 2, depending on Operand Length An example how to handle these exceptions is not yet included in the demo source. If someone would like to see more about this -> mail me! (as you might have noticed, I definitely WANT your mails! :) ) ПОЛЕЗНЫЕ INTERRUPTS Формат : Function name Interrupt number (or CALL address) in hexadecimal numbers Input registers Return registers Notes Func: Get RAM Size Call: INT 12h Input: --- Return: AX - Memory size in kb Notes: Returns only size of conventional memory Func: Move Block (AH=87h) Call: INT 15h Input: AH=87h CX=Number of Words in Buffer ES:SI=Address of Descriptor Table Return: CY=0 - ok CY=1 - error AH=Status Notes: Transfers a block of max. 64k (max. CX=8000h) via Protected Mode to any memory location. ES:SI -> Dummy <--, Table begin --' Source Descriptor Destination Descriptor BIOS Codesegment Stacksegment Every entry is 8 bytes long. First entry has to be set to zero. Entry structure: - Segment size (word) - Low Word 24-bit Segment Address (word) - High Byte 24-bit Segment Address (byte) - Access Flag (byte, =93h) - Reserved (word) The last two entries have to be set to zero. Error code in AH: 00 - Transfer completed without error 01 - RAM Parity Error 02 - Exception 03 - A20 failure Func: Extended Memory Size (AH=88h) Call: INT 15h Input: AH=88h Return: CY=0 - ok AX=ext. memory size in kb CY=1 - error AH=error code (80h - invalid command, 86h - function not supported) Notes: Function returns extended memory size stored at address 20h and 31h in CMOS RAM of clock chip. Extended Memory can only be used if base memory is at least 512k big. If HIMEM.SYS is loaded, the function returns 0. Func: Virtual Mode (AH=89h) Call: INT 15h Input: AH=89h BH=first PIC start BL=second PIC start CX=ofsfet of code segment ES:SI=pointer to Descriptor Table Return: CY=0 - ok AH=0 CY=1 - error AH=0ffh Notes: Function destroys all registers. ES:SI points to Descriptor Table. ES:SI -> Dummy <--, Table begin --' Interrupt Table User Data Segment (DS) User Extra Segment (ES) User Stack Segment (SS) User Code Segment (CS) Internal Code Segment Entries structured like in function Move Block (AH=87h). Dummy has to be set to zero. Third entry points to IDT built by program (Real Mode structure). Last Descriptor has to be set to zero. BH contains mappings for first PIC (start of first 8 hardware interrupts, i.e. BH=8 if IRQ0 should be shifted to IRQ8). BL contains mappings for second PIC. Carry flag has to be cleared before function call. CX contains code segment where execution in V86 should start. After function call no BIOS is available, return to Real Mode only by resetting CPU. VIRTUAL DMA SPECIFICATION (VDS) -- VDS обслуживает в Protected Mode механизм пэйджинга Error codes used in all functions: 01h region not in contigous memory 02h region crossed a physical alignment boundary 03h unable to lock pages 04h no buffer available 05h region too large for buffer 06h buffer currently in use 07h invalid memory region 08h region wasn't locked 09h number of physical pages greater than table length 0ah invalid buffer ID 0bh copy out of buffer range 0ch invalid DMA channel number 0dh disable count overflow 0eh disable count underflow 0fh function not supported 10h reserved flag bits set in DX DMA Descriptor Structure - DDS Формат: Offset Bytes Meaning 00h 4 size of region 04h 4 region offset 08h 2 region segment 0ah 2 buffer ID 0ch 4 linear address extended format (EDDS): Offset Bytes Meaning 00h 4 size of region 04h 4 region offset 08h 2 region segment 0ah 2 reserved 0ch 2 number available 0eh 2 number used 10h 4 linear address (region 0) 14h 4 size in bytes (region 0) 18h 4 linear address (region 1) 1ch 4 size in bytes (region 1) . . . Структура page tables: Offset Bytes Meaning 00h 4 size of region 04h 4 region offset 08h 2 region segment 0ah 2 reserved 0ch 2 number available 0eh 2 number used 10h 4 Page Table Entry 0 14h 4 Page Table Entry 1 . . . Bits 1-12 of a Page Table Entry should be cleared. Bit 0 has to be set if the page is present and locked. Func: VDS Get Version (AX=8102h) Call: INT 4Bh Input: AX=8102h DX=0 Return: CY=1 - error AL=error code CF=0 - ok AH=major version number AL=minor version number BX=product number CX=revision number DX=flags SI:DI=buffer size Notes: Flag bits in DX: Bit Meaning 0 PC/XT Bus System (1Mb addressable) 1 physical buffer/remap region in 1st Mb 2 automatic remap enabled 3 all memory physically contigous 4-15 reserved SI:DI contains maximal size of DMA buffer. Func: VDS Lock DMA Region (AX=8103h) Call: INT 4Bh Input: AX=8103h DX=Flags ES:SI=DMA Descriptor Return: CY=1 - error AL=error code CY=0 - ok Notes: DX is used as flag register to control the operation. Bit Meaning 0 reserved (cleared) 1 copy data to buffer (ignored if bit 2 set) 2 don't allocate buffer if region not contigous or exceeds physical boundaries (bit 4,5) 3 don't try to automatically remap 4 region must not exceed 64kb 5 region must not exceed 128kb 6-15 reserved (cleared) Region Size Field in DDS contains size of maximal contigous memory area. If Carry Flag is clear, area is locked and may not be swapped. Physical Address and Buffer ID are filled by the function. If Buffer ID is 0, no buffer has been allocated. Func: VDS Unlock DMA Region (AX=8104h) Call: INT 4Bh Input: AX=8104h DX=flags ES:DI=DMA Descriptor Return: CY=1 - error AL=error code CY=0 - ok Notes: Flag bits in DX: Bit Meaning 0 reserved (cleared) 1 Copy data from buffer 2-15 reserved (cleared) Region Size, Physical Address and Buffer ID in DDS have to be filled. Func: VDS Scatter/Gather Lock Region (AX=8105h) Call: INT 4Bh Input: AX=8105h BX=page offset (not sure about it) DX=flags ES:DI=DMA Descriptor Return: CY=1 - error AL=error code CY=0 - ok Notes: Function is used to lock parts of memory. Useful if parts of memory are swapped out. Flag bits in DX: Bit Meaning 0-5 reserved (cleared) 6 return EDDS with page table entries 7 only lock existing pages, fill not existing pages with 0 8-15 reserved (cleared) Region Size, Linear Segment, Linear Offset and Number Available have to be set. Region Size Field in EDDS will be filled with size of largest contigous memory block. Number Used will be filled with the number of used pages. If bit 6 in DX is set, lower 12 bits of BX should contain offset of first page (not sure about that). Func: VDS Scatter/Gather Unlock Region (AX=8106h) Call: INT 4Bh Input: AX=8106h DX=Flags ES:DI=DMA Descriptor Return: CY=1 - error AL=error code CY=0 - ok Notes: Flag bits in DX: Bit Meaning 0-5 reserved (cleared) 6 EDDS contains page table entries 7 EDDS may contain not present pages 8-15 reserved (cleared) ES:DI contains EDDS initialised by function 8105h. Func: VDS Request DMA Buffer (AX=8107h) Call: INT 4Bh Input: AX=8107h DX=Flags ES:DI=DMA Descriptor Return: CY=1 - error AL=error code CY=0 - ok Notes: Flag bits in DX: Bit Meaning 0 reserved (cleared) 1 Copy data to buffer 2-15 reserved (cleared) ES:DI contains pointer to DDS. Region Size has to be filled. If bit 1 in DX is set, Region Offset and Region Segment have to be filled, too. Function returns Physical Address, Buffer ID and Region Size. Func: VDS Release DMA Buffer (AX=8108h) Call: INT 4Bh Input: AX=8108h DX=flags ES:DI=DMA Descriptor Return: CY=1 - error AL=error code CY=0 - ok Notes: Flag bits in DX: Bit Meaning 0 reserved (cleared) 1 copy data from buffer 2-15 reserved (cleared) Buffer ID in DDS has to filled. If bit 1 in DX is set, Region Size, Region Offset and Region Segment have to be initialised, too. Func: VDS Copy into DMA Buffer (AX=8109h) Call: INT 4Bh Input: AX=8109h DX=0 ES:DI=DMA Descriptor BX:CX=offset Return: CY=1 - error AL=error code CY=0 - ok Notes: BX:CX contains offset into DMA Buffer. ES:DI contains pointer to DDS. Buffer ID, Region Offset, Region Segment and Region Size have to be initialised. Func: VDS Copy out of DMA Buffer (AX=810ah) Call: INT 4Bh Input: AX=810ah DX=0 ES:DI=DMA Descriptor BX:CX=offset Return: CY=1 - error AL=error code CY=0 - ok Notes: BX:CX contains offset into DMA Buffer. ES:DI contains pointer to DDS. Buffer ID, Region Offset, Region Segment and Region Size have to be initialised. Func: VDS Disable DMA Translation (AX=810bh) Call: INT 4Bh Input: AX=810bh DX=0 BX=DMA channel Return: CY=1 - error AL=error code CY=0 - ok Notes: Function stops DMA transfer on channel BX. Func: VDS Enable DMA Translation (AX=810ch) Call: INT 4Bh Input: AX=810ch DX=0 BX=DMA channel Return: CY=1 - error AL=error code CY=0 - ok Notes: Function starts DMA transfer on channel BX. ------------------------------------------------------------------------------- -- THE VIRTUAL CONTROL PROGRAM INTERFACE (VCPI) -- The Virtual Control Program Interface (VCPI) was the first standard to manage memory in a Protected Mode or Virtual 86 Mode environment. In has been founded in 1987 by many different companies. (PharLap, Quarterdeck, Qualitas, LOTUS, Autodesk and others) The communication between the interface and the application is divided into a Server and a Client. The program that provides the interface services is recognized as the Server. The application will be the Client. To call the Server, there are two ways: in Real Mode, you have to use INT 67h, in Protected Mode, you have to use a FAR CALL. Everytime I speak of page addresses, I mean the common format to address a page (bits 31-22=page directory, bits 21-12=directory entry, bits 11-0=offset). The offset part of the page address is normally always set to 0. [Page Directory I am not sure about the information contained here. Please mail me if there are errors. I hoped it would be handy for you, so it is included. Func: VCPI Installation Check (INT 67h, AX=DE00h) Call: INT 67h Input: AX=DE00h Return: AH=0 - VCPI is available BH=major version number BL=minor version number AH!=0 - VCPI not available Notes: Some docs say that AH=84h on return if VCPI isn't available, but EMM is enabled. Func: VCPI Get Protected Mode Interface (INT 67h, AX=DE01h) Call: INT 67h Input: AX=DE01h DS:SI=pointer to descriptors ES:DI=pointer to client pages Return: AH=0 - ok DI=pointer into page directory EBX=offset of entry point AH!=0 - error Notes: To call the Server in Protected Mode, you have to use the returned address. The memory at DS:SI has to have enough space for three GDT Descriptors, the first descriptor will be filled with the VCPI code segment. Use a FAR CALL into this segment, offset EBX, to reach the Server dispatcher. The space has to be in the first page in the applications code segment. ES:DI has to contain a pointer to a list of pages used by the Client. In DI, a pointer to the first unused page is returned. Func: VCPI Get Maximum Physical Memory (INT 67h, AX=DE02h) Call: INT 67h Input: AX=DE02h Return: AH=0 - ok EDX=page address AH!= - error Notes: EDX contains the address of the highest 4kb page in memory. The lowest 12 bits are set to zero. Some Clients are using this call to initialize their data structures. Func: VCPI Get Number of Free 4K Pages (INT 67h / CALL FAR, AX=DE03h) Call: INT 67h / CALL FAR Input: AX=DE03h Return: AH=0 - ok EDX=number of free pages AH!=0 - error Notes: The call returns the number of free pages that are available for all tasks. This function is available in Protected Mode, too. (CALL FAR...) Func: VCPI Allocate a 4K Page (INT 67h / CALL FAR, AX=DE04h) Call: INT 67h / CALL FAR Input: AX=DE04h Return: AH=0 - ok EDX=page address AH!=0 - error Notes: The function allocates a 4K page for the Client. The lowest 12 bits of EDX are set to 0. This function is available in Protected Mode, too. Func: VCPI Free a 4K Page (INT 67h / CALL FAR, AX=DE05h) Call: INT 67h / CALL FAR Input: AX=DE05h EDX=page address Return: AH=0 - ok AH!=0 - error Notes: The page has to be allocated by function DE04h. The lowest 12 bits of EDX are set to 0. This function is available in Protected Mode, too. Func: VCPI Get Physical Address of Page in First MB (INT 67h, AX=DE06h) Call: INT 67h Input: AX=DE06h CX=page number Return: AH=0 - ok EDX=page address AH!=0 Notes: The function returns the address of a page in the first MB. The lowest 12 bits of EDX are set to zero. The page number in CX is the address of the page SHL 12. (This is written in my VCPI docs, but quite illogical, because then there are only the 4 highest bits available for the page number) Func: VCPI Read CR0 (INT 67, AX=DE07h) Call: INT 67h Input: AX=DE07h Return: AH=0 - ok EBX=CR0 AH!=0 - error Notes: The function returns CR0 in EBX because MOV xxx,CR0 isn't allowed in V86 Mode. However, EMM386 and QEMM simulate this instruction and you don't have to use an interrupt call. Func: VCPI Read Debug Register (INT 67h, AX=DE08h) Call: INT 67h Input: AX=DE08h ES:DI=pointer to buffer Return: AH=0 - ok AH!=0 - error Notes: ES:DI has to provide enough space for 8 entries, every entry has a size of 4 bytes. The function stores DR0, DR1, ..., DR7. DR4 and DR5 are unused. Func: VCPI Set Debug Register (INT 67h, AX=DE09h) Call: INT 67h Input: AX=DE09h ES:DI=pointer to buffer Return: AH=0 - ok AH!=0 - error Notes: ES:DI has to point to a table with 8 entries, every entry has a size of 4 bytes. The function loads DR0, DR1, ..., DR7. DR4 and DR5 are unused. Func: VCPI Get 8259 Interrupt Vector Mappings (INT 67h, AX=DE0Ah) Call: INT 67h Input: AX=DE0Ah Return: AH=0 - ok BX=1st PIC Vector Map CX=2nd PIC Vector Map AH!=0 - error Notes: The Server returns the mapping from the Master PIC in BX (start of first 8 hardware IRQs) and the mapping from the Slave PIC in CX (start of next 8 hardware IRQs). If there's no Slave PIC installed, CX is undefined. Func: VCPI Set 8259 Interrupt Mappings (INT 67h, AX=DE0Bh) Call: INT 67h Input: AX=DE0Bh BX=1st PIC Vector Map CX=2nd PIC Vector Map Return: AH=0 - ok AH!=0 - error Notes: Master PIC is programmed with BX, Slave PIC is programmed with CX. Interrupts have to be disabled before calling this function. Func: VCPI Switch to Protected Mode (INT 67h, AX=DE0Ch) Call: INT 67h Input: AX=DE0Ch ESI=pointer to data structure Return: AH=0 - ok AH!=0 - error Notes: The data structure has to be setup by the Client in the first MB. ESI has to contain the linear address of it. Structure as follows: Offset Bytes Meaning 00h 4 new value for CR3 04h 4 linear address in first MB of value for GDT register (6 bytes) 08h 4 linear address in first MB of value for IDT register (6 bytes) 0Ch 2 selector for LDT register 0Eh 2 selector for Task Register 10h 4 EIP of Protected Mode entry point 14h 2 CS of Protected Mode entry point The function loads GDTR, IDTR, LDTR and TR. SS:ESP has to point to a stack with at least 16 bytes available on entry. EAX, ESI, DS, ES, FS and GS are destroyed. The CPU continues execution in Protected Mode at address CS:EIP specified in the table. Interrupts are disabled on return. Func: Switch from Protected Mode to V86 Mode (CALL FAR, AX=DE0Ch) Call: CALL FAR Input: AX=DE0Ch DS=segment selector Return: --- Notes: The stack has to be shifted in the first MB on entry. DS has to contain a selector for a segment that includes the address area returned by function DE01h. The function switches to V86 mode. Interrupt have to be disabled. GDTR, IDTR, LDTR and TR are initialised by the Server. SS:ESP has to contain the following structure: Offset Meaning -28h GS -24h FS -20h DS -1Ch ES -18h SS -14h ESP -10h reserved -0Ch CS -08h EIP 00h return address EAX is destroyed. ------------------------------------------------------------------------------- -- THE DOS PROTECTED MODE INTERFACE (DPMI) -- After the successful VCPI standard, a new standard was founded: DPMI. With the DPMI, some "bugs" of VCPI were removed, for example VCPI allowed to run a task on CPL=0. DPMI has been published in 1990 by Microsoft and Intel with version 0.9, in 1991 version 1.0 has been published. All DPMI functions are reentrant. Many implementations use a VMM, so be sure to lock your memory if you don't want it to be swapped to disk. Every DPMI task uses four stacks: - Protected Mode Application Stack (CPL=0) - Locked Protected Mode Stack - Real Mode Stack - DPMI Host Stack (CPL=0). The Protected Mode Stack is used by the Client when switching from Real- to Protected Mode. The Locked Protected Mode Stack is used by the DPMI Server to simulate hardware IRQs. The Real Mode Stack is used for Real Mode IRQs and the DPMI Host Stack is used by (who else could it have been... ;) ) the DPMI Host. Unlike VCPI, all Protected Mode function can be reached via INT 31h. I didn't include the interface here, because it is nearly 70 (140 on screen) pages "small". I'd really like to know if someone knows a source, but if there's enough demand, I'll include the whole interface here. ------------------------------------------------------------------------------- -- OTHER TEXTS -- This document has been created to be part of the comp.lang.asm.x86 FAQ, section 13 (Real Mode/Protected Mode). Jerzy Tarasiuk (the author of the directly in the FAQ included text) and I cooperate on writing and extending the FAQ contribution. (Again, please mail us about something you want to have included, I LOVE RECEIVING MAILS! ;) ) His texts are on zfja-gate.fuw.edu.pl:cpu/protect.mod/* . There is also a listserver, to get the files mail to listserv@zfja-gate.fuw.edu.pl with the body of the letter containing GET/CPU/PROTECT.MOD/*.ZIP GET/CPU/PROTECT.MOD/*.TXT to get all of his files (about 200k). Subjects are switching from Real Mode to Protected Mode, V86 Mode, basic multitasking and using Protected Mode with Turbo Pascal (NOT the Turbo Pascal DPMI stuff!!). Jerzy's internet address is: JT@zjfa-gate.fuw.edu.pl Another really good choice is Tran's Protected Mode package. I don't know which ftp server has this file available, but it should be on x2ftp.oulu.fi (teeri.oulu.fi). However, if you have Fidonet access, you can request it at 2:2426/2030 (file PMC100.ZIP). This file contains a complete DOS Extender for BC++ 4.0 (can be used with Watcom, too), including all sources. For those German speaking people out there, I suggest reading the following book: Author: Dr. Wolfgang Matthes Title: Intel's i486 Architektur und Befehlsbeschreibung der xxx86er-Familie Published by: Elektor ISBN: 3-928051-29-6

Оставьте свой комментарий !

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

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