内存与调试-内存管理(4)扩内存之覆盖、交换、虚拟

内存扩充技术有三种:
内存覆盖(Overlay),内存交换(Swapping),内存虚拟。

对系统内存不够用采取的措施:
(1) 早期,微软的DOS,内存仅640K,程序大—–手动覆盖(overlay),把需要的指令和数据保存在内存中 。
(2) 程序多,自动交换技术(swapping),暂时不能执行的程序送到外存,代价大 。【从之前的程序员管理变成了操作系统自动管理】
(3) 以更小的页粒度单位在有限的内存中装入更多更大的程序,采用自动的虚拟存储技术

一、内存覆盖

早期的计算机系统中,主存容量小,虽然主存中仅存放一道用户程序,但是存储空间放不下用户进程的现象也经常发生,这一矛盾可以用覆盖基础来解决。

覆盖的基本思想是:由于程序运行时并非任何时候都要访问程序及数据的各个部分(尤其是大程序),因此可以把用户空间分成一个固定区和若干覆盖区。将经常活跃的部分放在固定区。其余部分按调用关系分段。

覆盖技术的实现是把程序划分为若干个功能上相对独立的程序段(又叫覆盖),按照其自身的逻辑结构使那些不会同时运行的程序段(覆盖)共享同一块内存区域(覆盖区)。其大小应由覆盖区中的最大覆盖来确定。

程序段先保存在磁盘上,程序运行时首先将那些即将要访问的段放入覆盖区,其他段放在外存中,在需要调用前,系统将其调入覆盖区,替换覆盖区中原有的段。

覆盖技术的特点是打破了必须将一个进程的全部信息装入主存后才能运行的限制,但当同时运行程序的代码量大于主存时仍不能运行。


例如,一个用户程序由6个模块组成,下图给出了各个模块的调用关系,Main模块是一个独立的段,其调用A和B模块,A和B是互斥被调用的两个模块。在A模块执行过程中,调用C模块;而在B模块执行过程中,它可能调用D或E模块(D和E模块也是互斥被调用的)。为该用户程序建立的覆盖结构如下:Main模块是常驻段,其余部分组成两个覆盖段。

由以上推理可知,A和B模块组成覆盖段1,C、D和E组成覆盖段2。为了实现真正覆盖,相应的覆盖区应为每个覆盖段中最大覆盖的大小。

二、内存交换

交换(对换)的基本思想是,把处于等待状态(或在CPU调度原则下被剥夺运行权利)的程序从内存移到辅存,把内存空间腾出来,这个过程叫做换出。把准备好竞争CPU运行的程序从辅存移到内存,这个过程又称为换入中级调度(策略)就是釆用交换技术。

例如,有一个CPU采用时间片轮转调度算法的多道程序环境。时间片到,内存管理器将刚刚执行的进程换出,将另一进程换入到 刚刚释放的内存空间中。同时,CPU调度器可以将时间片分配给其他已在内存中的进程。每个进程用完时间片都与另一进程交换。理想情况下,内存管理的交换过程速度足够快,总有进程在内存中可以执行。 有些交换需要注意一下几个问题:

  • 交换需要备份存储,通常是快速磁盘。它必须足够大,并且提供对这些内存映像的直接访问。
  • 为了有效使用使用CPU,需要每个进程的执行时间比交换时间长,而影响交换时间的主要是转移时间。转移时间与所交换的内存空间成正比。
  • 如果换出进程,必须保证该进程是完全处于空闲状态。 交换空间通常作为磁盘的一整块,且独立于文件系统,因此使用就可能很快。
  • 交换通常在有很多进程运行且内存空间吃紧时开始 启动,而系统负荷降低就暂停。
  • 普通的交换使用不多,但交换策略的某些变种在许多系统中(如UNIX系统)仍发挥作用。

1)对换的引入

在多道程序环境下,一方面,在内存中的某些进程由于某事件尚未发生而被阻塞运行,但它却占用了大量的内存空间,甚至有时可能出现在内存中所有进程都被阻塞而迫使CPU停止下来等待的情况。另一方面,却又有着许多作业在外存等待,因无内存而不能进入内存运行的情况。显然这是对系统资源的一种严重浪费,使系统吞吐量下降,于是增设了对换(交换)设施。

—– 所谓“对换”,是指把内存中暂时不能运行的进程或者暂时不用的程序和数据调出到外存上,以便腾出足够的内存空间,再把已具备运行条件的进程调入内存。

—– 如果对换是以整个进程为单位的,便称之为“整体对换”或“进程对换”。解决内存紧张的问题。

—– 如果对换是以“页“或”段“为单位进行的,则分别称之为”页面对换“或”分段对换“。为了实现进程对换,系统必须能实现3个方面的功能:对换空间的管理、进程的换出、进程的换入。

2)对换空间的管理

在具有对换功能的OS中,通常把外存分为文件区对换区。前者用于存放文件,后者用于存放从内存换出的进程。对换区采用的是连续分配的方式(考虑到对换的速度)。

3)进程的换出与换入

换出:每当一进程由于创建子进程而需要更多的内存空间,但又无足够的内存空间等情况发生时,系统应将某进程换出。系统首先选择处于阻塞状态优先级最低的进程作为换出进程,然后启动磁盘,将该进程的程序和数据传送到磁盘的对换区。若传送过程未出现错误,便可回收该进程所占用的内存空间,并对该进程的进程控制块做相应的修改。

换入:把外存交换区中的数据和程序换到内存中。系统应定时的查看所有进程的状态,从中找出”就绪“状态但已换出的进程。将其中换出时间最久(换出到磁盘上)的进程作为换入进程,将之换入。直至已无可换入的进程或无可换出的进程为止。

交换的特点是打破了一个程序一旦进入主存便一直运行到结束的限制,但运行的进程大小仍受实际主存的限制。

三、内存覆盖与内存交换的区别

与覆盖技术相比,交换不要求程序员给出程序段之间的覆盖结构,而且交换主要是在进程或作业之间进行;而覆盖则主要在同一个作业或进程中进行。另外,覆盖只能覆盖与覆盖程序段无关的程序段覆盖技术则已成为历史;而交换技术在现代操作系统中仍具有较强的生命力。

四、内存虚拟

虚拟内存理论基础:

局部性原理(principle of locality)程序在执行过程的一个较短时期,所执行的指令的地址,指令的操作数地址都局限于一定区域。

  • 时间局部性:一条指令的一次执行和下次执行,一个数据的一次访问和下次访问都在较短时间内;
  • 空间局部性:当前指令和邻近的几条指令,当前访问的数据和邻近的几个数据都集中在一个较小区域内。

原理表明理论上虚存可以实现。程序只有一小部分在内存上,大部分在硬盘上,os在MMU帮助下完成。

虚存机制:

在分页、分段内存管理的硬件支持下,在装入程序时,只把当前需要执行的部分页或段装入内存,就可以开始执行;
当执行到指令或数据不在内存上时(缺页、缺段异常),由处理器通知操作系统,若有空余空间则将相应的页面或段调入内存,继续执行;
另一方面,os将内存中暂时不用的页、段调出保存在外存上以腾出空间。

虚存技术基本特征:

大的用户空间:内存可以小,硬盘必须足够。提供给用户的虚拟空间=物理内存+硬盘。
部分交换:swap in /swap out 是对部分虚拟地址空间进行的
不连续:物理内存分配的不连续,虚拟空间使用的不连续(内外存)

虚拟内存具体实现:

多采用虚拟页式内存管理,增加了请求调页和页面置换功能。只装入部分页面即可启动程序,如果要运行的程序和数据不在内存即页表某项invalid,则会抛出异常,向系统发出缺页中断请求,OS根据产生异常的地址找到对应在外存中的页面调入,使得继续运行。

 


参考:

https://blog.csdn.net/dongyanxia1000/article/details/51425141

https://cloud.tencent.com/developer/article/1194832

https://blog.csdn.net/github_36487770/article/details/54934919

内存与调试-内存管理(3)用户空间和内核空间

X86地址空间分布

linux物理地址空间布局:

物理地址空间的顶部以下一段空间,被PCI设备的I/O内存映射占据,它们的大小和布局由PCI规范所决定。640K~1M这段地址空间被BIOS和VGA适配器所占据。

  Linux系统在初始化时,会根据实际的物理内存的大小,为每个物理页面创建一个page对象,所有的page对象构成一个mem_map数组。

进一步,针对不同的用途,Linux内核将所有的物理页面划分到3类内存管理区中,如图,分别为ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM。

  ZONE_DMA的范围是0~16M,该区域的物理页面专门供I/O设备的DMA使用。之所以需要单独管理DMA的物理页面,是因为DMA使用物理地址访问内存,不经过MMU,并且需要连续的缓冲区,所以为了能够提供物理上连续的缓冲区,必须从物理地址空间专门划分一段区域用于DMA。

  ZONE_NORMAL的范围是16M~896M,该区域的物理页面是内核能够直接使用的。

  ZONE_HIGHMEM的范围是896M~结束,该区域即为高端内存,内核不能直接使用。

linux虚拟地址

内核空间分布

在kernel image下面有16M的内核空间用于DMA操作。位于内核空间高端的128M地址主要由3部分组成,分别为vmalloc area,持久化内核映射区,临时内核映射区。

由于ZONE_NORMAL和内核线性空间存在直接映射关系,所以内核会将频繁使用的数据如kernel代码、GDT、IDT、PGD、mem_map数组等放在ZONE_NORMAL里。而将用户数据、页表(PT)等不常用数据放在ZONE_ HIGHMEM里,只在要访问这些数据时才建立映射关系(kmap())。比如,当内核要访问I/O设备存储空间时,就使用ioremap()将位于物理地址高端的mmio区内存映射到内核空间的vmalloc area中,在使用完之后便断开映射关系。 上面描述默认都是32位的机器,对于64位的机器,PAGE_OFFSET为0x0xffff880000000000,用户地址空间范围:0x0000000000000000 – 0x00007fffffffffff,内核代码地址空间:0xffffffff80000000 – 0xffffffffa0000000。

大白话:内核的线性地址 – PAGE_OFFSET = 内核的物理地址

用户空间分布

用户进程的代码区一般从虚拟地址空间的0x08048000开始,这是为了便于检查空指针。代码区之上便是数据区,未初始化数据区,堆区,栈区,以及参数、全局环境变量。

 linux虚拟地址与物理地址映射的关系

Linux将4G的线性地址空间分为2部分,0~3G为user space,3G~4G为kernel space。

  由于开启了分页机制,内核想要访问物理地址空间的话,必须先建立映射关系,然后通过虚拟地址来访问。为了能够访问所有的物理地址空间,就要将全部物理地址空间映射到1G的内核线性空间中,这显然不可能。于是,内核将0~896M的物理地址空间一对一映射到自己的线性地址空间中,这样它便可以随时访问ZONE_DMA和ZONE_NORMAL里的物理页面;此时内核剩下的128M线性地址空间不足以完全映射所有的ZONE_HIGHMEM,Linux采取了动态映射的方法,即按需的将ZONE_HIGHMEM里的物理页面映射到kernel space的最后128M线性地址空间里,使用完之后释放映射关系,以供其它物理页面映射。虽然这样存在效率的问题,但是内核毕竟可以正常的访问所有的物理地址空间了。

linux中可执行程序与虚拟地址空间的映射关系

虚拟内存区域(VMA,Virtual Memory Area)是Linux中进程虚拟地址空间中的一个段,在Windows里面叫虚拟段。当操作系统创建线程后,会在进程相应的数据结构中设置一个.text段的VMA,它在虚拟空间中的地址为0x08048000~0x08049000,它对应ELF文件中的偏移为0的.text。可以查看操作系统为运行的进程维护的信息

从上面的图可以看出,虚拟空间地址为0x08048000~0x08049000的VMA映射为elf文件中的一个段(segment),并且是按整页进行映射的。

  由于linux下的ELF可执行文件会有很多个段(section),所以如果把每个section都映射为一个VMA,那么没有一个页大小的段(section)也会被映射为一个页的VMA,这样就浪费了物理空间,由于不足会用0补充。故elf有一个装载的段(segment),与前面的段(section)不同,前面的段(section)主要用于链接,而段(segment)主要用于装载进内存。

可以看出段(segment)02包含了很多的段(section),那链接器怎样将段(section)合并到一个段(segment)中的呢?可以通过段(section)的权限来合并,如以代码段为代表的权限为可读可执行权限;以数据段和BSS段为代表的权限为可读可写的段;以只读数据为代表的权限为只读权限。

  ELF与Linux进程虚拟空间映射关系如下图所示:

  即使把多个段(section)合并到几个段(segment),每个段(segment)还是又很能产生较大的页内碎片,怎样解决这个问题呢?Unix巧妙的通过各个段(segment)接壤部分共享一个物理页来解决这个问题。

high memory 和 low memory

高内存区域和低内存区域,是针对物理地址空间来说的。

在32位系统中,运行 命令可以看到

high  total 即 high memory   ; low total 即 low memory

cat /proc/meminfo

total: used: free: shared: buffers: cached: 
Mem: 1049051136 765775872 283275264 0 158441472 376979456 
Swap: 1036115968 0 1036115968 
MemTotal: 1024464 kB 
MemFree: 276636 kB 
MemShared: 0 kB 
Buffers: 154728 kB 
Cached: 368144 kB 
... 
HighTotal: 131072 kB 
HighFree: 3064 kB 
LowTotal: 893392 kB 
LowFree: 273572 kB

在32位的linux系统上,CPU 最大能寻址是4GB内存。
内存被进一步划分成被内核直接映射的 Low memory (or Normal memory),和内核间接映射的 High memory

– The kernel itself (including its active modules, e.g. Check Point kernel modules) can only make use of LOW memory.
– User processes on the system (anything that is not kernel itself) can potentially make use of LOW and HIGH memory.
在64位系统中,因为有足够的虚拟地址空间,Low memory equals  Total memory. When running  cat /proc/meminfo command on the module, HighTotal and HighFree will always be zero:
# cat /proc/meminfo
MemTotal:      3591768 kB
MemFree:        135968 kB
Buffers:        378312 kB
Cached:        1754432 kB
SwapCached:         68 kB
Active:         965532 kB
Inactive:      2080476 kB
HighTotal:           0 kB
HighFree:            0 kB

实际上,我在现在的电脑上测试,发现没有 HighTotal 和HighFree 显示项了。

512M的内存空间划分

来自:https://stackoverflow.com/questions/4528568/how-does-the-linux-kernel-manage-less-than-1gb-physical-memory

Not all virtual (linear) addresses must be mapped to anything. If the code accesses unmapped page, the page fault is risen.

The physical page can be mapped to several virtual addresses simultaneously.

In the 4 GB virtual memory there are 2 sections: 0x0… 0xbfffffff – is process virtual memory and 0xc0000000 .. 0xffffffff is a kernel virtual memory.

  • How can the kernel map 896 MB from only 512 MB ?

It maps up to 896 MB. So, if you have only 512, there will be only 512 MB mapped.

If your physical memory is in 0x00000000 to 0x20000000, it will be mapped for direct kernel access to virtual addresses 0xC0000000 to 0xE0000000 (linear mapping).

  • What about user mode processes in this situation?

Phys memory for user processes will be mapped (not sequentially but rather random page-to-page mapping) to virtual addresses 0x0 …. 0xc0000000. This mapping will be the second mapping for pages from 0..896MB. The pages will be taken from free page lists.

  • Where are user mode processes in phys RAM?

Anywhere.

  • Every article explains only the situation, when you’ve installed 4 GB of memory and the

No. Every article explains how 4 Gb of virtual address space is mapped. The size of virtual memory is always 4 GB (for 32-bit machine without memory extensions like PAE/PSE/etc for x86)

As stated in 8.1.3. Memory Zones of the book Linux Kernel Development by Robert Love (I use third edition), there are several zones of physical memory:

  • ZONE_DMA – Contains page frames of memory below 16 MB
  • ZONE_NORMAL – Contains page frames of memory at and above 16 MB and below 896 MB
  • ZONE_HIGHMEM – Contains page frames of memory at and above 896 MB

So, if you have 512 MB, your ZONE_HIGHMEM will be empty, and ZONE_NORMAL will have 496 MB of physical memory mapped.

Also, take a look to 2.5.5.2. Final kernel Page Table when RAM size is less than 896 MBsection of the book. It is about case, when you have less memory than 896 MB.

Also, for ARM there is some description of virtual memory layout: http://www.mjmwired.net/kernel/Documentation/arm/memory.txt

The line 63 PAGE_OFFSET high_memory-1 is the direct mapped part of memory

X64地址空间分布

The 64-bit x86 virtual memory map splits the address space into two: the lower section (with the top bit set to 0) is user-space, the upper section (with the top bit set to 1) is kernel-space. (Note that x86-64 defines “canonical” “lower half” and “higher half” addresses, with a number of bits effectively limited to 48 or 56; see Wikipedia for details.)

The complete map is documented in detail in the kernel; currently it looks like

===========================================================================================
    Start addr    |   Offset   |     End addr     |  Size   | VM area description
===========================================================================================
                  |            |                  |         |
 0000000000000000 |    0       | 00007fffffffffff |  128 TB | user-space virtual memory
__________________|____________|__________________|_________|______________________________
                  |            |                  |         |
 0000800000000000 | +128    TB | ffff7fffffffffff | ~16M TB | non-canonical
__________________|____________|__________________|_________|______________________________
                  |            |                  |         |
 ffff800000000000 | -128    TB | ffffffffffffffff |  128 TB | kernel-space virtual memory
__________________|____________|__________________|_________|______________________________

实际上,

  • the kernel’s memory are those high addresses where the 17 high-order bits are 1
  • user space memory are low memory, where the high-order bits are 0

 

参考:
https://www.cnblogs.com/chengxuyuancc/archive/2013/04/17/3026920.html
https://unix.stackexchange.com/questions/509607/how-a-64-bit-process-virtual-address-space-is-divided-in-linux

https://stackoverflow.com/questions/4528568/how-does-the-linux-kernel-manage-less-than-1gb-physical-memory

https://support.symantec.com/us/en/article.tech244351.html

http://www.cnblogs.com/zszmhd/archive/2012/08/29/2661461.html、深入理解linux内核、Linux Core Kernel Commentrary、程序员的自我修养。

内存与调试-内存管理(1)实/保护模式与分段/分页管理

备注:维基百科下的一张long mode图片,在这篇文章中引用了两次,以后重新编辑时,注意一下。

一、逻辑/虚拟/线性/物理地址

  • 逻辑地址就是 CS:IP
  • 线性地址(也叫虚拟地址):由逻辑地址变化而来
  • 物理地址:就是真正的内存地址,由线性地址变化而来

CS是段寄存器,IP是程序计数器也叫指令指针。
CS对应硬盘上程序文件内的代码段,数据段到内存的映射,IP就是程序文件的段内偏移。当然CS也包括程序运行时产生的堆栈段等。

程序文件头中的段重定位表,它可以包含多个数据段和代码段。程序加载到内存时:
1、8086前的CPU比如8080,没有实模式,直接CS:IP 计算出物理地址。
2、8086开始的CPU,有了实模式,即CS左移4位+IP段内偏移=物理地址。实模式下程序的物理内存地址由CS:IP指定,操作系统无法管理内存。
3、80286开始出现保护模式,操作系统加载程序后,填写到段描述表中(GDT,LDT),并将信息写回到程序内存中的段重定位表中,听说是这样的,到时再考证一下。

3.1.假如定义了多个代码段或者数据段,同一类型段的不同段中切换时,譬如data段1切换到data段2时,对DS的重新赋值,一般情况下是直接从应用程序头段中获取定义的重定位表项地址中取得。

3.2.应用程序在刚获取cpu执行权限时,首先设置堆栈空间SS\SP\DS\ES等寄存器,在设置SS\SP时应该先屏蔽中断,否则在设置SS和SP之间产生中断是个极其恐怖的事,因为有的设计中处理中断也是在当前堆栈空间中进行的。 譬如: 19. 19 00000010 [00000000]code_2_segment dd section.code_2.start ;[0x10] //从该地址中存放的内容,取出来放到CS和IP中。code_2_segment 本身是一个数据段定义。 参考资料:《X86汇编语言:从实模式到保护模式》【这个带考证是否实模式和保护模式都适用】

分段管理 = 逻辑地址变成线性地址的过程
分页管理 = 线性地址变成物理地址的过程

  • 8086CPU 之前,没有分段管理,逻辑地址计算后变成物理地址=线性地址
  • 8086CPU中,开始出现分段管理,线性地址 = 物理地址
  • 80386CPU中,开始出现分页管理,所以线性地址≠物理地址

补充说明:
8080是8位微处理器,数据总线为8位,地址总线为16位。
8086是首枚16位微处理器,地址总线20位,数据总线16位,为了扩大内存寻址范围,才采用分段管理的方式。【补充常识:8088CPU内部数据总线16位,外部数据总线8位,仍算16位处理器】

有一个问题,8080没有采用分段,怎么解决寻址呢?

在微处理器的历史上,第一款微处理器芯片 4004 是由 Intel 推出的,那是一个 4 位 的微处理器。在 4004 之后,Intel 推出了一款 8 位处理器 8080,它有 1 个主累加器(寄存 器 A)和 6 个次累加器(寄存器 B,C,D,E,H 和 L),几个次累加器可以配对(如组成 BC, DE 或 HL)用来访问 16 位的内存地址,也就是说 8080 可访问到 64K 内的地址空间。另外,那时还没有段的概念,访问内存都要通过绝对地址, 因此程序中的地址必须进行硬编码(给出具体地址),而且也难以重定位,这就不难理 解为什么当时的软件大都是些可控性弱,结构简陋,数据处理量小的工控程序了。

二、实模式与保护模式

x86 CPU有实模式、保持模式、虚拟8086模式、系统管理模式等的分别。 x86 CPU只有在启动的时候才能进入实模式,一旦切换到保持模式就无法退出回到实模式。

80186 CPU使用的还是8086的内核,所以还是实模式,仍采用分段管理。
80286 CPU才开始出现保护模式,改进版的分段管理。
80386 CPU开始支持内存分页管理。

8086CPU(16位数据线,20位地址性)采用分段管理是为了扩大内存寻址范围。
80286CPU改进分段管理,是为了实现保护模式。

显然实模式≠分段管理,保护模式 ≠ 分页管理,那什么叫保护模式,什么叫实模式?

简单的讲,实模式就是8086CPU使用的模式。当然那时还没有实模式的叫法。只是后面出现了保护模式,所以旧的模式就叫实模式了。实模式即实际地址模式,用户写的代码中CS与IP寄存器组合成的地址即为实际的物理内存地址。

1、实模式简介:

  •  16位寄存器
  •  20位地址线,可访问1MB内存
  •  通过CS/DS寄存器左移4位+IP寄存器的值生成20位访问地址

1M内存空间:包括了 bios-rom,引导系统,外设显存等等,需要继续,总结,整理,1M内存各事务的空间占用分布。。。

这里有两个问题值得注意:

1. CS<<4 + IP理论上来讲,最大可以表示的数值是0xFFFF0 + 0xFFFF = 0x10FFEF,即大约1M+64KB-16Bytes,然而由于地址线只有20根(A0~A19),这个地址最前面的1无法被表示,当CS=0xFFFF时,实际访问的地址0x10FFEF就变成了0xFFEF。即地址由卷回了0地址到64KB-16Bytes处。

这个问题引出了80286的A20 Gate

2. 由于程序可以任意修改当前的CS/DS值,所有程序可以使用全部1MB的内存,所以这个CPU几乎没有办法有效地支持多任务,因为两个程序一起运行的话很容易互相踩到内存。所以当时的使用的方式系统中同时运行的只有一个应用程序和一个DOS操作系统。操作系统和应用规定了各自能使用的内存地址范围,比如说DOS只使用高64KB的内存,其它的内存给应用程序使用。这样就可以互不影响。要想运行另一个应用程序必须先退出当前运行的应用程序。

这个问题引出了保护模式,保护模式的初衷就是保护一个进程的内存不被其他进程非法访问。

2、保护模式:

保护模式的目的【内存分配的控制权由程序指定变成系统分配

1、保护一个进程的内存不被其他进程非法访问
2、方便实现多任务系统

80286的保护模式

在80286中,段式访存得到的改进,原来段寄存器+IP得到的地址不在是实际的物理地址,而是要经过一个转换层转换才变成一个物理地址。CS里面的内存不再是20位物理地址的高16位,而变成了段描述符表中的索引。

特别提醒:图中的段选择子(selector),作为索引index,查找GDT或LDT时,需要✖️8,因为段描述符(Descriptor)是64位的,占8个字节。

如图所示: 原来16位的段寄存器改名叫段选择子。现在是如何工作的,如何控制一个程序可以访问的内存范围呢?

答案: DOS系统在启动用户应用程序时先在内存中设置两个表,一个叫全局描述符表,GDT,一个叫局部描述符表LDT。表中的每个条目实际代表一段内存地址,包含这段内存的启始地址和段的长度,即base和limit。一个表的多个条目即代码这个用户程序可以访问的多块内存。

DOS系统把这两个表的内存启始地址写到LDTR和GDTR两个寄存器里。并在CS、DS寄存器中设置好一个初始的索引。

应用程序在访问内存时,CS:IP组合称为逻辑地址,CPU拿CS中的3~15位所组成的数据作为索引去LDT或GDT中找到一个描述符,拿这个描述符的base + IP做为实际的物理内存地址去访存。LDT和GDT只有DOS操作系统才能修改,因而应用程序只能访问DOS为它设置好的内存范围,而不能随意访问全部物理内存。

应用程序内存不够用时,需要调用一些系统调用,让DOS分配一段内存,把将这段内存的base, limit做成一个描述符加入到GDT或LDT中。

应用程序如果胡乱修改CS,造成无法索引到一个有效的段描述符就会发一个“段错误, segmentation fault”。

如图所示,在80286中,CS/DS/ES/FS寄存器的意义发生了变化。它的3~15位是描述符表的索引。TI用来表示索引的是哪一张表,是GDT还是LDT。RPL称为请求权限级别。RPL对应描述符中的DPL,只要RPL的级别高于(值小于)DPL时,才有权限访问这段内存。 描述符中的DPL如下图所示(都是64位的占8个字节,故计算偏移量需要index*8):

80286的多任务

借助段描述符表,系统可以为应用程序分配内存,并且限制用户对内存的访问。这时候,多任务的方式汇总如下图:

基于这种内存管理方式,用户应用程序可以实现动态链接。比如说一个程序分为代码段、数据段、零初始化段等,它依赖的库也是分段的,系统在加载程序时,只需为每个段分配一段内存,并为每个段设置一个描述符即可。 每个段的起始地址可以在加载时根据实际情况修改。

代码段设置成只读只需要其描述符的TYPE字段即可。

三、IA32介绍

从 80386 以后,Intel 的 CPU 经历了 80486、Pentium、PentiumII、PentiumIII 等型号,虽然 它们在速度上提高了好几个数量级,功能上也有不少改进,但基本上属于同一种系统结 构的改进与加强,而无本质的变化,所以我们把 80386 以后的处理器统称为 IA32(32 Bit Intel Architecture)。

IA32分段(48位逻辑地址->32位线性地址)

之前80286寻址方式大白话就是: 利用段寄存器(CS、DS、SS之类的)查找段表中的线性地址,因为没有分页,线性地址就等于物理地址。

正规点的说法:段选择子/器–> 段描述符【IA32也是这个分段思路,但是段选择器和段描述符等具体格式可能80286有所不同】

IA32分段原理图

下面的selector 就是段选择子(CS:IP中的CS),offset 就是段内偏移(CS:IP中的IP) 

IA32段选择器

  • 索引号(index):所对应的段描述符处于GDT或LDT中的索引。
  • TI:TI=0表示对应段描述符保存在GDT(全局描述符表)中,TI=1表示对应的段描述符保存在LDT(局部描述符表)中。
  • RPL:请求者的特权级,表示发起选择器的过程的权限级别。Selectors contain a field called the requestor’s privilege level (RPL). The RPL is intended to represent the privilege level of the procedure that originates a selector.

补充:

(1)段描述符中有DPL标记项( Descriptor Privilege Level ),这两位用于指定段的特权级。

(2)内部处理器寄存器记录当前特权级别(CPL,Current Privilege Level)。通常CPL等于处理器当前正在执行的段的DPL。当控制转移到具有不同DPL的段时,CPL会发生变化。

(3)处理器共支持4 种特权级别,分别是 0、 1、 2、 3,其中 0 是最高特权级别, 3 是最低特权级别。在linux系统中只分2级,CPL为0是最高权限(内核态使用),CPL为3是用户态使用。

(3)数据访问权限。仅当目标段的DPL在数值上大于或等于CPL和选择器的RPL的最大值时,指令才可以加载数据段寄存器(并随后使用目标段)。换句话说,过程只能访问处于相同或更低特权级别的数据。任务的可寻址域随着CPL的变化而变化。当CPL为零时,可以访问所有权限级别的数据段; 当CPL为1时,只能访问权限级别为1到3的数据段; 当CPL为3时,只能访问权限级别为3的数据段。例如,可以使用80386的此属性来防止应用程序过程读取或更改操作系统的表。

(4)代码执行权限。通常只能同级访问,除了系统调用和中断:当控制转移到符合要求的段时,CPL不会更改。这是CPL可能与当前可执行段的DPL不相等的唯一情况。

IA32段描述符

从图可以看出,一个段描述符指出了段的32位基地址和20位段界限(即段长)。

G位是粒度位,当G=0时,段长表示段格式的字节长度,即一个段最长 可达1M字节。当G=1时,段长表示段的以4K字节为一页的页的数目,即一个段最长可达 1M×4K=4G字节。

X位表示缺省操作数的大小,如果X=0,操作数为16位,如果X=1,操作数 为32位。

P位(Present) 是存在位,表示段描述符描述的这个段是否在内存中,如果在 内存中。P=1;如果不在内存中,P=0。

DPL(Descriptor Privilege Level),就是描述符特权级,它占两位,其值为0~3, 用来确定这个段的特权级即保护等级。

S位(System)表示这个段是系统段还是用户段。如果S=0,则为系统段,如果S=1,则 为用户程序的代码段、数据段或堆栈段。【S位也就是DPL后面那一位】

TYPE:它区分不同的描述符格式,它指定了段的预期用途。

A:A=1已被访问过,A=0未被访问过。

° 段描述符是一种数据结构,实际上就是段表项,分两类:
• 普通段:用户/内核的代码段和数据段描述符
• 系统控制段描述符,又分两种:
- 特殊系统控制段描述符,包括:局部描述符表(LDT)描述 --> Local Descriptor Table descriptors-->LDTD
符和任务状态段(TSS)描述符  -->Task state segment descriptors -->TSSD
- 控制转移类描述符,包括:调用门描述符、任务门描述符、
中断门描述符和陷阱门描述符
  • LDT 描述符和TSS描述符,只能出现在 GDT中。
  • 调用门和任务门可以出现在LDT中。
  • 【特别强调一下:GDT中的存放的是 LDTD 和 TSSD,不是 LDT 和 TSS】

LDT描述符存放LDT的基地址:

linux从2.2版开始,Linux让所有的进程(或叫任务)都使用相同的逻辑地址空间,因此 就没有必要使用局部描述符表LDT。但内核中也用到LDT,那只是在VM86模式中运行 Wine,因为就是说在Linux上模拟运行Winodws软件或DOS软件的程序时才使用。

linux中所有进程共享默认 LDT 段。默认情况下,其中会包含一个空的段描述符。这个默认 LDT 段描述符存储在 GDT 中。Linux 所生成的 LDT 的大小是 24 个字节。默认有 3 个条目:
LDT[0] = 空
LDT[1] = 用户代码段
LDT[2] = 用户数据/堆栈段描述符
https://www.ibm.com/developerworks/cn/linux/l-memmod/index.html【问题是进程数限制这块内容,感觉不对啊】后来上网查询发现,linux2.2版本中,在GDT中为每个进程静态预分配了两项(一个是TSS,一个是LDT,为了符合intel 规范),因为GDT 表项有限制,所以 linux进程数有限制。
后来,linux内核改进了2.4版本,估计就演变成了 一个CPU带有一个TSS和一个GDT(GDT中带有一个默认的LDT)
http://www.newsmth.net/bbsanc.php?path=%2Fgroups%2Fcomp.faq%2FKernelTech%2Fothers%2FM.1018946573.v0
http://linux.ximizi.com/linux/linux2512.htm
TSS任务状态描述符存放任务的信息:

  • 处理器寄存器状态
  • I / O端口权限
  • 内层堆栈指针
  • 以前的TSS链接

目的是为了实现进程切换时,靠硬件实现任务信息切换,换进程就会将TSS切换。32位系统下,还有软切换:利用堆栈来保存任务信息。64位就只有软切换了,因为硬切换太慢。

该X86-64架构不支持硬件任务切换。但是,TSS仍可用于以64位扩展模式运行的机器。在这些模式中,TSS仍然有用,因为它存储:

  1. 堆栈指针针对每个权限级别进行寻址。
  2. 中断堆栈表的指针地址(上面的内层堆栈指针部分讨论了对此的需求)。
  3. 偏移IO权限位图的地址。

此外,在这些模式下扩展任务寄存器,以便能够保存64位基址。

每个 TSS 段 (TSS segment) 描述符都代表一个不同的进程。TSS 中保存了每个 CPU 的硬件上下文信息,它有助于有效地切换上下文。例如,在 U->K 模式的切换中,x86 CPU 就是从 TSS 中获取内核模式堆栈的地址。

进程恢复执行前必须装入寄存器的一组数据成为硬件上下文(hardware context)。硬件上下文是进程可执行上下文的一个自己,因为可执行上下文包含进程执行时所需要的所有信息。在Linux中,进程硬件上下午的一部分存放在TSS段,而剩余部分存放在内核态堆栈中。

早期Linux版本利用80×86体系结构所需提供的硬件支持,并通过far jmp1指令跳到next进程TSS描述符的选择符来执行进程切换。当执行这条指令时,CPU通过自动保存原来的硬件上下文,装入新的硬件上下文来执行硬件上下文切换。但Linux2.6使用软件执行进程切换,原因有:

  1. 通过一组mov指令逐步执行切换,这样能较好地控制所装入的数据的合法性,一面被恶意用户伪造。far jmp指令不会有这样的检查。
  2. 旧方法和新方法所需时间大致相同。

进程切换值发生在内核态,在执行进程切换之前,用户态进程使用的所有寄存器内容已保存在内核堆栈上,这也包括ss和esp这对寄存器的内容。

http://guojing.me/linux-kernel-architecture/posts/process-switch/

任务状态段(TSS)

80x86体系结构包含了一个特殊的段类型,叫任务状态段(Task State Segment,TSS)来存放硬件上下文,尽管Linux并不使用硬件上下文切换,但是强制它为系统中每个不同的CPU创建一个TSS,这样做主要有两个理由:

当80x86的一个CPU从用户态切换到内核态时,它就从TSS中后去内核态堆栈的地址。
当用户态进程试图通过in或out指令访问一个I/O端口时,CPU需要访问存放在TSS中的I/O许可位图以检查该进程是否有访问端口的权利。
更确切的说,当进程在用户态执行in或out指令时,控制单元执行下列操作:

检查eflags寄存器中的2位IOPL字段,如果字段的值为3,控制单元就执行I/O指令。否则,执行下一个检查。
访问tr寄存器以确定当前的TSS和相应的I/O许可权位图。
检查I/O指令中指定的I/O端口在I/O许可权位图中对应的位,如果该位清,这条指令就执行,否则控制单元产生一个异常。
tss_struct结构描述TSS的格式,init_tss数组为系统上每个不同的CPU存放一个TSS。在每次进程切换时,内核都更新TSS的某些字段以便相应的CPU控制单元可以安全地检索到它需要的信息。因此,TSS反映了CPU上当前进程的特权级,但不必为没有在运行的进程保留TSS。

每个TSS有它自己8字节的任务状态段描述符(Task State Segment Descriptor,TSSD)。这个描述符包括指向TSS起始地址的32位Base字段,20位Limit字段。TSSD的S标志位被清0,以表示相应的TSS时系统段的事实。

Type字段被置位11或9以表示这个段实际上是一个TSS。在Intel的原始设计中,系统中的每个进程都应当指向自己的TSS;Type字段的第二个有效位叫Busy位;如果进程正由CPU执行,则该位置1,否则为0。在Linux的设计中,每个CPU只有一个TSS,因此Busy位总是为1.

由Linux创建的TSSD存放在全局描述符表(GDT)中,GDT的基地址存放在每个CPU的gdtr寄存器中。每个CPU的tr寄存器包含相应TSS的TSSD选择符,也包含了两个隐藏的非编程字段:TSSD的Base字段和Limit字段。这样,处理器就能够直接TSS寻址而不需要从GDT中检索TSS地址。

thread字段
在每次进程切换时,被替换的进程的硬件上下文必须保存在别处。不能像Intel原始设计那样保存在TSS中,因为Linux为每个处理器而不是为每个进程使用TSS。

因此,每个进程描述符包含一个类型为thread_struct的thread字段,只要进程被切换出去,内核就把其硬件上下文保存在这个结构中。随后可以看到,这个数据结构包含的字段涉及大部分CPU寄存器,但不包括eax、ebx等等这些通用寄存器。它们的值保留在内核堆栈中。

执行进程切换
进程切换可能只发生在精心定义的点:schedule()函数,这个函数很长,会在以后更长的篇幅里讲解。。这里,只关注内核如何执行一个进程切换。

进程切换由两步组成:

切换页全局目录以安装一个新的地址空间。
切换内核态堆栈和硬件上下文,因为硬件上下文提供了内核执行新进程所需要的所有信息,包含CPU寄存器。

综上所述,linux系统中 一个CPU 带一个GDT 和TSS (GDT中有一个默认的LDT ,并被所有进程共享,进程切换入的信息保存在TSS中,进程切换出的信息保存在thread字段中)【按intel的设计,每一个进程都该有一个LDT和TSS】

The TSS may reside anywhere in memory. A special segment register called the task register (TR) holds a segment selector that points to a valid TSS segment descriptor which resides in the GDT (a TSS descriptor may not reside in the LDT). Therefore, to use a TSS the following must be done by the operating system kernel:

Create a TSS descriptor entry in the GDT
Load the TR with the segment selector for that segment
Add information to the TSS in memory as needed
For security purposes, the TSS should be placed in memory that is accessible only to the kernel.

TR 寄存器的内容是 选择子(相当于任务门描述符中的选择子,看了下英文图文中 task gate 和 task gate descriptor 居然是一个意思)–>指向 TSS描述符(TSSD在GDT中)–>指向 内存中的  TSS

参考:https://bbs.pediy.com/thread-62510.htm
来自:http://www.logix.cz/michal/doc/i386/chp07-03.htm

80386 32-Bit Task State Segment

 31              23              15              7             0
     +---------------+---------------+---------------+-------------+-+
     |          I/O MAP BASE         | 0 0 0 0 0 0 0   0 0 0 0 0 0 |T|64
     |---------------+---------------+---------------+-------------+-|
     |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|              LDT              |60
     |---------------+---------------+---------------+---------------|
     |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|              GS               |5C
     |---------------+---------------+---------------+---------------|
     |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|              FS               |58
     |---------------+---------------+---------------+---------------|
     |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|              DS               |54
     |---------------+---------------+---------------+---------------|
     |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|              SS               |50
     |---------------+---------------+---------------+---------------|
     |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|              CS               |4C
     |---------------+---------------+---------------+---------------|
     |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|              ES               |48
     |---------------+---------------+---------------+---------------|
     |                              EDI                              |44
     |---------------+---------------+---------------+---------------|
     |                              ESI                              |40
     |---------------+---------------+---------------+---------------|
     |                              EBP                              |3C
     |---------------+---------------+---------------+---------------|
     |                              ESP                              |38
     |---------------+---------------+---------------+---------------|
     |                              EBX                              |34
     |---------------+---------------+---------------+---------------|
     |                              EDX                              |30
     |---------------+---------------+---------------+---------------|
     |                              ECX                              |2C
     |---------------+---------------+---------------+---------------|
     |                              EAX                              |28
     |---------------+---------------+---------------+---------------|
     |                            EFLAGS                             |24
     |---------------+---------------+---------------+---------------|
     |                    INSTRUCTION POINTER (EIP)                  |20
     |---------------+---------------+---------------+---------------|
     |                          CR3  (PDPR)                          |1C
     |---------------+---------------+---------------+---------------|
     |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|              SS2              |18
     |---------------+---------------+---------------+---------------|
     |                             ESP2                              |14
     |---------------+---------------+---------------+---------------|
     |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|              SS1              |10
     |---------------+---------------+---------------+---------------|
     |                             ESP1                              |0C
     |---------------+---------------+---------------+---------------|
     |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|              SS0              |8
     |---------------+---------------+---------------+---------------|
     |                             ESP0                              |4
     |---------------+---------------+---------------+---------------|
     |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|   BACK LINK TO PREVIOUS TSS   |0
     +---------------+---------------+---------------+---------------+

----------------------------------------------------------------------------
NOTE
      0 MEANS INTEL RESERVED. DO NOT DEFINE.
----------------------------------------------------------------------------




TSS Descriptor for 32-bit TSS
  31                23                15                7               0
 +-----------------+-+-+-+-+---------+-+-----+---------+-----------------+
 |                 | | | |A| LIMIT   | |     |  TYPE   |                 |
 |   BASE 31..24   |G|0|0|V|         |P| DPL |         |   BASE 23..16   | 4
 |                 | | | |L|  19..16 | |     |0|1|0|B|1|                 |
 |-----------------+-+-+-+-+---------+-+-----+-+-+-+-+-+-----------------|
 |                                   |                                   |
 |             BASE 15..0            |             LIMIT 15..0           | 0
 |                                   |                                   |
 +-----------------+-----------------+-----------------+-----------------+




Task Register
                          +-------------------------+
                          |                         |
                          |                         |
                          |       TASK STATE        |
                          |        SEGMENT          |<---------+
                          |                         |          |
                          |                         |          |
                          +-------------------------+          |
           16-BIT VISIBLE             ^                        |
              REGISTER                |   HIDDEN REGISTER      |
       +--------------------+---------+----------+-------------+------+
    TR |      SELECTOR      |      (BASE)        |       (LIMT)       |
       +---------+----------+--------------------+--------------------+
                 |                    ^                     ^
                 |                    +-----------------+   |
                 |          GLOBAL DESCRIPTOR TABLE     |   |
                 |        +-------------------------+   |   |
                 |        |     TSS DESCRIPTOR      |   |   |
                 |        +------+-----+-----+------+   |   |
                 |        |      |     |     |      |---+   |
                 |        |------+-----+-----+------|       |
                 +------->|            |            |-------+
                          +------------+------------+
                          |                         |
                          +-------------------------+




Task Gate Descriptor

   31                23               15                7             0
  +-----------------+----------------+-+-----+---------+-----------------+
  |##################################| |     |         |#################|
  |############(NOT USED)############|P| DPL |0 0 1 0 1|###(NOT USED)####| 4
  |##################################| |     |         |#################|
  |----------------------------------+-+-----+---------+-----------------|
  |                                  |###################################|
  |              SELECTOR            |############(NOT USED)#############| 0
  |                                  |###################################|
  +-----------------+----------------+-----------------+-----------------+



Task Gate Indirectly Identifies Task

         LOCAL DESCRIPTOR TABLE              INTERRUPT DESCRIPTOR TABLE
       +-------------------------+           +-------------------------+
       |                         |           |                         |
       |        TASK GATE        |           |        TASK GATE        |
       +------+-----+-----+------+           +------+-----+-----+------+
       |      |     |     |      |           |      |     |     |      |
       |------+-----+-----+------|           |------+-----+-----+------|
    +--|            |            |        +--|            |            |
    |  +------------+------------+        |  +------------+------------+
    |  |                         |        |  |                         |
    |  |                         |        |  |                         |
    |  +-------------------------+        |  +-------------------------+
    +----------------+  +-----------------+
                     |  |    GLOBAL DESCRIPTOR TABLE
                     |  |  +-------------------------+
                     |  |  |                         |
                     |  |  |     TASK DESCRIPTOR     |
                     |  |  +------+-----+-----+------+
                     |  |  |      |     |     |      |
                     |  +->|------+-----+-----+------|
                     +---->|            |            |--+
                           +------------+------------+  |
                           |                         |  |
                           |                         |  |
                           +-------------------------+  |
                                                        |
                           +-------------------------+  |
                           |                         |  |
                           |                         |  |
                           |                         |  |
                           |       TASK STATE        |  |
                           |         SEGMENT         |  |
                           |                         |  |
                           |                         |  |
                           |                         |  |
                           +-------------------------+<-+

四类门描述符:
陷阱门:处理异常(除了双重故障(#DF)异常用任务门实现)

中断门:处理中断。

中断门和陷阱门只有在中断描述符表IDT中才有效。

任务门:任务门指示任务。任务门内的选择子必须指示GDT中的任务状态段TSS描述符,门中的偏移无意义。任务的入口点保存在TSS中。利用段间转移指令JMP和段间调用指令CALL,通过任务门可实现任务切换。

调用门:描述某个子程序的入口。调用门内的选择子必须实现代码段描述符,调用门内的偏移是对应代码段内的偏移。利用段间调用指令CALL,通过调用门可实现任务内从外层特权级变换到内层特权级。对于在x86特权级别之间转移控制特别重要,尽管在大多数现代操作系统上都没有使用这种机制。

IA32之GDT、LDT、IDT
全局描述符表(GDT)

1、只有一个,用来存放系统内每个任务共用的
2、描述符,例如,内核代码段、内核数据段、用户代码段、用户数据段以及TSS(任务状态段)等都属于GDT中描述的段。

GDTR是指向GDT基地址的寄存器,存放的是线性地址。

局部描述符表(LDT)

存放某任务(即用户进程)专用的描述符。

LDTR是存放LDT在GDT中的描述符,相当于段选择子,里面存放的是LDT基地址的线性地址。

intel建议每一个进程都有1个LDT和TSS,当然 TSS 和 LDT 的描述符都在GDT上,按理说 用户代码段和数据段应该放在 LDT上,不过 LDT 在linux中不怎么需要,所以就用户代码和数据段都放在了GDT上。

来自:http://ju.outofmemory.cn/entry/111444

中断描述符表(IDT)

包含256个中断门、陷阱门和任务门描述符。IR寄存器存放的是IDT基地址的线性地址。


陷阱门和中断门在IDT中:

任务门在LDT或IDT中:

调用门在LDT和GDT中:

参考自:

  • https://pdos.csail.mit.edu/6.828/2005/readings/i386/s06_03.htm
  • https://css.csail.mit.edu/6.858/2014/readings/i386/s07_04.htm
  • https://pdos.csail.mit.edu/6.828/2005/readings/i386/s09_06.htm

IA32分段中的缓存机制

段选择器(selector)【也就是段寄存器】在查找段描述表(descriptor table)的描述符(segment descriptor)时,会将第一遍查找到的结果缓存到段选择器的不可见部分,这样后面就不用查段描述符表了,直接从段选择器的缓存中读取。

linux中的分段机制
Linux中的异常和中断处理
• Linux利用陷阱门来处理异常,利用中断门来处理中断。
• 异常和中断对应处理程序都属于内核代码段,所以,所有中断门和陷阱门
的段选择符(0x60)都指向GDT 中的“内核代码段”描述符。
• 通过中断门进入到一个中断服务程序时,CPU 会清除EFLAGS 寄存器中
的IF 标志,即关中断;通过陷阱门进入一个异常处理程序时,CPU 不会
修改IF 标志。也就是说,外部中断不支持嵌套处理,而内部异常则支持
嵌套处理。
• 任务门描述符中不包含偏移地址,只包含TSS 段选择符,这个段选择符
指向GDT 中的一个TSS 段描述符,CPU 根据TSS 段中的相关信息装载
SS 和ESP 等寄存器,从而执行相应的异常处理程序。
• Linux中,将类型号为8的双重故障(#DF)用任务门实现,而且是唯一
通过任务门实现的异常。
• 双重故障TSS 段描述符在GDT 中位于索引值为0x1f 的表项处,即13
位索引为0 0000 0001 1111,且其TI=0(指向GDT),RPL=00(内
核级代码),即任务门描述符中的段选择符为00F8H。

为什么 use data 是 0x7b =123 【15*8 +3】,为什么要加 3,内核段却没有加 3 https://stackoverflow.com/questions/11735729/segment-definitions-for-linux-on-x86

其实上面的0x7b =123 【15*8 +3】 是用户数据段的选择子 selector,而GDT中用户数据段的描述符descriptor地址是 GDT基地址+15*8

也就是说GDT中的描述符不存在内存空隙,都是紧密排列的。除了GDT中第一项8个字节留空。

#define __KERNEL_DS (GDT_ENTRY_KERNEL_DS*8)
#define __USER_DS   (GDT_ENTRY_DEFAULT_USER_DS*8+3)
#define __USER_CS   (GDT_ENTRY_DEFAULT_USER_CS*8+3)


其实是 理解问题:
GDT中的USER_DS 的descriptor是 基地址 + index * 8【因为一个GDT项占8个字节】
而用户程序的DS ,即数据段寄存器 是【高13位是索引,位3是指向GDT还是LDT,低2位是权限】

即 【 0000   0000  0111   1011 】 高13位是index索引 ,乘8是左移3位的意思,
011 中 0代表指向GDT表,11代表RPL的权限是用户态。
所以 GDT_ENTRY_DEFAULT_USER_DS*8+3   结果是 数据段的选择子(符)【selector】

至于KERNEL_DS,因为 位3是0代表指向GDT,低2位都是0,代表RPL是内核态。所以只要index*8就行了

参考:https://wenku.baidu.com/view/9b369289680203d8ce2f24d4.html

唯一的问题是:CS:IP。因为设置成CS=0,那么IP成了线性地址,每个进程的线性地址都不同,难道IP重定位了。

因为很多RISC处理器并不支持段机制。但是对段机制相关知识的了解是进入 Linux内核的必经之路。

从2.2版开始,Linux让所有的进程(或叫任务)都使用相同的逻辑地址空间,因此 就没有必要使用局部描述符表LDT。但内核中也用到LDT,那只是在VM86模式中运行 Wine,因为就是说在Linux上模拟运行Winodws软件或DOS软件的程序时才使用。

Linux的设计人员让段的基地址为0,而段的界限为 4GB,这时任意给出一个偏移量,则等式为“0+偏移量=线性地址”,也就是说“偏移量 =线性地址”。另外由于段机制规定“偏移量 < 4GB”,所以偏移量的范围为0H~ FFFFFFFFH,这恰好是线性地址空间范围,也就是说虚拟地址直接映射到了线性地址, 我们以后所提到的虚拟地址和线性地址指的也就是同一地址。

Linux在启动的过程中设置了段寄存器的值和全局描述符表GDT的内容,段的定义在 include/asm-i386/segment.h中:

#define __KERNEL_CS 0x10 /*内核代码段,index=2,TI=0,RPL=0*/
#define __KERNEL_DS 0x18 /*内核数据段, index=3,TI=0,RPL=0*/
#define __USER_CS 0x23 /*用户代码段, index=4,TI=0,RPL=3*/
#define __USER_DS 0x2B /*用户数据段, index=5,TI=0,RPL=3*/

从定义看出,没有定义堆栈段,实际上,Linux内核不区分数据段和堆栈段,这也体 现了Linux内核尽量减少段的使用。因为没有使用LDT,因此,TI=0,并把这4个段都放在 GDT中, index就是某个段在GDT表中的下标。内核代码段和数据段具有最高特权,因此其 RPL为0,而用户代码段和数据段具有最低特权,因此其RPL为3。可以看出,Linux内核再次简化了特权级的使用,使用了两个特权级而不是4个。

全局描述符表的定义在arch/i386/kernel/head.S中:

ENTRY(gdt_table)
.quad 0x0000000000000000 /* NULL descriptor */
.quad 0x0000000000000000 /* not used */
.quad 0x00cf9a000000ffff /* 0x10 kernel 4GB code at
0x00000000 */
.quad 0x00cf92000000ffff /* 0x18 kernel 4GB data at
0x00000000 */
.quad 0x00cffa000000ffff /* 0x23 user 4GB code at
0x00000000 */
.quad 0x00cff2000000ffff /* 0x2b user 4GB data at
0x00000000 */
.quad 0x0000000000000000 /* not used */
.quad 0x0000000000000000 /* not used */
/*
* The APM segments have byte granularity and their bases
* and limits are set at run time.
*/
.quad 0x0040920000000000 /* 0x40 APM set up for bad BIOS's
*/
.quad 0x00409a0000000000 /* 0x48 APM CS code */
.quad 0x00009a0000000000 /* 0x50 APM CS 16 code (16 bit) */
.quad 0x0040920000000000 /* 0x58 APM DS data */
.fill NR_CPUS*4,8,0 /* space for TSS's and LDT's */

从代码可以看出,GDT放在数组变量gdt_table中。按Intel规定,GDT中的第一项为空, 这是为了防止加电后段寄存器未经初始化就进入保护模式而使用GDT的。第二项也没用。从 下标2到5共4项对应于前面的4种段描述符值。从描述符的数值可以得出:

段的基地址全部为0x00000000
段的上限全部为0xffff 
段的粒度G为1,即段长单位为4KB 
段的D位为1,即对这四个段的访问都为32位指令 
段的P位为1,即四个段都在内存。

Intel的规定,每个进程有一个任务状态段(TSS)和局部描述符表LDT,但Linux 也没有完全遵循Intel的设计思路。如前所述,Linux的进程没有使用LDT,而对TSS的使用也非常有限,每个CPU仅使用一个TSS。

IA32分页(32位线性地址->32位物理地址)

有《深入理解LINUX内存管理》到时去看看。

分段情况:

linux 从2.2版开始,Linux让所有的进程(或叫任务)都使用相同的逻辑地址空间,也就是基地址 =0,偏移地址 =线性地址。

分页情况:

一个问题:0.12版本的linux 只有 1张页目录表,cr3 就一个值。现在一个进程一张页目录表,cr3切换多个值。https://www.zhihu.com/question/24622613 

看了一下书,大致说,线性地址相同的进程,采用了不同的页目录,所以两者的物理地址是不同的。这就是CR3 多个的解释。

Linux内核软件架构习惯与分成硬件相关层和硬件无关层。对于页表管理,2.6.10以前(包括2.6.10)在硬件无关层使用了3级页表目录管理的方式,它不管底层硬件是否实现的也是3级的页表管理:

linux在 x86 架构上,pmd 在硬件中并不存在,但是在内核代码中它是与 pgd 合并在一起的。

为了支持大内存区域,Linux 也采用了这种三级分页机制。在不需要为大内存区域时,即可将 pmd 定义成“1”,返回两级分页机制。

linux分页级别是在编译时进行优化的,我们可以通过启用或禁用中间目录来启用两级和三级分页(使用相同的代码)。32 位处理器使用的是 pmd 分页,而 64 位处理器使用的是 pgd 分页。

Page Global Directory (PGD)

Page Middle Directory (PMD)

Page Table (PTE)

从2.6.11开始,为了配合64位CPU的体系结构,硬件无关层则使用了4级页表目录管理的方式:

Page Global Directory (PGD)

Page Upper Directory (PUD)

Page Middle Directory (PMD)

Page Table (PTE)

IA32分页寄存器

参考:
http://blog.sina.com.cn/s/blog_85998e38010122wq.html 【不准确】
https://en.wikipedia.org/wiki/Control_register

CR0(32位中最高位PG代表是否分页,最低位PE代表是否分段)

x86_32的CR0为32bit。X86_64下为64bit,其中低32bit与x86_32的CR0保持一致,高32bit没有定义,作保留使用,除了bit4其他所有位都是可读可写的。
Protected-Mode Enable (PE) Bit. Bit 0. PE=0,表示CPU处于实模式; PE=1表CPU处于保护模式,并使用分段机制。
Paging Enable (PG) Bit. Bit 31. 该位控制分页机制,PG=1,启动分页机制;PG=0,不使用分页机制。

CR2(存放发生页错误时的虚拟地址)

下面应该是32位和64位的情况下

CR3(存放最高级页目录地址(物理地址)或页目录指针地址

各级页表项中存放的也是物理地址。格式如下:【PAE 代表 物理地址扩展技术】

Page-Level Writethrough (PWT) Bit. Bit 3. Page-level writethrough indicates whether the highest-level page-translation table has a writeback or writethrough caching policy. When PWT=0, the table has a writeback caching policy. When PWT=1, the table has a writethrough caching policy.
Page-Level Cache Disable (PCD) Bit. Bit 4. PCD=1,表示最高目录表不可缓存,PCD=0,相反。

图3-4中不使用PAE技术,有两层页表。最高层为页目录有1024项,占用4KB。page_directory_table base address为物理地址,指向4KB对齐的页目录地址。
图3-5中,使用PAE技术,三层页表寻址。最高层为页目录指针,4项,占用32B空间。所以 page_directory_table base address为27位,指向32B对齐的页目录指针表。

Typically, the upper 20 bits of CR3 become the page directory base register (PDBR), which stores the physical address of the first page directory entry. If the PCIDE bit in CR4 is set, the lowest 12 bits are used for the process-context identifier(PCID).

CR4(legacy mode 下低32位与x86_32的CR4一致)

 

  • Page-Size Extensions (PSE) Bit. Bit 4. PSE=1,启用PSE,PSE=0,不启用。
  • Physical-Address Extension (PAE) Bit. Bit 5.PAE=1,启用PAE,支持2MB的超级页(superpage);PAE=0,不启用PAE。
    Bit Name Full Name Description
    0 VME Virtual 8086 Mode Extensions If set, enables support for the virtual interrupt flag (VIF) in virtual-8086 mode.
    1 PVI Protected-mode Virtual Interrupts If set, enables support for the virtual interrupt flag (VIF) in protected mode.
    2 TSD Time Stamp Disable If set, RDTSC instruction can only be executed when in ring 0, otherwise RDTSC can be used at any privilege level.
    3 DE Debugging Extensions If set, enables debug register based breaks on I/Ospace access.
    4 PSE Page Size Extension If unset, page size is 4 KiB, else page size is increased to 4 MiBIf PAE is enabled or the processor is in x86-64 long mode this bit is ignored.
    5 PAE Physical Address Extension If set, changes page table layout to translate 32-bit virtual addresses into extended 36-bit physical addresses.
    6 MCE Machine Check Exception If set, enables machine check interrupts to occur.
    7 PGE Page Global Enabled If set, address translations (PDE or PTE records) may be shared between address spaces.
    8 PCE Performance-Monitoring Counter enable If set, RDPMC can be executed at any privilege level, else RDPMC can only be used in ring 0.
    9 OSFXSR Operating system support for FXSAVE and FXRSTOR instructions If set, enables Streaming SIMD Extensions (SSE) instructions and fast FPU save & restore.
    10 OSXMMEXCPT Operating System Support for Unmasked SIMD Floating-Point Exceptions If set, enables unmasked SSE exceptions.
    11 UMIP User-Mode Instruction Prevention If set, the SGDT, SIDT, SLDT, SMSW and STR instructions cannot be executed if CPL > 0.
    12 LA57 (none specified) If set, enables 5-Level Paging.
    13 VMXE Virtual Machine Extensions Enable see Intel VT-x x86 virtualization.
    14 SMXE Safer Mode Extensions Enable see Trusted Execution Technology (TXT)
    16 FSGSBASE Enables the instructions RDFSBASE, RDGSBASE, WRFSBASE, and WRGSBASE.
    17 PCIDE PCID Enable If set, enables process-context identifiers (PCIDs).
    18 OSXSAVE XSAVE and Processor Extended States Enable
    20 SMEP Supervisor Mode Execution Protection Enable If set, execution of code in a higher ring generates a fault.
    21 SMAP Supervisor Mode Access PreventionEnable If set, access of data in a higher ring generates a fault.
    22 PKE Protection Key Enable See Intel 64 and IA-32 Architectures Software Developer’s Manual.
EFER(Extended Feature Enable Register)-Additional Control registers in x86-64 series

Long Mode Enable (LME) Bit. Bit 8. LME=1,启用long mode,注意必须先将CR0.PG=0后才能设置LME=1,然后再设置CR0.PG=1,则进入long mode。LME=0 ,使用legacy mode。【CPU的 long mode 给 64位操作系统用,legacy mode给32位或16位操作系统用】维基百科中有详细解释Long mode 和 Legacy mode

IA32分页模型
分页控制器总结:

概况一下:

1、要开启分页,必须 激活 PG位。
2、PAE 未激活下,必须同时开启PSE和PE,页大小才能变成4MB,如果还开启PSE-36,则物理地址还会变成36位。
3、开始PAE后,物理地址就直接变成了36位,至于页大小直接看PS是否激活。

常规分页:

PSE(Page Size Extension)分页:就是扩展页框大小的

PSE-36分页:不仅增加页框大小还扩展了物理地址位数

PAE (Physical Address Extention)分页:用来扩展物理地址位数的

PAE+PSE分页:即扩展物理地址位数又增加页框大小

PAE与PSE介绍

PSE就是设置页框大小的,由 CR4寄存器的PSE位+PDE中的PS位 来控制。从实际情况来看,只要页框大小发生改变都属于运用了PSE技术。

PAE就是用来扩展物理地址的,但是线性地址不变。https://docs.microsoft.com/en-us/previous-versions/windows/hardware/design/dn613969(v=vs.85)
文章提到:PAE 只用于32位系统。后文将提到x64的长模式,其实是运用了 extends  PAE ,然后 64位模式中,有三种页框大小,可以说也运用了PSE技术。【长模式中的extends  PAE是针对 64位模式还是兼容模式呢,估计应该是64位模式吧】https://en.wikipedia.org/wiki/X86-64

查看和启用PAE:我们这里说的是32位的系统,因为64位的长模式默认是extends PAE。

Windows平台:

先查看cpu是否支持 PAE,然后再 boot.ini  或BCD 文件夹中,启用PAE。(Everest Ultimate Edition 软件可以查看CPU 是否支持PAE)可以查看我的电脑属性,观察是否启用了物理地址扩展。

linux平台:

centos下载地址      http://mirror.nsc.liu.se/centos-store/5.6/isos/i386/  

32位centos5情况:我看了一下发行版就一个安装包,估计是系统安装器会自动根据 CPU情况,选择采用 PAE 内核,还是 NONPAE 内核

但是有 https://blog.csdn.net/chinalinuxzend/article/details/1759112 提到,centos4会自动选择内核版本,但是centos5 没有那么智能只会默认安装nonpae版本。

 1,centos5下的内存变少了?
   这个问题始于一台dell 2950的系统安装
    dell 2950,双至强1.160GHZ
             4G内存
   安装完centos5之后只能看到3.3G的内存,少了700多MB,
   用free和top两个命令都发现是3.3G,   咦,内存跑到哪儿去了?

   咦,内存跑到哪儿去了?

   问了dell的服务支持,对方答可以正常支持redhat4这个版本
   安装centos4.4后,内存显示为4.1G,正常,
   为什么centos5就不可以?
   
   2,使用PAE核心
    centos 5.0 默认安装 for i386的内核不支持 4g+的内存 
    需要安装上kernel-PAE
    进入centos安装盘,rpm -ivh kernel-PAE*

    然后修改grub设置
    vi /boot/grub/grub.conf 
    找到:
    title CentOS (2.6.18-8.el5PAE)
        root (hd0,0)
        kernel /vmlinuz-2.6.18-8.el5PAE ro root=/dev/VolGroup00/LogVol00
        initrd /initrd-2.6.18-8.el5PAE.img
    修改此记录所对应的一项为默认启用的核心即可
    如:
    default=0
    保存退出,重新启动机器,设置生效,
    在centos5下可以看到4.1G内存了

查看CPU是否支持PAE

[madhatta@www ~]$ grep -i pae /proc/cpuinfo 
flags       : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe nx lm constant_tsc pni monitor ds_cpl vmx est tm2 ssse3 cx16 xtpr lahf_lm
flags       : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe nx lm constant_tsc pni monitor ds_cpl vmx est tm2 ssse3 cx16 xtpr lahf_lm
                                     ^^^

查看系统内核是否支持PAE:[centos5 的内核版本号有标明是否支持 PAE]

[madhatta@www ~]$ uname -r
2.6.18-194.32.1.el5PAE

参考:https://serverfault.com/questions/247080/how-do-i-find-out-if-pae-is-enabled

32位centos6情况:发行版还是一个软件,看情况是说内核默认是PAE版本,没有NONPAE版本,如果要安装在不支持PAE的CPU上需要修改替换内核。

具体参考:
https://www.centos.org/forums/viewtopic.php?t=67337
https://www.centos.org/forums/viewtopic.php?t=2157

centos6 版本,使用 uname -r 命令显示的内核字符串,已经不会显示是否支持 PAE 了。

但仍可以在 /boot 目录下,查看到【 config-内核字符串 】文件
如果该文件支持PAE ,会有  CONFIG_X86_PAE=y  配置项

============ 举例如下  ===========

[root@localhost ~]# uname -r
2.6.32-220.el6.i686
[root@localhost ~]# ls /boot
System.map-2.6.32-220.el6.i686  initramfs-2.6.32-220.el6.i686.img
config-2.6.32-220.el6.i686      lost+found
efi                             symvers-2.6.32-220.el6.i686.gz
grub                            vmlinuz-2.6.32-220.el6.i686
[root@localhost ~]# cat /boot/config-2.6.32-220.el6.i686 | grep PAE
CONFIG_X86_PAE=y
[root@localhost ~]#

有一个奇怪的地方,虚拟机设置了禁用CPU的pae功能,但是32位的centos6还是能发现CPU支持PAE。在32位的centos7中,只要虚拟机禁用了CPU的pae功能,系统就认为CPU不支持PAE。

方法一下载第三方nonpae内核

https://www.liangzl.com/get-article-detail-917.html
装完系统后,仍从安装光盘进入  Rescue Installed system,接着进入命令行。

从 Rescue Shell  进入bash  【  /mnt/sysimage/ 是系统提示给出的】
chroot /mnt/sysimage/ /bin/bash


安装nonpae内核
rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
rpm -Uvh http://www.elrepo.org/elrepo-release-6-8.el6.elrepo.noarch.rpm
yum --enablerepo=elrepo-kernel install kernel-lt-NONPAE


检查系统已安装的内核
rpm -qa|grep kernel

退出当前 Chroot Shell 和 Rescure Shell
exit
exit

方法二自己编译nonpae内核

http://www.digitage.co.uk/digitage/library/linux/installing-centos-6-4-on-non-pae-hardware

https://my.oschina.net/colben/blog/1456759

32位centos7情况:发行版还是一个软件。虚拟机设置CPU成PAE还是NONPAE都能成功安装,并且系统的 cat /proc/cpuinfo 能正确识别CPU 是否支持PAE功能。【以此推理,centos7 下的内核要么PAE和非PAE 都支持,要么就只支持非PAE】

参考:  https://centosfaq.org/centos/centos-7-i386-pae-kernel/

最暴力的手段就是编译内核模块,查看CR4 寄存器 是否开启了PAE功能。具体可以看本文第5节   Linux内存地址映射实验

补充:关于如何启用PAE,这其实没有实际意义的。因为安装器会自动判断系统需要nonpae还是pae内核。就算是centos6版本,要安装的也是nonpae内核。前面centos5的文章链接,https://blog.csdn.net/chinalinuxzend/article/details/1759112。提到过一种现象,需要进一步了解。

### 安装 pae 内核方法
yum install kernel-PAE
### 后面重启服务器就行
查看  CPU信息,内存信息,内核信息
查看CPU信息
cat /proc/cpuinfo


查看内存信息   条目解释 参考 http://linuxperf.com/?p=142
cat /proc/meminfo

查看版本信息
[root@localhost ~]# cat /proc/version
Linux version 3.10.0-957.21.3.el7.centos.plus.i686 ([email protected]) (gcc version 4.8.5 20150623 (Red Hat 4.8.5-36) (GCC) ) #1 SMP Tue Jun 18 18:05:09 UTC 2019


[root@localhost ~]# uname -a 
Linux db1.xxx.com 2.6.18-194.el5xen #1 SMP Fri Apr 2 15:34:40 EDT 2010 x86_64 x86_64 x86_64 GNU/Linux

依次是

操作系统名称: Linux   uname -s 可以单独看到这个信息
计算机名: db1.xxx.com  uname -n 可以单独看到这个信息
操作系统发行编号: 2.6.18-194.el5xen  uname -r 可以单独看到这个信息
操作系统发行时间: #1 SMP Fri Apr 2 15:34:40 EDT 2010  uname -v 可以单独看到这个信息
计算机类型,进程类型,硬件平台:x86_64    uname –m,uname –p,uname -i 可以单独看到这个信息
操作系统信息:GNU/Linux  uname -o 可以单独看到这个信息

下面是32位linux系统内存1G下的情况:

[root@localhost ~]# cat /proc/meminfo
MemTotal:        1027020 kB
MemFree:          728684 kB
MemAvailable:     762276 kB
Buffers:            2108 kB
Cached:           166804 kB
SwapCached:            0 kB
Active:            94164 kB
Inactive:         137300 kB
Active(anon):      62768 kB
Inactive(anon):     6728 kB
Active(file):      31396 kB
Inactive(file):   130572 kB
Unevictable:           0 kB
Mlocked:               0 kB
HighTotal:        139208 kB
HighFree:         103120 kB
LowTotal:         887812 kB
LowFree:          625564 kB
SwapTotal:        839676 kB
SwapFree:         839676 kB
Dirty:                24 kB
Writeback:             0 kB
AnonPages:         62556 kB
Mapped:            21576 kB
Shmem:              6944 kB
Slab:              28172 kB
SReclaimable:      13316 kB
SUnreclaim:        14856 kB
KernelStack:         968 kB
PageTables:          716 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:     1353184 kB
Committed_AS:     298248 kB
VmallocTotal:     122880 kB
VmallocUsed:       32164 kB
VmallocChunk:      88156 kB
AnonHugePages:     24576 kB
CmaTotal:              0 kB
CmaFree:               0 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       4096 kB
DirectMap4k:       49144 kB
DirectMap4M:      860160 kB
[root@localhost ~]# 

#########################
DirectMap4k:    DirectMap4M:   是TLB缓存 所指向的内存大小 40M + 840M = 890M 接近1GB = 系统内存
AnonHugePages统计的是 Transparent HugePages (THP),THP与Hugepages不是一回事,区别很大。
标准大页管理是预分配的方式,而透明大页管理则是动态分配的方式。
THP 是一个抽象层, 可以自动创建、管理和使用传统大页的大多数方面。

查看CPU信息:address sizes : 36 bits physical, 48 bits virtual 【应该是CPU最大支持 36位物理地址,48位虚拟地址。的确是这样,同样的CPU 在64位系统中,也显示 36位物理地址,48位虚拟地址】【备注这里的32位linux虚拟机关了CPU的PAE功能】

[root@localhost ~]# cat /proc/cpuinfo
processor	: 0
vendor_id	: GenuineIntel
cpu family	: 6
model		: 58
model name	: Intel(R) Core(TM) i5-3210M CPU @ 2.50GHz
stepping	: 9
microcode	: 0x19
cpu MHz		: 2494.316
cache size	: 3072 KB
physical id	: 0
siblings	: 1
core id		: 0
cpu cores	: 1
apicid		: 0
initial apicid	: 0
fdiv_bug	: no
f00f_bug	: no
coma_bug	: no
fpu		: yes
fpu_exception	: yes
cpuid level	: 13
wp		: yes
flags		: fpu vme de pse tsc msr mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht rdtscp constant_tsc xtopology nonstop_tsc eagerfpu pni pclmulqdq monitor ssse3 cx16 pcid sse4_1 sse4_2 x2apic popcnt aes xsave avx rdrand hypervisor lahf_lm fsgsbase
bogomips	: 4988.63
clflush size	: 64
cache_alignment	: 64
address sizes	: 36 bits physical, 48 bits virtual
power management:

[root@localhost ~]# cat /proc/cpuinfo

IA32分页缓存机制

处理器引入MMU后,读取指令、数据需要访问两次内存:首先通过查询页表得到物理地址,然后访问该物理地址读取指令、数据。为了减少因为MMU导致的处理器性能下降,引入了TLB,TLB是Translation Lookaside Buffer的简称,可翻译为“地址转换后援缓冲器”,也可简称为“快表”。简单地说,TLB就是页表的Cache,其中存储了当前最可能被访问到的页表项,其内容是部分页表项的一个副本。只有在TLB无法完成地址翻译任务时,才会到内存中查询页表,这样就减少了页表查询导致的处理器性能下降。

TLB大致原理图:

四、x86-64

x86-64 (also known as x64x86_64AMD64 and Intel 64[note 1]) is the 64-bit version of the x86 instruction set. 

但  x86-64 ≠ IA-64 IA-64,又称英特尔安腾架构(Intel Itanium architecture),使用在Itanium处理器家族上的64位元指令集架构,由英特尔公司与惠普公司共同开发。IA是Intel Architecture(英特尔架构)的缩写,64指64位系统。使用这种架构的CPU,包括Itanium和Itanium 2。此架构与x86及x86-64并不相容,操作系统与软件需使用IA-64专用版本。

注意一下,开64位虚拟机的时候,设置禁止CPU的pae功能,但是centos 还是能发现系统支持pae,即cat /proc/cpuinfo 还是能看到pae参数。

x86-64分段

64位系统下,运行的是long mode。64位软件运行64位模式,32位和16位软件运行兼容模式。

在长模式下,段的影响取决于处理器是否运行在兼容模式还是64位模式。

  • 在兼容模式下,分段功能好像在传统模式,并使用传统32位或16位的保护模式语义。
  • 在64位模式下,分段是禁止的,建立64位虚拟地址空间(flat memory model吧,估计偏移地址就是线性地址的意思。某些寄存器的一定功能,特别是系统段寄存器可持续在64位模式下运行。各种段寄存器都设置成0。至于GS/FS 是系统定义用法的寄存器,没有设置成0。

【估计逻辑地址就是线性地址了,而且目前线性地址是48位的。为什么是48位线性地址,这涉及到线性地址的空间分配问题:分为用户空间和内核空间:如下图所示:】

===========================================================================================
    Start addr    |   Offset   |     End addr     |  Size   | VM area description
===========================================================================================
                  |            |                  |         |
 0000000000000000 |    0       | 00007fffffffffff |  128 TB | user-space virtual memory
__________________|____________|__________________|_________|______________________________
                  |            |                  |         |
 0000800000000000 | +128    TB | ffff7fffffffffff | ~16M TB | non-canonical
__________________|____________|__________________|_________|______________________________
                  |            |                  |         |
 ffff800000000000 | -128    TB | ffffffffffffffff |  128 TB | kernel-space virtual memory
__________________|____________|__________________|_________|______________________________

参考:
维基百科X86_memory_segmentation
64位微处理器系统编程

The x86-64 architecture does not use segmentation in long mode (64-bit mode). 
Four of the segment registers: CS, SS, DS, and ES are forced to 0, and the limit to 264. 
The segment registers FS and GS can still have a nonzero base address. 
This allows operating systems to use these segments for special purposes. 
Unlike the global descriptor table mechanism used by legacy modes, 
the base address of these segments is stored in a model-specific register. 
The x86-64 architecture further provides the special SWAPGS instruction,
 which allows swapping the kernel mode and user mode base addresses.

For instance, Microsoft Windows on x86-64 uses the GS segment to point to the Thread Environment Block, 
a small data structure for each thread, which contains information 
about exception handling, thread-local variables, and other per-thread state. 
Similarly, the Linux kernel uses the GS segment to store per-CPU data.

x86-64分页

参考:https://bbs.pediy.com/thread-203391.htm

x64的PAE的问题

在64位centos中,我在虚拟机界面关了CPU的PAE功能,但是在系统的命令行中,仍可以显示支持PAE功能。【64位系统我设置了双核CPU,所以下面可以看到两个CPU信息】

下面这个是在本地64位虚拟机上测试的(cpu支持36位物理地址,48位线性地址

[coolguy@localhost ~]$ cat /proc/cpuinfo
processor	: 0
vendor_id	: GenuineIntel
cpu family	: 6
model		: 58
model name	: Intel(R) Core(TM) i5-3210M CPU @ 2.50GHz
stepping	: 9
microcode	: 0x19
cpu MHz		: 2494.316
cache size	: 3072 KB
physical id	: 0
siblings	: 2
core id		: 0
cpu cores	: 2
apicid		: 0
initial apicid	: 0
fpu		: yes
fpu_exception	: yes
cpuid level	: 13
wp		: yes
flags		: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc eagerfpu pni pclmulqdq ssse3 cx16 pcid sse4_1 sse4_2 x2apic popcnt aes xsave avx rdrand hypervisor lahf_lm fsgsbase
bogomips	: 4988.63
clflush size	: 64
cache_alignment	: 64
address sizes	: 36 bits physical, 48 bits virtual
power management:

processor	: 1
vendor_id	: GenuineIntel
cpu family	: 6
model		: 58
model name	: Intel(R) Core(TM) i5-3210M CPU @ 2.50GHz
stepping	: 9
microcode	: 0x19
cpu MHz		: 2494.316
cache size	: 3072 KB
physical id	: 0
siblings	: 2
core id		: 1
cpu cores	: 2
apicid		: 1
initial apicid	: 1
fpu		: yes
fpu_exception	: yes
cpuid level	: 13
wp		: yes
flags		: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc eagerfpu pni pclmulqdq ssse3 cx16 pcid sse4_1 sse4_2 x2apic popcnt aes xsave avx rdrand hypervisor lahf_lm fsgsbase
bogomips	: 4988.63
clflush size	: 64
cache_alignment	: 64
address sizes	: 36 bits physical, 48 bits virtual
power management:

[coolguy@localhost ~]$ 

下面这个是在64位服务器上测试的(你看cpu支持40位物理地址,48位线性地址

[root@superguy ~]# cat /proc/cpuinfo
processor	: 0
vendor_id	: GenuineIntel
cpu family	: 6
model		: 79
model name	: Intel(R) Xeon(R) CPU E5-2697 v4 @ 2.30GHz
stepping	: 1
microcode	: 0x1
cpu MHz		: 2299.994
cache size	: 16384 KB
physical id	: 0
siblings	: 1
core id		: 0
cpu cores	: 1
apicid		: 0
initial apicid	: 0
fpu		: yes
fpu_exception	: yes
cpuid level	: 13
wp		: yes
flags		: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon rep_good nopl xtopology cpuid tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch cpuid_fault invpcid_single pti ssbd ibrs ibpb fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm rdseed adx smap xsaveopt arat
bugs		: cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds
bogomips	: 4601.65
clflush size	: 64
cache_alignment	: 64
address sizes	: 40 bits physical, 48 bits virtual
power management:

[root@superguy ~]# 

core与xeon的比较

Core i7处理器被认为是Intel的“性能”CPU系列。与更具价值导向的Core i5系列相比,Core i7芯片具有更多可用内核,更高频率和更大的缓存分配,因此其速度快,响应速度足以支持当今最苛刻的应用。他们缺乏Xeon对纠错内存的支持(见下文),但对于高级用户,Core i7 CPU可以超频暂时以高于额定速度运行,而Xeon型号则不能。

英特尔至强CPU提供与Core i7系列类似的频率和高速缓存大小,以及更高的最大核心数。但最重要的区别是支持纠错码(ECC)内存,这是一种服务器级功能,使Xeon芯片在执行要求苛刻的任务关键型计算的工作站中特别有价值。ECC内存有助于在导致问题之前找到并修复99.999%的软内存错误,这可以大大减少数据损坏和破坏系统崩溃的工作。

x64的线性地址结构

在x64体系中只实现了48位的virtual address,高16位被用作符号扩展,这高16位要么全是0,要么全是1。

64位系统中因为页表项,页目录项之类的,变成了64位【32位系统中常规页表项是32位】,为了满足原来的4K的页面,就从原来的 2^10 x 32 =4K 变成 2^9 x 64 =4k  ,所以索引地址由32位下的10位,变成了9位。

x64的常规分页模型

 

  PML4T(Page Map Level4 Table)及表内的PML4E结构,每个表为4K,内含512个PML4E结构,每个8字节
  PDPT (Page Directory Pointer Table)及表内的PDPTE结构,每个表4K,内含512个PDPTE结构,每个8字节
  PDT (Page Directory Table) 及表内的PDE结构,每个表4K,内含512个PDE结构,每个8字节
  PT(Page Table)及表内额PTE结构,每个表4K,内含512个PTE结构,每个8字节。
每个table entry 的结构都是8个字节64位宽,而virtual address中每个索引值都是9位,因此每个table都是512 x 8 = 4K字节。

x64的最大物理地址(MAXPHYADDR)

在Intel中使用MAXPHYADDR来表示最大的物理地址,我们可以通过CPUID的指令来获得处理支持的最大物理地址,然而这已经不在此次的讨论范围之内,我们需要知道的只是:
当MAXPHYADDR 为36位,在Intel平台的桌面处理器上普遍实现了36位的最高物理地址值,也就是我们普通的个人计算机,可寻址64G空间;
当MAXPHYADDR 为40位,在Inter的服务器产品和AMD 的平台上普遍实现40位的最高物理地址,可寻址达1TB;
当MAXPHYADDR为52位,这是x64体系结构描述最高实现值,目前尚未有处理器实现。

而对下级表的物理地址的存储4K页面寻址遵循如下规则:
① 当MAXPHYADDR为52位时,上一级table entry的12~51位提供下一级table物理基地址的高40位,低12位补零,达到基地址在4K边界对齐;
② 当MAXPHYADDR为40位时,上一级table entry的12~39位提供下一级table物理基地址的高28位,此时40~51是保留位,必须置0,低12位补零,达到基地址在4K边界对齐;
③ 当MAXPHYADDR为36位时,上一级table entry的12~35位提供下一级table物理基地址的高24位,此时36~51是保留位,必须置0,低12位补零,达到基地址在4K边界对齐。

x64页转换模型

X64,准确的说应该是IA32e paging 模型提供了三种页转换模型,
① 4K页面的转换表结构;
② 2M 页面的转换结构;
③ 1G页面的转换结构;
在64位模式下,处理器将48位的虚拟地址转化为物理地址,在兼容模式下,转化32位的虚拟地址。

三种模型都是物理页帧的基地址加上页偏移得到物理地址,不同只是在于页帧的大小划分不同:
①4K页面: 使用PML4T,PDPT,PDT和PT 四级页转化表结构;
②2M页面:使用PML4T,PDPT 和PDT三级页转化表结构;
③1G 页面:使用PML4T和PDPT二级页表转化结构。
而在这里我们主要讨论的是4K页面大小的寻址方式,因为在个人计算机上,普遍都是4K
页面寻址,其他的方式也主要就是页面大小的差异。

x64页转换过程
CR3

(1)当CR4.PCIDE = 0时,CR3的结构如图

CR3可以使用64位宽,但是它表示的PML4T的物理基地址同样受到之前所说的MAXPHYADDR的约束,图示的只是理想的MAXPHYADDR为52位时的情况。

(2)而当CR4.PCIDE = 1的时,CR3的结构如图

CR3的低12位提供一个PCID值,用来定义当前Process Context ID.
当对CR3进行更新时,CR3第63位决定是否需要处理器的TLB和paging-struct cache,这不在我们此次谈论的范围之内。

 PML4E

接着再看PML4E的结构,如图:

PML4E并没有PS标志位,因此第7位是保留的,而PML4E提供的PDPT的物理基地址也受之前的MAXPHYADDR规则的约束。

PDPTE

然后就是PDPTE结构:
由于新增了1G 页面,因此在PDPTE结构里将控制1G的页面转化,由PDPTE.PS标志位进行转换,如图:

当PDPTE.PS=1,也就是PDPTE的第7位为1时,PDPTE将提供1G的物理页面地址;当PDPTE.PS=0,也就是PDPTE的第7位为0时,使用非1G的页面,将提供下一级的PDT的物理基地址,同样受MAXPHYADDR规则的约束。

1G页面下的PDPTE 的结构解析如下:

同样地,PDPTE提供的1G页面的物理地址也遵守MAXPHYADDR的规则,1G页面的地址低30将补0,意味着1G边界上对齐。
4K和2M页面下的PDPTE结构解析如下:

将提供下一级PDT的物理基地址,同样也遵循MAXPHYADDR规则,那么再根据PDE.PS再决定是使用2M页面还是4K页面。

PDE

PDE的结构和PDPTE类似,也是用PS(第7位)表示是使用2M的页面还是4K 的页面,下面是2M 页面的PDE结构解析:

同样对于页面的物理基地址也遵循MAXPHYAD原则。
接下来是4K 页面的 PDE 结构解析,也遵循MAXPHYADDR 规则。

PTE

PTE的结构解析如下,同样遵循MAXPHYADDR规则。

五、Linux内存地址映射实验

参考资料:

基础概念介绍:http://malgenomeproject.org/os2018fall/09_linux_paging_example.pdf

linux32位系统内存实验

http://ilinuxkernel.com/?p=1276   文档 Linux内存地址映射(32位)
实验相关源码下载地址:Memory_Address_Mapping

关于linux查看寄存器如CR3,看了一下需要自己编写内核moudle ,然后加载调用。

下面代码(注意:本源码是32位系统)可用来读取CRn控制寄存器的值。来自https://ilinuxkernel.com/?p=606

#include <linux/module.h>    
#include <linux/proc_fs.h>    
 
static char modname[] = "cr4";
static int cr4;
 
static int my_get_info( char *buf, char **start, off_t off, int count )
{
    int    len = 0;
 
    asm(" movl %cr4, %ebx \n movl %ebx, cr4 ");
    len += sprintf( buf+len, "cr4=%08X ", cr4 );
    len += sprintf( buf+len, "PSE=%X ", (cr4>>4)&1 );
    len += sprintf( buf+len, "PAE=%X ", (cr4>>5)&1 );
    len += sprintf( buf+len, "\n" );    
 
    return    len;
}
 
int init_module( void )
{
    printk( "<1>\nInstalling \'%s\' module\n", modname );
    create_proc_info_entry( modname, 0, NULL, my_get_info );
 
    return    0;
}
 
void cleanup_module( void )
{
    remove_proc_entry( modname, NULL );
    printk( "<1>Removing \'%s\' module\n", modname );
}
 
MODULE_LICENSE("GPL");

内核模块编写教程:
http://tldp.org/LDP/lkmpg/2.6/html/lkmpg.html
https://www.cnblogs.com/wrjvszq/p/4260996.html

linux64位系统内存实验

64位系统,分段产生的虚拟地址是48位,因为段寄存器都设置成0,估计是偏移地址是48位的【GS和FS两个段寄存器没有设置成0,这两个是保留给操作系统自己定义和使用的,CPU没有指定这两个寄存器的功能】,然后看分页情况,映射到物理地址可能是36,40,52位物理地址。

http://ilinuxkernel.com/?p=1303  文档 Linux内核在x86_64 CPU中地址映射

实验相关源码下载地址:Memory_Address_Mapping_x86-64


EMS (Expanded Memory Specification)

扩充内存(expand memory)与扩展内存(extend memory) 

看了一下维基百科:(扩充内存被扩展内存取代了)

https://en.wikipedia.org/wiki/Expanded_memory#EMS

《微型计算机技术》      《微型计算机接口技术》

EMS 好像是一种 实地址模式下的内存扩充技术。

后话

内存分页机制还是需要自己写简单的操作系统,才能更好的明白。

操作系统demo:其实bios 自带了基本的 的驱动,包括磁盘和显示器。【比如进入bios界面,我们就能打开显示器上面的字符,所以编写操作系统时,不需要磁盘驱动,引导代码也只是一段简单的汇编。bios的磁盘驱动是识别扇区,操作系统的文件系统是对扇区的抽象】

独自一人编写一个操作系统是什么概念?

操作系统真象还原 https://github.com/seaswalker/tiny-os

《30 天自制操作系统》中文源码

畅销书《 自己动手写操作系统》再版——《 Orange’S:一个操作系统的实现》

JOS 系统教程

https://github.com/kiukotsu/ucore    https://www.xuetangx.com/courses/TsinghuaX/30240243X/2015_T1/about

http://malgenomeproject.org/os2018fall/

os resources
micron教你如何开发OS:http://code.google.com/p/micron
来自台湾的BenOS:http://ben6.blogspot.tw/search/label/osdev
The Operating System resource center:http://www.nondot.org/sabre/os/articles
SGOS at Xiaoxia:http://code.google.com/p/sgos/
非常尼克斯推荐的实现内核相关文档: http://verynix.com/os-dev-book.html
How to write a simple operating system: http://mikeos.berlios.de/write-your-own-os.html
OS Dev Wiki: http://wiki.osdev.org/Main_Page
Write your own operating system: http://geezer.osdevbrasil.net/osd/index.htm
https://www.kernel.org/doc/ols/2014/ols2014-barbalace.pdf (a replicated-kernel OS which is based on Linux)
 
指尖操作系统: http://os-z.com/
魔芋——OS入门堂: http://blog.csdn.net/flyback/article/category/126349
GridOS(曾经是Future Alpha的网站):http://www.woos.cn/index.htm
miaowangjian的OS专栏:http://blog.csdn.net/miaowangjian
迷途知返的汇编学习笔记:http://pwwang.com/tag/%E6%B1%87%E7%BC%96%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/
IT元素的实模式到保护模式的切换:http://www.ourys.com/post/gdt-descriptor-table.html
MIT couse 6.828 operating system engieering:http://pdos.csail.mit.edu/6.828/2012/

参考:

《x86/x64体系探索及编程》

80286与保护模式

80386的分段机制、分页机制和物理地址的形成

【OS修炼指南目录】—-《X86汇编语言-从实模式到保护模式》读书笔记目录表

https://pdos.csail.mit.edu/6.828/2005/readings/i386/toc.htm

http://www.kerneltravel.net/chenlj/lecture4.pdf

Linux内核源代码情景分析(上册)

关于分段分页的

https://slideplayer.com/slide/9995089/

https://medium.com/hungys-blog/linux-kernel-memory-addressing-a0d304283af3

http://www.scs.stanford.edu/05au-cs240c/notes/l2.pdf

http://www.rcollins.org/ddj/May96/

http://www.rcollins.org/articles/2mpages/2MPages.html

https://yajin.org/os2018fall/09_linux_paging_example_x86_64.pdf

http://www.cs.albany.edu/~sdc/CSI500/Spr10/Classes/C15/intelpaging83-94.pdf

https://blog.csdn.net/SHU15121856/article/details/78868968

http://www.renyujie.net/articles/article_ca_x86_5.php

关于PAE问题:

https://docs.microsoft.com/en-us/previous-versions/windows/hardware/design/dn613969(v=vs.85)

http://www.voidcn.com/article/p-bmzsqmdy-bek.html

https://serverfault.com/questions/247080/how-do-i-find-out-if-pae-is-enabled

关于查看服务器信息的:

https://blog.csdn.net/u012625036/article/details/17397507

https://blog.csdn.net/ghj1976/article/details/6158953

 

内存与调试-用WinDbg从虚拟/线性地址查找出物理地址

一、内存映射规则

从虚拟地址(也叫线性地址),转换到物理地址,必须先要弄明白映射规则。影响内存映射规则的有:

  • 系统位数(32位还是64位)
  • PSE(Page Size Extension)
  • PAE(Physical Address Extension)

具体规则可以去看内存分页模型,我这边是32位的XP虚拟机。

关于PAE:打开我的电脑属性发现没有“物理地址扩展”一行,也就是说当前xp虚拟机没有开启PAE。

关于PSE:我的XP虚拟机在CR4寄存器中标记可开启PSE,但是在页目录表项PDE( page directory entries)的bit7(有bit0实际是第8位),发现是0,表示不启用PSE。

综上我的XP虚拟机采用的是传统两级分页模型。

二、32位系统常规两级分页

线性地址划分是10-10-12 

特别提醒:
1、目录项和页表项中的基地址都是20位的
2、线性地址分解出的目录索引,页索引都是10位的,具体操作时=基地址 +索引数*4 【要特别注意啊】

(因为32位系统一个地址占4字节,如果是64位系统,那么就是 基地址 +索引数*8 因为64位系统一个地址占8字节)

三、动手实验线性地址转物理地址

1、在虚拟测试机中编写实验代码

#include <stdio.h>
int main(int argc, char* argv[])
{
  char testName[38] = "HelloWorld ++ to get phpsics address";
  printf("testName:%x\n",testName);
  printf("testName:%s\n",testName);
  getchar();
  return 0;
}

编译并运行,从下面的截图中我们可以看到字符串的虚拟/线性地址是22ff40

2、虚拟主机开启调试

线性地址是22ff40,32位系统,所以线性地址实际是0022ff40。

前10位是 0000 0000 00
中10位是 10 0010 1111
后12位是 1111 0100 0000

那我们先打开WInDbg,使用KD命令行

========备注,先查找程序的 页目录物理地址
kd> !process 0 0 pse_helloworld.exe
PROCESS 81b0b620  SessionId: 0  Cid: 0660    Peb: 7ffde000  ParentCid: 063c
    DirBase: 0da68000  ObjectTable: e195a780  HandleCount:   7.
    Image: pse_helloworld.exe

========备注,发现页目录物理地址是16进制的 0da68000 ,
========然后因为线性地址的 页目录索引是 0
kd> !dd 0da68000 + 0x0*4 
# da68000 0dd56067 0dc95067 00000000 00000000
# da68010 00000000 00000000 00000000 00000000
# da68020 00000000 00000000 00000000 00000000
# da68030 00000000 00000000 00000000 00000000
# da68040 00000000 00000000 00000000 00000000
# da68050 00000000 00000000 00000000 00000000
# da68060 00000000 00000000 00000000 00000000
# da68070 00000000 00000000 00000000 00000000

========备注,这里找到了页表的物理地址是16进制的 0dd56067,
========基地址是前20位,即0dd56000 + 页表索引是0x22f*4
kd> !dd 0dd56000 + 0x22f*4
# dd568bc 0e1da067 0af0b025 0af0c025 00000000
# dd568cc 00000000 00000000 00000000 00000000
# dd568dc 00000000 00000000 00000000 00000000
# dd568ec 00000000 00000000 00000000 00000000
# dd568fc 00000000 0e09f067 0a260067 0e0e3067
# dd5690c 028f2067 00000000 00000000 00000000
# dd5691c 00000000 00000000 00000000 00000000
# dd5692c 00000000 00000000 00000000 00000000

========备注,这里找到了页表项的基地址是 0e1da067的前20位即 0e1da000
========再加上线性地址的偏移地址 0xf40,所以最终的 物理地址是 0e1da000 +0xf40
========在这里我们成功看到了 物理地址的 内容是 HelloWorld ++ to get physics address
kd> !db 0e1da000 +0xf40
# e1daf40 48 65 6c 6c 6f 57 6f 72-6c 64 20 2b 2b 20 74 6f HelloWorld ++ to
# e1daf50 20 67 65 74 20 70 68 70-73 69 63 73 20 61 64 64  get phpsics add
# e1daf60 72 65 73 73 00 00 40 00-78 ff 22 00 ce 13 40 00 [email protected]."...@.
# e1daf70 35 00 33 00 39 00 38 00-b0 ff 22 00 4b 12 40 00 5.3.9.8...".K.@.
# e1daf80 01 00 00 00 48 37 3e 00-40 29 3e 00 00 40 40 00 ....H7>.@)>..@@.
# e1daf90 a4 ff 22 00 ff ff ff ff-a8 ff 22 00 01 00 00 00 ..".......".....
# e1dafa0 06 00 00 00 40 29 3e 00-00 00 00 00 00 e0 fd 7f ....@)>.........
# e1dafb0 c0 ff 22 00 98 12 40 00-01 00 00 00 09 00 00 00 .."...@.........

3、开启PAE后尝试查找物理地址

官网查询发现,只需要在 boot.ini中添加 /PAE 就行了,效果如下。

[boot loader]
timeout=30
default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS
[operating systems]
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional" /noexecute=optin /fastdetect
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional Test Debug" /fastdetect /PAE /debug /debugport=COM1 /baudrate=115200

xp以上的Windows版本是编辑BCD文件,

32 位元 Windows 7 開啟 PAE:


Start → All Programs→ Accessories→  Command Prompt 點右鍵,

再點 Run as administrator,執行以下命令:
 
bcdedit /set pae forceenable



特别提醒:开启 PAE 需要重新开机才能生效。

按道理这样做就应该成功了啊。但是PAE还是没有开启,因为我的电脑里属性页还是没有显示“物理地址扩展”。这又是一个坑啊。

后来我用Everest Ultimate Edition 查看主板–>内存,发现系统支持PAE,但处理器不支持,也就是说当前xp虚拟机没有开启PAE。

查询后发现,可以在VirtualBox中设设置开启PAE功能。

Select the VM in the list of VMs and click Settings->General->Advanced and there is a checkbox for PAE.

It's now in System > Processor. Please, check the complete VM settings before complaining where to find something. 
Reading the manual is a must in this case, we have it for a reason

然后重新运行那个实验代码:发现结果虚拟地址还是22ff40,果然虚拟地址还是一样的啊。

查阅资料:发现现在的内存模型是PAE,线性地址划分是 2-9-9-12
线性地址是22ff40,32位系统,所以线性地址实际是0022ff40。 
前2位是 00  页目录指针索引 是0
中前9位是 00 0000 001 页目录项索引 是1  
中后9位是 0 0010 1111 页表项索引 十六进制:0x2f  十进制:47 
后12位是 1111 0100 0000 页面偏移 是0xf40

特别提醒:
PAE下的【页目录指针项,页目录项,页表项】是64位的,所以用dq命令,计算索引偏移量的时候,是*8,因为64位地址是8字节的。

页表项的内容就是页框,也就是页框(我叫页面了)的地址,也就是上图说的64bit的 PT entry,64位是地址编号,页框偏移是12位,也就是一个页框内可以有2^12个基本内存单元。每个内存单元是1字节,所以页框大小是4KB。

我们打开虚拟主机,进行调试看看:

kd> !process 0 0 pse_helloworld.exe
PROCESS 820be708  SessionId: 0  Cid: 03ac    Peb: 7ffd8000  ParentCid: 05c8
    DirBase: 07200280  ObjectTable: e104da90  HandleCount:   7.
    Image: pse_helloworld.exe

=====备注:PAE模式下分页内容中35-12位是基地址位,除了PDE.PS=1的情况,详情看内存分页机制
=====PDE 是页目录项,其中bit7也就是第8位,毕竟还有bit0呢
=====页目录指针索引,是0,所以下面没有添加 索引偏移
kd> !dq 07200280
# 7200280 00000000`09d8b001 00000000`09a0c001
# 7200290 00000000`09bcd001 00000000`09e4a001
# 72002a0 00000000`f8d212e0 00000000`07f53001
# 72002b0 00000000`07f94001 00000000`08011001
# 72002c0 00000000`11be9001 00000000`11caa001
# 72002d0 00000000`11d2b001 00000000`11b28001
# 72002e0 00000000`f8d21300 00000000`003f0001
# 72002f0 00000000`004b1001 00000000`004ae001

=====备注:页目录项索引是1
kd> !dq 0`09d8b000 + 0x1*8
# 9d8b008 00000000`0aa35067 00000000`09ab1067
# 9d8b018 00000000`00000000 00000000`00000000
# 9d8b028 00000000`00000000 00000000`00000000
# 9d8b038 00000000`00000000 00000000`00000000
# 9d8b048 00000000`00000000 00000000`00000000
# 9d8b058 00000000`00000000 00000000`00000000
# 9d8b068 00000000`00000000 00000000`00000000
# 9d8b078 00000000`00000000 00000000`00000000

=====备注:页表项索引是0x2f 
kd> !dq 0`0aa35000 + 0x2f*8
# aa35178 00000000`09cb7067 00000000`0b218025
# aa35188 00000000`0b1d9025 00000000`00000000
# aa35198 00000000`00000000 00000000`00000000
# aa351a8 00000000`00000000 00000000`00000000
# aa351b8 00000000`00000000 00000000`00000000
# aa351c8 00000000`00000000 00000000`00000000
# aa351d8 00000000`00000000 00000000`00000000
# aa351e8 00000000`00000000 00000000`00000000

=====备注:页内偏移是 0xf40,又一次成功看到了内存物理地址的内容
kd> !db 0`09cb7000 + 0xf40
# 9cb7f40 48 65 6c 6c 6f 57 6f 72-6c 64 20 2b 2b 20 74 6f HelloWorld ++ to
# 9cb7f50 20 67 65 74 20 70 68 70-73 69 63 73 20 61 64 64  get phpsics add
# 9cb7f60 72 65 73 73 00 00 40 00-78 ff 22 00 ce 13 40 00 [email protected]."...@.
# 9cb7f70 35 00 33 00 39 00 38 00-b0 ff 22 00 4b 12 40 00 5.3.9.8...".K.@.
# 9cb7f80 01 00 00 00 48 37 3e 00-40 29 3e 00 00 40 40 00 ....H7>.@)>..@@.
# 9cb7f90 a4 ff 22 00 ff ff ff ff-a8 ff 22 00 01 00 00 00 ..".......".....
# 9cb7fa0 06 00 00 00 40 29 3e 00-00 00 00 00 00 80 fd 7f ....@)>.........
# 9cb7fb0 c0 ff 22 00 98 12 40 00-01 00 00 00 09 00 00 00 .."...@.........

 


参考:

https://bbs.pediy.com/thread-203391.htm

https://bbs.pediy.com/thread-180989.htm

https://www.twblogs.net/a/5b83db5d2b71777cb15c6302/zh-cn

https://blog.csdn.net/tutucoo/article/details/84729919

https://asemia623.pixnet.net/blog/post/36116779-%E2%98%86%E3%80%90%E5%9C%96%E8%A7%A3%EF%BD%9C%E6%95%99%E5%AD%B8%EF%BD%9C%E4%B8%8B%E8%BC%89%E3%80%91%E5%A6%82%E4%BD%95%E9%96%8B%E5%95%9Fpae%E3%80%81%E5%A6%82%E4%BD%95%E6%9F%A5

https://forums.virtualbox.org/viewtopic.php?p=44859

内存与调试-window内核调试WinDbg、KD环境搭建

为什么会去学内核调试,因为之前学内存管理的时候,介绍过内核调试可以查看进程的物理地址,亲自动手实验,加深理解。

最初看到KD命令行(kernel debug),感觉很神奇,可以查找内存物理地址,查询后发现是内核调试,需要安装软件,然后建立调试环境。

一、安装WinDbg软件

注意:WinDbg软件是安装在主机上的,用于调试测试机。

微软官网下载,Debugging Tools for Windows (WinDbg, KD, CDB, NTSD)

3 ways to get Debugging Tools for Windows

  • As part of the WDKDebugging Tools for Windows is included in the WDK. You can get the WDK here.
  • As a standalone tool set 【感觉独立模式不错,但是没有xp版本的】If you want to download only Debugging Tools for Windows, install the Windows SDK, and, during the installation, select the Debugging Tools for Windows box and clear all the other boxes.
  • As part of the Windows SDKInstall the complete Windows Software Development Kit (SDK). Debugging Tools for Windows is included in the Windows SDK. You can get the Windows SDK here.

记得我下载的是XP版WDK文件GRMWDK_EN_7600_1.ISO,好像不是选择独立模式,将下载好的ISO文件(也叫镜像文件)进行解压。解压出来后,选择Debugging Tools 文件夹就行了。这里已经提取了xp版的Windbg软件,可以直接下载安装。

如果安装出错,可能是缺少类似  Microsoft Visual C++ 2010 Redistributable 等软件

二、配置双机连接

需要两台电脑,一台作为主机,另一台作为被测试机。主机连接到测试机才能进行内核调试。   那没有两台电脑怎么办?

方案一:一台主机,一台虚拟机,虚拟机作为测试机。

方案二:两台虚拟机,一台作为主机,一台作为测试机。

1、开启测试机的debug模式

无论方案一还是方案二,都要开启测试机的debug模式。

对于 Windows XP,直接编辑 C:\\boot.ini 文件。添加新的启动项:

multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional -Debug" /fastdetect /debug /debugport=com1 /baudrate=115200

其中/baudrate=115200,表示串行通信的速率 bps,115200 是串行通信的最大速率,因此使用串行通信进行内核调试时,如果进行频繁的单步跟踪和要传递较大的文件(如 .kdfiles 和 .dump 命令),那么会感觉到速度有些慢。com1是连接端口号。

最终编辑好的boot.ini文件效果如下所示,

[boot loader]
timeout=30
default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS
[operating systems]
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional" /fastdetect
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional -Debug" /fastdetect /debug /debugport=com1 /baudrate=115200

从 Windows Vista 开始,考虑到 boot.ini 文件很容易被恶意软件所修改,因此不再使用 boot.ini 文件,而是使用 Boot Configuration Data—BCD。修改 BCD,需要启动一个管理员权限(Run As Administrator)的命令行窗口。然后使用 bcdedit 命令来编辑 BCD(建议首先将当前启动入口复制一份):

c:\Windows\system32>bcdedit /copy {current} /d "Win7 Debug with Serial port"
The entry was successfully copied to {new_uuid}

c:\Windows\system32>bcdedit /debug {new_uuid} on
The operation completed successfully.

BCD 中有一套全局的调试设置,使用 bcdedit /dbgsettings 可以观察和修改这套全局设置:

c:\Windows\system32>bcdedit /dbgsettings
debugtype Serial
debugport 1
baudrate 115200
The operation completed successfully.

可以看到默认的全局调试设置中,调试类型为 Serial,调试端口为 1,波特率为 115200,这已经是我们需要的设置了。当然也可以通过如下命令再次设置为串口调试:

c:\Windows\system32>bcdedit /dbgsettings serial DEBUGPORT:1 BAUDRATE:115200
The operation completed successfully.

如果希望为某个自启动项设置单独的调试选项,那么可以使用 bcdedit /set:

bcdedit /set {new_uuid} debugtype serial
bcdedit /set {new_uuid} debugport 1
bcdedit /set {new_uuid} baudrate 115200

2、配置虚拟机的连接环境

这个地方是一个大坑,需要特别注意一下。其中方案一的配置和方案二的配置是不一样的,需要特别注意。

方案一的操作:

虚拟机设置方面:

主机操作windbg软件,连接时的配置:

特别提醒:

这种方案里主机肯定是Windows系统,所以pipe管道文件的路径的写法是   \\.\pipe\debugpipe   不像linux系统中的 / xx/xx/  路径写法。

还有虚拟机实际输出文件是pipe,所以主机的WinDbg配置连接时,需要勾选pipe。

方案二的操作:
虚拟测试机的设置:
虚拟主机的设置:
虚拟主机操作WinDbg软件,连接虚拟测试机的配置:
特别提醒:
1、“Port  Path/Address”  的路径是:/tmp/debugpipe  因为我实际电脑主机是mac系统,所以用 “/” 。如果实际电脑主机是Windows的话,那么 “Port  Path/Address”  的路径类似 \\.\pipe\debugpipe
2、虚拟主机和虚拟测试机中,只要有一个创建pipe文件就行了,另一个直接连上已经存在的pipe文件。[大白话就是两台主机的设置界面的connect to existing pipe/socket 只要有一个勾选上就行了]
3、虚拟主机用WinDbg连接虚拟测试机时,不要勾选pipe。因为之前两台虚拟机都已经配置过外部信息采用pipe形式,所以虚拟主机的外部已经自动变成pipe形式和虚拟测试机进行信息交互了,不用多此一举。所以虚拟主机连接虚拟测试机时,不需要勾选pipe形式。

三、添加符号(Symbols)文件

1、什么是符号文件

https://docs.microsoft.com/zh-cn/windows-hardware/drivers/debugger/symbols-and-symbol-files

大白话:符号symbol文件,包含全局变量,局部变量,行号,函数入口地址等等,所有的这些东西都叫符号。符号是方便显示调试信息的。

When applications, libraries, drivers, or operating systems are linked, the linker that creates the .exe and .dll files also creates a number of additional files known as symbol files.
Symbol files hold a variety of data which are not actually needed when running the binaries, but which could be very useful in the debugging process.
Typically, symbol files might contain:
Global variables
Local variables
Function names and the addresses of their entry points
Frame pointer omission (FPO) records
Source-line numbers
Each of these items is called, individually, a symbol. For example, a single symbol file Myprogram.pdb might contain several hundred symbols, including global variables and function names and hundreds of local variables. Often, software companies release two versions of each symbol file: a full symbol file containing both public symbols and private symbols, and a reduced (stripped) file containing only public symbols. For details, see Public and Private Symbols.
When debugging, you must make sure that the debugger can access the symbol files that are associated with the target you are debugging. Both live debugging and debugging crash dump files require symbols. You must obtain the proper symbols for the code that you wish to debug, and load these symbols into the debugger.
Windows Symbols

Windows keeps its symbols in files with the extension .pdb.

2、设置符号文件夹路径

https://docs.microsoft.com/zh-cn/windows-hardware/drivers/debugger/using-a-symbol-server

原理就是:我们设置一个存放符号文件的本地文件夹,WinDbg 会自动从 微软的符号服务器中下载符号文件,然后存放到刚刚指定的本地文件夹。WinDbg具体调试时,就会利用本地文件夹的符号文件。

具体操作:打开WindDbg,选择File菜单,找到Symbol File Path说明:C:\SysCache 是本地下载symbol的文件夹,http://msdl.microsoft.com/download/symbols 是微软符号服务器。

SRV*C:\SysCache*http://msdl.microsoft.com/download/symbols

四、常见问题和简单操作

1、启动顺序问题

方案2中,因为是两台虚拟机,启动时首先要启动创建pipe的那台虚拟机,然后再启动不创建pipe,只连接pipe的虚拟机。

方案1中,因为主机本身就启动了,所以只要启动虚拟测试机就行了。

注意无论是方案一还是方案二,启动虚拟测试机时,都需要选择debug模式启动项,不要选择正常模式启动项啊。

2、debugee not connected

当在主机或虚拟主机上,打开 WInDbg 软件–>file菜单–>kernel debug 选项时,会出现 debug not connetced  。这个是正常现象。因为第一次需要下载symbol 符号文件,然后呢,要进行内核 debug 的话,还要选择debug菜单,执行break操作,这样子才能 完成 两台机器间的通讯连接。

3、常见KD命令

r cr4
读取控制寄存器cr4的内容

!process 0 0 
命令查看当前系统所有进程信息

!process 0 0 notepad.exe
查看文本编辑器的进程信息  ,举例如下,其中 Dirbase 是内存页目录表的基地址

kd> !process 0 0 notepad.exe PROCESS 81dbd5a8 SessionId: 0 Cid: 0754 Peb: 7ffd7000 ParentCid: 05e0 DirBase: 0f255000 ObjectTable: e14c47d0 HandleCount: 50. Image: notepad.exe


!dd db9e000
查看 db9e000 物理地址的内存 显示二进制内容 (32位格式显示内存信息)

!dq db9e000
查看 db9e000 物理地址的内存 显示二进制内容 (64位格式显示内存信息)

!db db9e000
查看 db9e000 物理地址的内存  显示ascii编码后的内容

 

五、VirtualKD

在这里还可以使用VirtualKD自动设置调试。除了自动化之外,VirtualKD的另一个重要特性是性能提升。VirtualKD承诺将Windows内核调试速度提高45倍,这值得花时间进行投资。

由于我不运行Windows主机,我实际上不会这样做; VirtualKD只能在Windows主机上完成,不支持Linux。但是,您可以在此处找到非常好的步骤:http://virtualkd.sysprogs.org/tutorials/install/

参考链接:

https://resources.infosecinstitute.com/introduction-to-kernel-debugging-with-windbg/#gref

https://reverseengineering.stackexchange.com/questions/11367/kernel-debugging-between-two-virtual-machines-not-working

http://neilscomputerblog.blogspot.com/2011/05/vitualbox-kernel-debugging.html

https://www.cnblogs.com/exclm/p/4097576.html

https://www.tenforums.com/tutorials/5558-windbg-basics-debugging-crash-dumps-windows-10-a.html

https://www.tenforums.com/tutorials/5560-configure-windows-10-create-minidump-bsod.html

坑爹生活反思-博客系统待优化点

1、文章标题支持  自定义 生成。

2、目录与标签的问题,如何利用 自定义标签 生成 自定义目录分类。

3、文章主题问题,生活类文章 和  技术类文章,应该是两种不同的风格和样式,相当于两个网站的风格。

4、关于短文章 和长文章  交织在一起的 布局优化

5、关于 图片 和文章 关联问题,删除文章 自动删除图片

6、博客和电子书阅读体验还是不一样的,如何改进。

7、实模式和保护模式,分段与分页管理。两篇文章即有联系又有区别,分开写,还是合在一起写,有没有一种更好的 内容组织模式。既能体现联系,又能反应区别。

字符编码-用Cheat Engine查看内存线性地址及系统内部编码

Windows操作系统,使用的编码是不是UTF-16

答案:应该是utf-16编码。

实验验证:

  • 新建一个txt文本文档,然后写一段英文字符 比如 helloword,默认保存方式是ANSI也就是常见的ASCII编码。
  • 保存完再重新打开该文档。接着打开Cheat Engine ,选择注入文本编辑器的进程,然后搜索hellword字符串,记得勾选utf-16,找到内存地址后,选择查看内存空间,记住该文本内容存放在内存中的地址是虚拟地址(也叫线性地址),且内存上存放文本内容采用的是utf-16 编码,不再是ANSI编码了。

 

字符编码-windows内部编码探讨用notepad++  查看十六进制代码如下:

字符编码-windows内部编码探讨

字符编码-windows内部编码探讨

字符编码-windows内部编码探讨

这里我们可以看到 hellword 字符串在内存中的虚拟地址(即线性地址)是000B386A


参考文章链接:https://social.microsoft.com/Forums/zh-TW/dc75573f-85c3-47a1-b761-97dda06ee847/windows2580520316319953247965292203512999230340325343072126159199?forum=windowsxpzhchs


windows操作系统区别于大多数其他操作系统的特点之一是,它的大多数内部文本串是以16位宽的Unicode字符来存储和处理的。Unicode(统一的字符编码标准)是一个国际字符集标准,它为世界上绝大多数一直的字符集定义了唯一的16位值(有关Unicode的更多信息,请参考www.unicode.org以及MSDN Library中有关的程序车技文档)
因为许多应用程序只处理8位(单字节)ANSI字符串,所以,接受字符串参数的windows的函数都有两个入口点:一个Unicode(宽字符,16位)和一个ANSI(窄字符,8位)。windows的以下几个版本,即windows95、windows98、windowsME,并没有为所有的windows函数实现Unicode的接口,所有如果所设计的应用程序要考虑到运行在这些操作系统之上,则往往使用窄字符版本。如果你调用一个windows函数的窄字符版本,则输入的字符串参数在被系统处理之前,先转成Unicode,而输出的参数则在被返回给应用程序之前,从Unicode转成ANSI字符串。因此,如果你有一个老的服务程序或者一段代码需要运行在windows上,而这份代码是按照ANSI字符串来编写的,那么windows将把ANSI字符转成Unicode,以便于内部使用。然而,windows永远不会转换文件内部的数据——由应用程序来决定是否要存储为Unicode或者ANIS。
在windows的以前版本中,亚洲和中东版本是美国和欧洲核心版本的一个超集,其中也包含了额外的windows函数,它可以满足处理更加复杂的文本输入和排版需求(比如从右至左的文本输入)。到了windows2000,所有语种的版本都包含同样的windows函数。windows不再使用单独的语言版本,而是有一份全球统一的二进制代码,因此,同一份安装可以支持多种语言(只需求加入各种语言包即可)。应用程序也可以利用这一套windows函数,做到同一份应用程序二进制代码可以支持多种语言!

互联网入门-KB / KiB,MB / MiB,GB / GiB区别

1KiB = 1,024Byte
1MiB = 1,024KiB
1GiB = 1,024MiB = 1,048,576 KiB
1TiB = 1,024GiB = 1,073,741,824 KiB

1KB = 1,000 Byte
1MB = 1,000 KB
1GB = 1,000,000 KB
1TB = 1,000,000,000 KB

  • 其中,KiB是kilo binary byte的缩写,指的是千位二进制字节
  • 而KB是kilobyte的缩写,指的是千字节
  • 说白了就是二进制与十进制的区别,也就是两种标准问题:国际单位制 SI 制定的十进制标准和 国际电工委员会 制定的二进制标准,也就是通常所说的SI和IEC标准。

众 所周知,在计算机中是采用二进制,在电脑世界里,以2的次方数为“批量”处理Byte会方便一些,整齐一些。每1024Byte为1KB,每1024KB为1MB,每1024MB为1GB,每1024GB为1TB。

而在国际单位制中TB、GB、MB、KB是“1000进制”的数,为此国际电工协会(IEC)拟定了”KiB”、“MiB”、“GiB”的二进制单位,专用来标示“1024进位”的数据大小;而硬盘厂商在计算容量方面是以每1000为一进制的,每1000字节为1KB,每1000KB为1MB,每1000MB为1GB,每1000GB为1TB,在操作系统中对容量的计算是以1024为进位的,并且并未改为”KiB”、“MiB”、“GiB”的二进制单位,这差异造成了硬盘容量“缩水”。买硬碟回家的时候是不是常常发现怎么买回去的容量,与真实可用的容量相差了一点点呢?

这是因为厂商、电脑使用的单位不同的缘故,500G硬碟厂商是使用1KB = 1,000 Byte 计算,但是到了电脑会变成 1KiB = 1,024Byte 计算。因此使用厂商的算法500GB = 500,000 MB,而到了电脑500,000 MB / 1024 MiB = 488.28125 GB。

来自:https://www.twblogs.net/a/5b8d73a02b717718833e15b6/zh-cn

内存与调试-Cheat Engine 介绍与教程通关

Cheat Engine 简称 CE ,这是一款开源的内存查看和修改软件。作用如下:

  • 1、可以查看和修改别的软件的内存数据。
  • 2、可以修改别的软件的运行逻辑和过程。(也就是注入汇编代码)

官网:https://www.cheatengine.org/

CE 是英文版的,里面自带了一个新手通关教程,不过是英文的,可能看不懂。嗯,没关系,网上有人翻译了带中文教程的中文版CE。点击此处下载CE免安装中文版

感觉通关教程,难道不是很大。

1、下面两个是教程1-8 的图文解说,这里就不具体写了。
  • https://www.52pojie.cn/thread-833773-1-1.html
  • https://blog.csdn.net/cgs_______/article/details/77799091

但是有一个巨坑要讲一下:就是关卡8(即step8),多级指针的问题:

最后一个基址指针,需要设置多次偏移,才能使得指针能够指向最终的目标地址。内存与调试-Cheat Engine 介绍与教程通关

等最后,操作完成,点击改变指针就能进入第九关了。如果最后一个基址指针不设置多次偏移(offset)的话,就永远过了第8关了。

2、第九关教程,有点难度,可以看这里:

经过实际测试,这个版本的CE关卡9教程,和下面的攻略有稍许不匹配,但基本思路是一样的。教程来自  https://zhuanlan.zhihu.com/p/33702835 。

游戏讲的是,你需要调整游戏内部数据,来使得玩家控制的Dave和Eric打败500血的电脑。正常情况自动执行,玩家的血量会很快归0,so just hack it!

1 找到四个血量的地址

首先,用精确数值扫描的方法,找到Dave的血量地址,注意这个地方血量是单浮点型。

如上图,我找到了两个玩家的血量地址,并设置了描述。

接着,以Dave为例,右击,选择“是什么改写了这个地址”,打开debug窗口,并再次攻击Dave,找到相应的代码,如图

打开:“显示反汇编程序”

右击moves [rbx+08],xmm0,选择,找出指令访问的地址。出现一个新的空白对话框,再次对每个角色攻击一次,结果如图。

可以发现,前两个地址正是Dave和Eric的地址,并且如果你之前用类似的方法找到了两个电脑的地址,正是下面两个。故猜测,运行“攻击”时,程序是依次获得该角色的指针位置,放到rbx寄存器中,然后在offset为08的位置,就是该角色的血量,修改这个血量,即角色受到了攻击。那么,要让Dave和Eric胜出,我们就要让他们血量不减少的同时,找到电脑的血量,并使其减少。

2 分析数据

选中四个地址,右击选中“打开选中的地址分析数据”(control+D)。如图

可以发现,每个角色,除了在基址的前几个地址有数据,别的都是0,仔细贯彻这些数据可以发现,在offset为14的位置,玩家的值为1,电脑的值为2。并且多次攻击发现,这个值为定值。故,我们可以采用这一点来区分玩家与电脑,达成目标。

回到汇编代码的窗口,分析命令moves [rbx+08],xmm0,并结合上面的代码,我们发现下图这样的代码:

显然,这是一个简单的改变[rbx+08]位置值的指令。先将[rbx+08](血量)存储的值放到xmm1中,再减掉一个xmm0,然后在放回去。

故在subss xmm1,xmm0处代码注入。

3 注入代码

打开-内存浏览器-工具-自动汇编

再打开模板-代码注入

如图

代码会注入在原代码的上方,并且原来的代码也会保留。其中newmem,originalcode等,是汇编中的位置标志,可以直接用来跳转。

故在newmem下添加代码如下:

cmp [rbx+14],1 //判断是否是玩家
jne originalcode //不是玩家,直接跳转到原代码执行
addss xmm1,xmm0 //是玩家,将相减改为相加。也可以去掉这一句,即不对玩家血量进行修改
jmp exit //调到exit处

然后点击执行。回到tutorial程序,重启游戏并自动执行,结果如图

至此,解决了CE的最后一个问题,成功通关。

总的来说,CE是一个相当强劲的修改器,用完之后也深深感到制作者设计之精妙,Cheat Engine :: Index 官方论坛里还有更多的奇技淫巧,有兴趣的朋友可以去看看。


3、CE有什么用,答用于内存分析:

1、可以看到Windows 内存中 字符串编码是utf-16 形式。字符编码-用Cheat Engine查看内存线性地址及系统内部编码

2、可以研究内存分页机制 (下面的文章大体OK,但有部分错误)

https://blog.csdn.net/tutucoo/article/details/84729919

https://www.twblogs.net/a/5b83db5d2b71777cb15c6302/zh-cn

互联网入门-shtml,html,xhtml,dtd,xsd,htm的含义与解析

一、Html与Xhtml 的区别与联系

XHTML1.0是“3种HTML 4文件根据XML 1.0标准重组”而成的,W3C推荐标准。

XHTML 1.0 Strict(严格版)是参照“HTML 4.01 Strict”改编,但不包括被弃用的元素。其文件类型描述为:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

XHTML 1.0 Transitional(过渡版)是参照“HTML 4.01 Transitional”改编,包括已于Strict版本被弃用的呈现性元素(例如<center><font>等)。其文件类型描述为:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

XHTML 1.0 Frameset(框架版)是参照“HTML 4.01 Frameset”改编,并允许于网页中定义框架元素。其文件类型描述为:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">

xhtml1.0与html4.01的区别 https://www.w3.org/TR/2002/REC-xhtml1-20020801/#diffs

XHTML是XML的一种应用。基于这一事实,那些在以SGML为基础的HTML 4中,不完全合法的用法,应被改写。

文档应该是结构良好的

良好结构(Well-formed)[3]是由XML[4]引入的一个新概念。也就是说所有的元素都必须有结束标签或者以特殊的方式书写(如下所述),而且所有的标签必须合理地嵌套。

尽管如此,交叉使用在SGML中仍然是合法的,而且在现有的浏览器中也能够被广泛接受。

正确:元素嵌套

<p><em>这是一个要强调的段落。</em></p>
错误:元素交叉

<em><p>这是一个要强调的段落。</em></p>

元素名称和属性必须小写

XHTML文件要求所有的HTML元素名称和属性名称都要小写。因为XML本身大小写意义不同,因此必须区分开来。比如,<li><LI>是完全不同的。

要有结束标签

基于SGML的HTML 4里面,允许特定的标签省略结束标签;这些元素暗含有结束标记。XHTML不允许省略结束标记。所有元素(包括在DTD中声明为空的标签),都必须有结束标签。在DTD中声明为空的元素可以用结束标签或者使用空元素速记法(参见空元素)。

正确的:结束的元素

<p>這是一個段落。</p><p>這是另一個段落。</p>
錯誤的:没有结束的元素

<p>這是一個段落。<p>這是另一個段落。
等等等。。。直接看原文
HTML 4.01和XHTML 1.0之间的相似性导致许多网站和内容管理系统采用最初的W3C XHTML 1.0建议书。为了帮助作者过渡,W3C提供了有关如何以HTML兼容的方式发布XHTML 1.0文档的指导,并将它们提供给非XHTML设计的浏览器。

这种“HTML兼容”内容使用HTML媒体类型(text/html)而不是XHTML(application/xhtml+xml)的官方Internet媒体类型发送。

二、html与htm的区别

没有区别,但因DOS、Windows 95等早期系统采用的8.3命名规则只支持最长3字符的扩展名,为了兼容采用了htm,而linux或者ios都是支持多位扩展名的。

同理还有 jpg与jpeg 的区别。

三、shtml 与html的区别

本质上都属于静态网页一种,html属于纯静态,客户端浏览器读取html文件是什么就呈现给浏览者什么内容。而shtml则可以使用SSI。

SSI是为WEB服务器提供的一套命令,这些命令只要直接嵌入到HTML文档的注释内容之中即可。如:
<!–#include file=”info.htm”–>
就是一条SSI指令,其作用是将”info.htm”的内容拷贝到当前的页面中,当访问者来浏览时,会看到其它HTML文档一样显示info.htm其中的内容。

假如我们A页面是shtml的静态网页,而A页面里我们使用了include包含嵌入B静态html页面,如果你的服务器空间支持Shtml SSI这个时候我们,浏览器打开A页面时候,就会在A页面显示A原本内容以及B页面内容,我们查看网页源代码,不会发现B页面引入痕迹,而是看到B页面内容完全在A页面里。

假如:
1、A shtml页面里内容是:
我包含页面B:<!–#include file=”b.html”–>

2、B html网页内容:
我是B页面内容

3、这个时候浏览器查看A页面HTML源代码:
A shtml页面里内容是:我是B页面内容

这个就是shtml ssi 包含include魅力之处。

其它的SSI指令使用形式基本同刚才的举例差不多,可见SSI使用只是插入一点代码而已,使用形式非常简单。

当然,如果WEB服务器不支持SSI,它就会只不过将它当作注释信息,直接跳过其中的内容;浏览器也会忽略被包含信息,我们可以查看源代码看到include引入注解信息。

四、dtd的含义

经常能够看到dtd,那到底是什么意思呢?(ps,提前剧透一下,现在dtd已经被xsd取代了)

这首先要从xml,说起:下面举例的是早期2.3版本的web.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app id="WebApp_ID">
	<display-name>q</display-name>
	<welcome-file-list>
		<welcome-file>index.html</welcome-file>
		<welcome-file>index.htm</welcome-file>
		<welcome-file>index.jsp</welcome-file>
		<welcome-file>default.html</welcome-file>
		<welcome-file>default.htm</welcome-file>
		<welcome-file>default.jsp</welcome-file>
	</welcome-file-list>
</web-app>

第一行是 XML 声明。它定义 XML 的版本 (1.0) 和所使用的编码 (UTF-8)。

下面的是DOCTYPE声明。DOCTYPE是document type(文档类型)的简写,它规定了文档类型,文档类型遵循的协议名,以及文档类型协议的具体定义(也就是DTD,Document Type Definition)

在html中,我们也能看DOCTYPE,比如:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

说白了,dtd就是定义了文件中可以使用哪些标签,以及标签的用法。

所有的 XML 文档(以及 HTML 文档)均由以下简单的构建模块构成:

  • 元素
  • 属性
  • 实体
  • PCDATA
  • CDATA

(DTD就是用来定义这些模块的),dtd教程参考http://www.w3school.com.cn/dtd/index.asp

五、xsd的含义

XML Schema 是基于 XML 的 DTD 替代者。
XML Schema 描述 XML 文档的结构。
XML Schema 语言也称作 XML Schema 定义(XML Schema Definition,XSD)。
参考教程http://www.w3school.com.cn/schema/index.asp

举个例子:3.1新版的web.xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
  <display-name>o2o</display-name>

</web-app>

第一行,没有变化,第二行不再是DOCTYPE了。

先讲xmlns,这个是xml namespace 的含义,也就是 xml 命名空间。
我重新将web.xml的代码编排一下,便于理解。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" 
id="WebApp_ID" version="3.1">
  <display-name>o2o</display-name>

</web-app>xmlns="http://xmlns.jcp.org/xml/ns/javaee"

首先xmlns="http://xmlns.jcp.org/xml/ns/javaee" 代表了整个web.xml的默认命名空间。
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  表示如果标签遇到xsi:xxx的,就以这个http://www.w3.org/2001/XMLSchema-instance命名空间为准。
xsi:schemaLocation 后面跟的是一些列键值对,记录的是 <命名空间,命名空间的定义>

下面是springmvc.xml的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
		http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">

。。。
</beans>

说明:上述例子中的命名空间xsi调用方法  xsi:schemaLocation或xsi:noNamespaceSchemaLocation 其实就是xsd中对命名空间调用方法进行设置。

这显然是逻辑自恰的,我们在xsd文件中,对命名空间调用一些方法进行设置。那么在xml文件中,自然就可以在命名空间中识别这些设置了。至于xml头中的xsi:schemaLocation,其实也是对命名空间xsi的设置。设置的目的就是将相应的命名空间和xsd文件进行键值对关联。至于xsi的命名空间,因为在本xml文件中,没有用到该命名空间,所以xsi是炮灰,纯粹就是为了让别的命名空间和xsd进行关联。

参考

https://www.w3.org/TR/xmlschema-1/#Instance_Document_Constructions

https://stackoverflow.com/questions/34202967/xmlns-xmlnsxsi-xsischemalocation-and-targetnamespace