परीक्षण करने का प्रयास करते समय क्या इसे 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-बिट कर्नेल (आर्क लिनक्स पर 4.12.8-2-ARCH) के तहत चल रहा हूं, इसलिए 32-बिट उपयोगकर्ता स्थान में संपूर्ण 4GiB उपलब्ध है। (64-बिट कर्नेल पर 64-बिट कोड के विपरीत, या 32-बिट कर्नेल के साथ जहां 2:2 या 3:1 उपयोगकर्ता/कर्नेल विभाजन उच्च पृष्ठ को कर्नेल पता बना देगा।)

मैंने न्यूनतम स्थिर निष्पादन योग्य (कोई सीआरटी स्टार्टअप या libc, बस एएसएम) से कोशिश नहीं की है क्योंकि मुझे नहीं लगता कि इससे कोई फर्क पड़ेगा। कोई भी 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 में दिखाई नहीं देते हैं जो अभी भी कर्नेल को पते को अस्वीकार करने के लिए मना लेते हैं? मैंने ENOMEM की घटनाओं को 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 को निष्क्रिय करने का प्रयास किया था क्योंकि जब एमएमएपी विफल हो जाता है तो यह सिर्फ segfaults होता है।)

8
Peter Cordes 8 पद 2017, 13:34

1 उत्तर

सबसे बढ़िया उत्तर

एमएमएपी फ़ंक्शन अंततः या तो 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 पेज के लिए और दूसरा गार्ड पेज के रूप में इस्तेमाल किया जाता है। अनिवार्य रूप से, vssyscall पृष्ठ को अनमैप नहीं किया जा सकता है और इसलिए उपयोगकर्ता पता स्थान का उच्चतम पता प्रभावी रूप से 0xFFFFe000 है। 64-बिट प्रक्रियाओं के लिए, एक गार्ड पृष्ठ आरक्षित है। ये पृष्ठ केवल 64-बिट इंटेल और एएमडी प्रोसेसर पर आरक्षित हैं क्योंकि केवल इन प्रोसेसर पर SYSCALL तंत्र का उपयोग किया जाता है।

यहाँ वह जाँच है जो get_unmapped_area में की जाती है:

if (addr > TASK_SIZE - len)
     return -ENOMEM;
6
Hadi Brais 27 फरवरी 2019, 18:45