# Lab1: exercises 3
Lab1: exercises 3
-
在地址0x7c00处设置一个断点,该地址是引导扇区的加载位置。 继续执行直到该断点。 使用源代码和反汇编文件 obj/boot/boot.asm 来跟踪 boot/boot.S 中的代码,以追踪代码的执行。 还可以在 GDB 中使用 x/i 命令反汇编引导加载程序中的指令序列,并将原始引导加载程序源代码与obj/boot/boot.asm和 GDB 中的反汇编进行比较。
-
跟踪到 boot/main.c 中的 bootmain() ,然后进入 readsect()。 标识与 readsect() 中的每个语句相对应的确切汇编指令。 遍历其余的 readsect() 并返回到 bootmain() 中,并标识用于从磁盘读取内核其余扇区的 for 循环的开始和结束。 找出循环结束后将运行的代码,在其中设置一个断点,然后继续该断点。 然后逐步完成引导加载程序的其余部分。
完成后回答如下问题:
· 处理器从什么时候开始执行32位代码? 究竟是什么原因导致从16位模式切换到32位模式?
· 引导加载程序执行的最后一条指令是什么,刚加载的内核的第一条指令是什么?
· 内核的第一条指令在哪里?
· 引导加载程序如何确定必须读取多少个扇区才能从磁盘获取整个内核? 它在哪里找到此信息?
开始整活儿
exercise 第一部分:
启动两个 terminal,第一个 执行 make qemu-nox-gdb
,第二个执行 make gdb
,接下来开始调试。
在第二个窗口执行 b *0x7c00
,在执行 c
将代码执行到断点处。接下来执行 x/Ni
将后续的 N 条指令反汇编出来,并与 obj/boot/boot.asm 进行比较。
(gdb) b *0x7c00
Breakpoint 1 at 0x7c00
(gdb) c
Continuing.
[ 0:7c00] => 0x7c00: cli
Breakpoint 1, 0x00007c00 in ?? ()
(gdb) x/10i
0x7c01: cld
0x7c02: xor %ax,%ax
0x7c04: mov %ax,%ds
0x7c06: mov %ax,%es
0x7c08: mov %ax,%ss
0x7c0a: in $0x64,%al
0x7c0c: test $0x2,%al
0x7c0e: jne 0x7c0a
0x7c10: mov $0xd1,%al
0x7c12: out %al,$0x64
对比 boot.asm
00007c00 <start>:
.set CR0_PE_ON, 0x1 # protected mode enable flag
.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>:
# 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:
# 从 0x64 端口读取一个字节,存于 ax 寄存器的低八位
inb $0x64,%al # Wait for not busy
7c0a: e4 64 in $0x64,%al
# 测试 al 中的第二位(与操作),若结果为 0 则 ZF = 1
testb $0x2,%al
7c0c: a8 02 test $0x2,%al
# ZF 为 0 就跳转循环 seta20.1
jnz seta20.1
7c0e: 75 fa jne 7c0a <seta20.1>
# 0x64 端口空闲,将 0xdl 送往该端口
movb $0xd1,%al # 0xd1 -> port 0x64
7c10: b0 d1 mov $0xd1,%al
outb %al,$0x64
7c12: e6 64 out %al,$0x64
发现在 boot.asm 中,将源码的标识符诸如 start、seta20.1
等和其对应的物理地址罗列了出来。在源码被会变成为机器代码过后,这些标识符会转换成物理地址。
exercise 第二部分:
直接读 boot.asm 的代码吧
先来一张intel 8086寄存器
8088在架构上与8086非常相似。主要区别是只有8条数据线,而不是8086的16条线。
很多汇编读不懂。。先放着吧
问题回答
-
处理器从什么时候开始执行32位代码? 究竟是什么原因导致从16位模式切换到32位模式?
从 boot.S 的这儿开始
# Jump to next instruction, but in 32-bit code segment. # Switches processor into 32-bit mode. ljmp $PROT_MODE_CSEG, $protcseg
-
引导加载程序执行的最后一条指令是什么,刚加载的内核的第一条指令是什么?
boot loader 执行的最后一条指令是
// call the entry point from the ELF header
// note: does not return!
((void (*)(void)) (ELFHDR->e_entry))();
通过在 boot.asm 中查找到 boot loader 最后执行的语句的物理地址为 0x7d6b
,在该处设置断点,查看其下一执行的语句。
-
内核的第一条指令在哪里?
-
引导加载程序如何确定必须读取多少个扇区才能从磁盘获取整个内核? 它在哪里找到此信息?
在 elf 头文件的 Program Header Table 中存放有。内核载入内存执行时,是以段为组织的,每个段对应 elf 文件中 Program Header Table 中的一个条目,故可以依据 Program Header Table 来确定需要读取多少扇区来获取整个内核。
这篇文章有一些关于 Program Header Table 的介绍:ELF文件解析(一):Segment和Section