gdb中-x是为了实现通过文件的初始化gdb
GAS(gcc)(AT&T 语法),NASM(Intel 语法)
当boot loader 引导操作系统的时候,机器必须有如下的状态:
EAX:
必须包含魔数OX2BADB002,这个值告诉操作系统目前它是由兼容的Multiboot 的boot loader 引导的。
EBX:
必须包含boot loader 提供的多重引导信息结构(见3.3 节多重信息引导结构)的32位物理地址。
CS:
必须是32 位的读/执行的代码段,偏移是0 以及界限是 0XFFFFFFFF。具体值没有定义。
DS
ES
FS
GS
SS:
必须是32 位的读/执行数据段,偏移是0 以及界限是 0XFFFFFFFF。具体值没有定义。
A20 GATE :
必须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!!!
引用: