332 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			332 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
/* heap.cc: Cygwin heap manager.
 | 
						|
 | 
						|
This file is part of Cygwin.
 | 
						|
 | 
						|
This software is a copyrighted work licensed under the terms of the
 | 
						|
Cygwin license.  Please consult the file "CYGWIN_LICENSE" for
 | 
						|
details. */
 | 
						|
 | 
						|
#include "winsup.h"
 | 
						|
#include "cygerrno.h"
 | 
						|
#include "shared_info.h"
 | 
						|
#include "path.h"
 | 
						|
#include "fhandler.h"
 | 
						|
#include "dtable.h"
 | 
						|
#include "cygheap.h"
 | 
						|
#include "child_info.h"
 | 
						|
#include "ntdll.h"
 | 
						|
#include <sys/param.h>
 | 
						|
 | 
						|
#define assert(x)
 | 
						|
 | 
						|
static ptrdiff_t page_const;
 | 
						|
 | 
						|
/* Minimum size of the base heap. */
 | 
						|
#define MINHEAP_SIZE (4 * 1024 * 1024)
 | 
						|
/* Chunksize of subsequent heap reservations. */
 | 
						|
#define RAISEHEAP_SIZE (1 * 1024 * 1024)
 | 
						|
 | 
						|
static uintptr_t
 | 
						|
eval_start_address ()
 | 
						|
{
 | 
						|
#ifdef __x86_64__
 | 
						|
  /* On 64 bit, we choose a fixed address outside the 32 bit area.  The
 | 
						|
     executable starts at 0x1:00400000L, the Cygwin DLL starts at
 | 
						|
     0x1:80040000L, other rebased DLLs are located in the region from
 | 
						|
     0x2:00000000L up to 0x4:00000000L, -auto-image-based DLLs are located
 | 
						|
     in the region from 0x4:00000000L up to 0x6:00000000L.
 | 
						|
     So we let the heap start at 0x6:00000000L. */
 | 
						|
  uintptr_t start_address = 0x600000000L;
 | 
						|
#else
 | 
						|
  /* 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. */
 | 
						|
  uintptr_t start_address = 0x20000000L;
 | 
						|
  if ((uintptr_t) NtCurrentTeb () >= 0xbf000000L)
 | 
						|
    {
 | 
						|
      /* However, if we're running on a /3GB enabled 32 bit system or on
 | 
						|
	 a 64 bit system, and the executable is large address aware, then
 | 
						|
	 we know that we have spare 1 Gig (32 bit) or even 2 Gigs (64 bit)
 | 
						|
	 virtual address space.  This memory region is practically unused
 | 
						|
	 by Windows, only PEB and TEBs are allocated top-down here.  We use
 | 
						|
	 the current TEB address as very simple test that this is a large
 | 
						|
	 address aware executable.
 | 
						|
	 The above test for an address beyond 0xbf000000 is supposed to
 | 
						|
	 make sure that we really have 3GB on a 32 bit system.  Windows
 | 
						|
	 supports smaller large address regions, but then it's not that
 | 
						|
	 interesting for us to use it for the heap.
 | 
						|
	 If the region is big enough, the heap gets allocated at its
 | 
						|
	 start.  What we get are 0.999 or 1.999 Gigs of free contiguous
 | 
						|
	 memory for heap, thread stacks, and shared memory regions. */
 | 
						|
      start_address = 0x80000000L;
 | 
						|
    }
 | 
						|
#endif
 | 
						|
  return start_address;
 | 
						|
}
 | 
						|
 | 
						|
static SIZE_T
 | 
						|
