ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

【Windows核心编程】如何知道程序运行中当前操作的内存地址范围,自己实现一个文件映射类

2022-05-09 12:00:08  阅读:182  来源: 互联网

标签:文件 映射 程序运行 Windows 访问 内存 pTemp 内存地址 PAGE


大部分人穷极一生都止步于自己的“陷阱”里,所以古人才有了破而后立的感悟!

问题来源

    此问题源于对文件映射FileMapping的改造需求。我们知道FileMapping的便利性,但可能在某个很小的开发范围内,会发现FileMapping的局限性!那就是只能对内核支持的文件对象进行映射,而内核文件对象意味着文件系统驱动,因而导致正常情况下只能对Windows支持的FAT/NTFS等文件系统中的文件进行映射。假如我有一个文件在远端服务器,我不想通过文件系统驱动的方式(网络共享也在其中)进行加载,也不想下载到本地磁盘,而是想直接将远端文件映射到本地内存中,那现有的FileMapping就无法完成了,或者需要开发驱动才能完成。

    所以,有没有办法在用户态环境下,改造或实现一个FileMapping,让其能够解决该问题?

问题解析

    从FileMapping原理上看,操作映射有以下步骤:

1)创建时,告诉进程哪个文件有能力被映射到内存(CreateFileMapping),

2)在访问内存之前,我们还需要告知进程文件中哪段内容会被映射到内存(MapViewOfFile),再把相应的内存空间保留起来,留到需要时使用,且文件数据并未加载到该内存中。

3)在访问内存时,由内核自动映射文件数据到相应的内存。

4)映射细节:访问内存涉及到读和写,在读之前需要先将文件数据加载,在写之后需要将内存数据保存到文件

5)释放

    综上,若要实现一个FileMapping,我们需要在内存中划分保留空间,调用 VirtualAlloc 即可,接着最重要的是需要知道进程当前访问的内存地址,以及如何在进程读内存前和写内存后进行相应的“映射”操作。至于文件数据,我们可以放一边,因为当前需求是获取网络数据,当然也可以是任意其他方式读取数据(比如从串口设备中读取数据)。

寻求方案

    日常开发中,我们知道在调试时,调试器是可以知道被调试进程当前执行的代码地址,以及各种变量地址和内容的,当然我们没必要知道这么详细,而且实际中不太可能开发一个调试器去实现该功能,我们有更好的方法。通过开源的内核源码以及相关信息,我们知道程序在访问文件映射内存时,是通过触发一个异常STATUS_ACCESS_VIOLATION,让内核知道,然后再去自动加载文件数据到内存,之后再让程序重新执行访问内存,最后进程才正常继续往后面执行。

    所以,我们同样需要让知道如何触发并捕获访问内存异常的。

解决方法

1)如何触发内存访问异常?

我们知道访问空指针或者无效指针,程序就会出现异常错误,不处理异常就会导致程序崩溃。

无效指针是因为对应的内存地址没有申请,而通过VirtualAlloc申请内存后,就可以正常访问了,如下:

pBaseAddress:=VirtualAlloc(nil,1024, MEM_COMMIT, PAGE_READWRITE);

学习Windows的内存管理机制后,就知道内存属性PAGE_READWRITE代表该内存可以被读写,这里我们将内存设置为PAGE_NOACCESS,后面程序对该内存空间进行访问时,就会触发内存访问异常STATUS_ACCESS_VIOLATION(因为没有可访问属性)

也可以在后续使用VirtualProtect对内存属性进行设置,如:

VirtualProtect(pBaseAddress, 1024, PAGE_NOACCESS, oldProtect);

 

2)如何捕获异常

进一步学习Windows异常机制,可以知道通过AddVectoredExceptionHandler(和RemoveVectoredExceptionHandler)添加异常处理函数。就能够对STATUS_ACCESS_VIOLATION异常进行处理。

 

