Я работаю над изменением подпрограмма, которая переключается в реальный режим и из него для выполнения прерывания BIOS, но при этом возникают проблемы с разбиением на страницы. Раньше он работал без подкачки, но теперь, когда моя ОС использует подкачку, мне нужно отключить его перед входом в реальный режим (и включить его после).
Моя проблема в том, что при выполнении дальнего перехода, чтобы отключить страницу, что-то идет не так, и я получаю перезагрузку.
Код, показанный ниже, работает путем создания сопоставления идентификаторов сначала с использованием таблицы страниц boot_page_table1
, которая представляет собой просто таблицу страниц, идентификатор которой отображает первые 4 МиБ. Это необходимо сделать, поскольку я использую подкачку для запуска моего кода ядра из более высокой памяти, и весь код ядра адресуется, начиная с 0xC0100000
, а загружается, начиная с 0x00100000
. Затем я сбрасываю TLB и перехожу к ближайшей метке, но на этот раз использую адрес в нижней памяти. Теперь указатель моей инструкции должен указывать на код сопоставления идентификаторов, и отключение разбиения на страницы должно быть безопасным. Затем бит пейджинга отключается в cr3
, TLB снова сбрасывается, потому что я параноик, и код для переключения режимов продолжается.
Код работает, копируя себя в 16-битную память по адресу 0x7c00, а затем перескакивая на нее, чтобы он мог работать в 16-битном реальном режиме.
Если я НЕ отключу бит подкачки и оставлю все остальное без изменений, jmpw CODE16:REBASE(p_mode16)
будет работать, и бесконечный цикл после перехода введен, заставляя меня думать, что эта проблема возникает из-за того, как я отключил подкачку. Я что-то упускаю при отключении разбиения по страницам? Я видел в других сообщениях что «из-за того, что вы делаете очень необычно, вы можете столкнуться с ошибками и проблемами совместимости с вашим эмулятором», но я еще не уверен, что это просто мой код неправильный.
Код написан с использованием синтаксиса Intel с ассемблером GAS.
.intel_syntax noprefix
.code32
.global int32, _int32
#define regs16_t_size 13*2
#define INT32_BASE 0x00007C00
#define REBASE(x) (((x) - reloc) + INT32_BASE)
#define GDTENTRY(x) ((x) << 3)
#define CODE32 0x08
#define DATA32 0x10
#define CODE16 0x18
#define DATA16 0x20
#define STACK16 (INT32_BASE - regs16_t_size)
.global reloc
.global int32_end
.section .text
int32: .code32 # by Napalm
_int32:
cli # disable interrupts
pusha # save register state to 32bit stack
# Enable identity mapping the first MiB, jump, then disable paging
push [boot_page_directory] # Push first page directory entry to restore it after
mov eax, (offset boot_page_table1) - 0xC0000000 + 0x003
mov [boot_page_directory], eax
mov ecx, cr3 # Reload crc3 to force a TLB flush so the changes to take effect.
mov cr3, ecx
mov eax, (offset napalm_switch_disable_paging) - 0xC0000000
jmp eax
napalm_switch_disable_paging:
# Code is now running with the instruction pointer in lower memory,
# but the code is still assembled as though its in higher memory. Because
# of this, something like jmp INT32_BASE would fail since it would
# assemble as a relative jump from an address around 0xC0100000 to 0x7C00
# but will be running at an address around 0x00100000 causing it to jump to
# 0x40007C00.
# Disable paging bit
mov eax, cr0
and eax, ~0x80000000
mov cr0, eax
mov ecx, cr3 # Reload crc3 to force a TLB flush so the changes to take effect.
mov cr3, ecx
mov esi, (offset reloc) - 0xC0000000 # set source to code below
mov edi, INT32_BASE # set destination to new base address
mov ecx, int32_end - reloc # set copy size to our codes size
cld # clear direction flag (so we copy forward)
rep movsb # do the actual copy (relocate code to low 16bit space)
mov eax, INT32_BASE
jmp eax # jump to new code location
reloc: .code32 # by Napalm
mov [REBASE(stack32_ptr)], esp # save 32bit stack pointer
sidt [idt_ptr] # save 32bit idt pointer
sgdt [gdt_ptr] # save 32bit gdt pointer
lgdt [REBASE(gdt16_ptr)] # load 16bit gdt pointer
lea esi, [esp+0x24] # set position of intnum on 32bit stack
lodsd # read intnum into eax
mov [REBASE(ib)], al # set intrrupt immediate byte from our arguments
mov esi, [esi] # read regs pointer in esi as source
mov edi, STACK16 # set destination to 16bit stack
mov ecx, regs16_t_size # set copy size to our struct size
mov esp, edi # save destination to as 16bit stack offset
rep movsb # do the actual copy (32bit stack to 16bit stack)
jmpw CODE16:REBASE(p_mode16) # switch to 16bit selector (16bit protected mode)
p_mode16: .code16
jmp .-2
...
more of the routine thats not run due to the bug
...
stack32_ptr: # address in 32bit stack after we
.4byte 0x00000000 # save all general purpose registers
idt16_ptr: # IDT table pointer for 16bit access
.2byte 0x03FF # table limit (size)
.4byte 0x00000000 # table base address
gdt16_base: # GDT descriptor table
.null: # 0x00 - null segment descriptor
.4byte 0x00000000 # must be left zero'd
.4byte 0x00000000 # must be left zero'd
.code32: # 0x01 - 32bit code segment descriptor 0xFFFFFFFF
.2byte 0xFFFF # limit 0:15
.2byte 0x0000 # base 0:15
.byte 0x00 # base 16:23
.byte 0x9A # present, iopl/0, code, execute/read
.byte 0xCF # 4Kbyte granularity, 32bit selector; limit 16:19
.byte 0x00 # base 24:31
.data32: # 0x02 - 32bit data segment descriptor 0xFFFFFFFF
.2byte 0xFFFF # limit 0:15
.2byte 0x0000 # base 0:15
.byte 0x00 # base 16:23
.byte 0x92 # present, iopl/0, data, read/write
.byte 0xCF # 4Kbyte granularity, 32bit selector; limit 16:19
.byte 0x00 # base 24:31
.code16: # 0x03 - 16bit code segment descriptor 0x000FFFFF
.2byte 0xFFFF # limit 0:15
.2byte 0x0000 # base 0:15
.byte 0x00 # base 16:23
.byte 0x9A # present, iopl/0, code, execute/read
.byte 0x0F # 1Byte granularity, 16bit selector; limit 16:19
.byte 0x00 # base 24:31
.data16: # 0x04 - 16bit data segment descriptor 0x000FFFFF
.2byte 0xFFFF # limit 0:15
.2byte 0x0000 # base 0:15
.byte 0x00 # base 16:23
.byte 0x92 # present, iopl/0, data, read/write
.byte 0x0F # 1Byte granularity, 16bit selector; limit 16:19
.byte 0x00 # base 24:31
gdt16_ptr: # GDT table pointer for 16bit access
.2byte gdt16_ptr - gdt16_base - 1 # table limit (size)
.4byte gdt16_base # table base address
int32_end: # end marker (so we can copy the code)
.byte 0x00
Строка с jmp .-2
на метке p_mode16
никогда не достигается, вместо этого происходит перезагрузка. Если jmp .-2
поставить прямо перед jmpw
, тогда ОС войдет в бесконечный цикл, как и ожидалось. Я запускаю это на QEMU версии 2.11.1 с qemu-system-i386
.