mit6.828 - lab1筆記

来源:https://www.cnblogs.com/toso/p/18170630
-Advertisement-
Play Games

安裝環境編譯qemu 1. PC啟動 打開兩個視窗,在第一個視窗中 make qemu-gdb,會啟動內核,但在執行第一個指令之前停下; 在第二個視窗中make gdb,實時觀察第一個視窗中的執行過程。 從這裡可以觀察到: IBM PC 在物理地址 0x000ffff0 開始執行, 位於為 ROM ...


安裝環境編譯qemu

image.png

1. PC啟動

打開兩個視窗,在第一個視窗中 make qemu-gdb,會啟動內核,但在執行第一個指令之前停下;
在第二個視窗中make gdb,實時觀察第一個視窗中的執行過程。

image.png

從這裡可以觀察到:

  • IBM PC 在物理地址 0x000ffff0 開始執行, 位於為 ROM BIOS 保留的 64KB 區域的最頂部。
  • PC 的第一個指令執行的是 CS=0xf000 IP=0xfff0
  • 第一條指令是 jmp 指令, 跳轉到分段地址 CS = 0xf000 和 IP = 0xe05b。

image.png

## 為什麼第一個指令在這個位置?
這是因為 8088的BIOS 是“硬連線”的 到物理地址範圍 0x000f0000-0x000fffff, 從而確保BIOS首先獲得對機器的控制
0xffff0 是 BIOS 結束前的 16 個位元組 (0x100000),BIOS做的第一件事是向後jmp 到 BIOS 中較早的位置;

2. bootloader

bootloader 的開始
bootsec 如果磁碟是可啟動的, 第一個扇區稱為 boot sector, 因為這是引導載入程式代碼所在的位置。

當 BIOS 找到可啟動軟盤或硬碟時, 會將其載入(512位元組)至物理地址的記憶體的0x7c00 0x7dff。然後64KB大小的BOIS的最後一句話即是:
jmp $0x0000,$0x7c00
將控制轉交給了 bootloader

image.png

boot loader 的任務有兩個:

  1. 將處理器從實模式切換到保護模式。因為實模式最多只能訪問1MB的記憶體。
  2. 從硬碟讀取內核,載入到記憶體。bootstrap使用特殊I/O指令,直接訪問IDE磁碟設備存儲器來讀取。

boot loader 的實現:
 一個彙編語言源文件,boot/boot.S
 一個 C 源文件 boot/main.c
 反彙編文件: obj/boot/boot.asm

先看代碼、然後看反彙編、再調試,摸清楚 boot loader 的流程

閱讀源碼

boot/boot.S的內容:

  1. 載入全局描述符表 GDT
  2. 開啟保護模式:將CR0寄存器的PE_ON位置1
  3. 通過ljmp進入保護模式
  4. 載入各個段描述符
  5. 跳轉至 bootmain.c

boot/bootmain.c的內容

  1. 載入kernel的elf文件頭:從硬碟1號扇區(第二個扇區)的起始處讀取4KB大小的內容至 0x0010_0000處,並將其視為ELF結構體
  2. 將 kernel 的各個段載入至記憶體

boot/boot.S

boot.S 中有一個令人迷惑的代碼:

image.png

在即將跳轉到C語言實現的bootmain的時候,居然將 start標號 給了esp,那麼 start 代表了什麼?

image.png

啊,start位於代碼的一開始的地方,這裡不是應該存代碼嗎?給了esp,後面棧不得把這下麵的代碼的都給覆蓋了?
稍等下,棧是從高地址向低地址生長的,這裡boot.S的代碼在ide里看雖然寫在start下麵,但是在記憶體里是start更高的地方。從 obj/boot.asm 里來看:

image.png

start 位於 0x7C00,之後的代碼位於0x7C00之上,而棧則向0x7C00下方生長

image.png

boot/main.c

boot/bootmain.c的內容

  1. 載入kernel的elf文件頭:從硬碟1號扇區(第二個扇區)的起始處讀取4KB大小的內容至 0x0010_0000處,並將其視為ELF結構體
  2. 將 kernel 的各個段載入至記憶體

image.png

其中的迴圈會逐個將 /obj/kern/kernl 的段載入至對應的物理地址(註意,readseg 的第一個參數是 ph->p_pa),可以通過 objdump -l kernel 查看:

image.png

最終記憶體視圖如下:

image.png


##### 看反彙編發現了一些有趣的事情:
1. 迴圈中,調用函數後的遞增操作,在彙編層面會在調用之前發生

![image.png](https://pic-bed-1258913394.cos.ap-nanjing.myqcloud.com/20240501213701.png)


2. 調用前,調用者負責傳參,被調者負責保護現場,還原現場;返回後,調用者負責將傳參占用的空間還原


關於ELF和編譯鏈接

在開發者完成一個C語言程式程式 xxx.c ,為了讓他跑起來,需要由編譯器將其編譯成 xxx.o 的對象文件,然後由鏈接器將所有已經編譯的對象文件鏈接成 xxx 可執行文件。


3. 內核

目的:理解lab1的簡易內核的工作過程

任務:閱讀 /kern 下的代碼。

lab1的內核功能十分簡單,如上文中運行起來的那樣,他的shell只提供兩個功能,help和kerninfo。
內核相關的代碼位於 /kern 之下。

entry.S:初始化記憶體映射,設置頁表、棧指針
entrypgdir.c:頁表設計

init.c:初始化shell,初始化終端設備、啟動shell
console.h, console.c:終端功能的實現
printf.c:列印功能的實現
monitor.h, monitor.c:shell功能的實現

挺好,為了理解 lab1 的內核,接下來就沿著 entry.S 和 init.c 去分析內核。
即,分析entry.S對記憶體映射的處理、init.c 中終端設備的初始化shell的處理

記憶體映射的處理

關於記憶體的處理,lab1目前沒有記憶體管理,只是用起來了虛擬記憶體,將4MB物理記憶體映射到原位和高處。即:

  • 0x00000000 至 0x00400000 的物理地址 -> 0x00000000 至 0x00400000 的虛擬地址
  • 0x00000000 至 0x00400000 的物理地址 -> 0xf0000000 至 0xf0400000 的虛擬地址
    畢竟這麼大的記憶體已經足夠映射當前內核了。

先來看看怎麼映射的

entry.S:載入頁表

在 boolloader 階段,bootmain 最後通過 ((void (*)(void)) (ELFHDR->e_entry))();
將控制轉交給了 /kern/entry.S,然後來看看entry.S

image.png

關於數組 entry_pgdir

entry.S 首先讀取了頁表 entry_pgdir,這個變數在 /kern/entrypgdir.c 中定義:

pte_t entry_pgtable[NPTENTRIES];

__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
};

__attribute__((__aligned__(PGSIZE)))
pte_t entry_pgtable[NPTENTRIES] = {
	0x000000 | PTE_P | PTE_W,
	0x001000 | PTE_P | PTE_W,
	0x002000 | PTE_P | PTE_W,
	0x003000 | PTE_P | PTE_W,
	0x004000 | PTE_P | PTE_W,
	0x005000 | PTE_P | PTE_W,
	0x006000 | PTE_P | PTE_W,
	0x007000 | PTE_P | PTE_W,
	0x008000 | PTE_P | PTE_W,
	0x009000 | PTE_P | PTE_W,
	0x00a000 | PTE_P | PTE_W,
	//省略...
}

其中 [0] = ((uintptr_t)entry_pgtable - KERNBASE) + PTE_P, 實現了
0x00000000 至 0x00400000 的物理地址 -> 0x00000000 至 0x00400000 的虛擬地址
[KERNBASE>>PDXSHIFT] = ((uintptr_t)entry_pgtable - KERNBASE) + PTE_P + PTE_W 實現了
0x00000000 至 0x00400000 的物理地址 -> 0xf0000000 至 0xf0400000 的虛擬地址

關於頁表的映射和計算方法,見另一個單獨的筆記 "lab1 關於頁表的知識"

關於巨集 RELOC

從代碼中可以看到,在頁表載入之前,所有的符號都需要使用巨集 RELOC ,其含義是將符號的地址減去 0xF000_0000,即,將虛擬地址轉化為真實的物理地址。
這就說明 entry.S 被鏈接到了 0xF000_0000 上。
通過 objdump -h 來看也確實如此

image.png

但是對應的makefile是將其指定到 0xf000_0000 上的,可以從 /kern/kernel.ld 中找到

image.png

關於 bootstack

把目光回到 entry.S 的代碼,在代碼的最後通過標號 bootstack 和 bootstacktop定義了棧的位置,話說,這裡究竟對應的物理地址是哪裡呢?

image.png

