Вход в защищенный режим из DOS-программы

Я думаю, что прочитал около дюжины вопросов, которые в основном дублируют этот, но я до сих пор не нашел решения.

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

вот мой код, который вызывает ошибку в DOSBox и на ПК с Pentium II под управлением MS-DOS 7. Ассемблер - MASM 5.10

single segment stack                                                      
assume cs:single,ds:single 

gdt        dq 0

c_limit_lo dw 0ffffh     
c_base_lo  dw 0
c_base_mid db 0
c_priv     db 10011110b ;present set, highest priv, type set, conforming, read      
c_limit_hi db 11001111b ;granularity set, operand size 32  
c_base_hi  db 0

d_limit_lo dw 0ffffh
d_base_lo  dw 0
d_base_mid db 0
d_priv     db 10010010b ;present set, highest priv, type clr, expand dn, write  
d_limit_hi db 11001111b ;granularity set, big set 
d_base_hi  db 0
gdt_end:

gdt_limit  dw gdt_end-offset gdt-1
gdt_addr   dd ?

start:
     mov ax, cs
     mov ds, ax
                                   ;calc phys address of current code segment and
                                   ;insert it into code and data descriptors 
     .386p                         
     xor eax, eax
     mov ax, cs                    
     mov cl, 4              
     shl eax, cl                   ;multiply cs by 16 to get phys address of seg
     mov edx, eax
     mov c_base_lo, ax
     mov d_base_lo, ax             ;low word
     mov cl, 16
     shr eax, cl
     mov c_base_mid, al
     mov d_base_mid, al            ;middle byte
     mov c_base_hi, ah
     mov d_base_hi, ah             ;high byte
     add edx, offset gdt           ;add offset of gdt 
     mov gdt_addr, edx             ;gdt address set 

                                   ;attempt to enter protected mode
     cli                           ;disable interrupts
     in al, 70h
     or al, 80h
     out 70h, al                   ;turn off nonmasked interrupts
     in al, 92h
     or al, 2
     out 92h, al                   ;enable A20 line
     lgdt [gdt_limit]
     mov eax, cr0         
     or eax, 1
     mov cr0, eax                  ;enter protected mode
     db 66h                        ;specify 32-bit operand
     jmp_op  db 0eah               ;manually encoded "jmp 8h:enter_32" TRIPLE FAULT
     jmp_loc_lo dw offset enter_32
     jmp_loc_hi dw 0
     jmp_sel dw 8
enter_32:  
     mov eax, 0ffffffffh           ;sometimes doesn't triple fault on infinite jump or hlt instruction
back:jmp back                      ;but always triple faults on mov



the_stack db 64 dup (0ffh)          ;64 byte stack

single ends                                                                  
end start       

Тройная ошибка кажется в какой-то степени «везением». Определенные конфигурации префиксов 0x67 и nops после дальнего перехода заставляют процессор вести себя так, как будто он остановлен. Я не совсем понимаю.

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

Обновление: это не ошибка с однобайтовыми инструкциями (инструкции с одной кодировкой независимо от режима процессора). Я думаю, что попробую перейти в сегмент, определенный USE32.

Этот код не является ошибкой:

     jmp_op  db 0eah               
     jmp_loc_lo dw offset enter_32
     jmp_loc_hi dw 0
     jmp_sel dw 8
enter_32:  
     aaa
     daa
     cmc
     cld
     cli
     stc
     nop
     aaa
     daa
     cmc
     cld
     cli
     stc
     nop
     hlt

person My life is a bug.    schedule 13.05.2020    source источник
comment
Обратите внимание, что вам нужно установить ассемблер в 32-битный режим при сборке 32-битного кода. Кодировка для mov eax, 0ffffffffh имеет префикс 66h в 16-битных режимах, но не в 32-битном режиме. То же самое для инструкции jmp back.   -  person fuz    schedule 13.05.2020
comment
Чтобы добавить к комментарию fuz. Вам действительно нужно создать сегменты, которые используют 16-битное кодирование для кода реального режима и 32-битное кодирование для защищенного режима. Вы можете сделать это, создав свои собственные сегменты с параметрами USE16 и USE32, чтобы изменить поведение. Мне интересно, работает ли у вас такой код: pastebin.com/Ce2BXqdd (это слегка измененная версия за час до того, как я опубликовал изменения, связанные со стеком)   -  person Michael Petch    schedule 13.05.2020
comment
Следует также отметить, что если вы попытаетесь запустить этот код, уже находясь в защищенном режиме, он потерпит неудачу. Обычно рекомендуется проверить, не находитесь ли вы уже в защищенном режиме, прежде чем пытаться перейти в него. Это может произойти, если у вас загружен EMM386, запущена задача VM8086 и т. д.   -  person Michael Petch    schedule 14.05.2020
comment
Я предполагаю, что 5.10 был как минимум 5.10a.   -  person Michael Petch    schedule 15.05.2020


Ответы (1)


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

Полная программа защищенного режима с демонстрацией VGA и переключением режима обратно в реальный режим:

single segment stack                                                      
assume cs:single,ds:single 

gdt        dq 0                             ;global descriptor table
p_code     dq 00cf9e000000ffffh             ;protected mode code descriptor
p_data     dq 00cf92000000ffffh             ;protected mode data descriptor
r_code     dq 008f9a000000ffffh             ;real mode code descriptor
r_data     dq 008f92000000ffffh             ;real mode data descriptor
v_buff     dq 00cf920a0000ffffh             ;vga buffer descriptor

gdt_limit  dw offset gdt_limit-offset gdt-1 ;gdt_limit <- gdt byte size -1
gdt_addr   dd offset gdt                    ;gdt_addr <- offset of gdt, phys address of
                                            ;code segment will be added
start:
    mov ax, cs 
    mov ds, ax                   ;ds = cs, single segment
    mov ax, 13h
    int 10h                      ;enter vga 320x200x256

    .386p                        ;enable 32-bit extensions
    xor eax, eax                 ;clear high word of eax
    mov ax, cs                   ;eax <- cs 
    shl eax, 4                   ;eax <- physical address of cs 
    add [gdt_addr], eax          ;gdt_addr <- physical address of gdt
    mov word ptr [r_code+2], ax  
    mov word ptr [r_data+2], ax  ;insert low word of cs phys address
    shr eax, 16
    mov byte ptr [r_code+4], al
    mov byte ptr [r_data+4], al  ;insert middle byte of cs address
    mov byte ptr [r_code+7], ah
    mov byte ptr [r_data+7], ah  ;insert high byte of cs address

    xor eax, eax                 ;clear high word of eax
    mov ax, seg32                ;eax <- seg32 segment address
    shl eax, 4                   ;eax <- physical address of seg32 
    mov word ptr [p_code+2], ax   
    mov word ptr [p_data+2], ax  ;insert low word of seg32 phys address
    shr eax, 16
    mov byte ptr [p_code+4], al
    mov byte ptr [p_data+4], al  ;insert middle byte of seg32 address
    mov byte ptr [p_code+7], ah
    mov byte ptr [p_data+7], ah  ;insert high byte of seg32 address

    cli                          ;disable interrupts
    in al, 70h                   ;al <- cmos ram index register port
    or al, 80h                   ;set bit 7 to disable nmi 
    out 70h, al                  ;nmi disabled
    in al, 92h                   ;al <- ps/2 system control port
    or al, 2                     ;set bit 1 to enable a20
    out 92h, al                  ;a20 enabled

    lgdt [gdt_limit]             ;load gdt 
    mov eax, cr0                  
    or eax, 1                    ;set pe bit
    mov cr0, eax                 ;enter protected mode
    db 66h                       ;specify 32-bit operand
    db 0eah                      ;manually encoded jmp 8h:0, jump to offset 0 of seg32
    dd offset enter_32                  
    dw 8 

