Hello everyone, it's been a while...
I wanted to tell you about a problem I have when handling interrupts from user space, or rather, from ring 3.
Let's go by parts. I'll put it in a list to make it easier to read.
Where we are?:- We are in
protected mode only (32 bits).
-
GDT and
IDT are
correctly configured (i will provide more info below).
- We
ignore the paging code for now, since it is disabled for testing purposes.
- I do set a
new stack for the kernel.
- Kernel interrupts (without DPL change) are executed correctly, both hardware and exceptions.
-
Jump to ring 3 is done correctly, and everything
runs fine.
- The
selectors change and are
fine, and the CPL is fine(3).
The problem:
- When a hardware (timer) interrupt is received, it jumps to the interrupt handler, but SS:ESP does not get on the stack.
- The user stack is kept, it does not change to the kernel stack.
- Ignores ss0 and esp0 in the TSS.
- Being IOPL=0, if it tries to acknowledge the PIC interrupts, a GPF occurs.
- All this ends messing up the stack.
More info:The GDT:
- NULL all zeroed
- KERNEL DPL(0) CS = 0x08; BASE=0x00000000; LIMIT= 0xFFFFFFFF; ACCESS=0x9A; FLAGS=0xC
- KERNEL DPL(0) DS = 0x10; BASE=0x00000000; LIMIT= 0xFFFFFFFF; ACCESS=0x92; FLAGS=0xC
- USER DPL(3) CS = 0x18; BASE=0x00000000; LIMIT= 0xFFFFFFFF; ACCESS=0xFA; FLAGS=0xC
- USER DPL(3) DS = 0x20; BASE=0x00000000; LIMIT= 0xFFFFFFFF; ACCESS=0xF2; FLAGS=0xC
- TSS = 0x28; BASE=0x00000000; LIMIT= 0xFFFFFFFF; ACCESS=0x9A; FLAGS=0xC
The IDT:
All Entries(hardware interrupts and exceptions) are with
- Selector: KERNEL CS (0x8)
- DPL = 0
- 32-bit Interrupt gate
The TSS:
- SS0 = 0x10(kernel DS)
- ESP0 = about 0x0010922c
- CS = kernel CS | 0x3
- DS,ES,FS,GS,SS = kernel DS | 0x3
Debug Info:First of all, say that I dumped the memory related to all system structures like GDT, TSS and IDT, and checked that are ok. Also checked, the stack push and pop, instruction by instruction, in kernel mode and when jumping to user mode(by iret).
This is my selectors before setting the for the jump, while still in supervisor mode:
Code:
<bochs:11> sreg
es:0x0010, dh=0x00cf9300, dl=0x0000ffff, valid=1
Data segment, base=0x00000000, limit=0xffffffff, Read/Write, Accessed
cs:0x0008, dh=0x00cf9d00, dl=0x0000ffff, valid=1
Code segment, base=0x00000000, limit=0xffffffff, Execute-Only, Conforming, Accessed, 32-bit
ss:0x0010, dh=0x00cf9300, dl=0x0000ffff, valid=31
Data segment, base=0x00000000, limit=0xffffffff, Read/Write, Accessed
ds:0x0010, dh=0x00cf9300, dl=0x0000ffff, valid=31
Data segment, base=0x00000000, limit=0xffffffff, Read/Write, Accessed
fs:0x0010, dh=0x00cf9300, dl=0x0000ffff, valid=1
Data segment, base=0x00000000, limit=0xffffffff, Read/Write, Accessed
gs:0x0010, dh=0x00cf9300, dl=0x0000ffff, valid=1
Data segment, base=0x00000000, limit=0xffffffff, Read/Write, Accessed
ldtr:0x0000, dh=0x00008200, dl=0x0000ffff, valid=1
tr:0x0028, dh=0x0000eb10, dl=0x896089c8, valid=1
gdtr:base=0x00108900, limit=0x4f
idtr:base=0x00108060, limit=0x7ff
Now, how the stack is filled before the iret:
Code:
<bochs:28> print-stack
Stack address size 4
| STACK 0x00109214 [0x0010295c] (<unknown>) << This is my user function
| STACK 0x00109218 [0x0000001b] (<unknown>) << My user cs ... 0x18 | 0x3(rpl)
| STACK 0x0010921c [0x00000202] (<unknown>) << The eflags
| STACK 0x00109220 [0x00106400] (<unknown>) << The new stack (taken into consideration that it grows down, don't worry)
| STACK 0x00109224 [0x00000023] (<unknown>) << The user stack selector, ss.... 0x20 | 0x3 (rpl)
| STACK 0x00109228 [0x00100350] (<unknown>) << The ret from kernel, just ignore
| STACK 0x0010922c [0x00000000] (<unknown>)
| STACK 0x00109230 [0x00000000] (<unknown>)
| STACK 0x00109234 [0x00000000] (<unknown>)
| STACK 0x00109238 [0x00000000] (<unknown>)
| STACK 0x0010923c [0x00000000] (<unknown>)
....
From user, I can write a string to the screen framebuffer, so that is OK.
Now, after the switch, the selectors look like this:
Code:
<bochs:32> sreg
es:0x0023, dh=0x00cff300, dl=0x0000ffff, valid=1
Data segment, base=0x00000000, limit=0xffffffff, Read/Write, Accessed
cs:0x001b, dh=0x00cffb00, dl=0x0000ffff, valid=1
Code segment, base=0x00000000, limit=0xffffffff, Execute/Read, Non-Conforming, Accessed, 32-bit
ss:0x0023, dh=0x00cff300, dl=0x0000ffff, valid=1
Data segment, base=0x00000000, limit=0xffffffff, Read/Write, Accessed
ds:0x0023, dh=0x00cff300, dl=0x0000ffff, valid=1
Data segment, base=0x00000000, limit=0xffffffff, Read/Write, Accessed
fs:0x0023, dh=0x00cff300, dl=0x0000ffff, valid=1
Data segment, base=0x00000000, limit=0xffffffff, Read/Write, Accessed
gs:0x0023, dh=0x00cff300, dl=0x0000ffff, valid=1
Data segment, base=0x00000000, limit=0xffffffff, Read/Write, Accessed
ldtr:0x0000, dh=0x00008200, dl=0x0000ffff, valid=1
tr:0x0028, dh=0x0000eb10, dl=0x896089c8, valid=1
gdtr:base=0x00108900, limit=0x4f
idtr:base=0x00108060, limit=0x7ff
And then, when it jumps to the handler, the selectors look like this:
Code:
es:0x0023, dh=0x00cff300, dl=0x0000ffff, valid=1
Data segment, base=0x00000000, limit=0xffffffff, Read/Write, Accessed
cs:0x000b, dh=0x00cf9d00, dl=0x0000ffff, valid=1
Code segment, base=0x00000000, limit=0xffffffff, Execute-Only, Conforming, Accessed, 32-bit
ss:0x0023, dh=0x00cff300, dl=0x0000ffff, valid=31
Data segment, base=0x00000000, limit=0xffffffff, Read/Write, Accessed
ds:0x0023, dh=0x00cff300, dl=0x0000ffff, valid=31
Data segment, base=0x00000000, limit=0xffffffff, Read/Write, Accessed
fs:0x0023, dh=0x00cff300, dl=0x0000ffff, valid=1
Data segment, base=0x00000000, limit=0xffffffff, Read/Write, Accessed
gs:0x0023, dh=0x00cff300, dl=0x0000ffff, valid=1
Data segment, base=0x00000000, limit=0xffffffff, Read/Write, Accessed
ldtr:0x0000, dh=0x00008200, dl=0x0000ffff, valid=1
tr:0x0028, dh=0x0000eb10, dl=0x896089c8, valid=1
gdtr:base=0x00108900, limit=0x4f
idtr:base=0x00108060, limit=0x7ff
Also, look at the stack:
Code:
| STACK 0x001063e8 [0x00000000] (<unknown>) << EAX value, not important for us
| STACK 0x001063ec [0x00000020] (<unknown>) << Interrupt number(32 = timer)
| STACK 0x001063f0 [0x00000000] (<unknown>) << Dummy error code
| STACK 0x001063f4 [0x00102971] (<unknown>) << Correct EIP from user function
| STACK 0x001063f8 [0x0000001b] (<unknown>) << The user CS
| STACK 0x001063fc [0x00000216] (<unknown>) << The eflags
| STACK 0x00106400 [0x000a7325] (<unknown>) << Garbage(out of stack boundaries)
| STACK 0x00106404 [0x616c6f48] (<unknown>) << Garbage(out of stack boundaries)
| STACK 0x00106408 [0x6e754d20] (<unknown>) << Garbage(out of stack boundaries)
IMPORTANT: Look at the stack frame before and after, and you realize. ESP is not changed upon interrupt.
Kernel stack is 0x0010922c and the user stack is on 0x001063fc.
So SS does not change, nor ESP. And the user SS:ESP are not pushed to the stack, the kernel stack!!
Some code:Now lets have a look on our code.
First of all, the GDT and TSS:
Code:
void gdt_init()
{
//null entry
gdt_set_gate(GDT_NULL_ENTRY,0,0,0,0);
//kernel code segment
gdt_set_gate(GDT_KERNEL_CS_ENTRY, 0xFFFFFFFF, 0x0, (GDT_P|GDT_S|GDT_EX|GDT_DC), (GDT_G|GDT_DB));
//kernel data segment
gdt_set_gate(GDT_KERNEL_DS_ENTRY, 0xFFFFFFFF, 0x0, (GDT_P|GDT_S|GDT_RW), (GDT_G|GDT_DB));
//user code segment
gdt_set_gate(GDT_USER_CS_ENTRY, 0xFFFFFFFF, 0x0, (GDT_P|GDT_DPL(3)|GDT_S|GDT_EX|GDT_RW), (GDT_G|GDT_DB));
//user data segment
gdt_set_gate(GDT_USER_DS_ENTRY, 0xFFFFFFFF, 0x0, (GDT_P|GDT_DPL(3)|GDT_S|GDT_RW), (GDT_G|GDT_DB));
gdt.base = (uint32) &gdt_entries;
gdt.limit = (sizeof(gdt_entry_t)*MAX_GDT_ENTRIES)-1;
install_tss(GDT_TSS_ENTRY, KERNEL_DS, 0x00); //I update the stack later
__install_gdt(&gdt);
__flush_tss(GDT_TSS_ENTRY * sizeof(gdt_entry_t));
}
void update_tss_stack(uint32 kernel_esp)
{
kernel_tss.esp0 = kernel_esp;
}
void install_tss (uint32 entry, uint16 ss, uint32 esp)
{
uint32 base = (uint32) &kernel_tss;
gdt_set_gate(entry, base + sizeof(tss_t), base,
(GDT_P | GDT_DPL(3) | TSS_32B_AVAIL), 0);
memset(&kernel_tss, 0, sizeof(tss_t));
kernel_tss.ss0 = ss;
kernel_tss.esp0 = esp;
kernel_tss.cs = KERNEL_CS | 0x3;
kernel_tss.ss = KERNEL_DS | 0x3;
kernel_tss.es = KERNEL_DS | 0x3;
kernel_tss.ds = KERNEL_DS | 0x3;
kernel_tss.fs = KERNEL_DS | 0x3;
kernel_tss.gs = KERNEL_DS | 0x3;
}
The ltr instruction:
Code:
__flush_tss:
movw 4(%esp), %ax
ltr %ax
ret
The user mode jump code:
Code:
_user_stack_bottom:
.space 1024, 0
_user_stack:
.section .text
.global __jump_user
__jump_user:
cli
mov $0x23, %eax
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
mov $_user_stack, %eax
pushl $0x23 // SS ESP EFLAGS CS EIP
pushl %eax
pushfl
pop %eax
or $0x200, %eax
push %eax
pushl $0x1b
push $user_entry
iret
In the user_entry function I do not do nothing weird, just print a string and keep the CPU on an endless loop:
Code:
user_entry:
mov $fmt, %ebx
pushl %ebx
mov $hola, %ebx
pushl %ebx
call kprintf
add $8, %esp
w:
nop
nop
nop
jmp w
This is the IRQ handler:
Code:
_irq_\num:
xchg %bx,%bx //Bochs breakpoint, just ignore
pushl $\num
pushl $\num+HW_OFFSET
jmp _irq_common
.endm
_irq_common:
pushal
pushl %ds
pushl %es
pushl %fs
pushl %gs
push %esp
call irq_handler
addl $4, %esp
popl %gs
popl %fs
popl %es
popl %ds
popal
addl $8,%esp
iret
And last, but not least, the structure (passed by reference) to the C handler:
Code:
struct regs
{
uint32 gs, fs, es, ds;//0-12
uint32 edi, esi, ebp, esp, ebx, edx, ecx, eax;//16----32-36-40-44
uint32 int_no, err_code;
uint32 eip, cs, eflags, useresp, ss;
};
I think that's all. If you have any question, need more info, or the code itself, just let me know.
Bye!