實驗思考題
Thinking 1.1

使用命令
/OSLAB/compiler/usr/bin/mips_4KC-gcc -c test1.c
/OSLAB/compiler/usr/bin/mips_4KC-ld test1.o -o test1
/OSLAB/compiler/usr/bin/mips_4KC-objdump -DS test1.o > o.txt
/OSLAB/compiler/usr/bin/mips_4KC-objdump -DS test1 > out.txt
反編譯.o文件:
2 test1.o: file format elf32-tradbigmips
3
4 Disassembly of section .text:
5
6 00000000 <main>:
7 0: 3c1c0000 lui gp,0x0
8 4: 279c0000 addiu gp,gp,0
9 8: 0399e021 addu gp,gp,t9
10 c: 27bdffe0 addiu sp,sp,-32
11 10: afbe0018 sw s8,24(sp)
12 14: 03a0f021 move s8,sp
13 18: 24020003 li v0,3
14 1c: afc20010 sw v0,16(s8)
15 20: c7c00010 lwc1 $f0,16(s8)
16 24: 468000a1 cvt.d.w $f2,$f0
17 28: 8f820000 lw v0,0(gp)
18 2c: d4400000 ldc1 $f0,0(v0)
19 30: 46201082 mul.d $f2,$f2,$f0
20 34: c7c00010 lwc1 $f0,16(s8)
21 38: 46800021 cvt.d.w $f0,$f0
22 3c: 46201002 mul.d $f0,$f2,$f0
23 40: f7c00008 sdc1 $f0,8(s8)
24 44: 00001021 move v0,zero
25 48: 03c0e821 move sp,s8
26 4c: 8fbe0018 lw s8,24(sp)
27 50: 27bd0020 addiu sp,sp,32
28 54: 03e00008 jr ra
29 58: 00000000 nop
30 5c: 00000000 nop
31 Disassembly of section .reginfo:
32
33 00000000 <.reginfo>:
34 0: f2000004 0xf2000004
35 4: 00000000 nop
36 8: 00000005 0x5
37 ...
38 Disassembly of section .pdr:
39
40 00000000 <.pdr>:
41 0: 00000000 nop
42 4: 40000000 mfc0 zero,c0_index
43 8: fffffff8 sdc3 $31,-8(ra)
44 ...
45 14: 00000020 add zero,zero,zero
46 18: 0000001e 0x1e
47 1c: 0000001f 0x1f
48 Disassembly of section .rodata:
49
50 00000000 <.rodata>:
51 0: 40091eb8 0x40091eb8
52 4: 51eb851f beql t7,t3,fffe1484 <main+0xfffe1484>
53 8: 00000000 nop
54 c: 00000000 nop
55 ...
反編譯.out文件:
2 test1: file format elf32-tradbigmips
3
4 Disassembly of section .reginfo:
5
6 00400094 <.reginfo>:
7 400094: f2000004 0xf2000004
8 400098: 00000000 nop
9 40009c: 00000005 0x5
10 ...
11 4000a8: 10007ff0 b 42006c <main+0x1ffbc>
12 Disassembly of section .text:
13
14 004000b0 <main>:
15 4000b0: 3c1c0fc0 lui gp,0xfc0
16 4000b4: 279c7f40 addiu gp,gp,32576
17 4000b8: 0399e021 addu gp,gp,t9
18 4000bc: 27bdffe0 addiu sp,sp,-32
19 4000c0: afbe0018 sw s8,24(sp)
20 4000c4: 03a0f021 move s8,sp
21 4000c8: 24020003 li v0,3
22 4000cc: afc20010 sw v0,16(s8)
23 4000d0: c7c00010 lwc1 $f0,16(s8)
24 4000d4: 468000a1 cvt.d.w $f2,$f0
25 4000d8: 8f828018 lw v0,-32744(gp)
26 4000dc: d4400110 ldc1 $f0,272(v0)
27 4000e0: 46201082 mul.d $f2,$f2,$f0
28 4000e4: c7c00010 lwc1 $f0,16(s8)
29 4000e8: 46800021 cvt.d.w $f0,$f0
30 4000ec: 46201002 mul.d $f0,$f2,$f0
31 4000f0: f7c00008 sdc1 $f0,8(s8)
32 4000f4: 00001021 move v0,zero
33 4000f8: 03c0e821 move sp,s8
34 4000fc: 8fbe0018 lw s8,24(sp)
35 400100: 27bd0020 addiu sp,sp,32
36 400104: 03e00008 jr ra
37 400108: 00000000 nop
38 40010c: 00000000 nop
39 Disassembly of section .rodata:
40
41 00400110 <.rodata>:
42 400110: 40091eb8 0x40091eb8
43 400114: 51eb851f beql t7,t3,3e1594 <main-0x1eb1c>
44 400118: 00000000 nop
45 40011c: 00000000 nop
46 Disassembly of section .got:
47
48 10000000 <_GLOBAL_OFFSET_TABLE_>:
49 10000000: 00000000 nop
50 10000004: 80000000 lb zero,0(zero)
51 10000008: 00400000 0x400000
52 ...
Thinking 1.2

