1. Related data structures of protected mode
Definition of necessary data structures for protected mode
• GDT: Global Descriptor Table , also known as segment descriptor table, is a data structure in protected mode. It contains multiple describe tors, which define the starting , boundary attributes, etc.
• Descriptor: Segment descriptor, including segment base address, segment boundary, and segment attributes.
• Selector: Selector, its function is to indicate the offset of the segment descriptor relative to the GDT base address.
• GDTR : GDT register. Its structure is similar to GDTPTR , with 6 bytes. The first two bytes are the GDT limit, and the last 4 bytes are the GDT base address.
Definition of macro descriptor
As shown in the figure below, Descriptor stores segment base addresses, segment boundaries and related attributes respectively. For some reasons, segment base addresses and segment boundaries are stored separately:
2. From real mode to protected mode
pmtest 1. asm code display
; ==========================================
; pmtest1.asm
; 编译方法:nasm pmtest1.asm -o pmtest1.bin
; ==========================================
%include "pm.inc" ; 常量, 宏, 以及一些说明
org 07c00h
jmp LABEL_BEGIN
[SECTION .gdt]
; GDT
; 段基址, 段界限 , 属性
LABEL_GDT: Descriptor 0, 0, 0 ;
空描述符
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32; 非
一致代码段
LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ;
显存首地址
; GDT 结束
GdtLen equ $ - LABEL_GDT ; GDT 长度
GdtPtr dw GdtLen - 1 ; GDT 界限
dd 0 ; GDT 基地址
; GDT 选择子
SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT
SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT
; END of [SECTION .gdt]
[SECTION .s16]
[BITS 16]
LABEL_BEGIN:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0100h
; 初始化 32 位代码段描述符
xor eax, eax
mov ax, cs
shl eax, 4
add eax, LABEL_SEG_CODE32
mov word [LABEL_DESC_CODE32 + 2], ax
shr eax, 16
mov byte [LABEL_DESC_CODE32 + 4], al
mov byte [LABEL_DESC_CODE32 + 7], ah
; 为加载 GDTR 作准备
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_GDT ; eax <- gdt 基地址
mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址
; 加载 GDTR
lgdt [GdtPtr]
; 关中断
cli
; 打开地址线 A20
in al, 92h
or al, 00000010b
out 92h, al
; 准备切换到保护模式
mov eax, cr0
or eax, 1
mov cr0, eax
; 真正进入保护模式
jmp dword SelectorCode32:0 ; 执行这一句会把 SelectorCode32 装入
cs,
; 并跳转到 Code32Selector:0 处
; END of [SECTION .s16]
[SECTION .s32]; 32 位代码段. 由实模式跳入.
[BITS 32]
LABEL_SEG_CODE32:
mov ax, SelectorVideo
mov g s, ax ; 视频段选择子(目的)
mov edi, (80 * 11 + 79) * 2 ; 屏幕第 11 行, 第 79 列。
mov ah, 0Ch ; 0000: 黑底 1100: 红字
mov al, 'P'
mov [gs:edi], ax
; 到此停止
jmp $
SegCode32Len equ $ - LABEL_SEG_CODE32
; END of [SECTION .s32]
Results of the
From real mode to protected mode
1. Prepare GDT.
2. Load gdtr with lgdt .
3. Open A20.
4. Set the PE bit of cr0 .
5. Jump and enter protected mode.
jmp jump: dword prefix
In line 71 of pmtest1.asm : jmp dword SelectorCode32 :0 Deleting the dword identifier will cause the offset address to be truncated after compilation, and may not jump to the specified location correctly. Reason: At this time, the operating environment of the operating system has been converted from real mode (16-bit) to protected mode (32- bit ). However, due to compiler limitations, this code can only be placed in the 16 -bit assembly code part, so a dword is required. Flag to prevent 32 - bit length offset address from being truncated and causing unknown errors. The decompiled source code of the test code is as follows:
GDT table structure
Structure of Descriptor
The construction of the segment descriptor is carried out with the help of the pm.inc file, which defines the segment base address, segment boundary and related attributes respectively . The macro will reorganize the data according to the definition to meet the requirements of the specification.
Selector structure
The value of the Selector is the difference between the segment descriptor position and the initial position of the GDT table.
Switching GDT table
Since the code in real mode stores the offset address of the segment in the segment base address of the GDT table. Therefore, in protected mode , the program can use the predefined Selector to switch the GDT table.
3.2. Return to real mode from protected mode
It is opposite to the method of entering protected mode from real mode, but since the related operations of entering real mode need to be written in the 16 -bit code segment, and due to cache reasons, there are several steps :
1. Load the relevant segment register.
2. Clear the PE bit of cr0 .
3. Jump and return to the 16- bit code segment.
4. Set the real mode segment register.
5. Close A20.
6. Turn on interrupt
7. Return to real mode
Execution effect display
The picture below shows the execution effect of the pmtest2.asm code. It can be seen that after booting into protected mode, the program prints characters and returns to the DOS system in real mode:
4. LDT switching
LDT (Local Descriptor Table)
LDT is the same as GDT , both are descriptor tables, but the LDT table only has a partial scope, while the GDT table has a global scope.
pmtest 3. asm code snippet display
[SECTION .gdt]
...
LABEL_DESC_LDT: Descriptor 0, LDTLen - 1, DA_LDT ;
LDT
...
SelectorLDT equ LABEL_DESC_LDT - LABEL_GDT
; END of [SECTION .gdt]
...
[SECTION .s32]; 32 位代码段. 由实模式跳入.
[BITS 32]
LABEL_SEG_CODE32:
...
; Load LDT
mov ax, SelectorLDT
lldt ax
jmp SelectorLDTCodeA:0 ; 跳入局部任务
...
; END of [SECTION .s32]
...
; LDT
[SECTION .ldt]
ALIGN 32
LABEL_LDT:
; 段基址 段界限 属性
LABEL_LDT_DESC_CODEA: Descriptor 0, CodeALen - 1, DA_C + DA_32 ; Code, 32 位
LDTLen equ $ - LABEL_LDT
; LDT 选择子
SelectorLDTCodeA equ LABEL_LDT_DESC_CODEA - LABEL_LDT + SA_TIL
; END of [SECTION .ldt]
Structure of LDT table
1. Define the code segment corresponding to the LDT table in the GDT table
2. Define the corresponding LDT table in the LDT table
– In the selector definition of the LDT table, based on the code offset, an additional attribute SA_TIL needs to be added. This attribute will be stored in the TL part of the selector in the pm, inc macro to identify whether the selector is Selector of LDT table
Use of LDT table
1. Use the lldt command to load the segment where the LDT table is located
2. Use the selector corresponding to the corresponding LDT table to jump
3. Execute code
Execution effect display
The picture below shows the execution effect of the pmtest3.asm code. As you can see, the program prints out the letter L in the LDT table section:
5. Permission access and switching between segments
Permission access rules
In the IA32 segmentation mechanism, there are a total of 4 privilege levels , from high to low, which are 0, 1, 2, and 3. The smaller the number, the greater the privilege level.
CPL
CPL is the privilege level of the currently executing program or task. It is stored in bits 0 and 1 of cs and ss . Under normal circumstances, CPL is equal to the privilege level of the segment where the code is located. The processor will change the CPL when the program moves to a code segment with a different privilege level . Consistent code segments can be accessed by code with the same or lower privilege level . When the processor accesses a consistent code segment with a different privilege level than the CPL , the CPL is not changed .
DPL
DPL represents the privilege level of a segment or gate. It is stored in the DPL field of the segment descriptor or gate descriptor . When the current code segment attempts to access a segment or gate, the DPL will be compared with the CPL and the RPL of the segment or gate selector . The DPL will be treated differently depending on the segment or gate type:
• Data segment: DPL specifies the minimum privilege level that can access this segment .
• Non-conforming code section (without using call gates): DPL specifies the privilege level for accessing this section .
• Conforming code section: DPL specifies the highest privilege level for accessing this section .
RPL
RPL is expressed through bits 0 and 1 of the segment selector . The processor checks the RPL and CPL to confirm whether an access request is legal. That is to say, if the number of RPL is larger than that of CPL (the larger the number, the lower the privilege level), then RPL will play a decisive role, and vice versa.
Privilege level verification test
Use pmtest2.asm to modify the DPL of the data segment descriptor and the RPL of its segment selector :
[SECTION .gdt]
; GDT
;
LABEL_DESC_DATA: Descriptor 0, DataLen-1, DA_DRW+DA_DPL1 ; Data
; GDT 结束
; GDT 选择子
SelectorData equ LABEL_DESC_DATA - LABEL_GDT + SA_RPL3
; END of [SECTION .gdt]
The debugging results are as follows:
Debugger error: load_seg_reg (DS, 0x0023): RPL & CPL must be <= DPL proves that the rules of privilege level management are violated here
Switch between segments
There are several ways to transfer between different code segments:
• Use jmp or call instructions
– The target operand contains the segment selector of the target code segment
– The target operand points to a call gate descriptor containing the target code section selector.
• Transfer using gate descriptors
Among them, the transfer between code segments that can be performed through jmp and call is very limited. For non-consistent code segments, it can only be transferred between code segments with the same privilege level. When encountering a consistent code segment, it can go from low to high at most, and the CPL will not change. If you need to transfer between different privilege levels, you need to use the gate descriptor to transfer immediately.
gate descriptor
Gate descriptor structure
The call gate directly defines a series of attributes such as the selector and entry offset address of the corresponding segment of the target code , and can directly jump to the code segment. There are four types of door descriptors :
• call gate
• Interrupt gate
• Trap door
• Task gate
Use of call gates
pmtest4.asm code snippet display
[SECTION .gdt]
; GDT
; 段基址, 段界限 , 属性
LABEL_DESC_CODE_DEST: Descriptor 0,SegCodeDestLen-1, DA_C+DA_32; 非一致 代码段,32
...
; 门 目标选择子,偏移,DCount, 属性
LABEL_CALL_GATE_TEST: Gate SelectorCodeDest, 0, 0,
DA_386CGate+DA_DPL0
...
; GDT 选择子
SelectorCodeDest equ LABEL_DESC_CODE_DEST - LABEL_GDT
SelectorCallGateTest equ LABEL_CALL_GATE_TEST - LABEL_GDT
; END of [SECTION .gdt]
...
[SECTION .s32]; 32 位代码段. 由实模式跳入.
[BITS 32]
...
; 测试调用门(无特权级变换),将打印字母 'C'
call SelectorCallGateTest:0
...
jmp SelectorLDTCodeA:0 ; 跳入局部任务,将打印字母 'L'。
...
; END of [SECTION .s32]
Use the Gate macro in pm . inc to call the gate. In the code snippet, the location pointed to by this gate is SelectorCodeDest:0, which is the code at LABEL_DESC_CODE_DEST .
Execution effect display
The program enters the code section pointed to by the call gate and outputs the letter C
6. Use call gates to change privilege levels
Privilege checks used by call gates
Privilege level translation of the stack - TSS
Because when making a call-ret jump, the stack will store the cs:eip address before the jump and return at the end of . However, when there is a privileged code jump, the stack segment called will also change. Therefore, TSS (Tasj-State Stack) needs to be used to avoid such problems.
Structure of TSS
Transfer process assisted by TSS
1. Select which ss and esp should be switched to from the TSS based on the DPL (new CPL) of the target code segment
2. Read new ss and esp from TSS . During this process, if ss, esp or TSS boundary errors are found, an invalid TSS exception (#TS) will occur .
3. Check the ss descriptor. If an error occurs, a #TS exception will also be generated. Also produces #TS open m. mt access media equipment
4. Temporarily save the current ss and esp values.
5. Load new ss and esp.
6. Push the just saved ss and esp values into the new stack.
7. Copy the parameters from the caller stack to the callee stack (new stack). The number of copied parameters is determined by the Param Count item in the call gate . If Param Count is 0 , parameters will not be copied.
8. Push the current cs and eip onto the stack.
9. Load the new cs and eip specified in the call gate and start executing the callee process.
TSS- assisted return process
1. Check the RPL in the saved cs to determine whether to change the privilege level when returning.
2. Load the cs and eip on the callee stack (at this time, the code segment descriptor and selected subtype and privilege level will be checked).
3. If the ret instruction contains parameters, increase the value of esp to skip the parameters, and then esp will point to the saved caller ss and esp . Note that the parameter of ret must correspond to the value of ParamCount in the call gate .
4. Load ss and esp, switch to the caller stack, and the callee's ss and esp are discarded. Here the ss descriptor, esp and ss segment descriptor will be checked.
5. If the ret instruction contains parameters, increase the value of esp to skip the parameters (which are already on the caller stack ).
6. Check the values of ds , es , fs , and gs . If the DPL of the segment pointed to by any of the registers is less than the CPL (this rule does not apply to consistent code segments), then an empty descriptor will be loaded into the register .
Using call gates for privilege level translation
pmtest5.asm implements a ring 3 privilege level code (which will print the number 3 on the screen ); it also initializes TSS and prints the letter C on the screen by referencing the ring 0 privilege level code fragment through the call gate. The experimental results are shown in the figure below:
Test success
1. Problem solving and hands-on modification
1.1 GDT , Descriptor , Selector , GDTR structures, and what are their meanings? How are they related ? How to use the macros defined by pm.inc ?
GDT (Global Descriptor Table ), also called segment descriptor table, is a data structure in protected mode . It contains multiple descriptors, which define the starting address of the segment, boundary attributes, etc., and its function is to provide a segmented storage mechanism;
Descriptor is a segment descriptor, including segment base address, segment limit, and segment attributes;
Selector is a selector and can correspond to a descriptor. In the pmtest1.asm program , it corresponds to the offset of the descriptor relative to the GDT base address.
GDT is a structure array that contains multiple Descriptors. Each Descriptor is an entry in the GDT array and stores the segment base address, segment boundaries and attributes of each segment. The Selector records the offset, table type corresponding Descriptor relative to the GDT base address (LABEL_GDT). GDTR records the base address and limits of the GDT table. Based on the above, you can get the address of a Descriptor . Addressing in protected mode is to first find the GDT by GDTR , then find the address of the corresponding segment according to the Descriptor , and then add the offset within the segment to obtain a linear address.
The Descriptor in the program is generated by the macro Descriptor in pm.inc . The specific use of macros is as follows:
a. Macro name Descriptor, 3 indicates that there are three parameters, namely segment base address, limit, and attribute;
b. The first line of dw is two bytes, which determines the segment boundary;
c. The second and third lines dw and dd determine the segment base addresses 1 and 2;
d. The two bytes of dw in the fourth line constitute the segment attribute and segment boundary 2;
e. The two bytes of dw in the last line constitute segment base address 3.
1.2 What are the key steps from real mode to protected mode? Why turn off interrupts? Why open the A20 address line? What steps are needed to switch from protected mode back to real mode?
Steps from real mode to protected mode:
a. Prepare GDT, initialize GDT attributes, and define the descriptors and selectors of the required segments;
b. Initialization section;
c. Fill the physical address of GDT into GdtPtr , and then load the filled address into register gdtr;
d. Turn off interrupt;
e. Open the A20 address line;
f. Set the PE bit of cr0 ;
g. Jump to the first address of the segment corresponding to the descriptor and enter protected mode.
The reason why interrupts need to be turned off is that the interrupt processing is different from that in real mode. If interrupts are enabled but the corresponding interrupt processing mechanism is not yet complete, an error will occur.
The reason why the A20 address line needs to be opened is that there is only a 20 -bit address bus in the 8086 CPU , and its maximum addressing capability can only reach 1 MB. The 8086 design is that when a program accesses a memory address above 1 MB , it will " wrap back " from address 0. That is to say, when accessing 1 MB and zero 1 bit, the actual space accessed is address 1 . In later CPUs , the access space has already exceeded 1 MB, which leads to incompatibility. IBM designed that if A20 is not opened, the rollback mechanism will continue to be used, and the twentieth address will be 0. If you open A2 0, you can access memory addresses above 1 MB normally.
Steps from real mode to protected mode:
- Load an appropriate descriptor selector into the relevant segment register;
- Jump to 16 -bit code segment;
- Clear the PE bit of cr0 ;
- Jump back to real mode;
- Close A20;
- Turn on interrupts.
1.3 Explain the switching principle of different permission codes. What are the usage scenarios of call, jmp and retf ? Can they be interchanged?
For jmp , long jumps and short jumps only have different results. Short jumps correspond to intra-segment jumps , and long jumps correspond to inter-segment jumps.
For call , since the call instruction will affect the stack , long calls and short calls will have different effects. In the short call, the call instruction will push the next instruction eip onto the stack. When the ret instruction is executed , the eip will is popped from the stack.
During a long call, the call instruction will also push eip and cs onto the stack. When retf is encountered, eip and cs will pop up . Generally speaking, call is equivalent to push+jmp and ret is equivalent to pop+jmp.
1.4 Hands-on modification:
① Customize and add 1 GDT code segment and 1 LDT code segment. In the GDT segment, a string is written to a memory data structure , and then the function of the code segment in the LDT segment is to read and print the content of the GDT ;
This function is implemented with reference to pmtest3.asm . The experimental results are as follows:
Step 1: Modify the StrTest content in the .Data section
Step 2: Modify the 32- flavor code segment and jump into the LDT local task
Step 3: Modify the logic in LDT and CodeA , set the offset to the offset of TestStr , and finally output it to the screen
② Customize two GDT code segments A and B, which belong to different privilege levels. The functions are customized and require the jump from A to B and the jump from B to A.