BUAA OS Lab1

LAB1实验报告

thinking1.1

首先编写如下测试c程序:

1
2
3
4
5
6
7
#include <stdio.h>

int main(){
        int a = 1;
        printf("%d\n",a);
        return 0;
}

使用gcc -c参数编译,使用objdump -DS 命令导出结果,取main函数部分的call命令观察如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Disassembly of section .text:

0000000000000000 <main>:
   0:   f3 0f 1e fa             endbr64 
   4:   55                      push   %rbp
   5:   48 89 e5                mov    %rsp,%rbp
   8:   48 83 ec 10             sub    $0x10,%rsp
   c:   c7 45 fc 01 00 00 00    movl   $0x1,-0x4(%rbp)
  13:   8b 45 fc                mov    -0x4(%rbp),%eax
  16:   89 c6                   mov    %eax,%esi
  18:   48 8d 05 00 00 00 00    lea    0x0(%rip),%rax        # 1f <main+0x1f>
  1f:   48 89 c7                mov    %rax,%rdi
  22:   b8 00 00 00 00          mov    $0x0,%eax
  27:   e8 00 00 00 00          call   2c <main+0x2c>
  2c:   b8 00 00 00 00          mov    $0x0,%eax
  31:   c9                      leave  
  32:   c3         

可以看到call命令对应指令为e800000000,未链接

使用gcc ./test.o -o test链接文件,使用objdump -DS导出结果,main函数部分如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
0000000000001149 <main>:
    1149:       f3 0f 1e fa             endbr64 
    114d:       55                      push   %rbp
    114e:       48 89 e5                mov    %rsp,%rbp
    1151:       48 83 ec 10             sub    $0x10,%rsp
    1155:       c7 45 fc 01 00 00 00    movl   $0x1,-0x4(%rbp)
    115c:       8b 45 fc                mov    -0x4(%rbp),%eax
    115f:       89 c6                   mov    %eax,%esi
    1161:       48 8d 05 9c 0e 00 00    lea    0xe9c(%rip),%rax        # 2004 <_IO_stdin_used+0x4>
    1168:       48 89 c7                mov    %rax,%rdi
    116b:       b8 00 00 00 00          mov    $0x0,%eax
    1170:       e8 db fe ff ff          call   1050 <printf@plt>
    1175:       b8 00 00 00 00          mov    $0x0,%eax
    117a:       c9                      leave  
    117b:       c3                      ret    

可见指令为e8 db fe ff ff,说明文件已经链接完成,同时该程序内部有大量相关代码。

使用带有mips-linux-gnu-前缀的命令重复上述实验:

链接前的.o文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
00000000 <main>:
   0:   27bdffd8        addiu   sp,sp,-40
   4:   afbf0024        sw      ra,36(sp)
   8:   afbe0020        sw      s8,32(sp)
   c:   03a0f025        move    s8,sp
  10:   3c1c0000        lui     gp,0x0
  14:   279c0000        addiu   gp,gp,0
  18:   afbc0010        sw      gp,16(sp)
  1c:   24020001        li      v0,1
  20:   afc2001c        sw      v0,28(s8)
  24:   8fc5001c        lw      a1,28(s8)
  28:   3c020000        lui     v0,0x0
  2c:   24440000        addiu   a0,v0,0
  30:   8f820000        lw      v0,0(gp)
  34:   0040c825        move    t9,v0
  38:   0320f809        jalr    t9
  3c:   00000000        nop
  40:   8fdc0010        lw      gp,16(s8)
  44:   00001025        move    v0,zero
  48:   03c0e825        move    sp,s8
  4c:   8fbf0024        lw      ra,36(sp)
  50:   8fbe0020        lw      s8,32(sp)
  54:   27bd0028        addiu   sp,sp,40
  58:   03e00008        jr      ra
  5c:   00000000        nop

推测其中的38位置为调用对应的指令。

链接后的文件中main对应位置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
004006e0 <main>:
  4006e0:       27bdffd8        addiu   sp,sp,-40
  4006e4:       afbf0024        sw      ra,36(sp)
  4006e8:       afbe0020        sw      s8,32(sp)
  4006ec:       03a0f025        move    s8,sp
  4006f0:       3c1c0042        lui     gp,0x42
  4006f4:       279c9010        addiu   gp,gp,-28656
  4006f8:       afbc0010        sw      gp,16(sp)
  4006fc:       24020001        li      v0,1
  400700:       afc2001c        sw      v0,28(s8)
  400704:       8fc5001c        lw      a1,28(s8)
  400708:       3c020040        lui     v0,0x40
  40070c:       24440830        addiu   a0,v0,2096
  400710:       8f828030        lw      v0,-32720(gp)
  400714:       0040c825        move    t9,v0
  400718:       0320f809        jalr    t9
  40071c:       00000000        nop
  400720:       8fdc0010        lw      gp,16(s8)
  400724:       00001025        move    v0,zero
  400728:       03c0e825        move    sp,s8
  40072c:       8fbf0024        lw      ra,36(sp)
  400730:       8fbe0020        lw      s8,32(sp)
  400734:       27bd0028        addiu   sp,sp,40
  400738:       03e00008        jr      ra
  40073c:       00000000        nop

