User mode syscalls problem

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.
WinExperements
Member
Member
Posts: 97
Joined: Thu Jul 14, 2022 9:45 am
Contact:

User mode syscalls problem

Post by WinExperements »

Hello! I have some problems with syscalls and user mode, when user space program calls syscall, then the arguments that passed to the handlers seems a little incorrect and when the function returns i got #PF with random address. Yeah i know that it's because the address is the last that pushed into it, but why the arguments are changes?
Here some code of multitasking,IRQ and syscalls:
process scheduling:

Code: Select all

if (disableScheduler) return;
    struct process *kill = process_findByStatus(PROCESS_KILLING);
    if (kill != NULL) {
        __process_destroy(kill);
        if (runningTask == kill) {
            runningTask = NULL;
        }
    }
    if (runningTask != NULL) {
        // Simply select new task
        arch_saveRegs(stack,runningTask->esp);
        runningTask = process_findNextByStatus(PROCESS_RUNNING,runningTask->lAddr);
        runningTask->quota = 0;
    } else {
        // first switch
        runningTask = process_findByStatus(PROCESS_RUNNING);
    }
    if (runningTask == NULL) {
        // okay switch to idle
        runningTask = idle;
    }
    // change the TSS and VMM
    tss_set_stack(0x10,runningTask->kernelESP);
    if (!pmml_isPageAllocated((void *)runningTask->dir)) {
        printf("proc: AAA\n");
        return;
    }
    vmm_switch((int *)runningTask->dir);
	arch_switchContext(runningTask->esp);
}
IRQ handler:

Code: Select all

push eax
    push ecx
    push edx
    push ebx
    push ebp
    push esi
    push edi
    mov ax,ds
    push eax
    mov ax, 0x10  ; load the kernel data segment descriptor
    mov ds, eax
    mov es, eax
    mov fs, eax
    mov gs, eax
    push esp
    call irq_handler
    mov esp,eax
    jmp irq_handler_exit ; Exit from this code using jmp
[GLOBAL irq_handler_exit]
irq_handler_exit:
    pop ebx
    mov ds, ebx
    mov es, ebx
    mov fs, ebx
    mov gs, ebx
    pop edi
    pop esi
    pop ebp
    pop ebx
    pop edx
    pop ecx
    pop eax
    add esp, 8     ; Cleans up the pushed error code and pushed ISR number
    iret           ; pops 5 things at once: CS, EIP, EFLAGS, SS, and ESP
And syscall handler:

Code: Select all

registers_t *syscall_handler(registers_t *regs) {
	if (regs->eax > 23) {
		printf("No such syscall number: %d\n",regs->eax);
		process_kill(process_getCurrentPID());
		process_yield();
	} else {
		int (*handler)(int,int,int,int,int) = (int (*)(int,int,int,int,int))syscalls[regs->eax];
		regs->eax = handler(regs->edx,regs->ecx,regs->ebx,regs->edi,regs->esi);
	}
	return regs;
}
Octocontrabass
Member
Member
Posts: 5563
Joined: Mon Mar 25, 2013 7:01 pm

Re: User mode syscalls problem

Post by Octocontrabass »

WinExperements wrote:when user space program calls syscall, then the arguments that passed to the handlers seems a little incorrect
Incorrect how? I don't see anything that would obviously corrupt your syscall arguments, although your design decisions are very strange. (Why have separate "ISR" and "IRQ" code? Why return anything from the C portion of your ISR when you never use it to switch stacks? Why are syscalls in the "IRQ" category?)
WinExperements wrote:and when the function returns i got #PF with random address.
Probably caused by the same thing that's corrupting your syscall arguments.

I'd start by running QEMU with "-d int" and see if the register dumps from the syscall and the subsequent #PF reveal anything interesting.
WinExperements
Member
Member
Posts: 97
Joined: Thu Jul 14, 2022 9:45 am
Contact:

Re: User mode syscalls problem

Post by WinExperements »

Octocontrabass wrote:I'd start by running QEMU with "-d int" and see if the register dumps from the syscall and the subsequent #PF reveal anything interesting.
Yeah, i found that it's begin rewriten after the scheduler changes task, this is my functions for saving and restoring task stack

Code: Select all

void arch_saveRegs(void *from,void *to) {
	registers_t *f = (registers_t *)from;
	registers_t *tt = (registers_t *)to;
	tt->eax = f->eax;
	tt->ebx = f->ebx;
	tt->ecx = f->ecx;
	tt->edx = f->edx;
	tt->ebp = f->ebp;
	tt->esi = f->esi;
	tt->edi = f->edi;
	tt->eip = f->eip;
}

Code: Select all

 registers_t *regs = (registers_t *)stack;
    PUSH_ASM(regs->ss);
    PUSH_ASM(regs->useresp);
    PUSH_ASM(regs->eflags);
    PUSH_ASM(regs->cs);
    PUSH_ASM(regs->eip);
    PUSH_ASM(0);
    PUSH_ASM(0);
    PUSH_ASM(regs->eax);
    PUSH_ASM(regs->ecx);
    PUSH_ASM(regs->edx);
    PUSH_ASM(regs->ebx);
    PUSH_ASM(regs->ebp);
    PUSH_ASM(regs->esi);
    PUSH_ASM(regs->edi);
    PUSH_ASM(regs->ds);
    asm volatile("jmp irq_handler_exit");
