MIT6.828 lab1地址:http://pdos.csail.mit.edu/6.828/2014/labs/lab1/

第一个练习,主要是让我们熟悉汇编,嗯,没什么好说的。

Part 1: PC Bootstrap

首先,整个实验使用qemu这款模拟软件来,来对代码进行调试,相当于我们在qemu这个模拟的计算机平台上,运行自己的程序。可以再qemu这个软件上进行gdb的调试,比较方便。

首先看下整个内核在qemu上的模拟的结果:

整个内核现在能实现的就两个功能,一个kerninfo,显示内核的一些简单信息,还有个help,暂时什么都没有。这里先来看下kerninfo里面的东西,主要是内核的一些入口地址,数据,程序地址什么的。这里可以看到,虚拟内存和物理内存的映射就是简单的线性映射,比较直观。

接下来看一下系统的具体的启动过程是怎么样的:

这张图是整个系统的物理内存的分布。主要看一下地址在0x100000(1MB)以下的内存分布情况,系统启动的时候主要是在1M以下的地方活动。对于很多PC来说,1M一下的存储布局都是差不多的,这个主要是因为最开始的Intel的的8088一开始内存只有1M,所以后面的计算机为了向下兼容,在1M一下的内存布局都是这样的。

这里主要是看下BIOS,BIOS是Basic Input/Output System,主要的工作是做各种初始化,比如检查一下内存的大小,还有显卡等一些外设的初始化,然后会载入OS,运行内核。

接下来看一下BIOS的具体的引导步骤:

首先,打开两个终端,分别输入命令:make qemu-gdb和gdb

打开gdb,就可以看到下面的内容:

程序是从地址0xffff0开始的,而且一开始就来一个长跳转。开始地址是0xffff0的原因是,以前的PC因为只有1M的内存,采用的是实地址模式,为了在开机和重启的时候能够保证跳到BIOS里面,就把开始的地址设定为0xffff0,因为那个地址已经非常的接近1M地址的顶部,在那里不会有其他程序。

可以看到,在左边,有一个[f000:fff0],程序地址就是通过这个来计算的。这两个分别是寄存器cs和eip的值。

通过gdb也可以看到,eip是0xfff0,cs是0xf000。其中,cs是段地址。程序地址的寻址是通过下面的方法来计算的:

addr=cs*16+eip

通过计算,就可以得到现在程序指向的地址:0xffff0

程序地址这么计算的原因是因为,在以前的8088上,地址线有20根,而数据线只有16根,这样就没有办法用16根数据线来表示20位的地址。所以后面就用这个方法,采用段地址,来进行数据线的扩展,来进行寻址操作。后面的计算机数据线和地址线就没有这个问题了,但为了向下兼容以前的程序,在BIOS里面,还是采用这种方式来进行寻址。

练习二:熟悉gdb的si指令。

接下来的代码,感觉没什么特别的,然后下面的就全是BIOS的一些常规设置。

Part 2: The Boot Loader

等BIOS执行完之后,接下来就会跳转到引导扇区了,引导扇区中的内容被再到内存地址为0x7c00---0x7dff上,然后执行引导程序。
If the disk is bootable, the first sector is called the boot sector, since this is where the boot loader code resides. When the BIOS finds a bootable floppy
or hard disk, it loads the 512-byte boot sector into memory at physical addresses 0x7c00 through 0x7dff, and then uses a jmp instruction to set the CS:IP to 0000:7c00, passing control to the boot loader.
引导程序主要做两件事:1.将系统从实模式转化为32位保护模式,在保护模式下,才能访问全部的地址空间(>1M的地址)2.载入内核文件。
关于保护模式和实地址模式:

In the protected mode, selector values are interpreted completely differently than in real mode. In real mode, a selector value is a paragraph number of physical memory. In protected mode,
a selector value is an index into a descriptor table. In both modes, programs are divided into segments. In real mode, these segments are at fixed positions in physical memory and the selector value denotes the paragraph number of the beginning of the segment.
In protected mode, the segments are not at fixed positions in physical memory. In fact, they do not have to be in memory at all!

In protected mode, each segment is assigned an entry in a descriptor table. This entry hasall the information that the system needs to know about the segment. Thisinformation includes: is
it currently in memory; if in memory, where is it;access permissions (e.g., read-only). The index of the entry of the segment is the selector value that is stored in segment registers.

 

