HIT-OS LAB1 调试分析 Linux 0.00 引导程序
调试分析 Linux 0.00 引导程序
请简述 head.s 的工作原理
boot.s 通过 jmpi 0,8 刷新控制寄存器进入保护模式并跳转至 0x0008:0x0000(CS:EIP) 即物理地址 0x00000000 处进入 head.s 程序。head.s 首先初始化段寄存器 DS、SS,堆栈指针 esp 指向 head.s 中事先预留的 init_stack,随后调用函数 setup_idt、setup_gdt 初始化 IDT、GDT 表及 IDTR、GDTR。其中 IDT 的 256 个描述符被设置为指向函数 ignore_int 的中断门,该函数负责向显示终端打印字符 ‘C’,GDT 中设置了 8 个描述符,分别为空描述符[0]、内核代码段描述符[1]、内核数据段描述符[2]、显示终端所需描述符[3]、任务段描述符 TSS0[4]、LDT 描述符 LDT0[5]、任务段描述符 TSS1[6]、LDT 描述符 LDT1[7]。随后,重新设置 DS、ES 等段寄存器以确保其使用新设置的 GDT 并重置 SS:ESP 指向原始堆栈的栈底以清空堆栈,紧接着通过 outb 指令设置 8253 芯片时钟中断信号频率为 100 HZ。在此之后,改写 IDT 表中的 0x08 及 0x80 号描述符分别将其设置为时钟中断及系统中断(系统调用)。在正确设置寄存器 EFLAGS、TR、LDT 后打开中断。为模拟曾经由用户程序 task0 发出系统调用进入内核,手动在内核栈中压入 task0 对应 SS、ESP、EFLAGS、CS:EIP,随后使用 iret 指令隐式进入 task0 代码起始处执行。task0、task1 循环使用系统调用 int 0x80 分别在屏幕中打印字符 ‘A’、’B’,每当时钟中断到来时由 IDT 中 0x08 号指向的中断门陷入 timer_interrupt 中断处理函数,中断处理函数负责交替切换任务 0、1 并正确设置对应任务所需段寄存器,随后使用 iret 隐式跳转至目标任务代码继续执行。如图 1,最终效果呈现为任务 0、1 交替执行,屏幕中循环交替出现由若干个连续 ‘A’、’B’ 所组成的字符串。
请记录 head.s 的内存分布状况,写明每个数据段,代码段,栈段的起始与终止的内存地址
Symbol | Type | Start Addr | End Addr |
---|---|---|---|
startup_32 | Code | 0x00000000 | 0x000000ad |
setup_gdt | Code | 0x000000ad | 0x000000b5 |
setup_idt | Code | 0x000000b5 | 0x000000e5 |
write_char | Code | 0x000000e5 | 0x00000114 |
ignore_int | Code | 0x00000114 | 0x00000130 |
timer_interrupt | Code | 0x0000012a | 0x00000166 |
system_interrupt | Code | 0x00000166 | 0x0000017d |
current | Data | 0x0000017d | 0x00000181 |
scr_loc | Data | 0x00000181 | 0x00000185 |
lidt_opcode | Data | 0x00000186 | 0x0000018c |
lgdt_opcode | Data | 0x0000018c | 0x00000192 |
idt | Data | 0x00000198 | 0x00000998 |
gdt | Data | 0x00000998 | 0x000009d8 |
end_gdt | Stack | 0x000009d8 | 0x00000bd8 |
init_stack | Data | 0x00000bd8 | 0x00000bde |
ldt0 | Data | 0x00000be0 | 0x00000bf8 |
tss0 | Data | 0x00000bf8 | 0x00000c60 |
krn_stk0 | Stack | 0x00000c60 | 0x00000e60 |
ldt1 | Data | 0x00000e60 | 0x00000e78 |
tss1 | Data | 0x00000e78 | 0x00000ee0 |
krn_stk1 | Stack | 0x00000ee0 | 0x000010e0 |
task0 | Code | 0x000010e0 | 0x000010f4 |
task1 | Code | 0x000010f4 | 0x00001108 |
usr_stk1 | Stack | 0x00001108 | 0x00001308 |
简述 head.s 57 至 62 行在做什么?
如图 2,x86 架构在执行中断及系统调用时,会隐式将相关寄存器压栈以保存现场。对于特权级改变的中断及系统调用,硬件会依次向栈中压入调用前的 SS、ESP、EFLAGS、CS、EIP 寄存器并更新 SS、ESP、EFLAGS、CS、EIP 至中断处理函数起始状态,函数执行后使用 iret 命令隐式弹栈恢复 SS、ESP、EFLAGS、CS、EIP 至调用前状态。为实现用户与内核间的隔离,需使用 iret 指令模拟曾经由用户程序 task0 发出系统调用进入内核,因此需手动在内核栈中压入 task0 起始状态对应 SS、ESP、EFLAGS、CS:EIP,随后使用 iret 指令进入 task0 代码起始处执行。
简述 iret 执行后, pc 如何找到下一条指令?
如图 3,iret 执行前(内核)栈中被压入 SS、ESP、EFLAGS、CS、EIP,单步执行后如图 4 所示,CS:EIP 被设置为先前栈中压入的 0x000f:0x000010e0,指向 task0 代码起始地址处。
记录 iret 执行前后,栈是如何变化的?
如图 5、6,SS:ESP 由 0x10:0x00000bc4 变为先前栈中压入的 0x0017:0x00000bd8,指向 task0 用户栈栈底(同时也是先前内核程序所用栈)。
当任务进行系统调用时,即 int 0x80 时,记录栈的变化情况。
如图 7、8,task0 在执行 int 0x80 前,将欲打印字符 ‘A’ 的 ASCII 码 65 放入 eax 寄存器中作为 int 0x80 中断处理函数 system_interrupt 的参数,task0 内核栈krn_stk0 的栈顶 0x00000E60 附近未压入值。
如图 9,执行 int 0x80 中断指令后,SS:ESP 切换至 TSS0 中预设的 0x10:0x00000e60 (krn_stk0) 并压入 SS、ESP、EFLAGS、CS、EIP,对应栈中的值 0x0017、0x00000bd8、0x00000246、0x000f、0x000010eb。
如图 10,由 iret 指令返回时,SS、ESP、EFLAGS、CS、EIP 隐式弹栈填充对应寄存器,指向 int 0x80 下条指令 movl $0xfff, %ecx 地址处执行。
最终效果如图 11,bochs 终端处首字符由 ‘B’ 变为 ‘A’。