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!!!
引用: