X86 禁用分页后,操作系统在远跳转时重置
我正在修改一个从realmode切换到realmode执行BIOS中断的程序,但是遇到了分页问题。我以前让它在没有分页的情况下工作,但现在我的操作系统使用分页,我需要在进入realmode之前禁用它(并在之后启用它) 我的问题是,当执行跳远以使页面禁用生效时,出现了严重错误,我重新启动了 下面显示的代码首先使用页面表X86 禁用分页后,操作系统在远跳转时重置,x86,paging,gnu-assembler,osdev,intel-syntax,X86,Paging,Gnu Assembler,Osdev,Intel Syntax,我正在修改一个从realmode切换到realmode执行BIOS中断的程序,但是遇到了分页问题。我以前让它在没有分页的情况下工作,但现在我的操作系统使用分页,我需要在进入realmode之前禁用它(并在之后启用它) 我的问题是,当执行跳远以使页面禁用生效时,出现了严重错误,我重新启动了 下面显示的代码首先使用页面表boot\u page\u table1创建一个标识映射,该页面表只是标识映射前4个MiB的页面表。必须这样做,因为我现在使用分页从更高的内存运行内核代码,所有内核代码都从0xC01
boot\u page\u table1
创建一个标识映射,该页面表只是标识映射前4个MiB的页面表。必须这样做,因为我现在使用分页从更高的内存运行内核代码,所有内核代码都从0xC0100000
开始寻址,而从0x00100000
开始加载。然后我刷新TLB并跳转到附近的标签,但这次使用的是较低内存中的地址。我的指令指针现在应该指向标识映射代码,禁用分页应该是安全的。然后在cr3
中禁用分页位,TLB再次刷新,因为我有妄想症,切换模式的代码继续
代码的工作原理是将自身压缩到0x7c00处的16位内存中,然后跳转到该内存中,这样它就可以在16位realmode下工作
如果我不禁用分页位,而让其他内容保持不变,那么jmpw CODE16:REBASE(p_mode16)
会工作,跳转后的无限循环会让我认为这个问题是由于我禁用分页而发生的禁用分页时我是否遗漏了什么?我在另一个网站上看到“因为你所做的非常不寻常,你可能会在模拟器上遇到bug和兼容性问题”,但我还不确定这是否只是我的代码错了
该代码是使用带有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
.intel\u语法noprefix
.code32
.global int32,_int32
#定义注册表16\u t\u大小13*2
#定义INT32_基0x00007C00
#定义REBASE(x)((x)-reloc)+INT32_基)
#定义GDTENTRY(x)((x)<3)
#定义代码32 0x08
#定义数据32 0x10
#定义代码16 0x18
#定义数据16 0x20
#定义堆栈16(INT32_基-regs16_t_大小)
.全球重新调整
.global int32_end
.第节.正文
int32:.代码32#由凝固汽油弹生成
_int32:
cli#禁用中断
pusha#将寄存器状态保存到32位堆栈
#对第一个MiB启用标识映射、跳转,然后禁用分页
按[boot_page_directory]#按第一页目录项以在启动后恢复
mov eax(偏移启动页表1)-0xC0000000+0x003
mov[boot_page_directory],eax
mov ecx,cr3#重新加载crc3以强制TLB刷新,使更改生效。
mov-cr3,ecx
mov eax,(偏移凝固汽油弹开关禁用分页)-0xC0000000
jmp-eax
凝固汽油弹开关禁用分页:
#代码现在正在运行,指令指针位于较低的内存中,
#但是代码仍然被组装,好像它在更高的内存中。因为
#其中,像JMPInt32_BASE这样的东西会失败,因为它会
#作为从0xC0100000到0x7C00的地址的相对跳转进行组装
#但将在0x00100000左右的地址运行,导致跳转到
#0x40007C00。
#禁用分页位
mov-eax,cr0
和eax,~0x8000000
mov-cr0,eax
mov ecx,cr3#重新加载crc3以强制TLB刷新,使更改生效。
mov-cr3,ecx
mov esi(偏移重新调整)-0xC0000000#将源代码设置为下面的代码
mov edi,INT32#将目的地设置为新的基址
mov ecx,int32_end-reloc#将拷贝大小设置为我们的代码大小
cld#清除方向标志(因此我们向前复制)
rep movsb#执行实际复制(将代码重新定位到低16位空间)
mov eax,INT32_底座
jmp eax#跳转到新代码位置
reloc:.code32#凝固汽油弹
mov[REBASE(堆栈32#ptr)],esp#保存32位堆栈指针
sidt[idt_ptr]#保存32位idt指针
sgdt[gdt_ptr]#保存32位gdt指针
lgdt[REBASE(gdt16#ptr)]#加载16位gdt指针
lea esi,[esp+0x24]#设置intnum在32位堆栈上的位置
lodsd#将intnum读入eax
mov[REBASE(ib)],al#根据我们的参数设置intrrupt立即字节
mov esi,[esi]#读取esi中的regs指针作为源
mov edi,堆栈16#将目标设置为16位堆栈
mov ecx,regs16_t#u size#将拷贝大小设置为我们的结构大小
mov esp,edi#将目的地另存为16位堆栈偏移量
rep movsb#执行实际复制(32位堆栈到16位堆栈)
jmpw代码16:REBASE(p#U模式16)#切换到16位选择器(16位保护模式)
p_模式16:.代码16
jmp-2
...
更多由于错误而未运行的例程
...
堆栈32_ptr:#在我们
.4字节0x00000000#保存所有通用寄存器
idt16_ptr:#用于16位访问的IDT表指针
.2字节0x03FF#表格限制(大小)
.4字节0x00000000
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