MIT6.828 Lab1 The Boot Loader

環(huán)境

ubuntu 20.04 64位系統(tǒng)

part2的內(nèi)容是從./lab/obj/kernel.img中引導(dǎo)我們的系統(tǒng)JOS,本文很大一部分內(nèi)容都是翻譯Lab1
課程的原地址:MIT-6.828

預(yù)先需要的知識(shí):

雖然mit這課提供了相關(guān)知識(shí),但是我個(gè)人覺(jué)得,對(duì)保護(hù)模式有過(guò)實(shí)操以后牵囤,看相關(guān)的保護(hù)模式代碼確實(shí)很容易懂很多削罩。

  1. 匯編,要知道基本的語(yǔ)句是干嘛的靴寂,比如說(shuō)jne,jl等等,推薦王爽《匯編原理》
  2. 了解x86保護(hù)模式的基本工作帕胆,這里推薦《x86-實(shí)模式到保護(hù)模式》
  3. 基本的C語(yǔ)言操作荆永,了解編譯連接等基礎(chǔ)知識(shí)

本文涉及的Exercise:

  • exercise 3:這個(gè)是和調(diào)試相關(guān)的,做了
  • exercise 4: 這個(gè)是和指針(pointer)相關(guān)的學(xué)習(xí)废亭,沒(méi)做
  • exercise 5:這個(gè)是和了解鏈接地址相關(guān)的,做了
  • exercise 6:查看加載內(nèi)核前后內(nèi)存的區(qū)別具钥,做了

正文

軟盤(pán)或者硬盤(pán)的存儲(chǔ)單元都是512字節(jié)的豆村,每512一個(gè)字節(jié)稱(chēng)為一個(gè)扇區(qū)。512字節(jié)是disk最小的傳輸粒度(transfer granularity),也就是說(shuō)每次往硬盤(pán)寫(xiě)入的或者讀出的數(shù)據(jù)都是512字節(jié)的整數(shù)倍的骂删,比如說(shuō)1024字節(jié)(2個(gè)扇區(qū))掌动。如果一個(gè)硬盤(pán)是可引導(dǎo)的(bootable),那么他的第一個(gè)扇區(qū)叫做引導(dǎo)扇區(qū)(MBR就是這個(gè)東西)宁玫,在這個(gè)扇區(qū)內(nèi)部存放著boot loader粗恢。當(dāng)BIOS發(fā)現(xiàn)一個(gè)軟盤(pán)(floppy)或者一個(gè)硬盤(pán)是可引導(dǎo)的時(shí)候,BIOS會(huì)將引導(dǎo)扇區(qū) (boot sector)內(nèi)的bootloeader復(fù)制到內(nèi)存的0x7c00處(cs:ip=0x0000:0x7c00)撬统,然后BIOS會(huì)執(zhí)行一個(gè)jmp 0x0000:0x7c00适滓,接著跳轉(zhuǎn)到了引導(dǎo)扇區(qū)的代碼去執(zhí)行。

PS:上面這段基本是翻譯lab1 part2的說(shuō)明恋追,實(shí)際情況應(yīng)該和這里說(shuō)的有點(diǎn)點(diǎn)不一樣凭迹。因?yàn)锽oot sector只有很小的一點(diǎn),就512字節(jié)苦囱⌒岢瘢可能是因?yàn)檫@個(gè)lab1的代碼也很少,所以直接在boot sector里面引導(dǎo)內(nèi)核撕彤∮沭科學(xué)的做法應(yīng)該是boot sector里面的代碼引導(dǎo)boot loader猛拴,boot loader去將內(nèi)核引導(dǎo)到內(nèi)存當(dāng)中

