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 $@.out $^ # Line 28
    $(V)$(OBJDUMP) -S $@.out >$@.asm
    $(V)$(OBJCOPY) -S -O binary -j .text $@.out $@
    $(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 $@.out $^ # Line 28
    $(V)$(OBJDUMP) -S $@.out >$@.asm
    $(V)$(OBJCOPY) -S -O binary -j .text $@.out $@
    $(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:

    1. Explain the interface between printf.c and console.c. Specifically, what function does console.c export? How is this function used by printf.c?

      kern/console.c directly exposed function cputchar to kern/printf.c, which uses cons_putc. Every output/print function in kern/printf.c uses this.

    2. 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

      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 to crt_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.

    3. 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 does fmt point? To what does ap point?

        fmt points to “x %d, y %x, z %d\n”, and ap points to a variable list of x, y, z.

    4. 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”, and ap points to a variable list of 57616, &i.

        b. 57616(base 10) is E110(base 16), so prints he110 out.

        c. &i points to a byte array of 00 64 6c 72. As x86 is little-endian, when storing these bytes, things comes to a 72 6c 64 00, which represents rld\0. Then it prints World 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 to 0x726c6400.

        b. 57616 should not be changed.

    5. 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 of 3, _.

    6. 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 of va_start and va_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.

Lab 1 Challenge

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 that eip.

  • Complete the implementation of debuginfo_eip by inserting the call to stab_binsearch to find the line number for an address.

  • Add a backtrace command to the kernel monitor, and extend your implementation of mon_backtrace to call debuginfo_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 most length characters of string.

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;
    }
Nemo Xiong avatar
Nemo Xiong
我永远喜欢妃爱
comments powered by Disqus