[Closed]How to prepare and enter the first user program?

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
stskyblade
Posts: 12
Joined: Sun Jul 28, 2024 9:56 pm

[Closed]How to prepare and enter the first user program?

Post by stskyblade »

I'm developing a simple OS kernel. Now, I want my kernel can "load and execute" my userspace programs.
Github: https://github.com/stskyblade/StarOS
At branch: dev-process
I have a userspace programs "testadd". (https://github.com/stskyblade/StarOS/bl ... estadd.cpp)
The code loads userspace program:
https://github.com/stskyblade/StarOS/bl ... ss.cpp#L10

What I want:
I want my kernel to execute program "testadd". And I can observe the execution of "testadd" by GDB debugging, like EIP and general registers changing.

What I encounter:
It triggers a Interrupt 9(Coprocessor Segment Overrun) when I use an "iret" instruction to switch control flow to userspace programs. I think this is because I didn't prepare the environment of userspace program right.

Simplely describe the logic in function "execv":
- read the executable file to memory in kernel
- prepare a new paging directory for the program
- parse ELF structure of the file: add memory mappings, copy .text section
- prepare process LDT, one code segment, one data segment
- set a TSS(Task State Segment), and set fields
- add a TSS(Task State Segment) descriptor in GDT
- set NT(nested task) bit
- to simulate the stack content when a userspace program is interrupted, I push SS, ESP, EFLAGS, CS, EIP to the kernel stack
- use IRET to return to the process, https://github.com/stskyblade/StarOS/bl ... s.cpp#L233
- IRET triggers Interrupt 9 immediately

Background:
My kernel executes in Protected and Paging-enabled environment in ring 0 I think.
I want my userspace program to run in ring 3.

Memory mapping in kernel:
Segment selectors all points to 0x0, so virtual address is identical to linear address.
Only used memory pages is mapped to the same physical address in kernel page directory, so linear address is same as physical address.

Memory mapping in process space:
Only .text section is mapped to the virtual address according to the ELF file. Other address space is not been used yet.
Last edited by stskyblade on Thu Dec 19, 2024 5:27 am, edited 1 time in total.
MichaelPetch
Member
Member
Posts: 797
Joined: Fri Aug 26, 2016 1:41 pm
Libera.chat IRC: mpetch

Re: How to prepare and enter the first user program?

Post by MichaelPetch »

I can't build because of a missing src/vectors.pl file. But looking at your code I have to ask if there is a reason you are using hardware task switching through task gates rather than rolling your own task switching? Can you fix you project so it can be cloned and others can build it?

With that being said your use of inline assembly could be a problem. An example: https://github.com/stskyblade/StarOS/bl ... s.cpp#L221

Code: Select all

    uint32_t data = 0;
    data = tss->ss;
    uint32_t data = 0;
    data = tss->ss;
    __asm__ __volatile__("push %0\n\t"::"r"(data));
    data = tss->esp;
    __asm__ __volatile__("push %0\n\t"::"r"(data));
    data = tss->eflags;
    __asm__ __volatile__("push %0\n\t"::"r"(data));
    data = tss->cs;
    __asm__ __volatile__("push %0\n\t"::"r"(data));
    data = tss->eip;
    __asm__ __volatile__("push %0\n\t"::"r"(data));

    KERNEL_TSS->back_link = selector; // FIXME: bug
    __asm__ __volatile__("debug_process:\n\t" ::);
    __asm__ __volatile__("iret\n\t" ::);
In 32-bit code can't reliably do a `push` alone by itself in inline assembly like this. The compiler will be unaware that the stack layout has changed and it could alter the behaviour of the code in ways you don't expect. Have no idea if this is even related to your issue.
stskyblade
Posts: 12
Joined: Sun Jul 28, 2024 9:56 pm

Re: How to prepare and enter the first user program?

Post by stskyblade »

Be very very careful: when build the disk image, it needs accessing to loop device. Don't let it destroy you computer.

Code: Select all

 97 # make a disk image
 98 # partition 200MB
 99 # have two text file
100 set(USB_DEVICE /dev/sdb)
101 set(MOUNT_POINT /mnt/fat32_device)
102 set(FS_IMG /tmp/fs.img)
103 set(LOOP_DEVICE /dev/loop0)
104 set(USER_PROGRAMS testadd)
105 add_custom_command(
106   OUTPUT ${PROJECT_BINARY_DIR}/fs.img
107   DEPENDS ${KERNEL_FILE} ${USER_PROGRAMS}
108   WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
109 
110   COMMAND sudo dd if=/dev/zero of=${FS_IMG} count=512000                                                                                                                                 
111   COMMAND sudo losetup ${LOOP_DEVICE} ${FS_IMG}
112   COMMAND echo "Partition..."
113   COMMAND sudo sfdisk -q ${LOOP_DEVICE} < ${PROJECT_SOURCE_DIR}/partition_layout.dump
114   COMMAND sudo partprobe ${LOOP_DEVICE}
115   COMMAND sudo mkfs.fat -F 32 ${LOOP_DEVICE}p1
116   COMMAND sudo mkdir -p ${MOUNT_POINT}
117   COMMAND sudo mount ${LOOP_DEVICE}p1 ${MOUNT_POINT}
118   COMMAND echo "Copy files..."
119   COMMAND sudo cp ${PROJECT_BINARY_DIR}/${KERNEL_FILE} ${MOUNT_POINT}
120   COMMAND sudo cp ${PROJECT_BINARY_DIR}/${USER_PROGRAMS} ${MOUNT_POINT}
121   COMMAND sudo cp ${PROJECT_SOURCE_DIR}/root_dir/hello.txt ${MOUNT_POINT}
122   COMMAND sudo cp ${PROJECT_SOURCE_DIR}/root_dir/world.txt ${MOUNT_POINT}
123   COMMAND sudo cp ${PROJECT_SOURCE_DIR}/root_dir/large_hello.txt ${MOUNT_POINT}
124   COMMAND sudo umount ${LOOP_DEVICE}p1
125   COMMAND sudo rm -rf ${MOUNT_POINT}
126   COMMAND sudo losetup -d ${LOOP_DEVICE}
127   # directly manipulate image on project directory is extreamly slow
128   COMMAND sudo cp /tmp/fs.img ${PROJECT_BINARY_DIR}/fs.img
129   COMMAND echo "Done."
130 )
I have updated my Readme.md file, with some building instructions. I now use CMake build system. Forget about the Makefile.

Code: Select all

git clone -b dev-process https://github.com/stskyblade/StarOS.git
cd StarOS
mkdir build
cd build
cmake ..
cmake --build . --target qemu
I don't use src/vectors.pl. I rewrite it with python, in file `codegen_interrupt_handlers.py`. Code generation is done in CMakeLists.txt.

Why do I choose hardware task switching method?
Because I didn't add memory mappings for kernel code in process address space. I need to find a way to change CS:EIP and PDBR(paging directory, CR3) simultaneously, as well as change privilege level to ring 3. I don't know how to do it.
I read INTEL 80386 PROGRAMMER'S REFERENCE MANUAL, and think this is the only way to do it. Tell me if I'm wrong.
I'm not an expertise.
Octocontrabass
Member
Member
Posts: 5568
Joined: Mon Mar 25, 2013 7:01 pm

Re: How to prepare and enter the first user program?

Post by Octocontrabass »

stskyblade wrote: Wed Dec 18, 2024 5:31 amIt triggers a Interrupt 9(Coprocessor Segment Overrun)
Unless you're running your OS on a 386 with a coprocessor installed, you can't have a coprocessor segment overrun. It sounds like you enabled maskable interrupts without initializing the interrupt controllers and received IRQ1.
stskyblade wrote: Wed Dec 18, 2024 5:31 am- prepare process LDT, one code segment, one data segment
Do you really need a LDT? Why can't you use the GDT?
stskyblade wrote: Wed Dec 18, 2024 5:31 am- set NT(nested task) bit
Hardware task switching is almost as obsolete as the coprocessor segment overrun exception. It's slow, it doesn't work in 64-bit mode, and it doesn't exist in non-x86 architectures. If you want to change CR3 every time you switch between kernel mode and user mode, it's better to always map a small piece of kernel code.
nullplan
Member
Member
Posts: 1790
Joined: Wed Aug 30, 2017 8:24 am

Re: How to prepare and enter the first user program?

Post by nullplan »

stskyblade wrote: Wed Dec 18, 2024 6:07 pm Why do I choose hardware task switching method?
Because I didn't add memory mappings for kernel code in process address space. I need to find a way to change CS:EIP and PDBR(paging directory, CR3) simultaneously, as well as change privilege level to ring 3. I don't know how to do it.
I read INTEL 80386 PROGRAMMER'S REFERENCE MANUAL, and think this is the only way to do it. Tell me if I'm wrong.
Honestly, hardware task switching was always too complicated for me to figure out. It only saves the general purpose registers, which I can do in software anyway, and if I do it in software, then I can save the FPU registers (and other extended registers) at the same time, which avoids problems on multi-CPU systems. This way I can migrate any task to another CPU as I see fit.

I would not use the 80386 programmer's manual. Things have changed in the last 30 years. Go and download the current Intel SDM or AMD APM. They contain the same information, it's just a green-vs-blue thing, mostly. In particular the initialization of paging has changed and no longer works the way it was described in that manual (which is also why SCO Unix for the 386 doesn't work on a 486 or higher CPU). The normal way to change address spaces is to do it in a place that is mapped to the same physical address in both the old and new address spaces. This is one of the reasons why all modern operating systems map the kernel into all address spaces. Once you have one place that stays the same when a new CR3 is loaded, it is no longer necessary to change CR3 and CS:EIP at the same time, and the need for hardware task switching (an arcane mechanism I wouldn't trust any emulator to implement right) just falls away.
Carpe diem!
stskyblade
Posts: 12
Joined: Sun Jul 28, 2024 9:56 pm

Re: How to prepare and enter the first user program?

Post by stskyblade »

According to your suggestions, I realized that using hardware context switching is not a good choice. I decided to use the software context switching method. Of course, I need to map kernel code memory in every process's address space.

In this case, this problem no longer exists. I will close this post.

I sincerely thank you for your help. This is a really difficult question for me, and it has stuck me for a long time.
Post Reply