Page 1 of 2

User mode syscalls problem

Posted: Mon Oct 03, 2022 9:20 am
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;
}

Re: User mode syscalls problem

Posted: Mon Oct 03, 2022 1:11 pm
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.

Re: User mode syscalls problem

Posted: Mon Oct 03, 2022 2:28 pm
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?

Re: User mode syscalls problem

Posted: Mon Oct 03, 2022 3:07 pm
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?

Re: User mode syscalls problem

Posted: Mon Oct 03, 2022 3:12 pm
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))

Re: User mode syscalls problem

Posted: Mon Oct 03, 2022 3:40 pm
by Octocontrabass
Inline assembly isn't allowed to modify the stack pointer.

Re: User mode syscalls problem

Posted: Mon Oct 03, 2022 4:14 pm
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.

Re: User mode syscalls problem

Posted: Tue Oct 04, 2022 3:41 am
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?

Re: User mode syscalls problem

Posted: Tue Oct 04, 2022 4:18 am
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

Re: User mode syscalls problem

Posted: Tue Oct 04, 2022 4:30 am
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;

Re: User mode syscalls problem

Posted: Tue Oct 04, 2022 6:08 am
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?

Re: User mode syscalls problem

Posted: Tue Oct 04, 2022 12:45 pm
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.

Re: User mode syscalls problem

Posted: Tue Oct 04, 2022 12:56 pm
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.

Re: User mode syscalls problem

Posted: Tue Oct 04, 2022 12:56 pm
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.

Re: User mode syscalls problem

Posted: Tue Oct 04, 2022 1:01 pm
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.