我有一些Visual C ++代码接收指向缓冲区的指针,该缓冲区包含需要由我的代码处理的数据和该缓冲区的长度。由于我控制之外的错误,有时这个指针未经初始化或未适合阅读(即当我尝试访问缓冲区中的数据时会导致崩溃。)
所以,我需要在使用之前验证这个指针。我不想使用IsBadReadPtr或IsBadWritePtr,因为每个人都认为他们是错误的。 (谷歌他们的例子。)他们也不是线程安全的 - 在这种情况下可能不是一个问题,虽然线程安全的解决方案会很好。
我已经看到了使用VirtualQuery完成此操作的建议,或者只是在异常处理程序中执行memcpy。但是,需要进行此检查的代码是时间敏感的,因此我需要最有效的检查,这也是100%有效。任何想法都将不胜感激。
为了清楚起见:我知道最好的做法是只读坏指针,让它引起异常,然后追溯到源并修复实际问题。但是,在这种情况下,坏指针来自我无法控制的Microsoft代码,所以我必须验证它们。
另请注意,我不关心指向的数据是否有效。我的代码正在寻找特定的数据模式,如果没有找到它们将忽略它们。我只是想防止在对这些数据运行memcpy时发生崩溃,并且在尝试memcpy时处理异常需要在我的代码中更改十几个地方(但是如果我有像IsBadReadPtr这样的东西来调用我只会必须在一个地方更改代码。)
答案 0 :(得分:10)
bool IsBadReadPtr(void* p)
{
MEMORY_BASIC_INFORMATION mbi = {0};
if (::VirtualQuery(p, &mbi, sizeof(mbi)))
{
DWORD mask = (PAGE_READONLY|PAGE_READWRITE|PAGE_WRITECOPY|PAGE_EXECUTE_READ|PAGE_EXECUTE_READWRITE|PAGE_EXECUTE_WRITECOPY);
bool b = !(mbi.Protect & mask);
// check the page is not a guard page
if (mbi.Protect & (PAGE_GUARD|PAGE_NOACCESS)) b = true;
return b;
}
return true;
}
答案 1 :(得分:9)
线程安全的解决方案很不错
我猜测只有IsBadWritePtr不是线程安全的。
在异常处理程序中执行memcpy
这实际上是IsBadReadPtr正在做的......如果你在代码中做了,那么你的代码就会遇到与IsBadReadPtr实现相同的错误:http://blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx
<强> - 编辑: - 强>
我读过的IsBadReadPtr的唯一问题是坏指针可能指向(因此你可能不小心碰到)堆栈的防护页面。也许你可以通过以下方式避免这个问题(因此安全地使用IsBadReadPtr):
此外,与上述URL相关的一些注释也建议使用VirtualQuery。
答案 2 :(得分:6)
这些功能使用不好的原因是问题无法可靠解决。
如果您正在调用的函数返回指向已分配的内存的指针,那么看起来有效,但它指向其他不相关的数据,如果您使用它会损坏您的应用程序,该怎么办?
最有可能的是,您调用的函数实际上表现正常,并且您正在滥用它。 (不保证,但经常案例。)
它的功能是什么?
答案 3 :(得分:1)
为什么你不能打电话给api
AfxIsValidAddress((p),sizeof(type),FALSE));
答案 4 :(得分:0)
检查内存有效性的任何实现都受到使IsBadReadPtr失败的相同行为的影响。你可以发布一个示例callstack,你想在哪里检查从Windows传递给你的指针的内存有效性吗?这可能有助于其他人(包括我)在一开始就诊断为什么需要这样做。
答案 5 :(得分:0)
我能想到的最快的解决方案是使用VirtualQuery
查询虚拟内存管理器以查看给定地址是否有可读页面,缓存结果(但是缓存会降低检查的准确性。)
示例(不缓存):
BOOL CanRead(LPVOID p)
{
MEMORY_BASIC_INFORMATION mbi;
mbi.Protect = 0;
::VirtualQuery(((LPCSTR)p) + len - 1, &mbi, sizeof(mbi));
return ((mbi.Protect & 0xE6) != 0 && (mbi.Protect & PAGE_GUARD) == 0);
}
答案 6 :(得分:0)
如果变量未初始化,则会被冲洗。迟早它会成为你不想玩的东西的地址(比如你自己的堆栈)。
如果您认为需要这个,那么(uintptr_t)var&lt; 65536是不够的(Windows不允许分配底部64k),没有真正的解决方案。 VirtualQuery等似乎“有效”,但迟早会烧毁你。
答案 7 :(得分:0)
如果你不得不求助于检查数据模式,可以参考以下几点:
如果你提到使用IsBadReadPtr,你可能正在为Windows x86或x64开发。
您可以检查指针。指向对象的指针将是单词对齐的。在32位窗口中,用户空间指针的范围为0x00401000-0x7FFFFFFF,或者对于大地址感知应用程序,则为0x00401000-0xBFFFFFFF。高2GB / 1GB是为内核空间指针保留的。
对象本身将存在于不可执行的读/写内存中。它可能存在于堆中,也可能是全局变量。如果它是全局变量,您可以验证它是否存在于正确的模块中。
如果您的对象具有VTable,并且您没有使用其他类,请将其VTable指针与来自已知正常对象的另一个VTable指针进行比较。
范围检查变量以查看它们是否可能有效。例如,bool只能是1或0,所以如果你看到一个值为242,那显然是错误的。指针也可以进行范围检查并检查对齐。
如果其中包含对象,请检查其VTable和数据。
如果您没有具有已知VTable地址的良好对象,则可以使用这些规则检查VTable是否有效:
答案 8 :(得分:0)
这是我使用的,它只是通过使用 #define
来代替官方的 microsoft 的,这样您就可以使用 microsoft 的,而不必担心它们会让您失望。
// Check memory address access
const DWORD dwForbiddenArea = PAGE_GUARD | PAGE_NOACCESS;
const DWORD dwReadRights = PAGE_READONLY | PAGE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY;
const DWORD dwWriteRights = PAGE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY;
template<DWORD dwAccessRights>
bool CheckAccess(void* pAddress, size_t nSize)
{
if (!pAddress || !nSize)
{
return false;
}
MEMORY_BASIC_INFORMATION sMBI;
bool bRet = false;
UINT_PTR pCurrentAddress = UINT_PTR(pAddress);
UINT_PTR pEndAdress = pCurrentAddress + (nSize - 1);
do
{
ZeroMemory(&sMBI, sizeof(sMBI));
VirtualQuery(LPCVOID(pCurrentAddress), &sMBI, sizeof(sMBI));
bRet = (sMBI.State & MEM_COMMIT) // memory allocated and
&& !(sMBI.Protect & dwForbiddenArea) // access to page allowed and
&& (sMBI.Protect & dwAccessRights); // the required rights
pCurrentAddress = (UINT_PTR(sMBI.BaseAddress) + sMBI.RegionSize);
} while (bRet && pCurrentAddress <= pEndAdress);
return bRet;
}
#define IsBadWritePtr(p,n) (!CheckAccess<dwWriteRights>(p,n))
#define IsBadReadPtr(p,n) (!CheckAccess<dwReadRights>(p,n))
#define IsBadStringPtrW(p,n) (!CheckAccess<dwReadRights>(p,n*2))
这种方法基于我对 Raymond Chen 的博文 If I'm not supposed to call IsBadXxxPtr, how can I check if a pointer is bad?
的理解答案 9 :(得分:0)
这是一个老问题,但这一部分:
<块引用>需要进行此检查的代码是时间敏感的,所以我需要 最有效的检查可能也是 100% 有效
VirtualQuery() 接受内核调用,因此对于大部分时间都可以读取内存的情况,异常处理程序中的简单 memcpy() 会更快。
__try
{
memcpy(dest, src, size);
}__except(1){}
在没有例外的情况下都保持在用户模式。对于内存读取多于读取好的用例,可能会慢一点(因为它会触发一个异常,该异常是通过内核来回往返)。
您还可以使用自定义 memcpy 循环和 *size 对其进行扩展,以便您可以准确返回实际读取的字节数。
答案 10 :(得分:-1)
我担心你运气不好 - 没有办法可靠地检查指针的有效性。什么Microsoft代码给你不好的指针?
答案 11 :(得分:-1)
如果您使用的是VC ++,那么我建议使用特定于Microsoft的特定关键字__try __except 并且捕获硬件异常