Я уже давно не работал в защищенном режиме (то есть, давно не писал програми, которые переходят в этот режим). Сейчас опять понадобилось войти в него (1), сделать кое-что и выйти обратно в реальный (2). С (1) все получается, а вот когда дело доходит до (2), то компьютер перезагружается, виртуальные машины виснут (ну а Microsoft VirtualPC выдает: "An unrecoverable processor error has been encountered. The virtual machine will reset now). По порядку. Программа записана на дискете, грузится загрузочным сектором по адресу 1000:0000 и управление передается по этому адресу. После загрузки сразу происходит переключение в PM, при этом GDT такая: Код (Text): align 16 GDT: dq 0x0000000000000000 ;; 0x00: NULL dq 0x00009A001000FFFF ;; 0x08: CODE 16-bit, base: 0x1000, limit: 0xFFFF dq 0x000092001000FFFF ;; 0x10: DATA 16-bit, base: 0x1000, limit: 0xFFFF dq 0x00CF9A000000FFFF ;; 0x18: CODE 32-bit, base: 0x00000000, limit: 0xFFFFFFFF dq 0x00CF92000000FFFF ;; 0x20: DATA 32-bit, base: 0x00000000, limit: 0xFFFFFFFF GDT_end: align 16 gdtr_image: dw GDT_END - GDT - 1 dd GDT align 16 idtr_image_rm: dw 0 dd 0 Код программы написан на fasm и начинается с такой директивы: org BOOT_BASE. (это чтобы постоянно не корректировать смещения, когда мы защищенном режиме). BOOT_BASE equ 0x10000 Вся программа в одном .asm файле. Бинарник меньше 64 килобайт. Вот код перехода в защищенный режим: Код (Text): PROC_CODE16_SELECTOR equ 0x08 PROC_DATA16_SELECTOR equ 0x10 PROC_CODE_SELECTOR equ 0x18 PROC_DATA_SELECTOR equ 0x20 USE16 ;; Загрузить GDTR. lgdt [gdtr_image - BOOT_BASE] ;; Сохранить IDTR (для перехода в RM). sidt [idtr_image_rm - BOOT_BASE] ;; Заблокировать прерывания. cli ;; Включить шлюз A20 in al, 0x92 or al, 0x02 out 0x92, al ;; Перейти в PM. mov eax, cr0 mov al, 1 mov cr0, eax db 0x66 db 0xEA dd entry32 dw PROC_CODE_SELECTOR USE32: entry32: В entry32 начинается полезная часть программы. Кстати, там же настраивается IDT и разрешаются прерывания. После её завершения происходит возврат в RM (в реальном режиме тоже кое-что делается, поэтому мне важно перейти в него): Код (Text): ;; Заблокировать прерывания. cli ;; Возврат в RM. db 0x66 db 0xEA dw return_to_rm - BOOT_BASE dw PROC_CODE16_SELECTOR USE16 return_to_rm: mov ax, PROC_DATA16_SELECTOR mov ds, ax mov es, ax mov fs, ax mov gs, ax mov ss, ax lidt [idtr_image_rm - BOOT_BASE] mov eax, cr0 and al, 11111110b mov cr0, eax dw 0xEA dw entry16 - BOOT_BASE dw 0x1000 entry16: mov ax, cs mov ds, ax mov ss, ax mov sp, 0xFFFF mov ax, 0xB800 mov es, ax mov di, 0x0000 mov al, 'X' mov ah, 3 mov word[es:di], ax jmp $ - BOOT_BASE Символ 'X' не выводится, комп перезагружется.
О, заметил одну ошибку: Код (Text): ;; Возврат в RM. db 0x66 ;; нужно или нет? (впрочем, даже если закоменнтировать, ничего не работает.) db 0xEA dw return_to_rm - BOOT_BASE dw PROC_CODE16_SELECTOR
Если перед кодом указана директива org 0x10000, то в 16-разрядных дескрипторах нужно использовать соотв. базу (0x10000). Значения 0x1000 должны загружаться в сегментные регистры после переключения в реальный режим. Кроме того, fasm позволяет избежать необходимости корректировать адреса. Для этого нужно объявить несколько адресных пространств. Код (Text): org 0 ; используется по умолчанию - начало сегмента по адресу 0x10000 ... jmp fword PROC_CODE_SELECTOR:entry32 org $+0x10000 ; внутрисегментное смещение 32-разрядного кода относительно нулевой базы с учетом местоположения 16-разрядного кода use32 entry32: ... Нужно, т.к. ты кодируешь 16-разрядную инструкцию в 32-разрядном коде (если бы смещение в коде инструкции было 32-разрядное, тогда бы префикс указывать не нужно было). В fasm'е можно поступить проще. Код (Text): jmp dword PROC_CODE16_SELECTOR:return_to_rm
Код (Text): mov sp, 0xFFFF Лучше, чтобы sp имел четное значение. Хочешь чтобы стек располагался в конце сегментра, обнули sp. Можно стек расположить и перед твоим сегментом, т.к. судя по всему эта память не используется. Код (Text): xor ax,ax mov ss,ax xor sp,sp
Phantom_84 Спасибо, помог . Кроме того, была еще ошибка (опечатка), без которой не получилось бы перепрыгнуть в 16-разрядный режим: