Lab 1 Part 2: The Boot Loader

Exercise 3, 在地址 0x7c00 處設(shè)下端點(diǎn)繼續(xù)執(zhí)行

  • At what point does the processor start executing 32-bit code? What exactly causes the switch from 16- to 32-bit mode?

    ljmp    $PROT_MODE_CSEG, $protcseg
    
  • What is the last instruction of the boot loader executed, and what is the first instruction of the kernel it just loaded?

    7d71: ff 15 18 00 01 00 call *0x10018

  • Where is the first instruction of the kernel?

    movw  $0x1234,0x472
    
  • How does the boot loader decide how many sectors it must read in order to fetch the entire kernel from disk? Where does it find this information?

    ph->p_memsz
    

    Exercise 3

    #include <stdio.h>
    #include <stdlib.h>
    
    void
    f(void)
    {
        int a[4];
        int *b = malloc(16);
        int *c;
        int i;
    
        printf("1: a = %p, b = %p, c = %p\n", a, b, c);
    
        c = a;
        for (i = 0; i < 4; i++)
          a[i] = 100 + i;
        c[0] = 200;
        printf("2: a[0] = %d, a[1] = %d, a[2] = %d, a[3] = %d\n",
         a[0], a[1], a[2], a[3]);
    
        c[1] = 300;
        *(c + 2) = 301;
        3[c] = 302;
        printf("3: a[0] = %d, a[1] = %d, a[2] = %d, a[3] = %d\n",
       a[0], a[1], a[2], a[3]);
    
        c = c + 1;
        *c = 400;
        printf("4: a[0] = %d, a[1] = %d, a[2] = %d, a[3] = %d\n",
         a[0], a[1], a[2], a[3]);
    
        // 輸出200 400 301 302
        // 現(xiàn)在a數(shù)組的字節(jié)分布為(小端)C8000000 90010000 2D010000 2E010000(00C8 0190 012D 012E)
        // c指向a[1]
        c = (int *) ((char *) c + 1);
        // 將c先轉(zhuǎn)換為char指針指向下一個(gè)字節(jié)后再轉(zhuǎn)回int指針
        *c = 500;
        //  C8000000 90*010000 2D*010000  修改 * 號(hào)里面的四個(gè)字節(jié)
        // 500 -> F4010000
        // a-> C8000000 90F40100 00010000 2E010000
        // 輸出200 128144 256 302
        printf("5: a[0] = %d, a[1] = %d, a[2] = %d, a[3] = %d\n",
         a[0], a[1], a[2], a[3]);
    
        b = (int *) a + 1;
        c = (int *) ((char *) a + 1);
        printf("6: a = %p, b = %p, c = %p\n", a, b, c);
        //a = 0x7ffeebb28200, b = 0x7ffeebb28204, c = 0x7ffeebb28201
        //可以看到int指針+1 from c to b 是 4 個(gè)自己 而 char 指針只是一個(gè)字節(jié)
     }
    
    int
    main(int ac, char **av)
    {
        f();
        return 0;
    }
    
    
    

Exercise 6

i386-elf-objdump -f obj/kern/kernel 


obj/kern/kernel:     file format elf32-i386
architecture: i386, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x0010000c

看到內(nèi)核的第一條程序的入口是 0x1000c

這里有個(gè)問(wèn)題不是很明白, boot.asm 中券盅,call 的最后一條程序的地址為此為啥會(huì)跳到knernal呢?

7d71:   ff 15 18 00 01 00       call   *0x10018

Part 3: The Kernel

i386-elf-objdump -x obj/kern/kernel | grep -2n LOAD

可以看見(jiàn) text 的 link address("VMA")-> f0100000 與 load address ("LMA") 00100000 有所不同钦奋。

15-Idx Name          Size      VMA       LMA       File off  Algn
16-  0 .text         0000171e  f0100000  00100000  00001000  2**2
17:                  CONTENTS, ALLOC, LOAD, READONLY, CODE

這時(shí)因?yàn)椴僮飨到y(tǒng)常常會(huì)被鏈接到很高的虛擬地址(eg. f0100000 ) 為了原理用戶(hù)程序會(huì)使用到的地址空間。然而有許多的機(jī)器并沒(méi)有0xf0100000 (3.7GB) 這么大的內(nèi)存携龟,所以我們需要處理器的內(nèi)存管理器講這個(gè)地址map 到 0x00100000 (kernel 被 load 的地址柴钻。

Exercise 7.

b *0x0010000c #在內(nèi)核的第一條指令處放下一個(gè)斷點(diǎn)
si #進(jìn)行單步調(diào)試

(gdb) b *0x10000c
Breakpoint 1, 0x0010000c in ?? ()
(gdb) si
=> 0x100015:    mov    $0x110000,%eax
0x00100015 in ?? ()
(gdb) si
=> 0x10001a:    mov    %eax,%cr3
0x0010001a in ?? ()
(gdb) si
=> 0x10001d:    mov    %cr0,%eax
(gdb) si
=> 0x100020:    or     $0x80010001,%eax
0x00100020 in ?? ()
(gdb) x/8x 0xf0100000
0xf0100000 <_start+4026531828>: 0x00000000  0x00000000  0x00000000  0x00000000
0xf0100010 <entry+4>:   0x00000000  0x00000000  0x00000000  0x00000000
(gdb) si
=> 0x100025:    mov    %eax,%cr0
0x00100025 in ?? ()
(gdb) x/8x 0xf0100000
0xf0100000 <_start+4026531828>: 0x00000000  0x00000000  0x00000000  0x00000000
0xf0100010 <entry+4>:   0x00000000  0x00000000  0x00000000  0x00000000
(gdb) si
=> 0x100028:    mov    $0xf010002f,%eax
0x00100028 in ?? ()
(gdb) x/8x 0xf0100000
0xf0100000 <_start+4026531828>: 0x1badb002  0x00000000  0xe4524ffe  0x7205c766
0xf0100010 <entry+4>:   0x34000004  0x0000b812  0x220f0011  0xc0200fd8

可以看見(jiàn)在當(dāng)內(nèi)核執(zhí)行了 mov $0xf010002f,%eax 這個(gè)指令之后锄列,0xf0100000地址開(kāi)始有值饺饭。==這里不是很明白這個(gè)地址的意思==

Comment out the movl %eax, %cr0 in kern/entry.S, trace into it, and see if you were right. 導(dǎo)致沒(méi)有分頁(yè)會(huì)讓整個(gè)程序卡死。

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.

num = getuint(&ap, lflag);
base = 8;
goto number;
  1. Specifically, what function does console.c export? How is this function used by printf.c?
static void
putch(int ch, int *cnt)
{
    cputchar(ch);
    *cnt++;
}

用來(lái)在 console 中輸出字符起宽。

  1. 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_pos 超過(guò)了crt_buf的最大值洲胖,將 crt_buf 的 1-n-1 移動(dòng)到 0-n-2

再將下標(biāo)為 n-1 的那行全部置為空格,屬性設(shè)置為0x0700

參數(shù)int c 是什么坯沪,0xff0x0700又是什么绿映?
int c一共32bit,其中高16位用來(lái)表示屬性腐晾,低16位用來(lái)表示字符叉弦。因此,與0xff作 and 運(yùn)算就是去掉屬性藻糖,只看字符內(nèi)容淹冰。與~0xff作 and 運(yùn)算就是去掉字符,只看屬性巨柒。與0x0700作 or 運(yùn)算就是設(shè)為默認(rèn)屬性樱拴。

==這部分是什么意思我還沒(méi)有弄懂==。后面再看看是什么意思潘拱。

  1. fmt 指向的是 "x %d, y %x, z %d\n"

    (gdb) x/s fmt
    0xf0101a6e:  "x %d, y %x, z %d\n
    
vcprintf()
vcprintf (fmt=0xf0101a6e "x %d, y %x, z %d\n", ap=0xf010ff04 "\001")
putch (ch=120, cnt=0xf010fecc)
cons_putc('x')
cons_putc(' ')
va_arg()
cons_putc('1')
cons_putc(',')
cons_putc(' ')
cons_putc('y')
cons_putc(' ')
va_arg()
cons_putc('3')
cons_putc(',')
cons_putc(' ')
cons_putc('z')
cons_putc(' ')
va_arg()
cons_putc('4')
cons_putc('\n')

可以用下面這個(gè)命令將相關(guān)的函數(shù)從文件中找到并且生成斷點(diǎn)的命令:進(jìn)行debug疹鳄。

grep -HnE "cons_putc|va_arg|vcprintf" lib/printfmt.c kern/printf.c | cut -f1,2 -d ":" | xargs -I {} echo break {}
  1. 57616 = 0xe110。此外芦岂,根據(jù)x86的小端序瘪弓,&i指向了byte序列0x72、0x6c禽最、0x64腺怯、0x00。這等同于字符串”rld”川无。所以呛占,最終的輸出為”He110 World”。

The Stack

x86 的棧指針(esp)指向被使用棧的最低位置(因?yàn)闂5自诘刂返母呶弧?

關(guān)于棧的相關(guān)信息可以參考:

設(shè)置斷點(diǎn)

b kern/init.c:12
b kern/init.c:16
b kern/init.c:20
#調(diào)用test_backtrace時(shí)
lea    -0x1(%ebx), %eax #eax = ebx - 1 -> x-1
push   %eax #被調(diào)函數(shù)param壓棧 從右往左
call   0xf0100040 #call 標(biāo)號(hào)(將當(dāng)前的IP壓棧后懦趋,轉(zhuǎn)到標(biāo)號(hào)處執(zhí)行指令)
#等效于:
#pushl %eip 會(huì)在函數(shù)返回是通過(guò) ret 將其出棧
#movl f, %eip
#執(zhí)行 call 指令后 esp - 1
#esp            0xf010ff80          0xf010ff80
#esp            0xf010ff7c          0xf010ff7c

#eip 指向當(dāng)前函數(shù)調(diào)用
#eip            0xf0100074          0xf0100074 <test_backtrace+52>
#eip            0xf01008e3          0xf01008e3 <cprintf>

#進(jìn)入test_backtrace時(shí)
push   %ebp #主調(diào)函數(shù)幀基指針EBP
mov    %esp,%ebp # 將父函數(shù)的棧頂作為被調(diào)函數(shù)的棧底
push   %ebx 

sub    $0xc,%esp #將棧頂指針向下移動(dòng) 12 字節(jié)(3wds) 在棧上開(kāi)辟一個(gè)空間存儲(chǔ)局部變量,注意這里用的是 sub(改變ESP值來(lái)為函數(shù)局部變量預(yù)留空間)
mov    0x8(%ebp),%ebx #將ebx  設(shè)置為 ebp + 8

#test_backtrace return時(shí)
mov    -0x4(%ebp),%ebx
leave
#等效于
#movl %ebp, %esp
#popl %ebp
ret
#等效于
#popl %eip


根據(jù)慣例晾虑,寄存器%eax%edx%ecx為主調(diào)函數(shù)保存寄存器(caller-saved registers)仅叫,當(dāng)函數(shù)調(diào)用時(shí)帜篇,若主調(diào)函數(shù)希望保持這些寄存器的值,則必須在調(diào)用前顯式地將其保存在棧中诫咱;被調(diào)函數(shù)可以覆蓋這些寄存器笙隙,而不會(huì)破壞主調(diào)函數(shù)所需的數(shù)據(jù)。

寄存器%ebx坎缭、%esi%edi為被調(diào)函數(shù)保存寄存器(callee-saved registers)竟痰,即被調(diào)函數(shù)在覆蓋這些寄存器的值時(shí)签钩,必須先將寄存器原值壓入棧中保存起來(lái),并在函數(shù)返回前從棧中恢復(fù)其原值坏快,因?yàn)橹髡{(diào)函數(shù)可能也在使用這些寄存器铅檩。

(gdb) bt
#0  test_backtrace (x=0) at kern/init.c:18
#1  0xf0100068 in test_backtrace (x=1) at kern/init.c:16
#2  0xf0100068 in test_backtrace (x=2) at kern/init.c:16
#3  0xf0100068 in test_backtrace (x=3) at kern/init.c:16
#4  0xf0100068 in test_backtrace (x=4) at kern/init.c:16
#5  0xf0100068 in test_backtrace (x=5) at kern/init.c:16
#6  0xf01000d4 in i386_init () at kern/init.c:39
#7  0xf010003e in relocated () at kern/entry.S:80

bt 命令可以看見(jiàn)堆棧的情況

通過(guò) x/20x $esp 命令可以看到從棧頂往下走的情況其中 callee (被調(diào)函數(shù))的返回指令為 0xf0100068 是在 caller 的ebp 下面。所以通過(guò) *(ebp+1) 就可以將起讀出來(lái)莽鸿。

0x00000001 <- caller ebx
0xf010ff58 <- caller ebp
0xf0100068 <- add    $0x10,%esp  
0x00000000 <- callee param
0x00000001
0xf010ff78 
0x00000000
0xf0100882 
0x00000002 <- caller ebx
0xf010ff78 <- caller ebp
0xf0100068 <- add    $0x10,%esp(epi)
0x00000001 <- callee param

增加的代碼如下

uint32_t ebp,*args;
cprintf("Stack backtrace:\n");
ebp = read_ebp();
while (ebp != 0)
{
    args = (uint32_t *)ebp;//將 ebp 的值轉(zhuǎn)換為指針地址
    ebp = args[0]; //將 caller 的 ebp 傳給這個(gè)參數(shù)
    cprintf("\tebp %x  eip %x  args %08x %08x %08x %08x %08x\n",ebp, args[1], args[2], args[3], args[4],args[5], args[6]);
}

Exercise 12:

這里需要知道 stab 是什么東西柠并。可以參考這個(gè)文章: STAB 格式富拗。

// Hint:
//  There's a particular stabs type used for line numbers.
//  Look at the STABS documentation and <inc/stab.h> to find
//  which one.
// Your code here.
stab_binsearch(stabs, &lline, &rline, N_SLINE, addr);
if (lline <= rline)
{
    info->eip_line = stabs[lline].n_desc;
}
else
{
    return -1;
}
static struct Command commands[] = {
        {"help", "Display this list of commands", mon_help},
        {"kerninfo", "Display information about the kernel", mon_kerninfo},
        {"backtrace", "Display information about the kernel", mon_backtrace}};
running JOS: (1.5s)
  printf: OK
  backtrace count: OK
  backtrace arguments: OK
  backtrace symbols: OK
  backtrace lines: OK
Score: 50/50

總結(jié):

至此,我已經(jīng)看了這個(gè)lab1快有一周的時(shí)間了鸣戴,由于關(guān)于C語(yǔ)言, 匯編的許多東西我都沒(méi)有特別明白所以看起來(lái)非常的吃力啃沪。

技術(shù)總結(jié):

  1. 電腦上電之后開(kāi)始載入 BIOS, BIOS 開(kāi)始搜索外設(shè)尋找 bootable 的外設(shè)窄锅,如果 bootable创千, BIOS 將會(huì)把 OS 的 bootloabder 載入到 0x7c00~ 0x7dff 并且將 CS:IP 7c00.
  2. OS 的bootloabder 開(kāi)始加載核心。首先是是通過(guò)一系列的匯編語(yǔ)言開(kāi)啟CPU的保護(hù)模式入偷,然后調(diào)用 bootmain() 函數(shù)追驴。bootmain() 函數(shù)通過(guò)讀取磁盤(pán)將 ELF 格式的 kernel 載入到內(nèi)存中,并且將 CS:IP 跳轉(zhuǎn)到 ELF 的 enty 地址開(kāi)始運(yùn)行核心代碼疏之。
  3. Kernel 將啟動(dòng)分頁(yè)模式將低地址的代碼(0x00100000) 映射到(0xf0100000) 高位地址殿雪。
  4. 之后實(shí)驗(yàn)內(nèi)容讓我們閱讀了 cprintf (類(lèi)似 printf)的代碼,并且完善了其實(shí)現(xiàn)锋爪。主要的思想是根據(jù) % 后面的symbol來(lái)確獲取定不定變量的 va_arg函數(shù)的傳入類(lèi)型丙曙,從而通過(guò)不同的指針獲取不同的數(shù)據(jù)類(lèi)型。因?yàn)橹羔橆?lèi)型的不同其骄,同樣的數(shù)據(jù)會(huì)被翻譯成不同的結(jié)果亏镰。
  5. 接下來(lái)是函數(shù)調(diào)用的過(guò)程(怎么壓棧,以及棧的相關(guān)知識(shí))拯爽。
  6. 最后是學(xué)習(xí) backtrace 相關(guān)的東西索抓,對(duì)程序保存時(shí)輸出的信息有了一點(diǎn)了解。

