首页 > 系统相关 >浅聊一下 C#程序的 内存映射文件 玩法

浅聊一下 C#程序的 内存映射文件 玩法

时间:2023-06-13 18:24:57浏览次数:45  
标签:apphost 00 exe 映射 浅聊 C# 玩法 内存 Amd64VtoP

一:背景

1. 讲故事

前段时间训练营里有朋友问 内存映射文件 是怎么玩的?说实话这东西理论我相信很多朋友都知道,就是将文件映射到进程的虚拟地址,说起来很容易,那如何让大家眼见为实呢?可能会难倒很多人,所以这篇我以自己的认知尝试让大家眼见为实。

二:如何眼见为实

1. 我想象的文件映射

在任何讨论之前,内存文件映射大概像下面这样,多个进程可以完全View一个文件,也可以 View 文件的一部分到进程的虚拟地址中,画个图大概像下面这样。

但仔细一想,这里还有很多的小细节,比如:

疑问1:到底是映射文件还是映射磁盘的物理地址 ?

疑问2:既然是后备存储,那是不是每次修改虚拟地址都要刷硬盘 ?

疑问3:内存页是4k为一个单位,文件大小不是 4k 整数倍怎么办 ?

这三个疑问我相信很多朋友或多或少都会遇到,这里我简单解答一下,后面再用 windbg 验证。

  1. 严格来说是 硬盘物理地址

  2. 文件所处的硬盘地址为后备存储这个不假,但这里有个小细节,对虚拟地址的读写涉及到 内存页 概念,如果访问的虚拟地址所在的物理地址不在 物理内存 中,就会引发缺页中断,操作系统会将 磁盘上的 4k 页粒度灌入到 物理内存 中,同样的道理,如果修改了虚拟地址,那么物理内存页就是脏数据,会在后续的某个时刻刷新到 硬盘 上,产生磁盘 IO。

总的来说:从磁盘到物理内存(内存条) 之间的内存页的换入换出都是一种按需的 懒加载懒写入行为,稍后我们用 windbg 验证下。

  1. 内存的管理采用的是内存页的方式,如果 View 大于 文件Size,那么文件会扩容到 4k 对齐,这样方便对文件追加写入。

综合上面的三点信息,图就可以画的再详细一点了,比如下面这样:

熟悉内存管理的朋友应该知道,我们程序的 exe 和 dll 就是用 内存映射文件 的方式加载到虚拟地址中的,所以就拿它开刀吧。

2. 一段测试代码

为了方便演示,上一段简单的的测试代码,观察 ConsoleApp1.exe 的映射方式。


        static void Main(string[] args)
        {
            Console.WriteLine($"当前时间:{DateTime.Now}, 程序启动!");
            Console.ReadLine();
        }

接下来用 windbg 启动 ConsoleApp1.exe 两次,结合详细分解图,我们观察下这两个进程的虚拟地址所映射的内存条物理地址是否一致?

  1. 实例1

ModLoad: 00007ff6`bfe00000 00007ff6`bfe2a000   apphost.exe
ModLoad: 00007ff9`b1450000 00007ff9`b1648000   ntdll.dll
...

0:008> lmvm apphost
Browse full module list
start             end                 module name
00007ff6`bfe00000 00007ff6`bfe2a000   apphost  C (private pdb symbols)  c:\mysymbols\apphost.pdb\1643A9EB126F4FE184548E9CC1B740B71\apphost.pdb
    Loaded symbol image file: D:\net7\ConsoleApplication1\ConsoleApp1\bin\Debug\net6.0\ConsoleApp1.exe
    Image path: apphost.exe
    Image name: apphost.exe
    ...

0:008> ~
   0  Id: 232c.4abc Suspend: 1 Teb: 0000000e`7b1a5000 Unfrozen

  1. 实例2

ModLoad: 00007ff6`bfe00000 00007ff6`bfe2a000   apphost.exe
ModLoad: 00007ff9`b1450000 00007ff9`b1648000   ntdll.dll
...

0:008> ~
   0  Id: 60e8.3e3c Suspend: 1 Teb: 000000da`ab498000 Unfrozen
   1  Id: 60e8.53b0 Suspend: 1 Teb: 000000da`ab49a000 Unfrozen

这里要提醒一下的是在 Windows 平台上 ConsoleApp1.exe 已经成了一个引导程序,通过 lmvm 可以看到它其实是 apphost.exe

两个实例都开起来后,可以看到 apphost.exe 在各自进程的虚拟地址都一样,那他们的物理地址是否也一样呢? 要寻找答案,接下来我们到 Windows 内核态去挖一挖。


lkd> !process 0 0 ConsoleApp1.exe

PROCESS ffff838bd84c9080
    SessionId: 8  Cid: 232c    Peb: e7b1a4000  ParentCid: 0b14
FreezeCount 2
    DirBase: 3468cf000  ObjectTable: ffff938feae02900  HandleCount: 172.
    Image: ConsoleApp1.exe

PROCESS ffff838bef157080
    SessionId: 8  Cid: 60e8    Peb: daab497000  ParentCid: 4804
FreezeCount 2
    DirBase: 3552f3000  ObjectTable: ffff938fe8f7ec40  HandleCount: 166.
    Image: ConsoleApp1.exe

从卦中看,Cid: 232c 是我们的实例1, Cid: 60e8 是我们的实例2,接下来用 windbg 提供的 !vtop 命令观察 apphost.exe 的首地址对应的物理地址。


// ----  实例1 -----
lkd> !vtop 3468cf000 00007ff6bfe00000
Amd64VtoP: Virt 00007ff6bfe00000, pagedir 00000003468cf000
Amd64VtoP: PML4E 00000003468cf7f8
Amd64VtoP: PDPE 00000001138dbed0
Amd64VtoP: PDE 00000002153dcff8
Amd64VtoP: PTE 000000024dadd000
Amd64VtoP: Mapped phys 00000002271c2000
Virtual address 7ff6bfe00000 translates to physical address 2271c2000.

//----  实例2 -----

lkd> !vtop 3552f3000 00007ff6bfe00000
Amd64VtoP: Virt 00007ff6bfe00000, pagedir 00000003552f3000
Amd64VtoP: PML4E 00000003552f37f8
Amd64VtoP: PDPE 00000002db7ffed0
Amd64VtoP: PDE 0000000208100ff8
Amd64VtoP: PTE 000000033de01000
Amd64VtoP: Mapped phys 00000002271c2000
Virtual address 7ff6bfe00000 translates to physical address 2271c2000.

从卦中看,实例1 和 实例2 的 虚拟地址 映射的 物理地址 是相同的 2271c2000。这也很好的解释了那张图。

有朋友可能会有疑问,能否看下 2271c2000 这个 物理地址 的内容? 这当然是可以的,用 windbg 的 !da 就好了。


lkd> !db 2271c2000
#2271c2000 4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00 MZ..............
#2271c2010 b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00 ........@.......
#2271c2020 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#2271c2030 00 00 00 00 00 00 00 00-00 00 00 00 e8 00 00 00 ................
#2271c2040 0e 1f ba 0e 00 b4 09 cd-21 b8 01 4c cd 21 54 68 ........!..L.!Th
#2271c2050 69 73 20 70 72 6f 67 72-61 6d 20 63 61 6e 6e 6f is program canno
#2271c2060 74 20 62 65 20 72 75 6e-20 69 6e 20 44 4f 53 20 t be run in DOS 
#2271c2070 6d 6f 64 65 2e 0d 0d 0a-24 00 00 00 00 00 00 00 mode....$.......

从卦中看,物理地址上有一段 This program cannot be run in DOS mode,这不就是经典的 PE 文件哈,如果不相信可以用 WinHex 打开 ConsoleApp1.exe 即可,截图如下:

最后就是内核中的 内存管理器 会将 物理地址 与 磁盘地址 进行打通,实现懒加载和懒写入。

3. 如何自定义实现

Image 虽然是一个快捷的观察内存文件映射方式,那如果自己能实现一个就更有意思了,比如下面对 1.txt 进行文件映射,在 C# 中有一个快捷类 MemoryMappedFile 实现了 win32api 的封装,参考代码如下:


    internal class Program
    {
        static void Main(string[] args)
        {
            int capaticy = 1024; //1k

            using (var mmf = MemoryMappedFile.CreateFromFile(@"C:\1.txt", FileMode.OpenOrCreate,
                                                            "testmapfile",
                                                             capaticy,
                                                             MemoryMappedFileAccess.ReadWrite))
            {
                var viewAccessor = mmf.CreateViewAccessor(0, capaticy);

                while (true)
                {
                    Console.WriteLine("请输入你要写入的内容: ");

                    string input = Console.ReadLine();

                    viewAccessor.WriteArray(0, input.ToArray(), 0, input.Length);
                }
            }
        }
    }

接下来用 windbg 附加一下,观察 1.txt 是不是被 MappedFile 上了,同时做的修改有没有更新到物理磁盘上。


0:006> !address

  BaseAddr EndAddr+1 RgnSize     Type       State                 Protect             Usage
-----------------------------------------------------------------------------------------------
...
+  31a0000  31a1000     1000 MEM_MAPPED  MEM_COMMIT  PAGE_READWRITE                     MappedFile "\Device\HarddiskVolume3\1.txt"
...

0:006> du 31a0000
031a0000  "helloworld!"

从卦中可以看到,虽然 1.txt 最大的 View 区间是 1k,但提交的内存页还是按照最小粒度 4k 给的。

三:总结

这篇我们就简单的浅聊一下,如果这块是知识盲区的朋友应该会有一点帮助,希望没有带偏大家,更多的细节期待大家挖掘!

图片名称

标签:apphost,00,exe,映射,浅聊,C#,玩法,内存,Amd64VtoP
From: https://www.cnblogs.com/huangxincheng/p/17478410.html

相关文章

  • buaa os lab4-challenge 信号系统的实现
    buaaoslab4-challenge信号系统的实现信号是什么生活中我们会收到各种各样的信号,比如老师在群里布置了一个新的ddl,或者肚子发出咕咕的叫声提醒我们该吃饭了,接收到信号之后我们并不是马上处理,需要等到一些合适的时机并前横利弊,比如对于人来说肯定是吃饭重要,所以我们会忽略ddl......
  • 【解决一个小问题】golang 的 `-race`选项导致 unsafe代码 panic
    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!cnblogs博客zhihuGithub公众号:一本正经的瞎扯为了提升性能,使用unsafe代码来重构了凯撒加密的代码。代码如下:const( lowerCaseAlphabet="abcdefghijklmnopqrstuvwxyz" upperCaseAlphabet="ABCDEFGHIJKLMN......
  • NMI watchdog :BUG:softlockup 解决方案
    问题现象:解决方案:解决办法:设置为30s覆盖到配置文件中echo30>/proc/sys/kernel/watchdog_thresh查看tail-1/proc/sys/kernel/watchdog_thresh30临时生效sysctl-wkernel.watchdog_thresh=30常见原因:1>服务器电源供电不足,导致CPU电压不稳导致CPU死锁2>vcpus超过物理cpu实......
  • windows安装pytorch环境
    由于CUDAVersion更新到11.7了,因此,本教程也同步更新安装Anaconda(1)首先打开Anaconda官网,下载对应平台的安装包Anaconda官网我们这里安装的包是Anaconda3-2022.10-Windows-x86_64.exe然后,双击exe文件,开始安装等待安装结束(这里选择Justme即可)注:这里有一种方法可以自动添加......
  • Docker学习笔记 -day-01
    参考资料:Docker从入门到实践Docker教程|菜鸟教程Docker基本概念镜像:类似面向对象程序设计中的类容器:类似面向对象程序设计中的对象仓库:类似Maven,Gradle包管理工具,仓库主要是用于存放镜像安装Docker以CentOS-8.5.211-x86_64为例(参考CentOS安装Docker):终端......
  • Java中@Transactional 注解使用注意事项
    隔离级别关键词原子性(Atomicity):事务是数据库的逻辑工作单位,它对数据库的修改要么全部执行,要么全部不执行。一致性(Consistemcy):事务前后,数据库的状态都满足所有的完整性约束。隔离性(Isolation):并发执行的事务是隔离的,一个不影响一个。如果有两个事务,......
  • lspci的输出简单分析
    CentOS系统的输出:可以看到输出分成两部分,编号加设备描述。编号通过冒号":"又分成了三部分,第一个部分是PCIe的domainID,第二个部分是busID,第三个部分是deviceid.functionid。PCI设备的组织形式是一个树形,这表示一个domain可以包含多个bus,一个bus又包含了多个device,一个de......
  • C# Winform TabControl边框设置
     参考https://stackoverflow.com/questions/2567172/c-sharp-tabcontrol-border-controls  ......
  • Lucas(卢卡斯定理)
    \(C^m_n\equivC^{m/p}_{n/p}*C^{m\mod\p}_{n\mod\p}\)首先,我们可以知道如下定理我们令\(n=ap+b\),\(m=cp+d\)则由二项式定理得\((1+x)^n\equiv\Sigma_{i=0}^nC^i_nx^i(mod\p)\)---------(1)由\(n=ap+b\)可知\((1+x)^n\equiv(1+x)^{......
  • React Native In Action中的demo的运行
     我用了最新的xcode和新的reactnative。Chapter6: Navigation这一章中的事例跑不起来,应该是程序用了就的版本的reactnavigation("react-navigation":"^2.0.1"),同时关于Navigation的用法也做了不小的改动,因此需要重新看相关文档改写。参考:https://reactnavigation.org/docs......