General Protection Fault when switching to u-mode with iret

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
LIC
Member
Member
Posts: 44
Joined: Mon Jun 04, 2018 8:10 am
Libera.chat IRC: lic

General Protection Fault when switching to u-mode with iret

Post by LIC »

Hello,
i am following tutorial on building an OS but I have a problem getting to user mode.

My GDT is set up like this:

Code: Select all


void init_gdt() {

	gdt_ptr.base = (u32)&gdt_entries;
	gdt_ptr.limit = sizeof(gdt_entry_t) * GDT_SIZE;

	gdt_entries[0] = create_gdt_entry(0, 0, 0, 0); // 0x00

	gdt_entries[1] = create_gdt_entry(0, 0xffffffff, 0x98, 0xd0); // code segment 0x08
	gdt_entries[2] = create_gdt_entry(0, 0xffffffff, 0x92, 0xd0); // data segment 0x10
	gdt_entries[3] = create_gdt_entry(0x00, 0x00, 0x96, 0xd0);	// kernel stack 0x18 (not used)

	gdt_entries[4] = create_gdt_entry(0x30000, 0x1000, 0xf8, 0xd0); // user code segment 0x20
	gdt_entries[5] = create_gdt_entry(0x30000, 0x1000, 0xf2, 0xd0); // user data segment 0x28
	gdt_entries[6] = create_gdt_entry(0x00, 0x30000, 0xf6, 0xd0); // user stack 0x30 (not used)

	load_gdt((u32)&gdt_ptr);

	print_ok();
	print("New GDT loaded\n");

}

gdt_entry_t create_gdt_entry(const u32 base, const u32 limit, const u8 access, const u8 gran) {

	return (gdt_entry_t) {
		.base_low 		= (base & 0xffff),
		.base_middle 	= (base >> 16) & 0xff,
		.base_high 		= (base >> 24) & 0xff,

		.limit_low 		= limit & 0xffff,
		.granularity 	= (gran & 0xf0) | ((limit >> 16) & 0x0f),

		.access 		= access
	};

}

The TSS is loaded like that:

Code: Select all

void init_tss() {

    // init the TSS to zeros
    memset(&default_tss, 0, sizeof(tss_t));

    __asm__("  movw %%ss, %0 \n \
       movl %%esp, %1" : "=m" (default_tss.ss0), "=m" (default_tss.esp0) : );

    // entry 7 = 0x38
    gdt_entries[7] = create_gdt_entry((u32)&default_tss, sizeof(tss_t), 0xe9, 0x00);

    // load TSS register
    tss_load();

}

Code: Select all

tss_load:
    mov ax, 0x3b ; tss segment (or it with 0x03 for ring 3 lvl)
    ltr ax
    ret
The kernel then copies executable code (printing a character in the top left corner and halting) to location 0x30000 like that:

Code: Select all


	uchar *ptr = 0x30000;
	ptr[0] = 0xc6;
	ptr[1] = 0x05;
	ptr[2] = 0x00;
	ptr[3] = 0x80;

	ptr[4] = 0x0b;
	ptr[5] = 0x00;
	ptr[6] = 0x72;
	ptr[7] = 0xc6;

	ptr[8] = 0x05;
	ptr[9] = 0x01;
	ptr[10] = 0x80;
	ptr[11] = 0x0b;

	ptr[12] = 0x00;
	ptr[13] = 0x72;
	ptr[14] = 0xfa;
	ptr[15] = 0xf4;

and here is the routine that switches to user mode:

Code: Select all


jmp_umode:
    cli

    push 0x2b       ; push user ss
    push 0x1000    	; push user esp

    pushfl
    pop eax				;
	or eax, 0x200		;
    and eax, 0xffffbfff	;
	push eax			; re-enable interupt after switch

    push 0x23       ; push user cs (0x23)
    push 0x00       ; push code offset

    mov ax, 0x2b	; user data segement
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    iretd


But after executing iretd I get a General Protection Fault with error code 0x000010d8.
Any clue on why I am getting a General Protection Fault and why iret is not actually switching to 0x20:0x00?

Regards
rwosdev
Member
Member
Posts: 49
Joined: Thu Apr 26, 2018 11:21 pm

