In the previous article Linux from Scratch 10: Triple jump process – from the bootloader to the operating system, to the application, because did not introduce the concept of privilege level, user applications and operating systems work in the same privilege level, so you can directly through the [segment selector: offset] way, to invoke the operating system functions in the code segment, as shown below:

The orange part of the user program header indicates the two system functions provided by the operating system, in which segment descriptor of the operating system they are located, and at what offset address.

Once the privilege level is introduced, the above invocation will not work.

Because the privileges of user programs must be lower than those of the operating system, the operating system prevents user programs from jumping into the segment selector and offset address of a function even if the user program knows it.

For example, the CPL and RPL of the application program are 3, but the DPL of the operating system function segment is 0, which cannot pass the privilege check.

As those of you who read the previous article know, setting the type. C flag to 1 in the descriptor of the object snippet means that it is a compliance (or consistency) snippet that allows low-privileged user programs to call.

In addition to this method, the processor provides another, more “formal” way to move lower-privileged code to higher-privileged code: call gates.

In this article, we will learn the mechanism of calling a door, along with all the door descriptors.

Door descriptor

A door is a passage. Through this channel, you can enter another code snippet for execution.

In x86, there are the following gates:

Call gate: used to transfer lower-privileged code to higher-privileged code;

Task gate: used for scheduling between different tasks;

Interrupt gate: used to execute interrupt handlers asynchronously;

Trap door: Also used to execute interrupt handlers, except that the interrupt is generated internally by the processor;

Gate descriptors are essentially the same as the previous section descriptors, which are used to describe a piece of code, but with an added layer of indirection.

Here is the structure of the four gate descriptors (32-bit system) :

From the structure of the four gate descriptors above, we can see that they do not directly record the starting address and boundary of the target code segment, but record the selectors of the target code segment.

In other words, first find the code segment selector through the gate descriptor, then use this selector to find the real target code segment descriptor in GDT, and finally find the target code segment starting address and boundary, attribute and other information, namely the following structure:

So these doors add a layer of indirection.

This layer of indirection provides many benefits for the operating system.

First, for interrupt handling, putting all interrupt descriptors in one table decouples the addresses of interrupt handlers.

Second, gates can be used to provide more flexible control over privilege levels for more complex operations that perform code segment transfers.

About TSS selectors in mission gates:

  1. The so-called task gate can be simply understood as being used for task switching.

  2. A TSS section is a snapshot of the context information of a task.

  3. Whenever the processor finds that the selector child points to a descriptor that is a task gate (via the TYPE field), it performs a task switch:

A. Save the context in the current CPU to the TSS segment of the current task;

B. Load the context content of the TSS section pointed to in the TSS selector into the CPU register, so as to realize the task switch.

Invoke the gate privilege checking rule

As you can see from the name of the invocation gate, it serves system calls.

Let’s look at its descriptor structure again:

Number of arguments: How many arguments the caller passes to the object code (through the stack space);

DPL: represents the privilege level of the calling gate itself;

Target snippet selector: The selector of the final call to the target snippet, which is needed to find the base address of the target snippet in the GDT;

Offset: the number of bytes offset by the calling code from the beginning address of the target code segment;

From these fields, this is tailor-made for calling higher-privileged OS code from lower-privileged user code, as long as the processor lets the user program off the hook for privileges.

In fact, this is true: when a user requests a gate, the operating system does the following privilege checks:

  1. The current privilege level CPL (user program) and request privilege level RPL must be [higher than or equal to] the DPL in the calling gate;

That is, in numerical terms: CPL <= DPL, RPL <= DPL. (Note: this is the DPL in the call gate descriptor)

  1. The current privilege level CPL(user program) must be [lower than or equal to] DPL in the target code snippet;

That is, numerically: CPL >= DPL in the target snippet descriptor.

As can be seen from the above rule, even though the target code segment only allows the same or lower privileged code to enter through the call gate, it also verifies the previous statement that the higher privileged code does not actively migrate to the lower privileged code.

If the privilege check is passed, does the current privilege CPL change after entering the target code snippet?

This depends on the value of the C flag bit in the TYPE field of the target snippet descriptor:

Type. C = 1: CPL remains unchanged and is still privilege level 3 in user programs;

Type. C = 0: CPL changes to the privilege level of the target code segment;

Call the gate usage procedure

Install call gate

The setup is to construct a call gate descriptor in the GDT with its target snippet selector pointing to the real snippet.

Assume: The following diagram is the state before the call gate is installed:

The operating system provides two system functions to be called by user programs in separate code segments (there is a code segment descriptor in the GDT).

Then add a gate descriptor (index = 8) in GDT, and the index number in the “target code segment selector” in the descriptor is equal to 8:

Note: According to the privilege checking rules mentioned above, the DPL of the call gate descriptor needs to be set to 3 (the same as the CPL of the user program) in order for the user program to enter the call gate correctly.

Tell the user program the selector of the call gate

As usual, the operating system can fill in the selector for the calls and the offset address of the function at the convention location in the header of the user program:

The value of the selector is 0x0043(binary: 0000_0000_0100_0011) :

RPL = 3;

Look it up in GDT;

Index = 8;

A user program enters a system function by calling a gate

When a user program calls a system function, the handler starts checking the privilege levels of these three parties:

  1. CPL = 3, RPL = 3;

  2. DPL of the call gate itself = 3;

  3. DPL = 0 in the descriptor (index = 7) pointed to by the object snippet selector in the call gate;

The value of these privilege levels meets the requirement of the privilege level rule of the calling gate and is then executed in the code where the system function resides.

The stack of switching

X86 processors require that the current privilege level CPL be the same as the DPL of the target stack segment.

Therefore, after the user program enters the system functions in the operating system:

1. If the privilege level CPL has not changed

The system function is executed using the same stack space as the user program.

If the user program passes parameters through the stack, the system function can retrieve those parameters directly in the same stack space.

2. If the privilege level CPL changes

When system functions are executed, they need to be switched to the user program’s stack space at the level of 0 privilege (which the operating system prepares in advance when the user program is loaded).

At the same time, the processor copies all the parameters in the stack space used by the user program at privilege 3 to the stack space at privilege 0, so that the system functions can obtain these parameters correctly.





Recommended reading

[1] C language pointer – from the underlying principle to the tricks, with graphics and code to help you explain thoroughly [2] step by step analysis – how to use C to achieve object-oriented programming [3] The original GDB underlying debugging principle is so simple [4] inline assembly is terrible? Finish this article and end it!

Other series albums: selected articles, C language, Linux operating system, application design, Internet of Things

This article is participating in the topic “Through Linux 30 years” essay activity.