It's pushed all the registers only for test, i delete this soon.
Any reasons?
User avatar
AndrewAPrice
Member
Member
Posts: 2300
Joined: Mon Jun 05, 2006 11:00 pm
Location: USA (and Australia)

Re: User mode syscalls problem

Post by AndrewAPrice »

WinExperements wrote:

Code: Select all

 registers_t *regs = (registers_t *)stack;
    PUSH_ASM(regs->ss);
    PUSH_ASM(regs->useresp);
    PUSH_ASM(regs->eflags);
    PUSH_ASM(regs->cs);
    PUSH_ASM(regs->eip);
    PUSH_ASM(0);
    PUSH_ASM(0);
    PUSH_ASM(regs->eax);
    PUSH_ASM(regs->ecx);
    PUSH_ASM(regs->edx);
    PUSH_ASM(regs->ebx);
    PUSH_ASM(regs->ebp);
    PUSH_ASM(regs->esi);
    PUSH_ASM(regs->edi);
    PUSH_ASM(regs->ds);
    asm volatile("jmp irq_handler_exit");
I don't think this will work as you intend. How is PUSH_ASM defined?
My OS is Perception.
WinExperements
Member
Member
Posts: 97
Joined: Thu Jul 14, 2022 9:45 am
Contact:

Re: User mode syscalls problem

Post by WinExperements »

AndrewAPrice wrote:
WinExperements wrote:

Code: Select all

 registers_t *regs = (registers_t *)stack;
    PUSH_ASM(regs->ss);
    PUSH_ASM(regs->useresp);
    PUSH_ASM(regs->eflags);
    PUSH_ASM(regs->cs);
    PUSH_ASM(regs->eip);
    PUSH_ASM(0);
    PUSH_ASM(0);
    PUSH_ASM(regs->eax);
    PUSH_ASM(regs->ecx);
    PUSH_ASM(regs->edx);
    PUSH_ASM(regs->ebx);
    PUSH_ASM(regs->ebp);
    PUSH_ASM(regs->esi);
    PUSH_ASM(regs->edi);
    PUSH_ASM(regs->ds);
    asm volatile("jmp irq_handler_exit");
I don't think this will work as you intend. How is PUSH_ASM defined?

Code: Select all

#define PUSH_ASM(vol) __asm__ __volatile__("pushl %%eax" : : "a" (vol))
Octocontrabass
Member
Member
Posts: 5563
Joined: Mon Mar 25, 2013 7:01 pm

Re: User mode syscalls problem

Post by Octocontrabass »

Inline assembly isn't allowed to modify the stack pointer.
devc1
Member
Member
Posts: 439
Joined: Fri Feb 11, 2022 4:55 am
Location: behind the keyboard

Re: User mode syscalls problem

Post by devc1 »

Try disassembling the C functions, it is probably that GCC is pushing some registers into the stack and you are not popping the correct amount of values before returning from the handler.
Also Try to return in C code instead of jump and see if it proves what I said before.
Can't you just push the values in assembly ?

It is never recommended to jump manually within a C function.
WinExperements
Member
Member
Posts: 97
Joined: Thu Jul 14, 2022 9:45 am
Contact:

Re: User mode syscalls problem

Post by WinExperements »

devc1 wrote:It is never recommended to jump manually within a C function.
Yeah i rewriten it in assembly, but now i have #UD in bochs, and #GP in QEMU.
I writen it like this:

Code: Select all

[extern irq_handler_exit]
[global arch_switchContext]
arch_switchContext:
	mov esi,[esp+4]
	push dword [esi+56]
	push dword [esi+52]
	push dword [esi+48]
	push dword [esi+44]
	push dword [esi+40]
	push dword 0
	push dword 0
	push dword [esi+28]
	push dword [esi+24]
	push dword [esi+20]
	push dword [esi+16]
	push dword [esi+12]
	push dword [esi+8]
	push dword [esi+4]
	push dword [esi+0]
	jmp irq_handler_exit
Maybe the problem is context saving?
devc1
Member
Member
Posts: 439
Joined: Fri Feb 11, 2022 4:55 am
Location: behind the keyboard

Re: User mode syscalls problem

Post by devc1 »

Are you calling arch_switchContext from C ?
it is never recommended to jump manually within C function and within a function called from C. Basicly the Interrupt stack frame isn't well setup, if you call exit_handler from C it will push the return address.

Here is my recommended handler :
- Just create a normal C function without any special attributes.
- Create the handler (or wrapper) in assembly, it will just push registers, call the C function, pop them then iret.

Example :

Code: Select all

PageFaultISR:
       pusha
       call PageFaultHandler
       popa
       iretd
devc1
Member
Member
Posts: 439
Joined: Fri Feb 11, 2022 4:55 am
Location: behind the keyboard

Re: User mode syscalls problem

Post by devc1 »

Here is an explanation of function calls and interrupts.

- The call instruction is actually a task switch and all the Flags are affected, it sets the TS Flag (Task switched) and pushes the return address (EIP) in the stack.
- the ret instruction pops the return address from the stack pointed to by EIP and jumps to it, it has an optional 16 bit argument which contains the number of additional bytes to pop of the stack.

Code: Select all

ret ; Pops EIP and jumps
ret 32 ; Pops EIP along with 32 bytes from the stack and jumps
There is no pushed ISR number, and there is no meaning of pushed the Stack Pointer itself.


- When an interrupt occurs, a stack frame is pushed to the stack, the iret instruction pops this stack frame to return to the previous task. An invalid stack frame will cause these #UD #PF #GPF....

The thing is that the CPU is jumping to an invalid address because you're not preserving the value of EIP.

Format of a stack frame :
On a task scheduler, we create our own stack frame in order to switch to that task.

Code: Select all

    struct {
         (Optionnal) UINT64 StatusCode; // Some interrupts do not push a status code
         UINT64        InstructionPointer; 
         UINT64        CodeSegment; 
         UINT64        CpuRflags; 
         UINT64        StackPointer; 
         UINT64        StackSegment;
     } IntStack;
WinExperements
Member
Member
Posts: 97
Joined: Thu Jul 14, 2022 9:45 am
Contact:

Re: User mode syscalls problem

Post by WinExperements »

My irq handler_exit called from assembly code, but i rewriten the timer interrupt handler, and i now i have only #GF with invalid segment.
My question is how i can pass arguments to arch_restoreContext correctly? Because my code seems strange
This is my code:

Code: Select all

scheduler_irq:
    push 0
    push 0
    push eax
    push ecx
    push edx
    push ebx
    push ebp
    push esi
    push edi
    mov ax,ds
    push eax
    mov ax, 0x10  ; load the kernel data segment descriptor
    mov ds, eax
    mov es, eax
    mov fs, eax
    mov gs, eax
    push esp
    call process_schedule
    ; Push only for test, our ESP from runningTask
    mov esi,[runningTask+0]
    push dword [esi+56]
	 push dword [esi+52]
	 push dword [esi+48]
	 push dword [esi+44]
	 push dword [esi+40]
	 push esi
    jmp arch_switchContext
EDIT: How i can use trap frame to switch thread context?
Octocontrabass
Member
Member
Posts: 5563
Joined: Mon Mar 25, 2013 7:01 pm

Re: User mode syscalls problem

Post by Octocontrabass »

You're making this a lot more complicated than it needs to be.

You can save and restore a thread's context by switching stacks. The function you call to do that might look like this:

Code: Select all

; void switchStack( unsigned * oldesp, unsigned newesp )

switchStack:
    push ebx
    push ebp
    push esi
    push edi
    mov eax, [esp+20]
    mov [eax], esp
    mov esp, [esp+24]
    pop edi
    pop esi
    pop ebp
    pop ebx
    ret
This code doesn't include updating the TSS and CR3 - you can add that code to this function, or do it separately before calling this function.

If a thread has ring 3 context, the ring 3 context will be at the bottom of the stack (at the highest address). Threads don't need to have a ring 3 context - you can have threads that run entirely in ring 0, and this function will still save and restore their context.
WinExperements
Member
Member
Posts: 97
Joined: Thu Jul 14, 2022 9:45 am
Contact:

Re: User mode syscalls problem

Post by WinExperements »

Octocontrabass wrote:If a thread has ring 3 context, the ring 3 context will be at the bottom of the stack (at the highest address). Threads don't need to have a ring 3 context - you can have threads that run entirely in ring 0, and this function will still save and restore their context.
Yeah i see'd the Brendan's multitasking tutorial and even used that before the re-design.
Your example can save and restore tasks in ring 3? This must called by the interrupt handler or the scheduler method?
I ask this questions to understand more about the context switch with this design.
devc1
Member
Member
Posts: 439
Joined: Fri Feb 11, 2022 4:55 am
Location: behind the keyboard

Re: User mode syscalls problem

Post by devc1 »

WinExperiments wrote:How i can use trap frame to switch thread context?
You see the structure I mentionned before ? An IRQ does not push a status code.

You will just do this :

Context switching to the new thread

Code: Select all

push (Thread_EIP)
push (Thread_CS)
push (Thread_Eflags)
push (Thread_ESP)
push (Thread_SS)
iret
Just for your information : you must save thread's EIP,CS,EFLAGS,ESP,SS from the stack frame, they're pushed by the CPU because their values are changed when passing control to the handler.

I was also lost when trying to create a scheduler, reading the intel manual is what helped me.
devc1
Member
Member
Posts: 439
Joined: Fri Feb 11, 2022 4:55 am
Location: behind the keyboard

Re: User mode syscalls problem

Post by devc1 »

I am talking to you as you're creating a scheduler.
You're trying to create an interrupt for system calls right ? So you don't need to make a stack frame manually.
Post Reply