测试操作系统时执行程序集 "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;
如果您正在学习相同的教程,这就是问题所在,我想如果您来到这里,您也会这样做。
我目前正在尝试构建自己的操作系统,但在尝试使用 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;
如果您正在学习相同的教程,这就是问题所在,我想如果您来到这里,您也会这样做。