接上面正文,用CD-ROMs內(nèi)的引導(dǎo)扇區(qū)能做工作更多蚀狰,因?yàn)镃D-ROMs一個(gè)扇區(qū)有2048字節(jié)愉昆,但是再M(fèi)IT6.828這門(mén)課里面,我們就是使用的傳統(tǒng)的512字節(jié)位扇區(qū)單位的硬盤(pán)麻蹋。在我們的代碼中boot loader 由兩個(gè)文件組合而成跛溉,分別是./boot/boot.S./boot/main.c,boot loader主要完成下面兩個(gè)工作:

  1. Boot loader將處理器從實(shí)模式切換到保護(hù)模式,因?yàn)樵趯?shí)模式之下處理器只能訪(fǎng)問(wèn)0-1MB的內(nèi)存扮授,切換到保護(hù)模式才使得處理可以訪(fǎng)問(wèn)更大的內(nèi)存芳室。這里不詳細(xì)說(shuō)明保護(hù)模式和實(shí)模式的區(qū)別,保護(hù)模式的編程可參考《X86-從實(shí)模式到保護(hù)模式》這本書(shū)
  2. 接下來(lái)boot loader會(huì)通過(guò)匯編指令從硬盤(pán)當(dāng)中將kernel讀入到內(nèi)存當(dāng)中刹勃。具體的和交互的工作比較無(wú)聊堪侯,等做完所有的系列看看有時(shí)間再來(lái)補(bǔ)充吧。

接下來(lái)是本節(jié)課的重點(diǎn),就是幾個(gè)exercise的實(shí)驗(yàn)

Exercise 3

使用GDB調(diào)試下boot loader荔仁,設(shè)置breakpoint在0x7c00處伍宦,逐步調(diào)試boot loead的代碼。并且對(duì)照著反編譯的obj/boot/boot.asm來(lái)查看當(dāng)前正在運(yùn)行的到那一步了乏梁。接著觀(guān)察在./boot/main.c的當(dāng)中代碼的運(yùn)行雹拄,包括函數(shù)readsect(),追蹤readsect(),看看從硬盤(pán)讀取kernel的代碼掌呜。

并且回答下列問(wèn)題:

  • 在哪里代碼開(kāi)始運(yùn)行32位的代碼? 是什么讓處理器從16位切換到32位運(yùn)行?
  • 哪一條語(yǔ)句是boot loader最后一條執(zhí)行的坪哄?哪一條語(yǔ)句是kernel最先被加載的质蕉?
  • 哪一條語(yǔ)句是kernel中最先執(zhí)行的語(yǔ)句?
  • boot loader如何知道內(nèi)核有多少個(gè)扇區(qū)大恤婕 模暗?它是怎么知道的?

接下來(lái)一個(gè)一個(gè)實(shí)驗(yàn)解決這些問(wèn)題吧。

在哪里代碼開(kāi)始運(yùn)行32位的代碼? 是什么讓處理器從16位切換到32位運(yùn)行念祭?
首先兑宇,先使用b *0x7c00設(shè)置一個(gè)斷點(diǎn),然后按下鍵盤(pán)上的c粱坤,GDB就會(huì)之習(xí)慣到0x7c00處并且停在那里隶糕,這是GDB調(diào)試設(shè)置斷點(diǎn)的方式,前面我們說(shuō)到0x7c00這個(gè)地址就會(huì)開(kāi)始執(zhí)行boot loader的代碼站玄。結(jié)果如下:

boot loader的最開(kāi)始的代碼

GDB截圖:
GDB調(diào)試的結(jié)果

通過(guò)截圖可以看到枚驻,此時(shí)GDB執(zhí)行的語(yǔ)句正是我們boot loader的第一條語(yǔ)句--cli (我不再贅述具體匯編指令的作用)
接下來(lái)在GDB輸入si,他就會(huì)執(zhí)行接下去的命令。
接下來(lái)先對(duì)boot.S這個(gè)文件做一些介紹注釋

#include <inc/mmu.h>

# .set是AT&T的語(yǔ)法株旷,和x86的equ是一個(gè)意思,就是生命一些常量,在變異的時(shí)候會(huì)被直接替換為對(duì)應(yīng)的值
.set PROT_MODE_CSEG, 0x8         # kernel code segment selector
.set PROT_MODE_DSEG, 0x10        # kernel data segment selector
.set CR0_PE_ON,      0x1         # protected mode enable flag

.globl start
start:
  .code16                     # Assemble for 16-bit mode
  cli                         # 清除中斷標(biāo)志
  cld                         # 字符串復(fù)制的方向锉矢,遞增復(fù)制

  # Set up the important data segment registers (DS, ES, SS).
  xorw    %ax,%ax             # Segment number zero
  movw    %ax,%ds             # -> Data Segment
  movw    %ax,%es             # -> Extra Segment
  movw    %ax,%ss             # -> Stack Segment

  # Enable A20:
  #   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.
#下面是處理鍵盤(pán)的一些工作以及開(kāi)啟A20地址線(xiàn)
seta20.1:
  inb     $0x64,%al               # Wait for not busy
  testb   $0x2,%al
  jnz     seta20.1

  movb    $0xd1,%al               # 0xd1 -> port 0x64
  outb    %al,$0x64

seta20.2:
  inb     $0x64,%al               # Wait for not busy
  testb   $0x2,%al
  jnz     seta20.2

  movb    $0xdf,%al               # 0xdf -> port 0x60
  outb    %al,$0x60

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

  # 加載GDT到gdtr寄存器沽损,并且將cr0寄存器的最低位設(shè)置為1
# 這樣就開(kāi)啟了保護(hù)模式
  lgdt    gdtdesc
  movl    %cr0, %eax
  orl     $CR0_PE_ON, %eax
  movl    %eax, %cr0
  
 #跳轉(zhuǎn)到保護(hù)模式下的代碼去執(zhí)行
  ljmp    $PROT_MODE_CSEG, $protcseg

上面的那幾行是和鍵盤(pán)處理相關(guān)的,具體的意思看這里鍵盤(pán)處理缠俺。上面這幾行代碼就完成了保護(hù)模式的開(kāi)啟。關(guān)鍵是以下幾條代碼:

  lgdt    gdtdesc
  movl    %cr0, %eax
  orl     $CR0_PE_ON, %eax
  movl    %eax, %cr0

回到我們最初的問(wèn)題磷雇,是在哪里完成了實(shí)模式到保護(hù)模式的切換呢躏救? 答案就是下面一行代碼完成了實(shí)模式到保護(hù)模式的跳轉(zhuǎn)

 ljmp    $PROT_MODE_CSEG, $protcseg

boot.S當(dāng)中盒使,其他的代碼崩掘,稍微解釋下

#下面幾個(gè)SET都調(diào)用了宏函數(shù),具體的實(shí)現(xiàn)在./lab/inc/mmu.h當(dāng)中
gdt:
  SEG_NULL              # null seg
  SEG(STA_X|STA_R, 0x0, 0xffffffff) # code seg
  SEG(STA_W, 0x0, 0xffffffff)           # data seg

上面是初始化了幾個(gè)需要的描述符少办,在這里代碼段描述符和數(shù)據(jù)段描述符都是平坦(flat)的苞慢,也就是說(shuō)他們的地址范圍都是從0~4GB。因此eip寄存器當(dāng)中的地址實(shí)際上就是真正的物理地址英妓。還需要注意一點(diǎn)就是,cs:ip組成的地址在保護(hù)模式下叫做線(xiàn)性地址(linear address)挽放,在引入頁(yè)式內(nèi)存管理的時(shí)候,那個(gè)地址叫做虛擬地址virtual address蔓纠。在那種情況下辑畦,真正的物理地址需要經(jīng)過(guò)如下的轉(zhuǎn)換: virtual address > linear address > physical address。
好腿倚,回到正題纯出,我們現(xiàn)在已經(jīng)知道了,通過(guò)對(duì)cr0寄存器的設(shè)置敷燎,在使用jmp跳轉(zhuǎn)暂筝,就進(jìn)入了保護(hù)模式,現(xiàn)在我們來(lái)觀(guān)察下實(shí)驗(yàn)結(jié)果:

實(shí)模式到保護(hù)模式的跳轉(zhuǎn)

在ljmp這條指令前面的地址是[0:7c2d]硬贯,我們可以到此時(shí)的尋址方式還是最簡(jiǎn)單的8086的cs:ip模式乖杠,在Jmp之后,下一條指令mov澄成,前面的地址已經(jīng)是0x7c32,成功了胧洒,我們已經(jīng)完成了從實(shí)模式到保護(hù)模式的跳轉(zhuǎn)畏吓,第一個(gè)問(wèn)題解決完畢。

boot loader最后一條執(zhí)行的命令是什么卫漫? kernel第一條執(zhí)行的命令是什么菲饼?
按照boot.S的代碼,我們來(lái)看下最后兩條非常關(guān)鍵的代碼

  movl    $start, %esp
  call bootmain

call bootmain列赎,這個(gè)就會(huì)去執(zhí)行main.C當(dāng)中的代碼。好了接下來(lái)我們還一個(gè)參照物饼煞,對(duì)照文件./lab/obj/boot/boot.asm來(lái)查看我們代碼的運(yùn)行诗越。
首先運(yùn)行的是main.c中的bootmain()這個(gè)函數(shù)嚷狞,在bootmain()函數(shù)中去調(diào)用了readseg函數(shù)

