一种User Mode下访问物理内存及Kernel Space的简单实现
一種User Mode下訪問物理內(nèi)存及Kernel Space的實(shí)現(xiàn)
一.背景
?????? WinCE發(fā)展到6.0之后,內(nèi)存結(jié)構(gòu)和管理方法進(jìn)行了完善。對(duì)應(yīng)用程序影響比較大的有Virtual Memory Layout的變化,如每個(gè)進(jìn)程的虛擬內(nèi)存空間擴(kuò)展為2GB。對(duì)驅(qū)動(dòng)程序影響比較大的有Pointer和Share Memory,這一點(diǎn)在Driver與OS接口部分對(duì)指針和內(nèi)存的保護(hù)方法中可以看到。對(duì)OAL影響比較大的有,系統(tǒng)Boot Process的改變。
?????? 另外,6.0上廢除了Full Kernel Mode的不合理設(shè)計(jì),將Kernel Mode和User Mode進(jìn)行了細(xì)分。由此帶來了本文中要討論的問題,就是如何在User Mode下操作Kernel Mode下的內(nèi)存空間。
?????? 大家一定想到了,簡(jiǎn)單的在User Mode下訪問Kernel Mode下才有權(quán)限訪問的內(nèi)存肯定是行不通,可以間接的通過Kernel下的Driver或者其它與Kernel中的代碼進(jìn)行通信的方法來訪問。
?????? 本文先討論一下第一種方法實(shí)現(xiàn)中要解決的問題,最后會(huì)將主要代碼實(shí)現(xiàn)粘貼出來。
二.需要解決的幾個(gè)問題
?????? 前面已經(jīng)提到主要的實(shí)現(xiàn)思路,需要解決的問題是如何加載該驅(qū)動(dòng),以及如何保證Driver運(yùn)行在Kernel Mode下,以及如何將Driver和Exe組合到一起。
1.用戶模式下Driver的加載
?????? 為了方便實(shí)現(xiàn)User Mode下Driver的動(dòng)態(tài)加載,將這支在User Mode和Kernel Mode下做轉(zhuǎn)換的Driver做成流驅(qū)動(dòng)。
?????? User Mode下加載Driver,只需兩個(gè)步驟:首先將Driver拷貝到對(duì)象存儲(chǔ)下的Windows目錄下,然后調(diào)用Device Manager的API ActivateDevice()來實(shí)現(xiàn)動(dòng)態(tài)的加載。
?????? 函數(shù)ActivateDevice()的使用非常簡(jiǎn)單,其聲明如下:
| This function loads a device driver. For additional functionality, use the ActivateDeviceEx function. HANDLE ActivateDevice( ? LPCWSTR lpszDevKey, ??DWORD dwClientInfo );ParameterslpszDevKey [in] Pointer to a string that identifies the location under the HKEY_LOCAL_MACHINE registry subtree where the Driver registry subkey for the device resides. A driver registry subkey contains the dynamic-link library (DLL) name, device prefix, friendly name, and other device information. dwClientInfo [in] Data to store in the Active registry subkey for the device in the ClientInfo registry entry. The registry path to the Active registry subkey for the device is passed in as the context parameter to the device's XXX_Init (Device Manager) function. After the value in dwClientInfo is stored in the registry under HKEY_LOCAL_MACHINE/Drivers/Active, the Device Manager calls XXX_Init. Devload.h defines DEVLOAD_CLIENTINFO_VALNAME and DEVLOAD_CLIENTINFO_VALTYPE to facilitate access to the ClientInfo key. |
?????? 可以看到,第一個(gè)參數(shù)用來指定Driver的注冊(cè)表路徑,而第二個(gè)參數(shù)用來寫入到Active Key下,如果不需要寫入的話,可以置為NULL。?
2.如何保證加載的Driver處于Kernel Mode下
?????? 6.0下引入了Group的概念,通過注冊(cè)表可以去定義一個(gè)Group,一個(gè)簡(jiǎn)單的Group定義如下:
| [HKEY_LOCAL_MACHINE/Drivers/ProcGroup_0002] ??? "ProcName"="servicesd.exe" ??? "ProcVolPrefix"="$services" ??? "ProcTimeout"=dword:20000 ? [HKEY_LOCAL_MACHINE/Drivers/ProcGroup_0003] ??? "ProcName"="udevice.exe" ??? "ProcVolPrefix"="$udevice" |
?????? 其實(shí),簡(jiǎn)單點(diǎn)理解Group就是將Driver的加載方式進(jìn)行細(xì)分,方便不同的Driver使用不同的系統(tǒng)組件進(jìn)行加載。
?????? Driver的注冊(cè)表項(xiàng)可以用來指定加載自己的Group,如下:
| [HKEY_LOCAL_MACHINE/Drivers/BuiltIn/Ethman] "Prefix"="ETM" "Dll"="ethman.dll" "Index"=dword:1 ; WZCSVC must be started before ethman "Order"=dword:2A ; Flags==12 is DEVFLAGS_LOADLIBRARY and DEVFLAGS_LOAD_AS_USERPROC "Flags"=dword:12 "UserProcGroup"=dword:3 ; // default to group 3 ? [HKEY_LOCAL_MACHINE/Drivers/BuiltIn/SIP] "Prefix"="SIP" "Dll"="softkb.DLL" "Order"=dword:1 "Index"=dword:0 ;Flags==10 is DEVFLAGS_LOAD_AS_USERPROC "Flags"=dword:10 "UserProcGroup"=dword:3 ; // default to group 3 |
?????? 對(duì)于指定通過某個(gè)Group進(jìn)行加載的Driver,系統(tǒng)中進(jìn)行加載的時(shí)候會(huì)引入Reflect機(jī)制。該機(jī)制主要用來對(duì)檢察注冊(cè)表項(xiàng)中的IoLen和IoBase值,當(dāng)檢察到Driver中訪問了不在IoLen和IoBase指定區(qū)域的物理內(nèi)存時(shí),將會(huì)出現(xiàn)系統(tǒng)異常。
?????? 對(duì)于那些沒有指定使用Group進(jìn)行加載的驅(qū)動(dòng),WinCE6.0中將其加載到Kernel Mode下。也就是說,它們具有了訪問整個(gè)4GB空間的權(quán)限。
?????? 要保證User Mode下加載的Driver處于Kernel Mode,只需要在注冊(cè)表中不去指定User Group就可以了。????
3.如何將Driver DLL和Exe 做成一個(gè)文件
?????? 之所以將Driver和EXE組合成一個(gè)文件,是為了用戶使用的方便。想象一下,如果不把兩者做成一個(gè)文件的話,一個(gè)簡(jiǎn)單的訪問物理內(nèi)存的應(yīng)用程序就變成了兩個(gè)文件,那是多么不美觀的事情。
?????? 其實(shí)實(shí)現(xiàn)將Driver DLL和EXE做成一個(gè)文件有兩個(gè)方法。方法一,由于DLL和EXE都是PE結(jié)構(gòu)的,可以使用網(wǎng)上的加殼工具將其組合成一個(gè)PE文件,而在運(yùn)行的時(shí)候自動(dòng)去殼即可。方法二,將DLL中的信息提取出來放到EXE的Data Section,然后在運(yùn)行的時(shí)候,將這些數(shù)據(jù)重新組合成一個(gè)DLL。
?????? 這里我采用了第二種方法來實(shí)現(xiàn)組合Driver和EXE文件。
三.代碼實(shí)現(xiàn)
1.將Driver注冊(cè)表的操作簡(jiǎn)化
?????? 操作過CE下注冊(cè)表的兄弟們都知道,微軟設(shè)計(jì)的注冊(cè)表非常簡(jiǎn)單,可是操作API實(shí)在是不那么友好。
?????? 我這里使用了PB6.0源文件PUBLIC/WCESHELLFE/OAK/CTLPNL/CPLMAIN/cplmacro.h中的類CReg來實(shí)現(xiàn)對(duì)Driver注冊(cè)表項(xiàng)的讀寫動(dòng)作。
?????? 該注冊(cè)表類主要封裝了注冊(cè)表的Open/Read/Write API,為用戶提供了一種更加友好的注冊(cè)表操作接口。
?????? 該類的定義和實(shí)現(xiàn)如下:
| class CReg { private: ???? HKEY m_hKey; ???? int????? m_Index; ???? LPBYTE?? m_lpbValue; // last value read, if any ? public: ???? BOOL Create(HKEY hkRoot, LPCTSTR pszKey) { ???????? DWORD dwDisp; ???????? return ERROR_SUCCESS==RegCreateKeyEx(hkRoot, pszKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &m_hKey, &dwDisp); ???? } ? ???? BOOL Open(HKEY hkRoot, LPCTSTR pszKey, REGSAM sam=KEY_READ) { ???????? return ERROR_SUCCESS==RegOpenKeyEx(hkRoot, pszKey, 0, sam, &m_hKey); ???? } ? ???? CReg(HKEY hkRoot, LPCTSTR pszKey) { ???????? m_hKey = NULL; ???????? m_Index = 0; ???????? m_lpbValue = NULL; ???????? Open(hkRoot, pszKey); ???? } ? ???? CReg() { ???????? m_hKey = NULL; ???????? m_Index = 0; ???????? m_lpbValue = NULL; ???? } ? ???? ~CReg() { ???????? if(m_hKey) RegCloseKey(m_hKey); ???????? MyFree(m_lpbValue); ???? } ? ???? void Reset() { ???????? if(m_hKey) RegCloseKey(m_hKey); ???????? MyFree(m_lpbValue); ???????? m_hKey = NULL; ???????? m_Index = 0; ???????? m_lpbValue = NULL; ???? } ? ???? operator HKEY() { return m_hKey; } ? ???? BOOL IsOK(void) { return m_hKey!=NULL; } ? ? ???? BOOL EnumKey(LPTSTR psz, DWORD dwLen) { ???????? if(!m_hKey) return FALSE; ???????? return ERROR_SUCCESS==RegEnumKeyEx(m_hKey, m_Index++, psz, &dwLen, NULL, NULL, NULL, NULL); ???? } ? ???? BOOL EnumValue(LPTSTR pszName, DWORD dwLenName, LPTSTR pszValue, DWORD dwLenValue) { ???????? DWORD dwType; ???????? if(!m_hKey) return FALSE; ???????? dwLenValue *= sizeof(TCHAR); // convert length in chars to bytes ???????? return ERROR_SUCCESS==RegEnumValue(m_hKey, m_Index++, pszName, &dwLenName, NULL, &dwType, (LPBYTE)pszValue, &dwLenValue); ???? } ? ???? BOOL ValueSZ(LPCTSTR szName, LPTSTR szValue, DWORD dwLen) { ???????? if(!m_hKey) return FALSE; ???????? dwLen *= sizeof(TCHAR); // convert length in chars to bytes ???????? return ERROR_SUCCESS==RegQueryValueEx(m_hKey, szName, NULL, NULL, (LPBYTE)szValue, &dwLen); ???? } ? ???? DWORD ValueBinary(LPCTSTR szName, LPBYTE lpbValue, DWORD dwLen) { ???????? if(!m_hKey) return FALSE; ???????? DWORD dwLenWant = dwLen; ???????? if(ERROR_SUCCESS==RegQueryValueEx(m_hKey, szName, NULL, NULL, lpbValue, &dwLen)) ????????????? return dwLen; ???????? else ????????????? return 0; ???? } ? ???? LPCTSTR ValueSZ(LPCTSTR szName); ? ???? LPBYTE ValueBinary(LPCTSTR szName) { ???????? return (LPBYTE)ValueSZ(szName); ???? } ? ???? DWORD ValueDW(LPCTSTR szName, DWORD dwDefault=0) { ???????? if(!m_hKey) return FALSE; ???????? DWORD dwValue = dwDefault; ???????? DWORD dwLen = sizeof(DWORD); ???????? RegQueryValueEx(m_hKey, szName, NULL, NULL, (LPBYTE)&dwValue, &dwLen); ???????? return dwValue; ???? } ? ???? BOOL SetSZ(LPCTSTR szName, LPCTSTR szValue, DWORD dwLen) { ???????? //Prefix ???????? if(!m_hKey) return FALSE; ???????? // ???????? return ERROR_SUCCESS==RegSetValueEx(m_hKey, szName, 0, REG_SZ, (LPBYTE)szValue, sizeof(TCHAR)*dwLen); ???? } ? ???? BOOL SetSZ(LPCTSTR szName, LPCTSTR szValue) { ???????? return SetSZ(szName, szValue, 1+lstrlen(szValue)); ???? } ? ???? BOOL SetDW(LPCTSTR szName, DWORD dwValue) { ???????? //Prefix ???????? if(!m_hKey) return FALSE; ???????? // ???????? return ERROR_SUCCESS==RegSetValueEx(m_hKey, szName, 0, REG_DWORD, (LPBYTE)&dwValue, sizeof(DWORD)); ???? } ? ???? BOOL SetBinary(LPCTSTR szName, LPBYTE lpbValue, DWORD dwLen) { ???????? //Prefix ???????? if(!m_hKey) return FALSE; ???????? // ???????? return ERROR_SUCCESS==RegSetValueEx(m_hKey, szName, 0, REG_BINARY, lpbValue, dwLen); ???? } ? ???? BOOL SetMultiSZ(LPCTSTR szName, LPCTSTR lpszValue, DWORD dwLen) { ???????? return ERROR_SUCCESS==RegSetValueEx(m_hKey, szName, 0, REG_MULTI_SZ, (LPBYTE)lpszValue, sizeof(TCHAR)*dwLen); ???? } ? ???? BOOL DeleteValue(LPCTSTR szName) { ???????? //Prefix ???????? if(!m_hKey) return FALSE; ???????? // ???????? return ERROR_SUCCESS==RegDeleteValue(m_hKey, szName); ???? } ? ???? BOOL DeleteKey(LPCTSTR szName) { ???????? if(!m_hKey) return FALSE; ???????? return ERROR_SUCCESS==RegDeleteKey(m_hKey, szName); ???? } }; |
?????? 具體的使用方法可參照后面代碼中LoadMemDrv()的實(shí)現(xiàn)。
2.Driver和DLL合并與解壓
?????? 合并方法很簡(jiǎn)單。首先,我將DLL中的每個(gè)字節(jié)的數(shù)據(jù)提取出來組合成一個(gè)數(shù)組,然后在AP中引用該數(shù)組。
?????? 解壓的時(shí)候,直接將該數(shù)組組合成Driver的DLL就行了,如下:
| // 從靜態(tài)變量區(qū)提取MEM_DRV_NAME驅(qū)動(dòng)的內(nèi)容,并將其組合成為一個(gè)Driver的dll { ???? HANDLE?? hTempFile = INVALID_HANDLE_VALUE; ???? DWORD??? dwBytesReturned = 0; ???? TCHAR??? szFileName[MAX_PATH/2] = {0,}; ? ???? wsprintf(szFileName, L"%s%s", TEXT("//"), MEM_DRV_NAME); ? ???? hTempFile = CreateFile( ???????? szFileName, ???????? GENERIC_READ|GENERIC_WRITE, ???????? FILE_SHARE_WRITE|FILE_SHARE_READ, ???????? NULL, ???????? CREATE_ALWAYS, ???????? FILE_ATTRIBUTE_NORMAL, ???????? NULL ???????? ); ? ???? if (INVALID_HANDLE_VALUE == hTempFile) ???? { ???????? LogMessage(TEXT("[ERR] Faild to create file. File name %s"), szFileName); ???? } ???? else ???? {???????????? ???????? // DllFile就是DLL變量數(shù)組的名字 ???????? // 這里將DLL的內(nèi)容寫入到前面創(chuàng)建的文件L"MyMemoryDrv.dll"中 ???????? if (!WriteFile(hTempFile, DllFile, sizeof(DllFile), &dwBytesReturned, NULL)) ???????? { ????????????? LogMessage(TEXT("[ERR] Faild to write file.? Error code 0x%x"), GetLastError()); ???????? } ???????? else ???????? {????????????????? ????????????? LogMessage(TEXT("Create driver %s successfully"), szFileName); ???????? } ? ???????? CloseHandle(hTempFile); ???????? ???????? DeleteFile(MEM_DRV_DST_PATH); ? ???????? //if (!CopyFile(szFileName, L"//me.dat", 0)) ???????? if (!CopyFile(szFileName, MEM_DRV_DST_PATH, FALSE)) ???????? { ????????????? LogMessage(L"[ERR] Copy memory driver from %s to %s failed, Error code 0x%x!", szFileName, MEM_DRV_DST_PATH, GetLastError()); ???????? } ???? }??????? } |
3.Driver的實(shí)現(xiàn)
?????? 由于該Driver的功能僅僅是在User Mode和Kernel Mode下的內(nèi)存之間做轉(zhuǎn)換,所以只需要簡(jiǎn)單的實(shí)現(xiàn)一下DeviceIoControl就可以了,其它的流接口除了Open和Init直接為空就行了。
?????? 如下:
| /* * make use of MEM_IOControl to control memory address space conversion * Para: *??? pInBuf:?????? physical or virtual memory address *??? nInBufSize:?? 4 *??? pOutBuf:????? user buffer used to store data *??? nOutBufSize:? size of data the user wanted in bytes [note********************] */ DWORD MEM_IOControl(DWORD Handle, DWORD dwIoControlCode, PBYTE pInBuf, ?????????????????????? DWORD nInBufSize, PBYTE pOutBuf, DWORD nOutBufSize, PDWORD pBytesReturned) { ???? DWORD bRetVal = ERROR_SUCCESS; ???? PBYTE pMemBuffer = NULL; ???? ???? NKDbgPrintfW(L"MEM_IOControl(). Handle 0x%x, Code 0x%x, pInBuf 0x%8x, InSize 0x%x, OutSize 0x%x/r/n", Handle, dwIoControlCode, *(DWORD *)pInBuf, nInBufSize, nOutBufSize); ???? ??? switch(dwIoControlCode) { ???? case IOCTL_MEM_GET_PHYSICAL_RAM: ???????? NKDbgPrintfW(TEXT("IOCTL_MEM_GET_PHYSICAL_RAM/r/n")); ???????? break; ???? case IOCTL_MEM_GET_VIRTUAL_RAM: ???????? NKDbgPrintfW(TEXT("IOCTL_MEM_GET_VIRTUAL_RAM/r/n")); ???????? break; ???? default: ???????? NKDbgPrintfW(TEXT("**UNKNOWN**/r/n")); ???????? break; ??? } ???? ???? ??? switch(dwIoControlCode) { ???? case IOCTL_MEM_GET_PHYSICAL_RAM: ???????? {???????????? ????????????? do ????????????? {????????????????? ?????????????????? if (pInBuf == NULL || nInBufSize != sizeof(DWORD) || pOutBuf == NULL || nOutBufSize == 0) ?????????????????? { ?????????????????????? ?????????????????????? NKDbgPrintfW((_T("MEM_IOControl: IOCTL_MEM_GET_PHYSICAL_RAM - invalid paramter/n/r"))); ?????????????????????? bRetVal = ERROR_INVALID_PARAMETER; ?????????????????????? break; ?????????????????? } ?????????????????? ?????????????????? pMemBuffer = (PBYTE)VirtualAlloc(NULL, nOutBufSize, MEM_RESERVE, PAGE_NOACCESS); ?????????????????? ?????????????????? if (NULL != pMemBuffer) ?????????????????? { ?????????????????????? ?????????????????????? if (!VirtualCopy((void *)pMemBuffer, (void *)((*(DWORD *)pInBuf)>>8), nOutBufSize, PAGE_READWRITE | PAGE_NOCACHE | PAGE_PHYSICAL)) ?????????????????????? { ??????????????????????????? NKDbgPrintfW((_T("[MEMDRV] MEM_IOControl() : pMemBuffer VirtualCopy() Failed /n/r"))); ??????????????????????????? bRetVal = ERROR_INVALID_PARAMETER; ?????????????????????? } ?????????????????????? else ?????????????????????? { ??????????????????????????? ??????????????????????????? __try ??????????????????????????? {??? ???????????????????????????????? NKDbgPrintfW(L"Physical Add: 0x%8x, Virtual Add: 0x%8x/r/n", *(DWORD *)pInBuf, *(DWORD*)pMemBuffer); ???????????????????????????????? memcpy(pOutBuf, pMemBuffer, nOutBufSize); ??????????????????????????? } ??????????????????????????? __except(EXCEPTION_EXECUTE_HANDLER) ??????????????????????????? { ???????????????????????????????? NKDbgPrintfW((L"[ERR] Throw out exception in MEMDRV: MEM_IOControl()")); ???????????????????????????????? bRetVal = ERROR_INVALID_PARAMETER; ??????????????????????????? } ?????????????????????? } ?????????????????????? ?????????????????????? VirtualFree(pMemBuffer, 0, MEM_RELEASE); ?????????????????? }??? ?????????????????? else ?????????????????? { ?????????????????????? NKDbgPrintfW((_T("[MEMDRV] MEM_IOControl() : pMemBuffer VirtualCopy() Failed /n/r"))); ?????????????????????? bRetVal = ERROR_INVALID_PARAMETER;????????????????????? ?????????????????? } ????????????? } ????????????? while(0);????????? ????????????? ???????? } ???????? break; ???? case IOCTL_MEM_GET_VIRTUAL_RAM: ???????? {???????????? ????????????? NKDbgPrintfW(TEXT("IOCTL_MEM_GET_VIRTUAL_RAM/r/n")); ????????????? __try ????????????? {??? ?????????????????? // copy data from pInBuf to pOutBuf ?????????????????? memcpy(pOutBuf, &pInBuf, nOutBufSize);??? ????????????? } ????????????? __except(EXCEPTION_EXECUTE_HANDLER) ????????????? { ?????????????????? NKDbgPrintfW((L"[ERR] Throw out exception in MEMDRV: MEM_IOControl()")); ?????????????????? bRetVal = ERROR_INVALID_PARAMETER; ????????????? } ???????? } ???????? break; ???? default: ???????? NKDbgPrintfW(TEXT("**UNKNOWN**/r/n")); ???????? bRetVal = ERROR_INVALID_PARAMETER; ???????? break; ??? } ???? ???? return (ERROR_SUCCESS == bRetVal); } |
4.Driver的加載????
?????? Driver的加載包括兩個(gè)過程。首先將Driver相關(guān)的注冊(cè)表項(xiàng)寫入到注冊(cè)表中,然后調(diào)用API ActivateDevice()來實(shí)現(xiàn)動(dòng)態(tài)的加載。
?????? 代碼如下:
| /* ? [HKEY_LOCAL_MACHINE/Drivers/BuiltIn/MEM] "Dll"="MyMemoryDrv.dll" "Prefix"="MEM" "Index"=dword:1 "Order"=dword:0 "FriendlyName"="MEM driver" ? */ #define MEM_DRV_NAME???????????? L"MEM1:" bool? LoadMemDrv(void) { ???? BOOL bRetVal = false; ? ???? // Step1: modify the registry ???? class CReg MemDrvReg; ? ???? bRetVal = MemDrvReg.Create(HKEY_LOCAL_MACHINE, MEM_DRV_PATH); ???? bRetVal= MemDrvReg.SetSZ(L"Dll", L"MyMemoryDrv.dll");//, sizeof(L"MyMemoryDrv.dll")/sizeof(TCHAR)); ???? MemDrvReg.SetSZ(L"Prefix", L"MEM");//, sizeof(L"MEM")/sizeof(TCHAR)); ???? MemDrvReg.SetDW(L"Order", 0); ???? MemDrvReg.SetDW(L"Index", 1); ???? MemDrvReg.SetDW(L"Index", 1); ???? MemDrvReg.SetSZ(L"FriendlyName", L"MEM driver");//, sizeof(L"MEM driver")/sizeof(TCHAR)); ? ???? // Step2: load driver unsing device manager ???? hActiveMemDrv = INVALID_HANDLE_VALUE; ? ???? hActiveMemDrv = ActivateDevice(MEM_DRV_PATH, 0); ???? if (INVALID_HANDLE_VALUE == hActiveMemDrv) ???? { ???????? LogMessage(L"[ERR]Load driver %s failed", MEM_DRV_FULL_PATH); ???????? goto EXIT; ???? } ? ???? // Step3: Open stream driver???? ???? hFile = INVALID_HANDLE_VALUE; ???? hFile = CreateFile(MEM_DRV_NAME, ???????? GENERIC_READ|GENERIC_WRITE, ???????? 0, ???????? NULL, ???????? OPEN_EXISTING, ???????? FILE_ATTRIBUTE_NORMAL, ???????? NULL); ???? ???? if (INVALID_HANDLE_VALUE == hFile) ???? { ???????? LogMessage(L"[ERR] Open stream driver %s failed. Error code 0x%8x", MEM_DRV_NAME, GetLastError()); ???????? goto EXIT; ???? } ? ???? bIsDrvLoad = true; ???? bRetVal = TRUE; ? EXIT:??? ???? return (bRetVal == TRUE); } |
附:
具體的實(shí)現(xiàn)代碼可以到我的資源中下載。
總結(jié)
以上是生活随笔為你收集整理的一种User Mode下访问物理内存及Kernel Space的简单实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 蔚来单日换电量首次突破 5 万次,春节期
- 下一篇: RAPI简单说明及Sample Code