Page 1 of 1

Page fault on iret?

Posted: Fri Oct 03, 2025 12:37 pm
by restingwitchface
I've gotten exceptions working for i686, here's my stub for it:

Code: Select all

.macro isr vec
.global _isr_\vec
.type _isr_\vec, @function

_isr_\vec:
	pushal
	cld
	call isr_\vec
	popal
	iret
.endm
So I've worked on porting everything in my OS so far to x86_64, and it mostly works, except I for some reason get a page fault on iret. Here's my stub:

Code: Select all

/* Doesn't save SSE/SSE2/MMX state, but we don't use that in the kernel anyways. */
.macro pushaq
	pushq %rax
	[...]
	pushq %r15
.endm

.macro popaq
	popq %r15
	[...]
	popq %rax
.endm

.macro isr vec
.global _isr_\vec
.type _isr_\vec, @function

_isr_\vec:
pushaq
	cld
	call isr_\vec
popaq
	iret
.endm
With gdb (setting a breakpoint at the ISR's first and last instruction) I verified that RSP is the same on entry and exit from the interrupt, so it's not trying to restore some junk state:

Code: Select all

(gdb) print $rsp
$1 = (void *) 0xffffc00000187f88
(gdb) c
Continuing.
(gdb) print $rsp
$2 = (void *) 0xffffc00000187f88
Here's the section of the QEMU logs containing the original interrupt (keyboard, 0x21) and the following page fault:

Code: Select all

Servicing hardware INT=0x21
     0: v=21 e=0000 i=0 cpl=0 IP=0008:ffffc000000007c0 pc=ffffc000000007c0 SP=0010:ffffc00000187fb0 env->regs[R_EAX]=0000000000000000
RAX=0000000000000000 RBX=ffffc00000100010 RCX=ffffc00000100120 RDX=00000000000003f8
RSI=000000000000000a RDI=0000000000000020 RBP=ffffc00000187ff0 RSP=ffffc00000187fb0
R8 =cccccccccccccccd R9 =ffffc000001989b1 R10=0000000000000000 R11=0000000000000000
R12=ffffc00000000a30 R13=ffffc00000000a80 R14=ffffc00000080052 R15=ffffc000000012a0
RIP=ffffc000000007c0 RFL=00000246 [---Z-P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 0000000000000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 0000000000000000 ffffffff 00af9b00 DPL=0 CS64 [-RA]
SS =0010 0000000000000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0010 0000000000000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0010 0000000000000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0010 0000000000000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 0000000000000000 0000ffff 00008200 DPL=0 LDT
TR =0000 0000000000000000 0000ffff 00008b00 DPL=0 TSS64-busy
GDT=     0000000000200010 00000017
IDT=     ffffc00000188fe0 00000fff
CR0=80000011 CR2=0000000000000000 CR3=0000000000203000 CR4=00000020
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=0000000000000044 CCD=0000000000000000 CCO=EFLAGS
EFER=0000000000000500
check_exception old: 0xffffffff new 0xe
     1: v=0e e=0000 i=0 cpl=0 IP=0008:ffffc00000000069 pc=ffffc00000000069 SP=0010:ffffc00000187f88 CR2=0000000000187f88
RAX=0000000000000000 RBX=ffffc00000100010 RCX=ffffc00000100120 RDX=00000000000003f8
RSI=000000000000000a RDI=0000000000000020 RBP=ffffc00000187ff0 RSP=ffffc00000187f88
R8 =cccccccccccccccd R9 =ffffc000001989b1 R10=0000000000000000 R11=0000000000000000
R12=ffffc00000000a30 R13=ffffc00000000a80 R14=ffffc00000080052 R15=ffffc000000012a0
RIP=ffffc00000000069 RFL=00000097 [--S-APC] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 0000000000000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 0000000000000000 ffffffff 00af9b00 DPL=0 CS64 [-RA]
SS =0010 0000000000000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0010 0000000000000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0010 0000000000000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0010 0000000000000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 0000000000000000 0000ffff 00008200 DPL=0 LDT
TR =0000 0000000000000000 0000ffff 00008b00 DPL=0 TSS64-busy
GDT=     0000000000200010 00000017
IDT=     ffffc00000188fe0 00000fff
CR0=80000011 CR2=0000000000187f88 CR3=0000000000203000 CR4=00000020
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=0000000000000008 CCD=fffffffffffffff9 CCO=SUBB
EFER=0000000000000500
And here's the QEMU monitor's paging information (you'll notice I was too lazy to remove the VGA text buffer's identity mapping :P):

Code: Select all

(qemu) info mem
00000000000b8000-00000000000b9000 0000000000001000 -rw
0000000000200000-0000000000201000 0000000000001000 -rw
ffffc00000000000-ffffc00000002000 0000000000002000 -rw
ffffc00000080000-ffffc00000081000 0000000000001000 -rw
ffffc00000100000-ffffc00000101000 0000000000001000 -rw
ffffc00000180000-ffffc0000018b000 000000000000b000 -rw
ffffc00000200000-ffffc00000201000 0000000000001000 -rw
ffffff8000000000-ffffff8000002000 0000000000002000 -rw
ffffffe000000000-ffffffe000002000 0000000000002000 -rw
ffffffffc0000000-ffffffffc0001000 0000000000001000 -rw
fffffffff0000000-fffffffff0001000 0000000000001000 -rw
ffffffffffe00000-ffffffffffe01000 0000000000001000 -rw
fffffffffff80000-fffffffffff81000 0000000000001000 -rw
fffffffffffff000-0001000000000000 0000000000001000 -rw
Does anybody know what's going on here? Again, this all works fine on 32-bit. As you can see, the address it's trying to access here seems to be 0x187f88: I have no idea where that comes from...

Re: Page fault on iret?

Posted: Fri Oct 03, 2025 1:10 pm
by iansjack
Try using “iretq” instead of “iret”.

Re: Page fault on iret?

Posted: Fri Oct 03, 2025 1:18 pm
by restingwitchface
Thanks, that works!
In your Interrupt Service Routines, remember to return from the interrupt using the IRETQ instruction instead of IRET, as assemblers will not translate that for you. Many 64-bit IDT related problems on the forum are caused by that missing 'Q'. Don't let this happen to you.
Oops! Only now seeing this paragraph right below the 64-bit IDT structure I was reading...

What's the difference? I guess Intel was too lazy to make iret invalid in 64-bit (like they did with pusha) and have it still use the 32-bit interrupt structure.

Re: Page fault on iret?

Posted: Fri Oct 03, 2025 1:26 pm
by iansjack
“iret” is still valid in 64-bit mode, but it indicates the use of 32-bit arguments. It would have worked if your kernel was not higher-half.

Re: Page fault on iret?

Posted: Fri Oct 03, 2025 2:27 pm
by Octocontrabass
restingwitchface wrote: Fri Oct 03, 2025 1:18 pmWhat's the difference?
Instructions that allow both 32-bit and 64-bit operands default to 32-bit if you don't specify otherwise. If you just write "iret" you get "iretl" instead of "iretq".
restingwitchface wrote: Fri Oct 03, 2025 1:18 pmI guess Intel was too lazy to make iret invalid in 64-bit (like they did with pusha) and have it still use the 32-bit interrupt structure.
It would've been AMD, and I don't think they ever explained their reasoning. I would guess they saw "iretw" being useful somewhere in 32-bit code and figured "iretl" might be useful in 64-bit code.
iansjack wrote: Fri Oct 03, 2025 1:26 pmIt would have worked if your kernel was not higher-half.
Would it? I thought the stack layout depended on the operand size...

Re: Page fault on iret?

Posted: Fri Oct 03, 2025 11:21 pm
by iansjack
My mistake. I thought the page fault was caused by the “iret” instruction only using the lower 32 bits of RSP so that it never got to see the stack frame.

I didn’t mean to imply that there wouldn’t be further errors.