參考文章

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末毯炮,一起剝皮案震驚了整個(gè)濱河市逼肯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌否副,老刑警劉巖汉矿,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異备禀,居然都是意外死亡洲拇,警方通過(guò)查閱死者的電腦和手機(jī)奈揍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)赋续,“玉大人男翰,你說(shuō)我怎么就攤上這事∨β遥” “怎么了蛾绎?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)鸦列。 經(jīng)常有香客問(wèn)我租冠,道長(zhǎng),這世上最難降的妖魔是什么薯嗤? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任顽爹,我火速辦了婚禮,結(jié)果婚禮上骆姐,老公的妹妹穿的比我還像新娘镜粤。我一直安慰自己,他們只是感情好玻褪,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布肉渴。 她就那樣靜靜地躺著,像睡著了一般带射。 火紅的嫁衣襯著肌膚如雪同规。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,573評(píng)論 1 305
  • 那天庸诱,我揣著相機(jī)與錄音捻浦,去河邊找鬼。 笑死桥爽,一個(gè)胖子當(dāng)著我的面吹牛朱灿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播钠四,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼盗扒,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了缀去?” 一聲冷哼從身側(cè)響起侣灶,我...
    開(kāi)封第一講書(shū)人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎缕碎,沒(méi)想到半個(gè)月后褥影,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咏雌,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年凡怎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了校焦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡统倒,死狀恐怖寨典,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情房匆,我是刑警寧澤耸成,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站浴鸿,受9級(jí)特大地震影響井氢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜岳链,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一毙沾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧宠页,春花似錦、人聲如沸寇仓。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)遍烦。三九已至俭嘁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間服猪,已是汗流浹背供填。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留罢猪,地道東北人近她。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像膳帕,于是被迫代替她去往敵國(guó)和親粘捎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

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