我正在尝试使用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
。
dwSize
参数是否有最大值?
我的问题是否有其他解决方案,例如其他功能?
答案 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图像?
在这里,您需要在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。无法保证其正确性。
您的进程地址空间在您指定的地址处没有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)
。