当前位置: 首页 > 面试题库 >

为什么不能在64位内核的32位Linux进程中映射(MAP_FIXED)最高虚拟页面?

宋健柏
2023-03-14
问题内容

尝试测试时是否允许访问跨越x86中零边界的内存?在Linux的用户空间中,我编写了一个32位测试程序,该程序试图映射32位虚拟地址空间的低和高页。

之后echo 0 | sudo tee /proc/sys/vm/mmap_min_addr,我可以映射零页面,但是我不知道为什么不能映射-4096(即(void*)0xfffff000最高页面)。
为什么要mmap2((void*)-4096)退货-ENOMEM

strace ./a.out 
execve("./a.out", ["./a.out"], 0x7ffe08827c10 /* 65 vars */) = 0
strace: [ Process PID=1407 runs in 32 bit mode. ]
....
mmap2(0xfffff000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = -1 ENOMEM (Cannot allocate memory)
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0

另外,什么支票在中拒绝它linux/mm/mmap.c,为什么要这样设计?这是确保创建指向一个过去的对象的指针不会回绕并中断指针比较的部分吗,因为ISO C和C
++允许创建指向过去的一个指针,但不能在外部对象。

我在64位内核(Arch
Linux上为4.12.8-2-ARCH)下运行,因此32位用户空间具有整个可用的4GiB。(与64位内核或32位内核(其中2:2或3:1用户/内核拆分会使高页成为内核地址)上的64位代码不同。)

我没有尝试使用最小的静态可执行文件(没有CRT启动程序或libc,只有asm),因为我认为这不会有所作为。没有一个CRT启动系统调用看起来可疑。

在断点处停下来时,我检查了一下/proc/PID/maps。主页尚未使用。堆栈包括第二高的页面,但未映射首页。

00000000-00001000 rw-p 00000000 00:00 0             ### the mmap(0) result
08048000-08049000 r-xp 00000000 00:15 3120510                 /home/peter/src/SO/a.out
08049000-0804a000 r--p 00000000 00:15 3120510                 /home/peter/src/SO/a.out
0804a000-0804b000 rw-p 00001000 00:15 3120510                 /home/peter/src/SO/a.out
f7d81000-f7f3a000 r-xp 00000000 00:15 1511498                 /usr/lib32/libc-2.25.so
f7f3a000-f7f3c000 r--p 001b8000 00:15 1511498                 /usr/lib32/libc-2.25.so
f7f3c000-f7f3d000 rw-p 001ba000 00:15 1511498                 /usr/lib32/libc-2.25.so
f7f3d000-f7f40000 rw-p 00000000 00:00 0 
f7f7c000-f7f7e000 rw-p 00000000 00:00 0 
f7f7e000-f7f81000 r--p 00000000 00:00 0                       [vvar]
f7f81000-f7f83000 r-xp 00000000 00:00 0                       [vdso]
f7f83000-f7fa6000 r-xp 00000000 00:15 1511499                 /usr/lib32/ld-2.25.so
f7fa6000-f7fa7000 r--p 00022000 00:15 1511499                 /usr/lib32/ld-2.25.so
f7fa7000-f7fa8000 rw-p 00023000 00:15 1511499                 /usr/lib32/ld-2.25.so
fffdd000-ffffe000 rw-p 00000000 00:00 0                       [stack]

是否有没有出现的VMA区域maps仍然说服内核拒绝该地址?我查看了ENOMEMin
的出现linux/mm/mmapc.,但是要阅读很多代码,所以也许我错过了一些东西。保留一些高地址范围的东西,还是因为它在堆栈旁边?

以其他顺序进行系统调用无济于事(但是PAGE_ALIGN和类似的宏会被仔细地编写,以避免在屏蔽之前回绕,因此无论如何都不可能。)

完整源代码,使用编译gcc -O3 -fno-pie -no-pie -m32 address-wrap.c

#include <sys/mman.h>

//void *mmap(void *addr, size_t len, int prot, int flags,
//           int fildes, off_t off);

int main(void) {
    volatile unsigned *high =
        mmap((void*)-4096L, 4096, PROT_READ | PROT_WRITE,
             MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS,
             -1, 0);
    volatile unsigned *zeropage =
        mmap((void*)0, 4096, PROT_READ | PROT_WRITE,
             MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS,
             -1, 0);


    return (high == MAP_FAILED) ? 2 : *high;
}

(我省去了尝试取消引用的部分,(int*)-2因为它在mmap失败时只会出现段错误。)


问题答案:

mmap函数最终调用do_mmap或do_brk_flags,它们执行满足内存分配请求的实际工作。这些函数依次调用get_unmapped_area。正是在该函数中进行检查,以确保不能分配超出TASK_SIZE定义的用户地址空间限制的内存。我引用代码:

 * There are a few constraints that determine this:
 *
 * On Intel CPUs, if a SYSCALL instruction is at the highest canonical
 * address, then that syscall will enter the kernel with a
 * non-canonical return address, and SYSRET will explode dangerously.
 * We avoid this particular problem by preventing anything executable
 * from being mapped at the maximum canonical address.
 *
 * On AMD CPUs in the Ryzen family, there's a nasty bug in which the
 * CPUs malfunction if they execute code from the highest canonical page.
 * They'll speculate right off the end of the canonical space, and
 * bad things happen.  This is worked around in the same way as the
 * Intel problem.

#define TASK_SIZE_MAX   ((1UL << __VIRTUAL_MASK_SHIFT) - PAGE_SIZE)

#define IA32_PAGE_OFFSET    ((current->personality & ADDR_LIMIT_3GB) ? \
                    0xc0000000 : 0xFFFFe000)

