MIT 6.828 Lab 1/ Part 2
- 2020 年 8 月 18 日
- 筆記
- mit-6.828 實驗筆記
Exercise 03
– obj/boot/boot.asm 反彙編文件
- 截取asm部分文件並注釋理解
# Set up the important data segment registers (DS, ES, SS).
xorw %ax,%ax # Segment number zero
7c02: 31 c0 xor %eax,%eax
//指令地址: 指令機器碼 指令機器碼反彙編到的指令
movw %ax,%ds # -> Data Segment
7c04: 8e d8 mov %eax,%ds
movw %ax,%es # -> Extra Segment
7c06: 8e c0 mov %eax,%es
movw %ax,%ss # -> Stack Segment
7c08: 8e d0 mov %eax,%ss
gdb指令:
- 在bootloader第一條語句 0x7C00處設置斷點並對比查看後續指令(與boot.asm對比)
4個問題回答:
-
At what point does the processor start executing 32-bit code? What exactly causes the switch from 16- to 32-bit mode?
-
當電腦加電後首先以實模式(real mode)運行,此時為16-bit;
當執行
ljmp $PROT_MODE_CSEG, $protcseg
後切換到32-bit,因為此時需要運行保護模式,而linux系統下的保護模式為32-bit
-
-
What is the last instruction of the boot loader executed, and what is the first instruction of the kernel it just loaded?
- boot loader執行的最後一條語句是bootmain子程式中的最後一條語句 」 ((void (*)(void)) (ELFHDR->e_entry))(); 「,即跳轉到作業系統內核程式的起始指令處。
- 這個第一條指令位於/kern/entry.S文件中,第一句 movw $0x1234, 0x472
-
Where is the first instruction of the kernel?
- 第一條指令位於/kern/entry.S文件中
-
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?
- 首先關於作業系統一共有多少個段,每個段又有多少個扇區的資訊位於作業系統文件中的Program Header Table中。這個表中的每個表項分別對應作業系統的一個段。並且每個表項的內容包括這個段的大小,段起始地址偏移等等資訊。所以如果我們能夠找到這個表,那麼就能夠通過表項所提供的資訊來確定內核佔用多少個扇區。
- 那麼關於這個表存放在哪裡的資訊,則是存放在作業系統內核映像文件的ELF頭部資訊中。
Exercise 04
讀pointer.c 程式碼複習c語言指針用法
#include <stdio.h>
#include <stdlib.h>
void
f(void)
{
int a[4];
int *b = malloc(16); //申請一個15位元組的記憶體塊大小並返回一個指針
int *c;
int i;
printf("1: a = %p, b = %p, c = %p\n", a, b, c); //列印地址
c = a; //指針C 指向數組a的首地址
for (i = 0; i < 4; i++)
a[i] = 100 + i; //給a數組賦值100~103
c[0] = 200; //給c[0]賦值200, 也就是修改a[0]
printf("2: a[0] = %d, a[1] = %d, a[2] = %d, a[3] = %d\n",
a[0], a[1], a[2], a[3]); //列印數組a的值
c[1] = 300; //修改a[1]
*(c + 2) = 301; //指針+2,代表是index+2,指向的是c[2]
3[c] = 302; //c編譯器當成*(3+c),故c[3]=302
printf("3: a[0] = %d, a[1] = %d, a[2] = %d, a[3] = %d\n",
a[0], a[1], a[2], a[3]); //200 300 301 302
c = c + 1; //c指針前移,指向a[1]
*c = 400; //a[1]=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
c = (int *) ((char *) c + 1); //char類型佔1個位元組,int類型佔16位(2位元組),所以此時c指針先被轉換為char類型,然後+1,相當於指向了下一個位元組,即a[1]的第二個位元組,然後再改為int*,即控制對象為16位;
*c = 500; //修改c指針指向的內容,a[1]a[2]都會受影響
printf("5: a[0] = %d, a[1] = %d, a[2] = %d, a[3] = %d\n",
a[0], a[1], a[2], a[3]); //200 128144 256 302
b = (int *) a + 1; //b指針指向a的下一個對象(後移16個位元組)
c = (int *) ((char *) a + 1); //與第一次c的改變類似
printf("6: a = %p, b = %p, c = %p\n", a, b, c);
//第6個輸出表明指針加減某個整數對應的記憶體偏移量取決於指針的類型。
}
int
main(int ac, char **av)
{
f();
return 0;
}
更深層解析://blog.csdn.net/amgtgsh3150267/article/details/101834733
運行截圖
指針複習總結:
- char類型佔1個位元組;int/short類型佔2位元組(16位);long類型佔4位元組
int *ip
表明*ip 是 int類型的;*ip=\*ip+10
就是*ip的內容+10(*ip)++
的括弧不可以省略,因為*/++運算符是遵循從右至左的結合順序int *pa;
pa+1
將指向下一個int對象a[10] ; pa=a;
那麼pa[i] 等價於*(pa+i)等價於a[i]
3[a]
也成立,c編譯器當成:*(3+i)
- int *pa; pa = &a[0] 與 pa = a 等價;
ELF binary 解析
編譯並連接C程式時:
- compiler 將(.c)file → (.o)file[包含二進位彙編語言指令]
- linker將所有的compiled objects files 合併成binary image[即 a binary in ELF format]
ELF binary 關注
-
將ELF executable 當作是 a header with loading information
-
關注ELF binary中的變長的program header段(記錄program sections的資訊) 以及 具體的program sections
-
練習要求:
Examine the full list of the names, sizes, and link addresses of all the sections in the kernel executable
鏈接地址 & 運行地址
VMA(link address,鏈接地址):The link address of a section is the memory address from which the section expects to execute.
LMA(load address,載入地址): The load address of a section is the memory address at which that section should be loaded into memory.
補充材料:(清華 ucore)
Link addr& Load addr
Link Address是指編譯器指定程式碼和數據所需要放置的記憶體地址,由鏈接器配置。Load Address是指程式被實際載入到記憶體的位置(由程式載入器ld配置)。一般由可執行文件結構資訊和載入器可保證這兩個地址相同。Link Addr和LoadAddr不同會導致:
- 直接跳轉位置錯誤
- 直接記憶體訪問(只讀數據區或bss等直接地址訪問)錯誤
- 堆和棧等的使用不受影響,但是可能會覆蓋程式、數據區域
注意:也存在Link地址和Load地址不一樣的情況(例如:動態鏈接庫,即動態重定位的時候)。
補充:程式執行前的步驟:
- 編譯-鏈接-裝入
-
裝入:
-
絕對裝入:固定地址再定位,程式地址在編譯鏈接時直接制定程式在執行時訪問的實際存儲器地址
-
靜態重定位裝入:裝入程式在程式執行之前地址在定位,執行期間不會改變
-
動態重定位裝入:程式裝入時不修改邏輯地址,訪問物理記憶體前再實時地修改邏輯地址為物理地址
-
練習
-
objdump
指令:objdump命令是Linux下的反彙編目標文件或者可執行文件的命令,它以一種可閱讀的格式讓你更多地了解二進位文件可能帶有的附加資訊。 -
objdump -h obj/kern/kernel
:
objdump -h obj/boot/boot.out
objdump -x obj/kern/kernel
"vaddr"
Other information for each program header is given, such as the virtual address ,"paddr"
the physical address"memsz" and "filesz"
the size of the loaded area
備註:SYMBOL TABLE模組完整程式碼:
cindy@cindy-virtual-machine:~/mit-6.828/lab/jos$ objdump -x obj/kern/kernel
obj/kern/kernel: 文件格式 elf32-i386
obj/kern/kernel
體系結構:i386, 標誌 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
起始地址 0x0010000c
程式頭:
LOAD off 0x00001000 vaddr 0xf0100000 paddr 0x00100000 align 2**12
filesz 0x00007dac memsz 0x00007dac flags r-x
LOAD off 0x00009000 vaddr 0xf0108000 paddr 0x00108000 align 2**12
filesz 0x0000b6a8 memsz 0x0000b6a8 flags rw-
STACK off 0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**4
filesz 0x00000000 memsz 0x00000000 flags rwx
節:
Idx Name Size VMA LMA File off Algn
0 .text 00001acd f0100000 00100000 00001000 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .rodata 000006bc f0101ae0 00101ae0 00002ae0 2**5
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .stab 00004291 f010219c 0010219c 0000319c 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .stabstr 0000197f f010642d 0010642d 0000742d 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .data 00009300 f0108000 00108000 00009000 2**12
CONTENTS, ALLOC, LOAD, DATA
5 .got 00000008 f0111300 00111300 00012300 2**2
CONTENTS, ALLOC, LOAD, DATA
6 .got.plt 0000000c f0111308 00111308 00012308 2**2
CONTENTS, ALLOC, LOAD, DATA
7 .data.rel.local 00001000 f0112000 00112000 00013000 2**12
CONTENTS, ALLOC, LOAD, DATA
8 .data.rel.ro.local 00000044 f0113000 00113000 00014000 2**2
CONTENTS, ALLOC, LOAD, DATA
9 .bss 00000648 f0113060 00113060 00014060 2**5
CONTENTS, ALLOC, LOAD, DATA
10 .comment 00000024 00000000 00000000 000146a8 2**0
CONTENTS, READONLY
SYMBOL TABLE:
f0100000 l d .text 00000000 .text
f0101ae0 l d .rodata 00000000 .rodata
f010219c l d .stab 00000000 .stab
f010642d l d .stabstr 00000000 .stabstr
f0108000 l d .data 00000000 .data
f0111300 l d .got 00000000 .got
f0111308 l d .got.plt 00000000 .got.plt
f0112000 l d .data.rel.local 00000000 .data.rel.local
f0113000 l d .data.rel.ro.local 00000000 .data.rel.ro.local
f0113060 l d .bss 00000000 .bss
00000000 l d .comment 00000000 .comment
00000000 l df *ABS* 00000000 obj/kern/entry.o
f010002f l .text 00000000 relocated
f010003e l .text 00000000 spin
00000000 l df *ABS* 00000000 entrypgdir.c
00000000 l df *ABS* 00000000 init.c
00000000 l df *ABS* 00000000 console.c
f01001d0 l F .text 0000001e serial_proc_data
f01001ee l F .text 00000062 cons_intr
f0113080 l O .bss 00000208 cons
f0100250 l F .text 0000012e kbd_proc_data
f0113060 l O .bss 00000004 shift.1385
f0101ca0 l O .rodata 00000100 shiftcode
f0101ba0 l O .rodata 00000100 togglecode
f0113000 l O .data.rel.ro.local 00000010 charcode
f010037e l F .text 00000203 cons_putc
f0113288 l O .bss 00000002 crt_pos
f0113290 l O .bss 00000004 addr_6845
f011328c l O .bss 00000004 crt_buf
f0113294 l O .bss 00000001 serial_exists
f0111200 l O .data 00000100 normalmap
f0111100 l O .data 00000100 shiftmap
f0111000 l O .data 00000100 ctlmap
00000000 l df *ABS* 00000000 monitor.c
f0113010 l O .data.rel.ro.local 00000018 commands
00000000 l df *ABS* 00000000 printf.c
f0100a2c l F .text 00000026 putch
00000000 l df *ABS* 00000000 kdebug.c
f0100aa5 l F .text 000000f5 stab_binsearch
00000000 l df *ABS* 00000000 printfmt.c
f0100dac l F .text 000000be printnum
f0100e6a l F .text 00000021 sprintputch
f0113028 l O .data.rel.ro.local 0000001c error_string
f01012fe l .text 00000000 .L20
f0100fa2 l .text 00000000 .L36
f01012eb l .text 00000000 .L35
f0100f5e l .text 00000000 .L34
f0100f27 l .text 00000000 .L66
f0100f8a l .text 00000000 .L33
f0100f30 l .text 00000000 .L32
f0100f39 l .text 00000000 .L31
f0100fc5 l .text 00000000 .L30
f010112f l .text 00000000 .L29
f0100fe1 l .text 00000000 .L28
f0100fb9 l .text 00000000 .L27
f010120a l .text 00000000 .L26
f010122a l .text 00000000 .L25
f010103a l .text 00000000 .L24
f01011b8 l .text 00000000 .L23
f0101296 l .text 00000000 .L21
00000000 l df *ABS* 00000000 readline.c
f01132a0 l O .bss 00000400 buf
00000000 l df *ABS* 00000000 string.c
00000000 l df *ABS* 00000000
f0111308 l O .got.plt 00000000 _GLOBAL_OFFSET_TABLE_
f0100da8 g F .text 00000000 .hidden __x86.get_pc_thunk.cx
f010000c g .text 00000000 entry
f01014f6 g F .text 00000026 strcpy
f01005ac g F .text 00000021 kbd_intr
f01008b1 g F .text 0000000a mon_backtrace
f010010e g F .text 0000006e _panic
f0100784 g F .text 00000000 .hidden __x86.get_pc_thunk.si
f01000aa g F .text 00000064 i386_init
f01016ac g F .text 00000066 memmove
f010138c g F .text 0000001e snprintf
f0100eac g F .text 0000047d vprintfmt
f01005cd g F .text 0000005a cons_getc
f0100a8d g F .text 00000018 cprintf
f0101712 g F .text 0000001a memcpy
f01013aa g F .text 00000109 readline
f0110000 g O .data 00001000 entry_pgtable
f0100040 g F .text 0000006a test_backtrace
f0101329 g F .text 00000063 vsnprintf
f0113060 g .bss 00000000 edata
f0100627 g F .text 00000126 cons_init
f0100780 g F .text 00000000 .hidden __x86.get_pc_thunk.ax
f010642c g .stab 00000000 __STAB_END__
f010642d g .stabstr 00000000 __STABSTR_BEGIN__
f0101980 g F .text 0000014d .hidden __umoddi3
f0100581 g F .text 0000002b serial_intr
f0101870 g F .text 0000010a .hidden __udivdi3
f0100776 g F .text 0000000a iscons
f010178a g F .text 000000de strtol
f01014cf g F .text 00000027 strnlen
f010151c g F .text 00000029 strcat
f01136a4 g O .bss 00000004 panicstr
f01136a0 g .bss 00000000 end
f010017c g F .text 00000050 _warn
f0101640 g F .text 00000020 strfind
f0101acd g .text 00000000 etext
0010000c g .text 00000000 _start
f0101576 g F .text 0000003f strlcpy
f01015df g F .text 0000003c strncmp
f0101545 g F .text 00000031 strncpy
f01001cc g F .text 00000000 .hidden __x86.get_pc_thunk.bx
f010172c g F .text 0000003d memcmp
f010074d g F .text 00000014 cputchar
f0101660 g F .text 0000004c memset
f0100761 g F .text 00000015 getchar
f0100e8b g F .text 00000021 printfmt
f0107dab g .stabstr 00000000 __STABSTR_END__
f01015b5 g F .text 0000002a strcmp
f0100b9a g F .text 0000020e debuginfo_eip
f0100a52 g F .text 0000003b vcprintf
f0110000 g .data 00000000 bootstacktop
f0112000 g O .data.rel.local 00001000 entry_pgdir
f0108000 g .data 00000000 bootstack
f010219c g .stab 00000000 __STAB_BEGIN__
f01014b3 g F .text 0000001c strlen
f010161b g F .text 00000025 strchr
f01007dc g F .text 000000d5 mon_kerninfo
f01008bb g F .text 00000171 monitor
f0101769 g F .text 00000021 memfind
f0100788 g F .text 00000054 mon_help
Exercise 05
上面引入了VMA 和 LMA,理解如下:
- 鏈接地址:可以理解為通過編譯器鏈接器處理形成的可執行程式中指令的地址,即邏輯地址
- 載入地址則是可執行文件真正被裝入記憶體後運行的地址,即物理地址
⚠ 由於在boot loader運行時還沒有任何的分段處理機制,或分頁處理機制,所以boot loader可執行程式中的鏈接地址就應該等於載入地址。
- step01:拷貝原有的obj/boot/boot.asm 以便用來比較
- step02:打開boot/Makefrag文件,修改鏈接地址,這裡改為
0x7E00
- step03:在lab下輸入make,重新編譯內核,首先查看一下obj/boot/boot.asm,並且和之前的那個obj/boot/boot.asm文件做比較。下圖是新編譯出來的boot.asm:
下圖是修改之前的boot.asm
可以看出,二者區別在於可執行文件中的鏈接地址不同了,原來是從0x7C00開始,現在則是從0x7E00開始
- step04:然後我們還是按照原來的方式,調試一下內核:
由於BIOS會把boot loader程式默認裝入到0x7c00處,所以我們還是再0x7C00處設置斷點,並且運行到那裡,結果發現如下:
可見第一條執行指令仍舊是正確的,所以我們接著往下一步步運行。
接下來的幾步仍舊是正常的,但是直到運行到一條指令:
圖中的0x7c1e處指令,
lgdtw 0x7e64
這條指令我們之前講述過,是把指令後面的值所指定記憶體地址處後6個位元組的值輸入全局描述符表暫存器GDTR,但是當前這條指令讀取的記憶體地址是0x7e64,我們在圖中也展示了一下這個地址處後面6個單元存放的值,發現是全部是0。這肯定是不對的,正確的應該是在0x7c64處存放的值,即圖中最下面一樣的值。可見,問題出在這裡,GDTR表的值讀取不正確,這是實現從實模式到保護模式轉換的非常重要的一步。
我們可以繼續運行,知道發現下面這句:
正常來說,0x7c2d處的指令
ljmp $0x08m $0x7e32
應該跳轉到的地址應該就是ljmp的下一條指令地址,即0x7c32,但是這裡給的值是0x7e32,所以造成錯誤,此時下條指令變成了0xfe05b。 然後後面 就..全面崩盤
Excersise 06:
– kernel起始地址查看
objdump -f obj/kern/kernel
– 理解boot/main.c 裡面的ELF loader
minimal ELF loader 把kernel裡面的每個section從disk 按照section’s load address讀進memory之後, 跳到了kernel的entry point
– 知識概要總結
BIOS
負責執行基本的系統初始化,從某些適當的位置(如 floppy disk(軟盤), hard disk, CD-ROM, or the network)載入作業系統,並將機器的控制權給作業系統(通過jmp指令將CS:IP設置為0000:7c00,0x7c00
是the boot sector被載入到的物理地址)- ⚠
the boot loader
將處理器從16-bit real model
切換到32-bit protected model
,使得1M以上的記憶體地址可以訪問(主要boot.S完成。將控制訊號傳遞0x60與0x64埠,載入GDT表,將CR0的bit0位設為1,call bootmain)。還從硬碟中讀取內核到記憶體並跳轉到內核入口地址(主要main.c完成。先讀取ELF program headers
,然後通過ELFHDR內的資訊將kernel載入到記憶體正確位置,最後跳轉內核入口地址0x00100000
) - the boot loader將內核載入到記憶體的物理地址(載入地址)是
0x00100000
,但內核在記憶體中的虛擬地址(鏈接地址)是0xf0100000
- CR0中的兩個控制位
PG (Paging)和PE(Protection Enable)
。只有在保護方式下(PE=1)分頁機制才可能生效。PE=1, PG=1,分頁機制生效,把線性地址轉換為物理地址。PE=1, PG=0,分頁機制無效,線性地址就直接作為物理地址。 - 數據在記憶體中有
大端(big-endian)格式
–高尾端,尾端放在高地址處和小端(little-endian)格式
—低尾端, 尾端放在低地址處 backtrace函數
實現的關鍵在於ebp
。當前ebp指向當前子程式的棧幀(frame)
基地址,地址記憶體的是caller的棧幀基地址,裡面存的又是再外層的caller的棧幀基地址,這樣就可以區分出每一層程式的棧幀,從而實現回溯。
- 開機第一條指令:0xffff0 BIOS系統啟動,這裡只是存放了一條跳轉指令,
- BIOS 基本輸入/輸出系統,其本質是一個固化在主板Flash/CMOS上的軟體)和位於軟盤/硬碟引導扇區中的OS Boot Loader(在ucore中的bootasm.S和bootmain.c)一起組成。通過跳轉指令跳到BIOS例行程式起始點(0xe05b)。BIOS做完電腦硬體自檢和初始化後,會選擇一個啟動設備(例如軟盤、硬碟、光碟等),並且讀取該設備的第一扇區(即主引導扇區或啟動扇區)到記憶體一個特定的地址0x7c00處,然後CPU控制權會轉移到那個地址繼續執行。
- boot loader 載入內核並跳到內核入口 0x0010000c
- 注意:內核載入到記憶體的物理地址(載入地址)是
0x00100000
,但內核在記憶體中的虛擬地址(鏈接地址)是0xf0100000
– Exercise 06
- BIOS切換到boot loader之前,檢查記憶體
0x100000
處8個word(此處1個word=2bytes)- 往後8個 word全是0
- 當boot loader將內核載入到記憶體之後,再檢查
- 往後8個word有內容了
※:進入內核後,內核已經載入到以0x100000
為始地址的主存空間,所以相鄰地址都有內容了
重點:
-
boot loader 進入到內核:
- 從kernel文件中可以看到起始地址為
0x10000c
- 從main.c文件中看到e_entry的地址:
- 從kernel文件中可以看到起始地址為
- 疑惑: