在上一篇著述中胖白系列,咱们依然告成的从实模式,过渡到了保护模式。
保护模式与实模式最本色的折柳便是:保护模式使用了全局相貌符表,用来保存每一个步伐(bootloader,操作系统,应用步伐)使用到的每个段信息:初始地址,长度,以过火他一些保护参数。
这篇著述,咱们来看一下 bootloader 是怎样来进行自我进化到保护模式的,然后潜入看一下保护模式是怎样对内存进行安全保护的。
作为布景常识,咱们先来看一下 x86 中的地址变换历程:
x86 处理器中的分页机制是不错被关闭的,此时线性地址就等于物理地址,这亦然咱们一直扣问的情况。
下一篇著述,咱们就把 x86 中的分页机制翻开,并与 Linux 中的分段和分页机制进行对比。
实模式:bootloader 为步伐估量段的基地址在之前的著述:Linux重新学06:16张结构图,透顶相识【代码重定位】的底层旨趣中,咱们扣问了 bootloader 是怎样把应用步伐读取到内存中,临了跳入到步伐的进口地址的。
这里所说的步伐,不错是操作系统,也不错是应用步伐。
底下这张图,是步伐被加载到内存中之后,header 中的信息:
因为步伐是被 bootloader 动态读取到内存中的,它是不知说念我方被放在内存中的什么位置,因此它也不知说念我方代码段、数据段、栈的初始地址。
关联词,步伐要念念大约平素奉行,就必须要知说念这些信息,那奈何办?
只须 bootloader 才调经管问题,因为是它来把步伐从硬盘加载到内存中的。
因此,bootloader 在跳入步伐的进口地址之前,必须把其中的代码段、数据段、栈段的基地址估量出来,然后写入到步伐的 header 中,如下图所示:
这么的话,步伐初始奉行时,就不错从我方的 header 中得回到这 3 个段基地址,何况赋值给相应的寄存器,从而告成的奉行步伐。
也便是说:步伐的 header 空间,充任了 bootloader 与它进行信拒却互的序言,用来传递 3 个段寄存器的基地址。
以上的这个历程,一直使命在实模式,因此就莫得段相貌符什么事情。
在以后著述中,咱们还会看到在保护模式下,bootloader 仍然会愚弄 OS 的 header 空间,来传递段的索引号。然后 OS 愚弄这个段索引号,去查找 GDT 表,从而找到每一个段的基地址以过火他一些保护信息。
保护模式:bootloader 为我方创建段相貌符bootloader 从 BIOS 接受系统之后,刚初始是运行在实模式下的。
当它完成一些准备使命之后,就不错参加保护模式了,也便是把 CR0 寄存器的 bit0 成就为 1。
这个准备使命中,最舛误的便是:诞生 GDT 这个表,何况把 GDT 的初始地址,存储到寄存器 GDTR 中。
底下这张图,是 bootloader 被加载到内存中的布局图:
bootloader 被加载到 0x0000_7C00 地址处胖白系列。
它最少需要创建 3 个段相貌符:代码段、数据段和栈段。
详情 GDT 的地址在创建段相貌符之前,需要先详情: 把 GDT 表放在内存中的什么位置?
暂且就把它放在 0x0001_0000 这个地址吧,距离零地址 64K 的位置。
按照处理器的条目,在第 1 个表项(称之为 item 或者 entry,每本书上都不同样)必须为空相貌符(index = 0)。
创建代码段相貌符bootloader 的代码放在 0x0000_7C00 初始的地址,长度是 512B。
笔据这些信息,就不错构造出代码段的相貌符了:
创建数据段相貌符bootloader 待会需要把操作系统或其他应用步伐,从硬盘读取到内存中,举例:读取到 0x0002_0000 的位置。
那么 bootloader 就必须大约探问到这个位置,何况是以数据段的读写模式。
为了愚弄沿途的 4G 内存空间,bootloader 不错把这 4G 空间,作为一个数据段来界说它的相貌符,如下:
创建栈段相貌符表面上,bootloader 不错使用内存中的随性一块优游空间,来作为我方的栈。
因为栈在 push 操作的技能,是向低地址地方增长的。
因此好多册本都会把栈顶基地址成就为 bootloader 的初始地址,也便是 0x0000_7C00 地址处,何况把栈的空间大小截止在 4K 的限制。
笔据以上这些信息,就不错创建出栈的段相貌符,如下:
当以上这几个段的相貌符都创建好之后,就不错把 GDT 的地址(0x0001_0000),成就到 GDTR 寄存器中了。
临了,再把 CR0 寄存器的 bit0 成就为 1,就庄重的参加保护模式来奉行 bootloader 中背面的代码了。
段相貌符是怎样确保段的安全探问的? 段寄存器高速缓存参加保护模式之后,诚然对段寄存器中内容的评释调动了,关联词奉行每一条领导,如故需要使用到这些段寄存器的: cs, ds, ss等等。
念念象一下:每奉行一条领导,都会从逻辑地址中,得回到段索引号,然后去查找 GDT 表,从而定位到段的基地址。
大家都知说念步伐有个“局部性”旨趣,也便是王人集奉行的代码,都是王人集在一段王人集的步伐空间中的。
糗百-成人版这个王人集的步伐空间,它们都是在吞并个代码段中,因此段的基地址都是换取的,那么它们都属于 GDT 中吞并个代码段相貌符所代表的段空间。
要是每一条领导都去查表,就会影响到步伐的奉行成果。
是以,处理器里面就为每一个段寄存器,安排了一个高速缓存。
拿代码段寄存器 cs 来说:当奉行一条领导的技能,要是它与上一条领导中的段索引号不同,才会笔据新的段索引号到 GDT 中查找相应的段相貌符表项。
查找到之后,就把这个表项的内容复制到 cs 寄存器的高速缓存中。
当不时奉行背面的领导时,要是逻辑地址中的段索引号莫得变化,处理器就径直从高速缓存中读取段相貌,从而幸免了查表操作,晋升了系统成果。
对段寄存器自己的保护当逻辑地址中段寄存器的索引号调动时,就会笔据新的索引号,到 GDT 中去查表。
天然了,这个索引号不可逾越 GDT 的边界。
当定位到某一个相貌符表项之后,就初始进行一系列查验。
再来看一下每一个段相貌符中 8 个字节的内容:
bit8 ~ bit11 界说了面前这个段的类型。
假如: 咱们在切换代码段空间的技能,不防备犯错,定位到了 GDT 中的一个数据段相貌符表项,那么处理器就大约实时发现:
“面前这个段相貌符的类型是数据段,你却把它算作念代码段来使用,碎裂,杀无赦!”
因此,处理器就会推辞把这个段相貌符复制到代码段的高速缓存中,从而对代码段寄存器进行了保护。
对段边界的查验在通过了第一层的段类型保护之后,还会不时对段的边界进行查验,这就要使用到逻辑地址中的偏移地址( EIP )了。
要是偏移地址逾越了相貌符中法例的边界,那么就证据发生作假了。
举例:在 bootloader 的代码段相貌符中,最大的边界是 512B,要是把 EIP 成就为 0x0000_1000,那就笃信作假了。
因为这个地址根底就不属于代码段的空间限制。
关于数据段来说相比故酷好,因为咱们把数据段相貌符的基地址成就为 0x0000_0000,段的边界是统共 4G 的空间,是以它不错对统共内存进行操作。
多念念一步:
代码段亦然属于这 4G 空间,因此不错通过数据段,来改写代码段空间中的领导内容。
也便是说:要是你念念修改代码段的领导,径直通过代码段来操作是不不错的。
因为代码段相貌符中法例了:代码段的内容只可被读取、奉行,关联词不可被写入。
此时,就不错匠心独具:代码段也放在 4G 的空间,那么就不错通过数据段的可写特色,来改写代码段中的领导。
胖白系列
念念一念念 gdb 的调试历程,是不是就愚弄了这个意旨?