使用readelf -h testELF解析testELF文件
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x8048490
Start of program headers: 52 (bytes into file)
Start of section headers: 4440 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 9
Size of section headers: 40 (bytes)
Number of section headers: 30
Section header string table index: 27
使用readelf -h vmlinux解析vmlinux文件
ELF Header:
Magic: 7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, big endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: MIPS R3000
Version: 0x1
Entry point address: 0x80010000
Start of program headers: 52 (bytes into file)
Start of section headers: 37164 (bytes into file)
Flags: 0x1001, noreorder, o32, mips1
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 2
Size of section headers: 40 (bytes)
Number of section headers: 14
Section header string table index: 11
在Data段的信息中可以看到,我們生成的readelf文件適用於解析小端存儲的,而我們的內核vmlinux是大端存儲的文件,因此無法解析內核。
Thinking 1.3

實驗操作系統使用GXemul仿真器,支持直接加載ELF格式的內核。BootLoader中Stage1的功能已經由其提供,而事實上此時已經跳到了內核入口,跳到內核第一步就是實現在boot/start.o中的代碼初始化硬件設備,設置堆棧,跳轉到main函數入口。
Thinking 1.4

在加載程序時,避免發生共享頁面和沖突頁面現象。首先,不同程序段的占用空間不能夠有重合,然后,盡量避免一個頁面同時被多個程序段所占用。即若前面的程序段末地址所占用的頁面地址為vi,則后續的程序段首地址應從下一頁面vi+1開始占用。
Thinking 1.5

內核入口在0x00000000處,main函數在0x80010000處。我們在start.S文件中編寫mips匯編代碼,設置堆棧后直接跳到main函數中。在跨文件調用函數時,每個函數會有一個固定的地址,調用過程為將需要存儲的值進行進棧等保護,再用jal跳轉到相應函數的地址。
Thinking 1.6

將宏定義的內容轉化為寄存器編號:
mtc0 $0,$12
#將SR寄存器清零
mfc0 $t0,$16
and $t0,~0x7 #要清零的位的反碼
ori $t0,0x2 #要置1的位
mtc0 $t0,$16
#將Config寄存器0號位和2號位置0,將1號位置1
上電后,需要設置SR寄存器來使CPU進入工作狀態,而硬件通常都是復位后讓許多寄存器的位為未定義。
Config寄存器的后三位為可寫的,用來決定固定的kseg0區是否經過高速緩存和其確切的行為如何。
實驗難點展示
本次實驗要做的很簡單很基礎,當然了,這是基於我做完了之后得出來的結論。在那么多文件夾內找到彼此間的調用關系實屬不易。我認為實驗中較難的地方在於對ELF文件的解析和補充printf函數的部分。
Exercise 1.2 解析ELF文件