Re: General Protection Fault when switching to u-mode with i

Post by rwosdev »

Your user code is actually trying to halt the system? That would at least be part of your issues, HLT is a privileged instruction. In my OS there's an idle thread running in ring 0, which is basically a HLT/JMP loop. To test user code I do a syscall to print registers in a loop
LIC
Member
Member
Posts: 44
Joined: Mon Jun 04, 2018 8:10 am
Libera.chat IRC: lic

Re: General Protection Fault when switching to u-mode with i

Post by LIC »

Hello and thank you for your reply.
I have changed the executed instructions to

Code: Select all

jmp $
and I still get a General Protection Fault.
When I set up segment 0x20 to be ring 0 code and I execute iret, everything works fine (the OS loops forever)
But when I set up segment 0x20 to be ring 3 code I still get the General Protection Fault with error 0x0000105c this time. I am not sure what this error code corresponds to...
User avatar
iansjack
Member
Member
Posts: 4706
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

Re: General Protection Fault when switching to u-mode with i

Post by iansjack »

Without disassembling your code, are you trying to move a value into ds:0xB8000?

As I see it there are two problems with this:

1. Your data segment starts at 0x30000, so you want to write to ds:0x88000.

2. Neither of those addresses is within your user data segments, so you can't write to them in user mode.

It would make your user code easier to understand if you listed it in assembler rather than a series of bytes.
LIC
Member
Member
Posts: 44
Joined: Mon Jun 04, 2018 8:10 am
Libera.chat IRC: lic

Re: General Protection Fault when switching to u-mode with i

Post by LIC »

Thank you iansjack for your reply but even by changing the executable code to

Code: Select all

jmp $
I still get the general protection fault.
User avatar
iansjack
Member
Member
Posts: 4706
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

Re: General Protection Fault when switching to u-mode with i

Post by iansjack »

I'm a bit puzzled by your error codes, as they don't appear to be valid codes for a GPF.

How do you know it's a GPF, and how are you determining the error code?
LIC
Member
Member
Posts: 44
Joined: Mon Jun 04, 2018 8:10 am
Libera.chat IRC: lic

Re: General Protection Fault when switching to u-mode with i

Post by LIC »

This is the interrupt:

Code: Select all

%macro ISR_ERRCODE 1
  	[GLOBAL isr%1]
  	isr%1:
    	cli
    	push byte %1
    	jmp isr_common_stub
%endmacro
Here is the code that handles interrupts:

Code: Select all

[EXTERN isr_handler]

;# Common ISR code
isr_common_stub:

    ;# 1. Save CPU state
	pusha

	mov ax, ds 		;# Lower 16-bits of eax = ds.
	push eax 		;# save the data segment descriptor

	mov ax, 0x10  	;# kernel data segment descriptor
	mov ds, ax
	mov es, ax
	mov fs, ax
	mov gs, ax

    ;# 2. Call C handler
	call isr_handler

    ;# 3. Restore state
	pop eax
	mov ds, ax
	mov es, ax
	mov fs, ax
	mov gs, ax
	popa

	add esp, 8 	;# Cleans up the pushed error code and pushed ISR number

	sti			;# enable interruption again

	iret 		;# pops 5 things at once: CS, EIP, EFLAGS, SS, and ESP

Code: Select all

typedef struct {
   u32 ds; 										/* Data segment selector */
   u32 edi, esi, ebp, esp, ebx, edx, ecx, eax; 	/* Pushed by pusha. */
   u32 int_no, err_code; 						/* Interrupt number and error code (if applicable) */
   u32 eip, cs, eflags, useresp, ss; 			/* Pushed by the processor automatically */
} registers_t;

Code: Select all