function  VectoredHandler(var ExceptionInfo: EXCEPTION_POINTERS): LONG; stdcall;
var
  oldProtect: DWORD;
  pAccessAddr: ULONG_PTR;
  pTemp: PByte;
begin
  Result := EXCEPTION_CONTINUE_EXECUTION;
  if ExceptionInfo.ExceptionRecord.ExceptionCode = STATUS_ACCESS_VIOLATION then
  begin
    pAccessAddr := UIntPtr(ExceptionInfo.ExceptionRecord.ExceptionInformation[1]);
    pAccessAddr := (pAccessAddr div 4096) *4096;
    if pAccessAddr=UIntPtr(Pointer(pBaseAddress)) then
    begin
      //EFlags::TF(bit 8, 即第9位) [Trap flag]   将该位设置为1以允许单步调试模式,清零则禁用该模式。即执行下一条指令后自动触发单步调试异常
      ExceptionInfo.ContextRecord.EFlags := ExceptionInfo.ContextRecord.EFlags or $100;
      VirtualProtect(pBaseAddress, 1024, PAGE_READWRITE, oldProtect);

      pTemp:=pBaseAddress;
      Inc(pTemp, 5);
      pTemp^ := 5;
      Form1.Memo1.Lines.Add('ChangeAccess: PAGE_READWRITE');
      Exit;
    end;
  end
  else if ExceptionInfo.ExceptionRecord.ExceptionCode = STATUS_SINGLE_STEP then
  begin
    VirtualProtect(pBaseAddress, 1024, PAGE_NOACCESS, oldProtect);
    Form1.Memo1.Lines.Add('ChangeAccess: PAGE_NOACCESS');
    Exit;
  end;

  Result := EXCEPTION_CONTINUE_SEARCH;
end;

 

var
  pAddVectoredExceptionHandler: TFnAddVectoredExceptionHandler;
  pRemoveVectoredExceptionHandler: TFnRemoveVectoredExceptionHandler;
var
  pTemp: PByte;
  oldProtect: DWORD;
  nTmp: Byte;
begin
    pVEHHandler := pAddVectoredExceptionHandler(1, @VectoredHandler); //安装VEH
    try
      pTemp := pBaseAddress;
      Inc(pTemp, 5);
      pTemp^ := 2;
      Memo1.Lines.Add('WillAccess: AfterWrite');

      Memo1.Lines.Add('WillAccess: BeforeRead');
      nTmp := pTemp^;
      if nTmp<>0 then
        ShowMessage('pTemp^='+IntToStr(nTmp))
      else
        ShowMessage('0000');
      Memo1.Lines.Add('Free');
      VirtualFree(pBaseAddress, 1024, MEM_FREE);
    finally
      pRemoveVectoredExceptionHandler(pVEHHandler);
    end;
end;

调用后输出信息:

WillAccess: BeforeWrite
STATUS_ACCESS_VIOLATION: ExceptionAddress: 00611288
AccessMode: Write
AccessAddress: 050E0005
ChangeAccess: PAGE_READWRITE
ChangeAccess: PAGE_NOACCESS
WillAccess: AfterWrite
WillAccess: BeforeRead
STATUS_ACCESS_VIOLATION: ExceptionAddress: 006112C0
AccessMode: Read
AccessAddress: 050E0005
ChangeAccess: PAGE_READWRITE
ChangeAccess: PAGE_NOACCESS
WillAccess: AfterRead

通过以上代码可以实现,访问内存的异常触发和捕获处理。

因此剩下的问题就是如何实现数据到内存的地址对应关系管理了。

最后

原理性的东西已经展示出来,功能性的东西就各自开发了。

此异常处理的原理还可以应用在代码保护,内存保护,反外挂,代码虚拟机等等地方,虽然看似简单,但是其作用真的有非常多的想象空间,实际就看每个人的经验和创作力了。

标签:文件,映射,程序运行,Windows,访问,内存,pTemp,内存地址,PAGE
来源: https://www.cnblogs.com/caibirdy1985/p/16248662.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有