Простой загрузчик для входа в защищенный режим

Я пытаюсь написать простой загрузчик для архитектуры x86, который должен просто выводить символ «A», входить в защищенный режим, а затем останавливаться. Мой код с комментариями выглядит следующим образом:

BITS 16
ORG 0x7c00

jmp 0:start ;set cs to 0

start:
mov ax,0x7c0
add ax,288
mov ss,ax
mov sp,4096

mov ax,0x7c0
mov ds,ax ;Sets segment descriptors for now

mov ah,0eh
mov al,65
int 10h ;Print A for test

jmp pretect ;Jump to a ~1 second delay before entering protected mode so we can see the 'A' if anything goes wrong

nod:
jmp nod ;Not used for now

gdtstp: ;global descriptor table
dq 0 ;Null
dw 0xffff ;Entry 08h, full 4gb
dw 0
db 0
db 0x9a
db 11001111b
db 0
dw 0xffff ;Entry 16h, full 4gb
dw 0
db 0
db 0x92
db 11001111b
db 0

gdtr: ;descriptor for gdt
dw 24
dd gdtstp

pretect: ;Wait for about 1 second before jumping to protect
mov esi,0x20000000
.loop:
dec esi
test esi, esi
jz protect
jmp .loop

protect: ;attempt to enter protected mode
cli
lgdt [gdtr] ;set gdt register
mov eax,cr0
or al,1
mov cr0,eax ;set bit 1 of cr0

jmp 08h:idle ;sets cs to 08h and jumps to idle

idle:
jmp idle ;Should stop here

times 510-($-$$) db 0
dw 0xaa55 ;magic number

Это в NASM и выполняется на qemu. У меня есть небрежный способ добавить задержку примерно в 1 секунду между выводом «A» и попыткой входа в защищенный режим. В настоящее время, когда я пытаюсь запустить этот код, он печатает букву «A», задерживается примерно на секунду, а затем перезагружается. Я не могу сказать, почему это так, но я предполагаю, что это вероятно потому, что глобальная таблица дескрипторов недействительна или неправильно загружена, или потому что дальний переход для установки селектора сегмента кода неверен.

Мой код должен делать следующее: напечатать 'A', иметь GDT из 3 записей: нулевой дескриптор, сегмент кода всех 4 ГБ и сегмент данных всех 4 ГБ, иметь GDTR, который определяет 24 байта, и адрес GDT, подождите 1 секунду, отключите прерывания, загрузите GDT, включите защищенный режим, установите селектор сегмента кода с большим переходом в состояние ожидания, а затем оставайтесь там на неопределенное время.

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

РЕДАКТИРОВАТЬ: после изменения

mov ax,0x7c0
mov ds,ax

to

mov ax,0
mov ds,ax

и помещая mov ax,08h между idle: и jmp idle, qemu вылетает и дает следующее:

qemu: fatal: Trying to execute code outside RAM or ROM at 0x000a0000
EAX=feeb0010 EBX=00000000 ECX=00000000 EDX=ffffffff
ESI=00000000 EDI=8000007c EBP=00000000 ESP=00000ffc
EIP=0009fc6d EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0000 00000000 0000ffff 00009300 DPL=0 DS16 [-WA]
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =08e0 00008e00 0000ffff 00009300 DPL=0 DS16 [-WA]
DS =0000 00000000 0000ffff 00009300 DPL=0 DS16 [-WA]
FS =0000 00000000 0000ffff 00009300 DPL=0 DS16 [-WA]
GS =0000 00000000 0000ffff 00009300 DPL=0 DS16 [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     00007c1f 00000018
IDT=     00000000 000003ff
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000
DR6=ffff0ff0 DR7=00000400
CCS=feeb0010 CCD=feeb0010 CCO=ADDB
EFER=0000000000000000
FCW=037f FSW=0000 [ST=0] FTW=00 MXCSR=00001f80
FPR0=0000000000000000 0000 FPR1=0000000000000000 0000
FPR2=0000000000000000 0000 FPR3=0000000000000000 0000
FPR4=0000000000000000 0000 FPR5=0000000000000000 0000
FPR6=0000000000000000 0000 FPR7=0000000000000000 0000
XMM00=00000000000000000000000000000000 XMM01=00000000000000000000000000000000
XMM02=00000000000000000000000000000000 XMM03=00000000000000000000000000000000
XMM04=00000000000000000000000000000000 XMM05=00000000000000000000000000000000
XMM06=00000000000000000000000000000000 XMM07=00000000000000000000000000000000

person user2649681    schedule 03.10.2018    source источник
comment
Используйте bochs со встроенным отладчиком или изучите вывод qemu на случай, если он сообщил вам, в чем было исключение.   -  person Jester    schedule 04.10.2018
comment
Я предполагаю, что дальнейший прыжок после входа в защищенный режим неправильный. Можете ли вы разобрать его или получить листинг сборки, чтобы увидеть, что на самом деле создается? Я думаю, это должно быть что-то вроде ea 48 7c 08 00. (7c48 - это адрес простоя, о котором я догадался.)   -  person prl    schedule 04.10.2018
comment
Коды операций для этого перехода выглядят ea 5f 7c 08 00. Инструкция jmp idle имеет коды операций eb fe и находится в позиции 0x5f в файле, что, как мне кажется, означает, что она загружена в позицию 0x7c5f (куда направляется дальний переход) в основной памяти.   -  person user2649681    schedule 04.10.2018
comment
Я не могу воспроизвести предложенную вами ошибку, изменив 0x7c0 на 0x0 (при загрузке DS) и поставив mov ax, 08h перед ожиданием jmp. Но вам следует загрузить все сегментные регистры (ES, DS, SS) с помощью селектора 010h (вы только установили для CS значение 08h). Вы также можете установить GS и FS на 010h, если хотите. EIP=0009fc6d в дампе говорит о том, что вы где-то заблудились в памяти.   -  person Michael Petch    schedule 04.10.2018
comment
Установка сегментов на 010h, как я предлагал, должна выполняться после jmp 08h:idle. После установки SS вы также должны установить ESP   -  person Michael Petch    schedule 04.10.2018
comment
Он не дает сбоев, но есть ли причина, по которой вызов int 10h с ah = 0Eh однократным входом в защищенный режим приведет к его перезагрузке? Я пытаюсь подтвердить, что он достиг этой части кода, поэтому я установил эти селекторы и добавил этот вызов прерывания, но он просто перезагружается   -  person user2649681    schedule 04.10.2018
comment
Потому что после входа в защищенный режим подпрограммы BIOS реального режима использовать нельзя. Вам нужно будет подумать о записи текста непосредственно в видеопамять (есть порты, в которые вы можете писать, что меняет положение курсора).   -  person Michael Petch    schedule 04.10.2018
comment
Знаете ли вы, почему, учитывая этот код, вы записываете значение в 0xb8000 до того, как jmp 08h:idle печатает символ, но делать это после того, как jmp не удается? Или даже каким-либо образом я могу проверить, действительно ли код заканчивается там, где он должен быть после jmp?   -  person user2649681    schedule 04.10.2018
comment
Что ж, я надеюсь, вы не помещаете код между меткой jmp 08h:idle и idle:, потому что эти инструкции никогда не будут выполнены, потому что вы перепрыгнули через них. Сделайте что-нибудь вроде jmp 08h:setcs setcs:, затем поместите код для инициализации сегментов и после этого выполните цикл ожидания.   -  person Michael Petch    schedule 04.10.2018
comment
Извините за нечеткую формулировку. Вот что я делал. jmp 08h:idle затем после idle: инициализации селекторов сегментов, затем запись в 0xb8000 после этого.   -  person user2649681    schedule 04.10.2018
comment
Какую инструкцию (и) вы используете для записи на b8000   -  person Michael Petch    schedule 04.10.2018
comment
mov ebx,0xb8000 \ mov ax,0x0748 ;value of character \ mov [ebx],ax Эти три инструкции, как я полагаю, должны записывать 0x0748 в 0xb8000 в памяти с помощью GDT, описанного в OP   -  person user2649681    schedule 04.10.2018
comment
После вашего JMP 08h:idle вам нужно будет убедиться, что у вас есть строка bits 32, чтобы с этого момента ассемблер выдавал 32-битные инструкции.   -  person Michael Petch    schedule 04.10.2018


Ответы (1)


Вам нужно загрузить ds с 0, а не с 7c0h.

В lgdt смещение в инструкции отсчитывается от 0. (Это единственная инструкция, в которой используется ds.)

person prl    schedule 03.10.2018
comment
В качестве альтернативы вы можете написать lgdt [gdtr-0x7c00] - person prl; 04.10.2018
comment
Означает ли это, что инструкция lgdt XXX устанавливает загрузку gdtr с ds: XXX? - person user2649681; 04.10.2018
comment
Да, каждая ссылка на память в инструкции использует сегментный регистр в сочетании со смещением, указанным в инструкции. Однако адрес gdtstp, загруженного из gdtr, используется непосредственно как линейный адрес без сегментного регистра. - person prl; 04.10.2018
comment
После внесения этого изменения и установки mov ax,08h после idle: qemu вылетает с ошибкой, которую я добавлю к вопросу. - person user2649681; 04.10.2018