笔记04 - HW02: boot xv6

JOS 是麻省理工学院(MIT)从 xv6 直接改过来的实验用工具,xv6-public 是官方在 Github 上放出的最新 xv6 源代码。

获取 xv6 源码

git://pdos.csail.mit.edu/xv6/xv6.git 不成功,git://github.com/mit-pdos/xv6-public.git 成功。

构建 xv6

以下是makefile的部分内容:

1
2
3
4
5
gcc -O -nostdinc -I. -c bootmain.c
gcc -nostdinc -I. -c bootasm.S
ld -m elf_i386 -N -e start -Ttext 0x7C00 -o bootblock.o bootasm.o bootmain.o
objdump -S bootblock.o > bootblock.asm
objcopy -S -O binary -j .text bootblock.o bootblock

==以下是对 gcc 参数的解释:==
-O 使用编译优化级别1编译程序。级别为1~3,级别越大优化效果越好,但编译时间越长。具体如下:
-O0:这个等级(字母“O”后面跟个零)关闭所有优化选项,也是CFLAGS或CXXFLAGS中没有设置 -O 等级时的默认等级。这样就不会优化代码,这通常不是我们想要的。
-O1:这是最基本的优化等级。编译器会在不花费太多编译时间的同时试图生成更快更小的代码。这些优化是非常基础的,但一般这些任务肯定能顺利完成。
-O2:-O1的进阶。这是推荐的优化等级,除非你有特殊的需求。-O2会比-O1启用多一些标记。设置了-O2后,编译器会试图提高代码性能而不会增大体积和大量占用的编译时间。
-O3:这是最高最危险的优化等级。用这个选项会延长编译代码的时间,并且在使用gcc4.x的系统里不应全局启用。自从3.x版本以来gcc的行为已经有了极大地改变。在3.x,-O3生成的代码也只是比-O2快一点点而已,而gcc4.x中还未必更快。用-O3来编译所有的软件包将产生更大体积更耗内存的二进制文件,大大增加编译失败的机会或不可预知的程序行为(包括错误)。在gcc 4.x.中使用-O3是不推荐的。
-Os:这个等级用来优化代码尺寸。其中启用了-O2中不会增加磁盘空间占用的代码生成选项。这对于磁盘空间极其紧张或者CPU缓存较小的机器非常有用。但也可能产生些许问题,因此软件树中的大部分ebuild都过滤掉这个等级的优化。使用-Os是不推荐的。

-nostdinc:可能默认的头文件库会首先调用,而-I下面的目录由于名字相同,优先级可能低于默认所以没能被调用,于是需要gcc编译的时候不要在标准系统目录中找头文件。

-I:用来指定头文件目录,/usr/include目录一般是不用指定的,gcc知道去那里找,但是如果头文件不在/usr/include里我们就要用-I参数指定了,比如头文件放在/myinclude目录里,那编译命令行就要加上-I/myinclude参数了,如果不加你会得到一个”xxxx.h: No such file or directory”的错误。-I参数可以用相对路径,比如头文件在当前目录,可以用-I.来指定。

-c:仅执行编译操作,不进行链接操作。将c语言文件bootmain.c(也可以是汇编输出文件*.S)编译输出bootmain.o文件。

==以下是对ld(GNU linker连接器)参数的解释:==
-m emulation:模仿 emulation 连接器。如-m elf_i386是模仿elf_i386连接器

-N 或 –omagic:把text和data节设置为可读写,同时取消数据节的页对齐。同时取消对共享库的连接。如果输出格式支持Unix风格的magic number,把输出标志为’OMAGIC’。

-e ENTRY:使用符号ENTRY作为程序的开始执行点,而不是使用缺省的进入点。如果没有叫做ENTRY的符号,连接器会企图把ENTRY作为一个数字进行分析,并使用它作为入口地址(数字会被解释为10进制的;可以使用前导的’0x’强制为16进制,或’0’作为8进制。)

-Tbss ORG,-Tdata ORG,-Ttext ORG:跟-section-start同义,不过把SECTIONNAME替换为’.bss’,’.data’或’.text’。-section-start的格式是:`–section-start SECTIONNAME=ORG’,通过指定ORG,指定节在输出文件中的绝对地址。可以多次使用这个选项来定位多个节。ORG必须是一个十六进制整数;为了跟其他连接器兼容,可以忽略前导’0x’。注意,在SECTIONNAME、等号、ORG之间不允许有空格出现。

-o OUTPUT:使用OUTPUT作为’ld’产生的程序的名字。如果这个选项没有指定,缺省的输出文件名是’a.out’。脚本命令’OUTPUT’也可以被用来指定输出文件的文件名。

==以下是对objdump(查看目标文件或者可执行的目标文件的构成的gcc工具)参数的解释:==
-S:尽可能反汇编出源代码,尤其当编译的时候指定了-g这种调试参数时,效果比较明显。隐含了-d参数。

==以下是对objcopy(用于将object的部分或全部内容拷贝到另一个object,从而可以实现格式的变换。)参数的解释:==
-S:去掉源文件的符号信息和relocation信息。

-O bfdname:使用指定的格式来写输出文件(即目标文件),bfdname是BFD库中描述的标准格式名。

-j sectionname:只将由sectionname指定的section拷贝到输出文件。

探究堆栈内容

bootasm.S 中在哪里初始化堆栈?
在 call bootmain 之前调用 movl $start, %esp 初始化堆栈,由于历史原因,boot loader 部分将会被加载到 0x7c00 的位置,即 $start 所代表的位置。此处将 0x7c00 赋值给 esp,而堆栈栈顶指针是向低地址方向增长的。

bootmain 函数对堆栈所做的第一条汇编指令是什么(查看bootblock.asm)?
push %ebp,将 ebp 进栈,此后将当前的栈顶指针的值赋给 ebp,从而可以实现递归调用和返回。

单步调试直到 call bootmain,堆栈的内容是什么?
设置断点到 0x10000c(内核程序入口地址),当执行该地址的指令后,堆栈发生什么变化?
以下是进入到内核程序入口后,以栈顶指针 esp 为指标查看到的内存内容(x/24x $esp,其中 esp 的值为0x7bcc)

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
26
27
28
29
30
31
0x7bcc:	        #进入内核后, esp 指向 0x7bcc
0x00007db7 #7db7 是进入内核elf->entry指令的下一条指令地址
0x00000000
0x00000000
0x00000000
0x7bdc:
0x00000000
0x00000000
0x00000000
0x00000000
0x7bec:
0x00000000
0x00000000
0x00000000
0x00000000
0x7bfc:
0x00007c4d # 7c4d 是 bootasm.S 调用 bootmain 函数时下一条指令的地址,即 call bootmain 后堆栈会存储 0x7c4d。
0x7c00: # !!! 此处即为栈顶指针的初始化位置,此后向低地址方向增长
0x8ec031fa
0x8ec08ed8
0xa864e4d0
0x7c0c:
0xb0fa7502
0xe464e6d1
0x7502a864
0xe6dfb0fa
0x7c1c:
0x16010f60
0x200f7c78
0xc88366c0
0xc0220f01

显示 Gitment 评论