readseg((uint32_t) ELFHDR, SECTSIZE*8, 0)

它有三個(gè)參數(shù)床未,所以將三個(gè)參數(shù)都?jí)喝氲綏.?dāng)中薇搁,ELFHDR=0x10000,SECTSIZE*8=4096,也就是對(duì)應(yīng)的
十六進(jìn)制0x1000啃洋,第三個(gè)參數(shù)是0裂允。根據(jù)C calling convention,右邊的參數(shù)先壓棧绝编,所以在匯編代碼中
我們可以看到第一個(gè)壓棧的是0十饥,第二個(gè)0x01000,第三個(gè)0x10000逗堵。

    readseg((uint32_t) ELFHDR, SECTSIZE*8, 0);
    7d2e:   52                      push   %edx
    7d2f:   6a 00                   push   $0x0
    7d31:   68 00 10 00 00          push   $0x1000
    7d36:   68 00 00 01 00          push   $0x10000
    7d3b:   e8 a2 ff ff ff          call   7ce2 <readseg>

本來(lái)想打算調(diào)試這一串的所有匯編語(yǔ)句的汁咏,后來(lái)一想工作量太大了攘滩,要逐個(gè)去看寄存器的內(nèi)容來(lái)判斷參數(shù)的變化漂问。我打算對(duì)main.c當(dāng)中的語(yǔ)句做一個(gè)詳細(xì)的注解蚤假。

//扇區(qū)的大小
#define SECTSIZE    512

//ELF header在內(nèi)存當(dāng)中臨時(shí)存放的地址
#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;
        //將系統(tǒng)kernel文件中偏移量的4096字節(jié)數(shù)據(jù)讀到0x10000處磷仰,
        //這是因?yàn)閞eadelf -l kernel顯示第一個(gè)需要被加載的段的地址在偏移兩0x1000處芒划,說(shuō)明前面全部都是
        //ELF header的內(nèi)容民逼,所以我們先將ELF header的內(nèi)容讀到內(nèi)存當(dāng)中
    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)
        // e_phoff得到的是0x34,ELFHDR + ELFHDR->e_phoff = 0x10034拼苍,這個(gè)地址就是第一個(gè)program header的地址
    ph = (struct Proghdr *) ((uint8_t *) ELFHDR + ELFHDR->e_phoff); 

    //eph是program table entry的個(gè)數(shù), ELFHDR->e_phnum
        //這里決定了要循環(huán)多少次
    eph = ph + ELFHDR->e_phnum;
    for (; ph < eph; ph++)
        // p_pa is the load address of this segment (as well
        // as the physical address)
                //根據(jù)readelf -l kernel的輸出結(jié)果,第一個(gè)段的offset = 0x1000,p_pa = 0x00100000,size = 0x07dac
                // 所以我們要做的就是俊犯,系統(tǒng)鏡像偏移0x1000處讀取0x07dac個(gè)字節(jié)的數(shù)據(jù)到0x00100000
                // 在readseg函數(shù)中燕侠,我們將offset轉(zhuǎn)為真正的扇區(qū)號(hào)绢彤,因?yàn)殓R像是存放在第1扇區(qū)開(kāi)始的茫舶,所以
                //這個(gè)是可以計(jì)算的
        readseg(ph->p_pa, ph->p_memsz, ph->p_offset);

    // call the entry point from the ELF header
    // note: does not return!
        //當(dāng)所有的段都加載到內(nèi)存的當(dāng)中的時(shí)候饶氏,下面這條語(yǔ)句完成了嚷往,跳轉(zhuǎn)到內(nèi)核當(dāng)中去執(zhí)行
    ((void (*)(void)) (ELFHDR->e_entry))();

bad:
    outw(0x8A00, 0x8A00);
    outw(0x8A00, 0x8E00);
    while (1)
        /* do nothing */;
}

readelf -l kernel 結(jié)果皮仁,結(jié)果顯示3 program headers贷祈,第一個(gè)program header的offset是在0x1000势誊,說(shuō)明0x1000前面都是elf header的內(nèi)容:

Elf file type is EXEC (Executable file)
Entry point 0x10000c
There are 3 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x001000 0xf0100000 0x00100000 0x07dac 0x07dac R E 0x1000
  LOAD           0x009000 0xf0108000 0x00108000 0x0b6a8 0x0b6a8 RW  0x1000
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x10

 Section to Segment mapping:
  Segment Sections...
   00     .text .rodata .stab .stabstr 
   01     .data .got .got.plt .data.rel.local .data.rel.ro.local .bss 
   02    

在解釋一下另外兩個(gè)函數(shù)

void readseg(uint32_t pa, uint32_t count, uint32_t offset)
{
    uint32_t end_pa;

    end_pa = pa + count;

    // round down to sector boundary
    //這里是因?yàn)閜a不一定都是512字節(jié)對(duì)齊的查近,我們將pa做一個(gè)512字節(jié)對(duì)齊
    //下面進(jìn)行一個(gè)舉例霜威,例如pa=700戈泼,~(512-1) = 0x1110_0000_0000
    //700 & 0x1110_0000_0000 = 0x200 = 512大猛,這樣我們就做到了對(duì)齊
    pa &= ~(SECTSIZE - 1);
        
        //使用偏移量來(lái)計(jì)算所要讀取的扇區(qū)是哪個(gè)
//淀零,比如說(shuō)上面的offset = 0x1000,0x1000/512+1 = 9唉堪,就是讀取9號(hào)扇區(qū)。
//扇區(qū)號(hào)+1這個(gè)應(yīng)該是這樣理解的撬槽,
//硬盤(pán)扇區(qū)默認(rèn)就是從1扇區(qū)開(kāi)始計(jì)算的侄柔。
//如果我們要讀取511字節(jié)的內(nèi)容,511/512=0,肯定是不對(duì)的究珊。所以要+1
    offset = (offset / SECTSIZE) + 1;

    // If this is too slow, we could read lots of sectors at a time.
    // We'd write more to memory than asked, but it doesn't matter --
    // we load in increasing order.
    while (pa < end_pa) {
        // Since we haven't enabled paging yet and we're using
        // an identity segment mapping (see boot.S), we can
        // use physical addresses directly.  This won't be the
        // case once JOS enables the MMU.
        readsect((uint8_t*) pa, offset);

        //物理地址+512字節(jié)
        pa += SECTSIZE;

        //讀下一個(gè)扇區(qū)
        offset++;
    }
}

void waitdisk(void)
{
    // wait for disk reaady
    while ((inb(0x1F7) & 0xC0) != 0x40)
        /* do nothing */;
}

void readsect(void *dst, uint32_t offset)
{
    // wait for disk to be ready
    waitdisk();

    //使用LBA模式的邏輯扇區(qū)方式來(lái)尋找扇區(qū),這里和硬件相關(guān)
      //就暫且不要管好了剿涮,涉及到的硬件細(xì)節(jié)太多了取试,確實(shí)很麻煩
    outb(0x1F2, 1);     // count = 1
    outb(0x1F3, offset);
    outb(0x1F4, offset >> 8);
    outb(0x1F5, offset >> 16);
    outb(0x1F6, (offset >> 24) | 0xE0);
    outb(0x1F7, 0x20);  // cmd 0x20 - read sectors

    // wait for disk to be ready
    waitdisk();

    // read a sector
    //一次讀取的單位是四個(gè)字節(jié)瞬浓,所以循環(huán)sectorsize/4 = 128次
    insl(0x1F0, dst, SECTSIZE/4);
}

好了猿棉,終于要回歸問(wèn)題了萨赁,首先回答boot loader最后執(zhí)行的一條指令是什么? 上面已經(jīng)說(shuō)了 ((void (*)(void)) (ELFHDR->e_entry))();這條語(yǔ)句會(huì)跳轉(zhuǎn)到內(nèi)核當(dāng)中去執(zhí)行位迂,那么這條語(yǔ)句就是boot loader最后一條語(yǔ)句了掂林。我們來(lái)驗(yàn)證一下。

跳轉(zhuǎn)到內(nèi)核

如圖call 0x10018這里存放的就是entry锣杂,0x10018這個(gè)地址是這樣得到的元莫,在ELF header 當(dāng)中偏移0x18存放的是entry踱蠢,前面我們將ELF header放到了0x10000處棋电,所以自然entry的地址就是0x10018了。在0x10018中存放的是0x10000c榆浓,call命令執(zhí)行完后撕攒,我們就到了0x10000c執(zhí)行了打却。我們使用readelf -a kernel命令查看以下:
entry