eval_initial_heap_size ()
 | 
						|
{
 | 
						|
  PIMAGE_DOS_HEADER dosheader;
 | 
						|
  PIMAGE_NT_HEADERS ntheader;
 | 
						|
  SIZE_T size;
 | 
						|
 | 
						|
  dosheader = (PIMAGE_DOS_HEADER) GetModuleHandle (NULL);
 | 
						|
  ntheader = (PIMAGE_NT_HEADERS) ((PBYTE) dosheader + dosheader->e_lfanew);
 | 
						|
  /* LoaderFlags is an obsolete DWORD member of the PE/COFF file header.
 | 
						|
     It's value is ignored by the loader, so we're free to use it for
 | 
						|
     Cygwin.  If it's 0, we default to the usual 384 Megs on 32 bit and
 | 
						|
     512 on 64 bit.  Otherwise, we use it as the default initial heap size
 | 
						|
     in megabyte.  Valid values are between 4 and 2048/8388608 Megs. */
 | 
						|
 | 
						|
  size = ntheader->OptionalHeader.LoaderFlags;
 | 
						|
#ifdef __x86_64__
 | 
						|
  if (size == 0)
 | 
						|
    size = 512;
 | 
						|
  else if (size < 4)
 | 
						|
    size = 4;
 | 
						|
  else if (size > 8388608)
 | 
						|
    size = 8388608;
 | 
						|
#else
 | 
						|
  if (size == 0)
 | 
						|
    size = 384;
 | 
						|
  else if (size < 4)
 | 
						|
    size = 4;
 | 
						|
  else if (size > 2048)
 | 
						|
    size = 2048;
 | 
						|
#endif
 | 
						|
  return size << 20;
 | 
						|
}
 | 
						|
 | 
						|
/* Initialize the heap at process start up.  */
 | 
						|
void
 | 
						|
user_heap_info::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 (almost) don't care where it ends up.  */
 | 
						|
 | 
						|
  page_const = wincap.page_size ();
 | 
						|
  if (!base)
 | 
						|
    {
 | 
						|
      uintptr_t start_address = eval_start_address ();
 | 
						|
      PVOID largest_found = NULL;
 | 
						|
      SIZE_T largest_found_size = 0;
 | 
						|
      SIZE_T ret;
 | 
						|
      MEMORY_BASIC_INFORMATION mbi;
 | 
						|
 | 
						|
      chunk = eval_initial_heap_size ();
 | 
						|
      do
 | 
						|
	{
 | 
						|
	  base = VirtualAlloc ((LPVOID) start_address, chunk, alloctype,
 | 
						|
			       PAGE_NOACCESS);
 | 
						|
	  if (base)
 | 
						|
	    break;
 | 
						|
 | 
						|
	  /* 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, including the
 | 
						|
	     region starting at 0x20000000. */
 | 
						|
	  while ((ret = VirtualQuery ((LPCVOID) start_address, &mbi,
 | 
						|
				      sizeof mbi)) != 0)
 | 
						|
	    {
 | 
						|
	      if (mbi.State == MEM_FREE)
 | 
						|
		{
 | 
						|
		  if (mbi.RegionSize >= chunk)
 | 
						|
		    break;
 | 
						|
		  if (mbi.RegionSize > largest_found_size)
 | 
						|
		    {
 | 
						|
		      largest_found = 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)
 | 
						|
		{
 | 
						|
		  chunk = largest_found_size;
 | 
						|
		  base = VirtualAlloc (largest_found, chunk, alloctype,
 | 
						|
				       PAGE_NOACCESS);
 | 
						|
		}
 | 
						|
	      /* Last resort (but actually we are probably broken anyway):
 | 
						|
		 Use the minimal heap size and let the system decide. */
 | 
						|
	      if (!base)
 | 
						|
		{
 | 
						|
		  chunk = MINHEAP_SIZE;
 | 
						|
		  base = VirtualAlloc (NULL, chunk, alloctype, PAGE_NOACCESS);
 | 
						|
		}
 | 
						|
	    }
 | 
						|
	}
 | 
						|
      while (!base && ret);
 | 
						|
      if (base == NULL)
 | 
						|
	api_fatal ("unable to allocate heap, heap_chunk_size %ly, %E",
 | 
						|
		   chunk);
 | 
						|
      ptr = top = base;
 | 
						|
      max = (char *) base + chunk;
 | 
						|
    }
 | 
						|
  else
 | 
						|
    {
 | 
						|
      /* total size commited in parent */
 | 
						|
      SIZE_T allocsize = (char *) top - (char *) base;
 | 
						|
 | 
						|
      /* Loop until we've managed to reserve an adequate amount of memory. */
 | 
						|
      SIZE_T reserve_size = chunk * ((allocsize + (chunk - 1)) / chunk);
 | 
						|
 | 
						|
      /* With ptmalloc3 there's a good chance that there has been no memory
 | 
						|
	 allocated on the heap.  If we don't check that, reserve_size will
 | 
						|
	 be 0 and from there, the below loop will end up overallocating due
 | 
						|
	 to integer overflow. */
 | 
						|
      if (!reserve_size)
 | 
						|
	reserve_size = chunk;
 | 
						|
 | 
						|
      char *p;
 | 
						|
      while (1)
 | 
						|
	{
 | 
						|
	  p = (char *) VirtualAlloc (base, reserve_size, alloctype,
 | 
						|
				     PAGE_READWRITE);
 | 
						|
	  if (p)
 | 
						|
	    break;
 | 
						|
	  if ((reserve_size -= page_const) < allocsize)
 | 
						|
	    break;
 | 
						|
	}
 | 
						|
      if (!p && in_forkee && !fork_info->abort (NULL))
 | 
						|
	api_fatal ("couldn't allocate heap, %E, base %p, top %p, "
 | 
						|
		   "reserve_size %ld, allocsize %ld, page_const %d",
 | 
						|
		   base, top,
 | 
						|
		   reserve_size, allocsize, page_const);
 | 
						|
      if (p != base)
 | 
						|
	api_fatal ("heap allocated at wrong address %p (mapped) "
 | 
						|
		   "!= %p (expected)", p, base);
 | 
						|
      if (allocsize && !VirtualAlloc (base, allocsize,
 | 
						|
				      MEM_COMMIT, PAGE_READWRITE))
 | 
						|
	api_fatal ("MEM_COMMIT failed, %E");
 | 
						|
    }
 | 
						|
 | 
						|
  /* CV 2012-05-21: Moved printing heap size here from strace::activate.
 | 
						|
     The value printed in strace.activate was always wrong, because at the
 | 
						|
     time it's called, cygheap points to cygheap_dummy.  Above all, the heap
 | 
						|
     size has not been evaluated yet, except in a forked child.  Since
 | 
						|
     heap_init is called early, the heap size is printed pretty much at the
 | 
						|
     start of the strace output, so there isn't anything lost. */
 | 
						|
  debug_printf ("heap base %p, heap top %p, heap size %ly (%lu)",
 | 
						|
		base, top, chunk, chunk);
 | 
						|
  page_const--;
 | 
						|
  // malloc_init ();
 | 
						|
}
 | 
						|
 | 
						|
#define pround(n) (((size_t)(n) + page_const) & ~page_const)
 | 
						|
/* Linux defines n to be intptr_t, newlib defines it to be ptrdiff_t.
 | 
						|
   It shouldn't matter much, though, since the function is not standarized
 | 
						|
   and sizeof(ptrdiff_t) == sizeof(intptr_t) anyway. */
 | 
						|
extern "C" void *
 | 
						|
sbrk (ptrdiff_t n)
 | 
						|
{
 | 
						|
  return cygheap->user_heap.sbrk (n);
 | 
						|
}
 | 
						|
 | 
						|
void __reg2 *
 | 
						|
user_heap_info::sbrk (ptrdiff_t n)
 | 
						|
{
 | 
						|
/* FIXME: This function no longer handles "split heaps". */
 | 
						|
 | 
						|
  char *newtop, *newbrk;
 | 
						|
  SIZE_T commitbytes, newbrksize, reservebytes;
 | 
						|
 | 
						|
  if (n == 0)
 | 
						|
    return ptr;					/* Just wanted to find current ptr
 | 
						|
						   address */
 | 
						|
 | 
						|
  newbrk = (char *) ptr + n;			/* Where new cptr will be */
 | 
						|
  newtop = (char *) pround (newbrk);		/* Actual top of allocated memory -
 | 
						|
						   on page boundary */
 | 
						|
 | 
						|
  if (newtop == top)
 | 
						|
    goto good;
 | 
						|
 | 
						|
  if (n < 0)
 | 
						|
    {						/* Freeing memory */
 | 
						|
      assert (newtop < top);
 | 
						|
      n = (char *) top - newtop;
 | 
						|
      /* FIXME: This doesn't work if we cross a virtual memory reservation
 | 
						|
	 border.  If that happens, we have to free the space in multiple
 | 
						|
	 VirtualFree calls, aligned to the former reservation borders. */
 | 
						|
      if (VirtualFree (newtop, n, MEM_DECOMMIT)) /* Give it back to OS */
 | 
						|
	goto good;
 | 
						|
      goto err;					/*  Didn't take */
 | 
						|
    }
 | 
						|
 | 
						|
  assert (newtop > top);
 | 
						|
 | 
						|
  /* Find the number of bytes to commit, rounded up to the nearest page. */
 | 
						|
  commitbytes = pround (newtop - (char *) top);
 | 
						|
 | 
						|
  /* Need to grab more pages from the OS.  If this fails it may be because
 | 
						|
     we have used up previously reserved memory.  Or, we're just plumb out
 | 
						|
     of memory.  Only attempt to commit memory that we know we've previously
 | 
						|
     reserved.  */
 | 
						|
  if (newtop <= max)
 | 
						|
    {
 | 
						|
      if (VirtualAlloc (top, commitbytes, MEM_COMMIT, PAGE_READWRITE))
 | 
						|
	goto good;
 | 
						|
      goto err;
 | 
						|
    }
 | 
						|
 | 
						|
  /* The remainder of the existing heap is too small to fulfill the memory
 | 
						|
     request.  We have to extend the heap, so we reserve some more memory
 | 
						|
     and then commit the remainder of the old heap, if any, and the rest of
 | 
						|
     the required space from the extended heap. */
 | 
						|
 | 
						|
  /* For subsequent chunks following the base heap, reserve either 1 Megs
 | 
						|
     per chunk, or the requested amount if it's bigger than 1 Megs. */
 | 
						|
  reservebytes = commitbytes - ((char *) max - (char *) top);
 | 
						|
  commitbytes -= reservebytes;
 | 
						|
  if ((newbrksize = RAISEHEAP_SIZE) < reservebytes)
 | 
						|
    newbrksize = reservebytes;
 | 
						|
 | 
						|
  if (VirtualAlloc (max, newbrksize, MEM_RESERVE, PAGE_NOACCESS)
 | 
						|
      || VirtualAlloc (max, newbrksize = reservebytes, MEM_RESERVE,
 | 
						|
		       PAGE_NOACCESS))
 | 
						|
    {
 | 
						|
      /* Now commit the requested memory.  Windows keeps all virtual
 | 
						|
	 reservations separate, so we can't commit the two regions in a single,
 | 
						|
	 combined call or we suffer an ERROR_INVALID_ADDRESS.  The same error
 | 
						|
	 is returned when trying to VirtualAlloc 0 bytes, which would occur if
 | 
						|
	 the existing heap was already full. */
 | 
						|
      if ((!commitbytes || VirtualAlloc (top, commitbytes, MEM_COMMIT,
 | 
						|
					 PAGE_READWRITE))
 | 
						|
	  && VirtualAlloc (max, reservebytes, MEM_COMMIT, PAGE_READWRITE))
 | 
						|
	{
 | 
						|
	  max = (char *) max + pround (newbrksize);
 | 
						|
	  goto good;
 | 
						|
	}
 | 
						|
      /* If committing the memory failed, we must free the extendend reserved
 | 
						|
         region, otherwise any other try to fetch memory (for instance by using
 | 
						|
	 mmap) may fail just because we still reserve memory we don't even know
 | 
						|
	 about. */
 | 
						|
      VirtualFree (max, newbrksize, MEM_RELEASE);
 | 
						|
    }
 | 
						|
 | 
						|
err:
 | 
						|
  set_errno (ENOMEM);
 | 
						|
  return (void *) -1;
 | 
						|
 | 
						|
good:
 | 
						|
  void *oldbrk = ptr;
 | 
						|
  ptr = newbrk;
 | 
						|
  top = newtop;
 | 
						|
  return oldbrk;
 | 
						|
}
 |