1 dd if=/dev/zero of=bin/ucore.img count=10000
2 dd if=bin/bootblock of=bin/ucore.img conv=notrunc
3 dd if=bin/kernel of=bin/ucore.img seek=1 conv=notrunc
// dd: convert and copy a file.
// for /dev/zero, Read operations from /dev/zero return as many null characters (0x00) as requested in the read operation.
// if: file which we are read from.
// of: file which we will write in.
// count: copy only N input blocks.
// conv: convert the file as per the comma separated symbol list.
// seek: skip a block. Here will simply jump 512 bytes. For default block size is 512 bytes.
(gdb) nI
=> 0xfff0: add %al,(%eax) # 0xfff0 -> eip.
# 探索了一下下
(gdb) x/i $cs << 4 + $pc
Argument to arithmetic operation not a number or boolean.
(gdb) x/i $($cs << 4 + $pc)
The history is empty.
(gdb) x/i ($cs << 4 + $pc)
Argument to arithmetic operation not a number or boolean.
(gdb) x/i (($cs << 4) + $pc)
0xfe05b: cmpw $0xffc8,%cs:(%esi)
# gdbinit function
define nI # for next Instruction.
x/i (($cs << 4) + $eip) # print the next instruction.
end
39 # start address should be 0:7c00, in real mode, the beginning address of the running bootloader
38 .globl start
37 start:
36 .code16 # Assemble for 16-bit mode
35 cli # Disable interrupts
34 cld # String operations increment
33
32 # Set up the important data segment registers (DS, ES, SS).
31 xorw %ax, %ax # Segment number zero
30 movw %ax, %ds # -> Data Segment
29 movw %ax, %es # -> Extra Segment
28 movw %ax, %ss # -> Stack Segment
27
26 # Enable A20:
25 # For backwards compatibility with the earliest PCs, physical
24 # address line 20 is tied low, so that addresses higher than
23 # 1MB wrap around to zero by default. This code undoes this.
22 seta20.1:
21 inb $0x64, %al # Wait for not busy(8042 input buffer empty).
20 testb $0x2, %al # bit 1: input buffer empty or not.
19 jnz seta20.1
18
17 movb $0xd1, %al # 0xd1 -> port 0x64
16 outb %al, $0x64 # 0xd1 means: write data to 8042's P2 port
15
14 seta20.2:
13 inb $0x64, %al # Wait for not busy(8042 input buffer empty).
12 testb $0x2, %al
11 jnz seta20.2
10
9 movb $0xdf, %al # 0xdf -> port 0x60
8 outb %al, $0x60 # 0xdf = 11011111, means set P2's A20 bit(the 1 bit) to 1
# Process:
# 1. start is the entry name, in Makefile process we inform ld to use start as the begin entry.
# 2. cli, cld: please refer to Intel Manual. It disable the interrupt flag.
# 3. clear segment register.
# A20 port should be opened here.
# How to open this port? -> 8042 port. 并且是output port上的某一位来控制
# 标准的写output的方法:
# 向64h发送0d1h命令,然后向60h写入Output Port的数据
# 因此方案就很清楚了:
# 1. 等待input buffer清空
# 2. 向64h发送0d1h表示要写入的命令
# 3. 再次等待input buffer清空
# 4. 写入数据0xdfh,使得倒数第二位为1即可,上面的0xdf只是一个选择
4 # Switch from real to protected mode, using a bootstrap GDT
3 # and segment translation that makes virtual addresses
2 # identical to physical addresses, so that the
1 # effective memory map does not change during the switch.
49 lgdt gdtdesc # define gdt before protected mode. the address is in gdtdesc.
## skip some code, simply check the gdt here. ##
1 gdt:
80 SEG_NULLASM # null seg
1 SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff) # code seg for bootloader and kernel
2 SEG_ASM(STA_W, 0x0, 0xffffffff) # data seg for bootloader and kernel
3
4 gdtdesc:
5 .word 0x17 # sizeof(gdt) - 1
6 .long gdt # address gdt
Flags
3 2 1 0
G DB L Reserved
G: granularity flag, 0->Limit value One Byte. 1->Limit value 4 KiB.
DB: size flag. 1->32-bit protected mode segment. 0 for 16-bit.
L: for 64-bit. skip here.
2 .set PROT_MODE_CSEG, 0x8 # kernel code segment selector
1 .set PROT_MODE_DSEG, 0x10 # kernel data segment selector
10 .set CR0_PE_ON, 0x1 # protected mode enable flag
15 movl %cr0, %eax
16 orl $CR0_PE_ON, %eax
17 movl %eax, %cr0
18
19 # Jump to next instruction, but in 32-bit code segment.
20 # Switches processor into 32-bit mode.
21 ljmp $PROT_MODE_CSEG, $protcseg
练习4:
bootloader如何读取硬盘扇区的?
bootloader是如何加载ELF格式的OS?
我们先给上一节的小尾巴做一个切断。
7 .code32 # Assemble for 32-bit mode
8 protcseg:
9 # Set up the protected-mode data segment registers
10 movw $PROT_MODE_DSEG, %ax # Our data segment selector
11 movw %ax, %ds # -> DS: Data Segment
12 movw %ax, %es # -> ES: Extra Segment
13 movw %ax, %fs # -> FS
14 movw %ax, %gs # -> GS
15 movw %ax, %ss # -> SS: Stack Segment
16
17 # Set up the stack pointer and call into C. The stack region is from 0--start(0x7c00)
18 movl $0x0, %ebp
19 movl $start, %esp
20 call bootmain
# PROT_MODE_DSEG: we have mentioned before. Set it for data segments.
# Stack: set the stack, with ebp->0x0, esp->0x7c00
# After the intialization of stack, call function bootmain.
现在我们进入函数bootmain,该文件位于bootmain.c中。
3 /* bootmain - the entry of bootloader */
2 void
1 bootmain(void) {
88 // read the 1st page off disk
1 readseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0);
2
3 // is this a valid ELF?
4 if (ELFHDR->e_magic != ELF_MAGIC) {
5 goto bad;
6 }
7
该函数调用了readseg函数,同时检查ELFHDR头是否是合法的,具体是检查魔数。
先看到readseg函数,以及内部的readsect函数。我们在注释中做了更详细的解析工作。
1 /* *
64 * readseg - read @count bytes at @offset from kernel into virtual address @va,
1 * might copy more than asked.
2 * */
3 static void
4 readseg(uintptr_t va, uint32_t count, uint32_t offset) {
5 uintptr_t end_va = va + count;
6
7 // round down to sector boundary
8 va -= offset % SECTSIZE; // when reading, va will start at the beginning of sector.
9
10 // translate from bytes to sectors; kernel starts at sector 1
11 uint32_t secno = (offset / SECTSIZE) + 1; // sector number.
12
13 // If this is too slow, we could read lots of sectors at a time.
14 // We'd write more to memory than asked, but it doesn't matter --
15 // we load in increasing order.
16 for (; va < end_va; va += SECTSIZE, secno ++) {
17 readsect((void *)va, secno); // using readsect to read by sector.
18 }
19 }
10 /* waitdisk - wait for disk ready */
11 static void // for 0xC0 and 0x40, I don't know the details. It is also dirty knowledge.
12 waitdisk(void) {
13 while ((inb(0x1F7) & 0xC0) != 0x40) // 0x1f7 is the state and order register.
14 /* inb is an inline-assembly function form of `inb port, data`*/;
15 }
17 /* readsect - read a single sector at @secno into @dst */
18 static void
19 readsect(void *dst, uint32_t secno) {
20 // wait for disk to be ready
21 waitdisk();
22 // outb: load byte into ports. Secno is the abstract address in LEA.
23 outb(0x1F2, 1); // count = 1. One sector every time.
24 outb(0x1F3, secno & 0xFF); // LEA: low 8
25 outb(0x1F4, (secno >> 8) & 0xFF); // LEA: mid 8
26 outb(0x1F5, (secno >> 16) & 0xFF); // LEA: high 8
27 outb(0x1F6, ((secno >> 24) & 0xF) | 0xE0); // LEA: 1110 [lba high 4].
28 outb(0x1F7, 0x20); // cmd 0x20 - read sectors
29
30 // wait for disk to be ready
31 waitdisk();
32
33 // read a sector
34 insl(0x1F0, dst, SECTSIZE / 4);
35 }