#Lab1
这个Lab干什么
- 了解Linux启动流程。
- 通过具体代码trace启动指令.
##Exercise #1 学习,了解ATT汇编语法。
- 略
##Exercise #2 学习, 了解C语言。 特别是指针
- 略
##Exercise #3
Take a look at the lab tools guide, especially the section on GDB commands. Even if you’re familiar with GDB, this includes some esoteric GDB commands that are useful for OS work.
- 略
Set a breakpoint at address 0x7c00, which is where the boot sector will be loaded. Continue execution until that breakpoint. Trace through the code in boot/boot.S, using the source code and the disassembly file obj/boot/boot.asm to keep track of where you are. Also use the x/i command in GDB to disassemble sequences of instructions in the boot loader, and compare the original boot loader source code with both the disassembly in obj/boot/boot.asm and GDB.
- 0x7c00是bootloader读取的第一条指令的地址。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
obj/boot/boot.asm
obj/boot/boot.out: file format elf32-i386
Disassembly of section .text:
00007c00 <start>:
.set CR0_PE_ON, 0x1 # protected mode enable flag
.as
.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
...
Trace into bootmain() in boot/main.c, and then into readsect(). Identify the exact assembly instructions that correspond to each of the statements in readsect(). Trace through the rest of readsect() and back out into bootmain(), and identify the begin and end of the for loop that reads the remaining sectors of the kernel from the disk. Find out what code will run when the loop is finished, set a breakpoint there, and continue to that breakpoint. Then step through the remainder of the boot loader.
- 继续追踪bootloader的启动流程, boot.结束之后会调用boot/main.c里bootmain函数.
- 阅读boot/boot.S可知, 最后执行的语句是
1
2
3
4
call bootmain
7c45: e8 c2 00 00 00
call 7d0c <bootmain>
//we can also verify that 0x7d0c is the address of function bootmain in boot/main.c
##Exercise #4
阅读boot/main.c的代码.
- Load Address(LMA) & Link Address(VMA)
- 首先用objdump查看kernel elf文件.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
objdump -h obj/kern/kernel
obj/kern/kernel: file format elf32-i386
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00001a47 f0100000 00100000 00001000 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .rodata 00000714 f0101a60 00101a60 00002a60 2**5
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .stab 0000390d f0102174 00102174 00003174 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .stabstr 000018a2 f0105a81 00105a81 00006a81 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .data 0000a300 f0108000 00108000 00009000 2**12
CONTENTS, ALLOC, LOAD, DATA
5 .bss 00000644 f0112300 00112300 00013300 2**5
ALLOC
6 .comment 00000027 00000000 00000000 00013300 2**0
CONTENTS, READONLY
- 一般来说, Load Address(LMA) 是程序(代码/.text)在内存中的位置, 计算机,期望从Link Address(VMA) 执行(代码/.text)
-
大部分时候, 两者是一样的. 关于这儿的0xf0100000和0x00100000, 在后边会有解释.
- 预备知识
- ELF文件格式 , 重点知道elf结构体中几个属性的意思。
- bootmain主要只做了这么几件事
- 读取kernel ELF
- 根据kernel ELF获取kernel的载入地址 (Load Address) , 并载入.
- 载入kernel之后, 根据ELF头获取kernel需要执行的函数地址, 并执行.
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
32
33
#define SECTSIZE 512
#define ELFHDR ((struct Elf *) 0x10000) // scratch space
void readsect(void*, uint32_t);
void readseg(uint32_t, uint32_t, uint32_t);
void
bootmain(void)
{
struct Proghdr *ph, *eph;
// read 1st page off disk
readseg((uint32_t) ELFHDR, SECTSIZE*8, 0);
// is this a valid ELF?
if (ELFHDR->e_magic != ELF_MAGIC)
goto bad;
// load each program segment (ignores ph flags)
ph = (struct Proghdr *) ((uint8_t *) ELFHDR + ELFHDR->e_phoff);
eph = ph + ELFHDR->e_phnum;
for (; ph < eph; ph++)
// p_pa is the load address of this segment (as well
// as the physical address)
readseg(ph->p_pa, ph->p_memsz, ph->p_offset);
// call the entry point from the ELF header
// note: does not return!
((void (*)(void)) (ELFHDR->e_entry))();
// SUNUS: call this function and enter the kernel.
bad:
outw(0x8A00, 0x8A00);
outw(0x8A00, 0x8E00);
while (1)
/* do nothing */;
}
- 在
之后,我们进入了 kernel.1
((void (*)(void)) (ELFHDR->e_entry))();
1
2
((void (*)(void)) (ELFHDR->e_entry))();
7d63: ff 15 18 00 01 00 call *0x10018
- 在0x7d63处下断点,之后si执行一条指令。正是 kern/entry.S 的第一条指令。 也标志着我们进入了kernel :), 通过查看obj/kern/kernel文件, 我们知道了进入kernel后的第一条指令位于 0x0010000c 处, 并且对应的文件为 kern/entry.S
1
2
3
4
5
obj/kern/kernel: file format elf32-i386
architecture: i386, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x0010000c
##Exercise #7
Use QEMU and GDB to trace into the JOS kernel and stop at the movl %eax, %cr0. Examine memory at 0x00100000 and at 0xf0100000. Now, single step over that instruction using the stepi GDB command. Again, examine memory at 0x00100000 and at 0xf0100000. Make sure you understand what just happened.
What is the first instruction after the new mapping is established that would fail to work properly if the mapping weren’t in place? Comment out the movl %eax, %cr0 in kern/entry.S, trace into it, and see if you were right.
-
这就很简单了,
执行完之后开启了内存映射.(boot/boot.s也对%cr0做过一次操作, 在那儿是开启32bit模式)1
movl %eax, %cr0
-
打开kernel的反汇编文件 obj/kern/kernel.asm, 得到打开内存应试的指令的地址为 0xf010025
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
obj/kern/kernel: file format elf32-i386
Disassembly of section .text:
f0100000 <_start+0xeffffff4>:
.globl _start
_start = RELOC(entry)
.globl entry
entry:
movw $0x1234,0x472 # warm boot
f0100000: 02 b0 ad 1b 00 00 add 0x1bad(%eax),%dh
f0100006: 00 00 add %al,(%eax)
f0100008: fe 4f 52 decb 0x52(%edi)
f010000b: e4 66 in $0x66,%al
f010000c <entry>:
f010000c: 66 c7 05 72 04 00 00 movw $0x1234,0x472
f0100013: 34 12
# sufficient until we set up our real page table in mem_init
# in lab 2.
# Load the physical address of entry_pgdir into cr3. entry_pgdir
# is defined in entrypgdir.c.
movl $(RELOC(entry_pgdir)), %eax
f0100015: b8 00 00 11 00 mov $0x110000,%eax
movl %eax, %cr3
f010001a: 0f 22 d8 mov %eax,%cr3
# Turn on paging.
movl %cr0, %eax
f010001d: 0f 20 c0 mov %cr0,%eax
orl $(CR0_PE|CR0_PG|CR0_WP), %eax
f0100020: 0d 01 00 01 80 or $0x80010001,%eax
movl %eax, %cr0
f0100025: 0f 22 c0 mov %eax,%cr0
- 在 0x0010025处下断点, 与预想一致 即 0xf0100000的内存与 0x00100000内存内容相同.
- (由于在该指令之前, 并没有打开内存映射. 所以 0xf0100025实际上还是 0x0010025 )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
=> 0x100025: mov %eax,%cr0
Breakpoint 2, 0x00100025 in ?? ()
(gdb) x/8x 0xf0100025
0xf0100025 <entry+25>: 0x00000000 0x00000000 0x00000000 0x00000000
0xf0100035 <relocated+6>: 0x00000000 0x00000000 0x00000000 0x00000000
(gdb) x/8x 0x00100025
0x100025: 0xb8c0220f 0xf010002f 0x00bde0ff 0xbc000000
0x100035: 0xf0110000 0x00005fe8 0x55feeb00 0x8353e589
(gdb) si
=> 0x100028: mov $0xf010002f,%eax
0x00100028 in ?? ()
(gdb) x/8x 0xf0100025
0xf0100025 <entry+25>: 0xb8c0220f 0xf010002f 0x00bde0ff 0xbc000000
0xf0100035 <relocated+6>: 0xf0110000 0x00005fe8 0x55feeb00 0x8353e589
(gdb) x/8x 0xf0100025
0xf0100025 <entry+25>: 0xb8c0220f 0xf010002f 0x00bde0ff 0xbc000000
0xf0100035 <relocated+6>: 0xf0110000 0x00005fe8 0x55feeb00 0x8353e589