可以看到 bootstack 緊鄰 .data 段
通過 readelf -s kernel 查看

image.png

結合 objdump -h kernel

image.png

確實如此,bootstack 和 .data都位於 0xf010_8000 ,那麼物理地址就是 0x0010_8000
棧頂 bootstacktop 的物理地址則是 0x0011_0000
在記憶體里看呢?

image.png


init.c:內核初始化

init.c 中最核心的函數是 i386_init

image.png

關於 清空BSS段

edata[]end[] 是在哪裡定義的?這兩個變數看起來指的是bss段的開始和結束。
這種問題當然要去看鏈接腳本了,查看 kern/kernel.ld

image.png

顯示輸出的處理

這裡涉及的代碼有

kern:
	console.h, console.c :涉及終端設備的初始化
	printf.c :涉及printf的實現
lib:
	printfmt.c:支撐printf的實現
	readline.c:實現從終端讀取
	string.c:涉及字元串的處理,支撐printf的實現
inc:
	string.h:涉及字元串的處理,支撐printf的實現
關於 cons_init

這裡主要用於初始化終端顯示器的硬體設置,其中代碼使用彙編,通過in out指令與設備交互,不過多深究了。
image.png

關於 printf 的實現

printf 的實現這裡借大佬的說明圖示意:

image.png

往控制台寫字元串,本質還是往物理地址0xB8000開始的顯存寫數據

jos 的練習提到 printf 的實現需要補充,具體位於 /lib/printfmt.c : vprintfmt 中

image.png

image.png

shell的處理

這裡涉及的代碼有

kern:
	monitor.h, monitor.c :命令的解析、各種命令的實現
關於monitor的實現

先看看 monitor.h

image.png

然後看看 monitor.c

image.png

這麼看,只要在 commands[] 中填充 backtrace 的數據就可以補充這個功能了。

image.png

monitor 是怎麼實現的呢?,比較短,直接放代碼了

void
monitor(struct Trapframe *tf)
{
	char *buf;

	cprintf("Welcome to the JOS kernel monitor!\n");
	cprintf("Type 'help' for a list of commands.\n");


	while (1) {
		buf = readline("K> ");
		if (buf != NULL)
			if (runcmd(buf, tf) < 0)
				break;
	}
}

本質就是一個迴圈,列印出 K> 然後接受輸入,然後根據輸入執行命令。看起來就像是大一C語言課設的XXX管理系統一樣。
看看 runcmd 如何實現:

image.png

挺好,那麼現在我們要做的就是實現 backtrace。

堆棧

涉及到的代碼:

kern:
	kdebug.h、kdebug.c:涉及Eipdebuginfo和debuginfo_eip的實現
inc:
	stab.h:涉及Stab表的數據結構
	x86.h:涉及讀取寄存器的內斂彙編

這裡我們回歸到jos的學習任務,研究關於棧幀的處理。並補充一些函數:
/kern/monitor.c:mon_backtrace
/kern/kdebug.c:debuginfo_eip、stab_binsearch

關於backtrace的實現

關於棧幀
棧幀,就是調用函數的時候,處理形參傳遞和實參存儲的數據結構。
在調用函數時,調用者負責傳遞形參,被調者負責保護現場、恢復現場,最後調用者將形參釋放掉。
這之中需要調用者和被調者的約定:
比如 函數列表中的參數,是從右至左的順序入棧的之類的。

這裡繼續借用大佬 gatsby123 博客中的圖,簡單示意,不做深究

image.png

jos的練習11 讓我們完成 mon_backtrace,希望我們將每個棧幀按照這樣的格式輸出:

Stack backtrace:
  ebp f0109e58  eip f0100a62  args 00000001 f0109e80 f0109e98 f0100ed2 00000031
  ebp f0109ed8  eip f01000d6  args 00000000 00000000 f0100058 f0109f28 00000061
  ...

不過好在 jos 已經實現了一些函數,供我們調用了,位於 /inc/x86.h
這裡提供了一些內聯彙編,用於讀取各種寄存器的值

image.png

完成這一步也是很簡單啦

image.png

但是 jos 的練習12上了強度,讓我們列印出這樣的效果:

image.png

就是在上面的基礎上,顯示當前棧幀所在的文件和,以及調用在文件的所在函數的第幾行發生。
為了實現這一功能,jos 在kern/kdebug.h 和 kern/kdebug.c 中提供了支持:

image.png

