• MIT 6.828 Lab 01: Part 3


    Part 03 The kernel

    关于Kernel的链接地址和运行地址
    • OS 习惯于linked and run at 高地址,把低地址留给user program
    • 利用processor 的memory management hardware 实现物理地址(load address)到虚拟地址(link address)的映射

    Exercise 07

    • 首先运行到kernel入口处,查询kernel发现入口地址为 0x0010000c(物理地址,即加载地址) ,

      调试断点的地址是物理地址而非虚拟地址

    • 然后si 直到下一步执行movl %eax, %cr0

      • 执行前:0x100000 后面有指令,但是0xf0100000 没有

      • 执行后:都有,因为此时已经完成了虚拟地址到物理地址的映像

    ※:不知道为什么,断点设置在0x100000 处不行(???)

    0x100000处的反汇编代码如下

    .globl entry
    entry:
    	movw	$0x1234,0x472			# warm boot
    f0100000:	02 b0 ad 1b 00 00    	add    0x1bad(%eax),%dh
    f0100006:	00 00                	add    %al,(%eax)
    f0100008:	fe 4f 52             	decb   0x52(%edi)
    f010000b:	e4                   	.byte 0xe4
    
    f010000c <entry>:
    f010000c:	66 c7 05 72 04 00 00 	movw   $0x1234,0x472
    
    补充知识:cr0
    • cr0全称是control register 0.

    • 把eax赋给cr0时,eax=0x80110001,对应上面的标志位就能知道发出了什么控制信息。

    • 最关键:

      • PG,这个信号打开了页表机制,以后都会自动将 0xf0000000 到 0xf0400000 的虚拟(逻辑)地址转成 0x00000000 到 0x00400000 的物理地址。
      • 所以此处会自动把0xf0100000转换成0x00100000,所以两者的值相等。
    • 如果注释掉这条语句,那么映射失败。下一条指令是jmp *%eax 此时eax的内容是0xf010002f 如果没有映射成功,那会指向这个物理高地址,而不是本应指向的0x100000附近的低地址,就会出错。

    Excercise 08

    • 参照16进制补充如下:

    注:虽然八进制前面需要添加'0',但是参照下面16进制都没有加'0x',所以还是注释掉了(网上有博主说加了之后扣了20分)

    问答:
    1. 第4题中需要用little-endien big-endien ,资料如下

      http://www.cnblogs.com/justinzhang/archive/2011/09/29/2195969.html

      注意:每个地址存1个字节,2位16进制数是1个字节(0xFF=11111111)

    2. 其他解答&解析,如下:

      https://www.cnblogs.com/fatsheep9146/p/5066690.html

    Exercise 09

    • 在kern/entry.S中找到设置堆栈的关键语句:

      relocated:
      	# Clear the frame pointer register (EBP)
      	# so that once we get into debugging C code,
      	# stack backtraces will be terminated properly.
      	movl	$0x0,%ebp			# nuke frame pointer
      
      	# Set the stack pointer
      	movl	$(bootstacktop),%esp
      
      	# now to C code
      	call	i386_init
      
    • movl $0x0,%ebp前后设置断点,检查%ebp%esp的内容变化

      Breakpoint 1, relocated () at kern/entry.S:74
      74		movl	$0x0,%ebp			# nuke frame pointer
      (gdb) info r
      ...
      esp            0x7bec              0x7bec
      ebp            0x7bf8              0x7bf8
      ...
      
      (gdb) si
      => 0xf0100034 <relocated+5>:	mov    $0xf0110000,%esp
      relocated () at kern/entry.S:77
      77		movl	$(bootstacktop),%esp
      (gdb) info r
      ...
      esp            0x7bec              0x7bec
      ebp            0x0                 0x0
      ...
      
      (gdb) si
      => 0xf0100039 <relocated+10>:	call   0xf01000aa <i386_init>
      80		call	i386_init
      (gdb) info r
      ...
      esp            0xf0110000          0xf0110000 <entry_pgtable>
      ebp            0x0                 0x0
      ...
      
    • 结论:

      • 这两个指令分别设置了%ebp,%esp两个寄存器的值。其中%ebp被修改为0。%esp则被修改为bootstacktop的值。这个值为0xf0110000。另外在entry.S的末尾还定义了一个值,bootstack。注意,在数据段中定义栈顶bootstacktop之前,首先分配了KSTKSIZE这么多的存储空间,专门用于堆栈,这个KSTKSIZE = 8 * PGSIZE = 8 * 4096 = 32KB。所以用于堆栈的地址空间为 0xf0108000-0xf0110000,其中栈顶指针指向0xf0110000. 那么这个堆栈实际坐落在内存的 0x00108000-0x00110000物理地址空间中。

      • 内核如何给堆栈保留空间?

        在entry.S中的数据段里面声明一块大小为32Kb的空间作为堆栈使用。从而为内核保留了一块空间。

      • 堆栈指针指向哪一端?

        堆栈由于是向下生长的,所以堆栈指针自然要指向最高地址了。最高地址就是我们之前看到的bootstacktop的值。所以将会把这个值赋给堆栈指针寄存器。

        • 注意:%esp存储的是当前堆栈的顶部,也就是说这里新加入的元素放在更低的位置;%ebp存储的是每个function加入堆栈时的esp值,可以用来trace back

    Exercise 10

    • 在obj/kern/kernel.asm中找到test_traceback函数的地址(0xf0100040),然后设置断点
    (gdb) b *0xf0100040
    Breakpoint 2 at 0xf0100040: file kern/init.c, line 13.
    (gdb) c
    Continuing.
    => 0xf0100040 <test_backtrace>:	endbr32 
    
    Breakpoint 2, test_backtrace (x=5) at kern/init.c:13
    13	{
    (gdb) info r
    ...
    esp            0xf010ffdc          0xf010ffdc
    ebp            0xf010fff8          0xf010fff8
    ...
    
    (gdb) c
    Continuing.
    => 0xf0100040 <test_backtrace>:	endbr32 
    
    Breakpoint 2, test_backtrace (x=4) at kern/init.c:13
    13	{
    (gdb) info r
    ...
    esp            0xf010ffbc          0xf010ffbc
    ebp            0xf010ffd8          0xf010ffd8
    ...
    
    • 结论

      esp ebp
      test_backtrace(5) 0xf010ffdc 0xf010fff8
      test_backtrace(4) 0xf010ffbc 0xf010ffd8
      test_backtrace(3) 0xf010ff9c 0xf010ffb8
      test_backtrace(2) 0xf010ff7c 0xf010ff98
      test_backtrace(1) 0xf010ff5c 0xf010ff78
      • 在test_backtrace(5)中调用test_backtrace(4),所以调用后内存中会同时存在二者的栈帧,test_backtrace(4)的栈帧就在test_backtrace(5)的栈帧之后。

    Exercise 11

    • printf("%08x",number) 输出8位16进制数,注意这里有little-endien 和 big-endien 的区别

      • 经典例题:下面程序输出的结果是多少?

        #include<stdio.h>
        int main()
        {
        	unsigned int a = 0xFFFFFFF7;
           	unsigned char i = (unsigned char)a;
        	char* b = (char*)&a;
        	printf("%08x,%08x
        ",i,*b);
        	return 0;
        }
        
    • 为什么可以直接ebp+4?

      因为ebp是一个寄存器,但这个寄存器不是只有一个位置只能存放刚好1个数据,而是可以将数据按顺序存入ebp寄存器中

    • 为了捋顺关系,搞清楚为什么eip=*((unsigned int *)(ebp+4)),写了一个模拟程序

      #include <iostream>
      using namespace std;
      
      int main()
      {
          unsigned int ebp, eip;
          ebp = 0x0;
         // eip = *((unsigned int*)(ebp + 4));
          unsigned int* tmp = ((unsigned int*)(ebp + 4));  //tmp 本身就是一个指针,现在是想要知道这个指针所指的地方存的内容
          cout << "&ebp=" << &ebp << "   ,ebp=" << ebp << "   ,ebp + 4=" << ebp + 4;
          cout << "
       (unsigned int*)(ebp+4)=" << (unsigned int*)(ebp + 4);
          cout << "
       ((unsigned int*)(ebp + 4))= tmp , *tmp= " << *tmp; //所以这里应该是*tmp,而非&tmp(是取tmp的地址)
          system("pause");
          return 0;
      }
      
    • eip=*((unsigned int *)(ebp+4))

      • ebp 是 unsigned int 类型的int,而非指针,所以把ebp里面的内容保存

      • 然后ebp+4 就是真的地址的数字值+4,然后在转化为(unsigned int*)。因为本质上ebp里面装的就是地址,现在把数据类型转化为地址,也就是指向了ebp之前存的地址偏移了4个字节的地址(即+32-bit)

        • 因为:unsigned int 长度为32位,而地址按字节寻址,那么ebp+4,相当于后移32位,即存储下一个unsigned int 的地方

          • 等价于:

            unsigned int *p=ebp;
            eip=*((unsigned int *)(ebp+4)) = p+1;
            
    • 填补代码:

      • 错误复杂版本
      int
      mon_backtrace(int argc, char **argv, struct Trapframe *tf)
      {
      // Your code here.
      cprintf("stack backtrace:
      ");
      unsigned int ebp, esp, eip;
      unsigned int args[5];//参数数组
      ebp=read_ebp();//读出当前ebp内容,当ebp为0时结束
      //从顶层往最底层(最初)开始读取
      while(ebq)
      {
        eip=*((unsigned int*)(ebp+4));
        for(int i=0;i<5;i++)
           args[i] = *((unsigned int*)(ebp+8+4i));
        cprintf("ebp %08x  eip %08x  args %08x %08x %08x %08x %08x
      ", ebp,eip,args[1],args[2],args[3],args[4],args[5]);
        
        ebp=*((unsigned int*)ebp);//取ebp值所代表的地址的值
      }
      return 0;
      }
      
      • 修改版本
      int
      mon_backtrace(int argc, char **argv, struct Trapframe *tf)
      {
      	// Your code here.
      	cprintf("stack backtrace:
      ");
      	unsigned int ebp,*p;
      	ebp=read_ebp();//读出当前ebp内容,当ebp为0时结束
      	//从顶层往最底层(最初)开始读取
      	while(ebp)
      	{
      	  p=(unsigned int *)ebp;
      	  cprintf("ebp %08x  eip %08x  args %08x %08x %08x %08x %08x
      ", ebp,p[1],p[2],p[3],p[4],p[5],p[6]);
      	  ebp=*((unsigned int*)ebp);//取ebp值所代表的地址的值
      	}
      	return 0;
      }
      
    函数栈帧:

    • 这个返回地址 可以理解为这一帧的函数的地址,因为这一帧代表这一个函数被暂时存放入堆栈,ebp是一个指引,代表再上一层函数的堆栈存放地点指引

    清华ucore 函数堆栈讲解:

    https://objectkuan.gitbooks.io/ucore-docs/content/lab1/lab1_3_3_1_function_stack.html

    • 结果:(图里面的FAIL是针对下个练习的)

    Exercise 12

    - 1.在kern/kdebug.c里面增加代码完善debuginfo_eip函数功能
    • 先要理解stabs结构

      • .stabs : 符号表

      • 引自:https://www.cnblogs.com/wuhualong/p/lab01_exercise12_print_more_info.html

        首先,使用objdump -G obj/kern/kernel > output.md将内核的符号表信息输出到output.md文件,在output.md文件中可以看到以下片段:

        Symnum n_type n_othr n_desc n_value  n_strx String
        118    FUN    0      0      f01000a6 2987   i386_init:F(0,25)
        119    SLINE  0      24     00000000 0      
        120    SLINE  0      34     00000012 0      
        121    SLINE  0      36     00000017 0      
        122    SLINE  0      39     0000002b 0      
        123    SLINE  0      43     0000003a 0      
        

        这个片段是什么意思呢?首先要理解第一行给出的每列字段的含义:

        • Symnum是符号索引,换句话说,整个符号表看作一个数组,Symnum是当前符号在数组中的下标
        • n_type是符号类型,FUN指函数名,SLINE指在text段中的行号
        • n_othr目前没被使用,其值固定为0
        • n_desc表示在文件中的行号
        • n_value表示地址。特别要注意的是,这里只有FUN类型的符号的地址是绝对地址,SLINE符号的地址是偏移量,其实际地址为函数入口地址加上偏移量。比如第3行的含义是地址f01000b8(=0xf01000a6+0x00000012)对应文件第34行。
    • 仔细理解kdebug.c 发现关键是理解stab_binsearch(...)函数,注解后的代码如下:

    int
    debuginfo_eip(uintptr_t addr, struct Eipdebuginfo *info)
    {
    	const struct Stab *stabs, *stab_end; 
    	const char *stabstr, *stabstr_end; 
    	int lfile, rfile, lfun, rfun, lline, rline;
    
    	// Initialize *info
    	info->eip_file = "<unknown>";
    	info->eip_line = 0;
    	info->eip_fn_name = "<unknown>";
    	info->eip_fn_namelen = 9;
    	info->eip_fn_addr = addr;
    	info->eip_fn_narg = 0;
    
    	// Find the relevant set of stabs
    	if (addr >= ULIM) {
    		stabs = __STAB_BEGIN__;
    		stab_end = __STAB_END__;
    		stabstr = __STABSTR_BEGIN__;
    		stabstr_end = __STABSTR_END__;
    	} else {
    		// Can't search for user-level addresses yet!
      	        panic("User address");
    	}
    
    	// String table validity checks
    	if (stabstr_end <= stabstr || stabstr_end[-1] != 0)
    		return -1;
    
    	// Now we find the right stabs that define the function containing
    	// 'eip'.  First, we find the basic source file containing 'eip'.//step01先找到包含eip的文件
    	// Then, we look in that source file for the function.  Then we look //step02然后在该文件里找到它的函数
    	// for the line number.  //step03然后寻找行编号
    
    	// Search the entire set of stabs for the source file (type N_SO).  
    	lfile = 0;
    	rfile = (stab_end - stabs) - 1;  //stabs 是一系列stabs的集中放置的指示指针
    	stab_binsearch(stabs, &lfile, &rfile, N_SO, addr);  //step01,更新 &lfile, &rfile
    	if (lfile == 0)
    		return -1;
    
    	// Search within that file's stabs for the function definition  第二步
    	// (N_FUN).  寻找function
    	lfun = lfile;
    	rfun = rfile;
    	stab_binsearch(stabs, &lfun, &rfun, N_FUN, addr);  //二分查找,更新&lfun, &rfun
    
    	if (lfun <= rfun) {
    		// stabs[lfun] points to the function name
    		// in the string table, but check bounds just in case.
    		if (stabs[lfun].n_strx < stabstr_end - stabstr)
    			info->eip_fn_name = stabstr + stabs[lfun].n_strx;
    		info->eip_fn_addr = stabs[lfun].n_value;
    		addr -= info->eip_fn_addr;//此时addr就是函数内的偏移量
    		// Search within the function definition for the line number. //function找到了,在这个范围内确定line
    		lline = lfun;
    		rline = rfun;
    	} else {
    		// Couldn't find function stab!  Maybe we're in an assembly
    		// file.  Search the whole file for the line number.  //如果找不到function stab说明是在汇编文件里,去寻找line number
    		info->eip_fn_addr = addr;
    		lline = lfile;
    		rline = rfile;
    	}
    	// Ignore stuff after the colon.
    	info->eip_fn_namelen = strfind(info->eip_fn_name, ':') - info->eip_fn_name;
    
    
    	// Search within [lline, rline] for the line number stab.
    	// If found, set info->eip_line to the right line number.
    	// If not found, return -1.
    	//
    	// Hint:
    	//	There's a particular stabs type used for line numbers.
    	//	Look at the STABS documentation and <inc/stab.h> to find
    	//	which one.
    	// Your code here.
    	
    	stab_binsearch(stabs,&lline,&rline,N_SLINE,addr);
    	if(lline <= rline)
    	{
    	    info->eip_line=stabs[lline].n_desc;
    	}
    	else
    	{
    	    cprintf("line not found! 
    ");
    	}
    

    ※ 最后即为自己添加的代码

    int
    mon_backtrace(int argc, char **argv, struct Trapframe *tf)
    {
    	// Your code here.
    	cprintf("stack backtrace:
    ");
    	unsigned int ebp,*eip,*p;
    	ebp=read_ebp();//读出当前ebp内容,当ebp为0时结束
    	eip=(unsigned int *)(ebp+4);
    	//从顶层往最底层(最初)开始读取
    	struct Eipdebuginfo info;
    	int flag = debuginfo_eip(*eip,&info);
    	while(ebp)
    	{
    	  p=(unsigned int *)ebp;
    	  cprintf("ebp %08x  eip %08x  args %08x %08x %08x %08x %08x
    ", ebp,p[1],p[2],p[3],p[4],p[5],p[6]);
    	  flag = debuginfo_eip(p[1],&info);
    	  if(flag ==0)
    	  {
    	      	  cprintf("	%s:%d: %.*s+%d
    ",info.eip_file,info.eip_line,info.eip_fn_namelen,info.eip_fn_name,(p[1] - info.eip_fn_addr));
    	  }
    	  ebp=*((unsigned int*)ebp);//取ebp值所代表的地址的值
    	}
    	return 0;
    }
    
    - 2. 在内核模拟器里面添加backtrace命令
    static struct Command commands[] = {
    	{ "help", "Display this list of commands", mon_help },
    	{ "kerninfo", "Display information about the kernel", mon_kerninfo },
    	{ "backtrace", "Display a backtrace of the function stack", mon_backtrace },
        //最后一句是自己添加的
    };
    
    两个问题:
    • debuginfo_eip函数的参数不应该是ebp而是eip ——>没有搞清楚ebp和eip的意思
      • 去理解函数堆栈,每一个栈帧的含义:
    • 最后cprintf里面不是info.eip_fn_addr,而是偏移量——eip-info.eip_fn_addr
    • info.eip_fn_addr是函数的起始位置,而eip是整体的指令地址,所以eip — info.eip_fn_addr是函数内的偏移
    • 输出格式少了个:
    • 本质:没有深层理解函数调用本质:https://www.jianshu.com/p/8ec9063a37bd
    成功啦啦啦~!

    ![image-20200731020029878](C:UsersCindy DengAppDataRoamingTypora ypora-user-imagesimage-20200731020029878.png)

    ![image-20200731020113398](C:UsersCindy DengAppDataRoamingTypora ypora-user-imagesimage-20200731020113398.png)

    ![image-20200731020136619](C:UsersCindy DengAppDataRoamingTypora ypora-user-imagesimage-20200731020136619.png)

    总结

    Lab1结束后,物理内存分布如下:

  • 相关阅读:
    mysql 事务只读: Could not retrieve transation read-only status server
    页面加载空白---(failed)net::ERR_INCOMPLETE_CHUNKED_ENCODING
    关于数据库mysql死锁:MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
    高性能分布式锁-redisson的使用
    C/S程序抓包
    linux 安装maven
    linux之jdk安装及环境
    腾讯云服务器搭建之mysql
    mysql去重保留id最小的
    MySQL中文全文检索
  • 原文地址:https://www.cnblogs.com/cindycindy/p/13523377.html
Copyright © 2020-2023  润新知