環(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í)很容易懂很多削罩。
- 匯編,要知道基本的語(yǔ)句是干嘛的靴寂,比如說(shuō)jne,jl等等,推薦王爽《匯編原理》
- 了解x86保護(hù)模式的基本工作帕胆,這里推薦《x86-實(shí)模式到保護(hù)模式》
- 基本的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è)工作:
- 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ū)
- 接下來(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é)果如下:
GDB截圖:
通過(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é)果:
在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)證一下。
如圖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
命令查看以下:用mit網(wǎng)站上哪個(gè)objdump -x也一樣猿推,我們可以看到蹬叭,我們的鏡像的entry確實(shí)是0x10000c秽五。讓我們乘勝追擊坦喘,解決下一個(gè)問(wèn)題瓣铣,kernel當(dāng)中第一句執(zhí)行的命令是什么?
用一下上面的圖棠笑,我們可以看到內(nèi)核當(dāng)中第一句執(zhí)行的命令是movw 0x1234 0x472,如下圖所示:
第三個(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)的地址,如下:
但是這里的意思是如此的,我用其他相類(lèi)似的地方來(lái)描述這個(gè)問(wèn)題三椿,我選了上面鍵盤(pán)處理的jz指令來(lái)說(shuō)明:
可以看到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:
另外鸭栖,因?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é)果如下圖:
可以看到匣吊,現(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ò)誤的地方毡泻,希望各位提醒我修改~