1. 程式人生 > 其它 >【Windows核心程式設計】如何知道程式執行中當前操作的記憶體地址範圍,自己實現一個檔案對映類

【Windows核心程式設計】如何知道程式執行中當前操作的記憶體地址範圍,自己實現一個檔案對映類

大部分人窮極一生都止步於自己的“陷阱”裡,所以古人才有了破而後立的感悟!

問題來源

    此問題源於對檔案對映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

通過以上程式碼可以實現,訪問記憶體的異常觸發和捕獲處理。

因此剩下的問題就是如何實現資料到記憶體的地址對應關係管理了。

最後

原理性的東西已經展示出來,功能性的東西就各自開發了。

此異常處理的原理還可以應用在程式碼保護,記憶體保護,反外掛,程式碼虛擬機器等等地方,雖然看似簡單,但是其作用真的有非常多的想象空間,實際就看每個人的經驗和創作力了。