地址映射
两种模式*两种模型
实模式
和保护模式
-
实模式
和保护模式
相对,实模式运行于20
位地址总线,保护模式则启用了32
位地址总线,地址使用的是虚拟地址,引入了描述符表; -
实模式
程序和程序之间基本上没有隔离和保护,系统容易死机蓝屏。 -
保护模式
主要保护的就是我们内存资源,实现应用层和内核层,应用层与应用之间的数据访问隔离,防止被非法访问,非法破坏,非法修改。而做到这一点的就是通过分段分页机制
实现的。所以保护模式
下要访问内存,需要一个地址转换
(虚拟地址转化成物理地址)的过程, - 虽然二者都引入了段这个概念,但是实模式的段是
64KB
固定大小只有16
不同的段(64KB*16=1MB
),cs,ds存放的是段的序号(模16的地址)。 - 保护模式则引入GDT和LDT段描述行表的数据结构来定义每个段。cs,ds存放的是
段选择子
。 - 简而言之,
保护模式
是为了保护的就是我们内存资源,所以为了增加一些安全属性,才引入了分段分页机制。实模式
下没有虚拟地址逻辑地址的概念,直接这样算出地址cs*16+ip
,所有的地址都是物理地址。
扁平模型
和分段模型
-
扁平模型
和分段模型
相对,区别在于程序的线性地址是共享一个地址空间还是需要分成多个段,即多个程序同时运行在同一个CS,DS的范围内,还是说每个程序都拥有自己的CS,DS。-
扁平模型
指令的逻辑地址要形成线性地址,不需要切换CS,DS; -
分段模型
的逻辑地址,必须要经过段选择子
去查找段述符,切换CS,DS,才能形成线性地址。 - 简而言之,在
实模式
下,分段模型
的出现只是为了解决寄存器位数和地址总线位数不匹配,才需要使用是用段寄存器左移n位+基址寄存器来间接寻址,如果寄存器位数和地址总线位数匹配,就可以直接寻址了,不再需要使用段寄存器了,此时就是扁平模型
。在保护模式
下,分段的概念发生变化,引入GDT和LDT段描述行表的数据结构来定义每个段。cs,ds存放的不再是段地址,而是存放的是段选择子
,但不叫分段模型
,而是称为扁平模型
-
全部寻址模型
- x86体系结构下,使用的较多的内存寻址模型主要是
实模式分段模型
(只是为了向后兼容,模拟8086/8088的doc环境才会用到)和实模式扁平模型
- 1.
实模式分段模型
real mode segment model(之前16bit系统)- 在早期8086时代的寻址方式,那时候寄存器才16位(只能表示64KB的内存空间),但地址总线是20位(能表示1MB的内存空间),问题来了,用16bit的寄存器的确来表示段内偏移,但段的起始地址是
20bit
的,而寄存器只有16bit,所以可以通过让段的起始地址对齐到16
(也就是说要求段的起始地址能被16整除,也就是低4bit
都为0),这样20bit的内存空间),存放的时候只需要存放段的起始地址中高16bit数据即可,从而16bit的寄存器也可以存放段的起始地址了。故段的起始地址右移4位存放在CS,DS等寄存器中,用IP寄存器存放段内偏移。addr= cs<<4+ip
实现了用16bit寄存器寻址20bit的内存空间。 - 对于8086/8088运行在实模式的程序,其实就是运行在
实模式分段模型
中。对于不同的程序,有不同的CS,DS值,每个程序的段起始址都不同。对于这样的程序而言,偏移地址16位的特性决定了每个段只有64KB大小。 - 16位寄存器(64K),20位地址(1M)
- 每个段64KB,共16个段
- cs/ds寄存器存放的是段的起始(16学节对齐,末尾4位为0,可不存,相当于seg>>4),ip等寄存中放段内偏移
- 在早期8086时代的寻址方式,那时候寄存器才16位(只能表示64KB的内存空间),但地址总线是20位(能表示1MB的内存空间),问题来了,用16bit的寄存器的确来表示段内偏移,但段的起始地址是
- 2.
实模式扁平模型
(real mode flat model)- 该模式只有在
80386
及更高的处理器中才能出现。 - 80386的实模式就是指CPU可用的地址线只有20位,能寻址0~1MB的地址空间。注意:80386的实模式并不等同于8086/8088的实模式,后者的实模式其实就是
实模式分段模型
。 -
扁平模型
,意味着我们这里不使用任何的分段寄存器。(其实还是使用了CS,DS,只是不用程序员去显式地为该寄存器赋值,jmp指令时就已经将CS,DS设置好了)
- 该模式只有在
- 3.
保护模式扁平模型
protected mode flat model(现在的32bit/64bit系统)- Linux,Vindow XP/7采用的内存寻址型
- Linux中,段主要分为4种,即为内核代码段,内核数据段,用户代码段,用户数据段。对于
内核代码段和数据段
而言CS,DS的值是0xC000 0000
,而用户代码和数据段
的CS,DS的值是0x0000 0000
- 当CPU运行32位模式,不管怎样,寄存器和指令都可以寻址整个线性地址空间,所以根本就
不需要再去使用基地址
。基址可以设为一个统一
的值 - 32/64位寄存器,32/64位地址
- 1个寄存器就可以寻址整个线性地址空间cs/ds寄存器值固定为0或者定值,无需再参与地址计算
- 逻辑地址到物理地址:页表,CR3,应用层和内核层公用CR3(会产生漏洞)
- Linux中,段主要分为4种,即为内核代码段,内核数据段,用户代码段,用户数据段。对于
- Linux,Vindow XP/7采用的内存寻址型
- 4.
保护模式分段模型
不存在,怎么理解呢- 首先在
保护模式
下,分段的概念发生变化,引入GDT和LDT段描述行表的数据结构来定义每个段。跟实模式分段模型
不一样. - 如果从以前的角度来理解,在x86和x64系统中寄存器位数和地址总线的位数是匹配的,不需要使用`分段模型。
- 首先在
保护模式下地址转换的过程
- 宏观来看:内存空间是先分段(代码段,数据段,栈等),段内再页(page,4k)
- 从逻辑地址到物理地址的翻译过程叫
寻址
。
逻辑地址
逻辑地址
是编译器生样的进程地址空间,类似的逻辑地址,甚至很多可能相成的,使用c语言指针的值就是逻辑地址。逻辑地址由段地址
(选择符)+段内偏移
组成
分段机制
段寄存器
-
段寄存器
就是为了段机制而存在的,但在不同的模式下,他们的作用,存放的数据各不一样。 - 在8086
实模式分段模型
- 段寄存器存放的是实实在在的段的起始地址,而且地址必须能被16整除的(就是说低四位都是0)
- 在实模式下没有虚拟地址逻辑地址的概念,直接这样算出地址
cs*16+ip
(是为了克服在实模式下,16bit的寄存器无法寻址20bit地址总线的内存空间的问题)
-代码
段寄存器CS
表示程序
的段地址 -
数据
段寄存器DS
表示操作数
的段地址 -
堆栈
段寄存器SS
表示堆栈
的段地址 -
附加
段寄存器ES
表示辅助据段
的段地址
- 在现在x86/x64
保护模式扁平模型
- 6个段寄存器称为cs、ss、ds、es、fs和gs
- cs,ss,ds代码段,栈段,数据段用;es,fs,gs任意数据段通用
- 段寄存器不再存放段的起始地址,而是是存放
段选择符
段的属性
-
段基地址
( Base Address)
段基地址规定线地址空间中段的开始地址 -
段界限
(Limit)
段的大小(20位表示,段属性中的G位指定粒度为1B或4KB,就是单位不同) - 基地址和界限定义了段所映射的线性地址的范围,超出范围外的内存就是不合法的了。
- 段有两种
增长方向
(由段其他属性指定 ):低地高地址(代码段);从高地址往低地(堆栈段) -
段的分类
代码段(CS),数据段(DS,ES),栈(SS),状态段(TR,在进程切换的时候用来保存进程的上下文寄存器中的值)
段描述符
-
段描述符
表示段的基地址,界限和属性的数据结构称为段描述符占8个字节(64位,低32位和高32位)
- 基地址分开储存,段界限分开存储
-
G
:段界限粒度( Granularity位:0表示界限粒度为1字节;1表示界限粒度为4K字节 -
DB
:代码段(D):1表示32位地址和操作数,0表示16位地址和操作数;
向下展数据段(B):1表示上部界限为4G,0表示64K;
栈段(B):1表示32位栈顶指针ESP,0表示16位SP -
AVL
位是软件可利用位:忽略 -
P
:存在 Present位,1存在内存,0表示不存在访问异常 -
DPL
:描述符特权级( Descriptor Privilege level共2位,(0,3)规定了所描述段的特权级,用于特权检查。 -
TYPE
:存储段的具体属性(4bit)
-
代码段描述符
:代表代码,它可以放在GDT或LDT中。置S标置为1。 -
数据段描述符
:代表数据段,代表代码它可以放在GDT或LDT中。置S标置为1。 -
任务状态段描述符
:代表任务状段,用于保存处理器奇存器的内容。它只能出现在GDT中,根据据相应的进程是否正CPU上运行,其Type学段的值分别为11或9。置S标志置为0. -
一致代码段
低级别(度用层,3级)能访问的高级别(内核级,0级)共享出来的代码段。非一致伏码段
就是只能本级别访问的代码段,低级不能访问高级的,高级不能访问。比如rpl是3,dpl是0,属于低级别访问高级别代码,在C=1时,即一致性代码段情况下,合法,否则不合法。 -
S
:代码段和数据段为1,任务状态段(TR)为0
-
段描述符表
(GDT,LDT)
段描述符是存放在段描述符表里的。有三种类型的描述符:
-
全局描述符表GDT
( Global Descriptor Table),表起始地址存放GDTR寄存器中 -
局部描述符表LDT
( Local Descriptor Table,表起始地址存放LDTR寄存器中 -
中断描述符表IDT
( Interrupt Descriptor Table),表起始地址存放IDT寄存器 - 全局描述符表GDT和中断描述符表IDT只有
一张
,局部描述符表可以有若干张(每个任务可以有一张) -
全局描述符表GDT
含有每个任务都可能或可以访问的段的描述符,通常包含描述操作系统所使用的代码段、数据段和堆栈段的描述符,也包含一些特殊的数据段描述符。就是说,全局描述符表GDT是可以被共享的
。 - 通过LDT可以使各个任务
私有
的段与其它任务相隔离,从而达到受保护的目的。(比如某个进程的指针跑飞了,至少不会破坏别的进程的数据)而通过GDT可以使各任务都需要使用的段能够被共享。 - 一个任务可使用的整个
虚拟地址空间
分为相等的两半,一半空间的描述符在GDT中,另一半空间的描述符在LDT中。(比如32bit系统中,地址空间为4G,高地址2G(内核的)存放GDT中,低地址2G(应用进程的)存放LDT中) - 在任务切换时,切换LDT,并不切换GDT
段选择子
- 保护模式下段寄存器(CS,DS,ES,SS等)里存放的是
段选择子
,16bit,分为3部分:
-
RPL
:最低两位是请求特校级RPL( Requested Privilege Level),用于特权检查,0最高级别,3最低级别。CPU的当前特权级CPL
,就是内核层和应用层)每当一个代码段选择子装入CS寄存器中时,处理器自动地CPL存放到CS的RPL字段。 -
TI
:段选择子的第2位是引用描述符表指示位,标记为TI( Table Indicator)Tl=0指示从全局描述符表GD中读取描述符:T=1指示从局部描述符表LDT中读取描述符。 -
Index
段选择子的高位是描述符索引(index)所谓描述符索引是指段描述符在描述符表的序号。
-
-
段描述符高速缓寄存器
- 提高访问速度,不用去遍历GDT和LDT表了
- 对程序员而言它是不可见
任务状态段
- TSS( Task State Segment),是操作系
统在进行进程切换时保存进程现场信息的
段,即保存CPU中各寄存器(如CS,EIP
ESP, eflags等等)的值,实现任务的挂起和恢复。A,B进程切换。 - TSS由104字节组成。
- 任务状态段寄存器TR:当前任务的在务状态段描述符选择子(类似于CS,DS)
分段机制将逻辑地址转换成线性地址的过程
虚拟地址(逻辑地址):CS(保护模式下是0)+eip=>线性地址:32位或者64位=>物理地址
线性地址
在保护模式下,线性地址
是由分段机制
将逻辑地址转化而来的,如果没有分段机制
作用,那么程序的逻辑地址就是线性地址了
。
分页机制将线性地址转换成物理地址的过程
物理地址
物理地址
是CPU在存取数据时最终在存取数据时地址总线上发的电平信号,靠该地址来访问对应数据。要得到物理地址,必须要将逻辑地址
经过分段,分页等机制
转化而来。
PDE和PTE解析(x86)
在x86系统中,线性地址是32bit,分为3部分
-
OFFSET
(物理内存的页内偏移,低12位,因为物理内存页是以4K为单位) -
PTI
(页表项索引,表示页表中某一项的下标,10位,因为页表项是存放在物理内存页中的,每一项在x86系统中是占4个字节,4KB/4B=1024个页表项,刚好10bit)-
PTE
(页表项),格式如下:
-
物理内存页的基地址
,高20bit,,下一级物理内存页的起始地址,因为物理内存页是对齐4K的,也就是以4K为单位,即物理内存页的起始地址能被4k整除,所以低12bit都为0。故低12bit就不用存储了,只需要存储高20bit的即可,恢复的时候,物理内存页的基地址<<12+12bit页内偏移地址
即可恢复完整的物理内存页的地址。 - 剩下12bit表示
物理内存页
的属性
-PAT
页表属性- 其他属性同下
-
-
PDI
(页目录项索引,表示页目录表中某一项的下标,10位,因为页目录表也是存放在物理内存页中的,每一项在x86系统中是占4个字节,4KB/4B=1024个页目录项,刚好10bit)-
PDE
(页目录项),格式如下
-
页表基地址
,高20bit,下一级页表的起始地址,因为页表存放在一个物理内存页中,是对齐4K的,也就是以4KB为单位,即所有页表基地址都能被4k整除,所以低12bit都为0。故低12bit就不用存储了,只需要存储高20bit的即可,恢复的时候,20bit页表基地址<<12+10bit的PTI<<2
即可恢复完整的页表地址。 - 剩下12bit表示
页表
属性-
P
:有效位。0表示表项无效 -
R/W
:0只读;1可读可写,修改可读可以把内存变成可写
-
U/S
:0表示R3程序可访,1表示只能R0级可访问,修改可以改变访问权限
-
PWT
(Page Write Through)
PWT=1时,写数据到缓存( CPU cache)同时将数据写入内存。 -
PCD
(Page Cache Disable)
PCD=1时,禁止的某个页写入缓存,直接写内存。 -
A
:0表示该页未被防问,1该已访问。 -
D
:脏位,0表示该页未写过,1该页己写过, -
PS
( Page Size):只存在于页目录表项中。0表示这是KB页
,指向一个下级页表。1表示这是4MB大页
,直接指向物理页(此时32位的虚拟地址分为两段,低22位为页内偏移
,2^22=4MB) -
G
:全局标志,G=1,刷新TLB
时将不会刷新PDE/PTE
的G位为1的页,G=1时,切换进程该PTE仍有效。 -
AVL
(Avail):仅用于多处理器系统;指明可读写或只读
-
-
-
CR3
寄存器存放的是页目录表的基地址 -
TLB
( Translation Lookaside Buffe),地址翻译缓存表,"快表",存线性地址翻译的物理地
址,线性地址<- ->物理地址。把上一次线性地址转化成物理内存的地址的记录存到TLB中,下一次先查TLB,如果有对应的记录,就不需要重新地址转换了,提高效率。 -
CPU cache
:容量比内存小但访间速度快,缓存物理内存中的数据:物理地址<- ->数据
在X64
系统中,线性地址是48bit,分为3部分,除了页内偏移,PDE,PTE还有:-
PML4 entry
在线性地址39~47bt用于素引PML4 entry,指向PDP -
PDP entry
-在线性地址的30~38bt用来素引 PDP entry指向PDE
-
结合pte修改内存只读实验
1.编写实验代码
#include
#include
int main(int argc, char* argu[])
{
/// 设置一个静态区的字符串
char *str = "hello world";
/// 打印静态区的内存地址(虚拟地址),打印静态区的字符串
printf("addr=%p, s\n", str, str);
printf("Plase press any key\n");
/// 让程序暂停;
getched();
/// 修改静态区内存的值
*str=A;
printf("%s\n", str);
return 0;
}
2.准备调试环境
- xp系统安装VC6等IDE
- 安装 windbg,准备好XP的符号
3.修改pe只读属性
-
!process 0 0
显示所有线程的信息 -
!vtop diraddr vaddr
将虚拟地址转换成物理地址,查看到PTE -
!ed PIEaddr
修改PTE的R/W字段为1,改成可读可写 - 按回车让程序继续执行,可以发现修改静态区字符串的值已经被修改了
PDPT,PDE和PTE解析(x86 PAE,PAE指的是将物理地址从32位扩大到36位)
-
OFFSET
(物理内存的页内偏移,低12位,因为物理内存页是以4K为单位) -
PTI
(页表项索引,表示页表中某一项的下标,9位,因为页表项是存放在物理内存页中的,每一项在x86系统中是占8个字节,4KB/8B=512个页表项,刚好9bit) -
PDI
(页目录项索引,表示页目录表中某一项的下标,9位,因为页目录表也是存放在物理内存页中的,每一项在x86系统中是占8个字节,4KB/8B=512个页目录项,刚好9bit) -
PDPT
页目录指针表索引,2位 -
CR3
寄存器存放的是页目录指针表的基地址 - 在x86 开启PAE的情况下
4PDPTE*512PDE*512PTE=2^20Pages*4KB/page=2^32B
虚拟内存地址依然是32bit,怎么支持到36位了呢?虚拟地址不是和物理地址一一对应的吗?
尝试理解:地址总线是36位的,将36位物理地址存放在8字节中,页表基地,页目录基地址,不再是20bit(32-12),而是24bit了,也就是说物理页的基地址是24bit,所以应该这样算4PDPTE*512PDE*512PTE=2^20Pages*(4KB*16)/page=2^36B
但在x86 开启PAE的情况下4PDPTE512PDE512PTE这个三维数组和在x86 没有启PAE的情况下的二维数组1024PDE*1024PTE的元素个数依然一样,但地址由20bit变成了24bit,那每个元素的及地址224/220=16,即一个页表项可以可以对应16个4k页,即一个页表项代表的物理页的基地址的跨度是跨越了16个4k页的大小,而偏移量还是12位,不就会导致剩余15个4k页的物理内存都没有对应的虚拟地址吗?那这样还是只支持4G啊
X86 PAE虚拟地址映射物理地址寻址实验
- PAE(Physical address extend),物理地址扩展,扩展物理内存地址从32位到36位,支持最多64GB的物理内存(页面然小为4KB)。为X86系统开启PAE方法(物理内存小于4G,开启无意义)
编写测试代码
#include
#include
#include
#include
#include
int main(int argc,char* argv[])
{
/// 在堆上分配1024个字节
char *str (char *)malloc(1024);
if(str == NULL)
return -1;
/// 初始化
memset(str,0,1024);
/// strcpy()有安全风险,可能会造成栈溢出。
/// 因为这个函数没有方法来保证有效的缓冲区尺寸,所以它仅仅能假定缓冲足够大来容纳要拷贝的字符串。
/// 在程序执行时,这将导致不可预料的行为,容易导致程序崩溃,最好使用strcpy_s()。
strcpy_s(str,"hello world");
/// 打印堆的内存地址(虚拟地址),打印堆的字符串
printf("addr of str:%p,%s\n",&str,str);
/// 让程序暂停
getch();
/// 手动释放堆内存
free(str);
str NULL;
return 0;
}
开启PAE
- WIN7:bcdedit
- 1.管理员方式打开CMD.
- 2.在cmd中输入:
bcdedit/set pae forceenable
这里的bcdedit
,是关于命令行的启动配置编辑器。使支持的物理内存大于4GB - 3.开启应用程序默认为3GB(3*1024):
bcdedit/set increaseuserva 3072
因为32位windows默认应用程序只能使用2G内存,剩下的都保留给系统内核了,所以还要开启3GB
- XP:boot.ini
在multi(0)disk(0)rdisk(0)partition(1)\WINDOWS=Microsoft Windows XP Professional /noexecute=optin /fastdetect
后加上/PAE
即multi(0)disk(0)rdisk(0)partition(1)\WINDOWS=Microsoft Windows XP Professional /noexecute=optin /fastdetect/PAE
打开windbg本地调试功能
- 以管理员权限运行:
bcdedit -debug on
打开local debug功能,然后重启系统
,这样,系统的本地调试功能就打开了。在本地调试部分,主要是利用windbg来查看一些系统数据信息,常见的数据结构以及内容等。
设置符号
开始调试
-
!process 0 0
显示所有进程的信息- PROCESS 82243020 SessionId:0 Cid:01e8 Peb:7ffd3000 ParentCid:01f4
DirBase:07880320
ObjectTable:e11b2470 HandleCount:12. Image:addr.exe - 07880320 页目录的基地址,用这个基地址可以算出物理地址
- PROCESS 82243020 SessionId:0 Cid:01e8 Peb:7ffd3000 ParentCid:01f4
-
.process /p eprocess
进入进程上下文,因为应用层各进程的内存之间是隔离的,切换到对应进程才能查看对应内存的数据 -
!dq addr+pdpe*8 L1
查看页目录基地址- 地址总线是
36位
的,!dq
是以8字节查看的虚拟地址的值,L1
只查看一个单位 -
- 得到的地址去除0-11(对齐到4K),取12-35位+pde*8
- 地址总线是
-
!dq addr+pde*8
查看页表基地址-
- 得到的地址去除0-11(对齐到4K),取12-35位+pte*8
-
-
!dq addr+pte*8 L1
查看物理内存页基地址-
- 得到的地址去除0-11(对齐到4K),取12-35位+offset即可得到物理地址
-
-
!dc paddr
即可查看到里面的内容验证 -
!vtop DirBase vaddr
windbg提供直接把虚拟地址转换成物理地址 -
PML4,PDP,PD和PT解析(x64)
- x64中是使用了低48位,每级页表占9位,共4级,缩写分别为:PML4,PDP,PD,PT
-
!pte vaddr
查香地址翻译过程 -
!vtop dirbase vaddr
虚拟地址转换为物理地址 -
CR3
(R3和内核公用导致了meltdown&spectre漏洞)
meltdown&spectre漏洞
- 指令在执行的时候会分支预测&乱序执行&CPU缓存旁路攻击
-
- 漏洞原理:在执行异常指令3时,出现异常,后面的指令不会被执行,但由于分支预测和乱序执行,将提前读取指令4的非法内核内存数据(当前用户态程序不可见)到CPU缓存中,但不会放到寄存器中(当前程序可见)经过合法性检测之后,将缓存中的数据放入寄存器,供当前程序访问,
- CPU缓存:用户态和内核态都无法正常访问,边信道攻击猜测,继而访问主机的完整内存。
- 边信道攻击:也叫旁路攻击针对加密电没备运每程中的时间消耗、功率消耗或电磁辐射之类的侧信道信息泄露而对加密设备进行攻击的方法被称为边信道攻击这类新型攻击的有效性远高玉密码分析的数学方法,因此给密码设备带来了严重的威胁
- 《没有绝对安全的系统:写在AES256破解之后》
http://jaq.alibaba.com/community/art/show?articleid=985 - 《Spectre Attacks:Exploiting Speculative Execution*》报告中包含的PoC代码,编译后生成可执行文件,在使用Intel处理器的Windows7 64位操作系统实验环境下进行测试。
https://github.com/turbo/KPTI-PoC-Collection
漏洞修复方法
- 修复方法:
KPTl
(Kernel Page Table Isolation)或KAISER
(Kernel Address Isolation to have Side-channels Efficiently Removed) -
KPT
会将内核内存与用户态进程分开,也就是将内核移到一个完全独立的地址空间中,所以对于普通进程来说是不可见的。KPT技术利用了Intel处理器的地址切换方式、缓存数据转储以及内存数据的重载。打了KPT1补丁之后内核访问用户态内存都要切CR3
了,TLB全清
。 - 采用
shadow页表
技术,R3,R0用不同的页表,内核她趾在R3中织有极少数被映射,大部分都无效,R0中的都有效,并且R3地址也都能访问,只通过SMAP和SMEP来进行保护。 -
SMEP
(Supervisor Mode Execution Prevention)当设置了CR4存器的控制位时,会保护特权进程(比如在内核态的程序)不能在不含supervisor标老(对宇ARM处理器,"就是PXN标志)的内存区域执行代码,其实就是内核程序不能跳转到用户态执行代码。 -
SMAP
(Supervisor Mode Access Prevention),内核态代码不能读写用户态的内存数据,SEMP控制执行,SMAP控制读写,为了相互通信,通过修改标志位,在某位置临时取消
SMAP,实现精确位置的读写 - 新的UserDirectoryTableBase用来保存R3的CR3,而原来的DirectoryTableBase则为R0的CR3。
0:kd>dt nt!_EPROCESS @Sproc ImageFileName Pcb.DirectoryTableBase
Pcb.UserDirectoryTableBa
*** ERROR:Module load completed but symbols could not be loaded for LiveKdD.SYS
+0x000 Pcb
+0x028 `DirectoryTableBase`:
0x1ab002
+0x278 `UserDirectoryTableBase`:
:0x2f00001
+0x450 ImageFileName:
[15]"System"
其他与内存相关的概念
-
Swap
分区:在系统的物理内存不够用的时候,把硬盘空间中的一音放出来,以供当前运行的程序使用。 -
page in
:分页(Page)从磁盘重新回到内存的过程 -
page out
:分页(Page)写入磁盘的过程 -
Page Fault
:当内核需要一个分页时,但发现此分页不在物理内存中因为已经被Page-Out了),此时就发生了分页错误Page Fault,CPU会产生一个hard page fault中断。 -
Hard page fault
也称为major page fault
,指需要访问的内存不在虚拟地址空间,也不在物理内存中,需要从慢速设备(也就是磁盘的Swap分区)载入。从swap回到物理内存也是hard page fault。 - 与之相对的
minor page fault
也称为soft page faut
,指需要访问的内存不在虚拟地址空间,但是在物理内存中,只需要MMU建立物理内存和虚拟地址空间的映射关系(建立一个页表)可。比如多个进程访问同一个共享内存中的数据,某些进程还没有建立起映射关系,会出现soft page fault
。 -
invalid faultt
也称为segment fault
,指进程需要访问的内存地址不在它的虚拟地址空间范围内(在保护模式,每个段有段的基地址和段的大小,如果段的偏移超过了段的大小,就不在有效范围内了)属于越界访问,内核会报segment fault错误。 -
对齐方式
: 结构体的自然对齐
进程上下文和中断上下文
- Linux内核工作在
进程上下文
或者中断下文
。- 提供系统调用服务的内核代码代表发起系统调用的应程序运行在
进程上下文
,代表进程运行。 - 另一方面,中断处理程序,异步运行在
中断上下文
,代表硬件运行,中断上下文文和特定进程无关
。运行在中断上下文的代码
就要受一些限制,不能做不面的事情:- 1、
睡眠或者放弃CPU
(硬件与人睡眠例子)
因为内核在进入中断之前会关闭进程调度,一旦睡眠或者放弃CPU,这时内核无法调度别的进程来执行,系统就会死掉。引起睡眠的情况:wake(),异步读循环等待,内存分配函数, - 2、
尝试获得信号量
如果获不到信号量,代码就会睡眠,效果同1 - 3、
执行耗时的任务
中断处理应该尽可能快,因为内核要响应大量服务和请求中断上下文占用CPU时间太长会严重影响系统功能 - 4、访问用户空间的虚拟地址
- 1、
- 提供系统调用服务的内核代码代表发起系统调用的应程序运行在
- 上下文context:上下文简单说来就是个环境,相对于进程而言,就是进程执行时的环境。具体来说就是
各个变量和数据,包括所有的寄存器变量、进程打开的文积内存信息等
。 - 上下文:关联到某个对象的空间,相当于人身上的一个口袋
- 一个
进程的上下文
可以分为三个部分:-
用户级上下文
:正文数据、用户堆栈以及共享存储区: -
寄存器上下文
:通用寄存器、程序寄存器(IP)、处理器状态寄存器(EFLAGS)栈指针(EFLAGS) -
系统级上下文
:进程控制块task struct、内存管理信息mm struct、vm_area_struct、pgd、pte)、内核栈
-
-
中断上下文
:其实也可以看作就是硬件传递过来的这些参数和内核需要保存的一些其他环境(主要是当前被中断的进程环境)
系统虚拟内存空间布局
系统虚拟内存空间布局
- X86支持32位寻址,因此可以支持最大2^32,4GB的虚拟内存空间(当然也可以通过PAE将寻址空间扩大到64GB,PAE即Physical address extension,x86的处理器增加了额外的地址线以选择那些增加了的内存,所以买体内存的大小从32位增加到了36位最大的实体内存由4GB增加到了64GB。
- X64内存理论上支持最大2^64的寻址空间,但实际上这个空间太大了,目前根本用不完。因此实际上x64系统一般都只支持到40多位(比如Windows:支持44位最大寻址空间为16TB,Linux支持48位最大寻址空间256TB等),支持的空间达到了TB级别。但是,无论是在内核空间还是在应用层空间,这些上TB的空间并不都是可用的,存在着所谓的
空洞
(HOLE)
程序内存布局
-
一个程序加载入内存之后,就处在进程空间。
-
静态区
-
.data
存放初始化
的全局变量和静态变量 -
.bss
存放未初始化
的全局变量和静态变量 -
.rdata
存放常量
-
-
代码区
存放程序执行的指令 -
堆
(heap),堆区内存的分配和释放需要使用malloc/free,不是自动分配的。 -
栈
(stack),函数在调用过程会使用到栈
堆和栈的区别
-
内存分配
- 栈:由系统自动分配与回收,intb=0,
增长由高到低
- 堆:malloc/free,地址由
低到高
- 栈:由系统自动分配与回收,intb=0,
-
大小限制
-栈:应用层1M到10M,内核层:12K到24K不等(所以内核层不能使用递归,避免栈溢出)- 堆:受限乐计算机系统中有效的虚拟内存。
-
效率比较
- 栈:由系统自动分配,速度较快。但程序员是
无法控制
。(指针函数不能够返回栈上的内存的地址,因为当函数执行完后,地址就无效了) - 堆:速度比较慢,而且容易产生
内存碎片
(明明系统存在内存,但想要分配的时候却分配不到),但可以使用一些机制来减少内存碎片。
- 栈:由系统自动分配,速度较快。但程序员是
-
存放内容
- 栈:栈是用来记录程序执行时函数调用过程中的活动记录(栈帧),参数,返回地址,ebp,局部变量等,有明确的类型和格式
- 堆:一般是在
堆的头部
用一个字节存放堆的大小
,剩余部公存储的内容,由程序员根据程序计算的需要决定,比较灵活,没有明确的类型和格式
文章来源于公众号:坚毅猿