Booting a PC
Table of Contents
Old things
Warning: These assembly are in AT&T
syntax: which is: mnemonic(OP) source, destination
.
INTEL
syntax is:mnemonic destination, source
see here for reference.
Part 1: PC Bootstrap
The PC’s Physical Address Space
Type | Address |
---|---|
32-bit memory mapping high boundary | 0xFFFFFFFF (4GB) |
Extended Memory | depends on amount of RAM |
BIOS ROM | 0x000F0000 ~ 0x00100000 (1MB) (+64KB) |
16-bit devices & expansion ROMs | 0x000C0000 ~ 0x000F0000 (960KB) (+192KB) |
VGA Display | 0x000A0000 ~ 0x000C0000 (768KB) (+128KB) |
Low Memory | 0x00000000 ~ 0x000A0000 (640KB) (+640KB) |
Exercise 1
Nothing to do.
Exercise 2
[a:b]: CS=a, IP=b
The target architecture is assumed to be i8086
[f000:fff0] 0xffff0: ljmp $0xf000,$0xe05b
0x0000fff0 in ?? ()
+ symbol-file obj/kern/kernel
(gdb) si
[f000:e05b] 0xfe05b: cmpl $0x0,%cs:0x6ac8
0x0000e05b in ?? ()
(gdb) si
[f000:e062] 0xfe062: jne 0xfd2e1
0x0000e062 in ?? ()
(gdb) si
[f000:e066] 0xfe066: xor %dx,%dx
0x0000e066 in ?? ()
(gdb) si
[f000:e068] 0xfe068: mov %dx,%ss
0x0000e068 in ?? ()
(gdb) si
[f000:e06a] 0xfe06a: mov $0x7000,%esp
0x0000e06a in ?? ()
(gdb) si
[f000:e070] 0xfe070: mov $0xf34c2,%edx
0x0000e070 in ?? ()
(gdb) si
[f000:e076] 0xfe076: jmp 0xfd15c
0x0000e076 in ?? ()
(gdb) si
[f000:d15c] 0xfd15c: mov %eax,%ecx
0x0000d15c in ?? ()
(gdb) si
[f000:d15f] 0xfd15f: cli
0x0000d15f in ?? ()
(gdb) si
[f000:d160] 0xfd160: cld
0x0000d160 in ?? ()
(gdb) si
[f000:d161] 0xfd161: mov $0x8f,%eax
0x0000d161 in ?? ()
(gdb) si
[f000:d167] 0xfd167: out %al,$0x70
0x0000d167 in ?? ()
(gdb) si
[f000:d169] 0xfd169: in $0x71,%al
0x0000d169 in ?? ()
(gdb) si
[f000:d16b] 0xfd16b: in $0x92,%al
0x0000d16b in ?? ()
(gdb) si
[f000:d16d] 0xfd16d: or $0x2,%al
0x0000d16d in ?? ()
(gdb) si
[f000:d16f] 0xfd16f: out %al,$0x92
0x0000d16f in ?? ()
(gdb) si
[f000:d171] 0xfd171: lidtw %cs:0x6ab8
0x0000d171 in ?? ()
(gdb) si
[f000:d177] 0xfd177: lgdtw %cs:0x6a74
0x0000d177 in ?? ()
(gdb) si
[f000:d17d] 0xfd17d: mov %cr0,%eax
0x0000d17d in ?? ()
(gdb) si
[f000:d180] 0xfd180: or $0x1,%eax
0x0000d180 in ?? ()
(gdb) si
[f000:d184] 0xfd184: mov %eax,%cr0
0x0000d184 in ?? ()
(gdb) si
[f000:d187] 0xfd187: ljmpl $0x8,$0xfd18f
0x0000d187 in ?? ()
(gdb) si
The target architecture is assumed to be i386
=> 0xfd18f: mov $0x10,%eax
0x000fd18f in ?? ()
(gdb) si
=> 0xfd194: mov %eax,%ds
0x000fd194 in ?? ()
(gdb) si
=> 0xfd196: mov %eax,%es
0x000fd196 in ?? ()
(gdb) si
=> 0xfd198: mov %eax,%ss
0x000fd198 in ?? ()
(gdb) si
=> 0xfd19a: mov %eax,%fs
0x000fd19a in ?? ()
(gdb) si
=> 0xfd19c: mov %eax,%gs
0x000fd19c in ?? ()
(gdb) si
=> 0xfd19e: mov %ecx,%eax
0x000fd19e in ?? ()
(gdb) si
=> 0xfd1a0: jmp *%edx
0x000fd1a0 in ?? ()
(gdb) si
=> 0xf34c2: push %ebx
0x000f34c2 in ?? ()
(gdb) si
=> 0xf34c3: sub $0x2c,%esp
0x000f34c3 in ?? ()
(gdb) si
=> 0xf34c6: movl $0xf5b5c,0x4(%esp)
0x000f34c6 in ?? ()
(gdb) si
=> 0xf34ce: movl $0xf447b,(%esp)
0x000f34ce in ?? ()
(gdb) si
=> 0xf34d5: call 0xf099e
0x000f34d5 in ?? ()
(gdb) si
=> 0xf099e: lea 0x8(%esp),%ecx
0x000f099e in ?? ()
(gdb) si
=> 0xf09a2: mov 0x4(%esp),%edx
0x000f09a2 in ?? ()
(gdb) si
=> 0xf09a6: mov $0xf5b58,%eax
0x000f09a6 in ?? ()
(gdb) si
=> 0xf09ab: call 0xf0574
0x000f09ab in ?? ()
(gdb) si
=> 0xf0574: push %ebp
0x000f0574 in ?? ()
(gdb) si
=> 0xf0575: push %edi
0x000f0575 in ?? ()
(gdb) si
=> 0xf0576: push %esi
0x000f0576 in ?? ()
(gdb) si
=> 0xf0577: push %ebx
0x000f0577 in ?? ()
(gdb) si
=> 0xf0578: sub $0xc,%esp
0x000f0578 in ?? ()
(gdb) si
=> 0xf057b: mov %eax,0x4(%esp)
0x000f057b in ?? ()
(gdb) si
=> 0xf057f: mov %edx,%ebp
0x000f057f in ?? ()
(gdb) si
=> 0xf0581: mov %ecx,%esi
0x000f0581 in ?? ()
* Marshmallow's comment
* Loop start pos
(gdb) si
=> 0xf0583: movsbl 0x0(%ebp),%edx
0x000f0583 in ?? ()
(gdb) si
=> 0xf0587: test %dl,%dl
0x000f0587 in ?? ()
(gdb) si
=> 0xf0589: je 0xf0758
0x000f0589 in ?? ()
(gdb) si
=> 0xf058f: cmp $0x25,%dl
0x000f058f in ?? ()
(gdb) si
=> 0xf0592: jne 0xf0741
0x000f0592 in ?? ()
(gdb) si
=> 0xf0741: mov 0x4(%esp),%eax
0x000f0741 in ?? ()
(gdb) si
=> 0xf0745: call 0xefc70
0x000f0745 in ?? ()
(gdb) si
=> 0xefc70: mov %eax,%ecx
0x000efc70 in ?? ()
(gdb) si
=> 0xefc72: movsbl %dl,%edx
0x000efc72 in ?? ()
(gdb) si
=> 0xefc75: call *(%ecx)
0x000efc75 in ?? ()
(gdb) si
=> 0xefc65: mov %edx,%eax
0x000efc65 in ?? ()
(gdb) si
=> 0xefc67: mov 0xf693c,%dx
0x000efc67 in ?? ()
(gdb) si
=> 0xefc6e: out %al,(%dx)
0x000efc6e in ?? ()
(gdb) si
=> 0xefc6f: ret
0x000efc6f in ?? ()
(gdb) si
=> 0xefc77: ret
0x000efc77 in ?? ()
(gdb) si
=> 0xf074a: mov %ebp,%ebx
0x000f074a in ?? ()
(gdb) si
=> 0xf074c: jmp 0xf0750
0x000f074c in ?? ()
(gdb) si
=> 0xf0750: lea 0x1(%ebx),%ebp
0x000f0750 in ?? ()
(gdb) si
=> 0xf0753: jmp 0xf0583
0x000f0753 in ?? ()
* Marshmallow's comment
* Loop end pos
0x000f0583 in ?? ()
(gdb) si
=> 0xf0587: test %dl,%dl
0x000f0587 in ?? ()
(gdb) si
=> 0xf0589: je 0xf0758
0x000f0589 in ?? ()
=> 0xf0758: add $0xc,%esp
(gdb) si
=> 0xf075b: pop %ebx
0x000f075b in ?? ()
(gdb) si
=> 0xf075c: pop %esi
0x000f075c in ?? ()
(gdb) si
=> 0xf075d: pop %edi
0x000f075d in ?? ()
(gdb) si
=> 0xf075e: pop %ebp
0x000f075e in ?? ()
(gdb) si
=> 0xf075f: ret
0x000f075f in ?? ()
(gdb) si
=> 0xf09b0: ret
0x000f09b0 in ?? ()
(gdb) si
=> 0xf34da: mov $0x40000000,%ebx
0x000f34da in ?? ()
(gdb) si
* Loop start
=> 0xf34df: lea 0x18(%esp),%eax
0x000f34df in ?? ()
(gdb) si
=> 0xf34e3: mov %eax,0x4(%esp)
0x000f34e3 in ?? ()
(gdb) si
=> 0xf34e7: lea 0x14(%esp),%eax
0x000f34e7 in ?? ()
(gdb) si
=> 0xf34eb: mov %eax,(%esp)
0x000f34eb in ?? ()
(gdb) si
=> 0xf34ee: lea 0x10(%esp),%ecx
0x000f34ee in ?? ()
(gdb) si
=> 0xf34f2: lea 0xc(%esp),%edx
0x000f34f2 in ?? ()
(gdb) si
=> 0xf34f6: mov %ebx,%eax
0x000f34f6 in ?? ()
(gdb) si
=> 0xf34f8: call 0xefea4
0x000f34f8 in ?? ()
(gdb) si
=> 0xefea4: push %ebp
0x000efea4 in ?? ()
(gdb) si
=> 0xefea5: push %edi
0x000efea5 in ?? ()
(gdb) si
=> 0xefea6: push %esi
0x000efea6 in ?? ()
(gdb) si
=> 0xefea7: push %ebx
0x000efea7 in ?? ()
(gdb) si
=> 0xefea8: mov %edx,%esi
0x000efea8 in ?? ()
(gdb) si
=> 0xefeaa: mov %ecx,%edi
0x000efeaa in ?? ()
(gdb) si
=> 0xefeac: mov 0x14(%esp),%ebp
0x000efeac in ?? ()
(gdb) si
=> 0xefeb0: pushf
0x000efeb0 in ?? ()
(gdb) si
=> 0xefeb1: pop %ecx
0x000efeb1 in ?? ()
(gdb) si
=> 0xefeb2: mov %ecx,%edx
0x000efeb2 in ?? ()
(gdb) si
=> 0xefeb4: xor $0x200000,%edx
0x000efeb4 in ?? ()
(gdb) si
=> 0xefeba: push %edx
0x000efeba in ?? ()
(gdb) si
=> 0xefebb: popf
0x000efebb in ?? ()
(gdb) si
=> 0xefebc: pushf
0x000efebc in ?? ()
(gdb) si
=> 0xefebd: pop %edx
0x000efebd in ?? ()
(gdb) si
=> 0xefebe: push %ecx
0x000efebe in ?? ()
(gdb) si
=> 0xefebf: popf
0x000efebf in ?? ()
(gdb) si
=> 0xefec0: xor %ecx,%edx
0x000efec0 in ?? ()
(gdb) si
=> 0xefec2: and $0x200000,%edx
0x000efec2 in ?? ()
(gdb) si
=> 0xefec8: jne 0xefee9
0x000efec8 in ?? ()
(gdb) si
=> 0xefee9: cpuid
0x000efee9 in ?? ()
(gdb) si
=> 0xefeeb: mov %eax,(%esi)
0x000efeeb in ?? ()
(gdb) si
=> 0xefeed: mov %ebx,(%edi)
0x000efeed in ?? ()
(gdb) si
=> 0xefeef: mov %ecx,0x0(%ebp)
0x000efeef in ?? ()
(gdb) si
=> 0xefef2: mov 0x18(%esp),%eax
0x000efef2 in ?? ()
(gdb) si
=> 0xefef6: mov %edx,(%eax)
0x000efef6 in ?? ()
(gdb) si
=> 0xefef8: pop %ebx
0x000efef8 in ?? ()
(gdb) si
=> 0xefef9: pop %esi
0x000efef9 in ?? ()
(gdb) si
=> 0xefefa: pop %edi
0x000efefa in ?? ()
(gdb) si
=> 0xefefb: pop %ebp
0x000efefb in ?? ()
(gdb) si
=> 0xefefc: ret
0x000efefc in ?? ()
(gdb) si
=> 0xf34fd: mov 0x10(%esp),%eax
0x000f34fd in ?? ()
(gdb) si
=> 0xf3501: mov %eax,0x1f(%esp)
0x000f3501 in ?? ()
(gdb) si
=> 0xf3505: mov 0x14(%esp),%eax
0x000f3505 in ?? ()
(gdb) si
=> 0xf3509: mov %eax,0x23(%esp)
0x000f3509 in ?? ()
(gdb) si
=> 0xf350d: mov 0x18(%esp),%eax
0x000f350d in ?? ()
(gdb) si
=> 0xf3511: mov %eax,0x27(%esp)
0x000f3511 in ?? ()
(gdb) si
=> 0xf3515: movb $0x0,0x2b(%esp)
0x000f3515 in ?? ()
(gdb) si
=> 0xf351a: mov $0xf58fb,%edx
0x000f351a in ?? ()
(gdb) si
=> 0xf351f: lea 0x1f(%esp),%eax
0x000f351f in ?? ()
(gdb) si
=> 0xf3523: call 0xefd55
0x000f3523 in ?? ()
(gdb) si
=> 0xefd55: push %ebx
0x000efd55 in ?? ()
(gdb) si
=> 0xefd56: xor %ecx,%ecx
0x000efd56 in ?? ()
(gdb) si
=> 0xefd58: mov (%eax,%ecx,1),%bl
0x000efd58 in ?? ()
(gdb) si
=> 0xefd5b: cmp (%edx,%ecx,1),%bl
0x000efd5b in ?? ()
(gdb) si
=> 0xefd5e: je 0xefd6c
0x000efd5e in ?? ()
(gdb) si
=> 0xefd60: setge %al
0x000efd60 in ?? ()
(gdb) si
=> 0xefd63: movzbl %al,%eax
0x000efd63 in ?? ()
(gdb) si
=> 0xefd66: lea -0x1(%eax,%eax,1),%eax
0x000efd66 in ?? ()
(gdb) si
=> 0xefd6a: jmp 0xefd73
0x000efd6a in ?? ()
(gdb) si
=> 0xefd73: pop %ebx
0x000efd73 in ?? ()
(gdb) si
=> 0xefd74: ret
0x000efd74 in ?? ()
(gdb) si
=> 0xf3528: test %eax,%eax
0x000f3528 in ?? ()
(gdb) si
=> 0xf352a: jne 0xf3582
0x000f352a in ?? ()
(gdb) si
=> 0xf3582: add $0x100,%ebx
0x000f3582 in ?? ()
(gdb) si
=> 0xf3588: cmp $0x40010000,%ebx
0x000f3588 in ?? ()
(gdb) si
=> 0xf358e: jne 0xf34df
0x000f358e in ?? ()
* Loop end
* je out to 0xefd6c
* Loop start
=> 0xefd6c: inc %ecx
(gdb) si
=> 0xefd6d: test %bl,%bl
0x000efd6d in ?? ()
(gdb) si
=> 0xefd6f: jne 0xefd58
0x000efd6f in ?? ()
(gdb) si
=> 0xefd58: mov (%eax,%ecx,1),%bl
0x000efd58 in ?? ()
(gdb) si
=> 0xefd5b: cmp (%edx,%ecx,1),%bl
0x000efd5b in ?? ()
(gdb) si
=> 0xefd5e: je 0xefd6c
0x000efd5e in ?? ()
* Loop end
* jne not satisfied, jmp out to 0xefd73
=> 0xefd73: pop %ebx
(gdb) si
=> 0xefd74: ret
0x000efd74 in ?? ()
(gdb) si
=> 0xf3528: test %eax,%eax
0x000f3528 in ?? ()
(gdb) si
=> 0xf352a: jne 0xf3582
0x000f352a in ?? ()
Part 2: The Boot Loader
Exercise 3
Set breakpoint at 0x7c00
(gdb) b *0x7c00
Breakpoint 1 at 0x7c00
Continue till breakpoint:
(gdb) c
Continuing.
[ 0:7c00] => 0x7c00: cli
Breakpoint 1, 0x00007c00 in ?? ()
-
At what point does the processor start executing 32-bit code?
-
What exactly causes the switch from 16- to 32-bit mode?
boot/boot.S
, Line 55
# Switch from real to protected mode, using a bootstrap GDT
# and segment translation that makes virtual addresses
# identical to their 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 <!-- Line 55 -->
Disassembly in obj/boot/boot.asm
, Line 77
# Switch from real to protected mode, using a bootstrap GDT
# and segment translation that makes virtual addresses
# identical to their physical addresses, so that the
# effective memory map does not change during the switch.
lgdt gdtdesc
7c1e: 0f 01 16 lgdtl (%esi)
7c21: 64 7c 0f fs jl 7c33 <protcseg+0x1>
movl %cr0, %eax
7c24: 20 c0 and %al,%al
orl $CR0_PE_ON, %eax
7c26: 66 83 c8 01 or $0x1,%ax
movl %eax, %cr0
7c2a: 0f 22 c0 mov %eax,%cr0
# Jump to next instruction, but in 32-bit code segment.
# Switches processor into 32-bit mode.
ljmp $PROT_MODE_CSEG, $protcseg # <!-- Line 77 -->
7c2d: ea .byte 0xea
7c2e: 32 7c 08 00 xor 0x0(%eax,%ecx,1),%bh
After this, the processor start to function in 32-bit mode.
- What is the last instruction of the boot loader executed, and what is the first instruction of the kernel it just loaded?
The last instruction of the boot loader executed is here at boot/main.c
Line 60:
// call the entry point from the ELF header
// note: does not return!
((void (*)(void)) (ELFHDR->e_entry))(); // <!-- Line 60 -->
The first instruction of the kernel is at kern/entry.S
Line 44, which is the entry point of the kernel ELF:
# '_start' specifies the ELF entry point. Since we haven't set up
# virtual memory when the bootloader enters this code, we need the
# bootloader to jump to the *physical* address of the entry point.
.globl _start
_start = RELOC(entry)
.globl entry
entry:
movw $0x1234,0x472 # warm boot <!-- Line 44 -->
- Where is the first instruction of the kernel?
The first instruction of the kernel is at is at kern/entry.S
Line 44, address 0x0010000c
.
# '_start' specifies the ELF entry point. Since we haven't set up
# virtual memory when the bootloader enters this code, we need the
# bootloader to jump to the *physical* address of the entry point.
.globl _start
_start = RELOC(entry)
.globl entry
entry:
movw $0x1234,0x472 # warm boot <!-- Line 44 -->
Reminder: in objdump
, it seems that it’s address is 0xf010000c
:
f010000c <entry>:
f010000c: 66 c7 05 72 04 00 00 movw $0x1234,0x472
f0100013: 34 12
f0100015: b8 00 20 11 00 mov $0x112000,%eax
f010001a: 0f 22 d8 mov %eax,%cr3
f010001d: 0f 20 c0 mov %cr0,%eax
f0100020: 0d 01 00 01 80 or $0x80010001,%eax
f0100025: 0f 22 c0 mov %eax,%cr0
f0100028: b8 2f 00 10 f0 mov $0xf010002f,%eax
f010002d: ff e0 jmp *%eax
- How does the boot loader decide how many sectors it must read in order to fetch the entire kernel from disk?
In boot/main.c
Line 52, where eph
is the size of sectors:
// load each program segment (ignores ph flags)
ph = (struct Proghdr *) ((uint8_t *) ELFHDR + ELFHDR->e_phoff);
eph = ph + ELFHDR->e_phnum; <!-- Line 52 -->
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);
- Where does it find this information?
ELFHDR->e_phnum
:
#define ELFHDR ((struct Elf *) 0x10000) // scratch space
// some codes
// load each program segment (ignores ph flags)
ph = (struct Proghdr *) ((uint8_t *) ELFHDR + ELFHDR->e_phoff);
eph = ph + ELFHDR->e_phnum; <!-- Line 52 -->
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);
Exercise 4
Nothing to do.
Exercise 5
- Then change the link address in boot/Makefrag to something wrong, run make clean, recompile the lab with make, and trace into the boot loader again to see what happens. Don’t forget to change the link address back and make clean again afterward!
Now let’s try something.
Modify boot/Makefrag
: in line 28,
$(OBJDIR)/boot/boot: $(BOOT_OBJS)
@echo + ld boot/boot
$(V)$(LD) $(LDFLAGS) -N -e start -Ttext 0x7C00 -o [email protected] $^ # Line 28
$(V)$(OBJDUMP) -S [email protected] >[email protected]
$(V)$(OBJCOPY) -S -O binary -j .text [email protected] $@
$(V)perl boot/sign.pl $(OBJDIR)/boot/boot
from 0x7C00
to 0x1145
:
$(OBJDIR)/boot/boot: $(BOOT_OBJS)
@echo + ld boot/boot
$(V)$(LD) $(LDFLAGS) -N -e start -Ttext 0x1145 -o [email protected] $^ # Line 28
$(V)$(OBJDUMP) -S [email protected] >[email protected]
$(V)$(OBJCOPY) -S -O binary -j .text [email protected] $@
$(V)perl boot/sign.pl $(OBJDIR)/boot/boot
then make clean && make qemu-gdb
.
EAX=00000011 EBX=00000000 ECX=00000000 EDX=00000080
ESI=00000000 EDI=00000000 EBP=00000000 ESP=00006f20
EIP=00007c30 EFL=00000006 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0000 00000000 0000ffff 00009300 DPL=0 DS16 [-WA]
CS =0000 00000000 0000ffff 00009b00 DPL=0 CS16 [-RA]
SS =0000 00000000 0000ffff 00009300 DPL=0 DS16 [-WA]
DS =0000 00000000 0000ffff 00009300 DPL=0 DS16 [-WA]
FS =0000 00000000 0000ffff 00009300 DPL=0 DS16 [-WA]
GS =0000 00000000 0000ffff 00009300 DPL=0 DS16 [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT= 00000000 00000000
IDT= 00000000 000003ff
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000
DR6=ffff0ff0 DR7=00000400
EFER=0000000000000000
Triple fault. Halting for inspection via QEMU monitor.
Exercise 6
- Reset the machine (exit QEMU/GDB and start them again).
- Examine the 8 words of memory at 0x00100000 at the point the BIOS enters the boot loader…
(gdb) x/8x 0x00100000
0x100000: 0x00000000 0x00000000 0x00000000 0x00000000
0x100010: 0x00000000 0x00000000 0x00000000 0x00000000
- …and then again at the point the boot loader enters the kernel.
(gdb) b * 0x10000c
Breakpoint 2 at 0x10000c
(gdb) c
Continuing.
The target architecture is assumed to be i386
=> 0x10000c: movw $0x1234,0x472
Breakpoint 2, 0x0010000c in ?? ()
(gdb) x/8x 0x00100000
0x100000: 0x1badb002 0x00000000 0xe4524ffe 0x7205c766
0x100010: 0x34000004 0x2000b812 0x220f0011 0xc0200fd8
- Why are they different?
Cuz the bootloader loads the kernel to 0x00100000
, so the contents in RAM changed.
(gdb) x/8i 0x00100000
0x100000: add 0x1bad(%eax),%dh
0x100006: add %al,(%eax)
0x100008: decb 0x52(%edi)
0x10000b: in $0x66,%al
0x10000d: movl $0xb81234,0x472
0x100017: and %dl,(%ecx)
0x100019: add %cl,(%edi)
0x10001b: and %al,%bl
Part 3: The Kernel
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.
Breakpoint 1, 0x0010000c in ?? ()
(gdb) si
=> 0x100015: mov $0x112000,%eax
0x00100015 in ?? ()
(gdb) si
=> 0x10001a: mov %eax,%cr3
0x0010001a in ?? ()
(gdb) si
=> 0x10001d: mov %cr0,%eax
0x0010001d in ?? ()
(gdb) si
=> 0x100020: or $0x80010001,%eax
0x00100020 in ?? ()
=> 0x100025: mov %eax,%cr0
0x00100025 in ?? ()
(gdb) x/8x 0x00100000
0x100000: 0x1badb002 0x00000000 0xe4524ffe 0x7205c766
0x100010: 0x34000004 0x2000b812 0x220f0011 0xc0200fd8
(gdb) x/8x 0xf0100000
0xf0100000 <_start+4026531828>: 0x00000000 0x00000000 0x00000000 0x00000000
0xf0100010 <entry+4>: 0x00000000 0x00000000 0x00000000 0x00000000
- 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.
(gdb) si
=> 0x100028: mov $0xf010002f,%eax
0x00100028 in ?? ()
(gdb) x/8x 0x00100000
0x100000: 0x1badb002 0x00000000 0xe4524ffe 0x7205c766
0x100010: 0x34000004 0x2000b812 0x220f0011 0xc0200fd8
(gdb) x/8x 0xf0100000
0xf0100000 <_start+4026531828>: 0x1badb002 0x00000000 0xe4524ffe 0x7205c766
0xf0100010 <entry+4>: 0x34000004 0x2000b812 0x220f0011 0xc0200fd8
What happened?
Paging is enabled.
After %cr0
is set: (CR0
= 80010011
), all memory references are using virtual address. Paging directory and paging table are in kern/entrypgdir.c
.
pte_t entry_pgtable[NPTENTRIES];
// The entry.S page directory maps the first 4MB of physical memory
// starting at virtual address KERNBASE (that is, it maps virtual
// addresses [KERNBASE, KERNBASE+4MB) to physical addresses [0, 4MB)).
// We choose 4MB because that's how much we can map with one page
// table and it's enough to get us through early boot. We also map
// virtual addresses [0, 4MB) to physical addresses [0, 4MB); this
// region is critical for a few instructions in entry.S and then we
// never use it again.
//
// Page directories (and page tables), must start on a page boundary,
// hence the "__aligned__" attribute. Also, because of restrictions
// related to linking and static initializers, we use "x + PTE_P"
// here, rather than the more standard "x | PTE_P". Everywhere else
// you should use "|" to combine flags.
__attribute__((__aligned__(PGSIZE)))
pde_t entry_pgdir[NPDENTRIES] = {
// Map VA's [0, 4MB) to PA's [0, 4MB)
[0]
= ((uintptr_t)entry_pgtable - KERNBASE) + PTE_P,
// Map VA's [KERNBASE, KERNBASE+4MB) to PA's [0, 4MB)
[KERNBASE>>PDXSHIFT]
= ((uintptr_t)entry_pgtable - KERNBASE) + PTE_P + PTE_W
};
- 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.
GDB console
(gdb) si
=> 0x100020: or $0x80010001,%eax
0x00100020 in ?? ()
(gdb) si
=> 0x100025: mov $0xf010002c,%eax
0x00100025 in ?? ()
(gdb) si
=> 0x10002a: jmp *%eax
0x0010002a in ?? ()
(gdb) si
=> 0xf010002c <relocated>: add %al,(%eax)
relocated () at kern/entry.S:74
74 movl $0x0,%ebp # nuke frame pointer
(gdb) si
Remote connection closed
QEMU console
qemu: fatal: Trying to execute code outside RAM or ROM at 0xf010002c
EAX=f010002c EBX=00010094 ECX=00000000 EDX=000000a4
ESI=00010094 EDI=00000000 EBP=00007bf8 ESP=00007bec
EIP=f010002c EFL=00000086 [--S--P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT= 00007c4c 00000017
IDT= 00000000 000003ff
CR0=00000011 CR2=00000000 CR3=00112000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000
DR6=ffff0ff0 DR7=00000400
CCS=00000084 CCD=80010011 CCO=EFLAGS
EFER=0000000000000000
FCW=037f FSW=0000 [ST=0] FTW=00 MXCSR=00001f80
FPR0=0000000000000000 0000 FPR1=0000000000000000 0000
FPR2=0000000000000000 0000 FPR3=0000000000000000 0000
FPR4=0000000000000000 0000 FPR5=0000000000000000 0000
FPR6=0000000000000000 0000 FPR7=0000000000000000 0000
XMM00=00000000000000000000000000000000 XMM01=00000000000000000000000000000000
XMM02=00000000000000000000000000000000 XMM03=00000000000000000000000000000000
XMM04=00000000000000000000000000000000 XMM05=00000000000000000000000000000000
XMM06=00000000000000000000000000000000 XMM07=00000000000000000000000000000000
GNUmakefile:188: recipe for target 'qemu-nox-gdb-vnc' failed
make: *** [qemu-nox-gdb-vnc] Aborted (core dumped)
You see, the system is trying to access a memory reference that is not within physical memory boundary.
Exercise 8
- We have omitted a small fragment of code - the code necessary to print octal numbers using patterns of the form “%o”. Find and fill in this code fragment.
lib/printfmt.c
, begins at line 206
// (unsigned) octal, Line 206
case 'o':
// Replace this with your code.
putch('X', putdat);
putch('X', putdat);
putch('X', putdat);
break;
Reference to unsigned decimal("%u") above:
// unsigned decimal
case 'u':
num = getuint(&ap, lflag);
base = 10;
goto number;
one possible answer is:
// (unsigned) octal
case 'o':
// Replace this with your code.
// putch('X', putdat);
// putch('X', putdat);
// putch('X', putdat);
num = getuint(&ap, lflag);
base = 8;
goto number;
// break;
-
Be able to answer the following questions:
-
Explain the interface between
printf.c
andconsole.c
. Specifically, what function doesconsole.c
export? How is this function used byprintf.c
?kern/console.c
directly exposed functioncputchar
tokern/printf.c
, which usescons_putc
. Every output/print function inkern/printf.c
uses this. -
Explain the following from
console.c
:if (crt_pos >= CRT_SIZE) { int i; memmove(crt_buf, crt_buf + CRT_COLS, (CRT_SIZE - CRT_COLS) * sizeof(uint16_t)); for (i = CRT_SIZE - CRT_COLS; i < CRT_SIZE; i++) crt_buf[i] = 0x0700 | ' '; crt_pos -= CRT_COLS; }
CRT_SIZE
is a macro that expands to the size of the CRT(?) screen size.This code segment does the following:
a. If the screen buffer is filled:
b. from pos
crt_buf + CRT_COLS
, moves(copy?)(CRT_SIZE - CRT_COLS) * sizeof(uint16_t)
bytes tocrt_buf
. This serves as moving screen buffer on line up, from line 1 to 0(overwrite).c. fill moved but not to be used source crt_buf with 0x0700 | 0x20, which is 0x0720. (What is 0x0720?)
d. reset crt_pos to last line of screen.
-
For the following questions you might wish to consult the notes for Lecture 2. These notes cover GCC’s calling convention on the x86. Trace the execution of the following code step-by-step:
int x = 1, y = 3, z = 4; cprintf("x %d, y %x, z %d\n", x, y, z);
-
In the call to
cprintf()
, to what doesfmt
point? To what doesap
point?fmt
points to “x %d, y %x, z %d\n”, andap
points to a variable list ofx, y, z
.
-
-
Run the following code.
unsigned int i = 0x00646c72; cprintf("H%x Wo%s", 57616, &i);
- What is the output?
He110 World
-
Explain how this output is arrived at in the step-by-step manner of the previous exercise.
a.
fmt
points to “H%x Wo%s”, andap
points to a variable list of57616, &i
.b.
57616
(base 10) isE110
(base 16), so printshe110
out.c.
&i
points to a byte array of00
64
6c
72
. As x86 is little-endian, when storing these bytes, things comes to a72
6c
64
00
, which representsrld\0
. Then it printsWorld
out. -
If the x86 were instead big-endian what would you set i to in order to yield the same output? Would you need to change 57616 to a different value?
a. For
i
, it should be changed to0x726c6400
.b.
57616
should not be changed.
-
In the following code, what is going to be printed after ‘
y=
’? (note: the answer is not a specific value.) Why does this happen?cprintf("x=%d y=%d", 3);
I think it’s an undefined behavior. The output depends on the current state of what immediately after
3
in the variable list of3, _
. -
Let’s say that GCC changed its calling convention so that it pushed arguments on the stack in declaration order, so that the last argument is pushed last. How would you have to change cprintf or its interface so that it would still be possible to pass it a variable number of arguments?
If this happens, I have to find out the exact length of the variable list, then switch the beginning address to the real beginning.
The exact length can be found in
fmt
(if no one use undefined behavior…). The only thing I should do is to rewrite the macros ofva_start
andva_arg
.We should ensuring the following:
-
va_start
points at the real start(rightmost) position. -
va_arg
point to the next(left) after one element in the list is processed.
-
-
Challenge
- Enhance the console to allow text to be printed in different colors.
We, ASCII artists!
cprintf(CYAN_BG_BOLD("Now with colors!"));
cprintf("\n");
see commit on branch lab1
and inc/color_console.h
for details.
The Stack
Exercise 9
- Determine where the kernel initializes its stack, and exactly where in memory its stack is located.
kern/entry.S
, line 69:
relocated: # Line 69
# Clear the frame pointer register (EBP)
# so that once we get into debugging C code,
# stack backtraces will be terminated properly.
movl $0x0,%ebp # nuke frame pointer
# Set the stack pointer
movl $(bootstacktop),%esp
# now to C code
call i386_init
# Should never get here, but in case we do, just spin.
If you ask me where:
same file, line 86
.data # Line 86
###################################################################
# boot stack
###################################################################
.p2align PGSHIFT # force page alignment
.globl bootstack
bootstack:
.space KSTKSIZE # here is stack size
.globl bootstacktop
bootstacktop:
You can find exact size here:
inc/memlayout.h
, line 96
// Kernel stack.
#define KSTACKTOP KERNBASE // line 96
#define KSTKSIZE (8*PGSIZE) // size of a kernel stack
#define KSTKGAP (8*PGSIZE) // size of a kernel stack guard
PGSIZE
is 4096 ———— you can check it in inc/mmu.h
Exercise 10
See 11.
Exercise 11
See 12.
Exercise 12
-
Modify your stack backtrace function to display, for each
eip
, the function name, source file name, and line number corresponding to thateip
. -
Complete the implementation of
debuginfo_eip
by inserting the call tostab_binsearch
to find the line number for an address. -
Add a
backtrace
command to the kernel monitor, and extend your implementation ofmon_backtrace
to calldebuginfo_eip
and print a line for each stack frame of the form:
K> backtrace
Stack backtrace:
ebp f010ff78 eip f01008ae args 00000001 f010ff8c 00000000 f0110580 00000000
kern/monitor.c:143: monitor+106
ebp f010ffd8 eip f0100193 args 00000000 00001aac 00000660 00000000 00000000
kern/init.c:49: i386_init+59
ebp f010fff8 eip f010003d args 00000000 00000000 0000ffff 10cf9a00 0000ffff
kern/entry.S:70: <unknown>+0
K>
- Tip: printf format strings provide an easy, albeit obscure, way to print non-null-terminated strings like those in STABS tables.
printf("%.*s", length, string)
prints at mostlength
characters ofstring
.
file kern/monitor.c
:
int
mon_backtrace(int argc, char **argv, struct Trapframe *tf)
{
// Your code here.
// variables
// ebp
uint32_t ebp = 0x00000000;
// get ebp
ebp = read_ebp();
cprintf("Stack backtrace:\n");
while (ebp != 0)
{
// get pointer at address ebp
uint32_t *ebp_ptr = (uint32_t *)ebp;
// eip is immediately after ebp
uint32_t eip = ebp_ptr[1];
// args list is right after eip.
uint32_t *args_list = &ebp_ptr[2];
// print out current step of ebp, eip and function args
// format:
// begins with 2 * [whitespace]
cprintf(" ");
// print out ebp, followed by 2 * [whitespace]
cprintf("ebp %x ", ebp);
// print out eip, followed by 2 * [whitespace]
cprintf("eip %x ", eip);
// print out args, then moves to next line.
cprintf("args %08x %08x %08x %08x %08x\n", args_list[0], args_list[1], args_list[2], args_list[3], args_list[4]);
// Eipdebuginfo to be used
struct Eipdebuginfo info;
if (debuginfo_eip(eip, &info) == 0)
{
// offset is the difference between current eip and the function beginning
uint32_t current_offset = eip - info.eip_fn_addr;
// print out those information.
// format:
// begins with 9 * [whitespace]
cprintf(" ");
// "file:line_number: function_name(with length of eip_fn_namelen)+offset"
// *eip_fn_name is NOT NULL TERMINATED!*
// I think that's why we need that eip_fn_namelen.
cprintf("%s:%d: %.*s+%d\n", info.eip_file, info.eip_line, info.eip_fn_namelen, info.eip_fn_name, current_offset);
}
else
{
// this should never happen.
cprintf("debuginfo_eip failed to get debuginfo for eip %x\n", eip);
}
// get next ebp
ebp = *ebp_ptr;
}
return 0;
}
file kern/kdebug.c
:
// Your code here.
stab_binsearch(stabs, &lline, &rline, N_SLINE, addr);
if (lline <= rline)
{
info->eip_line = stabs[lline].n_desc;
}
else
{
return -1;
}