gdb中-x是爲了實現經過文件的初始化gdb程序員
GAS(gcc)(AT&T 語法),NASM(Intel 語法)數據庫
當boot loader 引導操做系統的時候,機器必須有以下的狀態:數組
EAX:
必須包含魔數OX2BADB002,這個值告訴操做系統目前它是由兼容的Multiboot 的boot loader 引導的。函數
EBX:優化
必須包含boot loader 提供的多重引導信息結構(見3.3 節多重信息引導結構)的32位物理地址。ui
CS:
必須是32 位的讀/執行的代碼段,偏移是0 以及界限是 0XFFFFFFFF。具體值沒有定義。this
DS
ES
FS
GS
SS:
必須是32 位的讀/執行數據段,偏移是0 以及界限是 0XFFFFFFFF。具體值沒有定義。url
A20 GATE :spa
必須enable。操作系統
CR0:
31 位(PG)必須清除,第0 位(PE)必須設置(關係到保護模式)。其餘位沒有定義。
EFLAGS:
第17(VM)位必須清除,第9 位(IF)必須清除,其餘位沒有定義。處理器寄存器的其餘標誌位沒有定義。特別的,這包括
ESP:
若是須要的話,OS Image 能夠包含它本身的堆棧
GDTR:
即便段寄存器按照上面所說的設置,GDTR 這個時候也可能不合法。所以,在它設置本身的GDT 以前,不要load 任何的段寄存器(即便是從新reload 一樣的值)。
IDTR:
OS Image 在設置本身的IDT 以前必須關閉中斷。
引導信息結構:
在進入操做系統前,EBX 寄存器包含多重引導信息結構的物理地址,經過這個結構,Boot Loader 能夠和操做系統交換重要的信息:
multiboot.h
typedef struct multiboot_t { uint32_t flags; // Multiboot 的版本信息 /** * 從 BIOS 獲知的可用內存 * * mem_lower和mem_upper分別指出了低端和高端內存的大小,單位是K。 * 低端內存的首地址是0,高端內存的首地址是1M。 * 低端內存的最大可能值是640K。 * 高端內存的最大可能值是最大值減去1M。但並不保證是這個值。 */ uint32_t mem_lower; uint32_t mem_upper; uint32_t boot_device; // 指出引導程序從哪一個BIOS磁盤設備載入的OS映像 uint32_t cmdline; // 內核命令行 uint32_t mods_count; // boot 模塊列表 uint32_t mods_addr; /** * ELF 格式內核映像的section頭表。 * 包括每項的大小、一共有幾項以及做爲名字索引的字符串表。 */ uint32_t num; uint32_t size; uint32_t addr; uint32_t shndx; /** * 如下兩項指出保存由BIOS提供的內存分佈的緩衝區的地址和長度 * mmap_addr是緩衝區的地址,mmap_length是緩衝區的總大小 * 緩衝區由一個或者多個下面的大小/結構對 mmap_entry_t 組成 */ uint32_t mmap_length; uint32_t mmap_addr; uint32_t drives_length; // 指出第一個驅動器結構的物理地址 uint32_t drives_addr; // 指出第一個驅動器這個結構的大小 uint32_t config_table; // ROM 配置表 uint32_t boot_loader_name; // boot loader 的名字 uint32_t apm_table; // APM 表 uint32_t vbe_control_info; uint32_t vbe_mode_info; uint32_t vbe_mode; uint32_t vbe_interface_seg; uint32_t vbe_interface_off; uint32_t vbe_interface_len; } __attribute__((packed)) multiboot_t; /** * size是相關結構的大小,單位是字節,它可能大於最小值20 * base_addr_low是啓動地址的低32位,base_addr_high是高32位,啓動地址總共有64位 * length_low是內存區域大小的低32位,length_high是內存區域大小的高32位,總共是64位 * type是相應地址區間的類型,1表明可用RAM,全部其它的值表明保留區域 */ typedef struct mmap_entry_t { uint32_t size; // 留意 size 是不含 size 自身變量的大小 uint32_t base_addr_low; uint32_t base_addr_high; uint32_t length_low; uint32_t length_high; uint32_t type; } __attribute__((packed)) mmap_entry_t; // 聲明全局的 multiboot_t * 指針 extern multiboot_t *glb_mboot_ptr;
多重引導信息結構以及它的子結構能夠被Boot Loader 放在內存中的任何地方(固然除了位內核和引導模塊保留的部分)。在操做系統使用完這部分信息以前,操做系統必須保證
存放這個信息的內存不被覆蓋。
1.uint32_t flags 表示了一個操做系統須要或者指望Boot Loader所具備的一些特性。0-15 位表示須要的特性。若是Boot Loader看到這些位中的一位被置,可是由於一些緣由它不知道這些
位的意思,或者由於一些緣由它不能知足這個位所表示的特性這個時候它必須通知用戶而且不引導OS Image。16-31 是可選的特性,若是這些位中一位被設置可是Boot Loader不知道這
些位的做用,Boot Loader能夠簡單地忽略而後正常引導。所以,OS Image中,沒有定義的位的默認值應該0。這個時候,’flags’字段用作版本信息和簡單的特性選擇5。
例如boot.s中:
MBOOT_PAGE_ALIGN equ 1 << 0 ; 0 號位表示全部的引導模塊將按頁(4KB)邊界對齊 (0001) MBOOT_MEM_INFO equ 1 << 1 ; 1 號位表示 Multiboot 信息結構的 mem_* 變量(‘mem_lower’ 以及‘mem_upper’)包含可用內存的信息 (0010) ; (告訴GRUB把內存空間的信息包含在Multiboot信息結構中) ; 定義咱們使用的 Multiboot 的標記 MBOOT_HEADER_FLAGS equ MBOOT_PAGE_ALIGN | MBOOT_MEM_INFO
;0001:MBOOT_PAGE_ALIGN
;0010:MBOOT_MEM_INFO
;MBOOT_HEADER_FLAG包含不少位,其中就有上述二者,咱們將這二者對應的位設成1,即PAGE|MEM(0011)
2.若是’flags’中的第0位被設置,那麼表示 ‘mem *’字段有效。’mem_lower’ 以及‘mem_upper’分別表示lower 和upper 內存的數量,單位是KB。Lower 內存開始於地址0,upper 內存開始於地址1M。lower 內存的最大可能值是640KB。upper 內存返回的值是最大地址減去1M 後的值(可是不保證是這個值)。
3.若是’flags’中的第1 位被設置,那麼 ‘boot_device’字段是有效的,表示boot loader從哪個BIOS磁盤設備引導OS Image。若是OS Image沒有被從BIOS磁盤引導,‘boot_device’字
段必須不存在(第1位必須被清除)。‘boot_device’字段存在與4 個單字節的子字段中:
+-------+-------+-------+-------+
| drive | part1 | part2| part3 |
+-------+-------+-------+-------+
第一個字節表示BIOS INT 13 能理解的BIOS 驅動器號,好比 0X00 表示第一個軟盤或者0X80 表示第一個硬盤。
接下來的三個字節表示了boot 分區。’part 1’ 表示了最高級的分區號,’part 2’表示了高級分區的子分區。分區號一般從0 開始。
4.若是’flags’字段的第2 位被設置,’cmdline’字段有效,而且包含要傳遞給內核的命令行的物理地址。命令行一般是C-style 的以0 結尾的字符串。
5.若是’flags’字段的第3位被設置,’mods’字段表示哪些引導模塊(例如grub.conf中mudule initrid)將要和內核一塊兒引導,而且在哪裏能夠找到它們。’mods_count’包含要引導的引導模塊的數目,’mods_addr’包含了第一個引導模塊結構[module structure]的物理地址,每個引導模塊結構[module structure]的格式爲:
+-------------------+
0 | mod_start |
4 | mod_end |
+-------------------+
8 | string |
+-------------------+
12 | reserved (0) |
+-------------------+
前兩個字段表示引導模塊自己的開始和結束地址,’string’字段指定了一個與特殊的引導模塊相關的字符串,一般說來,這個字符串通常是一個命令行(好比操做系統把引導模塊當作一個可執行程序)或者一個路徑(例如操做系統把引導模塊當作一個文件),它的最精確的用途是由操做系統指定的。’reserved’字段必須被Boot Loader 設置成0,操做系統必須忽略它。
注意 :第4 位和第5 位是互斥的。
6.若是’flags’字段的第4 位被設置,那麼在多重引導信息結構中第28 字節開始的字段是
有效的:
+-------------------+
28 | tabsize |
32 | strsize |
36 | addr |
40 | reserved (0) |
+-------------------+
這表示一個a.out 內核印象中在哪裏能夠找到符號表。’addr’表示a.out 格式中結構數組大小(4 字節無符號long)的物理地址,緊跟在後面的是數組自己,而後是以0 結尾的字符串集合的大小(4 字節無符號long),最後是字符串集合自己。’tabsize’等於它的大小參數(在符號段開始能夠找到),’strsize’等於接下來的字符串表的大小參數(在字符串段的開始能夠找到),字符串表是供符號表參考的。注意即便’flags’的第4 位被設置,’tabsize’也能夠爲0,表示沒有符號
7.若是’flags’字段的第5 位被設置,那麼在多重引導信息結構中第28 字節開始的字段是有效的:
+-------------------+
28 | num |
32 | size |
36 | addr |
40 | shndx |
+-------------------+
這表示一個ELF 內核的section header 表的地址(addr),每個section的大小(size),section 的數目(num)以及做爲名字索引的字符串表(shndx:section header index)。他們對應於ELF 規範中程序頭的’shdr_ *’(’shdr_num’等等)。全部的sections 被load 進來之後,ELF section header 中的物理地址就表明這些sections 在內存中的位置。注意,’flags’字段的第5 位被設置,’shdr_sum’也可能爲0,表示沒有符號。
8.若是’flags’字段的第6 位被設置,那麼’mmap_ *’字段有效,表示一個緩衝區的地址和長度,這個緩衝區含有由BIOS 提供的memory map。’mmap_addr’是地址,’mmap_length’是緩
衝區的總長度。緩衝區中含有一個或者多個下面的size/structure 對。(size 被用來跳到下一個size/structure 對):
+-------------------+
-4 | size |(size 是不含 size 自身變量的大小)
+-------------------+
0 | base_addr_low |
4 | base_addr_high |
8 | length_low |
12 | length_high |
16 | type |
+-------------------+
‘size’是對應的structure 的大小(字節爲單位),最小是20 字節。’base_addr_low’是開始地址的低32 位的,’base_addr_high’是高32 位地址,這樣,開始地址是總共64 位
的。’length_low’是內存區域大小的低32 位,’length_high’是內存區域大小的高32 位,所以,長度總共是64 位。’type’是地址區域的類型,1 表示可用的RAM,其餘值都表示一個保留
的區域。這個map 保證列出全部能夠用在正經常使用途的RAM
9.若是’flags’字段的第7 位被設置,那麼字段’drives_ *’有效,表示第一個drive 結構[drivestructure]的物理地址以及drive 結構的大小。’drives_addr’是地址,’drives_length’是全部drive
結構的總大小。注意,’drives_length’能夠是0。每個drive 結構以下圖所示:
+-------------------+
0 | size |
+-------------------+
4 | drive_number |
+-------------------+
5 | drive_mode |
+-------------------+
6 | drive_cylinders |
8 | drive_heads |
9 | drive_sectors |
+-------------------+
10 – xx | drive_ports |
+-------------------+
‘size’字段表示這個結構的大小。大小與端口的多少有關。注意,大小可能不等於(10+2*端口數)。這主要是因爲對齊(alignment)的緣由。
‘drive_number’字段含有BIOS 的驅動器號。’drive_mode’字段表示boot loader 使用的訪問模式。當前,定義了以下的模式義:
0:CHS 模式。(傳統的 柱面/頭/扇區 地址模式)
1:LBA 模式(邏輯塊地址模式)
10.若是’flags’字段的第8 位被設置,那麼’config_table’字段有效,表示BIOS 調用GET CONFIGURATION 所返回的ROM 配置表的地址,這個表的大小必須爲0。
11.若是’flags’字段的第9 位被設置,’boot_loader_name’字段有效,值爲引導內核的bootloader 名字字符串的的地址。名字字符串是以0 結尾的C-style 字符串
12.若是’flags’字段的第10 位被設置,’apm_table’字段有效,包含有APM 表的物理地址。APM 表結構以下:
+----------------------+
0 | version |
2 | cseg |
4 | offset |
8 | cseg_16 |
10 | dseg |
12 | flags |
14 | cseg_len |
16 | cseg_16_len |
18 | dseg_len |
+----------------------+
字段’version’ ‘cseg’ ‘offset’ ‘cseg_16’ ‘dseg’ ‘flags’ ‘cseg_len’ ‘cseg_16_len’ ‘dseg_len’表示版本號,32 位保護模式的代碼段,entry point的偏移,16 位包含實模式的代碼段,16 位保護
模式的數據段,標誌,32 位保護模式的代碼段長度,16 位保護模式的代碼段長度以及16位保護模式數據庫長度。只有’offset’是4 字節,其餘都是2 字節
13.若是’flags’字段的第11 位被設置,圖形表[graphics table]有效。只有內核在多重引導頭中表示它接受圖形模式的狀況下,這個字段才能被設置。字
段’vbe_control_info’和」vbe_mode_info」分別含有由VBE 00H 功能返回的VBE 控制信息的物理地址和由VBE 01H 功能返回的VBE 模式信息。其餘的字段 ‘vbe_interface_seg’ ‘vbe_interface_off’ 以及」vbe_interface_len」 包含了在VBE 2.0+規範中定義的保護模式接口表。
__attribute__能夠設置函數屬性(Function Attribute)、變量屬性(Variable Attribute)和類型屬性(Type Attribute):
1.__attribute__ format(archetype, string-index, first-to-check):
__attribute__((format(printf,m,n)))
__attribute__((format(scanf,m,n)))
其中參數m與n的含義爲:
m:第幾個參數爲格式化字符串(format string);
n:參數集合中的第一個,即參數「…」裏的第一個參數在函數參數總數排在第幾,注意,有時函數參數裏還有「隱身」的(類成員函數(this指針是默認的))
例子:
本身定義的一個帶有可變參數的函數,其功能相似於printf:
//m=1;n=2
extern void myprint(const char *format,...) __attribute__((format(printf,1,2)));
//m=2;n=3
extern void myprint(int l,const char *format,...) __attribute__((format(printf,2,3)));
//m=3;n=4
extern void myprint(int l,const char *format,...) __attribute__((format(printf,3,4)));
其緣由是,類成員函數的第一個參數實際上一個「隱身」的「this」指針。
2._attribute__ ((noreturn))
該屬性通知編譯器函數從不返回值,當遇到相似函數須要返回值而卻不可能運行到返回值處就已經退出來的狀況,該屬性能夠避免出現錯誤信息,C庫函數中的abort()和exit()的聲明格式就採用了這種格式:
extern void exit(int) __attribute__((noreturn));
extern void abort(void) __attribute__((noreturn));
3._attribute__ ((const))
該屬性只能用於帶有數值類型參數的函數上。當重複調用帶有數值參數的函數時,因爲返回值是相同的,因此此時編譯器能夠進行優化處理,除第一次須要運算外,其它只須要返回第一次的結果就能夠了,進而能夠提升效率。該屬性主要適用於沒有靜態狀態(static state)和反作用的一些函數,而且返回值僅僅依賴輸入的參數
例子:
extern int square(int n) __attribute__((const));
定義:
int square(int n) __attribute__((const)){
for (i = 0; i < 100; i++ )
{
total += square(5) + i;
}
}
經過添加__attribute__((const))聲明,編譯器只調用了函數一次,之後只是直接獲得了相同的一個返回值。事實上,const參數不能用在帶有指針類型參數的函數中,由於該屬性不但影響函數的參數值,一樣也影響到了參數指向的數據,它可能會對代碼自己產生嚴重甚至是不可恢復的嚴重後果。而且,帶有該屬性的函數不能有任何反作用或者是靜態的狀態,因此,相似getchar()或time()的函數是不適合使用該屬性的。
4.__attribute__((aligned (alignment)))
該屬性規定變量或結構體成員的最小的對齊格式,以字節爲單位。例如:
int x __attribute__ ((aligned (16))) = 0; //編譯器將以16字節(注意是字節byte不是位bit)對齊的方式分配一個變量。
建立一個雙字對齊的int對,能夠這麼寫:
struct foo { int x[2] __attribute__ ((aligned (8))); };
5.__attribute__((packed))
要求編譯器不執行字節對齊原則
下面的例子中,my-packed-struct類型的變量數組中的值將會牢牢的靠在一塊兒,但內部的成員變量s不會被「pack」,若是但願內部的成員變量也被packed的話,my-unpacked-struct也須要使用packed進行相應的約束。
struct my_unpacked_struct
{
char c;
int i;
};
struct my_packed_struct
{
char c;
int i;
struct my_unpacked_struct s;
}__attribute__ ((__packed__));
/* * ===================================================================================== * * Filename: elf.h * * Description: ELF 格式的部分定義 * * Version: 1.0 * Created: 2013年11月06日 12時47分12秒 * Revision: none * Compiler: gcc * * Author: Hurley (LiuHuan), liuhuan1992@gmail.com * Company: Class 1107 of Computer Science and Technology * * ===================================================================================== */ #ifndef INCLUDE_ELF_H_ #define INCLUDE_ELF_H_ #include "types.h" #include "multiboot.h" #define ELF32_ST_TYPE(i) ((i)&0xf) // ELF 格式區段頭 typedef struct elf_section_header_t { uint32_t name; uint32_t type; uint32_t flags; uint32_t addr; uint32_t offset; uint32_t size; uint32_t link; uint32_t info; uint32_t addralign; uint32_t entsize; } __attribute__((packed)) elf_section_header_t; // ELF 格式符號 typedef struct elf_symbol_t { uint32_t name; uint32_t value; uint32_t size; uint8_t info; uint8_t other; uint16_t shndx; } __attribute__((packed)) elf_symbol_t; // ELF 信息 typedef struct elf_t { elf_symbol_t *symtab; //符號表(一個符號表(symbol table),它存放在程序中被定義和引用的函數和全局變量的信息。 //一些程序員錯誤地認爲必須經過-g選項來編譯一個程序,獲得符號表信息。實際上,每一個可重定位目標文件 //在.symtab中都有一張符號表。然而,和編譯器中的符號表不一樣,.symtab符號表不包含局部變量的表目。) //debug:一個調試符號表,其有些表目是程序中定義的局部變量和類型定義,有些表目是程序中定義和引用的全局變量,有些是原始的C源文件。只有以-g選項調用編譯驅動程序時,纔會獲得這張表 uint32_t symtabsz; //符號表大小 const char *strtab; //一個字符串表,其內容包括.symtab和.debug節中的符號表,以及節頭部中的節名字。字符串表就是以null結尾的字符串序列。 uint32_t strtabsz; //字符串表大小 } elf_t; // 從 multiboot_t 結構獲取ELF信息 elf_t elf_from_multiboot(multiboot_t *mb); // 查看ELF的符號信息 const char *elf_lookup_symbol(uint32_t addr, elf_t *elf); #endif // INCLUDE_ELF_H_
// 從 multiboot_t 結構獲取ELF信息 //grub會填充multiboot中結構體中內容,而後咱們根據這些數據,來填充elf信息,大概理解到這個就行 elf_t elf_from_multiboot(multiboot_t *mb) { int i; elf_t elf; elf_section_header_t *sh = (elf_section_header_t*)(mb->addr); //sh指向節頭(section .text等等)的數組 uint32_t shstrtab = sh[mb->shndx].addr; //shstrtab爲節頭字符串表 for (i = 0; i < mb->num; i++) { const char *name = (const char *)(shstrtab + sh[i].name);//sh[i].name爲節頭名字 // 在 GRUB 提供的 multiboot 信息中尋找內核 ELF 格式所提取的字符串表和符號表 if (strcmp(name, ".strtab") == 0) { elf.strtab = (const char *)sh[i].addr; elf.strtabsz = sh[i].size; } if (strcmp(name, ".symtab") == 0) { elf.symtab = (elf_symbol_t*)sh[i].addr; elf.symtabsz = sh[i].size; } } return elf; } // 查看ELF的符號信息 const char *elf_lookup_symbol(uint32_t addr, elf_t *elf) { int i; for (i = 0; i < (elf->symtabsz / sizeof(elf_symbol_t)); i++) { if (ELF32_ST_TYPE(elf->symtab[i].info) != 0x2) { continue; } // 經過函數調用地址查到函數的名字(地址在該函數的代碼段地址區間以內) if ( (addr >= elf->symtab[i].value) && (addr < (elf->symtab[i].value + elf->symtab[i].size)) ) { return (const char *)((uint32_t)elf->strtab + elf->symtab[i].name); } } return NULL; }
Done!!!
引用: