Linux重新学:x86 处理惩罚器怎么举行

杭州匡氏纺织有限公司

杭州匡氏纺织有限公司

  • 首页
  • 业务管理
  • 美容培训中心
  • 行业新闻
  • 服务支持
  • 你的位置:杭州匡氏纺织有限公司 > 服务支持 > Linux重新学:x86 处理惩罚器怎么举行

    Linux重新学:x86 处理惩罚器怎么举行

    发布日期:2022-08-07 21:38    点击次数:106
    实情势:bootloader 为顺序计算段的基地点 呵护情势:bootloader 为本身创立段形貌符 肯定 GDT 的地点 创立代码段的形貌符 创立数据段的形貌符 创立栈段的形貌符 段形貌符是怎么确保段的安好的? 段寄存器高速缓存 对段寄存器本身的呵护 对段边界的查抄

    在上一篇文章中,我们已经顺利的从实情势,过渡到了呵护情势。

    呵护情势与实情势最本质的差别就是:呵护情势应用了全局形貌符表,用来生活生涯每个顺序(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 的调试进程,是否是就行使了这个情理?

     



    栏目分类