【问题标题】:Error code 487 (ERROR_INVALID_ADDRESS) when using VirtualAllocEX使用 VirtualAllocEX 时出现错误代码 487 (ERROR_INVALID_ADDRESS)
【发布时间】:2014-02-17 13:23:16
【问题描述】:

我正在尝试使用 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. 我的问题还有其他解决方案吗,比如换个函数?

【问题讨论】:

  • 你说的第三个参数。由于您正在请求特定的起始地址,因此失败可能是因为您的大小请求会导致分配与已分配的其他内容发生冲突。
  • 为什么要分配到那个特定的地址?你想达到什么目的?您确实意识到 PE 基地址只是“首选”地址而不是保证,对吧?如果“首选”地址已在使用中,操作系统有时必须通过在加载模块时重新定位和修复模块来避免冲突。
  • 要让操作系统选择分配位置,只需将lpAddress 参数设置为NULL。
  • 除非您回复 cmets,否则您的赏金似乎毫无意义。一旦您提出正确的问题,我们就很容易回答您的问题。您需要提供一个 SSCCE,以及​​为什么您希望能够在看起来已经被保留的地址上保留内存的一些充分理由。您没有得到答案的原因不是因为提供的声誉不够,而是因为您的问题不完整。解决问题,您将得到答案。
  • @DavidM:谢谢。你的回答很好,但没有完全解决我的问题;)

标签: winapi memory-management error-code


【解决方案1】:

我从您的代码中猜测是您正尝试使用 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的小优化工具,(用于“rebase”,即调整基地址),当你用它分发你的EXE和你自己的DLL时,你可以用它来确保每个 DLL 都有一个不同的基地址,每个基地址都位于这样,当 Windows 加载您的 EXE 和 DLL 时,它们已经有了正确的地址,并且不太可能必须对它们中的任何一个进行 rebase(执行上述操作)。对于某些应用程序,这可以显着缩短启动时间。 (在现代版本的 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 映像以匹配新地址。

【讨论】:

  • 您的回答解决了我的 ERROR_INVALID_ADDRESS 问题。这几天一直在做这个。现在我知道为什么 VirtualAllocEx 会随机失败。很有道理。
  • @rtischer8277 很高兴能够提供帮助:)
【解决方案2】:

在地址参数中使用from NULL:

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

如果 lpAddress 为 NULL,则函数确定分配地址的位置 地区。

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

【讨论】:

    猜你喜欢
    • 2017-01-05
    • 2011-12-20
    • 2017-08-03
    • 2015-10-16
    • 2011-03-01
    • 2013-09-28
    • 1970-01-01
    • 1970-01-01
    • 2012-10-27
    相关资源
    最近更新 更多