可见此时main函数已经连接完成,jalr的目标来自-32720(gp),而gp在之前的指令经过了一系列处理。之所以链接过程与x86不同可能是由于指令集的差异导致。

其中objdump的参数-DS中-D表示反汇编所有section,-S表示隐含-d参数的同时尽可能反汇编出源代码。然而在此次测试中由于gcc没有指定-g参数,难以反汇编出源代码。

thinking1.2

使用编写的readelf显示如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
0:0x0
1:0x80010000
2:0x80011d80
3:0x80011d98
4:0x80011db0
5:0x0
6:0x0
7:0x0
8:0x0
9:0x0
10:0x0
11:0x0
12:0x0
13:0x0
14:0x0
15:0x0
16:0x0

对比系统自带的readelf,可见解析正确。

之所以无法解析自身,是由于gcc的编译参数导致。Makefile文件中的相关参数如下:

1
2
3
4
5
readelf: main.o readelf.o
        $(CC) $^ -o $@

hello: hello.c
        $(CC) $^ -o $@ -m32 -static -g

其中编译readelf时所用的参数没有-m32而编译hello所用的参数中有。而作业中编写的readelf中使用的是Elf32_Ehdr参数头,这意味着其只能适用于32位程序。因此,默认使用64位编译的readelf无法解析自身。

使用readelf验证如下:

1
2
3
4
5
git@20375061:~/20375061/tools/readelf (lab1)$ readelf -h ./readelf
ELF 头:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  类别:                              ELF64
  数据:                              2 补码,小端序 (little endian)
1
2
3
4
5
git@20375061:~/20375061/tools/readelf (lab1)$ readelf -h ./hello
ELF 头:
  Magic:   7f 45 4c 46 01 01 01 03 00 00 00 00 00 00 00 00 
  类别:                              ELF32
  数据:                              2 补码,小端序 (little endian)	

thinking1.3

启动过程分别由bootloader的两次boot和移交到内核代码后的启动过程完成。bootloader程序需要在BFC00000处既1FC00000物理地址处完成相关的启动操作,通过bootloader完成stage1并复制stage2代码到RAM并跳转,stage2将内核镜像读取到RAM中,最后跳转内核入口函数地址。如上过程将不同硬件的启动地址通过bootloader屏蔽,使得编写的内核不需要考虑最初的启动地址。在实验环境中,我们没有负责初始启动相关的代码编写而是由模拟器完成。

难点分析

在本次作业中,难点之一在于理解ELF这一目标文件格式。它分为可重定位文件(-c编译出的.0文件),可执行文件和共享对象问及那。首先要理解的是gcc编译出的未链接文件和链接后的问及那都属于ELF文件,并且可以用同一种方法解析。

ELF分为五个部分,ELF头包括了基本信息,并给出了节头表和程序头表(段头表)的位置。ELF头的各项是理解的关键,其中e_phoff记录了程序头表相对ELF头的位置,e_shoff记录了节头表相对ELF头的位置,e_[sh/ph][entsize/num]记录了节头/程序头表项的大小/数量。我们只需要根据这些属性既可以定位所有的程序头和端头表象位置。值得注意一个段中可能包含多个,而段完全可能不4k对齐,相邻段也可能发生冲突出现共用页面。

其中节头表项中sh_addr代表该节要加载到的虚拟地址位置,sh_size代表该节大小,而sh_addr代表该节的数据文件在elf文件中的位置。

难点之二在于对于裸机上的printk的编写的理解。对于库函数,我们必须要从最基础的内存位置出发编写显示代码,并且考虑到各种边界条件,对于c语言的指针的应用也有较高的要求。

实验体会

在本次实验中,较为深入地了解了ELF文件的格式,并且编写了针对32为ELF文件的SH解析代码。在编写printk的过程中,我们深入了解了各种格式化标签的解析过程,并完成了解析代码的编写。这使得我们在应用printf等库函数的过程中会对于其背后的实现原理由更深入的理解。

Licensed under CC BY-NC-SA 4.0
京ICP备2021032224号-1
Built with Hugo
主题 StackJimmy 设计
vi ./themes/hugo-theme-learn/layouts/partials/footer.html