北航操作系统课程lab1实验报告

OS Lab1实验报告

实验思考题

Thinking 1.1

使用man objdump命令,可以看到,对于objdump -DS指令而言,-D参数表示反汇编所有部分的内容(disassemble the contents of all sections),-S参数表示显示与反汇编汇合的源代码 (Display source code intermixed with disassembly, if possible.)

使用命令

 /OSLAB/compiler/usr/bin/mips_4KC-gcc -c test1.c
 /OSLAB/compiler/usr/bin/mips_4KC-ld test1.o -o test1
 /OSLAB/compiler/usr/bin/mips_4KC-objdump -DS test1.o > o.txt
 /OSLAB/compiler/usr/bin/mips_4KC-objdump -DS test1 > out.txt

反编译.o文件:

   2 test1.o:     file format elf32-tradbigmips
   3 
   4 Disassembly of section .text:
   5 
   6 00000000 <main>:
   7    0:   3c1c0000    lui gp,0x0                                           
   8    4:   279c0000    addiu   gp,gp,0
   9    8:   0399e021    addu    gp,gp,t9
  10    c:   27bdffe0    addiu   sp,sp,-32
  11   10:   afbe0018    sw  s8,24(sp)
  12   14:   03a0f021    move    s8,sp
  13   18:   24020003    li  v0,3
  14   1c:   afc20010    sw  v0,16(s8)
  15   20:   c7c00010    lwc1    $f0,16(s8)
  16   24:   468000a1    cvt.d.w $f2,$f0
  17   28:   8f820000    lw  v0,0(gp)
  18   2c:   d4400000    ldc1    $f0,0(v0)
  19   30:   46201082    mul.d   $f2,$f2,$f0
  20   34:   c7c00010    lwc1    $f0,16(s8)
  21   38:   46800021    cvt.d.w $f0,$f0
  22   3c:   46201002    mul.d   $f0,$f2,$f0
  23   40:   f7c00008    sdc1    $f0,8(s8)
  24   44:   00001021    move    v0,zero
  25   48:   03c0e821    move    sp,s8
  26   4c:   8fbe0018    lw  s8,24(sp)
  27   50:   27bd0020    addiu   sp,sp,32
  28   54:   03e00008    jr  ra
  29   58:   00000000    nop
  30   5c:   00000000    nop
  31 Disassembly of section .reginfo:
  32
  33 00000000 <.reginfo>:
  34    0:   f2000004    0xf2000004
  35    4:   00000000    nop
  36    8:   00000005    0x5
  37     ...
  38 Disassembly of section .pdr:
  39 
  40 00000000 <.pdr>:
  41    0:   00000000    nop
  42    4:   40000000    mfc0    zero,c0_index
  43    8:   fffffff8    sdc3    $31,-8(ra)
  44     ...
  45   14:   00000020    add zero,zero,zero
  46   18:   0000001e    0x1e
  47   1c:   0000001f    0x1f
  48 Disassembly of section .rodata:
  49 
  50 00000000 <.rodata>:
  51    0:   40091eb8    0x40091eb8
  52    4:   51eb851f    beql    t7,t3,fffe1484 <main+0xfffe1484>
  53    8:   00000000    nop
  54    c:   00000000    nop
  55     ...