直接在gdb中打断点,到0x7c00

其中,进行一系列的设置,并且载入了GDT,来进入保护模式。最后一句:

mov %eax,%cr0

eax当时的值是0x11,cr0被赋值为0x11.

来看一下cr0寄存器:

可以看到,上面的指令是将cr0的第0位置1,PE=1时,系统进入保护模式。

所以系统从0x7c2a之后,就进入了保护模式。

这里就可以回答第一个问题:

  • At what point does the processor start executing 32-bit code? What exactly causes the switch from 16- to 32-bit mode?
  • 系统从0x7c2a之后就开始实行了32位编码。从实模式转换到保护模式,使系统从16位转到了32位。
 

接下来可以看到,程序设置了各种段寄存器,并且最后设置了栈指针为0x7c00,因为引导程序是在0x7c00上的,而栈是向下增长的,所以,在引导程序里面,就把栈地址0x7bff(0x7c00-1,push会先减4(一个字的大小),在存入数据)和下面的空间设置为程序的栈空间,这一段地址空间是属于上面提到的1M地址空间里面的low memory的区域。

接下来,程序进入了bootmain(0x7d9b),程序的主要作用是去引导ELF kernel image的。

bootmain程序里面的最后一句:

从这里可以看到,程序进入了entry,即内核的开始入口地址,而这也是整个boot程序的最后一句。

  • What is the last instruction of the boot loader executed, and what is the first instruction of the kernel it just loaded?
  • 最后一句的就是上面的那句转入entry的代码。内核的第一句指令通过查看entry地址可以看到。要找entry的地址,只要反汇编内核程序就可以

在反汇编得到的信息里面查找entry,可以得到entry的地址:

entry的地址就在0xf010000c里,不过那个是虚拟地址,实际的载入地址是0x10000c,这里后来看了其他文章,知道0xf010000c被硬件转换为0x10000c,在以前的6.828课程里面,这种转换是手动转化,现在成了硬件自动转换:

09年和10年的课程代码:(还是以前的代码清晰,现在的代码没有手动转换,一开始在0xf010000c位置打断点,等了半天)

在那个地址打断点,得到的指令是:

接下来第三个问题也可以回答了:

3.Where is the first instruction of the kernel?

0x10000c第一条指令位置。

4.How does the boot loader decide how many sectors it must read in order to fetch the entire kernel from disk? Where does it find this
information?

应该是通过ELF文件知道需要的信息。下面是通过反汇编命令(objdump -h kernel)得到的文件头,应该是载入这些:

Loading the Kernel

ex4主要是先来一遍C指针的坑,里面的东西比较简单,都是基本的C指针,就不展开了。
接下来主要讲的是一些关于ELF的东西,ELF里面有一个固定大小的ELF header,里面主要要注意的是以下部分:
.text:程序的代码部分
.rodate:程序的只读数据部分。
.date:程序中被初始化的全局变量部分。
.bss:程序未初始化全局变量的保存地址。程序不给.bss段分配额外空间,只是记录其地址空间的大小,因为未初始化全局变量默认初始化为0;的未初始化全局变量默认为0;
通过反汇编命令:

objdump -h obj/kern/kernel
 
就可以查看,上面有图,EFL好像主要是给链接器用的,主要作用可以当成是程序载入物理内存的一个指引。
这里主要注意VMA是虚拟地址,LMA是载入地址,也就是这段内容需要被载入的物理内存的地址。在最后会有个LOAD的标志,表示需要被载入,想.bss和.comment里面的内容就不用被载入内存。
 

第五个练习主要是要改变链接器的链接地址,这里一开始按照题目要求盖面boot/Makefrag里面的-text的地址没有用……

后来知道,是要改变kernel.ld里面的内核载入地址的值:

这里吧AT(0x10000)里面的地址改变,就会出错,qemu一直在booting from hard disk界面跳,没有办法载入内核。

bootload的程序的入口是0x7c00,打断点,查看内存地址:

在0x10000开始的地方,全部都是0.接下来是内存入口的地址0x10000c打断点

