关于“保护模式的权限级别”

关于“保护模式的权限级别”
 在保护模式下,所有的应用程序都有权限级别(Privilege Level,简写为PL),这个权限级别按优劣次序分为四等:0,1,2,3。如果应用程序拥有第0级的权限(也就是说其PL=0或者说它是运行在Ring 0的应用程序),那它就可以执行所有的指令并访问所有数据;如果应用程序拥有的权限级别是第3级(即其PL=3或者说它运行在Ring 3),那它就被关在“镀金的笼子”里了:它能执行的指令是有限的,能访问的数据也是有限的。在Windows中,一般来说,用户的应用程序是运行在Ring 3级的(就是用Visual C++,Borland C++,Visual Basic,Delphi,Borland C++ Builder等SDK工具开发出的应用程序啦),也就是说享有的权限是最低的——换言之,受到了保护模式的“保护”,至少用户的应用程序没有权限去破坏操作系统,因为操作系统是运行在Ring 0级的。
 有了这种权限级别,操作系统就有机会在中断和I/O操作上产生“虚拟”效果。下面就以Windows为例详细讲讲这种虚拟中断和I/O的机制。
 由于操作系统的PL为0,所以可以捕获PL不为0的应用程序的中断或I/O请求,然后建立缓冲队列,再一一串行处理。例如,在Windows的DOS窗口中,应用程序更新显示的操作并不是直接送往显卡的,这些操作都被Windows捕获了,Windows负责在应用程序和显卡之间建立一个虚拟的显示设备Virtual Display Device(VDD),一面把应用程序(同一时刻不止一个应用程序)的I/O操作串行化后送往显卡,再及时更新Virtual Video Display。其结果是,Windows的桌面上有多个DOS窗口在分别刷新,它们都认为自己直接操纵着显卡,而实际上,显卡只有一块。另外,由此也可以想象得到,基于这样一种机制,Windows下图象的显示速度远比不上实模式下的DOS。这也是为什么要专门用WinG,DirectX来制作Windows下游戏的原因(利用WinG,DirectX,我们可以实现在保护模式下直接写屏)。
 利用保护模式的这种机制,我们可以制作“软件狗”。现在一种常见的软件加密手段是在PC机的串口或并口上接一块硬件卡(即“硬件狗”),当软件运行时便进行I/O操作,以从“硬件狗”那里获得版权认证。在Windows下,我们可以用运行在Ring 0的VxD来捕获I/O操作,假冒“硬件狗”来与软件交互,从而达到破解的目的。
 我们可以通过检查CS寄存器的前两位来获知当前应用程序的权限级别CPL(Current Privilege),比如说下面的代码(这是保护模式下的代码,可千万别拿DOS下实模式的程序来比划):
 mov  ax,cs
 or  ax,3  ;现在ax寄存器中就是当前应用程序的权限级别。
 聪明的你也许马上会说:“那我把CS寄存器的值给改了,不就可以改变当前应用程序的权限级别(CPL)了吗?”是啊,真希望事情变得这么简单,这样我就不用继续向下写了(^_0)。然而,事实是,你无法通过直接改变CS寄存器的值来改变CPL。因为当CPL=3时,许多指令是无法执行的,包括直接改写CS来改变CPL的操作。怎么样,受“保护”的滋味不是很好受吧?
 在Windows3.1,Win95下,I/O操作是不受IOPL困扰的,也就是说你可以在运行于Ring 3的应用程序中直接作I/O操作。但是在Windows NT下,我们连这点权力都被剥夺了。要想进行I/O操作,你必须准备好历经重重困难(如果你认为写Kernel Mode Driver是易如反掌的话,我就只有承认我错了)。
 还记得Windows时常出现的GPF(General Protection Fault)吗?就是那些令人恐惧的蓝屏。出现GPF通常是因为发生如下操作:
 1.对具有“只读”性质的段进行“写”操作。
 2.使用空的段选择器(Null Selector)。
 3.向CS寄存器中加载不可执行的段。
 4.段的越界操作(别忘了在保护模式下段也是有大小限制的,只是这个大小不定)。
 5.任务切换时的异常,比如说重入。
 6.如果CPL大于IOPL(当前应用程序的权限级别小于进行I/O操作所需的权限级别)。
 有些GPF并不会使Window出现蓝屏,比如说在Windows中I/O操作就看上去与实模式的DOS里没什么区别,其实这只是表面现象。假设当前IOPL=0,应用程序的CPL=3,这时进行I/O操作,在Windows内部是发生了GPF的,只是Windows代替CPL=3的应用程序进行I/O操作,然后再把结果送回CPL=3应用程序。在DOS里很简单的事,在Windows下却是如此麻烦,难怪应用程序在Windows下跑不快,因为每一条指令都要经过重重的权限盘查。
