LAB3 PartB page error, breakpoint exception and system call


Processing page error

Breakpoint Exception

system call

User mode startup

Page error and memory protection

Processing page error

         Page error exception, interrupt vector 14(   T_PGFLT) is a particularly important anomaly. We will practice a lot in this experiment and the next experiment. When a page error occurs in the processor, it stores the linear (i.e. virtual) address causing the error in the special processor control register CR2. In trap.c,   We provide the beginning of a special function   page_fault_handler() is used to handle page error exceptions.

        exercise5: trap_ Modify the dispatch so that the page error exception is assigned to the page_default_handler. One thing to note about the value is through trap_no determines the index of the exception in the IDT because trap_no push es num in the tracker.

//Note that this is return, otherwise the printframe information will be wrong.

	switch (tf->tf_trapno){
		case T_PGFLT:

Breakpoint Exception

         Breakpoint exception, interrupt vector 3(   T_BRKPT), usually used to allow the debugger to insert breakpoints in program code by temporarily replacing relevant program instructions with special 1-byte int3 software interrupt instructions. In JOS, we will abuse this exception slightly and convert it into a raw pseudo system call that any user environment can use to call the JOS kernel monitor. If we think of the JOS kernel monitor as the original debugger, this usage is actually somewhat appropriate. For example, the user mode implementation of panic() in lib/panic.c is executed after int3 displays its panic message.

         Exercise 6: same as the previous implementation.

case T_BRKPT:

Question3: depends on the dpl setting when initializing the breakpoint entry.

SETGATE(idt[T_BRKPT],0,GD_KT,trap_brkpt,3);//CPL < = DPL, int $3, user status, dpl=3

         If DPL is set to 0, a protection exception is generated, because INT is a system level instruction and is currently in user status. If DPL is set to 3, it cannot be carried out correctly, resulting in a general protection exception. When DPL is set to 3, the user state can be accessed and a breakpoint exception is thrown.

Question4: the key point is to distinguish whether the current permission can execute instructions correctly. That is to ensure that DPL > = CPL to prevent the use of kernel state instructions in user state.

system call

         User processes call system calls to ask the kernel to do things for them. When the user process calls the system call, the processor enters the kernel mode. The processor and the kernel cooperate to save the state of the user process. The kernel executes appropriate code for system call, and then restores the user process. The exact details of how a user process attracts the kernel's attention and how it specifies the call to execute vary from system to system.

         In the JOS kernel, int is used   An instruction that causes a processor interrupt. In particular, int  $ 0x30   Used as a system call interrupt. T_SYSCALL is defined as constant 48 (0x30). You must set the interrupt descriptor to allow the user process to cause the interrupt. Note that interrupt 0x30 cannot be generated by hardware, so there is no ambiguity that allows user code to be generated.

         The application will pass the system call number and system call parameters in the register. In this way, the kernel does not need to wander around the stack or instruction flow of the user environment. The system call number will go to% eax, and the parameters (up to 5 of them) will go to% edx,  % ecx,% ebx,% edi, and% esi. The kernel returns the return value back to% eax   The assembly code to call the system call has been written for you,   syscall() is in lib/syscall.c.



			result=syscall(tf->tf_regs.reg_eax,tf->tf_regs.reg_edx,tf->tf_regs.reg_ecx,tf->tf_regs.reg_ebx,tf->tf_regs.reg_edi,tf->tf_regs.reg_esi);//The system call number and parameters are stored in the register
tf->tf_regs.reg_eax=result;//Save results to eax


syscall(uint32_t syscallno, uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4, uint32_t a5)
	switch (syscallno) {//Different system call numbers return different functions
	case (SYS_cputs):
			sys_cputs((const char*)a1,a2);
			return 0;
	case (SYS_cgetc):
			return sys_cgetc();
	case (SYS_getenvid):
			return sys_getenvid();
	case (SYS_env_destroy):
		return sys_env_destroy(a1);
		return -E_INVAL;


        The function here is a function invoked by the user process. The file under user calls the function in lib/syscall to cause system interruption. Then the syscall in lib triggering system interruption through T-SYSCALL, enters the kernel, and then calls the specific processing function in kern/syscall, such as cprintf in user program hello. That is, after the system interrupts the kernel, it calls the cprintf in the kernel.

static inline int32_t
syscall(int num, int check, uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4, uint32_t a5)
// General system call: pass the system call number in AX. There are up to five parameters in DX, CX, BX, DI and SI. Use T_SYSCALL interrupts the kernel.
// "volatile" tells the assembler not to optimize this instruction just because we don't use it
 Return value.
// The last clause tells the assembler that this can potentially change the condition code and any memory location. 
	int32_t ret;

	asm volatile("int %1\n"
		     : "=a" (ret)//=Represents the output operand, accessed with% 1 and saved with eax
		     : "i" (T_SYSCALL),//Immediate integer operand
		       "a" (num),//eax
		       "d" (a1),//edx
		       "c" (a2),
		       "b" (a3),
		       "D" (a4),
		       "S" (a5)
		     : "cc", "memory");

	if(check && ret > 0)
		panic("syscall %d returned %d (> 0)", num, ret);

	return ret;

GCC inline compilation foundation - brief book

User mode startup

         The user program runs from lib/entry.S. After some settings, this code is called in lib/libmain.c. Modify libmain() to initialize the global pointer   Thisenv to point to this environment in the array struct Env   envs[]. In the case of make run Hello, print before“   hello, world  ” After, it will try thisenv - > env_id  

         This is the reason why it went wrong earlier. After adding thisenv to point to the correct env, the access id will not go wrong again. However, since the kernel currently supports only one environment, it will exit the environment after completion.

        entry.s first sets the data segment of the user mode, and then sets the entry of the new process in the code segment. First check whether the top of the current user stack is at USTACKTOP. If the parameter does not exist, manually add the virtual parameter and then execute the contents of libmain. argc refers to the number of parameters, argv is the array of parameter addresses, argv[0] Save the name of the program. Then switch to the current user environment and execute the user program umain().

libmain(int argc, char **argv)

	// save the name of the program so that panic() can use it
	if (argc > 0)
		binaryname = argv[0];
	// call user main routine
	umain(argc, argv);
	// exit gracefully

Page error and memory protection

         Memory protection is an important function of the operating system, which can ensure that errors in one program will not damage other programs or the operating system itself.

         The operating system usually relies on hardware support for memory protection. The operating system lets the hardware know which virtual addresses are valid and which are invalid. When a program attempts to access an invalid address or an address that does not have permission to access, the processor stops the program at the instruction that causes the error, and then captures information about the attempted operation into the kernel. If the fault is repairable, the kernel can To fix it and let the program continue. If the fault cannot be repaired, the program cannot continue because it will never cross the instruction that caused the fault.

         As an example of a repairable fault, consider automatically extending the stack (UXSTACKTOP). In many systems, the kernel initially allocates a single stack page, and then if the program makes an error accessing the pages below the stack, the kernel will automatically allocate these pages and let the program continue to run. By doing so, the kernel allocates only the stack memory required by the program, but the program can work under the illusion of having any large stack.         

         System calls pose an interesting problem for memory protection. Most system call interfaces let user programs pass pointers to the kernel. These pointers point to the user buffer to read or write to. The kernel then dereferences these pointers when making system calls. There are two problems:

          Page errors in the kernel can be much more serious than page errors in user programs. If a page error occurs when the kernel operates its own data structure, it is a kernel error, and the error handler should panic the kernel (and the whole system). But when the kernel dereferences the pointers given to it by the user program, it needs a way to remember that any page errors caused by these dereferences actually represent the user program.

          The kernel usually has more memory permissions than user programs. The user program may pass a pointer to a system call that points to memory that the kernel can read or write to but the program cannot read. The kernel must be careful not to be deceived into dereferencing such pointers, as this may reveal private information or destroy the integrity of the kernel.         

         Therefore, the kernel must be very careful when dealing with pointers provided by user programs. When passing a pointer to the kernel through a system call function, you should check whether its address space is user space and whether the page table allows memory operation.


        kern/trap.c, change page_fault_handler(), if the page error occurs in the kernel, a panic occurs.

	if((tf->tf_cs&3)==0)//cpl, if the current privilege level is 0, it is in the kernel
		panic("page fault happens in kernel mode!adress:%d",fault_va);

        kern/syscall.c, complete user_mem_check(), check the parameters of the system call and whether inaccessible memory has been accessed.

        The note says that there is no need for page alignment, just check the upper limit of the page by two more, but there is a problem in the implementation, and finally the alignment method is changed. Another thing to note is that after rounding down, if there is a problem accessing the first page, you should pay attention to whether the address of the first page returns the original va or the aligned address.

user_mem_check(struct Env *env, const void *va, size_t len, int perm)
	// LAB 3: Your code here.
	size_t  start=(size_t)ROUNDDOWN(va,PGSIZE);
	size_t end=(size_t)ROUNDUP((uint32_t)va+len,PGSIZE);
		pte_t* pte=pgdir_walk(env->env_pgdir,(const void*)start,0);

	user_mem_check_addr=(uintptr_t) (start<(size_t) va?(size_t)va:start);//start<va,the first is va rather than start
			return -E_FAULT;

	return 0;

        Finally, the user in kdebug.c_ mem_ There's nothing to say about the use of check. Just call it.

if(user_mem_check(curenv,(const void*)usd,sizeof(struct UserStabData),PTE_U)!=0)
		return -1;

stabs = usd->stabs;
stab_end = usd->stab_end;
stabstr = usd->stabstr;
stabstr_end = usd->stabstr_end;
// Make sure the STABS and string table memory is valid.
// LAB 3: Your code here.
if(user_mem_check(curenv,(const void*)stabs,sizeof(struct Stab),PTE_U)!=0)
		return -1;
if(user_mem_check(curenv,(const void*)stabstr,(size_t)(stabstr_end-stabstr),PTE_U)!=0)
		return -1;

The above is LAB3partB, which mainly deals with page errors.

Tags: C Operating System risc-v

Posted on Sun, 19 Sep 2021 05:02:13 -0400 by mrjameer