[异常] 关于创建init进程时产生的异常

前言

这篇blog是关于在《一个64位操作系统的设计与实现》中,第4.8节创建init进程产生的异常。会出现什么异常?如何解决?是这篇blog的重点。

涉及异常种类

助记符描述触发源
#GP(general protection)通用保护异常任何内存引用和保护检测
#PF(page fault)页错误任何内存引用
#UD(undefined opcode)无效/未定义的机器码UD2指令或保留机器码

正文

一、首先出现的是,#GP异常。

​ 先说结论:这个问题是示例代码的问题。
#GP异常​通常会打印出出现异常的RIP地址,通过objdump -D system反汇编源代码,根据RIP地址定位到__switch_to函数的retq指令,当__switch_to函数执行完毕后,会跳转到kernel_thread_func模块,通过打印该模块的函数地址,发现地址异常,示例代码是这样写的:

extern void kernel_thread_func(void);
__asm__ (
	"kernel_thread_func:	\n\t"
	"	popq	%r15		\n\t"
	"	popq	%r14		\n\t"	
	"	popq	%r13		\n\t"	
	// ...
);

猜想,这样定义函数时,函数地址的位置并不是我们想要的'正确地址',所以我通过另外一种方式定义该函数:

extern void kernel_thread_func(void);
void kernel_thread_func() {
	__asm__ (
		"	popq	%r15		\n\t"
		"	popq	%r14		\n\t"	
		"	popq	%r13		\n\t"	
		// ...
	);
}

成功解决该问题,但随之出现另外一个问题 #PF异常 ​。

二、#PF异常

​ 先说结论:作者在书籍中遗漏了一些注释代码,即有一小段代码需要注释,但是在书籍中未提到,源码中该段代码被注释,这段代码是:

// ~ kernel/memory.c

    // PML4页表中的前10个页表项清零(第一项清零就可以了),不会立即生效,需要调用flush_tlb函数
	// for(int i = 0;i < 10;i++) {
	// 	*(Phy_To_Virt(Global_CR3)  + i) = 0UL;
    // }

还有另一小段在书籍中未提到的也需要更改:

// ~ kernel/head.S
原句:
MOVQ $0xffff800000007E00, %RSP
更改后:
MOVQ _stack_start(%rip), %RSP

同样,#PF异常打印的异常信息得知CR2 = 0x101000, 以及RIP信息。通过全局搜索0x101000这个地址,最初是在head.S中赋值给CR3控制寄存器,并且通过输出测试以及更改CR3值,发现的确是关于CR3控制寄存器而出现的异常;
CR3值获取,是在Get_gdt函数中,在内存管理memory.c/init_memory()函数中,通过对比源码,发现原来不用不用注释的代码在新的一节中被注释;注释掉该段代码后,该异常消失,但又出现了 #UD异常

三、#UD异常

先说结论:为什么出现这个异常,我还没有找到具体原因,只理清大部分头绪,该异常可能会复现,但至少在init进程运行时已正常。导致该异常的原因是:栈不平衡?虚拟机问题?代码问题?

这个异常最难调试,相比较前两者而言,这个异常提供的信息少之又少;我是通过输出调试以及debug调试才找到原因所在;在调试过程中,有许多错误猜想,虽然对得到结果没什么用处,但是对理解创建init进程以及切换进程更加熟悉,直接说最后的正确步骤。

[更新]:须确定要切换进程的RIP寄存器值是否正确。

通过输出测试,定位到kernel_thread_func函数,debug反汇编代码,发现本来要执行init进程的指令跳到了0x00000000地址,而产生异常;init进程入口地址的地址信息存储在哪?RBX寄存器(在kernel_thread函数中赋值)。然后再次debug代码,通过调试(先break __switch_to的retq指令, 即在bochs执行b 0xa9de0xa9de是retq指令的物理地址,再一步步执行),发现pop rbx时,rbx是0x00000000,猜想,什么导致栈不平衡,没有正确返回?

通过在bochs调试r指令打印寄存器信息,发现RCX存储的就是init进程的入口地址!为什么会到了RCX而不是RBX?而函数参数原本存储在RDX中,结果发现函数参数存储在RDI中?为什么会这样。奇奇怪怪的存放顺序。书中提到,应用程序在进入内核层时已将进程的执行现场(所有通用寄存器值)保存起来,在哪里保存的?以何种形式保存?按道理来讲,即使压栈,压栈的顺序跟弹出的顺序一致(逆序),暂时无法解决该问题。

解决方式就是将kernel_thread_func函数中callq %rbx改成callq %rcx

	"	addq	$0x38,	%rsp	\n\t"
	"	movq	%rdx,	%rdi	\n\t"
	"	callq	*%rcx			\n\t"
	"	movq	%rax,	%rdi	\n\t"
	"	callq	do_exit			\n\t"

此上,解决了书籍中4.8节遗留的问题,我试着运行了源代码,发现同样出现了异常。