[SOLVED] INT=0x0d coming from ring 3?

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
avcado
Posts: 24
Joined: Wed Jan 20, 2021 11:32 am
Contact:

[SOLVED] INT=0x0d coming from ring 3?

Post by avcado »

I remapped the PIC to point to non-BIOS interrupts. Then I enabled interrupts, wrote a small irq handler that just sends EOI back to the PIC and returns. I run it in qemu with

Code: Select all

qemu-system-i386 -d int -M smm=off

Code: Select all

Servicing hardware INT=0x20
     0: v=20 e=0000 i=0 cpl=0 IP=0008:002005c5 pc=002005c5 SP=0010:00209868 env->regs[R_EAX]=00201000
EAX=00201000 EBX=00010000 ECX=00202ff3 EDX=00000010
ESI=00000000 EDI=00000000 EBP=00209870 ESP=00209868
EIP=002005c5 EFL=00200202 [-------] 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=     00200030 00000017
IDT=     00205010 000007ff
CR0=00000013 CR2=00000000 CR3=00000000 CR4=00000600
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
DR6=ffff0ff0 DR7=00000400
CCS=00000000 CCD=00209868 CCO=EFLAGS
EFER=0000000000000000
check_exception old: 0xffffffff new 0xd
     1: v=0d e=0000 i=0 cpl=3 IP=1140:0000156a pc=0001296a SP=0000:2d3c280c env->regs[R_EAX]=00000001
