如何强制 malloc 在 64 位系统上返回 32 位指针?

use*_*570 5 linux malloc 32-bit glibc x86-64

我的程序中有一个错误,因此它在 32 位上工作正常,但在 64 位上只能随机工作,因为程序中某处有 32 位指针截断。

\n\n

原因是,如果 malloc 返回一个内存地址,并且在指针分配时在高 32 位中设置了一个位,则指针将变为 NULL。

\n\n

所以我找到了触发段错误的指针。但它\xe2\x80\x99s不是我参与的程序(我\xe2\x80\x99m是用户而不是开发人员)并且\xe2\x80\x99s根本没有编译器警告。

\n\n

因此,如何确保 malloc 返回一个可以在 32 位模式下使用的值,而不是花费我没有\xc2\xb4t 的时间?

\n

Pet*_*des 3

最佳选择:让这个有缺陷的程序的作者将其代码修复为 64 位干净代码,或者使用Linux 的 x32 ABI ( gcc -mx32) 来处理 64 位寄存器但使用 32 位指针。


AFAIK,没有简单的方法可以完全按照您所要求的常规 glibc malloc 进行操作。将malloc/free替换 mmap(MAP_32BIT)为可能可行,但对于小分配来说很糟糕,因为它只能分配 4k 块。
mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_32BIT, -1, 0)/ munmap。它不是直接替代品,因为某些代码可能需要与free.

或者,如果您可以找到一个自定义程序来作为非 64 位干净的有缺陷的软件malloc的解决MAP_32BIT方法,您可以将其用作直接替代品。 或者修改自定义 malloc 库(例如tcmalloc添加)MAP_32BIT可能比构建整个自定义 glibc 容易得多。

@Basile Starynkevitch 建议也许可以使用mmap(MAP_FIXED|MAP_NORESERVE)映射一个很大的范围,这样剩下的就是低 32 位。(然后永远不要碰这个映射)。 用户空间中虚拟内存地址的范围 但这只有在将任何库加载到高地址之前完成才有效(How to limit a 64-bit process address space to less than 4G?建议了一种可能的方法来预链接以将库加载到低地址)。

MAP_FIXED将替换该范围内的现有映射,因此您可能想省略 MAP_FIXED 并仅给出一个非 NULL 提示地址并检查它是否映射到您请求的地址。


如果您的代码对于指针来说不是 64 位干净的,但您仍然想利用 64 位寄存器来提高效率[u]int64_t,那么 Linux 的 x32 ABI 可能正是您所需要的。

x32是 x86-64 长模式的 ILP32 ABI,因此指针 、size_t、 和long是 32 位类型,但 64 位整数类型如long longuint64_t可以使用 64 位寄存器。CPU 以 64 位长模式运行,并且 ABI 使用与常规 x86-64 System V ABI 相同的高效寄存器参数调用约定。

不要将 x32 与旧版 32 位代码 i386 ABI混淆。他们完全没有关系。x32 是对常规 x86-64 ABI 的较小修改。

使用 x32 的通常原因是具有大量指针数据结构的较小缓存占用空间,从而增加缓存命中率并节省内存带宽。

我没有适用于 x32 的 Intel SVML 运行时

英特尔的编译器和库(包括 SVML)从版本 16.0 开始支持 x32,请参阅英特尔的 x32 psABI 支持页面。如果您的版本早于 16.0,这可能是升级的好理由。

(该页面似乎说 x32 可能不支持 OpenMP,至少在 16.0 版本中是这样。如果我没看错的话,这将是一个问题。当前版本是 19.01,也许现在可以使用。)

请注意,添加两个参数的函数的 asm 输出uint64_t对于icc -O3 -mx32和 来说是相同的icc -O3 -m64,都使用add rdi, rsi/ mov rax,rdi。(虽然 ICC 擅长自动矢量化和自动并行化,但显然它不擅长发现lea rax, [rdi+rsi]窥视孔优化,并且在使用时mov不遵循其自己的优化手册建议先复制,然后销毁副本以获得更高效的 mov-消除。)

但无论如何,当前版本的 Intel 编译器本身肯定支持 x32;C++ 的 asm 输出显示 是uint64_tunsigned long long不是unsigned long.


让 GCC 使用 SVML:

GCC 有一个-mveclibabi=svml选项,可以使用 SVML 函数自动矢量化。因此,如果 x32 ICC 在使用 OpenMP 自动并行化时遇到问题,您可以尝试 GCC。

gcc -fopenmp -O3 -ffast-math -march=native -mveclibabi=svml也许应该不错。(-ffast-math类似于 ICC 默认启用的功能。)


让常规 64 位malloc返回 32 位指针

相关:问题的 OS X 版本:如何在 x86_64 上的第一个 4GB 内“malloc”(也没有简单的方法)。

我不认为 glibc malloc 有这个选项。

如果您能找到mmap用于malloc从操作系统获取新页面的调用并向MAP_32BIT其添加标志,则可以通过较小的更改构建您自己的 glibc。

将映射放入进程地址空间的前 2 GB 中。对于 64 位程序,此标志仅在 x86-64 上受支持。添加它是为了允许线程堆栈分配在前 2 GB 内存中的某个位置,从而提高某些早期 64 位处理器上的上下文切换性能

如果您编译非 PIE 可执行文件,中断应该已经在低 32 处,因此您不需要阻止 glibc 使用brk()小分配。

https://www.gnu.org/software/libc/manual/html_node/Malloc-Tunable-Parameters.htmlmallopt列出了可以通过调用或环境变量设置的内容,例如M_MMAP_THRESHOLD。将其设置为 4k 将使 glibc 始终使用 mmap 来进行该大小或更大的分配。但没有 32 位选项。