可以看到,在进入内核后,在0x10000地址的物理内存的地方,已经有了程序了。这个很好解释,因为内核的程序载入是在bootload的程序里面完成的,在刚刚进入bootload程序的时候,0x10000地址里面当然是空的。在bootload执行完成之后,内核程序已经载入了内存。

Part 3: The Kernel

这个主要讲保护模式下,虚拟内存的一些东西。

可以看到两句指令:

or $0x80010001,%eax
mov %eax,%cr0

这两条指令是把cr0的PE,PG,WP三个控制开关置1.

PE:置位是进入保护模式。启用分段管理模式。当PE=1,PG=0,此时只进入分段模式,所有的线性地址等于物理地址。

PG: 置位是进入了分页模式,线性地址需要经过一定的转化才是物理地址

WP: 当设置该标志时,处理器会禁止超级用户程序(例如特权级0的程序)向用户级只读页面执行写操作。

此时查看0x100000和0xf0100000,地址都是实际的物理地址,没有经过转化,所以0xf0100000和0x100000是没有关系的,不存在映射:

当设置了cr0寄存器之后,启动了分页机制,0x10000和0xf010000是对应的,具有映射关系,其实0xf0100000就是物理内存上0x100000上的内容。

如果分页机制没有准确执行的话,那通过指令:

mov $0xf01002f,%eax
jmp *%eax

当执行到jmp的时候,就会报错,因为没有启动分页模式,那0xf010002f就是实际的物理地址,那个地址里面是0,跳转过去无法执行指令。

Formatted Printing to the Console

第八个实验主要是看print(),这个我会在另外一篇详细分析一下print()这个函数。

The Stack

后面的实验主要是将栈,这个玩过csapp的实验,还是可以接受的。

首先是内核栈的初始化:

可以看到,设置完了cr0寄存器,马上就设置了esp寄存器,esp设置为0xf0110000.

最后一个练习主要是写一个backtrace函数,这个主要是根据现在已知的ebp和esp,采用回溯的方法,来得到程序的frame信息。有点类似于gdb里面的bt命令。这个只要了解在程序调用和程序返回的过程中,esp,eip,ebp三个指针的变化过程就可以了。在另一篇文章里面详细讲述了这方面的东西,这里就不展开了。

最后写的代码:

int
mon_backtrace(int argc, char **argv, struct Trapframe *tf)
{ struct Eipdebuginfo info;
unsigned int *ebp=(unsigned int *)read_ebp();
unsigned int *esp=(unsigned int *)read_esp();
unsigned int *eip=0;
unsigned int arg[5];
int i=0;
while(ebp)
{
for(i=0;i<5;i++)
arg[i]=*(ebp+i+2);
eip=ebp+1;
debuginfo_eip(*eip,&info);
cprintf(" ebp %08x eip %08x args ",(unsigned int)ebp,*eip );
for(i=0;i<5;++i)
cprintf("%08x ", arg[i]);
cprintf("\n"); cprintf("\t\t%s:%u:%.*s+%u\n",
info.eip_file,
info.eip_line,
info.eip_fn_namelen,
info.eip_fn_name,
*eip-info.eip_fn_addr);
esp=ebp+2;
ebp=(unsigned int *)*ebp;
}
return 0;
}

结果:

下面的是在gdb里面bt命令给出的结果:

可以看到,eip都是对的,还有调用的函数也是对的。但是位置有点问题,这个主要是通过它给的程序,程序通过stba来给出调试信息,stba这方面的东西也不是太懂,就不深究了。下次看看有机会碰到这方面的内容再说吧。

版权声明:本文为博主原创文章,未经博主允许不得转载。

