/* * mono-mmap.c: Support for mapping code into the process address space * * Author: * Mono Team (mono-list@lists.ximian.com) * * Copyright 2001-2008 Novell, Inc. */ #include "config.h" #ifdef HOST_WIN32 #include #include #else #include #if HAVE_SYS_STAT_H #include #endif #if HAVE_SYS_MMAN_H #include #endif #ifdef HAVE_SIGNAL_H #include #endif #include #include #include #include #include #endif #include "mono-mmap.h" #include "mono-mmap-internal.h" #include "mono-proclib.h" #include #define BEGIN_CRITICAL_SECTION do { \ MonoThreadInfo *__info = mono_thread_info_current_unchecked (); \ if (__info) __info->inside_critical_region = TRUE; \ #define END_CRITICAL_SECTION \ if (__info) __info->inside_critical_region = FALSE; \ } while (0) \ #ifndef MAP_ANONYMOUS #define MAP_ANONYMOUS MAP_ANON #endif #ifndef MAP_32BIT #define MAP_32BIT 0 #endif typedef struct { int size; int pid; int reserved; short stats_start; short stats_end; } SAreaHeader; static void* malloced_shared_area = NULL; static void* malloc_shared_area (int pid) { int size = mono_pagesize (); SAreaHeader *sarea = (SAreaHeader *) g_malloc0 (size); sarea->size = size; sarea->pid = pid; sarea->stats_start = sizeof (SAreaHeader); sarea->stats_end = sizeof (SAreaHeader); return sarea; } static char* aligned_address (char *mem, size_t size, size_t alignment) { char *aligned = (char*)((size_t)(mem + (alignment - 1)) & ~(alignment - 1)); g_assert (aligned >= mem && aligned + size <= mem + size + alignment && !((size_t)aligned & (alignment - 1))); return aligned; } #ifdef HOST_WIN32 int mono_pagesize (void) { SYSTEM_INFO info; static int saved_pagesize = 0; if (saved_pagesize) return saved_pagesize; GetSystemInfo (&info); saved_pagesize = info.dwAllocationGranularity; return saved_pagesize; } static int prot_from_flags (int flags) { int prot = flags & (MONO_MMAP_READ|MONO_MMAP_WRITE|MONO_MMAP_EXEC); switch (prot) { case 0: prot = PAGE_NOACCESS; break; case MONO_MMAP_READ: prot = PAGE_READONLY; break; case MONO_MMAP_READ|MONO_MMAP_EXEC: prot = PAGE_EXECUTE_READ; break; case MONO_MMAP_READ|MONO_MMAP_WRITE: prot = PAGE_READWRITE; break; case MONO_MMAP_READ|MONO_MMAP_WRITE|MONO_MMAP_EXEC: prot = PAGE_EXECUTE_READWRITE; break; case MONO_MMAP_WRITE: prot = PAGE_READWRITE; break; case MONO_MMAP_WRITE|MONO_MMAP_EXEC: prot = PAGE_EXECUTE_READWRITE; break; case MONO_MMAP_EXEC: prot = PAGE_EXECUTE; break; default: g_assert_not_reached (); } return prot; } void* mono_valloc (void *addr, size_t length, int flags) { void *ptr; int mflags = MEM_RESERVE|MEM_COMMIT; int prot = prot_from_flags (flags); /* translate the flags */ ptr = VirtualAlloc (addr, length, mflags, prot); return ptr; } void* mono_valloc_aligned (size_t length, size_t alignment, int flags) { int prot = prot_from_flags (flags); char *mem = VirtualAlloc (NULL, length + alignment, MEM_RESERVE, prot); char *aligned; if (!mem) return NULL; aligned = aligned_address (mem, length, alignment); aligned = VirtualAlloc (aligned, length, MEM_COMMIT, prot); g_assert (aligned); return aligned; } #define HAVE_VALLOC_ALIGNED int mono_vfree (void *addr, size_t length) { MEMORY_BASIC_INFORMATION mbi; SIZE_T query_result = VirtualQuery (addr, &mbi, sizeof (mbi)); BOOL res; g_assert (query_result); res = VirtualFree (mbi.AllocationBase, 0, MEM_RELEASE); g_assert (res); return 0; } void* mono_file_map (size_t length, int flags, int fd, guint64 offset, void **ret_handle) { void *ptr; int mflags = 0; HANDLE file, mapping; int prot = prot_from_flags (flags); /* translate the flags */ /*if (flags & MONO_MMAP_PRIVATE) mflags |= MAP_PRIVATE; if (flags & MONO_MMAP_SHARED) mflags |= MAP_SHARED; if (flags & MONO_MMAP_ANON) mflags |= MAP_ANONYMOUS; if (flags & MONO_MMAP_FIXED) mflags |= MAP_FIXED; if (flags & MONO_MMAP_32BIT) mflags |= MAP_32BIT;*/ mflags = FILE_MAP_READ; if (flags & MONO_MMAP_WRITE) mflags = FILE_MAP_COPY; file = (HANDLE) _get_osfhandle (fd); mapping = CreateFileMapping (file, NULL, prot, 0, 0, NULL); if (mapping == NULL) return NULL; ptr = MapViewOfFile (mapping, mflags, 0, offset, length); if (ptr == NULL) { CloseHandle (mapping); return NULL; } *ret_handle = (void*)mapping; return ptr; } int mono_file_unmap (void *addr, void *handle) { UnmapViewOfFile (addr); CloseHandle ((HANDLE)handle); return 0; } int mono_mprotect (void *addr, size_t length, int flags) { DWORD oldprot; int prot = prot_from_flags (flags); if (flags & MONO_MMAP_DISCARD) { VirtualFree (addr, length, MEM_DECOMMIT); VirtualAlloc (addr, length, MEM_COMMIT, prot); return 0; } return VirtualProtect (addr, length, prot, &oldprot) == 0; } void* mono_shared_area (void) { if (!malloced_shared_area) malloced_shared_area = malloc_shared_area (0); /* get the pid here */ return malloced_shared_area; } void mono_shared_area_remove (void) { if (malloced_shared_area) g_free (malloced_shared_area); malloced_shared_area = NULL; } void* mono_shared_area_for_pid (void *pid) { return NULL; } void mono_shared_area_unload (void *area) { } int mono_shared_area_instances (void **array, int count) { return 0; } #else #if defined(HAVE_MMAP) /** * mono_pagesize: * Get the page size in use on the system. Addresses and sizes in the * mono_mmap(), mono_munmap() and mono_mprotect() calls must be pagesize * aligned. * * Returns: the page size in bytes. */ int mono_pagesize (void) { static int saved_pagesize = 0; if (saved_pagesize) return saved_pagesize; saved_pagesize = getpagesize (); return saved_pagesize; } static int prot_from_flags (int flags) { int prot = PROT_NONE; /* translate the protection bits */ if (flags & MONO_MMAP_READ) prot |= PROT_READ; if (flags & MONO_MMAP_WRITE) prot |= PROT_WRITE; if (flags & MONO_MMAP_EXEC) prot |= PROT_EXEC; return prot; } /** * mono_valloc: * @addr: memory address * @length: memory area size * @flags: protection flags * * Allocates @length bytes of virtual memory with the @flags * protection. @addr can be a preferred memory address or a * mandatory one if MONO_MMAP_FIXED is set in @flags. * @addr must be pagesize aligned and can be NULL. * @length must be a multiple of pagesize. * * Returns: NULL on failure, the address of the memory area otherwise */ void* mono_valloc (void *addr, size_t length, int flags) { void *ptr; int mflags = 0; int prot = prot_from_flags (flags); /* translate the flags */ if (flags & MONO_MMAP_FIXED) mflags |= MAP_FIXED; if (flags & MONO_MMAP_32BIT) mflags |= MAP_32BIT; mflags |= MAP_ANONYMOUS; mflags |= MAP_PRIVATE; BEGIN_CRITICAL_SECTION; ptr = mmap (addr, length, prot, mflags, -1, 0); if (ptr == MAP_FAILED) { int fd = open ("/dev/zero", O_RDONLY); if (fd != -1) { ptr = mmap (addr, length, prot, mflags, fd, 0); close (fd); } } END_CRITICAL_SECTION; if (ptr == MAP_FAILED) return NULL; return ptr; } /** * mono_vfree: * @addr: memory address returned by mono_valloc () * @length: size of memory area * * Remove the memory mapping at the address @addr. * * Returns: 0 on success. */ int mono_vfree (void *addr, size_t length) { int res; BEGIN_CRITICAL_SECTION; res = munmap (addr, length); END_CRITICAL_SECTION; return res; } /** * mono_file_map: * @length: size of data to map * @flags: protection flags * @fd: file descriptor * @offset: offset in the file * @ret_handle: pointer to storage for returning a handle for the map * * Map the area of the file pointed to by the file descriptor @fd, at offset * @offset and of size @length in memory according to the protection flags * @flags. * @offset and @length must be multiples of the page size. * @ret_handle must point to a void*: this value must be used when unmapping * the memory area using mono_file_unmap (). * */ void* mono_file_map (size_t length, int flags, int fd, guint64 offset, void **ret_handle) { void *ptr; int mflags = 0; int prot = prot_from_flags (flags); /* translate the flags */ if (flags & MONO_MMAP_PRIVATE) mflags |= MAP_PRIVATE; if (flags & MONO_MMAP_SHARED) mflags |= MAP_SHARED; if (flags & MONO_MMAP_FIXED) mflags |= MAP_FIXED; if (flags & MONO_MMAP_32BIT) mflags |= MAP_32BIT; BEGIN_CRITICAL_SECTION; ptr = mmap (0, length, prot, mflags, fd, offset); END_CRITICAL_SECTION; if (ptr == MAP_FAILED) return NULL; *ret_handle = (void*)length; return ptr; } /** * mono_file_unmap: * @addr: memory address returned by mono_file_map () * @handle: handle of memory map * * Remove the memory mapping at the address @addr. * @handle must be the value returned in ret_handle by mono_file_map (). * * Returns: 0 on success. */ int mono_file_unmap (void *addr, void *handle) { int res; BEGIN_CRITICAL_SECTION; res = munmap (addr, (size_t)handle); END_CRITICAL_SECTION; return res; } /** * mono_mprotect: * @addr: memory address * @length: size of memory area * @flags: new protection flags * * Change the protection for the memory area at @addr for @length bytes * to matche the supplied @flags. * If @flags includes MON_MMAP_DISCARD the pages are discarded from memory * and the area is cleared to zero. * @addr must be aligned to the page size. * @length must be a multiple of the page size. * * Returns: 0 on success. */ #if defined(__native_client__) int mono_mprotect (void *addr, size_t length, int flags) { int prot = prot_from_flags (flags); void *new_addr; if (flags & MONO_MMAP_DISCARD) memset (addr, 0, length); new_addr = mmap(addr, length, prot, MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, -1, 0); if (new_addr == addr) return 0; return -1; } #else int mono_mprotect (void *addr, size_t length, int flags) { int prot = prot_from_flags (flags); if (flags & MONO_MMAP_DISCARD) { /* on non-linux the pages are not guaranteed to be zeroed (*bsd, osx at least) */ #ifdef __linux__ if (madvise (addr, length, MADV_DONTNEED)) memset (addr, 0, length); #else memset (addr, 0, length); #ifdef HAVE_MADVISE madvise (addr, length, MADV_DONTNEED); madvise (addr, length, MADV_FREE); #else posix_madvise (addr, length, POSIX_MADV_DONTNEED); #endif #endif } return mprotect (addr, length, prot); } #endif // __native_client__ #else /* dummy malloc-based implementation */ int mono_pagesize (void) { return 4096; } void* mono_valloc (void *addr, size_t length, int flags) { return malloc (length); } void* mono_valloc_aligned (size_t length, size_t alignment, int flags) { g_assert_not_reached (); } #define HAVE_VALLOC_ALIGNED int mono_vfree (void *addr, size_t length) { free (addr); return 0; } int mono_mprotect (void *addr, size_t length, int flags) { if (flags & MONO_MMAP_DISCARD) { memset (addr, 0, length); } return 0; } #endif // HAVE_MMAP #if defined(HAVE_SHM_OPEN) && !defined (DISABLE_SHARED_PERFCOUNTERS) static int use_shared_area; static gboolean shared_area_disabled (void) { if (!use_shared_area) { if (g_getenv ("MONO_DISABLE_SHARED_AREA")) use_shared_area = -1; else use_shared_area = 1; } return use_shared_area == -1; } static int mono_shared_area_instances_slow (void **array, int count, gboolean cleanup) { int i, j = 0; int num; void *data; gpointer *processes = mono_process_list (&num); for (i = 0; i < num; ++i) { data = mono_shared_area_for_pid (processes [i]); if (!data) continue; mono_shared_area_unload (data); if (!cleanup) { if (j < count) array [j++] = processes [i]; else break; } } g_free (processes); return j; } static int mono_shared_area_instances_helper (void **array, int count, gboolean cleanup) { const char *name; int i = 0; int curpid = getpid (); GDir *dir = g_dir_open ("/dev/shm/", 0, NULL); if (!dir) return mono_shared_area_instances_slow (array, count, cleanup); while ((name = g_dir_read_name (dir))) { int pid; char *nend; if (strncmp (name, "mono.", 5)) continue; pid = strtol (name + 5, &nend, 10); if (pid <= 0 || nend == name + 5 || *nend) continue; if (!cleanup) { if (i < count) array [i++] = GINT_TO_POINTER (pid); else break; } if (curpid != pid && kill (pid, 0) == -1 && (errno == ESRCH || errno == ENOMEM)) { char buf [128]; g_snprintf (buf, sizeof (buf), "/mono.%d", pid); shm_unlink (buf); } } g_dir_close (dir); return i; } void* mono_shared_area (void) { int fd; int pid = getpid (); /* we should allow the user to configure the size */ int size = mono_pagesize (); char buf [128]; void *res; SAreaHeader *header; if (shared_area_disabled ()) { if (!malloced_shared_area) malloced_shared_area = malloc_shared_area (0); /* get the pid here */ return malloced_shared_area; } /* perform cleanup of segments left over from dead processes */ mono_shared_area_instances_helper (NULL, 0, TRUE); g_snprintf (buf, sizeof (buf), "/mono.%d", pid); fd = shm_open (buf, O_CREAT|O_EXCL|O_RDWR, S_IRUSR|S_IWUSR|S_IRGRP); if (fd == -1 && errno == EEXIST) { /* leftover */ shm_unlink (buf); fd = shm_open (buf, O_CREAT|O_EXCL|O_RDWR, S_IRUSR|S_IWUSR|S_IRGRP); } /* in case of failure we try to return a memory area anyway, * even if it means the data can't be read by other processes */ if (fd == -1) return malloc_shared_area (pid); if (ftruncate (fd, size) != 0) { shm_unlink (buf); close (fd); } BEGIN_CRITICAL_SECTION; res = mmap (NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); END_CRITICAL_SECTION; if (res == MAP_FAILED) { shm_unlink (buf); close (fd); return malloc_shared_area (pid); } /* we don't need the file descriptor anymore */ close (fd); header = (SAreaHeader *) res; header->size = size; header->pid = pid; header->stats_start = sizeof (SAreaHeader); header->stats_end = sizeof (SAreaHeader); mono_atexit (mono_shared_area_remove); return res; } void mono_shared_area_remove (void) { char buf [128]; if (shared_area_disabled ()) { if (malloced_shared_area) g_free (malloced_shared_area); return; } g_snprintf (buf, sizeof (buf), "/mono.%d", getpid ()); shm_unlink (buf); if (malloced_shared_area) g_free (malloced_shared_area); } void* mono_shared_area_for_pid (void *pid) { int fd; /* we should allow the user to configure the size */ int size = mono_pagesize (); char buf [128]; void *res; if (shared_area_disabled ()) return NULL; g_snprintf (buf, sizeof (buf), "/mono.%d", GPOINTER_TO_INT (pid)); fd = shm_open (buf, O_RDONLY, S_IRUSR|S_IRGRP); if (fd == -1) return NULL; BEGIN_CRITICAL_SECTION; res = mmap (NULL, size, PROT_READ, MAP_SHARED, fd, 0); END_CRITICAL_SECTION; if (res == MAP_FAILED) { close (fd); return NULL; } /* FIXME: validate the area */ /* we don't need the file descriptor anymore */ close (fd); return res; } void mono_shared_area_unload (void *area) { /* FIXME: currently we load only a page */ BEGIN_CRITICAL_SECTION; munmap (area, mono_pagesize ()); END_CRITICAL_SECTION; } int mono_shared_area_instances (void **array, int count) { return mono_shared_area_instances_helper (array, count, FALSE); } #else void* mono_shared_area (void) { if (!malloced_shared_area) malloced_shared_area = malloc_shared_area (getpid ()); /* get the pid here */ return malloced_shared_area; } void mono_shared_area_remove (void) { if (malloced_shared_area) g_free (malloced_shared_area); malloced_shared_area = NULL; } void* mono_shared_area_for_pid (void *pid) { return NULL; } void mono_shared_area_unload (void *area) { } int mono_shared_area_instances (void **array, int count) { return 0; } #endif // HAVE_SHM_OPEN #endif // HOST_WIN32 #ifndef HAVE_VALLOC_ALIGNED void* mono_valloc_aligned (size_t size, size_t alignment, int flags) { /* Allocate twice the memory to be able to put the block on an aligned address */ char *mem = (char *) mono_valloc (NULL, size + alignment, flags); char *aligned; if (!mem) return NULL; aligned = aligned_address (mem, size, alignment); if (aligned > mem) mono_vfree (mem, aligned - mem); if (aligned + size < mem + size + alignment) mono_vfree (aligned + size, (mem + size + alignment) - (aligned + size)); return aligned; } #endif int mono_pages_not_faulted (void *addr, size_t size) { #ifdef HAVE_MINCORE int i; gint64 count; int pagesize = mono_pagesize (); int npages = (size + pagesize - 1) / pagesize; char *faulted = (char *) g_malloc0 (sizeof (char*) * npages); /* * We cast `faulted` to void* because Linux wants an unsigned * char* while BSD wants a char*. */ #ifdef __linux__ if (mincore (addr, size, (unsigned char *)faulted) != 0) { #else if (mincore (addr, size, (char *)faulted) != 0) { #endif count = -1; } else { count = 0; for (i = 0; i < npages; ++i) { if (faulted [i] != 0) ++count; } } g_free (faulted); return count; #else return -1; #endif }