void isr_handler(registers_t r) {

	/* this definition should be elsewhere */
	char *exception_messages[] = {
		"Division By Zero",
		"Debug",
		"Non Maskable Interrupt",
		"Breakpoint",
		"Into Detected Overflow",
		"Out of Bounds",
		"Invalid Opcode",
		"No Coprocessor",

		"Double Fault",
		"Coprocessor Segment Overrun",
		"Bad TSS",
		"Segment Not Present",
		"Stack Fault",
		"General Protection Fault",
		"Page Fault",
		"Unknown Interrupt",

		"Coprocessor Fault",
		"Alignment Check",
		"Machine Check",
		"Reserved",
		"Reserved",
		"Reserved",
		"Reserved",
		"Reserved",

		"Reserved",
		"Reserved",
		"Reserved",
		"Reserved",
		"Reserved",
		"Reserved",
		"Reserved",
		"Reserved"
	};
	/* ========================= */

	set_char_attr(YELLOW_FORGND);
    print("received interrupt ");
    print_int(r.int_no);
    print(": ");
    print(exception_messages[r.int_no]);
    print("\n");

    // if interrupt handler is set, run it
    if(interrupt_handlers[r.int_no]) {
        interrupt_handlers[r.int_no](r);
	}

	// stay there
	while(1);
}
And here is the function that handles GPF:

Code: Select all

static void general_protection_fault(registers_t r) {

    set_char_attr(BLUE_BACKGND | WHITE_FORGND);
    print("/!\\ GENERAL PROTECTION FAULT, SYSTEM HALTED /!\\ err code = ");
	print_hex(r.err_code);

    __asm__("cli\nhlt");
    while(1);

}
And this is what Qemu shows me:
Capture d’écran 2018-06-05 à 2.00.14 PM.png
As I have read, the error code that GPF shows is the segment that caused the error, however i do not understand why the error is 0x000010d8. Is 0x000010d8 a segment index ?? I am quiet confused...

Thank you for your replies!
User avatar
iansjack
Member
Member
Posts: 4706
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

Re: General Protection Fault when switching to u-mode with i

Post by iansjack »

I'm afraid I'm not very good at following mixed C and assembler code. What I would suggest is that you single-step the code in a debugger to see exactly where it is failing and what the status of memory and registers is at that point. Also check that the GDT in memory is actually what you think it is.
nullplan
Member
Member
Posts: 1801
Joined: Wed Aug 30, 2017 8:24 am

Re: General Protection Fault when switching to u-mode with i

Post by nullplan »

Hi,

I am not terribly familiar with passing structures by value in 32-bit code. I would probably end the pushing tirade with "push esp" and change the argument of isr_handler() to a pointer. At least I know how that works. In 64-bit, passing a large structure by value is defined to be no different than passing a pointer, and it is the caller's job to pass a copy (since arguments are local variables of the callee, so changes to them should not be reflected in the caller).

You know, you can push segment registers directly ("push ds"). Also, depending on where you're headed with your OS, it might be good to keep in mind that the segment registers could be different from each other. Or if they can't be today, they might tomorrow. Linux uses that to implement thread-pointers, for example.

Also, here's a few things you can try: Actually plot all the information in the exception frame. In particular, what is the saved CS:IP, and did you expect that value? Another thing you can try is setting the trap flag (bit 8 ) in the EFLAGS image in the iret frame you are preparing. That way you should get debug exceptions (remember to set the resume flag (bit 16) before returning from those!) after each instruction, and can thus trace out what is happening (keep plotting those CS:IP values...)

HTH,
Markus
Carpe diem!
LIC
Member
Member
Posts: 44
Joined: Mon Jun 04, 2018 8:10 am
Libera.chat IRC: lic

Re: General Protection Fault when switching to u-mode with i

Post by LIC »

Ok, thank you for all your replies
LIC
Member
Member
Posts: 44
Joined: Mon Jun 04, 2018 8:10 am
Libera.chat IRC: lic

[SOLVED]General Protection Fault when switching to u-mode wi

Post by LIC »

The problem was with the pushfl instruction that was treated as a label. I changed "pushfl" to "pushf" and everything seems to be working fine!

Code: Select all

jmp_umode:
    cli

    push 0x2b       ; push user ss
    push 0x1000    	; push user esp

    pushf
    pop eax				;
	or eax, 0x200		;
    and eax, 0xffffbfff	;
	push eax			; re-enable interupt after switch

    push 0x23       ; push user cs (0x23)
    push 0x00       ; push code offset

    mov ax, 0x2b	; user data segement
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    iretd
Post Reply