6.828 lab1 bootload的更多相关文章

  1. 《MIT 6.828 Lab1: Booting a PC》实验报告

    <MIT 6.828 Lab1: Booting a PC>实验报告 本实验的网站链接见:Lab 1: Booting a PC. 实验内容 熟悉x86汇编语言.QEMU x86仿真器.P ...

  2. 《MIT 6.828 Lab 1 Exercise 7》实验报告

    本实验链接:mit 6.828 lab1 Exercise 7. 题目 Exercise 7. Use QEMU and GDB to trace into the JOS kernel and st ...

  3. 《MIT 6.828 Lab 1 Exercise 4》实验报告

    本实验链接:mit 6.828 lab1 Exercise 4. 题目 Exercise 4. Read about programming with pointers in C. The best ...

  4. 《MIT 6.828 Lab 1 Exercise 3》实验报告

    本实验的网站链接:mit 6.828 lab1 Exercise 3. 题目 Exercise 3. Take a look at the lab tools guide, especially th ...

  5. 《MIT 6.828 Lab 1 Exercise 2》实验报告

    本实验链接:mit 6.828 lab1 Exercise2. 题目 Exercise 2. Use GDB's si (Step Instruction) command to trace into ...

  6. MIT 操作系统实验 MIT JOS lab1

    JOS lab1 首先向MIT还有K&R致敬! 没有非常好的开源环境我不可能拿到这么好的东西. 向每个与我一起交流讨论的programmer致谢!没有道友一起死磕.我也可能会中途放弃. 跟丫死 ...

  7. 《ucore lab1》实验报告

    资源 ucore在线实验指导书 我的ucore实验代码 练习1:理解通过make生成执行文件的过程 详见<ucore lab1 exercise1>实验报告 练习2:使用qemu执行并调试 ...

  8. MIT6.S081/6.828 实验1:Lab Unix Utilities

    Mit6.828/6.S081 fall 2019的Lab1是Unix utilities,主要内容为利用xv6的系统调用实现sleep.pingpong.primes.find和xargs等工具.本 ...

  9. MIT 6.828 | JOS | 关于虚拟空间和物理空间的总结

    Question: 做lab过程中越来越迷糊,为什么一会儿虚拟地址是4G 物理地址也是4G ,那这有什么作用呢? 解决途径: 停下来,根据当前lab的进展,再回头看上学期操作系统的ppt & ...

随机推荐

  1. windows7下virtualBox配置识别usb

    在windows7下安装virtualBox后.在虚拟机里面是不能识别手机的,此时我们须要做一些配置. 一. virtualBox菜单: 管理–全局设定–扩展–加入包(右側,virtualBox ex ...

  2. 百度地图 Android SDK - Hello Baidu Map

    例如,给广大以下主要开发者介绍了如何使用百度地图Android SDK构造的主应用程序的地图! 第一步.创建Androidproject,将百度地图Android SDK的开发包导入到project对 ...

  3. IDA strings view 中文字符的显示

    具体原理不清楚,在网上找了找,记录下. 第一步:将ida.cfg中cpp866 version的AsciiStringChars注释掉,把full version的AsciiStringChars取消 ...

  4. ORACLE 热备begin backup / end backup

    执行begin backup之后,oracle会把将要备份的数据文件都标记为hot-backup-in-progress,锁定所要备份的datafile header的scn,例如此时scn=100, ...

  5. 键盘事件之keydown keypress keyup区别

    经过测试,显然事件执行的顺序是: keydown->keypress->keyup. 但是连续按一个按键的话,会一直触发:keydown keypress.直到你提起按键,会触发keyup ...

  6. java中驼峰与下横线格式字符串互转算法

    public static final char UNDERLINE = '_'; /** * 驼峰格式字符串转换为下划线格式字符串 * * @param param * @return */ pub ...

  7. oracle查询表信息

    oracle查询表信息(索引,外键,列等) oracle中查询表的信息,包括表名,字段名,字段类型,主键,外键唯一性约束信息,索引信息查询SQL如下: 1.查询出所有的用户表 select * fro ...

  8. 蜗牛爱课 -- iOS 设计模式之模板模式

    1 前言 模板方法模式是面向对象软件设计中一种非常简单的设计模式.其基本思想是在抽象类的一个方法定义“标准”算法.在这个方法中调用的基本操作由子类重载予以实现.这个方法成为“模板”.因为方法定义的算法 ...

  9. 跳跃表 C#

               虽然avl树和红黑树在数据搜索和排序方面都是有效的数据结构,但是都显得特别麻烦,跳跃表就显得特别简单,虽然简单 不影响他性能,在平均情况下,其插入.删除.查找数据时间复杂度都是O ...

  10. Vijos 1121 马拦过河卒

    首先要看清题目,卒只能向右或者向下走.而不是四周转.这样的话就无解了. 定义f[i][j],表示走到(i,j)这个点时的总步数.这样就写出了一个递推公式f[i][j]=f[i-1]+f[i][j-1] ...