【问题标题】:How to force malloc to return a 32 bits like pointer on a 64 bits system?如何强制 malloc 在 64 位系统上返回像指针一样的 32 位?
【发布时间】:2019-06-15 11:51:53
【问题描述】:

我的程序中有一个错误,因此它在 32 位上运行良好,但在 64 位上只能随机运行,因为程序中某处的 32 位指针被截断。

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

所以我找到了触发段错误的指针。但这不是我参与的程序(我是用户而不是开发人员),根本没有编译器警告。

因此,与其花时间我没有,如何确保 malloc 返回一个可在 32 位模式下使用的值?

【问题讨论】:

  • 在这个程序中使用完整的 64 位有巨大的性能优势,所以我真的不想使用 x32。
  • the x32 ABI 对于您的用例有什么问题?长模式下的 32 位指针听起来正是您所需要的,除非您还 mmap 直接使用额外的地址空间,即使您有 malloc 使用 mmap(MAP_32BIT)?或者如果 long 是 32 位类型,程序无法利用 64 位寄存器?还是您实际上是指 i386,而不是 x32?
  • 那么带有 64 位指针的 x86-64 如何以 x32(64 位模式下的 ILP32)没有的方式提供帮助?你真的试过gcc -mx32 -O3 -march=native吗?注意-mx32 不是-m32
  • 你没有读到我写的东西。 x32 是 64 位模式。去阅读en.wikipedia.org/wiki/X32_ABI。它是用于 64 位模式 x86-64 的 ILP32 ABI。 uint64_t 适合 x32 中的单个寄存器。如果您的代码不是 64 位干净的指针,但使用大量 64 位整数数学,它可能正是您需要的。 (根据您的评论编辑进行编辑:除非您的编译器不支持 x32,否则这是一个问题。)如果您的编译器不支持 x32,那么显然您还没有尝试过...所以我认为您混淆了 x32 (-mx32) 带有蹩脚的旧 i386 32 位代码 (-m32)
  • x32 与普通的 -m64 完全相同,只是指针、size_tlong 是 32 位类型。它 64位的。所以实际的障碍是你没有 x32 SVML。我同意这是一个很好的选择,但请不要再说“即使它是 64 位”之类的关于 x32 的错误。查看从 ICC16 及更高版本获得的 uint64_t a+b 相同的 asm:godbolt.org/z/j8gIuh。 Intel 的编译器确实支持 x32,并且 SVML 可用于 x32 (or at least it was as of version 16.0),所以希望你可以去下载它。

标签: linux malloc x86-64 glibc 32-bit


【解决方案1】:

最好的选择:让这个有问题的程序的作者将他们的代码修复为 64 位干净,或者将 Linux's x32 ABI (gcc -mx32) 用于 64 位寄存器但 32 位指针。


AFAIK,没有简单的方法可以完全按照您的要求使用常规 glibc malloc。用mmap(MAP_32BIT) 替换malloc/free 可能会起作用,但对于小分配来说是可怕的,因为它只能分配4k 块。
mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_32BIT, -1, 0) / munmap。它不是直接替换,因为某些代码可能需要与 free 兼容的指针。

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

