Jos-lab1

Author Avatar
Descatles 3月 20, 2017

jos的启动过程总结如下:

首先是BIOS加载。然后从BIOS跳转至Boot loader。Boot loader 做了一些初始化工作如禁止中断,打开 A20地址线等,然后载入GDT,并从实模式跳转到32位指令的保护模式下。在初始化了寄存器后,读取硬盘第一页上的ELF文件,将内核载入内存,最终进入内核。

内核首先执行 entry.S 文件,entry.S 先将寻址模式从32位段模式转为32位段页模式,然后初始化栈空间,跳转到 i386_init中,接下来的过程都能够在终端中反映出来,在exercise中也会涉及到。

其中,BIOS部分是由QEMU提供的,不属于JOS。

以下是对此次lab中一些问题以及设计的回答:

(1) At what point does the processor start executing 32-bit code? What exactly causes the switch from 16- to 32-bit mode?

  # Jump to next instruction, but in 32-bit code segment.
  # Switches processor into 32-bit mode.

  ljmp$PROT_MODE_CSEG, $protcseg

.code32 # Assemble for 32-bit mode

.code32开始,处理器执行32位的代码。
ljmp $PROT_MODE_CSEG, $protcseg指令使得从16位模式切换至32位模式。

(2) What is the last instruction of the boot loader executed, and what is the first instruction of the kernel it just loaded?

(void (*)(void)) (ELFHDR->e_entry))();这行代码是boot loader最后执行的代码,而最后一条执行的汇编指令为call *0x10018
。Kernel刚加载时的第一条指令为movw $0x1234,0x472

(3) 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?

Bootloader 在内核的ELF文件头中可以获得关于需要读多少sectors的信息。

Exercise 5.

在从BIOS进入boot loader时,这8个words都是0。在从boot loader进入kernel后,则有真正的数据了。因为在第一个断点时还没有将内核代码载入至内存,第二个断点时则已经载入进去。在第二个断点时的内容是elf内核文件的内容。

Exercise 6.

可以发现,修改只对相对跳转的语句有影响。

Exercise 7.

Jmp *%eax 是旧的mapping依旧存在时出错的第一条指令。

Exercise 8.

照着前面的写即可,具体代码如下

case 'o':
            // Replace this with your code.
            // display a number in octal form and the form should begin with '0'
            putch('0', putdat);
            num = getuint(&ap, lflag);
            base = 8;
            goto number;

Exercise 9.

init.c中的代码cprintf("show me the sign: %+d, %+d\n", 1024, -1024);中了解到,就是在有加号的情况下需要把正或负号全表示出来。所以只需加入一个对是否是加号的判断,若是,则在数字是正数时在前面加上正号。

Exercise 10.

首先要判断要写入的值的指针是否是空指针,并判断指针要指向的值是否已溢出,若是,则输出已提供的错误信息。最后赋值。

Exercise 11.

首先判断padc是否是’-‘,如果不是,按原来的函数处理,若是,进入新的函数。新的函数如下

static void
printnum2(void (*putch)(int, void*), void *putdat,
     unsigned long long num, unsigned base, int width, int padc)
{
while(num>=base)
    {
   putch("0123456789abcdef"[num % base], putdat);
       num=num/base;
       width--;
    } 

    // then print this (the least significant) digit
    putch("0123456789abcdef"[num % base], putdat);
while (--width > 0)
    {
      putch(' ', putdat);
    }    
}

即先输出数字的表示,不满的部分再用空格填补。

Exercise 14.

代码如下

uint32_t *ebp = (uint32_t *) read_ebp();
while (ebp != NULL) 
{
    uint32_t* eip=(uint32_t*)(ebp+1);
    cprintf("  eip %08x  ebp %08x  ",*eip, ebp);
    cprintf(" args");
    int i=0;
    for(i=0;i<5;i++)
    {
        cprintf("  %08x",*((uint32_t*)(ebp+2+i)));
    }
    cprintf("\n");
    ebp = (uint32_t *) (*ebp);    
}    

read_ebp()函数得到当前的ebp位置后,相对ebp向上得到eip以及各参数值,最后从ebp寄存器得到外部函数的保存的ebp位置,循环指指针为空。需要注意的是打印的格式。

Exercise 15.

这个练习主要的部分只要在上个练习的循环中加入如下的代码:

struct Eipdebuginfo info;
debuginfo_eip(*eip, &info);
cprintf("\t%s:%d: %.*s+%d\n", info.eip_file,
  info.eip_line,info.eip_fn_namelen,
         info.eip_fn_name,*eip-info.eip_fn_addr);

只是对Eipdebuginfo类的一些使用以及打印,应该不需要赘述。

stab_binsearch(stabs, &lline, &rline, N_SLINE, addr);
if (lline<=rline)
{
    info->eip_line = stabs[lline].n_desc;
}
else 
{
    info->eip_line = -1;
}

kdebug.c中也要加入如下查找行号的代码。

为了能直接使用backtrace命令而不只是通过测试,需要在Command中注册一下

Exercise 16.

为了这个练习需要把start_overflow()中的return address替换为do_overflow()的地址,但因为要保证正常退出,需要把return address+4的地方填上本来应该返回的地址,以便从do_overflow()返回到本来应该返回的overflow_me()

代码如下:

char str[256] = {};
int nstr = 0;
char *pret_addr;
uint32_t addr = (uint32_t) do_overflow;    
pret_addr = (char *) read_pretaddr();
int i;
for(i=0;i<4;i++)
{
    uint32_t addr1;
    memset(str,0xd,256);
    addr1=(uint32_t)((*(pret_addr+i))&0xff);
    str[addr1]='\0';
    cprintf("%s%n", str, pret_addr+4+i);
    memset(str,0xd,256);
    addr1=(uint32_t)((addr >> (8*i)) & 0xff);
    str[addr1]='\0';
    cprintf("%s%n", str, pret_addr+i);
}

Exercise 17.

实际上就是调用rdtsc里的函数read_tsc(),计算下执行命令之间的cycle数并输出。也要在Command中注册下。

代码如下:

int
mon_time(int argc, char **argv, struct Trapframe *tf)
{
    uint64_t start;
    uint64_t end;
    uint64_t time;

    // Lookup and invoke the command
    if (argc == 1) {
        cprintf("time [command]\n");
        return 0;
    }
    int i=0;
    for (i = 0; i < NCOMMANDS; i++)
    {
        if (strcmp(argv[1], commands[i].name) == 0) 
        {
            argc=argc-1;
            argv=argv+1;
            start=read_tsc();
            commands[i].func(argc,argv,tf);
            end=read_tsc();
            time=end-start;
            cprintf("%s cycles: %llu\n",argv[0],time);
            return 0;
        }
    }
    cprintf("Unknown command '%s'\n", argv[1]);
    return 0;
}