#define TASK_SIZE       (test_thread_flag(TIF_ADDR32) ? \
IA32_PAGE_OFFSET : TASK_SIZE_MAX)

在具有48位虚拟地址空间的处理器上,__VIRTUAL_MASK_SHIFT值为47。

请注意,TASK_SIZE根据当前进程是32位32位,64位32位还是64位64位来指定。对于32位进程,将保留两个页面。一个用于vsyscall页面,另一个用作保护页面。本质上,无法取消映射vsyscall页面,因此用户地址空间的最高地址实际上是0xFFFFe000。对于64位进程,保留一个保护页。这些页面仅在64位Intel和AMD处理器上保留,因为仅在这些处理器SYSCALL上使用了该机制。

这是在中执行的检查get_unmapped_area

if (addr > TASK_SIZE - len)
     return -ENOMEM;


 类似资料:
  • 问题内容: 最近,我一直在对我公司的数据库产品的写入性能进行一些基准测试,并且发现仅切换到64位JVM可以使性能持续提高20-30%。 我不允许详细介绍我们的产品,但基本上它是面向列的数据库,已针对存储日志进行了优化。基准测试包括向其提供几GB的原始日志,并确定分析它们并将其作为结构化数据存储在DB中所需的时间。CPU和I / O的处理非常繁重,尽管很难说是什么比例。 有关设置的一些注意事项: 两

  • 问题内容: 本机整数算术指令是否比其计数器部件慢(在装有OS的计算机上)? 编辑:在当前CPU上,例如Intel Core2 Duo,i5 / i7等。 问题答案: 这取决于确切的CPU和操作。例如,在64位Pentium IV上,64位寄存器的乘法要慢得多。Core 2和更高版本的CPU从一开始就设计用于64位操作。 通常,即使是为64位平台编写的代码也使用32位变量,其中的值将适合它们。这主要

  • 编辑: > 继Ingo Leonhardt之后,我在以root用户身份而不是以标准用户身份登录后尝试了。这样做解决了root的问题(然后程序可以在以root身份登录时分配所有物理内存)。但这只适用于root用户,不适用于其他用户。但是,至少这意味着原则上内核可以很好地处理这一点,而且只有一个配置问题。 关于:我尝试显式地添加 null > 这是我自己的愚蠢造成的。在我的用户设置中有一个(早已被遗忘

  • 问题内容: 我已经使用Java一段时间了,而我典型的设置新开发机的习惯要求从Oracle站点下载并安装最新的JDK。 今天这引发了一个不寻常的问题, 回想起来,我已经安装了之前的两个版本,并且很高兴将普通的工具链插入(Eclipse)。在我的日常编程中,我不会回想起曾经因为使用64位JRE(或为此目的而针对64位JRE)而不得不以其他方式进行更改或思考的事情。 根据我对64位和32位的理解- 确实

  • 问题内容: 我从Linux 64位汇编程序访问进程命令行时遇到问题。为了用最少的代码重现该代码,我制作了一个32位程序,该程序打印程序名的前5个字符: 该程序正在运行。当我将其转换为64位并在Linux 64上运行时,它不会显示任何内容: 我的错误在哪里? 问题答案: 您正在将正确的地址加载到中。 然后调用32位syscall接口。这会将地址截断为32位,这使它不正确。(如果您使用调试器并在第一个

  • 问题内容: 我的电脑正在使用Windows 7 64位。但是将要部署我的jsp Web应用程序的服务器是32位。 我需要在PC上安装32位JDK / JRE才能进行开发吗?我正在使用Eclipse。 非常感谢你。 问题答案: 您绝对不需要安装32位JRE即可进行开发。您构建的Java代码不会跟踪您的64位。(我假设您没有使用JNI,这会使事情变得有些复杂。) 不过,您 可能 需要安装32位JRE进