首页
论坛
课程
招聘
[原创]物理地址、线性地址、逻辑地址之我见
2021-6-26 17:31 5780

[原创]物理地址、线性地址、逻辑地址之我见

2021-6-26 17:31
5780

前言

闲来无事,翻看收藏夹的连接,看到了描述这些地址的文章,看完之后总觉得缺少些什么。思之片刻有感,文章中没有给出简单的证实方法。随后百度之……论坛之……,然而,并未找到想要的结果,反而,产生了新的疑问。在众多文章中对于这些地址的概念描述差异不大,但是,对于范围大小的描述却各有千秋。这在……让我懵逼的同时,也牵动了我较真的神经!因此,本文试图解决以下几个问题:

  • 如何简单的证实这些地址,或者在一些触手可及的工具上找到些许线索证实。
  • 明确这些地址的范围。
  • 理解这些地址的基本作用。

本节虽名为前言,实则是发帖时才写的。下面的内容原本应为笔记,但独享不如分享!如有谬误,还望海涵,且请!多多指正。

文中内容以 Intel CPU 和 Windows 系统为例。

基本概念

  • Physical address: CPU 可访问的地址空间(如 8086 可访问 20 Bits 地址空间)。物理空间的范围为 36 ≤ MAXPHYADDR ≤ 52 Bits(64 GBytes ~ 4 PBytes),MAXPHYADDR 的值由 CPUID.80000008H:EAX[7:0] 查询。(Pentium Pro 之前皆为 32 Bits)。

  • Linear address: 有时也称 Virtual address。Linear address 是 Physical address 与 Logical address 的中间层,或称隔离层(保护模式隔离用户和资源……个人理解)。空间的范围为 32/48 Bits(4 GBytes/256 TBytes),具体由 CPUID.80000008H:EAX[15:8] 查询(如果 CPUID.80000001H:EDX.LM [29] = 1 则为 48 Bits,否则为 32 Bits。不支持此查询的 CPU 皆为 32 Bits)。

  • Logical address: 程序代码中使用的地址,在不同的编程环境中略有差异,实则已被削弱(见后文)。

1
2
3
4
5
mov eax, 0x80000008
cpuid
; Intel I7 6850K:
;         Linear address space  : ah = 48 Bits(支持 256 TBytes)
;         Phyiscal address space: al = 46 Bits(支持  64 TBytes) = MAXPHYADDR

在 Intel 的文档中,同为 6 系列的 CPU 中 MAXPHYADDR 值不同。

物理地址

物理地址中映射的是什么?答案是各种外部设备。一些地方将物理地址称为内存地址,个人认为亦对亦错,对的原因是 CPU 操作内存最为快捷(似乎也只能操作内存),而物理地址是为了让 CPU 将各种设备都视为内存。错的原因是物理地址中可以映射内存(条),但不局限于此!Intel 的 CPU 有两种物理空间:

  • Memory: 前面描述的大小为 MAXPHYADDR 的物理空间,这个空间中映射了各种外部设备:显卡、声卡、网卡、SATA、USB、APIC 等等。

  • I/O Ports: 独立编址的空间,仅有 64 KBytes(0 ~ 0xFFFF),此空间映射的也是各种外部设备。可通过 in/out 指令访问。0 ~ 255 的端口号使用立即数表示,超过 255 的端口号使用 dx 寄存器表示,存取的数据置于 eax/ax/al 中。

Windows 设备管理器按类型列出资源,其中的[内存]和[输入/输出(I/O)]项,即上述两种物理空间所映射的设备。在 x64 Windows 10 的[内存]项中,物理地址的值范围在 2^32 以内,这也许是设备厂商出于向下(x86)兼容的考虑。

 

做些简单的测试:

  • 测试一:将虚拟机内存调至 1 GBytes 或更低,然后启动虚拟机查看[内存]项,其中的地址值超过 1 GBytes。这就证实了物理地址……并非内存。
  • 测试二:[内存]项中皆为物理地址,而非线性地址,诸如,0x000A0000 ~ 0x000BFFFF,在本地机和虚拟机中皆为显卡相关,而这块地址也与实模式的显存映射地址一致。这就证实了这里使用的皆为物理地址。

线性地址

x86/x64 CPU 支持 32/64 Bits 寄存器寻址(4 GBytes ~ 16 EBytes),前者(x86)不会有任何问题,因为线性地址范围最小支持 32 Bits(当然也兼容更古老的 CPU)。然而,后者(x64)却引发了问题:CPU 仅支持 48 Bits 线性地址,因此,厂商规定线性地址的高 16 Bits 被作为符号扩展(0x0000... 或 0xFFFF...),这样的线性地址称为 canonical 地址;反之视为 non-canonical 地址(如高 16 Bits 的 0x0001... 或 0xFFF0... 等等),使用 non-canonical 地址将引发异常(Intel 手册描述)。

 

CPU 线性地址布局:

  • 0x00000000`00000000 ~ 0x00007FFF`FFFFFFFF:canonical 地址共 128 TBytes。
  • 0xFFFF8000`00000000 ~ 0xFFFFFFFF`FFFFFFFF:canonical 地址共 128 TBytes。
  • 0x00008000`00000000 ~ 0xFFFF7FFF`FFFFFFFF:non-canonical 地址共 16,776,960 TBytes。

    合法线性地址的 Bit 47 必须和 Bit 48 ~ 63 一致,否则为非法地址。若以后需要更大的空间,只需将符号位上移。

从 x64 Windows 8.1 及其之后的版本线性地址支持 256 TBytes,这与 CPU 描述的线性地址吻合。而之前的版本仅支持 16 TBytes 线性地址(微软描述,实测为 248 TBytes)。

1
2
3
4
5
6
7
8
9
10
    SYSTEM_INFO info = {};
    GetSystemInfo (&info);
/*
 x64 Win7:
    info.lpMinimumApplicationAddress = 0x00000000`00010000
    info.lpMaximumApplicationAddress = 0x000007FF`FFFEFFFF
 x64 Win10:
    info.lpMinimumApplicationAddress = 0x00000000`00010000
    info.lpMaximumApplicationAddress = 0x00007FFF`FFFEFFFF
*/

可以通过 windbg 的 !address 命令枚举线性地址布局,还可以使用 !pte 命令查看线性地址是否 canonical:

 

x64 Win7:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
kd>!address
....
      BaseAddress      EndAddress+1        RegionSize         VaType
----------------------------------------------------------------------------
       0`00000000        0`00010000        0`00010000         UserRange                          
            ....       
     7FF`FFFF0000      800`00000000        0`00010000         UserProbeArea                      
     800`00000000     8000`00000000     7800`00000000         <unknown>                          
    8000`00000000 FFFF8000`00000000 FFFF0000`00000000         NonAddressable                     
FFFF0800`00000000 FFFF8000`00000000     7800`00000000         SystemRange                        
FFFF8000`00000000 FFFFF680`00000000     7680`00000000         SystemRange                        
FFFFF680`00000000 FFFFF700`00000000       80`00000000         PageTables                         
            ....
FFFFFFFF`FFC00000 FFFFFFFF`FFFFFFFF        0`00400000         HAL

用户模式线性地址空间是固定大小的:0x800`00000000(因从 0 计数,有效地址要 - 1) = 8 TBytes。未知区域(unknown)大小 = 0x7800`00000000 = 120 TBytes。

 

内核模式从高到低对地址进行布局:HAL - PageTables + 1 = 0xFFFFFFFF`FFFFFFFF - 0xFFFFF680`00000000 + 1 = 0x980`00000000 = 9.5 TBytes。

 

从上向下数第一块 SystemRange 的 0xFFFF0800`00000000 和 0xFFFF8000`00000000 - 1 这两个地址都是 non-canonical,但如果将这两个地址的高 16 Bits 清零,则与 unknown 吻合(应该是微软将用户模式没有使用的 120 TBytes 归为内核了)。

 

第二块 SystemRange 的大小 = 118.5 TBytes(128 - 9.5)。综上内核模式共占 248 TBytes(0xFFFFFFFF`FFFFFFFF - 0xFFFF0800`00000000 + 1)。

 

x64 Win10:

1
2
3
4
5
6
7
8
9
10
11
kd>!address
....
      BaseAddress      EndAddress+1        RegionSize         VaType
----------------------------------------------------------------------------
       0`00000000        0`7ffe0000        0`7ffe0000         UserRange                          
            ....       
    7fff`ffff0000 ffff8000`00000000 ffff0000`00010000         UserProbeArea                      
    8000`00000000 ffff8000`00000000 ffff0000`00000000         NonAddressable                     
ffff8000`00000000 ffffa908`ac201000     2908`ac201000         SystemRange
            ....       
ffffffff`ffc00000 ffffffff`ffffffff        0`00400000         HAL

标准的 canonical 地址,用户与内核模式各占 128 TBytes。(SystemRange 块的 EndAddress 的值是随机的……不明所以)。

实模式,或保护模式分段下,线性地址就是物理地址;保护模式分页下,线性地址转物理地址涉及 32/48 Bits 的两种分页模式,论坛中有其他文章介绍,因此,就不赘言了。

逻辑地址

言不如表;表不如图。下图截取自 Intel 手册,图中绘出了逻辑地址到线性地址的转换过程。从图中大致可以将逻辑地址归纳为 'Segment:Offset' 形式,通常 Segment 是隐式的。而现今的编程环境基本皆为 Flat Model,也就是说逻辑地址已经被削弱,取而代之的是线性地址,至少 c/c++ 中指针使用线性地址。

 

参考资料

Memory Limits for Windows and Windows Server Releases

 

Kernel Virtual Address Layout

 

I/O Ports


[注意] 欢迎加入看雪团队!base上海,招聘安全工程师、逆向工程师多个坑位等你投递!

最后于 2021-6-26 17:51 被khristian编辑 ,原因:
收藏
点赞1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回