typedef struct {
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
} Elf32_Ehdr;
typedef struct{
Elf32_Word sh_name; /* Section name */
Elf32_Word sh_type; /* Section type */
Elf32_Word sh_flags; /* Section flags */
Elf32_Addr sh_addr; /* Section addr */
Elf32_Off sh_offset; /* Section offset */
Elf32_Word sh_size; /* Section size */
Elf32_Word sh_link; /* Section link */
Elf32_Word sh_info; /* Section extra info */
Elf32_Word sh_addralign; /* Section alignment */
Elf32_Word sh_entsize; /* Section entry size */
}Elf32_Shdr;
typedef struct {
// segment type
Elf32_Word p_type;
// offset from elf file head of this entry
Elf32_Off p_offset;
// virtual addr of this segment
Elf32_Addr p_vaddr;
// physical addr, in linux, this value is meanless and has same value of p_vaddr
Elf32_Addr p_paddr;
// file size of this segment
Elf32_Word p_filesz;
// memory size of this segment
Elf32_Word p_memsz;
// segment flag
Elf32_Word p_flags;
// alignment
Elf32_Word p_align;
}Elf32_Phdr;
ELF文件結構圖如上圖所示,而這三個結構體分別代表了ELF程序頭、Section節頭、Segment段頭,實際上這個問題困擾了我很久。可以看到 ,這些結構體占用的空間都不大,所以這里面肯定不包含具體的內容。經過多次試驗,我也終於想明白了
Elf32_Ehdr對應圖標的ELF header,其中e_entry指明了程序入口的虛擬地址。e_phoff指明程序頭表的偏移量。e_shoff 指明節頭表的偏移量。e_phentsize 表示程序頭表每一個表項的大小(即對應的Elf32_Phdr結構體大小)。e_phnum 表示程序頭表項數目。e_shentsize 表示節頭表每一個表項的大小(即對應的Elf32_Shdr結構體大小)。e_shnum 表示節頭表項數目。
多個節頭表項,可看做Elf32_Shdr[e_shnum]數組組成了節頭表(Section Header Table)。通過對每個結構體的sh_addr屬性的訪問,可以輕松知道每個Section的具體位置。
多個程序頭表項,可看做Elf32_Phdr[e_phnum]數組組成了程序頭表(Programmer Header Table)。FileSiz代表該段的數據在文件中的長度。MemSiz代表該段的數據在內存中所應當占的大小,MemSiz永遠大於等於FileSiz,C語言中未初始化的全局變量可以導致這個現象。
於是理清結構后,這個問題就十分簡單了。程序入口地址是binary,節頭起點和程序頭起點分別為binary+e_shoff和binary+e_phoff。要想得到所有Section的地址,只需要聲明一個Elf32_Shdr*類型的指針變量shdr,地址為shdr -> sh_addr,下一個節頭的訪問只需要shdr++(指針增加一,地址增加一個節頭的大小)。當然了,知道每個節頭的大小,也可以每次都加e_shentsize的偏移量來表示下一個節頭的地址。
Exercise 1.5 補全print.c函數
兩個難點,第一個是針對變長參數列表的分析,第二個是對local help functions功能的分析。
<stdarg.h> 中定義了幾個重要的宏:
typedef char* va_list;
//在調用參數表之前,定義一個va_list 類型的變量
void va_start ( va_list ap, prev_param );
//然后應該對ap 進行初始化,讓它指向可變參數表里面的第一個參數。第一個參數是 ap 本身,第二個參數是在變參表前面緊挨着的一個變量,即“...”之前的那個參數;
type va_arg ( va_list ap, type );
//然后是獲取參數,調用va_arg,它的第一個參數是ap,第二個參數是要獲取的參數的指定類型,然后返回這個指定類型的值,並且把 ap 的位置指向變參表的下一個變量位置;
void va_end ( va_list ap );
//獲取所有的參數之后,我們有必要將這個 ap 指針關掉,以免發生危險,方法是調用 va_end
在print.c函數中,*fmt指向格式化字符串現在分析到的一個字符,而va_arg則一直往后獲取新的一個參數。對*fmt的分析是直接讀字符串,碰到‘%’就進入下一步操作,碰到‘\0'就退出循環。獲取格式的操作十分容易。而坑點主要在第二處代碼補寫的地方,由於%d是有符號十進制數,和其他的不一樣,而在函數聲明
int PrintNum(char * buf, unsigned long u, int base, int negFlag,
int length, int ladjust, char padc, int upcase)
可以注意到第二個參數類型是unsigned long,這表明如果va_arg獲取到了一個負數的話,則需要將其轉化為正數,並將negFlag置1。
體會與感想
啟動過程理解
在學習lab1的過程中,初步看了下整體的文件夾和代碼。這些文件夾的名字取的都十分的恰當。以下是我的薄弱理解:boot用於在vmlinux啟動時做出第一手操作;drivers實際上是和硬件強關聯的,因為printf函數到了底層就靠他往某個地址寫入字符;gxemul是我們的用於仿真的軟件平台,make產生的vmlinux就在這里面;include是一些庫函數吧,有着一部分的c函數庫,還有對寄存器別名的宏定義等;init是內核啟動后經由boot操作結束后的進一步初始化;lib中我們實現了printf函數,可以理解成一個我們自己實現的一個庫文件夾;readelf主要是提供一個工具供我們解析ELF文件,貌似並不是啟動所需的必須成分;tools提供一個鏈接器,讓代碼段和數據段存在於應該在的位置上。
由於GXemul仿真器的作用,我們相當於直接走到啟動了內核這一步。在控制權轉向內核后,start.o文件開始初始化硬件,設置堆棧,並將當前PC跳轉到main函數入口。main函數的地址是在構建vmlinux的過程中,用tools/scse0_3.lds鏈接器鏈接到0x80010000地址上的,在init/main函數中,調用了mips_init函數,而printf函數所使用的庫就在我們寫的lib里面,這樣在啟動vmlinux時,顯示屏就會打出三行語句。這也是為什么如果沒實現1.5的內容是無法看到有效輸出的,或許也是因為如此才無法通過1.3和1.4的評測吧。
總結