可以看到 Eipdebuginfo 用於存儲當前eip的相關信息。這種功能的背後當然需要編譯器的支持,為了方便debug,編譯器可以通過stab將這些信息保存下來,

關於stab

按照 exercise12 的提示,通過 kernel.ld 可以看到.stab和 .stabstr 的相關連接選項

image.png

可以看到其中定義了 __STAB_BEGIN__ __STAB__END__ __STABSTR_BEGIN__ __STABSTR_END__

通過 objdump -h obj/kern/kernel 可以看到 stab 表

image.png

通過 objdump -G obj/kern/kernel 可以看到stab的內容

image.png

其中包含1213項,每項包括

symnum:序號
n_type:類型
n_othor:雜項信息
n_desc:描述信息
n_value:表示地址。特別要註意的是,這裡只有FUN類型的符號的地址是絕對地址,SLINE符號的地址是偏移量,
n_strx:stabstr表中對應的字元串的序號
string:stabstr表中對應的字元串

在 stab.h中有對應的數據結構:

image.png
那麼這些信息要怎麼使用呢,看看kdebug.c


stab_binsearch(stabs, region_left, region_right, type, addr)

某些符號表項類型按指令地址遞增順序排列。 例如,標記函數的 N_FUN 符號表項(n_type == N_FUN 的符號表項)和標記源文件的 N_SO 符號表項。

給定指令地址後,該函數會查找包含該地址的 "type "類型的符號表項。

搜索範圍為[*region_left, *region_right]。

因此,要搜索一整套 N 個符號表項,可以執行以下操作

// left = 0;
// right = N - 1; /* 最右邊的符號表項 */
// stab_binsearch(stabs, &left, &right, type, addr);

搜索會修改 *region_left 和 *region_right 以括住 "addr"。 *region_left 指向包含'addr'的匹配符號表項,*region_right 指向下一個符號表項之前。 如果 *region_left > *region_right,則表示 "addr "不包含在任何匹配的符號表項中。

// 例如,給定這些 N_SO 符號表項:
// 索引類型 地址
// 0 SO f0100000
// 13 SO f0100040
// 117 SO f0100176
// 118 SO f0100178
// 555 SO f0100652
// 556 SO f0100654
// 657 SO f0100849
// 此代碼:
// left = 0, right = 657;
// stab_binsearch(stabs, &left, &right, N_SO, 0xf0100184);
// 將退出設置 left = 118, right = 554.

這裡給出了 stab_binsearch 的使用說明,從函數名可以看出來他是使用二分查找演算法從stab中查找addr指定的type類型的符號,然後通過left返回出來。來簡單看看代碼:

image.png

然後來看看要處理的 debuginfo_eip

image.png

image.png

到現在為止,已經找到了所在文件名、所在函數名、所在函數地址、所在函數名長度、相對函數的偏移
就差所在行號了,找行號的代碼很好寫啊,照著寫就行了,這個函數調用,將範圍改一下,然後類型改成代碼段的行就行了,因為eip只會在代碼段里移動。

stab_binsearch(stabs, &lline, &rline, N_SLINE, addr);

但是,行號究竟是stab中的哪個成員提供的啊?
image.png

觀察一波 objdump -G 的輸出

image.png

目測 n_value對應的是SLINE的記憶體地址,而n_desc看著更像行號一些,於是:

image.png

補充一下 monitor.c

image.png

編譯測試一下:

image.png

看著好像成功了,試試評分

image.png

