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;
- Specifically, what function does
console.c
export? How is this function used byprintf.c
?
static void
putch(int ch, int *cnt)
{
cputchar(ch);
*cnt++;
}
用來(lái)在 console 中輸出字符起宽。
- 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
是什么坯沪,0xff
和0x0700
又是什么绿映?
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)有弄懂==。后面再看看是什么意思潘拱。
-
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 {}
- 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é):
- 電腦上電之后開(kāi)始載入 BIOS, BIOS 開(kāi)始搜索外設(shè)尋找 bootable 的外設(shè)窄锅,如果 bootable创千, BIOS 將會(huì)把 OS 的 bootloabder 載入到 0x7c00~ 0x7dff 并且將 CS:IP 7c00.
- 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)行核心代碼疏之。
- Kernel 將啟動(dòng)分頁(yè)模式將低地址的代碼(0x00100000) 映射到(0xf0100000) 高位地址殿雪。
- 之后實(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é)果亏镰。 - 接下來(lái)是函數(shù)調(diào)用的過(guò)程(怎么壓棧,以及棧的相關(guān)知識(shí))拯爽。
- 最后是學(xué)習(xí) backtrace 相關(guān)的東西索抓,對(duì)程序保存時(shí)輸出的信息有了一點(diǎn)了解。