4MB——2GB:
 这部分内存空间,被称为“Win32应用程序私有内存区”。Win32应用程序的代码、数据和资源都存放在这段内存中了,这部分内存,对于每个Win32应用程序来说都是私有的。一般来说,如果此段内存的数据被改写,那不大可能是别的Win32应用程序干的。这里是最能体现保护模式分页机制作用的地方。两个Win32应用程序,对相同的线性地址(4MB——2GB范围内)进行读写,实际上,它们是在对不同的物理地址进行读写。让我们算一下,这段内存对应多少个Page Directory Entry。我们知道一个Page Directory Entry对应4MB线性内存,那4MB——2GB的内存就对应于1024 / 2 – 1 = 511个Page Directory Entry。也就是说Win95通过操纵这511个Page Directory Entry实现了Win32应用程序“独立”的线性地址空间。打个比方来说吧,现在有511个抽屉,上帝告诉A说:这些抽屉里都是金币,同时告诉B说:这些抽屉里都是银币。并规定只有上帝能打开抽屉。其实抽屉里可能只有一枚铜板,也可能什么都没有。这时,A想看看某个抽屉里到底是不是金币,于是上帝就背地里临时往那个抽屉中放一个金币,然后打开抽屉让A看,于是A就信了。这个技俩同样作用于B,B也相信了那511个抽屉里都是他想要的银币。保护模式下的操作系统(这里当然指Win95)就相当于上帝,而被骗的A和B就相当于运行于保护模式下的Win32应用程序。那只操纵抽屉的上帝之手就是分页机制。
2GB——3GB:
 这部分内存空间我们称之为“应用程序共享内存区”,这里存放着Ring 3级应用程序需要共享的数据和代码。其中包括Win95系统DLL(如User32.dll,Kernel32.dll等)、内存映射文件、Win16应用程序以及DPMI调用分配的内存。
对于内存映射文件(通过CreateFileMapping API函数实现的),我想你一定不陌生,这是那些讲Win95应用程序开发的书中必提的一项技术。另外要提到的是,Win95保留了Windows3.1下的一个API函数GlobalAlloc,这个函数,乍看名时挺神奇的,好象它可以实现Global Alloc,其实不然,它只能分配到Per-Process的内存。别指望GlobalAlloc函数实现Win32应用程序间的数据共享。
通过把要共享的数据映射到2GB——3GB之间的线性内存空间,可以实现Win32应用程序间的数据共享。
 Win16应用程序是需要共享线性地址空间的,这是Windows 3.1的历史遗留问题。为了使以前运行于Windows 3.1的Win16应用程序能在Win95下有同样的运行效果。于是Microsoft决定把Win16应用程序放到这段共享内存区来运行。应该说这是比较合理的决策:既省时省力又保证了对上一代产品的良好兼容性(这里兼容性可以解释为好的坏的一并接受)。
 再从分页机制的角度上来思考一下这段“共享内存区”是如何实现的。2GB——3GB的线性空间对应着256个Page Directory Entry。无论谁在运行,Win95都不会改变这256个Page Directory Entry,也就是说2GB——3GB的线性地址空间对应的物理地址是一样的。这就样,Win95什么也不用作就实现了2GB——3GB线性地址空间的内存共享。
 很好笑,不是吗?对于Win32应用程序开发者来说,实现“数据共享”要比实现“独立的线性地址”困难得多,而对于操作系统来说,实现“数据共享”是如此简单,而实现“独立的线性地址”却是颇为费事。
3GB——4GB:
 这部分内存空间称为“系统内存区”。只有Ring 0级的VMM和VxD可以访问这部分内存空间。这部分内存也是共享的。虽说Ring 3的应用程序无法直接共享这部分内存空间(也就是说SDK中讲述的方法无法做到),但是我们还是称之为共享内存区,至少从分页机制的角度上说,对应于这部分内存的Page Directory Entry是不变的。其实Win32应用程序还是有办法访问这部分内存空间的。一般的作法是,在VxD中分配一块内存,然后把指向那块内存的32位地址指针传给Win32应用程序,这样就可以在Win32应用程序中直接访问那块内存了。前面我们在讲0——4MB内存空间时,提到High-linear address,即DOS VM备份的地方,就在3GB——4GB内存空间中。
