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

VirtualBox crashing when assembly "sti" is executed when testing Operating System

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

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

这是调用它的代码。这是一个名为 kernel_main 的函数,它是从另一个程序集文件中调用的。该文件只是在执行 OS 中的任何代码之前设置堆栈,但那里没有任何问题,一切正常,直到我将指令 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 called\n");

    // kernal never really stops, inf loop
    while (1)
        ;
}

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

Link 输出,因为它太大而无法在此处 post: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 的代码,我遵循了 os dev wiki 指南:

#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;
}

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

有时也会因为错误的idtr值(无效的指针导致崩溃)

检查 vbox 日志中的 idtr reg 值 如果你在 idt 的保护模式地址中加载 idt 显示一些奇怪的变化(左移 16 位或低 16 位中的某个值)

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

    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;

如果您正在学习相同的教程,这就是问题所在,我想如果您来到这里,您也会这样做。