ret_real:
    mov eax, cr0
    and al, 11111110b            ;clear pe bit
    mov cr0, eax                 ;real mode enabled     
    db 0eah                      ;jmp single:real_cs to load cs:ip
    dw offset real_cs
    dw seg single  

real_cs:
    mov ax, cs                      
    mov ds, ax                   ;ds = cs
    mov ss, ax                   ;ss = cs
    mov sp, offset s16_end       ;top of stack is end of stack
    in al, 70h                   ;al <- cmos ram index register port
    and al, 01111111b            ;clear bit 7 to enable nmi 
    out 70h, al                  ;nmi enabled
    sti                          ;enable interrupts

    mov ax, 40h
    mov es, ax                   ;access kbd data area via segment 40h
    mov word ptr es:[1ah], 1eh   ;set the kbd buff head to start of buff
    mov word ptr es:[1ch], 1eh   ;set kbd buff tail to same as buff head
                                 ;now the keyboard buffer is cleared.
    xor ah, ah                   ;select video mode function
    mov al, 3                    ;select 80x25 16 colors
    int 10h                      ;restore vga compatible text mode
    mov ax, 4c00h                ;Terminate process function selected
    int 21h                      ;return to ms-dos

s16 db 256 dup (0ffh)            ;needed 256 bytes to call int 10h on fx5200 vga bios
s16_end: 
single ends   


seg32 segment use32
assume cs:seg32,ds:seg32

enter_32:
    mov ax, 10h                  ;protected mode data segment selector
    mov ds, ax                   ;ds references main data segment
    mov ss, ax                   ;stack is in main data segment 
    mov esp, offset s32_end      ;initial top of stack is end of stack
    mov ax, 28h                  ;vga buffer selector
    mov es, ax                   ;es references vga buffer  
    mov eax, 0ffffffffh          ;initialize eax
write_scr:
    inc al
    inc ah
    rol eax, 16
    inc al
    inc ah                       ;increment each byte of eax
    xor edi, edi                 ;init index
    mov ecx, 320*200/4           ;vga buffer length in bytes

    push eax
    mov dx, 3dah                 ;dx <- vga status register
vrb_set:
    in al, dx                    ;al <- status byte
    test al, 8                   ;is bit vertical retrace bit set
    jnz vrb_set                  ;if so, wait for it to clear
vrb_clr:                         ;when clear, wait for it to be set
    in al, dx
    test al, 8
    jz vrb_clr                   ;loop back until vertical retrace bit has been set
    pop eax
    rep stosd                    ;fill vga buffer

    push eax
    in al, 60h                   ;al <- keyboard data port
    mov ebx, eax                 
    pop eax                     
    cmp bl, 1                    ;escape key scancode? 
    jne write_scr                ;if not, update screen
    mov ax, 20h                  ;real mode data selector
    mov ds, ax                   
    mov es, ax                   ;setup ds and es for real mode
    db 0eah                      ;jmp 18h:ret_real to load real mode code descriptor 
    dd offset ret_real
    dw 18h

s32 db 128 dup (0ffh)            ;128 byte stack
s32_end:
seg32 ends

end start

Сборка кода для 16-битного реального режима и запуск его в 32-битном защищенном режиме может привести к неожиданному поведению и сбоям. Этот 16-битный код:

    mov eax, 0ffffffffh
back:jmp back

Кодируется как:

66B8FFFFFFFF      mov eax,0xffffffff
EBFE              jmp short 0x6

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

66B8FFFF          mov ax,0xffff
FF                db 0xff
FF                db 0xff
EBFE              jmp short 0x6

После перемещения 0xffff в регистр AX процессор вызовет ошибку общей защиты (#GP), когда обнаружит недопустимую инструкцию (байт 0xff). При отсутствии надлежащей таблицы дескрипторов прерываний (IDT) и обработчика исключений для #GP возникает тройная ошибка.

person My life is a bug.    schedule 15.05.2020