反编译.out文件:

   2 test1:     file format elf32-tradbigmips
   3
   4 Disassembly of section .reginfo:
   5
   6 00400094 <.reginfo>:
   7   400094:   f2000004    0xf2000004
   8   400098:   00000000    nop
   9   40009c:   00000005    0x5
  10     ...
  11   4000a8:   10007ff0    b   42006c <main+0x1ffbc>
  12 Disassembly of section .text:
  13
  14 004000b0 <main>:
  15   4000b0:   3c1c0fc0    lui gp,0xfc0
  16   4000b4:   279c7f40    addiu   gp,gp,32576
  17   4000b8:   0399e021    addu    gp,gp,t9
  18   4000bc:   27bdffe0    addiu   sp,sp,-32
  19   4000c0:   afbe0018    sw  s8,24(sp)
  20   4000c4:   03a0f021    move    s8,sp
  21   4000c8:   24020003    li  v0,3
  22   4000cc:   afc20010    sw  v0,16(s8)
  23   4000d0:   c7c00010    lwc1    $f0,16(s8)
  24   4000d4:   468000a1    cvt.d.w $f2,$f0
  25   4000d8:   8f828018    lw  v0,-32744(gp)
  26   4000dc:   d4400110    ldc1    $f0,272(v0)
  27   4000e0:   46201082    mul.d   $f2,$f2,$f0
  28   4000e4:   c7c00010    lwc1    $f0,16(s8)
  29   4000e8:   46800021    cvt.d.w $f0,$f0
  30   4000ec:   46201002    mul.d   $f0,$f2,$f0
  31   4000f0:   f7c00008    sdc1    $f0,8(s8)
  32   4000f4:   00001021    move    v0,zero
  33   4000f8:   03c0e821    move    sp,s8
  34   4000fc:   8fbe0018    lw  s8,24(sp)
  35   400100:   27bd0020    addiu   sp,sp,32
  36   400104:   03e00008    jr  ra
  37   400108:   00000000    nop
  38   40010c:   00000000    nop
  39 Disassembly of section .rodata:
  40
  41 00400110 <.rodata>:
  42   400110:   40091eb8    0x40091eb8
  43   400114:   51eb851f    beql    t7,t3,3e1594 <main-0x1eb1c>
  44   400118:   00000000    nop
  45   40011c:   00000000    nop
  46 Disassembly of section .got:
  47 
  48 10000000 <_GLOBAL_OFFSET_TABLE_>:
  49 10000000:   00000000    nop
  50 10000004:   80000000    lb  zero,0(zero)
  51 10000008:   00400000    0x400000
  52     ...

Thinking 1.2

使用readelf -h testELF解析testELF文件

 ELF Header:
   Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
   Class:                             ELF32
   Data:                              2's complement, little endian
   Version:                           1 (current)
   OS/ABI:                            UNIX - System V
   ABI Version:                       0
   Type:                              EXEC (Executable file)
   Machine:                           Intel 80386
   Version:                           0x1
   Entry point address:               0x8048490
   Start of program headers:          52 (bytes into file)
   Start of section headers:          4440 (bytes into file)
   Flags:                             0x0
   Size of this header:               52 (bytes)
   Size of program headers:           32 (bytes)
   Number of program headers:         9
   Size of section headers:           40 (bytes)
   Number of section headers:         30
   Section header string table index: 27

使用readelf -h vmlinux解析vmlinux文件

 ELF Header:
   Magic:   7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00 
   Class:                             ELF32
   Data:                              2's complement, big endian
   Version:                           1 (current)
   OS/ABI:                            UNIX - System V
   ABI Version:                       0
   Type:                              EXEC (Executable file)
   Machine:                           MIPS R3000
   Version:                           0x1
   Entry point address:               0x80010000
   Start of program headers:          52 (bytes into file)
   Start of section headers:          37164 (bytes into file)
   Flags:                             0x1001, noreorder, o32, mips1
   Size of this header:               52 (bytes)
   Size of program headers:           32 (bytes)
   Number of program headers:         2
   Size of section headers:           40 (bytes)
   Number of section headers:         14
   Section header string table index: 11

在Data段的信息中可以看到,我们生成的readelf文件适用于解析小端存储的,而我们的内核vmlinux是大端存储的文件,因此无法解析内核。

Thinking 1.3

实验操作系统使用GXemul仿真器,支持直接加载ELF格式的内核。BootLoader中Stage1的功能已经由其提供,而事实上此时已经跳到了内核入口,跳到内核第一步就是实现在boot/start.o中的代码初始化硬件设备,设置堆栈,跳转到main函数入口。

Thinking 1.4

在加载程序时,避免发生共享页面冲突页面现象。首先,不同程序段的占用空间不能够有重合,然后,尽量避免一个页面同时被多个程序段所占用。即若前面的程序段末地址所占用的页面地址为vi,则后续的程序段首地址应从下一页面vi+1开始占用。

Thinking 1.5

内核入口在0x00000000处,main函数在0x80010000处。我们在start.S文件中编写mips汇编代码,设置堆栈后直接跳到main函数中。在跨文件调用函数时,每个函数会有一个固定的地址,调用过程为将需要存储的值进行进栈等保护,再用jal跳转到相应函数的地址。

Thinking 1.6

将宏定义的内容转化为寄存器编号:

 mtc0 $0,$12
 #将SR寄存器清零
 mfc0 $t0,$16
 and $t0,~0x7 #要清零的位的反码
 ori $t0,0x2 #要置1的位
 mtc0 $t0,$16
 #将Config寄存器0号位和2号位置0,将1号位置1

上电后,需要设置SR寄存器来使CPU进入工作状态,而硬件通常都是复位后让许多寄存器的位为未定义。

Config寄存器的后三位为可写的,用来决定固定的kseg0区是否经过高速缓存和其确切的行为如何。

实验难点展示

本次实验要做的很简单很基础,当然了,这是基于我做完了之后得出来的结论。在那么多文件夹内找到彼此间的调用关系实属不易。我认为实验中较难的地方在于对ELF文件的解析和补充printf函数的部分。

Exercise 1.2 解析ELF文件

 typedef struct {
         unsigned char   e_ident[EI_NIDENT];     /* Magic number and other info */
         Elf32_Half      e_type;                 /* Object file type */
         Elf32_Half      e_machine;              /* Architecture */
         Elf32_Word      e_version;              /* Object file version */
         Elf32_Addr      e_entry;                /* Entry point virtual address */
         Elf32_Off       e_phoff;                /* Program header table file offset */
         Elf32_Off       e_shoff;                /* Section header table file offset */
         Elf32_Word      e_flags;                /* Processor-specific flags */
         Elf32_Half      e_ehsize;               /* ELF header size in bytes */
         Elf32_Half      e_phentsize;            /* Program header table entry size */
         Elf32_Half      e_phnum;                /* Program header table entry count */
         Elf32_Half      e_shentsize;            /* Section header table entry size */
         Elf32_Half      e_shnum;                /* Section header table entry count */
         Elf32_Half      e_shstrndx;             /* Section header string table index */
 } Elf32_Ehdr;
 typedef struct{
         Elf32_Word sh_name;                 /* Section name */
         Elf32_Word sh_type;                 /* Section type */
         Elf32_Word sh_flags;                /* Section flags */
         Elf32_Addr sh_addr;                 /* Section addr */
         Elf32_Off  sh_offset;               /* Section offset */
         Elf32_Word sh_size;                 /* Section size */
         Elf32_Word sh_link;                 /* Section link */
         Elf32_Word sh_info;                 /* Section extra info */
         Elf32_Word sh_addralign;            /* Section alignment */
         Elf32_Word sh_entsize;              /* Section entry size */
 }Elf32_Shdr;
 typedef struct {  
     // segment type
     Elf32_Word p_type;  
     // offset from elf file head of this entry  
     Elf32_Off p_offset;  
     // virtual addr of this segment  
     Elf32_Addr p_vaddr;  
     // physical addr, in linux, this value is meanless and has same value of p_vaddr 
     Elf32_Addr p_paddr;  
     // file size of this segment  
     Elf32_Word p_filesz;  
     // memory size of this segment  
     Elf32_Word p_memsz;  
     // segment flag  
     Elf32_Word p_flags;  
     // alignment  
     Elf32_Word p_align; 
 }Elf32_Phdr;

ELF文件结构图如上图所示,而这三个结构体分别代表了ELF程序头、Section节头、Segment段头,实际上这个问题困扰了我很久。可以看到 ,这些结构体占用的空间都不大,所以这里面肯定不包含具体的内容。经过多次试验,我也终于想明白了

Elf32_Ehdr对应图标的ELF header,其中e_entry指明了程序入口的虚拟地址。e_phoff指明程序头表的偏移量。e_shoff 指明节头表的偏移量。e_phentsize 表示程序头表每一个表项的大小(即对应的Elf32_Phdr结构体大小)。e_phnum 表示程序头表项数目。e_shentsize 表示节头表每一个表项的大小(即对应的Elf32_Shdr结构体大小)。e_shnum 表示节头表项数目。

多个节头表项,可看做Elf32_Shdr[e_shnum]数组组成了节头表(Section Header Table)。通过对每个结构体的sh_addr属性的访问,可以轻松知道每个Section的具体位置。

多个程序头表项,可看做Elf32_Phdr[e_phnum]数组组成了程序头表(Programmer Header Table)。FileSiz代表该段的数据在文件中的长度。MemSiz代表该段的数据在内存中所应当占的大小,MemSiz永远大于等于FileSizC语言中未初始化的全局变量可以导致这个现象。

于是理清结构后,这个问题就十分简单了。程序入口地址是binary,节头起点和程序头起点分别为binary+e_shoffbinary+e_phoff。要想得到所有Section的地址,只需要声明一个Elf32_Shdr*类型的指针变量shdr,地址为shdr -> sh_addr,下一个节头的访问只需要shdr++(指针增加一,地址增加一个节头的大小)。当然了,知道每个节头的大小,也可以每次都加e_shentsize的偏移量来表示下一个节头的地址。

Exercise 1.5 补全print.c函数

两个难点,第一个是针对变长参数列表的分析,第二个是对local help functions功能的分析。

<stdarg.h> 中定义了几个重要的宏:

 typedef char* va_list;
 //在调用参数表之前,定义一个va_list 类型的变量
 void va_start ( va_list ap, prev_param );
 //然后应该对ap 进行初始化,让它指向可变参数表里面的第一个参数。第一个参数是 ap 本身,第二个参数是在变参表前面紧挨着的一个变量,即“...”之前的那个参数;
 type va_arg ( va_list ap, type );
 //然后是获取参数,调用va_arg,它的第一个参数是ap,第二个参数是要获取的参数的指定类型,然后返回这个指定类型的值,并且把 ap 的位置指向变参表的下一个变量位置;
 void va_end ( va_list ap );
 //获取所有的参数之后,我们有必要将这个 ap 指针关掉,以免发生危险,方法是调用 va_end

在print.c函数中,*fmt指向格式化字符串现在分析到的一个字符,而va_arg则一直往后获取新的一个参数。对*fmt的分析是直接读字符串,碰到‘%’就进入下一步操作,碰到‘\0'就退出循环。获取格式的操作十分容易。而坑点主要在第二处代码补写的地方,由于%d是有符号十进制数,和其他的不一样,而在函数声明

 int PrintNum(char * buf, unsigned long u, int base, int negFlag, 
      int length, int ladjust, char padc, int upcase)

可以注意到第二个参数类型是unsigned long,这表明如果va_arg获取到了一个负数的话,则需要将其转化为正数,并将negFlag置1。

体会与感想

启动过程理解

在学习lab1的过程中,初步看了下整体的文件夹和代码。这些文件夹的名字取的都十分的恰当。以下是我的薄弱理解:boot用于在vmlinux启动时做出第一手操作;drivers实际上是和硬件强关联的,因为printf函数到了底层就靠他往某个地址写入字符;gxemul是我们的用于仿真的软件平台,make产生的vmlinux就在这里面;include是一些库函数吧,有着一部分的c函数库,还有对寄存器别名的宏定义等;init是内核启动后经由boot操作结束后的进一步初始化;lib中我们实现了printf函数,可以理解成一个我们自己实现的一个库文件夹;readelf主要是提供一个工具供我们解析ELF文件,貌似并不是启动所需的必须成分;tools提供一个链接器,让代码段和数据段存在于应该在的位置上。

由于GXemul仿真器的作用,我们相当于直接走到启动了内核这一步。在控制权转向内核后,start.o文件开始初始化硬件,设置堆栈,并将当前PC跳转到main函数入口。main函数的地址是在构建vmlinux的过程中,用tools/scse0_3.lds链接器链接到0x80010000地址上的,在init/main函数中,调用了mips_init函数,而printf函数所使用的库就在我们写的lib里面,这样在启动vmlinux时,显示屏就会打出三行语句。这也是为什么如果没实现1.5的内容是无法看到有效输出的,或许也是因为如此才无法通过1.3和1.4的评测吧。

总结

从lab1过来人的角度来讲,只要懂了,就很简单,可在成为懂王之前,简直就是裂开,譬如Exercise 1.1,要是没有看到教程的提示,该怎么思考去哪个地方找路径啊?lab1首先还是需要我们对工具的一个熟练使用。阅读Makefile的能力对理解整个架构尤为重要,它勾勒了整个流程的先后顺序,而且也真的好用,一个make就可以将改变后的代码应用起来。重拾了mips汇编,在写mips的时候要注意跳转指令最多能跳转多大的空间,此外不知道会不会涉及到延迟槽的利用,这次在jal main后面不管加没加nop也都可以过评测。Linker Script的编写是一个新东西,但现在应该掌握一点基础就可以了,也算是见识到了.S文件和.lds文件的样子,感觉学的好多好杂啊。整体文件的基本逻辑其实还是欠缺一点理解的,ELF文件的解析也是较难理解的,一下子多了如此多的代码是很考验我的接受能力的,这可只是lab1诶,好在同为重课的OO进入到了博客周,才给了可怜的我一丝喘息之机。当然,过了一遍重要的代码还是有好处的,起码以后能够有一些些全局意识。

posted @ 2022-03-26 22:49  南风北辰  阅读(2147)  评论(4编辑  收藏  举报