用mit網(wǎng)站上哪個(gè)objdump -x也一樣猿推,我們可以看到蹬叭,我們的鏡像的entry確實(shí)是0x10000c秽五。讓我們乘勝追擊坦喘,解決下一個(gè)問(wèn)題瓣铣,kernel當(dāng)中第一句執(zhí)行的命令是什么?
用一下上面的圖棠笑,我們可以看到內(nèi)核當(dāng)中第一句執(zhí)行的命令是movw 0x1234 0x472,如下圖所示:

第一條執(zhí)行的命令

第三個(gè)問(wèn)題有點(diǎn)多余,第二個(gè)搞定了第三個(gè)就懂了循捺,下面我們回答第三個(gè)問(wèn)題
boot loader是如何知道內(nèi)核占據(jù)多少個(gè)扇區(qū)的从橘?
其實(shí)答案就在上面的代碼里面,在函數(shù)readseg()當(dāng)中牺勾,我們使用條件pa < end_pa來(lái)判斷是否還要繼續(xù)讀取數(shù)據(jù)驻民,如果條件成立就讀下一個(gè)扇區(qū)的內(nèi)容回还。并且我們使用offset來(lái)控制需要讀的扇區(qū)號(hào)柠硕。

Exercise 5

回答下列問(wèn)題:

  • 修改boot/Makefrag中的link address蝗柔,繼續(xù)跟蹤boot loader的一些代碼癣丧,看看boot loader會(huì)做一些什么錯(cuò)誤的事情呢胁编?

一個(gè)程序由普通的文本文件.c到可執(zhí)行文件嬉橙,中間需要經(jīng)過(guò)一個(gè)很重要的過(guò)程就是鏈接(link),說(shuō)實(shí)話(huà)我對(duì)于鏈接需要做的工作為并不了解很多拾给,我就指出一點(diǎn)比較重要的在匯編程序當(dāng)中蒋得,標(biāo)號(hào)的地址會(huì)因?yàn)檫B接地址的不同而發(fā)生變化额衙,暫時(shí)不理解沒(méi)關(guān)系县踢,首先需要得到這樣一個(gè)概念鏈接地址關(guān)乎著標(biāo)號(hào)的地址硼啤,對(duì)于普通的匯編代碼并沒(méi)有直接的影響
開(kāi)始實(shí)驗(yàn):
首先先看下原來(lái)的boot/Makefrag文件中的內(nèi)容(截取了關(guān)鍵的部分):

$(OBJDIR)/boot/boot: $(BOOT_OBJS)
    @echo + ld boot/boot
    $(V)$(LD) $(LDFLAGS) -N -e start -Ttext 0x7c00 -o $@.out $^
    $(V)$(OBJDUMP) -S $@.out >$@.asm
    $(V)$(OBJCOPY) -S -O binary -j .text $@.out $@
    $(V)perl boot/sign.pl $(OBJDIR)/boot/boot

上面的谴返,-Ttext說(shuō)明了鏈接的地址,即0x7c00,先用gdb調(diào)試下看下未修改的情況:

未修改前

lgdt命令應(yīng)該是將gdtdesc這個(gè)地址加載道gdtr寄存器,由于我這里有點(diǎn)問(wèn)題逼肯,我運(yùn)行的看不到gdtdesc標(biāo)號(hào)的地址,如下:
我的運(yùn)行情況

但是這里的意思是如此的,我用其他相類(lèi)似的地方來(lái)描述這個(gè)問(wèn)題三椿,我選了上面鍵盤(pán)處理的jz指令來(lái)說(shuō)明:
鍵盤(pán)處理的代碼

可以看到jz經(jīng)過(guò)編譯后葫辐,就變成了jne指令了搜锰,跳轉(zhuǎn)的目的地址就是0x7c14。好我們現(xiàn)在將上面boot/Makefrag當(dāng)中的0x7c00修改為0x7e00在來(lái)看看這里的變化耿战,注意別忘了make clean,然后重新make,如下圖所是蛋叼,不知道是不是因?yàn)橄到y(tǒng)環(huán)境的關(guān)系,gdb調(diào)試中Jne跳轉(zhuǎn)的標(biāo)號(hào)沒(méi)有發(fā)生變化剂陡,但是jmp到保護(hù)模式確實(shí)發(fā)生了異常狈涮,我沒(méi)有研究原因。但是在./boot/boot.asm當(dāng)中標(biāo)號(hào)的地址已經(jīng)發(fā)生了變化,下面可以看到已經(jīng)由0x7c14變成了0x7e14:
修改為0x7e00的結(jié)果

