测试操作系统时执行程序集"sti"时虚拟框崩溃



我目前正在尝试构建自己的操作系统,并且在尝试使用 VirtualBox 测试我的内核代码时遇到了问题。

当我调用汇编指令时,真正的问题出现了sti因为我目前正在尝试实现中断描述符表并与 PIC 通信。

下面是调用它的代码。它是从另一个程序集文件调用的名为kernel_main的函数。该文件只是在从操作系统执行任何代码之前设置堆栈,但那里没有任何问题,在我将指令asm("sti");添加到以下代码之前,一切正常:

/*   main function of our kernal 
*   accepts the pointer to multiboot and the magic code (no particular reason to take the magic number)
* 
*      use extern C to prevent gcc from changing the name
*/
extern "C" void kernel_main(void *multiboot_structure, uint32_t magic_number)
{
// can't use the standard printf as we're outside an OS currently
// we don't have access to glibc so we write our own printf
printf_boot_message("kernel.....n");
// create the global descriptor table
GlobalDescriptorTable gdt;
// create the interrupt descriptor table
InterruptHandler interrupt_handler(&gdt);
// enable interrupts (test)
asm("sti");    // <- causes crash 
// random debug printf
printf_boot_message("sti calledn");
// kernal never really stops, inf loop
while (1)
;
}

下面是虚拟盒子调试输出,我已经在谷歌上搜索了VINF_EM_TRIPLE_FAULT但主要发现了我认为不适用于我的 RAM 相关问题。上述代码中的 printf 调用按预期执行,然后 VM 立即崩溃,说明以下内容:

链接到输出,因为它太大而无法在此处发布:https://pastebin.com/jfPfhJUQ

这是我的中断处理代码:

* Implementations of the interrupt handling routines in sys_interrupts.h
*/
#include "sys_interrupts.h"
#include "misc.h"

//handle() is used to take the interrupt number, 
//i_number, and the address to the current CPU stack frame.
uint32_t InterruptHandler::handle(uint8_t i_number, uint32_t crnt_stkptr)
{
// debug
printf(" INTERRUPT");
// after the interrupt code has been executed,
// return the stack pointer so that the CPU can resume
// where it left off.
// this works for now as we do not have multiple
// concurrent processes running, so there is no issue
// of handling the threat number.
return crnt_stkptr;
}

// define the global descriptor table
InterruptHandler::_gate_descriptor InterruptHandler::interrupt_desc_table[N_ENTRIES];

// define the constructor. Takes a pointer to the global 
// descriptor table
InterruptHandler::InterruptHandler(GlobalDescriptorTable* global_desc_table)
{
// grab the offset of the usable memory within our global segment
uint16_t seg = global_desc_table->CodeSegmentSelector();
// set all the entries in the IDT to block request initially
for (uint16_t i = 0; i < N_ENTRIES; i++)
{
// create an a gate for a system level interrupt, calling the block function (does nothing) using seg as its memory.
create_entry(i, seg, &block_request, PRIV_LVL_KERNEL, GATE_INTERRUPT);
}

// create a couple interrupts for 0x00 and 0x01, really 0x20 and 0x21 in memory
//create_entry(BASE_I_NUM + 0x00, seg, &isr0x00, PRIV_LVL_KERNEL, GATE_INTERRUPT);
//create_entry(BASE_I_NUM + 0x01, seg, &isr0x01, PRIV_LVL_KERNEL, GATE_INTERRUPT);

// init the PICs
pic_controller.send_master_cmd(PIC_INIT);
pic_controller.send_slave_cmd(PIC_INIT);
// tell master pic to add 0x20 to any interrupt number it sends to CPU, while slave pic sends 0x28 + i_number
pic_controller.send_master_data(PIC_OFFSET_MASTER);
pic_controller.send_slave_data(PIC_OFFSET_SLAVE);

// set the interrupt vectoring to cascade and tell master that there is a slave PIC at IRQ2
pic_controller.send_master_data(ICW1_INTERVAL4);
pic_controller.send_slave_data(ICW1_SINGLE);

// set the PICs to work in 8086 mode
pic_controller.send_master_data(ICW1_8086);
pic_controller.send_slave_data(ICW1_8086);
// send 0s
pic_controller.send_master_data(DEFAULT_MASK);
pic_controller.send_slave_data(DEFAULT_MASK);

// tell the cpu to use the table
interrupt_desc_table_pointerdata idt_ptr;
//set the size
idt_ptr.table_size = N_ENTRIES * sizeof(_gate_descriptor) - 1;
// set the base address
idt_ptr.base_addr = (uint32_t)interrupt_desc_table;
// use lidt instruction to load the table 
// the cpu will map interrupts to the table
asm volatile("lidt %0" : : "m" (idt_ptr));


// issue debug print
printf_boot_message("   2: Created Interrupt Desc Table...n");
}
// define the destructor of the class
InterruptHandler::~InterruptHandler()
{
}


// function to make entries in the IDT
// takes the interrupt number as an index, the segment offset it used to specify which memory segment to use
// a pointer to the function to call, the flags and access level.
void InterruptHandler::create_entry(uint8_t i_number, uint16_t segment_desc_offset, void (*isr)(), uint8_t priv_lvl, uint8_t desc_type)
{
// set the i_number'th entry to the given params
// take the lower bits of the pointer
interrupt_desc_table[i_number].handler_lower_bits = ((uint32_t)isr) & 0xFFFF;

// take the upper bits
interrupt_desc_table[i_number].handler_upper_bits = (((uint32_t)isr) >> 16) & 0xFFFF;
// calculate the privilage byte, setting the correct bits
interrupt_desc_table[i_number].priv_lvl = 0x80 | ((priv_lvl & 3) << 5) | desc_type;

interrupt_desc_table[i_number].segment_desc_offset = segment_desc_offset;

// reserved byte is always 0
interrupt_desc_table[i_number].reserved_byte = 0;
}

// need a function to block or ignore any requests
// that we dont want to service. Requests could be caused
// by devices we haven't yet configured when testing the os.
void InterruptHandler::block_request()
{
// do nothing
}

// function to tell the CPU to send interrupts
// to this table
void InterruptHandler::set_active()
{
// call sti assembly to start interrup poling at the CPU level
asm volatile("sti");    // <- calling this crashes the kernel

// issue debug print
printf_boot_message("   4: Activated sys interrupts...n");
}

这是我的GDT的代码,我遵循了操作系统开发维基指南:

#include "global_desc_table.h"
/**
* A code segment is identified by flag 0x9A, cannot write to a code segment
* while a data segment is identified by flag 0x92
* 
* Based on the C code present on OSDEV Wiki
*/

GlobalDescriptorTable::GlobalDescriptorTable() : nullSegmentSelector(0, 0, 0),
unusedSegmentSelector(0, 0, 0),
codeSegmentSelector(0, 64*1024*1024, 0x9A),
dataSegmentSelector(0, 64*1024*1024, 0x92)
{

//8 bytes defined, but processor expects 6 bytes only 
uint32_t i[2];
//first 4 bytes is address of table
i[0] = (uint32_t)this;
//second 4 bytes, the high bytes,  are size of global desc table
i[1] = sizeof(GlobalDescriptorTable) << 16;

// tell processor to use this table using its ldgt function
asm volatile("lgdt (%0)" : : "p"  (((uint8_t *) i) + 2));

// issue debug print
printf_boot_message("   1: Created Global Desc Table...n");
}
// function to get the offset of the datasegment selector
uint16_t GlobalDescriptorTable::DataSegmentSelector()
{
// calculate the offset by subtracting the table's address from the datasegment's address
return (uint8_t *) &dataSegmentSelector - (uint8_t*)this;
}
// function to get the offset of the code segment 
uint16_t GlobalDescriptorTable::CodeSegmentSelector()
{
// calculate the offset by subtracting the table's address from the code segment's address
return (uint8_t *) &codeSegmentSelector - (uint8_t*)this;
}
// default destructor
GlobalDescriptorTable::~GlobalDescriptorTable()
{
}
/**
* The constructor to create a new entry segment, set the flags, determine the formatting for the limit, and set the base
*/
GlobalDescriptorTable::SegmentDescriptor::SegmentDescriptor(uint32_t base, uint32_t limit, uint8_t flags)
{
uint8_t* target = (uint8_t*)this;
//if 16 bit limit
if (limit <= 65536)
{ 
// tell processor that this is a 16bit entry
target[6] = 0x40;

} else {

// if the last 12 bits of limit are not 1s
if ((limit & 0xFFF) != 0xFFF)
{
limit = (limit >> 12) - 1;

} else {
limit >>= 12;
}

// indicate that there was a shift of 12 done
target[6] = 0xC0;
}
// set the lower and upper 2 lowest bytes of limit
target[0] = limit & 0xFF;
target[1] = (limit >> 8) & 0xFF;
//the rest of limit must go in lower 4 bit of byte 6, and byte 5
target[6] |= (limit >> 16) & 0xF;
//encode the pointer
target[2] =  base & 0xFF;
target[3] = (base >> 8) & 0xFF;
target[4] = (base >> 16) & 0xFF;
target[7] = (base >> 24) & 0xFF;
// set the flags 
target[5] = flags;

}

/**
* Define the methods to get the base pointer from an segment and 
* the limit for a segment, taken from os wiki
*/
uint32_t GlobalDescriptorTable::SegmentDescriptor::Base()
{
// simply do the reverse of wht was done to place the pointer in

uint8_t* target = (uint8_t*) this;
uint32_t result = target[7];
result = (result << 8) + target[4];
result = (result << 8) + target[3];
result = (result << 8) + target[2];
return result;
}
uint32_t GlobalDescriptorTable::SegmentDescriptor::Limit()
{
uint8_t* target = (uint8_t *)this;
uint32_t result = target[6] & 0xF;
result = (result << 8) + target[1];
result = (result << 8) + target[0];

//check if there was a shift of 12
if (target[6] & 0xC0 == 0xC0)
{
result = (result << 12) & 0xFFF;
}

return result;
}
i[0] = (uint32_t)this;
//second 4 bytes, the high bytes,  are size of global desc table
i[1] = sizeof(GlobalDescriptorTable) << 16;

我遇到了同样的问题,只需在两者之间交换 0 和 1:

i[1] = (uint32_t)this;
//second 4 bytes, the high bytes,  are size of global desc table
i[0] = sizeof(GlobalDescriptorTable) << 16;

如果您遵循相同的教程,这就是问题所在,我认为如果您来到这里,您会这样做。

有时也是由于错误的idtr值(无效指针导致崩溃(

检查 vbox 日志中的 IDTR 注册值 如果您在保护模式下加载 IDT,IDT 的地址会显示一些奇怪的变化(左移 16 位或低 16 位的某些值(

尝试根据此更改指针(这就是我所做的(或在进入保护模式之前使用 lidt(这也经过测试(

我的 GDT 中有一个错误,迫使内核从段读取无效指针。这导致了赛格故障。

最新更新