4MB——2GB:
 这部分内存空间,被称为“Win32应用程序私有内存区”。Win32应用程序的代码、数据和资源都存放在这段内存中了,这部分内存,对于每个Win32应用程序来说都是私有的。一般来说,如果此段内存的数据被改写,那不大可能是别的Win32应用程序干的。这里是最能体现保护模式分页机制作用的地方。两个Win32应用程序,对相同的线性地址(4MB——2GB范围内)进行读写,实际上,它们是在对不同的物理地址进行读写。让我们算一下,这段内存对应多少个Page Directory Entry。我们知道一个Page Directory Entry对应4MB线性内存,那4MB——2GB的内存就对应于1024 / 2 – 1 = 511个Page Directory Entry。也就是说Win95通过操纵这511个Page Directory Entry实现了Win32应用程序“独立”的线性地址空间。打个比方来说吧,现在有511个抽屉,上帝告诉A说:这些抽屉里都是金币,同时告诉B说:这些抽屉里都是银币。并规定只有上帝能打开抽屉。其实抽屉里可能只有一枚铜板,也可能什么都没有。这时,A想看看某个抽屉里到底是不是金币,于是上帝就背地里临时往那个抽屉中放一个金币,然后打开抽屉让A看,于是A就信了。这个技俩同样作用于B,B也相信了那511个抽屉里都是他想要的银币。保护模式下的操作系统(这里当然指Win95)就相当于上帝,而被骗的A和B就相当于运行于保护模式下的Win32应用程序。那只操纵抽屉的上帝之手就是分页机制。
2GB——3GB:
 这部分内存空间我们称之为“应用程序共享内存区”,这里存放着Ring 3级应用程序需要共享的数据和代码。其中包括Win95系统DLL(如User32.dll,Kernel32.dll等)、内存映射文件、Win16应用程序以及DPMI调用分配的内存。
对于内存映射文件(通过CreateFileMapping API函数实现的),我想你一定不陌生,这是那些讲Win95应用程序开发的书中必提的一项技术。另外要提到的是,Win95保留了Windows3.1下的一个API函数GlobalAlloc,这个函数,乍看名时挺神奇的,好象它可以实现Global Alloc,其实不然,它只能分配到Per-Process的内存。别指望GlobalAlloc函数实现Win32应用程序间的数据共享。
通过把要共享的数据映射到2GB——3GB之间的线性内存空间,可以实现Win32应用程序间的数据共享。
 Win16应用程序是需要共享线性地址空间的,这是Windows 3.1的历史遗留问题。为了使以前运行于Windows 3.1的Win16应用程序能在Win95下有同样的运行效果。于是Microsoft决定把Win16应用程序放到这段共享内存区来运行。应该说这是比较合理的决策:既省时省力又保证了对上一代产品的良好兼容性(这里兼容性可以解释为好的坏的一并接受)。
 再从分页机制的角度上来思考一下这段“共享内存区”是如何实现的。2GB——3GB的线性空间对应着256个Page Directory Entry。无论谁在运行,Win95都不会改变这256个Page Directory Entry,也就是说2GB——3GB的线性地址空间对应的物理地址是一样的。这就样,Win95什么也不用作就实现了2GB——3GB线性地址空间的内存共享。
 很好笑,不是吗?对于Win32应用程序开发者来说,实现“数据共享”要比实现“独立的线性地址”困难得多,而对于操作系统来说,实现“数据共享”是如此简单,而实现“独立的线性地址”却是颇为费事。
3GB——4GB:
 这部分内存空间称为“系统内存区”。只有Ring 0级的VMM和VxD可以访问这部分内存空间。这部分内存也是共享的。虽说Ring 3的应用程序无法直接共享这部分内存空间(也就是说SDK中讲述的方法无法做到),但是我们还是称之为共享内存区,至少从分页机制的角度上说,对应于这部分内存的Page Directory Entry是不变的。其实Win32应用程序还是有办法访问这部分内存空间的。一般的作法是,在VxD中分配一块内存,然后把指向那块内存的32位地址指针传给Win32应用程序,这样就可以在Win32应用程序中直接访问那块内存了。前面我们在讲0——4MB内存空间时,提到High-linear address,即DOS VM备份的地方,就在3GB——4GB内存空间中。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值