OS 禁用寻呼后跳远重置
OS resets on far jump after disabling paging
我正在修改一个 routine 切换到实模式以执行 BIOS 中断,但 运行 遇到分页问题。我之前在没有分页的情况下工作,但现在我的 OS 使用分页,我需要在进入实模式之前禁用它(并在之后启用它)。
我的问题是,当执行远跳使页面禁用生效时,出现了严重错误,我重新启动了。
下面显示的代码首先使用页面 table boot_page_table1
创建一个标识映射,它只是一个标识映射前 4 MiB 的页面 table。这是必须完成的,因为我当前使用分页从更高内存到 运行 我的内核代码,并且所有内核代码都从 0xC0100000
开始寻址,同时从 0x00100000
开始加载。然后我刷新 TLB 并跳转到附近的标签,但这次使用较低内存中的地址。我的指令指针现在应该指向恒等映射代码,禁用分页应该是安全的。然后在 cr3
中禁用分页位,TLB 再次刷新,因为我是偏执狂,切换模式的代码继续。
代码的工作原理是将自身复制到 0x7c00 处的 16 位内存,然后跳转到该处,以便它可以在 16 位实模式下工作。
如果我不禁用分页位并保持其他一切不变,jmpw CODE16:REBASE(p_mode16)
工作并且进入跳转后的无限循环让我认为这个问题是由于我禁用分页的方式造成的. 我在禁用分页时是否遗漏了什么? 我在其他 posts 上看到 "because what you're doing is very unusual you may run into bugs and compatibility problems with your emulator",但我不确定是否只是我的代码有问题。
代码是使用英特尔语法和 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
从未到达 p_mode16
标签处带有 jmp .-2
的行,而是重新启动。如果 jmp .-2
放在 jmpw
之前,那么 OS 会按预期进入无限循环。我在 QEMU 2.11.1 版本 运行 上使用 qemu-system-i386
.
进行此操作
问题是这样的:
gdt16_ptr: # GDT table pointer for 16bit access
.2byte gdt16_ptr - gdt16_base - 1 # table limit (size)
.4byte gdt16_base # GDT base in higher half that WILL NOT WORK WHEN PAGING IS DISABLED
因为你告诉 CPU GDT 在上半部分,禁用分页后它不能正确访问 GDT 条目(它可能访问 0xC000 处的物理地址????并读取谁知道呢 - 例如,可能是 PCI 设备的寄存器,可能是 "not RAM or device",等等),所以当远跳尝试将 CODE16
加载到 CS 时它会崩溃(因为 "who-knows-what" 不是' t 一个有效的代码描述符)。
要解决此问题,您需要在执行 sgdt [gdt_ptr]
之前修改 GDT 基数的值(例如,如果 gdt16_ptr
没有在别处使用)。
我正在修改一个 routine 切换到实模式以执行 BIOS 中断,但 运行 遇到分页问题。我之前在没有分页的情况下工作,但现在我的 OS 使用分页,我需要在进入实模式之前禁用它(并在之后启用它)。
我的问题是,当执行远跳使页面禁用生效时,出现了严重错误,我重新启动了。
下面显示的代码首先使用页面 table boot_page_table1
创建一个标识映射,它只是一个标识映射前 4 MiB 的页面 table。这是必须完成的,因为我当前使用分页从更高内存到 运行 我的内核代码,并且所有内核代码都从 0xC0100000
开始寻址,同时从 0x00100000
开始加载。然后我刷新 TLB 并跳转到附近的标签,但这次使用较低内存中的地址。我的指令指针现在应该指向恒等映射代码,禁用分页应该是安全的。然后在 cr3
中禁用分页位,TLB 再次刷新,因为我是偏执狂,切换模式的代码继续。
代码的工作原理是将自身复制到 0x7c00 处的 16 位内存,然后跳转到该处,以便它可以在 16 位实模式下工作。
如果我不禁用分页位并保持其他一切不变,jmpw CODE16:REBASE(p_mode16)
工作并且进入跳转后的无限循环让我认为这个问题是由于我禁用分页的方式造成的. 我在禁用分页时是否遗漏了什么? 我在其他 posts 上看到 "because what you're doing is very unusual you may run into bugs and compatibility problems with your emulator",但我不确定是否只是我的代码有问题。
代码是使用英特尔语法和 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
从未到达 p_mode16
标签处带有 jmp .-2
的行,而是重新启动。如果 jmp .-2
放在 jmpw
之前,那么 OS 会按预期进入无限循环。我在 QEMU 2.11.1 版本 运行 上使用 qemu-system-i386
.
问题是这样的:
gdt16_ptr: # GDT table pointer for 16bit access
.2byte gdt16_ptr - gdt16_base - 1 # table limit (size)
.4byte gdt16_base # GDT base in higher half that WILL NOT WORK WHEN PAGING IS DISABLED
因为你告诉 CPU GDT 在上半部分,禁用分页后它不能正确访问 GDT 条目(它可能访问 0xC000 处的物理地址????并读取谁知道呢 - 例如,可能是 PCI 设备的寄存器,可能是 "not RAM or device",等等),所以当远跳尝试将 CODE16
加载到 CS 时它会崩溃(因为 "who-knows-what" 不是' t 一个有效的代码描述符)。
要解决此问题,您需要在执行 sgdt [gdt_ptr]
之前修改 GDT 基数的值(例如,如果 gdt16_ptr
没有在别处使用)。