另外鸭栖,因?yàn)槲覀儸F(xiàn)在link address的不同松却,所以lgdt加載的標(biāo)號(hào)是錯(cuò)誤的地址,所以應(yīng)該無(wú)法正確的進(jìn)入保護(hù)模式,實(shí)驗(yàn)結(jié)果觀(guān)察到也確實(shí)是如此,結(jié)果如下圖:
跳入保護(hù)模式

可以看到匣吊,現(xiàn)在根本無(wú)法正常進(jìn)入保護(hù)模式见转,回到Bios的代碼去了乘客。所以我們的實(shí)驗(yàn)成功了!

Exercise 6

使用GDB調(diào)試命令,x/Nx,N是要查看的字(word)的個(gè)數(shù),warning:字(word)的長(zhǎng)度沒(méi)有一個(gè)統(tǒng)一的標(biāo)準(zhǔn),在GNU匯編當(dāng)中(意思就是是說(shuō)在A(yíng)T&T的語(yǔ)法下掸哑,其實(shí)x86也是一樣的)摔癣,一個(gè)字(word)的長(zhǎng)度應(yīng)該是兩個(gè)字節(jié)琢岩。問(wèn)題是:查看以下在進(jìn)入boot loader的時(shí)候,0x100000地址出的內(nèi)容吃警,以及進(jìn)入內(nèi)核后0x100000處的內(nèi)容。

實(shí)際上這個(gè)問(wèn)題并不需要我們?nèi)フJ(rèn)真的debug完疫,只要認(rèn)真思考下余舶,在剛進(jìn)入boot loader的時(shí)候,x100000這個(gè)地方應(yīng)該是空空如也的挟憔,因?yàn)閮?nèi)核還沒(méi)有被加載达传。根據(jù)readelf -l kernel的結(jié)果,我們知道內(nèi)核被加載到的地方是0x100000。所以當(dāng)剛進(jìn)入到內(nèi)核后,0x100000這個(gè)地方就是內(nèi)核的代碼了。

結(jié)束

好了,到這里為止,lab 1 part2所有的exercise都已經(jīng)做完了疯潭,通篇下來(lái)遵绰,想必對(duì)加載elf文件的內(nèi)核有了很大的了解逛犹。接下來(lái)要做的就是lab 1 part3 了2钡骸终惑!我在這個(gè)實(shí)驗(yàn)當(dāng)中也有一些不了解的地方,比如說(shuō)上面的lgdt指令和預(yù)期的結(jié)果不同涡扼。希望知道如何解決的老鐵門(mén)相互交流降铸,另外如果發(fā)現(xiàn)有錯(cuò)誤的地方毡泻,希望各位提醒我修改~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末昧碉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子墨闲,更是在濱河造成了極大的恐慌盾鳞,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異鼻听,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)珊肃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)叶堆,“玉大人,你說(shuō)我怎么就攤上這事斥杜∈牛” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵蔗喂,是天一觀(guān)的道長(zhǎng)忘渔。 經(jīng)常有香客問(wèn)我,道長(zhǎng)缰儿,這世上最難降的妖魔是什么畦粮? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮乖阵,結(jié)果婚禮上宣赔,老公的妹妹穿的比我還像新娘。我一直安慰自己瞪浸,他們只是感情好儒将,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著对蒲,像睡著了一般钩蚊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蹈矮,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天砰逻,我揣著相機(jī)與錄音,去河邊找鬼含滴。 笑死诱渤,一個(gè)胖子當(dāng)著我的面吹牛丐巫,可吹牛的內(nèi)容都是我干的谈况。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼递胧,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼碑韵!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起缎脾,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤祝闻,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體联喘,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡华蜒,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了豁遭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情着倾,我是刑警寧澤燕少,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站崇决,受9級(jí)特大地震影響恒傻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望诡渴。 院中可真熱鬧,春花似錦山上、人聲如沸潭辈。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至荔泳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間创肥,已是汗流浹背圈膏。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工糯俗, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留尿褪,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓得湘,卻偏偏與公主長(zhǎng)得像杖玲,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子淘正,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容