bochs调试Linux0.00
实验报告
请简述 head.s
的工作原理
head.s工作中主要包括初始化和任务切换两部分:
初始化部分主要是重新建立和设置IDT和GDT表,以及设置中断计时器,构建IDT表里的中断门描述符和系统调用陷阱门描述符等。
之后执行到任务0,在中断计时器的作用下,实现任务0和任务1之间的切换操作。
head.s
的内存分布状况,写明每个数据段,代码段,栈段的起始与终止的内存地址
刚跳转到0x0的时候:
数据段起始地址为0x00000000,终止地址为0x007fffff
代码段起始地址为0x00007c00,终止地址为0x00007c00+0x0000ffff
栈段起始地址为0x00007c00,终止地址为0x00007c00+0x0000ffff
初始化结束:
数据段起始地址为0x00000000,终止地址为0x007fffff
代码段起始地址为0x00000000,终止地址为0x007fffff
栈段起始地址为0x00000000,终止地址为0x007fffff
切换到任务0:
数据段起始未初始化
代码段起始地址为0x00000000,终止地址为0x003fffff
栈段起始地址为0x00000000,终止地址为0x007fffff
初始化完:
数据段起始地址为0x00000000,终止地址为0x003fffff
代码段起始地址为0x00000000,终止地址为0x003fffff
栈段起始地址为0x00000000,终止地址为0x003fffff
简述 head.s
57
至 62
行在做什么?
汇编指令:
1 | pushl $0x17 ;任务0当前局部空间数据段(堆栈段)选择符入栈 |
也就是这个时候要进行内核态到用户态之间的切换,从内核程序将控制权转移给任务0程序。
所以按顺序压栈了各种寄存器以及段选择符。
之所以要压栈+iret实现内核态到用户态的访问,
内核是不会调用用户层的代码,要想实现这逆向的转移,一般做法是在用户进程的核心栈 (tss->esp0)压入用户态的SS,ESP,EFLAGS,CS,EIP,伪装成用户进程是通过陷阱门进入核心态,之后通过iret返回用户态
简述 iret
执行后, pc
如何找到下一条指令?
iret等价于
1 | pop ip |
所以iret执行后,会重置大部分寄存器,这个时候ip寄存器发生变化,pc就能找到下一条指令了。
记录 iret
执行前后,栈是如何变化的?
iret前栈帧:
iret后:
可见,主要的有关栈的变化在于ss段寄存器,这里从0x0010变为了0x0017,段选择子发生了变化,选择了不同的段,对内存访问权限发生了变化等,表明从内核态转移到了用户态,访问的是权限等级为用户等级的栈段。
当任务进行系统调用时,即 int 0x80
时,记录栈的变化情况。
切换到任务0之后在0x10e9、0x10fd
系统调用号0x41和0x42
66 | sys_setsid |
---|---|
65 | sys_getpgrp |
Linux系统调用 int 80h int 0x80-CSDN博客
不过该系统调用是进入了我们写好的内核段代码,不会执行上面的linux系统调用列表,这里相当于布置了一个陷阱,int80将会进入我们的函数里。
实际上这段汇编是在无限循环打印A
每次int80 不都会进入一个函数,这个函数是内核态的,用于打印一个字符。
int80实现了用户态到内核态的切换,随后从内核态通过iret返回用户态
切换前:
切换后:
发生了四个寄存器的切换,切换到了中断处理函数那里,所以cs、ss段寄存器发生了变化。