当前位置:网站首页>ucore lab1
ucore lab1
2022-06-13 00:13:00 【Ethan97】
practice 1: Understanding through make The process of generating an execution file
Question 1 : Operating system image file ucore.img How is it generated step by step ?( It needs to be explained in more detail Makefile The meaning of each relevant command and command parameter in , And explain the result of the command )
stay Makefile In the middle of ucore.img
The code for is as follows :
# create ucore.img
UCOREIMG := $(call totarget,ucore.img)
$(UCOREIMG): $(kernel) $(bootblock)
$(V)dd if=/dev/zero [email protected] count=10000
$(V)dd if=$(bootblock) [email protected] conv=notrunc
$(V)dd if=$(kernel) [email protected] seek=1 conv=notrunc
$(call create_target,ucore.img)
First, create a file with the size of 10000 Block of bytes , And then bootblock
Copy the past .
Generate ucore.img
You need to be successful kernel
and bootblock
adopt make V=
The specific commands to be executed are as follows :
# compile init.c file , Generate init.o
+ cc kern/ern/init/init.c
gcc -Ikern/init/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/init/init.c -o obj/kern/init/init.o
# compile readline.c file , Generate readline.o
+ cc kern/libs/readline.c
gcc -Ikern/libs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/libs/readline.c -o obj/kern/libs/readline.o
# compile stdio.c file , Generate stdio.o
+ cc kern/libs/stdio.c
gcc -Ikern/libs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/libs/stdio.c -o obj/kern/libs/stdio.o
# compile kdebug.c file , Generate kdebug.o
+ cc kern/debug/kdebug.c
gcc -Ikern/debug/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/debug/kdebug.c -o obj/kern/debug/kdebug.o
# compile kmonitor.c file , Generate kmonitor.o
+ cc kern/debug/kmonitor.c
gcc -Ikern/debug/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/debug/kmonitor.c -o obj/kern/debug/kmonitor.o
# compile panic.c file , Generate painc.o
+ cc kern/debug/panic.c
gcc -Ikern/debug/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/debug/panic.c -o obj/kern/debug/panic.o
# Compile a series .c file
+ cc kern/driver/clock.c
gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/clock.c -o obj/kern/driver/clock.o
+ cc kern/driver/console.c
gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/console.c -o obj/kern/driver/console.o
+ cc kern/driver/intr.c
gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/intr.c -o obj/kern/driver/intr.o
+ cc kern/driver/picirq.c
gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/picirq.c -o obj/kern/driver/picirq.o
+ cc kern/trap/trap.c
gcc -Ikern/trap/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/trap/trap.c -o obj/kern/trap/trap.o
+ cc kern/trap/trapentry.S
gcc -Ikern/trap/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/trap/trapentry.S -o obj/kern/trap/trapentry.o
+ cc kern/trap/vectors.S
gcc -Ikern/trap/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/trap/vectors.S -o obj/kern/trap/vectors.o
+ cc kern/mm/pmm.c
gcc -Ikern/mm/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/mm/pmm.c -o obj/kern/mm/pmm.o
+ cc libs/printfmt.c
gcc -Ilibs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -c libs/printfmt.c -o obj/libs/printfmt.o
+ cc libs/string.c
gcc -Ilibs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -c libs/string.c -o obj/libs/string.o
# Link all the generated target files , And generate kernel Binary
+ ld bin/kernel
ld -m elf_i386 -nostdlib -T tools/kernel.ld -o bin/kernel obj/kern/init/init.o obj/kern/libs/readline.o obj/kern/libs/stdio.o obj/kern/debug/kdebug.o obj/kern/debug/kmonitor.o obj/kern/debug/panic.o obj/kern/driver/clock.o obj/kern/driver/console.o obj/kern/driver/intr.o obj/kern/driver/picirq.o obj/kern/trap/trap.o obj/kern/trap/trapentry.o obj/kern/trap/vectors.o obj/kern/mm/pmm.o obj/libs/printfmt.o obj/libs/string.o
# compile bootasm.S / bootmain.c / sign.c /
+ cc boot/bootasm.S
gcc -Iboot/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Os -nostdinc -c boot/bootasm.S -o obj/boot/bootasm.o
+ cc boot/bootmain.c
gcc -Iboot/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Os -nostdinc -c boot/bootmain.c -o obj/boot/bootmain.o
+ cc tools/sign.c
gcc -Itools/ -g -Wall -O2 -c tools/sign.c -o obj/sign/tools/sign.o
# Generate sign file
gcc -g -Wall -O2 obj/sign/tools/sign.o -o bin/sign
# Link generation bootblock Binary
+ ld bin/bootblock
ld -m elf_i386 -nostdlib -N -e start -Ttext 0x7C00 obj/boot/bootasm.o obj/boot/bootmain.o -o obj/bootblock.o
'obj/bootblock.out' size: 488 bytes
build 512 bytes boot sector: 'bin/bootblock' success!
# Generate ucore.img file
dd if=/dev/zero of=bin/ucore.img count=10000
10000+0 records in
10000+0 records out
5120000 bytes (5.1 MB) copied, 0.0325965 s, 157 MB/s
dd if=bin/bootblock of=bin/ucore.img conv=notrunc
1+0 records in
1+0 records out
512 bytes (512 B) copied, 0.000118495 s, 4.3 MB/s
dd if=bin/kernel of=bin/ucore.img seek=1 conv=notrunc
146+1 records in
146+1 records out
74923 bytes (75 kB) copied, 0.00055858 s, 134 MB/s
According to which we can see , To generate kernel
, Need to use GCC Compiler will kern
All the .c
All files are compiled and generated .o
File support . Again , To generate bootblock
, First, you need to generate bootasm.o、bootmain.o、sign
GCC Compilation options Detailed explanation :
Compilation options | meaning |
---|---|
-I | Specify the path that the library file contains (① Specify the value ② environment variable ③ Standard system search path ) |
-fno-builtin | Only identify with __builtin_ Prefixed GCC Built in functions , Disable most built-in functions , Prevent it from having the same name |
-Wall | After compilation, all Warning message |
-ggdb | Use GDB Join in Debugging information |
-m32 | Generate 32 Bit machine code ,int long pointer All are 32 position , Appoint x86 Processor specific options , Processor dependent options |
-gstabs | produce stabs Format debugging information , It doesn't contain GDB Expand |
-nostdinc | Do not search the header file of the standard system directory , Just search -I / -iquote / -isystem / -dirafter Specified header file , Catalog options |
-fno-stack-protector | Disable the stack protection mechanism , Tool options |
-c | Compile or assemble source files , But don't link . take .c/.i/.s And other suffixed files are compiled into .o suffix . Output type control |
-O | Optimize the generated code ,-Os Just optimize the size of the generated code , It turns on all -O2 Optimization options , Except for options that increase the size of the code . Optimization options |
LD Compilation options Detailed explanation :
Compilation options | meaning |
---|---|
-m | Specify the format of the generated file , By default LDEMULATION environment variable , Without this environment variable , It depends on linker Default configuration . adopt ld -V You can view the emulation . |
-nostdlib | Search only the specified Library Directory displayed on the command line , The directory specified in the link script is ignored , Include link scripts developed on the command line . |
-N | Set up text and data section read-write , Data segments are not paged , Do not link dynamic link libraries |
-e entry | Specifies the entry function that the program starts executing , Instead of the default entry point . |
-Tbss=org / -Tdata=org / -Ttext=org | adopt org Draw up a section Absolute address in the output file . |
dd Disk maintenance command Detailed explanation :
Linux dd The command is used to read 、 Convert and output data .dd Data can be read from standard input or file , Convert data according to the specified format , And then output to the file 、 Device or standard output .
if = file name : Input file name , Default to standard input .
of = file name : Output file name , Default to standard output .
ibs = bytes: Read in at a time bytes Bytes , That is, to specify a block size of bytes Bytes .( Default 512 byte )
obs = bytes: One output bytes Bytes , That is, to specify a block size of bytes Bytes .( Default 512 byte )
bs = bytes: Also set read in / The output block size is bytes Bytes .
cbs = bytes: One conversion bytes Bytes , That is, specify the conversion buffer size .
skip = blocks: Skip from the beginning of the input file blocks Block and start copying .
seek = blocks: Skip from the beginning of the output file blocks Block and start copying .
count = blocks: Just copy blocks Block , The size of the block is equal to ibs Number of bytes specified .
conv = < keyword >, Keywords can have the following 11 Kind of :
conversion: Convert the file with the specified parameters .
ascii: transformation ebcdic by ascii
ebcdic: transformation ascii by ebcdic
ibm: transformation ascii by alternate ebcdic
block: Convert each line to a length of cbs, Fill in the insufficient part with space
unblock: Make each line the length of cbs, Fill in the insufficient part with space
lcase: Convert uppercase characters to lowercase characters
ucase: Convert lowercase characters to uppercase characters
swab: Exchange each pair of bytes of input
noerror: Don't stop when something goes wrong
notrunc: Do not truncate the output file
sync: Fill each input block to ibs Bytes , Empty the insufficient part (NUL) Character complement .
N and BYTES may be followed by the following multiplicative suffixes:
c =1, w =2, b =512, kB =1000, K =1024, MB =1000*1000, M =1024*1024, xM =M
GB =1000*1000*1000, G =1024*1024*1024, and so on for T, P, E, Z, Y.
Question two : What are the characteristics of a hard disk primary boot sector that the system considers to be compliant with the specification ?
answer : Of the primary boot sector of the hard disk size = 512 Bytes, And the last two bytes are 0x55AA
.
stay sign.c The following checks were made :
// Check the size of the primary boot sector
if (size != 512) {
fprintf(stderr, "write '%s' error, size is %d.\n", argv[2], size);
return -1;
}
practice 2: Use qemu Execute and debug lab1 Software in .( Ask for a brief description of the exercise process in the report )
practice 3: analysis bootloader Process of entering protection mode .( It is required to write the analysis in the report )
bootloader Do the things :
1. Enable protection mode & Breaking mechanism
2. Read from hard disk kernel in ELF Format ucore kernel( With the MBR The following sectors ) And put it in a fixed position in memory
3. Jump to ucore Entrance point (entry point) perform , At this time, control is over ucore OS in
1. Why open A20, How to open A20?
Intel In the early 8086 CPU Provides 20 Root address line , The addressable space range is 02^20(00000HFFFFFH) Of 1MB Memory space . but 8086 Data processing bit width 16 position , No direct addressing 1MB Memory space , therefore 8086 An address translation mechanism of segment address plus offset address is provided .PC The addressing structure of the machine is segment:offset,segment and offset All are 16 Bit register , The maximum is 0ffffh, The calculation method of converting to physical address is to put segment Move left 4 position , Plus offset, therefore segment:offset The maximum address space that can be expressed should be 0ffff0h + 0ffffh = 10ffefh( Ahead 0ffffh yes segment=0ffffh And move to the left 4 The result of a , hinder 0ffffh Is the largest possible offset), This calculated 10ffefh How big ? It's about 1088KB, That is to say ,segment:offset The address of indicates capability , More than the 20 Physical addressing capability of bit address lines .** So when addressing to more than 1MB Memory time , It's going to happen “ Rewind ”( There will be no exceptions ). But the next generation is based on Intel 80286 CPU Of PC AT The computer system provides 24 Root address line , such CPU The addressing range of becomes 2^24=16M, It also provides a protection mode , Access to 1MB More memory , At this point, if you encounter “ Addressing more than 1MB” The situation of , The system will no longer “ Rewind ” 了 , This creates a downward incompatibility .** To maintain full downward compatibility ,IBM Decided to PC AT Add a hardware logic to the computer system , To imitate the above looping characteristics , And there it is A20 Gate. Their method is to put A20 Address line control and an output of the keyboard controller AND operation , To control A20 Opening of address line ( Can make ) On and off ( shielding \ prohibit ). At the beginning A20 Address line control is shielded ( Always for 0), Until the system software passes a certain IO Operation to open it ( see bootasm.S). Obviously , To access the high-end memory area in real mode , This switch must be on , In protected mode , Due to the use 32 Bit address line , If A20 Equal to 0, Then the system can only access odd megabytes of memory , That is, you can only access 0–1M、2-3M、4-5M…, This does not effectively access all available memory . So in protected mode , This switch must also be turned on .
2. How to initialize GDT surface ?
The sectioning machine involves 4 There are two key elements : Logical address 、 Segment descriptor ( Describe the properties of the segment )、 Segment descriptor table ( Containing multiple segment descriptors “ Array ”)、 Segment selector ( Segment register , The index used to locate the entry in the segment descriptor table )
Global descriptor table The global descriptor table is a table that holds multiple segment descriptors “ Array ”, The starting address is stored in the global descriptor table register GDTR in .GDTR Long 48 position , Among them high 32 Bit is the base address , low 16 Bit is the segment boundary . because GDT Can not have GDT Descriptors within itself are described and defined , So the processor uses GDTR by GDT This particular system segment . Be careful , The first segment descriptor in the global descriptor table is set as an empty segment descriptor .GDTR Segment boundaries in are in bytes . For containing N The segment bounds of descriptor tables of descriptors can usually be set to 8*N-1. stay ucore Medium boot/bootasm.S Medium gdt Address and kern/mm/pmm.c Array of global variables in gdt[] They are based on assembly language and C The concrete implementation of the global descriptor table of the language .
gdtdesc It is pointed out that the global descriptor table is in the symbol gdt It's about , take gdt Table load GDTR in .
# hold gdt The starting position and bounds of the table are loaded GDTR register
lgdt gdtdesc
# take CR0 Of the 0 Location 1, To turn on the protection mode
movl %cr0, %eax
orl $CR0_PE_ON, %eax
movl %eax, %cr0
cr0 The first of the registers 0 Bit is the protection mode bit PE, Set up PE Will make the processor work in protected mode .
3. How to enter the protection mode ?
By long jump command ljmp $PROT_MODE_CSEG, $protcseg
Enter protection mode .
make a concrete analysis bootmain.S The code is as follows :
bios Load from the first sector of the hard disk bootmain.S, And load it into the physical address of memory 0x7c00, Then start running in real mode .
#include <asm.h>
# Start the CPU: switch to 32-bit protected mode, jump into C.
# The BIOS loads this code from the first sector of the hard disk into
# memory at physical address 0x7c00 and starts executing in real mode
# with %cs=0 %ip=7c00.
.set PROT_MODE_CSEG, 0x8 # kernel code segment selector
.set PROT_MODE_DSEG, 0x10 # kernel data segment selector
.set CR0_PE_ON, 0x1 # protected mode enable flag
# start address should be 0:7c00, in real mode, the beginning address of the running bootloader
.globl start
start:
.code16 # Assemble for 16-bit mode
cli # Disable interrupts
cld # String operations increment
# Set up the important data segment registers (DS, ES, SS). Set each register to 0
xorw %ax, %ax # Segment number zero
movw %ax, %ds # -> Data Segment
movw %ax, %es # -> Extra Segment
movw %ax, %ss # -> Stack Segment
# Enable A20:
# For backwards compatibility with the earliest PCs, physical
# address line 20 is tied low, so that addresses higher than
# 1MB wrap around to zero by default. This code undoes this.
# About A20 Gate: https://chyyuu.gitbooks.io/ucore_os_docs/content/lab1/lab1_appendix_a20.html
# Theoretically speaking , We just need to operate 8042 The output port of the chip (64h) Of bit 1, You can control A20 Gate,
# But actually , When you are ready to 8042 When writing data in the input buffer of , There may be other data that has not been processed ,
# therefore , We must first prohibit keyboard operation , At the same time, wait until there is no data in the data buffer , To really operate 8042 On or off A20 Gate.
# open A20 Gate The specific steps are as follows ( Reference resources bootasm.S):
# wait for 8042 Input buffer It's empty ;
# send out Write 8042 Output Port (P2) Order to 8042 Input buffer;
# wait for 8042 Input buffer It's empty ;
# take 8042 Output Port(P2) Get byte number 2 Location 1, And then write 8042 Input buffer;
seta20.1:
inb $0x64, %al # Wait for not busy(8042 input buffer empty). wait for 8042 The keyboard controller is not busy
testb $0x2, %al # Determine whether the input cache is empty
jnz seta20.1
movb $0xd1, %al # 0xd1 -> port 0x64,0xd1 Indicates the write output port command , The parameter is then passed 0x60 Port write
outb %al, $0x64 # 0xd1 means: write data to 8042's P2 port
seta20.2:
inb $0x64, %al # Wait for not busy(8042 input buffer empty).
testb $0x2, %al
jnz seta20.2
movb $0xdf, %al # 0xdf -> port 0x60, adopt 0x60 Write data 11011111 the A20 Set up 1
outb %al, $0x60 # 0xdf = 11011111, means set P2's A20 bit(the 1 bit) to 1
# Switch from real to protected mode, using a bootstrap GDT
# and segment translation that makes virtual addresses
# identical to physical addresses, so that the
# effective memory map does not change during the switch.
# load GDT surface
lgdt gdtdesc
# take CR0 Of the 0 Location 1, To turn on protected mode
movl %cr0, %eax
orl $CR0_PE_ON, %eax
movl %eax, %cr0
# Jump to next instruction, but in 32-bit code segment.
# Switches processor into 32-bit mode.
# Long jump to 32 Bit code snippet , reinstall CS and EIP
ljmp $PROT_MODE_CSEG, $protcseg
.code32 # Assemble for 32-bit mode
protcseg:
# Set up the protected-mode data segment registers
# Set up DS、ES Equal segment register
movw $PROT_MODE_DSEG, %ax # Our data segment selector
movw %ax, %ds # -> DS: Data Segment
movw %ax, %es # -> ES: Extra Segment
movw %ax, %fs # -> FS
movw %ax, %gs # -> GS
movw %ax, %ss # -> SS: Stack Segment
# Set up the stack pointer and call into C. The stack region is from 0--start(0x7c00)
# Go to protected mode and finish , Get into boot Main method
movl $0x0, %ebp
movl $start, %esp
call bootmain
# If bootmain returns (it shouldn't), loop.
spin:
jmp spin
# Bootstrap GDT
.p2align 2 # force 4 byte alignment
gdt:
SEG_NULLASM # null seg
SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff) # code seg for bootloader and kernel
SEG_ASM(STA_W, 0x0, 0xffffffff) # data seg for bootloader and kernel
gdtdesc:
.word 0x17 # sizeof(gdt) - 1
.long gdt # address gdt
practice 4: analysis bootloader load ELF Format OS The process of .( It is required to write the analysis in the report )
By reading bootmain.c, understand bootloader How to load ELF file . By analyzing the source code and through qemu To run and debug bootloader&OS,
- bootloader How to read hard disk sectors ?
- bootmain The code for reading the hard disk is as follows :
unsigned int SECTSIZE = 512 ;
struct elfhdr * ELFHDR = ((struct elfhdr *)0x10000) ; // scratch space
/* bootmain - the entry of bootloader */
void
bootmain(void) {
// read the 1st page off disk
// Read eight sectors into memory
readseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0);
// is this a valid ELF?
// Check whether it is legal elf
if (ELFHDR->e_magic != ELF_MAGIC) {
goto bad;
}
//do something else...
}
Based on the above bootmain Analysis of ,bootmain call readseg Function to read the hard disk sector , and readseg You call the readsect Read one sector at a time :
/* waitdisk - wait for disk ready */
static void
waitdisk(void) {
while ((inb(0x1F7) & 0xC0) != 0x40)
/* do nothing */;
}
/* The process of reading a sector ( For reference. boot/bootmain.c Medium readsect Function implementation ) As follows 1. Wait until the disk is ready 2. Issue the command to read the sector 3. Wait until the disk is ready 4. Read the disk sector data to the specified memory */
/* readsect - read a single sector at @secno into @dst */
static void
readsect(void *dst, uint32_t secno) {
// wait for disk to be ready
waitdisk();
// Write the address 0x1f2~0x1f5,0x1f7, Issue the command to read the disk
outb(0x1F2, 1); // count = 1
outb(0x1F3, secno & 0xFF);
outb(0x1F4, (secno >> 8) & 0xFF);
outb(0x1F5, (secno >> 16) & 0xFF);
outb(0x1F6, ((secno >> 24) & 0xF) | 0xE0);
outb(0x1F7, 0x20); // cmd 0x20 - read sectors
// wait for disk to be ready
waitdisk();
// read a sector
// Read a sector
insl(0x1F0, dst, SECTSIZE / 4);
}
/* * * readseg - read @count bytes at @offset from kernel into virtual address @va, * might copy more than asked. * */
static void
readseg(uintptr_t va, uint32_t count, uint32_t offset) {
uintptr_t end_va = va + count;
// round down to sector boundary
// Move the starting address forward to the boundary
va -= offset % SECTSIZE;
// translate from bytes to sectors; kernel starts at sector 1
// Calculate the first sector code to be read
uint32_t secno = (offset / SECTSIZE) + 1;
// Read sector by sector
for (; va < end_va; va += SECTSIZE, secno ++) {
readsect((void *)va, secno);
}
}
- bootloader How to load ELF Format OS?
bootmain After reading the disk, start loading ELF file ,bootmain Load in ELF Format OS The code is as follows :
/* bootmain - the entry of bootloader */
void
bootmain(void) {
//read from disk
struct proghdr *ph, *eph;
// load each program segment (ignores ph flags)
// ELF The head has a description ELF A description table of where the file should be loaded into memory , Read it out here and save it in ph
ph = (struct proghdr *)((uintptr_t)ELFHDR + ELFHDR->e_phoff);
eph = ph + ELFHDR->e_phnum;
// As described in the program header table , take ELF The data in the file is loaded into memory
for (; ph < eph; ph ++) {
readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset);
}
// call the entry point from the ELF header
// note: does not return
// according to ELF Entry information in the header table , Find the entry to the kernel and start running
((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))();
bad:
outw(0x8A00, 0x8A00);
outw(0x8A00, 0x8E00);
/* do nothing */
while (1);
}
practice 5: Implement function call stack trace function ( You need to program )
Names of some registers
General Register( General registers ):EAX/EBX/ECX/EDX/ESI/EDI/ESP/EBP The low of these registers 16 Bit is 8086 Of AX/BX/CX/DX/SI/DI/SP/BP, about AX,BX,CX,DX These four registers , They can be accessed individually 8 Bit and low 8 position (AH,AL,BH,BL,CH,CL,DH,DL). Their meanings are as follows :
EAX: accumulator
EBX: Base register
ECX: Counter
EDX: Data register
ESI: Source address pointer register
EDI: Destination address pointer register
EBP: Base pointer register
ESP: Stack pointer register
Segment Register( Segment register , Also known as Segment Selector, Segment selector , Segment selector ): except 8086 Of 4 Out of segment (CS,DS,ES,SS),80386 Two paragraphs have also been added FS,GS, These segment registers are 16 Bit , Used for addressing memory segments with different attributes , Their meanings are as follows :
CS: Code segment (Code Segment)
DS: Data segment (Data Segment)
ES: Additional data segments (Extra Segment)
SS: stack segment (Stack Segment)
FS: Additional segment
GS Additional segment
Instruction Pointer( Instruction pointer register ):EIP It's low 16 Bit is 8086 Of IP, It stores The memory address of the next instruction to be executed , In segmented address translation , Indicates the intra segment offset address of the instruction .
Understanding of stack
The two most important points to understand the call stack are : The structure of the stack ,EBP register (EBP: Base pointer register ) The role of . A function call action can be decomposed into : Zero to many PUSH Instructions ( Used to stack parameters ), One CALL Instructions .CALL In fact, the instruction also implies a return address ( namely CALL Instruction the address of the next instruction ) The action of pressing the stack ( It's done by hardware ,CALL The address of the next instruction of the instruction is the current eip Value ). Almost all local compilers insert assembly instructions like the following before each function body :
pushl %ebp # take ebp Pressing stack
movl %esp , %ebp # esp -> ebp, esp: Stack pointer register
So before the program executes the actual instruction of a function , The following data has been put on the stack in sequence : Parameters 、 The return address 、ebp register . A stack structure similar to the following is obtained ( The order in which parameters are stacked depends on the calling method , Here we use C The language defaults to CDECL For example ):
+| The direction at the bottom of the stack | High address
| ... |
| ... |
| Parameters 3 |
| Parameters 2 |
| Parameters 1 |
| The return address |
| upper story [ebp] | <-------- [ebp]
| local variable | Low address
The meaning of these two assembly instructions is : First of all, will ebp Register on stack , Then put the top of the stack pointer esp Assign a value to ebp.“mov ebp esp” On the surface, this instruction is written in esp Cover ebp The value of the original , It's not . Because to ebp Before assignment , primary ebp The value has been stacked ( Stack top ), And new ebp It just points to the top of the stack . here ebp Registers are already in a very important position , This register stores an address in the stack ( primary ebp The top of the stack after entering the stack ), From this address , Up ( The direction at the bottom of the stack ) Can get the return address 、 Parameter values , Down ( Stack top direction ) Can get the value of the local variable of the function , And the address stores the... When the function of the previous layer is called ebp value .
generally speaking ,ss:[ebp+4] Is the return address ,ss:[ebp+8] Is the first parameter value ( The parameter value of the last stack , It is assumed here that it occupies 4 Byte memory ),ss:[ebp-4] Is the first local variable ,ss:[ebp] This is the upper floor ebp value . because ebp The address in is always “ When the upper layer function is called ebp value ”, And in every layer of function calls , Can pass the ebp value “ Up ( The direction at the bottom of the stack )” Can get the return address 、 Parameter values ,“ Down ( Stack top direction )” Can get the value of the local variable of the function . Recursion is thus formed , Until you reach the bottom of the stack . This is the function call stack .
The change process of function call stack
Function calls generally include the following steps :
1、 Parameter stack : Turn the parameter from right to left ( Or from right to left ) Press into the system stack in turn .
2、 Return the address to the stack : Push the address of the next instruction of the current code area call instruction into the stack , For the function to continue when it returns .
3、 Code area jump : The processor jumps from the current code area to the entrance of the called function .
4、 Stack frame adjustment
4.1 Save the current stack frame state value , Ready for later recovery of this stack frame (EBP Push ).
4.2 Switch the current stack frame to the new stack frame ( take ESP Value loading EBP, Update the bottom of the stack frame ).
4.3 Allocate space for new stack frames ( hold ESP Subtract the size of the required space , Raise the top of the stack ).
Function return roughly includes the following steps :
1、 Save return value , Usually, the return value of the function is saved in the register EAX in .
2、 Pop up the current frame , Restore the previous stack frame .
2.1 On the basis of stack balance , to ESP Plus the size of the stack frame , Lower the top of the stack , Reclaim the space of the current stack frame
2.2 Save the previous stack frame saved at the bottom of the current stack frame EBP Value pop in EBP register , Restore the previous stack frame .
2.3 Pop the return address of the function to EIP register .
3、 Jump : Jump back to the parent function according to the return address of the function and continue to execute .
kern/debug/kdebug.c
The comments and implementation of the are as follows :
/* * * print_stackframe - print a list of the saved eip values from the nested 'call' * instructions that led to the current point of execution * Print a series of nested calls to the current execution location eip value * * The x86 stack pointer, namely esp, points to the lowest location on the stack * that is currently in use. Everything below that location in stack is free. Pushing * a value onto the stack will involve decreasing the stack pointer and then writing * the value to the place that stack pointer points to. And popping a value does the * opposite. * Stack pointer register esp Point to the lowest address of the stack currently in use , Below this address is the free space of the stack * Pressing the stack causes the stack pointer to point to a lower address , And write the value to the position pointed to by the stack pointer ; The stack operation is the opposite . * * The ebp (base pointer) register, in contrast, is associated with the stack * primarily by software convention. On entry to a C function, the function's * prologue code normally saves the previous function's base pointer by pushing * it onto the stack, and then copies the current esp value into ebp for the duration * of the function. If all the functions in a program obey this convention, * then at any given point during the program's execution, it is possible to trace * back through the stack by following the chain of saved ebp pointers and determining * exactly what nested sequence of function calls caused this particular point in the * program to be reached. This capability can be particularly useful, for example, * when a particular function causes an assert failure or panic because bad arguments * were passed to it, but you aren't sure who passed the bad arguments. A stack * backtrace lets you find the offending function. * ebp( Base register ) Use software to correlate with the stack , When entering a c Function time , * 1. The initialization code of a function usually converts the previous function's ebp Pop down to save * 2. And then esp The value of ebp, To save function call information . * * The inline function read_ebp() can tell us the value of current ebp. And the * non-inline function read_eip() is useful, it can read the value of current eip, * since while calling this function, read_eip() can read the caller's eip from * stack easily. * read_ebp() Inform to get the current ebp Value ,read_eip() You can get the present eip Value * * In print_debuginfo(), the function debuginfo_eip() can get enough information about * calling-chain. Finally print_stackframe() will trace and print them for debugging. * debuginfo_eip() You can get enough information about the function call chain ,print_stackframe() Will track 、 Print this information * * Note that, the length of ebp-chain is limited. In boot/bootasm.S, before jumping * to the kernel entry, the value of ebp has been set to zero, that's the boundary. * ebp The length of the chain is limited , stay bootmain.S in ,ebp The value of is set to 0, This is its boundary . * */
void
print_stackframe(void) {
/* LAB1 YOUR CODE : STEP 1 */
/* (1) call read_ebp() to get the value of ebp. the type is (uint32_t); * (2) call read_eip() to get the value of eip. the type is (uint32_t); * (3) from 0 .. STACKFRAME_DEPTH * (3.1) printf value of ebp, eip * (3.2) (uint32_t)calling arguments [0..4] = the contents in address (uint32_t)ebp +2 [0..4] * (3.3) cprintf("\n"); * (3.4) call print_debuginfo(eip-1) to print the C calling function name and line number, etc. * (3.5) popup a calling stackframe * NOTICE: the calling funciton's return addr eip = ss:[ebp+4] * the calling funciton's ebp = ss:[ebp] */
uint32_t ebp = read_ebp(); // (1) call read_ebp() to get the value of ebp. the type is (uint32_t);
uint32_t eip = read_eip(); // (2) call read_eip() to get the value of eip. the type is (uint32_t);
int i, j;
for (i = 0; ebp != 0 && i < STACKFRAME_DEPTH; i ++) {
cprintf("ebp:0x%08x eip:0x%08x args:", ebp, eip); // (3.1) printf value of ebp, eip
uint32_t *args = (uint32_t *)ebp + 2;
for (j = 0; j < 4; j ++) {
cprintf("0x%08x ", args[j]); // (3.2) (uint32_t)calling arguments [0..4] = the contents in address (uint32_t)ebp +2 [0..4]
}
cprintf("\n"); // (3.3) cprintf("\n");
print_debuginfo(eip - 1); // (3.4) call print_debuginfo(eip-1) to print the C calling function name and line number, etc.
eip = ((uint32_t *)ebp)[1]; // eip go to caller Of CALL Instruction the address of the next instruction
ebp = ((uint32_t *)ebp)[0]; // (3.5) popup a calling stackframe because ebp Pointing to caller's ebp The pointer to
}
}
practice 6: Improve interrupt initialization and processing ( You need to program )
Interrupt descriptor table ( It can also be referred to as interrupt vector table in protection mode ) How many bytes does a table item occupy in the ? Which of them represent the entry of interrupt processing code ?
An entry in the interrupt descriptor table accounts for 8 byte . among 0~ 15 Bit and 48~ 63 The bits are offset
It's low 16 Position and height 16 position .16~ 31 Bit is segment selector . Obtain the base address of the segment through the segment selection sub segment , Add the offset in the segment to get the entry of the interrupt processing code .
Please perfect the programming kern/trap/trap.c The function that initializes the interrupt vector table in idt_init. stay idt_init Function , Initialize all interrupt entries in turn . Use mmu.h Medium SETGATE macro , fill idt An array of content . The entry of each interrupt is controlled by tools/vectors.c Generate , Use trap.c Declarative vectors The array can be .
About interrupt descriptor table idt: Interrupt number –> Interrupt descriptor table entry –> Interrupt handling routines
Each entry in the interrupt descriptor table records the address of an interrupt processing routine , Include segment selectors 、 The offset . Segment selectors can be selected in GDT Find segment descriptors in . Get the segment descriptor to get the base address of the interrupt service routine , add offset The starting address of the interrupt service routine is obtained .
The above search process is completed by hardware , But the tables are created by software , We just need lidt
Give orders cpu Know the address of the interrupt descriptor table .
/* idt_init - initialize IDT to each of the entry points in kern/trap/vectors.S */
void
idt_init(void) {
/* LAB1 YOUR CODE : STEP 2 */
/* (1) Where are the entry addrs of each Interrupt Service Routine (ISR)? * All ISR's entry addrs are stored in __vectors. where is uintptr_t __vectors[] ? * __vectors[] is in kern/trap/vector.S which is produced by tools/vector.c * (try "make" command in lab1, then you will find vector.S in kern/trap DIR) * You can use "extern uintptr_t __vectors[];" to define this extern variable which will be used later. * The entry address of the interrupt service routine is saved in __vectors in ,__vectors The array is in kern/trap/vector.S * take __vectors[] Declared as an external array of pointers * * (2) Now you should setup the entries of ISR in Interrupt Description Table (IDT). * Can you see idt[256] in this file? Yes, it's IDT! you can use SETGATE macro to setup each item of IDT * idt The array is the interrupt descriptor table , Using macros SETGATE To set up IDT Each of * * (3) After setup the contents of IDT, you will let CPU know where is the IDT by using 'lidt' instruction. * You don't know the meaning of this instruction? just google it! and check the libs/x86.h to know more. * Notice: the argument of lidt is idt_pd. try to find it! * Set it up IDT after , utilize lidt To make the cpu hear IDT The location of */
extern uintptr_t __vectors[];
int i;
for (i = 0; i < sizeof(idt) / sizeof(struct gatedesc); i ++) {
SETGATE(idt[i], 0, GD_KTEXT, __vectors[i], DPL_KERNEL);
}
// set for switch from user to kernel
SETGATE(idt[T_SWITCH_TOK], 0, GD_KTEXT, __vectors[T_SWITCH_TOK], DPL_USER);
// load the IDT
lidt(&idt_pd);
}
SETGATE
The macro is located in kern/mm/mmu.h
, The notes are as follows :
/* *
* Set up a normal interrupt/trap gate descriptor
* Set interrupt / Trap gate descriptor
* - istrap: 1 for a trap (= exception) gate, 0 for an interrupt gate
* - sel: Code segment selector for interrupt/trap handler
* - off: Offset in code segment for interrupt/trap handler
* - dpl: Descriptor Privilege Level - the privilege level required
* for software to invoke this interrupt/trap gate explicitly
* using an int instruction.
* gate: For the corresponding idt[] An array of content , The entry address of the processing function
* istrap: 1 yes trap door ,0 It's the interrupt door
* sel: interrupt / Code segment selector for trap gate processor
* off: The offset of the code segment
* dpl: The priority of the descriptor , The user program triggers this interrupt / The priority required by the trap
* */
#define SETGATE(gate, istrap, sel, off, dpl) { \
(gate).gd_off_15_0 = (uint32_t)(off) & 0xffff; \
(gate).gd_ss = (sel); \
(gate).gd_args = 0; \
(gate).gd_rsv1 = 0; \
(gate).gd_type = (istrap) ? STS_TG32 : STS_IG32; \
(gate).gd_s = 0; \
(gate).gd_dpl = (dpl); \
(gate).gd_p = 1; \
(gate).gd_off_31_16 = (uint32_t)(off) >> 16; \
}
Please perfect the programming trap.c Interrupt handling function in trap, Fill in... In the section dealing with clock interrupt trap The part of a function that handles clock interrupts , Make every time the operating system encounters 100 After the second clock interrupt , call print_ticks Subroutines , Print a line of text to the screen ”100 ticks”.
A clock is a peripheral with a special function , It's not just about timing . In the following chapters, we will talk about , It is precisely because of the regular clock interruptions , So that no matter what the present CPU Where to run , Operating systems are available at predetermined points in time CPU control power .
When you jump to the entry address of the interrupt processing routine , Such as :
vector2:
pushl $0
pushl $2
jmp __alltraps
.globl vector3
It can be seen that the interrupt processing routine jumps to __alltraps
,__alltraps
be located kern/trap/trapentry.S
in :
#include <memlayout.h>
# vectors.S sends all traps here.
.text
.globl __alltraps
__alltraps:
# push registers to build a trap frame structure trap frame structure
# therefore make the stack look like a struct trapframe Make the stack look like a structure trapframe
pushl %ds
pushl %es
pushl %fs
pushl %gs
pushal
# load GD_KDATA into %ds and %es to set up data segments for kernel
# take GD_KDATA Load into ds and es Register to set the data segment for the kernel
movl $GD_KDATA, %eax
movw %ax, %ds
movw %ax, %es
# push %esp to pass a pointer to the trapframe as an argument to trap()
# take esp Press a stack to point a to trapframe The pointer to trap() function
pushl %esp
# call trap(tf), where tf=%esp
# go to trap function
call trap
# pop the pushed stack pointer
popl %esp
# return falls through to trapret...
.globl __trapret
__trapret:
# restore registers from stack
popal
# restore %ds, %es, %fs and %gs
popl %gs
popl %fs
popl %es
popl %ds
# get rid of the trap number and error code
addl $0x8, %esp
iret
The above assembly code finally calls kern/trap/trap.c
Medium trap
function :
/* * * trap - handles or dispatches an exception/interrupt. if and when trap() returns, * the code in kern/trap/trapentry.S restores the old CPU state saved in the * trapframe and then uses the iret instruction to return from the exception. * trap Function processing 、 Distribution interrupted / abnormal , * When trap When function returns ,trapentry.S Restore saved in trapframe The original in cpu state , * And then use iret Command to return an exception . * */
void
trap(struct trapframe *tf) {
// dispatch based on what type of trap occurred
trap_dispatch(tf);
}
among trapframe
The structure is defined as follows :
/* registers as pushed by pushal */
struct pushregs {
uint32_t reg_edi;
uint32_t reg_esi;
uint32_t reg_ebp;
uint32_t reg_oesp; /* Useless */
uint32_t reg_ebx;
uint32_t reg_edx;
uint32_t reg_ecx;
uint32_t reg_eax;
};
struct trapframe {
struct pushregs tf_regs;
uint16_t tf_gs;
uint16_t tf_padding0;
uint16_t tf_fs;
uint16_t tf_padding1;
uint16_t tf_es;
uint16_t tf_padding2;
uint16_t tf_ds;
uint16_t tf_padding3;
uint32_t tf_trapno;
/* below here defined by x86 hardware */
// The following information is automatically saved by the hardware
uint32_t tf_err;
uintptr_t tf_eip;
uint16_t tf_cs;
uint16_t tf_padding4;
uint32_t tf_eflags;
/* below here only when crossing rings, such as from user to kernel */
// The following is the information that needs to be saved for the user state to generate an interrupt
uintptr_t tf_esp;
uint16_t tf_ss;
uint16_t tf_padding5;
} __attribute__((packed));
trap_dispatch
The implementation is as follows :
/* trap_dispatch - dispatch based on what type of trap occurred */
static void
trap_dispatch(struct trapframe *tf) {
char c;
switch (tf->tf_trapno) {
case IRQ_OFFSET + IRQ_TIMER: // Clock interrupt
/* LAB1 YOUR CODE : STEP 3 */
/* handle the timer interrupt */
// Dealing with clock interrupts
/* (1) After a timer interrupt, you should record this event using a global variable (increase it), such as ticks in kern/driver/clock.c * After a clock interrupt , Should let ticks The variable is incremented to record it * (2) Every TICK_NUM cycle, you can print some info using a function, such as print_ticks(). * Each pass TICK_NUM Secondary clock interrupt , Should use the print_ticks() Print some information * (3) Too Simple? Yes, I think so! */
ticks ++; // Each clock interrupt counter is incremented by one
if (ticks % TICK_NUM == 0) {
print_ticks(); // Every time TICK_NUM The clock interrupt calls once
}
break;
}
}
边栏推荐
- 2022美容师(技师)上岗证题目及答案
- The whole process from entering URL to displaying page (interview)
- PMP test difficulty and pass rate
- Browser cache execution process
- OSM地图本地发布-如何生成各省市矢量地图
- What can PMP bring to you
- 哲學和文學的區別
- June 11, 2022 diary: Mr. Wang's spring, mixed in
- [LeetCode]1. Sum of two numbers thirty-four
- Divicon est toujours utilisé dans le leaflet de l'ère H5?
猜你喜欢
Real time preview of PHP in browser by vscade
【Matlab】三维曲线与三维曲面
ik分词器的安装
PMP training organization
How to visit a website
What occupation is suitable for PMP?
Enterprise wechat H5_ Authentication, H5 application web page authorization login to obtain identity
How to load 100000 pieces of data in leaflet
USTC of China University of science and technology: Minrui Wang | distribution network voltage stabilization based on transformer Multi-Agent Reinforcement Learning
Buuctf-[ciscn 2019 preliminary]love math
随机推荐
How to make maputnik, a vector tile matching artifact, support GeoServer
PMP test difficulty and pass rate
Binary search the specified number of numbers in the array binary advanced
Machining Industry MES system Mold Industry MES system CNCl Medium Industry MES System MES code scanning and reporting MES data collection
[LeetCode]3. The longest substring without duplicate characters forty
【Matlab】矩阵操作
PMP renewal | PDU specific operation diagram
63. different paths II
[matlab] matrix operation
【Matlab】符号计算
Learn to divide subnets in an article
Distributed lock implementation
浏览器缓存的执行流程
MySQL index
H5時代leaflet中還在用DivIcon?
What can PMP bring to you
Start blogging
What are the levels of safety accidents
哲學和文學的區別
Can branches sign labor contracts with employees