我正在学习使用gcc,nasm,和qemu在i386上开发一个玩具操作系统。
我尝试初始化IDT并添加了一些中断处理程序。但我有个奇怪的错误我无法理解。
这是github回购。下面是有关这个问题的一些细节。
我认为问题隐藏在main.c中。
#define IDT_ADDR 0x2000
#define AR_INTGATE32 0x8e
typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;
//remapping pic
#define PIC0_ICW1 0x20
#define PIC0_OCW2 0x20
#define PIC0_IMR 0x21
#define PIC0_ICW2 0x21
#define PIC0_ICW3 0x21
#define PIC0_ICW4 0x21
#define PIC1_ICW1 0xa0
#define PIC1_OCW2 0xa0
#define PIC1_IMR 0xa1
#define PIC1_ICW2 0xa1
#define PIC1_ICW3 0xa1
#define PIC1_ICW4 0xa1
static inline void outb(uint16_t port, uint8_t val) {
asm volatile ( "outb %0, %1" : : "a"(val), "Nd"(port) );
}
void init_pic() {
outb(PIC0_IMR, 0xff);
outb(PIC1_IMR, 0xff);
outb(PIC0_ICW1, 0x11);
outb(PIC0_ICW2, 0x20); /* IRQ0-7 -> INT 0x20-0x27*/
outb(PIC0_ICW3, 1<<2);
outb(PIC0_ICW4, 0x01);
outb(PIC1_ICW1, 0x11);
outb(PIC1_ICW2, 0x28); /* IRQ8-15 -> INT 0x28-0x2f */
outb(PIC1_ICW3, 2);
outb(PIC1_ICW4, 0x01);
outb(PIC0_IMR, 0xfc); /* 11111011 PIC1*/
outb(PIC1_IMR, 0xff);
}
struct idt_entry_t {
uint16_t isr_low; // The lower 16 bits of the ISR's address
uint16_t kernel_cs; // The GDT segment selector that the CPU will load into CS before calling the ISR
uint8_t reserved; // Set to zero
uint8_t attributes; // Type and attributes; see the IDT page
uint16_t isr_high; // The higher 16 bits of the ISR's address
} __attribute__((packed));
void set_intrdesc(struct idt_entry_t *id, void* isr, uint16_t cs, uint8_t attr) {
id->isr_low = (uint32_t)isr & 0xffff;
id->kernel_cs = cs;
id->reserved = 0;
id->attributes = attr;
id->isr_high = (uint32_t)isr >> 16;
}
struct idt_entry_t *idt = (struct idt_entry_t*)IDT_ADDR;
struct interrupt_frame {
uint32_t tf_eip;
uint16_t tf_cs;
uint16_t tf_padding4;
uint32_t tf_eflags;
};
__attribute__ ((interrupt))
void interrupt_handler_0x80(struct interrupt_frame *frame) {
asm("mov $0x20, %eax");
while (1);
}
__attribute__ ((interrupt))
void general_protection_handler13(struct interrupt_frame *frame, uint32_t error_code) {
asm("mov $13, %eax");
while (1);
}
#define KERNEL_CS 0x8
void init_idt() {
for (int i = 0; i < 256; i++) {
set_intrdesc(idt + i, 0, 0, 0);
}
set_intrdesc(idt + 13, general_protection_handler13, KERNEL_CS, AR_INTGATE32);
struct {uint16_t limit; uint32_t addr;} __attribute__((packed)) idtr;
idtr.limit = 255;
idtr.addr = idt;
asm volatile ("lidt %0" :: "m" (idtr));
}
void main() {
init_idt();
init_pic();
asm("sti");
set_intrdesc(idt + 0x80, interrupt_handler_0x80, KERNEL_CS, AR_INTGATE32);
asm("int $0x80");
while (1) {
asm("hlt");
}
}当我尝试make log "TERMINAL=xfce4-terminal"时
The result of make log "TERMINAL=xfce4-terminal" -- just-print
nasm boot.s -o boot.bin
nasm setup.s -o setup.bin
gcc main.c -O -march=i386 -m32 -g -fno-builtin -fno-PIC -Wall -nostdinc -fno-stack-protector -ffreestanding -ffunction-sections -mgeneral-regs-only -c -o main.o
ld -nostdlib -Tmain.ld main.o -o main.bin.elf
objdump -S main.bin.elf > main.bin.asm
objcopy -S -O binary main.bin.elf main.bin
dd if=/dev/zero of=test.img count=100
dd if=boot.bin of=test.img conv=notrunc
dd if=setup.bin of=test.img seek=1 conv=notrunc
dd if=main.bin of=test.img seek=2 conv=notrunc
qemu-system-i386 -no-reboot -S -s -d int -D q.log -parallel stdio -hda test.img -serial null &
sleep 2
xfce4-terminal -e "gdb -q -x gdbinit",在int $0x80之后,gdb将在general_protection_handler13停下来。
下面是关于异常的interrupt frame和error code (我使用了一个名为gef的gdb插件)。
Breakpoint 1, general_protection_handler13 (frame=0xefff4, error_code=0x402) at main.c:74
74 asm("mov $13, %eax");
gef➤ p *frame
$1 = {
tf_eip = 0x823d,
tf_cs = 0x8,
tf_padding4 = 0x0,
tf_eflags = 0x247
}
gef➤ x/i 0x823d
0x823d <main+61>: int 0x80
gef➤ python print(bin(0x402), bin(0x80))
0b10000000010 0b10000000我知道GPE是由int $0x80引起的,error_code中的选择器指数是正确的。
GPE和0x80的IDT条目如下:
gef➤ p idt[13]
$2 = {
isr_low = 0x8249,
kernel_cs = 0x8,
reserved = 0x0,
attributes = 0x8e,
isr_high = 0x0
}
gef➤ p idt[0x80]
$3 = {
isr_low = 0x8242,
kernel_cs = 0x8,
reserved = 0x0,
attributes = 0x8e,
isr_high = 0x0
}这是qemu关于中断的日志。
0: v=80 e=0000 i=1 cpl=0 IP=0008:0000823d pc=0000823d SP=0010:000f0000 env->regs[R_EAX]=00002000
EAX=00002000 EBX=00000000 ECX=00000001 EDX=00000000
ESI=00000000 EDI=00000000 EBP=000e0000 ESP=000f0000
EIP=0000823d EFL=00000247 [---Z-PC] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT= 0000802c 00000017
IDT= 00002000 000000ff
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000
DR6=ffff0ff0 DR7=00000400
CCS=00000001 CCD=00000000 CCO=SARL
EFER=0000000000000000
check_exception old: 0xffffffff new 0xd
1: v=0d e=0402 i=0 cpl=0 IP=0008:0000823d pc=0000823d SP=0010:000f0000 env->regs[R_EAX]=00002000
EAX=00002000 EBX=00000000 ECX=00000001 EDX=00000000
ESI=00000000 EDI=00000000 EBP=000e0000 ESP=000f0000
EIP=0000823d EFL=00000247 [---Z-PC] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT= 0000802c 00000017
IDT= 00002000 000000ff
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000
DR6=ffff0ff0 DR7=00000400
CCS=00000001 CCD=00000000 CCO=SARL
EFER=0000000000000000我认为我的GDT和IDT是正确的。
我在不同的网站上读到过许多关于类似问题的答案,但不明白为什么我的int $0x80会导致General Prorection Exception,以及如何修复它。
为我的愚蠢感到抱歉,但我真的需要别人的帮助。
发布于 2022-10-07 13:55:10
IDT=00002000 000000ff建议您的IDT大小为0xff字节(+1)或256个字节。但是每个IDT条目是8个字节。也许您打算将大小设置为256*8-1或2047 (0x7ff)。因此,在我看来,大小并不正确,0x80异常超出了导致#GP错误的IDT的限制。
我注意到了问题中的代码:idtr.limit = 255;应该是idtr.limit = 256*sizeof(idt_entry_t)-1;,我还想问为什么您选择将IDT放在地址0x2000,而没有在C类struct idt_entry_t idt[256];中定义一个由256个idt_entry_t条目组成的数组,然后修改代码来使用IDT作为数组。然后可以使用sizeof(idt)-1设置IDT限制。
SP=0010:000f0000看起来很可疑。CR0说分页已经关闭。物理地址0xf 0000位于ROM BIOS内存区域。可能将堆栈ESP设置为0x90000。
https://stackoverflow.com/questions/73986788
复制相似问题