使用VirtualAllocEX时出错代码487(ERROR_INVALID_ADDRESS)

时间:2014-01-26 19:56:45

标签: winapi memory-management error-code

我正在尝试使用VirtualAllocEx()。当我将dwSize(第三个参数)设置为大于约63 MB的数字时,当我查看GetLastError()时,会导致生成错误代码487。但是,它适用于较小的尺寸,如4MB。

以下是我的代码的一部分:

VirtualAllocEx(peProcessInformation.hProcess,
               (LPVOID)(INH.OptionalHeader.ImageBase),
               dwImageSize, 
               MEM_RESERVE | MEM_COMMIT,
               PAGE_EXECUTE_READWRITE);

如果我使用4MB EXE文件,则LPVOID返回值为0x00400000,但在其他情况下(20MB或更大文件),它返回0x00000000

  1. dwSize参数是否有最大值?

  2. 我的问题是否有其他解决方案,例如其他功能?

2 个答案:

答案 0 :(得分:16)

我对您的代码的猜测是,您尝试使用类似this technique的内容手动将DLL或EXE加载到内存中 - 是吗?我将在最后解决这个问题(双关语),但首先要快速解释为什么VirtualAllocEx失败。

为什么VirtualAllocEx会出现此错误?

在特定地址分配内存的问题是,该地址需要有足够的空间来分配您请求的内存大小。这就是为什么,一般来说,当你请求内存时,你让操作系统决定把它放在哪里。 (另外,让OS / malloc库决定可以带来其他好处,例如减少碎片等 - 超出了这个答案的范围。)

您遇到的问题不是VirtualAllocEx无法分配64MB而不是4MB。 VirtualAllocEx可以根据需要分配(几乎)内存。问题是,在您指定的地址,在您的过程中,没有64MB的未分配内存。

考虑假设地址0-15(0x0 - 0xF),其中-标记空内存,x标记分配内存:

0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
x  x  -  x  -  -  -  -  x  -  -  -  -  -  -  -

这是您进程的内存空间。现在,您想在地址0x4分配4个字节。简单 - 0x4到0x7是免费的,所以你分配和获取(用X标记的新分配):

0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
x  x  -  x  X  X  X  X  x  -  -  -  -  -  -  -

优秀。但现在假设你想要分配6个字节。地址0x4没有六个空闲字节:在0x8处有一些内存:

0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
x  x  -  x  -  -  -  -  x  -  -  -  -  -  -  -
            1  2  3  4  bang!

你无法做到。问题不在于内存分配器不能处理分配6个字节,而是内存不能自由地执行它。最有可能的是,它可以改变内存 - 在正常的非GC程序中,你可以移动内存来腾出空间,因为你可能会留下一些不知道内容的悬挂指针。他们所指向的记忆改变了地址。唯一要做的就是失败并且根本不分配内存,或者分配它有可用空间的地方,比如0x9或0xA。

你可能想知道为什么VirtualAllocEx失败了ERROR_INVALID_ADDRESS而不是NULL:很可能是因为你指定了一个它无法分配的地址;因此,即使在该地址(可能)有一些空闲内存,也不够,并且地址不是有效的。这在the documentation中暗示:

  

通过指定MEM_COMMIT尝试提交特定的地址范围   没有MEM_RESERVE和非NULL lpAddress失败,除非整个   范围已被保留。生成的错误代码是   ERROR_INVALID_ADDRESS。

这不是你的情况:你一次指定两个标志,但如果方法不能保留,那么它实际上就属于这种情况。它无法在该地址保留整个范围,因此会提供错误代码ERROR_INVALID_ADDRESS

加载DLL或EXE图像

那么,你应该怎么处理你的问题,我从你的问题中猜测并且代码是在内存中加载DLL或EXE图像?

在这里,您需要在EXE文件中的图像位置有一些背景知识。通常,EXE在进程的虚拟地址位置0x400000处加载到存储器中。它是可选的:您的链接器可以将它放在任何地方,但是this value is common。类似地,DLL具有共同的默认位置:0x10000000。因此,对于一个EXE和一个DLL,你很好:图像加载器几乎可以肯定地将它们加载到它们请求的位置。

当你有两个DLL时,会发生什么,要求位于0x10000000?

答案是图片变基。图像位置是可选的,不是必需的。依赖于在特定地址加载的图像内的代码可以由图像加载器调整,因此第二个DLL可能不是加载到0x10000000,而是加载到其他地方 - 比如0x1080000。这是0x80000的地址差异,因此加载器实际上修补了DLL中的一堆地址和代码,因此所有认为应该引用0x10000000的位现在都指的是0x10800000。

这确实很常见,每次加载EXE时都会对几个DLL进行此操作。微软有一个名为rebase的小优化工具(用于"重新调整",即调整基地址),当你用它分发你的EXE和你自己的DLL时,这是很常见的,你可以使用这个来确保每个DLL都有一个不同的基地址,每个地址的位置都是这样,当Windows加载你的EXE和DLL时,它们已经有了正确的地址,并且它不太可能必须重新绑定 - 执行上述操作 - 对他们中的任何一个。对于某些应用,这可以显着改善开始时间。 (在现代版本的Windows中,有时候DLL会被移动 - 这是address space layout randomization,这是一种安全技术,可以在每次运行时故意确保代码不在同一个地址。)

(另一个原因是一些DLL和EXE压缩工具剥离了用于此重定位的数据。这很好,因为它使EXE更小......直到它需要重新定位,并且由于数据丢失,因此无法加载。或者您可以build with a fixed base, and it will magically work right until it doesn't.不要对您的EXE或DLL执行此操作。)

那么,当你尝试手动将DLL加载到内存中时,你应该怎么做,并且在它要求加载的地址上没有足够的空间用于它?容易 - 它不是一个致命的错误,只需将其加载到其他地方,然后自己执行变基。我建议如果你有问题要问一个新的SO问题,但为了给你一个起点,你可以使用RebaseImage功能,或者如果你不能使用它或想自己做,我找到this code which from a quick overview seems to perform this manually。无法保证其正确性。

TLDR

您的进程地址空间在您指定的地址处没有64MB的空白空间,因此您无法在那里分配64MB的内存。相反,将其分配到其他位置并修补/重新绑定加载的DLL或EXE图像以匹配新地址。

答案 1 :(得分:0)

在地址参数中从NULL使用:

void* pImageBase = VirtualAllocEx(peProcessInformation.hProcess,
               NULL,
               dwImageSize, 
               MEM_RESERVE | MEM_COMMIT,
               PAGE_EXECUTE_READWRITE);

如果lpAddress为NULL,则该函数确定在何处分配 地区。

请注意,对于其余的代码,请使用(void* pImageBase),而不要使用(INH.OptionalHeader.ImageBase)