EAX=00000001 EBX=00200202 ECX=0000fffd EDX=00000000
ESI=00000000 EDI=00000020 EBP=002005c5 ESP=2d3c280c
EIP=0000156a EFL=003a6686 [D-S--P-] CPL=3 II=0 A20=1 SMM=0 HLT=0
ES =0000 00000000 0000ffff 0000f300 DPL=3 DS16 [-WA]
CS =1140 00011400 0000ffff 0000f300 DPL=3 DS16 [-WA]
SS =0000 00000000 0000ffff 0000f300 DPL=3 DS16 [-WA]
DS =0000 00000000 0000ffff 0000f300 DPL=3 DS16 [-WA]
FS =0000 00000000 0000ffff 0000f300 DPL=3 DS16 [-WA]
GS =0000 00000000 0000ffff 0000f300 DPL=3 DS16 [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     00200030 00000017
IDT=     00205010 000007ff
CR0=00000013 CR2=00000000 CR3=00000000 CR4=00000600
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
DR6=ffff0ff0 DR7=00000400
CCS=00000000 CCD=000000ff CCO=ADDB
EFER=0000000000000000
qemu: fatal: invalid tss type
EAX=00000001 EBX=00200202 ECX=0000fffd EDX=00000000
ESI=00000000 EDI=00000020 EBP=002005c5 ESP=2d3c280c
EIP=0000156a EFL=003a6686 [D-S--P-] CPL=3 II=0 A20=1 SMM=0 HLT=0
ES =0000 00000000 0000ffff 0000f300 DPL=3 DS16 [-WA]
CS =1140 00011400 0000ffff 0000f300 DPL=3 DS16 [-WA]
SS =0000 00000000 0000ffff 0000f300 DPL=3 DS16 [-WA]
DS =0000 00000000 0000ffff 0000f300 DPL=3 DS16 [-WA]
FS =0000 00000000 0000ffff 0000f300 DPL=3 DS16 [-WA]
GS =0000 00000000 0000ffff 0000f300 DPL=3 DS16 [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     00200030 00000017
IDT=     00205010 000007ff
CR0=00000013 CR2=00000000 CR3=00000000 CR4=00000600
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
DR6=ffff0ff0 DR7=00000400
CCS=00000000 CCD=000000ff CCO=ADDB
EFER=0000000000000000
FCW=037f FSW=0000 [ST=0] FTW=00 MXCSR=00001f80
FPR0=0000000000000000 0000 FPR1=0000000000000000 0000
FPR2=0000000000000000 0000 FPR3=0000000000000000 0000
FPR4=0000000000000000 0000 FPR5=0000000000000000 0000
FPR6=0000000000000000 0000 FPR7=0000000000000000 0000
XMM00=0000000000000000 0000000000000000 XMM01=0000000000000000 0000000000000000
XMM02=0000000000000000 0000000000000000 XMM03=0000000000000000 0000000000000000
XMM04=0000000000000000 0000000000000000 XMM05=0000000000000000 0000000000000000
XMM06=0000000000000000 0000000000000000 XMM07=0000000000000000 0000000000000000
Aborted (core dumped)
Huh. I get a GPF after recieving a PIT interrupt. But hold on, why is cpl 3? Why is the KERNEL SELECTOR 1140h? I never jumped to usermode! I don't have anything in my GDT for usermode!?
I added a breakpoint right after init_idt() to see if ISR/IRQ initialization was the problem. It wasn't.

So I add a bit of code to my ISR handler, namely:

Code: Select all

isr_stub:
	mov al, 66h ;; 'f'
	out 0xe9, al
Running with '-debugcon stdio' in qemu does not output an f. It is still (weirdly) talking about a TSS! Do I need a TSS for SSE? I shouldn't, I would've seen this issue way before I got to where I am.

Code

isr.s

Code: Select all

extern exception_handler
%macro isr_err_stub 1
isr_stub_%+%1:
	;; The error code has already been pushed to the stack
	;; when the interrupt is raised, so all we need to do is
	;; push the ISR number
	push byte %1
	jmp isr_stub
  iret 
%endmacro

%macro isr_no_err_stub 1
isr_stub_%+%1:
	push byte 0		;; Empty error code
	push byte %1	;; Push the ISR number to the stack
	jmp isr_stub
  iret
%endmacro

isr_stub:
	mov al, 0x66
	out 0xe9, al
	pusha ;; Push all general purpose registers

	cld		;; ABI requires DF clear on function entry.
	call exception_handler

	popa
	add esp, 8
	iret

isr_no_err_stub 0
isr_no_err_stub 1
isr_no_err_stub 2
isr_no_err_stub 3
isr_no_err_stub 4
isr_no_err_stub 5
isr_no_err_stub 6
isr_no_err_stub 7
isr_err_stub    8
isr_no_err_stub 9
isr_err_stub    10
isr_err_stub    11
isr_err_stub    12
isr_err_stub    13
isr_err_stub    14
isr_no_err_stub 15
isr_no_err_stub 16
isr_err_stub    17
isr_no_err_stub 18
isr_no_err_stub 19
isr_no_err_stub 20
isr_no_err_stub 21
isr_no_err_stub 22
isr_no_err_stub 23
isr_no_err_stub 24
isr_no_err_stub 25
isr_no_err_stub 26
isr_no_err_stub 27
isr_no_err_stub 28
isr_no_err_stub 29
isr_err_stub    30
isr_no_err_stub 31

global isr_stub_table
isr_stub_table:
%assign i 0 
%rep    32 
  dd isr_stub_%+i
%assign i i+1 
%endrep


;; Like the ISRs, we need to do the same thing for the IRQs.
;; Instead of just halting, we want to eventually go and handle it,
;; when I write handlers for the IRQs.

;; Parameter one is the IRQ number itself,
;; and parameter two is the IRQ number + 32
%macro IRQ 2
irq_stub_%1:
	push byte 0		;; No error code (obviously)
	push byte %2	;; This is the IRQ
	jmp irq_stub
%endmacro

IRQ 0,  32
IRQ 1,  33
IRQ 2,  34
IRQ 3,  35
IRQ 4,  36
IRQ 5,  37
IRQ 6,  38
IRQ 7,  39
IRQ 8,  40
IRQ 9,  41
IRQ 10, 42
IRQ 11, 43
IRQ 12, 44
IRQ 13, 45
IRQ 14, 46
IRQ 15, 47

extern irq_handler		;; see isr.c
irq_stub:
	cld		;; ABI requires DF clear on function entry.
	call irq_handler
	popa

	add esp, 8
	iret

global irq_stub_table
irq_stub_table:
%assign j 0
%rep 15
	dd irq_stub_%+j
%assign j j+1
%endrep
My ISR/IRQ handler (isr.c)

Code: Select all

#include "cpu/isr.h"

// Gets the bad opcode at the IP.
// Adapted from my other project, mmlv.
uint32_t get_faulty_opcode(uint32_t ip){
	return (*(uint32_t*)ip) & 0xff;
}

/*
 31         16   15         3   2   1   0
+---+--  --+---+---+--  --+---+---+---+---+
|   Reserved   |    Index     |  Tbl  | E |
+---+--  --+---+---+--  --+---+---+---+---+ */
void analyze_gpf(uint32_t errcode){
	setcolor(0x00027d);
	printf("\nAnalysis:\n");
	if(errcode & (1<<0)) printf("This exception originated externally from processor\n");

	uint32_t tables = (errcode & (1<<1)) + (errcode & (1<<2));
	switch(tables){
		case 0b00:
			printf("Descriptor in GDT caused fault.\n");
			break;

		case 0b01:
			printf("Descriptor in IDT caused fault.\n");
			break;

		case 0b10:
			printf("Descriptor in LDT caused fault.\n");
			break;

		case 0b11:
			printf("Descriptor in IDT caused fault (binary 0b11)\n");
			break;
	}

	// Bits 3-15 are the selector
	printf("Index: %d (hex: %x)\n", (errcode >> 3) & 0x1FFF, (errcode >> 3) & 0x1FFF);
}

__attribute__((interrupt))
void exception_handler(registers_t* r){
  setcolor(0xFF0000);
  printf("Exception occurred at IP=%x:%x. (Opcode(s): %x %x)\n",
					r->cs, r->ip,
					get_faulty_opcode(r->ip), get_faulty_opcode(r->ip+1));

	printf("Interrupt number is %d (hex: %xh) [ec: %d]\n", r->int_no,
					r->int_no, r->errcode);

	// If the opcode is 0x0d (GPF) then we can analyze what actually
	// happened.
	if(r->int_no == 0x0d && r->errcode != 0) analyze_gpf(r->errcode);
  for(;;) __asm__("cli; hlt");
}



void irq_handler(registers_t* r){
	// Acknowledge the interrupt, send an EOI.
	// Did the IRQ come from slave PIC? (Anything above IRQ7 [39])
	if(r->int_no >= 40){
		outb(0xa0,0x20);
	}
	outb(0x20, 0x20); // Also send EOI to master PIC.
}
The registers_t structure

Code: Select all

typedef struct {
	uint32_t ds;
	uint32_t edi, esi, ebp, esp, ebx, edx, ecx, eax;
	uint32_t int_no, errcode;
	uint32_t ip, cs, eflags, useless, ss;
} registers_t
What's going on here?
Last edited by avcado on Sat Aug 24, 2024 7:40 am, edited 1 time in total.
Octocontrabass
Member
Member
Posts: 5418
Joined: Mon Mar 25, 2013 7:01 pm

Re: INT=0x0d coming from ring 3?

Post by Octocontrabass »

avcado wrote: Sat Aug 17, 2024 5:00 pmBut hold on, why is cpl 3? Why is the KERNEL SELECTOR 1140h?
Your ISR mistakenly switched the CPU into virtual 8086 mode.
avcado wrote: Sat Aug 17, 2024 5:00 pm

Code: Select all

%macro isr_err_stub 1
isr_stub_%+%1:
	;; The error code has already been pushed to the stack
	;; when the interrupt is raised, so all we need to do is
	;; push the ISR number
	push byte %1
	jmp isr_stub
  iret 
%endmacro
This IRET instruction does nothing except waste space. Delete it from both macros.
avcado wrote: Sat Aug 17, 2024 5:00 pm

Code: Select all

isr_stub:
	mov al, 0x66
	out 0xe9, al
	pusha ;; Push all general purpose registers
You need to push the general registers before you overwrite AL, not after.
avcado wrote: Sat Aug 17, 2024 5:00 pm

Code: Select all

;; Like the ISRs, we need to do the same thing for the IRQs.
They're all ISRs. You have ISRs for exceptions and you have ISRs for IRQs. If you use the wrong words to describe things, you'll confuse yourself and anyone who tries to help you.
avcado wrote: Sat Aug 17, 2024 5:00 pm

Code: Select all

irq_stub:
	cld		;; ABI requires DF clear on function entry.
	call irq_handler
	popa
You're trying to pop values you didn't push on the stack.
avcado wrote: Sat Aug 17, 2024 5:00 pm

Code: Select all

__attribute__((interrupt))
void exception_handler(registers_t* r){
You shouldn't use the interrupt attribute. This function has a parameter, but you're not passing any parameters in your assembly code.
avcado wrote: Sat Aug 17, 2024 5:00 pm

Code: Select all

void irq_handler(registers_t* r){
This function has a parameter, but you're not passing any parameters in your assembly code.
avcado wrote: Sat Aug 17, 2024 5:00 pm

Code: Select all

typedef struct {
	uint32_t ds;
	uint32_t edi, esi, ebp, esp, ebx, edx, ecx, eax;
	uint32_t int_no, errcode;
	uint32_t ip, cs, eflags, useless, ss;
} registers_t
You never push DS on the stack.
avcado
Posts: 24
Joined: Wed Jan 20, 2021 11:32 am
Contact:

Re: INT=0x0d coming from ring 3?

Post by avcado »

Octocontrabass wrote: Sat Aug 17, 2024 5:20 pm
avcado wrote: Sat Aug 17, 2024 5:00 pm

Code: Select all

irq_stub:
	cld		;; ABI requires DF clear on function entry.
	call irq_handler
	popa
You're trying to pop values you didn't push on the stack.
#-o I can't believe I missed it. Adding "pusha" before cld did in fact fix the issue (and allow me to recieve interrupts).
Octocontrabass wrote: Sat Aug 17, 2024 5:20 pm
avcado wrote: Sat Aug 17, 2024 5:00 pm

Code: Select all

__attribute__((interrupt))
void exception_handler(registers_t* r){
You shouldn't use the interrupt attribute. This function has a parameter, but you're not passing any parameters in your assembly code.
Why not? I did find your comment on a r/osdev post (https://www.reddit.com/r/osdev/comments ... interrupt/). It makes sense but I don't 100% understand why not to use it? I guess I should use "__attribute__((noreturn))" since it doesn't return and goes into a cli; hlt loop.

Thanks for the help though!:)
Octocontrabass
Member
Member
Posts: 5418
Joined: Mon Mar 25, 2013 7:01 pm

Re: INT=0x0d coming from ring 3?

Post by Octocontrabass »

avcado wrote: Sat Aug 17, 2024 5:45 pmIt makes sense but I don't 100% understand why not to use it?
In this case, you shouldn't use that attribute because you're calling it like an ordinary function. It'll use IRET instead of RET to return, which isn't going to work with an ordinary function call.

It also won't give you access to the saved registers, so you can't debug programs that cause exceptions, and it won't save and restore segment registers, so you can't use ring 3.
avcado wrote: Sat Aug 17, 2024 5:45 pmI guess I should use "__attribute__((noreturn))" since it doesn't return and goes into a cli; hlt loop.
You could, but at some point you probably will want to return from your exception handler, and then you'll need to remove it.
Post Reply