@Basile Starynkevitch 建议也许使用mmap(MAP_FIXED|MAP_NORESERVE) 来映射一个巨大的范围,所以剩下的就是低 32 位。 (然后永远不要碰这个映射)。 The range of virtual memory address in userspace 但这只有在任何库加载到高地址之前完成才有效(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_tlong 是 32 位类型,但像 long long 和 @987654348 这样的 64 位整数类型@ 可以使用 64 位寄存器。 CPU 以 64 位长模式运行,ABI 使用与常规 x86-64 System V ABI 相同的有效寄存器参数调用约定。

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

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

我没有用于 x32 的英特尔 SVML 运行时

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

(该页面似乎说 x32 可能不支持 OpenMP,至少在 16.0 版中。如果我没看错的话,那将是一个问题。当前是 19.01,也许它现在正在工作。)

请注意,添加两个 uint64_t 参数的函数的 asm 输出对于 icc -O3 -mx32icc -O3 -m64 是相同的,两者都使用 add rdi, rsi / mov rax,rdi。 (虽然 ICC 擅长自动矢量化和自动并行化,但显然它不善于发现 lea rax, [rdi+rsi] 作为窥视孔优化,并且在使用 mov 时不遵循其自己的优化手册建议先复制然后销毁复制more efficient mov-elimination。)

但无论如何,当前版本的英特尔编译器本身肯定支持 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 版本:How to 'malloc' within first 4GB on x86_64(也没有简单的方法)。

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

如果您能找到malloc 用来从操作系统获取新页面的mmap 调用,并添加MAP_32BIT 标志,则可以构建您自己的glibc,只需稍作改动即可。

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

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

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

【讨论】:

  • 也许值得一提... MAP_FIXED 设置时,MAP_32BIT 标志将被忽略” per mmap(2) man page
  • @jww:有道理,在这种情况下,内核不会对分配位置做出任何选择。如果提示地址会导致与现有映射发生冲突,则 MAP_FIXED 禁用回退到内核选择的地址。因此,它可以应用的唯一方法是如果在低 32 之外传递一个固定地址,则返回错误。(或者如果映射的 end 扩展到低 32 之外)。
  • 我正在寻找的是让 icc 不使用 SVML。但强化优化只启用了 SVML 代码生成。
【解决方案2】:

内核应该使用各种personality flags 来支持这一点:ADDR_LIMIT_32BITADDR_LIMIT_3GBPER_LINUX32PER_LINUX32_3GBPER_LINUX_32BITsetarch linux32 -B 命令调用了personality(PER_LINUX32|ADDR_LIMIT_32BIT),但是这个请求在 x86-64 上被内核忽略了:

$ setarch x86_64 -B grep stack /proc/self/maps  
7fff38461000-7fff38482000 rw-p 00000000 00:00 0                          [stack]

我认为这仅针对其他 64 位架构实现,以支持移植 32 位软件时出现指针截断问题。

【讨论】:

  • 嗯,是的,但我的主要目标是让程序快速运行,而不是花几天时间而不花很多时间。我担心创建分配器会比修复实际错误花费更多时间。
  • 感谢您发布此信息;我想知道personality 的东西是否有任何作用,它的用途是什么!其他一些 64 位 ISA,如 MIPS,扩展到 64 位 而不 引入新模式。确保区分用户空间是否正在运行可识别 64 位的代码会更有意义,因为内核 不需要 需要执行与 x86 等效的操作为用户空间选择 32 位或 64 位模式的不同 CS 值。
  • (在 MIPS 上,他们扩展了 ISA,因此 add/sub 等自然地将寄存器值符号扩展为 64 位,添加 dadd 双字加法指令。移位指令确实显式地符号 -将它们的 32 位结果扩展到 64 位。有关 MIPS64 和 x86-64 之间的一些比较,请参阅MOVZX missing 32 bit register to 64 bit register,这是一个有趣的 ISA 设计选择,特别是考虑到 MIPS 有如此多的操作码编码空间来添加许多指令的双字版本。但我认为 AMD64 是更好的选择:隐式零扩展很方便。)
  • @user2284570 问题是如果没有内核帮助,共享对象中的全局变量将被映射到地址空间的高位。您可以使用自定义分配器将堆地址强制为较低的 4 GiB,但全局变量的地址不会受此影响。
  • @FlorianWeimer glibc 是唯一的共享对象,用于在内存管理之外执行控制台写入和 ioctls 和 fread/fwrite。
猜你喜欢
  • 1970-01-01
  • 2012-11-29
  • 2011-09-08
  • 2012-03-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-19
  • 2012-06-12
相关资源
最近更新 更多