收工。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 經過前面幾個部分學習,相信學過的同學已經能夠掌握 .NET Emit 這種中間語言,並能使得它來編寫一些應用,以提高程式的性能。隨著 IL 指令篇的結束,本系列也已經接近尾聲,在這接近結束的最後,會提供幾個可供直接使用的示例,以供大伙分析或使用在項目中。 ...
  • 引言 在我們之前的文章中介紹過使用Bogus生成模擬測試數據,今天來講解一下功能更加強大自動生成測試數據的工具的庫"AutoFixture"。 什麼是AutoFixture? AutoFixture 是一個針對 .NET 的開源庫,旨在最大程度地減少單元測試中的“安排(Arrange)”階段,以提高 ...
  • 本章將和大家分享ES的數據同步方案和ES集群相關知識。廢話不多說,下麵我們直接進入主題。 一、ES數據同步 1、數據同步問題 Elasticsearch中的酒店數據來自於mysql資料庫,因此mysql數據發生改變時,Elasticsearch也必須跟著改變,這個就是Elasticsearch與my ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • GoF之工廠模式 @目錄GoF之工廠模式每博一文案1. 簡單說明“23種設計模式”1.2 介紹工廠模式的三種形態1.3 簡單工廠模式(靜態工廠模式)1.3.1 簡單工廠模式的優缺點:1.4 工廠方法模式1.4.1 工廠方法模式的優缺點:1.5 抽象工廠模式1.6 抽象工廠模式的優缺點:2. 總結:3 ...
  • VMware虛擬機安裝Centos-7.9 創作不易,點贊關註一下吧 1.安裝VMware Workstation Pro 大家根據自己的實際情況安裝合適版本的VMware Workstation Pro,具體的安裝推薦及各版本的下載鏈接大家可以看我之前發佈的一篇博客:VMware Workstat ...
  • 前言 新版WSL2已經支持鏡像模式網路 可以將WSL2的IP固定為與主機相同 鏡像模式網路 但是在啟用後WSL2中Docker運行的服務本機無法訪問 issues 10494 結合上述issues給出自己的使用新的幫助大家避坑 環境 Win11 23H2win + r > winver WSL 2. ...
  • 參考 Ubuntu installation on a RISC-V virtual machine using a server install image and QEMU 用到的文件 fw_jump.bin u-boot.bin ubuntu-22.04.3-preinstalled-serv ...
一周排行
    -Advertisement-
    Play Games
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...
  • 目錄前言PostgreSql安裝測試額外Nuget安裝Person.cs模擬運行Navicate連postgresql解決方案Garnet為什麼要選擇Garnet而不是RedisRedis不再開源Windows版的Redis是由微軟維護的Windows Redis版本老舊,後續可能不再更新Garne ...
  • C#TMS系統代碼-聯表報表學習 領導被裁了之後很快就有人上任了,幾乎是無縫銜接,很難讓我不想到這早就決定好了。我的職責沒有任何變化。感受下來這個系統封裝程度很高,我只要會調用方法就行。這個系統交付之後不會有太多問題,更多應該是做小需求,有大的開發任務應該也是第二期的事,嗯?怎麼感覺我變成運維了?而 ...
  • 我在隨筆《EAV模型(實體-屬性-值)的設計和低代碼的處理方案(1)》中介紹了一些基本的EAV模型設計知識和基於Winform場景下低代碼(或者說無代碼)的一些實現思路,在本篇隨筆中,我們來分析一下這種針對通用業務,且只需定義就能構建業務模塊存儲和界面的解決方案,其中的數據查詢處理的操作。 ...
  • 對某個遠程伺服器啟用和設置NTP服務(Windows系統) 打開註冊表 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpServer 將 Enabled 的值設置為 1,這將啟用NTP伺服器功 ...
  • title: Django信號與擴展:深入理解與實踐 date: 2024/5/15 22:40:52 updated: 2024/5/15 22:40:52 categories: 後端開發 tags: Django 信號 松耦合 觀察者 擴展 安全 性能 第一部分:Django信號基礎 Djan ...
  • 使用xadmin2遇到的問題&解決 環境配置: 使用的模塊版本: 關聯的包 Django 3.2.15 mysqlclient 2.2.4 xadmin 2.0.1 django-crispy-forms >= 1.6.0 django-import-export >= 0.5.1 django-r ...
  • 今天我打算整點兒不一樣的內容,通過之前學習的TransformerMap和LazyMap鏈,想搞點不一樣的,所以我關註了另外一條鏈DefaultedMap鏈,主要調用鏈為: 調用鏈詳細描述: ObjectInputStream.readObject() DefaultedMap.readObject ...
  • 後端應用級開發者該如何擁抱 AI GC?就是在這樣的一個大的浪潮下,我們的傳統的應用級開發者。我們該如何選擇職業或者是如何去快速轉型,跟上這樣的一個行業的一個浪潮? 0 AI金字塔模型 越往上它的整個難度就是職業機會也好,或者說是整個的這個運作也好,它的難度會越大,然後越往下機會就會越多,所以這是一 ...
  • @Autowired是Spring框架提供的註解,@Resource是Java EE 5規範提供的註解。 @Autowired預設按照類型自動裝配,而@Resource預設按照名稱自動裝配。 @Autowired支持@Qualifier註解來指定裝配哪一個具有相同類型的bean,而@Resourc... ...