实验手册:https://chyyuu.gitbooks.io/ucore_os_docs/content/
练习1:理解通过make生成执行文件的过程
1. 操作系统镜像文件ucore.img是如何一步一步生成的?
首先进入目录 home/moocos/ucore_lab/labcodes_answer/lab1_result
make:根据Makefile文件编译源代码、连接、生成目标文件、可执行文件。
make clean:清除上次的make命令所产生的object文件(后缀为“.o”的文件)及可执行文件。
make V=:显示make具体执行了哪些命令。
在linux下.c只是简单的文本文件,.o是编译之后的二进制文件也称对象文件。
以下是 make V= 显示的结果:
+ cc kern/init/init.c
gcc -Ikern/init/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/init/init.c -o obj/kern/init/init.o
+ cc kern/libs/readline.c
gcc -Ikern/libs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/libs/readline.c -o obj/kern/libs/readline.o
+ cc kern/libs/stdio.c
gcc -Ikern/libs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/libs/stdio.c -o obj/kern/libs/stdio.o
+ cc kern/debug/kdebug.c
gcc -Ikern/debug/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/debug/kdebug.c -o obj/kern/debug/kdebug.o
+ cc kern/debug/kmonitor.c
gcc -Ikern/debug/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/debug/kmonitor.c -o obj/kern/debug/kmonitor.o
+ cc kern/debug/panic.c
gcc -Ikern/debug/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/debug/panic.c -o obj/kern/debug/panic.o
+ cc kern/driver/clock.c
gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/clock.c -o obj/kern/driver/clock.o
+ cc kern/driver/console.c
gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/console.c -o obj/kern/driver/console.o
+ cc kern/driver/intr.c
gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/intr.c -o obj/kern/driver/intr.o
+ cc kern/driver/picirq.c
gcc -Ikern/driver/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/driver/picirq.c -o obj/kern/driver/picirq.o
+ cc kern/trap/trap.c
gcc -Ikern/trap/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/trap/trap.c -o obj/kern/trap/trap.o
+ cc kern/trap/trapentry.S
gcc -Ikern/trap/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/trap/trapentry.S -o obj/kern/trap/trapentry.o
+ cc kern/trap/vectors.S
gcc -Ikern/trap/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/trap/vectors.S -o obj/kern/trap/vectors.o
+ cc kern/mm/pmm.c
gcc -Ikern/mm/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -c kern/mm/pmm.c -o obj/kern/mm/pmm.o
+ cc libs/printfmt.c
gcc -Ilibs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -c libs/printfmt.c -o obj/libs/printfmt.o
+ cc libs/string.c
gcc -Ilibs/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -c libs/string.c -o obj/libs/string.o
+ ld bin/kernel
ld -m elf_i386 -nostdlib -T tools/kernel.ld -o bin/kernel obj/kern/init/init.o obj/kern/libs/readline.o obj/kern/libs/stdio.o obj/kern/debug/kdebug.o obj/kern/debug/kmonitor.o obj/kern/debug/panic.o obj/kern/driver/clock.o obj/kern/driver/console.o obj/kern/driver/intr.o obj/kern/driver/picirq.o obj/kern/trap/trap.o obj/kern/trap/trapentry.o obj/kern/trap/vectors.o obj/kern/mm/pmm.o obj/libs/printfmt.o obj/libs/string.o
+ cc boot/bootasm.S
gcc -Iboot/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Os -nostdinc -c boot/bootasm.S -o obj/boot/bootasm.o
+ cc boot/bootmain.c
gcc -Iboot/ -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc -fno-stack-protector -Ilibs/ -Os -nostdinc -c boot/bootmain.c -o obj/boot/bootmain.o
+ cc tools/sign.c
gcc -Itools/ -g -Wall -O2 -c tools/sign.c -o obj/sign/tools/sign.o
gcc -g -Wall -O2 obj/sign/tools/sign.o -o bin/sign
+ ld bin/bootblock
ld -m elf_i386 -nostdlib -N -e start -Ttext 0x7C00 obj/boot/bootasm.o obj/boot/bootmain.o -o obj/bootblock.o
'obj/bootblock.out' size: 488 bytes
build 512 bytes boot sector: 'bin/bootblock' success!
dd if=/dev/zero of=bin/ucore.img count=10000
10000+0 records in
10000+0 records out
5120000 bytes (5.1 MB) copied, 0.052428 s, 97.7 MB/s
dd if=bin/bootblock of=bin/ucore.img conv=notrunc
1+0 records in
1+0 records out
512 bytes (512 B) copied, 8.8643e-05 s, 5.8 MB/s
dd if=bin/kernel of=bin/ucore.img seek=1 conv=notrunc
146+1 records in
146+1 records out
74923 bytes (75 kB) copied, 0.00101346 s, 73.9 MB/s
cc来自于Unix的c语言编译器,是 c compiler 的缩写;gcc来自Linux世界,是GNU compiler collection 的缩写,是编译器集合。
makefile中,命令行第一个字符为加号+,则执行命令时不受到make的-n -t -q三个参数的影响。
上面命令前部分都是cc和gcc,用于编译.c文件生成.o文件,这里cc指向gcc,因此在Linux下cc等价于gcc。
如果不加V=,也会输出+cc,+ld等内容,可能这里是一些提示信息。
gcc中-c和-o是编译时可选的参数
-加-c, (compile)只编译生成中间同名目标文件,不链接,生成.o文件,不产生执行文件
-加-o,(output)指定输出文件名,该文件为可执行文件,不加-o会默认生成filemame.out
举例:
gcc -c hello.c 编译生成hello.o文件
gcc -o hello hello.c 生成可执行文件hello
gcc -c a.c 编译成目标文件a.o
gcc a.c 生成执行文件a.exe
gcc -o a -c a.c 编译成目标文件a
gcc -o a a.c 生成执行文件a.exe
在a.c中引用test.c中的一个函数后:
gcc -c test.c 编译成目标文件test.o
gcc -c a.c 编译成目标文件a.o
gcc -o a test.o a.o 生成执行文件a.exe
gcc -o a test.o a.c 生成执行文件a.exe
gcc -o a test.c a.c 生成执行文件a.exe
总结:只要参数中有-c,总是生成目标文件;只要参数中无-c而只有-o,则总是生成执行文件。
-fno-builtin:允许定义函数的时候和C语言的内建函数重名。
-Wall:该选项能发现程序中一系列的常见错误警告。虽然GCC提供了非常丰富的警告,但前提是你已经启用了它们,否则它不会报告这些检测到的警告。
-ggdb:-ggdb产生的debug信息更倾向于给GDB使用的。如果用的GDB调试器,那么使用-ggdb选项,如果是其他调试器,则使用-g。
-m32:-m32 生成32位机器的汇编代码;-m64则生成64位机器汇编代码。
-I(大写i):指定头文件路径(相对路径或绝对路径,建议相对路径)。gcc/g++ 会先在当前目录查找你所制定的头文件,如果没有找到,他回到默认的头文件目录找,如果使用 -I 制定了目录,他会先在你所制定的目录查找, 然后再按常规的顺序去找。
-gstabs:-gstabs,以stabs格式声称调试信息,但是不包括gdb调试信息;-gstabs+,以stabs格式声称调试信息,并且包含仅供gdb使用的额外调试信息。
-nostdinc:不在标准系统文件夹寻找头文件,只在 -I 等参数指定的文件夹中搜索头文件。
-fno-stack-protector:禁用堆栈保护机制。
ld:ld 命令是二进制工具集 GNU Binutils 的一员,是 GNU 链接器,用于将目标文件与库链接为可执行文件或库文件。在上面的ld命令的作用就是分别生成了kernel和bootblock文件。
命令格式:ld [OPTIONS] OBJFILES
-b <input-format>
指定目标代码输入文件的格式
-Bstatic
只使用静态库
-Bdynamic
只使用动态库
-Bsymbolic
把引用捆绑到共享库中的全局符号
-c <MRI-commandfile>, --mri-script=<MRI-commandfile>
为与 MRI 链接器兼容,ld 接受由 MRI 命令语言编写的脚本文件
--cref
创建跨引用表
-d,-dc,-dp
即使指定了可重定位的输出文件(使用-r),也会为公共符号分配空间。脚本命令“FORCE_COMMON_ALLOCATION”具有相同的效果
-defsym
在输出文件中创建指定的全局符号
-demangle
在错误消息中还原符号名称
-e <entry>
使用指定的符号作为程序的初始执行点
-E,--export-dynamic
对于ELF格式文件,创建动态链接的可执行文件时,把所有符号添加到动态符号表
-f <name>, --auxiliary=<name>
对于 ELF 格式共享对象,设置 DT_AUXILIARY 名称
-F <name>, --filter=<name>
对于ELF格式共享对象,设置 DT_FILTER 名称。这告诉动态链接器,正在创建的共享对象的符号表应该用作共享对象名称的符号表的筛选器。
-g
被忽略。用于提供和其他工具的兼容性
-h
对于 ELF 格式共享对象,设置 DT_SONAME 名称
-I<file>, -dynamic-linker <file>, --dynamic-linker=<file>
指定动态链接器。这仅在生成依赖动态链接库的 ELF 可执行文件时才有意义。默认的动态链接器通常是正确的,除非您知道正在做什么,否则不要使用该选项。
-l <namespec>, --library=<namespec>
把指定的库文件添加到要链接的文件清单
-L <searchdir>, --library-path=searchdir
把指定的路径添加添加到搜索库的目录清单
-M, --print-map
显示链接映射,用于诊断目的
-Map=<mapfile>:
将链接映射输出到指定的文件
-m <emulation>
模拟指定的链接器
-N,--omagic
指定读取/写入文本和数据段
-n,--nmagic
关闭节的页面对齐,并禁用对共享库的链接。如果输出格式支持Unix样式的幻数,则将输出标记为"NMAGIC"
-noinhibit-exec
生成输出文件,即使出现非致命链接错误。通常,如果链接器在链接过程中遇到错误,它将不会生成输出文件。
-no-keep-memory
ld 通常在内存中缓存输入文件的符号表来优化内存使用速度。此选项告诉 ld 不要缓存符号表。当链接大型可执行文件时,如果ld耗尽内存空间,则可能需要使用该选项
-O <level>
对于非零的优化等级,ld将优化输出。此操作会比较耗时,应该在生成最终的结果时使用。
-o <output>, --output=<output>
指定输出文件的名称
-oformat=<output-format>
指定输出文件的二进制格式
-R <filename>,--just-symbols=<filename>
从指定的文件读取符号名称和地址
-r,--relocatable
生成可重定位的输出(称为部分连接)
-rpath=<dir>
把指定的目录添加到运行时库搜索路径
-rpath-link=<dir>
指定搜索运行时共享库的目录
-S,--strip-debug
忽略来自输出文件的调试器符号信息
-s,--strip-all
忽略来自输出文件的所有符号信息
-shared, -Bshareable
创建共享库
-split-by-file[=size]
为每个目标文件在输出文件中创建额外的段大小达到size。size默认为1
-split-by-reloc[=count]
按照指定的长度在输出文件中创建额外的段
--section-start=<sectionname>=<org>
在输出文件中指定的地址定位指定的段
-T <scriptfile>, --script=<scriptfile>
使用 scriptfile 作为链接器脚本。此脚本将替换 ld 的默认链接器脚本(而不是添加到其中),因此脚本必须指定输出文件所需的所有内容。如果当前目录中不存在脚本文件,ld 会在 -L 选项指定的目录中查找
-Ttext=<org>
使用指定的地址作为文本段的起始点
-Tdata=<org>
使用指定的地址作为数据段的起始点
-Tbss=<org>
使用指定的地址作为bss段的起始点
-t,--trace
在处理输入文件时显示它们的名称
-u <symbol>, --undefined=<symbol>
强制指定符号在输出文件中作为未定义符号
-v, -V, --version
示ld版本号
-warn-common
当一个通用符号和另一个通用符号结合时发出警告
-warn-constructors
如果没有使用任何全局构造器,则发出警告
-warn-once
对于每个未定义的符号只发出一次警告
-warn-section-align
如果为了对齐而改动了输出段地址,则发出警告
--whole-archive
对于指定的存档文件,在存档中包含所有文件
-X, --discard-locals
删除所有本地临时符号
-x, --discard-al
删除所有本地符号
实例:ld -m elf_i386 -nostdlib -T tools/kernel.ld -o bin/kernel obj/kern/init/init.o
-m elf_i386:指定模拟仿真链接器为 elf_i386;-nostdlib:只搜索命令行上显式指定的库目录,在链接器脚本中指定的库目录(包括在命令行中指定的链接器脚本)将被忽略;-T tools/kernel.ld:指定链接器的脚本为 tools 目录下的 kernel.ld 文件;-o bin/kernel obj/kern/init/init.o:将转化好的可执行文件放到 bin/ 目录下,并命名为 kernel,链接的文件为 obj/kern/init/ 目录中的 init.o。
dd:用指定大小的块拷贝一个文件,并在拷贝的同时进行指定的转换。上面的dd命令将dev/zero,,bin/bootblock,bin/kernel 写入到bin/ucore.img。
注意:指定数字的地方若以下列字符结尾,则乘以相应的数字:b=512;c=1;k=1024;w=2
参数注释:
if=文件名:输入文件名,缺省为标准输入。即指定源文件。< if=input file >
of=文件名:输出文件名,缺省为标准输出。即指定目的文件。< of=output file >
ibs=bytes:一次读入bytes个字节,即指定一个块大小为bytes个字节。
obs=bytes:一次输出bytes个字节,即指定一个块大小为bytes个字节。
bs=bytes:同时设置读入/输出的块大小为bytes个字节。
cbs=bytes:一次转换bytes个字节,即指定转换缓冲区大小。
skip=blocks:从输入文件开头跳过blocks个块后再开始复制。
seek=blocks:从输出文件开头跳过blocks个块后再开始复制。
注意:通常只用当输出文件是磁盘或磁带时才有效,即备份到磁盘或磁带时才有效。
count=blocks:仅拷贝blocks个块,块大小等于ibs指定的字节数。
conv=conversion:用指定的参数转换文件。
ascii:转换ebcdic为ascii
ebcdic:转换ascii为ebcdic
ibm:转换ascii为alternate ebcdic
block:把每一行转换为长度为cbs,不足部分用空格填充
unblock:使每一行的长度都为cbs,不足部分用空格填充
lcase:把大写字符转换为小写字符
ucase:把小写字符转换为大写字符
swab:交换输入的每对字节
noerror:出错时不停止
notrunc:不截短输出文件
sync:将每个输入块填充到ibs个字节,不足部分用空(NUL)字符补齐。
实例:dd if=/dev/zero of=bin/ucore.img count=10000
从 /dev/zero 文件中读取内容来替代标准输入stdin,将输出的内容写入到 bin 目录下的 ucore.img,替代标准的输出 stdout,复制 10000 个输入块。
2.一个被系统认为是符合规范的硬盘主引导扇区的特征是什么?
参考其它博文,发现都是从sign.c文件里面找到如下代码段:
buf[510] = 0x55;
buf[511] = 0xAA;
FILE *ofp = fopen(argv[2], "wb+");
size = fwrite(buf, 1, 512, ofp);
if (size != 512) {
fprintf(stderr, "write '%s' error, size is %d.\n", argv[2], size);
return -1;
}
判断出硬盘主引导扇区的特征是大小为512字节,并且最后两个字节是0x55AA。
练习2:使用qemu执行并调试lab1中的软件。
通过查看Makefile,发现lab1-mon内容如下:
lab1-mon: $(UCOREIMG)
$(V)$(TERMINAL) -e "$(QEMU) -S -s -d in_asm -D $(BINDIR)/q.log -m 202 onitor stdio -hda $< -serial null"
$(V)sleep 2
$(V)$(TERMINAL) -e "gdb -q -x tools/lab1init"
第一条指令是让qemu把执行的指令log信息记录下来放入q.log,第二条是用gdb调试正在执行的Bootloader。
查看 labcodes_answer/lab1_result/tools/lab1init 文件内容:
file bin/kernel
target remote :1234
set architecture i8086
b *0x7c00
continue
x /2i $pc
这里都是gdb能识别的指令,第一行是加载bin/kernel,第二行是与qemu连接,第三行设置当前调试的CPU是8086,第四行是在0x7c00处设置一个断点,第五行继续运行,第六行是显示PC处的两条指令。
1.从CPU加电后执行的第一条指令开始,单步跟踪BIOS的执行。
关于qemu这里摘录实验指导书的内容:
运行参数
如果 qemu 使用的是默认 /usr/local/bin 安装路径,则在命令行中可以直接使用 qemu 命令运行程序。qemu 运行可以有多参数,格式如:
qemu [options] [disk_image]
其中 disk_image 即硬盘镜像文件。
部分参数说明:
`-hda file' `-hdb file' `-hdc file' `-hdd file'
使用 file 作为硬盘0、1、2、3镜像。
`-fda file' `-fdb file'
使用 file 作为软盘镜像,可以使用 /dev/fd0 作为 file 来使用主机软盘。
`-cdrom file'
使用 file 作为光盘镜像,可以使用 /dev/cdrom 作为 file 来使用主机 cd-rom。
`-boot [a|c|d]'
从软盘(a)、光盘(c)、硬盘启动(d),默认硬盘启动。
`-snapshot'
写入临时文件而不写回磁盘镜像,可以使用 C-a s 来强制写回。
`-m megs'
设置虚拟内存为 msg M字节,默认为 128M 字节。
`-smp n'
设置为有 n 个 CPU 的 SMP 系统。以 PC 为目标机,最多支持 255 个 CPU。
`-nographic'
禁止使用图形输出。
其他:
可用的主机设备 dev 例如:
vc
虚拟终端。
null
空设备
/dev/XXX
使用主机的 tty。
file: filename
将输出写入到文件 filename 中。
stdio
标准输入/输出。
pipe:pipename
命令管道 pipename。
等。
使用 dev 设备的命令如:
`-serial dev'
重定向虚拟串口到主机设备 dev 中。
`-parallel dev'
重定向虚拟并口到主机设备 dev 中。
`-monitor dev'
重定向 monitor 到主机设备 dev 中。
其他参数:
`-s'
等待 gdb 连接到端口 1234。
`-p port'
改变 gdb 连接端口到 port。
`-S'
在启动时不启动 CPU, 需要在 monitor 中输入 'c',才能让qemu继续模拟工作。
`-d'
输出日志到 qemu.log 文件。
在可以使用 gdb 调试程序之前,必须使用 -g 或 –ggdb编译选项编译源文件。gdb 调试程序时通常使用的命令:gdb progname
这里是常用的一些指令:https://blog.csdn.net/q357010621/article/details/80244789
从lab0的内容中可以找到,要进入bin目录下,执行qemu -S -s -hda ./ucore.img -monitor stdio指令。
再打开一个terminal,输入以下指令:
gdb kernel
target remote 127.0.0.1:1234
si
x /2i $pc
si是stepi的简写,单步执行一条机械指令(包括进入函数)。
2.在初始化位置0x7c00设置实地址断点,测试断点正常。
(gdb) b*0x7c00
Breakpoint 1 at 0x7c00
(gdb) c
Continuing.
Breakpoint 1, 0x00007c00 in ?? ()
(gdb) x /2i $pc
=> 0x7c00: cli
0x7c01: cld
3.从0x7c00开始跟踪代码运行,将单步跟踪反汇编得到的代码与bootasm.S和 bootblock.asm进行比较。
反汇编(Disassembly):把目标代码转为汇编代码的过程,也可以说是把机器语言转换为汇编语言代码、低级转高级的意思。
(gdb) b*0x7c00
Breakpoint 1 at 0x7c00
(gdb) c
Continuing.
Breakpoint 1, 0x00007c00 in ?? ()
(gdb) x /2i $pc
=> 0x7c00: cli
0x7c01: cld
(gdb) si
0x00007c01 in ?? ()
(gdb) si
0x00007c02 in ?? ()
(gdb) x /10i $pc
=> 0x7c02: xor %eax,%eax
0x7c04: mov %eax,%ds
0x7c06: mov %eax,%es
0x7c08: mov %eax,%ss
0x7c0a: in $0x64,%al
0x7c0c: test $0x2,%al
0x7c0e: jne 0x7c0a
0x7c10: mov $0xd1,%al
0x7c12: out %al,$0x64
0x7c14: in $0x64,%al
与bootasm.S和 bootblock.asm进行比较后发现相应位置的代码一致。
bootblock.S:
#include <asm.h>
# Start the CPU: switch to 32-bit protected mode, jump into C.
# The BIOS loads this code from the first sector of the hard disk into
# memory at physical address 0x7c00 and starts executing in real mode
# with %cs=0 %ip=7c00.
.set PROT_MODE_CSEG, 0x8 # kernel code segment selector
.set PROT_MODE_DSEG, 0x10 # kernel data segment selector
.set CR0_PE_ON, 0x1 # protected mode enable flag
# start address should be 0:7c00, in real mode, the beginning address of the running bootloader
.globl start
start:
.code16 # Assemble for 16-bit mode
cli # Disable interrupts
cld # String operations increment
# Set up the important data segment registers (DS, ES, SS).
xorw %ax, %ax # Segment number zero
movw %ax, %ds # -> Data Segment
movw %ax, %es # -> Extra Segment
movw %ax, %ss # -> Stack Segment
# Enable A20:
# For backwards compatibility with the earliest PCs, physical
# address line 20 is tied low, so that addresses higher than
# 1MB wrap around to zero by default. This code undoes this.
seta20.1:
inb $0x64, %al # Wait for not busy(8042 input buffer empty).
testb $0x2, %al
jnz seta20.1
movb $0xd1, %al # 0xd1 -> port 0x64
outb %al, $0x64 # 0xd1 means: write data to 8042's P2 port
seta20.2:
inb $0x64, %al # Wait for not busy(8042 input buffer empty).
testb $0x2, %al
jnz seta20.2
movb $0xdf, %al # 0xdf -> port 0x60
outb %al, $0x60 # 0xdf = 11011111, means set P2's A20 bit(the 1 bit) to 1
# Switch from real to protected mode, using a bootstrap GDT
# and segment translation that makes virtual addresses
# identical to physical addresses, so that the
# effective memory map does not change during the switch.
lgdt gdtdesc
movl %cr0, %eax
orl $CR0_PE_ON, %eax
movl %eax, %cr0
# 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
protcseg:
# Set up the protected-mode data segment registers
movw $PROT_MODE_DSEG, %ax # Our data segment selector
movw %ax, %ds # -> DS: Data Segment
movw %ax, %es # -> ES: Extra Segment
movw %ax, %fs # -> FS
movw %ax, %gs # -> GS
movw %ax, %ss # -> SS: Stack Segment
# Set up the stack pointer and call into C. The stack region is from 0--start(0x7c00)
movl $0x0, %ebp
movl $start, %esp
call bootmain
# If bootmain returns (it shouldn't), loop.
spin:
jmp spin
# Bootstrap GDT
.p2align 2 # force 4 byte alignment
gdt:
SEG_NULLASM # null seg
SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff) # code seg for bootloader and kernel
SEG_ASM(STA_W, 0x0, 0xffffffff) # data seg for bootloader and kernel
gdtdesc:
.word 0x17 # sizeof(gdt) - 1
.long gdt # address gdt
bootblock.asm:
obj/bootblock.o: file format elf32-i386
Disassembly of section .text:
00007c00 <start>:
# start address should be 0:7c00, in real mode, the beginning address of the running bootloader
.globl start
start:
.code16 # Assemble for 16-bit mode
cli # Disable interrupts
7c00: fa cli
cld # String operations increment
7c01: fc cld
# Set up the important data segment registers (DS, ES, SS).
xorw %ax, %ax # Segment number zero
7c02: 31 c0 xor %eax,%eax
movw %ax, %ds # -> Data Segment
7c04: 8e d8 mov %eax,%ds
movw %ax, %es # -> Extra Segment
7c06: 8e c0 mov %eax,%es
movw %ax, %ss # -> Stack Segment
7c08: 8e d0 mov %eax,%ss
00007c0a <seta20.1>:
# Enable A20:
# For backwards compatibility with the earliest PCs, physical
# address line 20 is tied low, so that addresses higher than
# 1MB wrap around to zero by default. This code undoes this.
seta20.1:
inb $0x64, %al # Wait for not busy(8042 input buffer empty).
7c0a: e4 64 in $0x64,%al
testb $0x2, %al
7c0c: a8 02 test $0x2,%al
jnz seta20.1
7c0e: 75 fa jne 7c0a <seta20.1>
movb $0xd1, %al # 0xd1 -> port 0x64
7c10: b0 d1 mov $0xd1,%al
outb %al, $0x64 # 0xd1 means: write data to 8042's P2 port
7c12: e6 64 out %al,$0x64
00007c14 <seta20.2>:
seta20.2:
inb $0x64, %al # Wait for not busy(8042 input buffer empty).
7c14: e4 64 in $0x64,%al
testb $0x2, %al
7c16: a8 02 test $0x2,%al
jnz seta20.2
7c18: 75 fa jne 7c14 <seta20.2>
movb $0xdf, %al # 0xdf -> port 0x60
7c1a: b0 df mov $0xdf,%al
outb %al, $0x60 # 0xdf = 11011111, means set P2's A20 bit(the 1 bit) to 1
7c1c: e6 60 out %al,$0x60
......
4.自己找一个bootloader或内核中的代码位置,设置断点并进行测试。
(gdb) b* 0x7c14
Breakpoint 2 at 0x7c14
(gdb) c
Continuing.
Breakpoint 2, 0x00007c14 in ?? ()
(gdb) x /2i $pc
=> 0x7c14: in $0x64,%al
0x7c16: test $0x2,%al