From 883ea27df0f7d9a5836feb493c35026381c55b2b Mon Sep 17 00:00:00 2001 From: Corinna Vinschen Date: Mon, 16 May 2011 10:27:14 +0000 Subject: * heap.cc (heap_init): Rewrite initial heap allocation to use addresses beyond 0x20000000. Explain why and how. * shared.cc (shared_info::heap_slop_size): Remove. * shared_info.h (class shared_info): Remove heap_slop_inited and heap_slop members. Remove heap_slop_size declaration. (CURR_SHARED_MAGIC): Update. * wincap.cc: Throughout, drop heapslop. * wincap.h (struct wincaps): Drop heapslop. --- winsup/cygwin/heap.cc | 95 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 70 insertions(+), 25 deletions(-) (limited to 'winsup/cygwin/heap.cc') diff --git a/winsup/cygwin/heap.cc b/winsup/cygwin/heap.cc index 21516d5c9..0fe170a89 100644 --- a/winsup/cygwin/heap.cc +++ b/winsup/cygwin/heap.cc @@ -17,6 +17,7 @@ details. */ #include "dtable.h" #include "cygheap.h" #include "child_info.h" +#include #define assert(x) @@ -30,41 +31,85 @@ heap_init () { const DWORD alloctype = MEM_RESERVE; /* If we're the forkee, we must allocate the heap at exactly the same place - as our parent. If not, we don't care where it ends up. */ + as our parent. If not, we (almost) don't care where it ends up. */ page_const = wincap.page_size (); if (!cygheap->user_heap.base) { + /* Starting with Vista, Windows performs heap ASLR. This spoils + the entire region below 0x20000000 for us, because that region + is used by Windows to randomize heap and stack addresses. + Therefore we put our heap into a safe region starting at 0x20000000. + This should work right from the start in 99% of the cases. But, + there's always a but. Read on... */ + uintptr_t start_address = 0x20000000L; + uintptr_t largest_found = 0; + size_t largest_found_size = 0; + SIZE_T ret; + MEMORY_BASIC_INFORMATION mbi; + cygheap->user_heap.chunk = cygwin_shared->heap_chunk_size (); - /* For some obscure reason Vista and 2003 sometimes reserve space after - calls to CreateProcess overlapping the spot where the heap has been - allocated. This apparently spoils fork. The behaviour looks quite - arbitrary. Experiments on Vista show a memory size of 0x37e000 or - 0x1fd000 overlapping the usual heap by at most 0x1ed000. So what - we do here is to allocate the heap with an extra slop of (by default) - 0x400000 and set the appropriate pointers to the start of the heap - area + slop. A forking child then creates its heap at the new start - address and without the slop factor. Since this is not entirely - foolproof we add a registry setting "heap_slop_in_mb" so the slop - factor can be influenced by the user if the need arises. */ - cygheap->user_heap.slop = cygwin_shared->heap_slop_size (); - while (cygheap->user_heap.chunk >= MINHEAP_SIZE) + do { - /* Initialize page mask and default heap size. Preallocate a heap - * to assure contiguous memory. */ - cygheap->user_heap.base = - VirtualAlloc (NULL, cygheap->user_heap.chunk - + cygheap->user_heap.slop, - alloctype, PAGE_NOACCESS); + cygheap->user_heap.base = VirtualAlloc ((LPVOID) start_address, + cygheap->user_heap.chunk, + alloctype, PAGE_NOACCESS); if (cygheap->user_heap.base) break; - cygheap->user_heap.chunk -= 1 * 1024 * 1024; + + /* Ok, so we are at the 1% which didn't work with 0x20000000 out + of the box. What we do now is to search for the next free + region which matches our desired heap size. While doing that, + we keep track of the largest region we found. */ + start_address += wincap.allocation_granularity (); + while ((ret = VirtualQuery ((LPCVOID) start_address, &mbi, + sizeof mbi)) != 0) + { + if (mbi.State == MEM_FREE) + { + if (mbi.RegionSize >= cygheap->user_heap.chunk) + break; + if (mbi.RegionSize > largest_found_size) + { + largest_found = (uintptr_t) mbi.BaseAddress; + largest_found_size = mbi.RegionSize; + } + } + /* Since VirtualAlloc only reserves at allocation granularity + boundaries, we round up here, too. Otherwise we might end + up at a bogus page-aligned address. */ + start_address = roundup2 (start_address + mbi.RegionSize, + wincap.allocation_granularity ()); + } + if (!ret) + { + /* In theory this should not happen. But if it happens, we have + collected the information about the largest available region + in the above loop. So, next we squeeze the heap into that + region, unless it's smaller than the minimum size. */ + if (largest_found_size >= MINHEAP_SIZE) + { + cygheap->user_heap.chunk = largest_found_size; + cygheap->user_heap.base = + VirtualAlloc ((LPVOID) start_address, + cygheap->user_heap.chunk, + alloctype, PAGE_NOACCESS); + } + /* Last resort (but actually we are probably broken anyway): + Use the minimal heap size and let the system decide. */ + if (!cygheap->user_heap.base) + { + cygheap->user_heap.chunk = MINHEAP_SIZE; + cygheap->user_heap.base = + VirtualAlloc (NULL, cygheap->user_heap.chunk, + alloctype, PAGE_NOACCESS); + } + } } + while (!cygheap->user_heap.base && ret); if (cygheap->user_heap.base == NULL) - api_fatal ("unable to allocate heap, heap_chunk_size %p, slop %p, %E", - cygheap->user_heap.chunk, cygheap->user_heap.slop); - cygheap->user_heap.base = (void *) ((char *) cygheap->user_heap.base - + cygheap->user_heap.slop); + api_fatal ("unable to allocate heap, heap_chunk_size %p, %E", + cygheap->user_heap.chunk); cygheap->user_heap.ptr = cygheap->user_heap.top = cygheap->user_heap.base; cygheap->user_heap.max = (char *) cygheap->user_heap.base + cygheap->user_heap.chunk; -- cgit v1.2.3