Let’s make your own operating system (#week_9_User_Mode)

Nimantha Gayan
6 min readSep 27, 2021

--

Hello, friends! This is the 9th week of my article series about creating your own operating system as a test. If you’ve read any of my earlier articles in this series, I believe it will help you grasp this section. In last week we talked about Page Frame Allocation. In this week I’m going to talk about User Mode. you can go through my last week article by this link.

User Mode

User mode is almost within reach now, it only takes a few steps to get there. While these steps seem as simple as presented in this chapter, they can be difficult to implement because there are many places where small errors cause hard-to-find errors.

Segments for User Mode

To enable user mode we need to add two more segments to the GDT. They are very similar to the kernel segments we added when we set up the GDT in the chapter about segmentation.

The difference is in the DPL, which now allows the code to run in PL3. Segments can still be used to address the entire address space, just using these segments for user mode code does not protect the kernel. For that we need pagination.

Setting Up For User Mode

There are a few things every user mode process needs.

  • Page frames for code, data and stack. At the moment it suffices to allocate one page frame for the stack and enough page frames to fit the program’s code. Don’t worry about setting up a stack that can be grow and shrink at this point in time, focus on getting a basic implementation work first.
  • The binary from the GRUB module has to be copied to the page frames used for the programs code.
  • A page directory and page tables are needed to map the page frames described above into memory. At least two page tables are needed, because the code and data should be mapped in at 0x00000000 and increasing, and the stack should start just below the kernel, at 0xBFFFFFFB, growing towards lower addresses. The U/S flag has to be set to allow PL3 access.

It might be convenient to store this information in a struct representing a process. This process struct can be dynamically allocated with the kernel’s malloc function.

Entering User Mode

The only way to execute code with a lower privilege level than the current privilege level (CPL) is to execute an iret or lret instruction - interrupt return or long return, respectively.

To enter user mode we set up the stack as if the processor had raised an inter-privilege level interrupt. The stack should look like the following.

[esp + 16]  ss   ; the stack segment selector we want for user mode
[esp + 12] esp ; the user mode stack pointer
[esp + 8] eflags ; the control flags we want to use in user mode
[esp + 4] cs ; the code segment selector
[esp + 0] eip ; the instruction pointer of user mode code to execute

The iret instructions then read these qualities from the stack and fill in the appropriate registers. Before executing iret, we must change to the catalog of pages that we configure for the measure of the client mode. Remember that the part must be programmed to continue executing the part code after exchanging the PDT client PDT (which only leads below 0xC0000000) when the change is made. Remember that the actual position of the PDT must be used when configuring the cr3 register.

The registry eflags contain a number of different banners, which are specified in section 2.3 of the Intel manual [33]. The Hinder Empower (IF) banner is generally of importance to us. The code collection guide cannot be used to empower intruders at Profit Level 3. In the event that the obstruction is disabled when entering the client mode, the obstruction cannot be activated once the lock is entered. client mode. Setting the IF banner in the eflags passage on the stack will be a hindrance in client mode, as the Get Together code statements set the record eflags to the compare value on the stack.

For the present, we ought to have intrudes on debilitated, as it requires somewhat more work to get between advantage level hinders to work appropriately (see the part “Framework calls”).
The worth eip on the stack should highlight the passage point for the client code — 0x00000000 for our situation. The worth esp on the stack ought to be the place where the stack begins — 0xBFFFFFFB (0xC0000000–4).
The qualities cs and ss on the stack ought to be the section selectors for the client code and client information fragments, separately. As we found in the division section, the most minimal two pieces of a portion selector is the RPL — the Requested Privilege Level. When utilizing iret to enter PL3, the RPL of cs and ss ought to be 0x3. The accompanying code shows a model

The register ds, and the other data segment registers, should be set to the same segment selector as ss. They can be set the ordinary way, with the mov assembly code instruction.

We are now ready to execute iret. If everything has been set up right, we should now have a kernel that can enter user mode.

Using C for User Mode Programs

When using C as a programming language for user-mode programs, it is important to consider the structure of the file that will be the result of compilation.

The reason we can use ELF as the file format for the kernel executable is because GRUB knows how to parse and interpret the ELF file format. If we implemented an ELF parser, we could also compile the user-mode programs into ELF binaries. We leave this up to the reader as an exercise.

One thing we can do to make it easier to develop user mode programs is to allow the programs to be written in C, but compile them to flat binaries instead of ELF binaries. In C the layout of the generated code is more unpredictable and the entry point, main, might not be at offset 0 in the binary. One common way to work around this is to add a few assembly code lines placed at offset 0 which calls main.

If this code is saved in a file called start.s, then the following code show an example of a linker script that places these instructions first in executable (remember that start.s gets compiled to start.o).

Note: *(.text) will not include the .text section of start.o again.

With this script we can write programs in C or assembler (or any other language that compiles to object files linkable with ld), and it is easy to load and map for the kernel (.rodata will be mapped in as writeable, though).

When we compile user programs we want the following GCC flags

-m32 -nostdlib -nostdinc -fno-builtin -fno-stack-protector -nostartfiles
-nodefaultlibs

For linking, the followings flags should be used:

-T link.ld -melf_i386  # emulate 32 bits ELF, the binary output is specified
# in the linker script

A C Library

It might now be interesting to start thinking about writing a small “standard library” for your programs. Some of the functionality requires system calls to work, but some, such as the functions in string.h, does not.

So this is the end of this week article see you in next article!!!

Thank you for reading

--

--

Nimantha Gayan
Nimantha Gayan

Written by Nimantha Gayan

Software Engineering , University Of Kelaniya

No responses yet