From 2271440777681ceb98cc87c43e2798a2b573fa9e Mon Sep 17 00:00:00 2001 From: Ben Gamari Date: Mon, 19 Apr 2021 14:07:21 -0400 Subject: [PATCH 01/13] rts/m32: Fix bounds check Previously we would check only that the *start* of the mapping was in the bottom 32-bits of address space. However, we need the *entire* mapping to be in low memory. Fix this. Noticed by @Phyx. (cherry picked from commit 72c1812feecd2aff2a96b629063ba90a2f4cdb7b) --- rts/linker/M32Alloc.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rts/linker/M32Alloc.c b/rts/linker/M32Alloc.c index e7c697bf60b..cd8751b3b04 100644 --- a/rts/linker/M32Alloc.c +++ b/rts/linker/M32Alloc.c @@ -244,8 +244,9 @@ m32_alloc_page(void) * pages. */ const size_t pgsz = getPageSize(); - uint8_t *chunk = mmapAnonForLinker(pgsz * M32_MAP_PAGES); - if (chunk > (uint8_t *) 0xffffffff) { + const size_t map_sz = pgsz * M32_MAP_PAGES; + uint8_t *chunk = mmapAnonForLinker(map_sz); + if (chunk + map_sz > (uint8_t *) 0xffffffff) { barf("m32_alloc_page: failed to get allocation in lower 32-bits"); } -- GitLab From 12989f386ced001ee3592440402d191e7c9f9fec Mon Sep 17 00:00:00 2001 From: Ben Gamari Date: Thu, 20 Jan 2022 15:17:10 -0500 Subject: [PATCH 02/13] rts/m32: Accept any address within 4GB of program text Previously m32 would assume that the program image was located near the start of the address space and therefore assume that it wanted pages in the bottom 4GB of address space. Instead we now check whether they are within 4GB of whereever the program is loaded. This is necessary on Windows, which now tends to place the image in high memory. The eventual goal is to use m32 to allocate memory for linker sections on Windows. (cherry picked from commit 2e9248b7f7f645851ceb49931d10b9c5e58d2bbb) --- rts/Linker.c | 57 +--------------------------------------- rts/LinkerInternals.h | 60 +++++++++++++++++++++++++++++++++++++++++++ rts/linker/M32Alloc.c | 27 +++++++++++-------- 3 files changed, 78 insertions(+), 66 deletions(-) diff --git a/rts/Linker.c b/rts/Linker.c index 3bbe4b8340a..51d87d05bc3 100644 --- a/rts/Linker.c +++ b/rts/Linker.c @@ -198,62 +198,7 @@ Mutex linker_mutex; /* Generic wrapper function to try and Resolve and RunInit oc files */ int ocTryLoad( ObjectCode* oc ); -/* Link objects into the lower 2Gb on x86_64 and AArch64. GHC assumes the - * small memory model on this architecture (see gcc docs, - * -mcmodel=small). - * - * MAP_32BIT not available on OpenBSD/amd64 - */ -#if defined(MAP_32BIT) && (defined(x86_64_HOST_ARCH) || (defined(aarch64_TARGET_ARCH) || defined(aarch64_HOST_ARCH))) -#define MAP_LOW_MEM -#define TRY_MAP_32BIT MAP_32BIT -#else -#define TRY_MAP_32BIT 0 -#endif - -#if defined(aarch64_HOST_ARCH) -// On AArch64 MAP_32BIT is not available but we are still bound by the small -// memory model. Consequently we still try using the MAP_LOW_MEM allocation -// strategy. -#define MAP_LOW_MEM -#endif - -/* - * Note [MAP_LOW_MEM] - * ~~~~~~~~~~~~~~~~~~ - * Due to the small memory model (see above), on x86_64 and AArch64 we have to - * map all our non-PIC object files into the low 2Gb of the address space (why - * 2Gb and not 4Gb? Because all addresses must be reachable using a 32-bit - * signed PC-relative offset). On x86_64 Linux we can do this using the - * MAP_32BIT flag to mmap(), however on other OSs (e.g. *BSD, see #2063, and - * also on Linux inside Xen, see #2512), we can't do this. So on these - * systems, we have to pick a base address in the low 2Gb of the address space - * and try to allocate memory from there. - * - * The same holds for aarch64, where the default, even with PIC, model - * is 4GB. The linker is free to emit AARCH64_ADR_PREL_PG_HI21 - * relocations. - * - * We pick a default address based on the OS, but also make this - * configurable via an RTS flag (+RTS -xm) - */ - -#if (defined(aarch64_TARGET_ARCH) || defined(aarch64_HOST_ARCH)) -// Try to use stg_upd_frame_info as the base. We need to be within +-4GB of that -// address, otherwise we violate the aarch64 memory model. Any object we load -// can potentially reference any of the ones we bake into the binary (and list) -// in RtsSymbols. Thus we'll need to be within +-4GB of those, -// stg_upd_frame_info is a good candidate as it's referenced often. -#define MMAP_32BIT_BASE_DEFAULT (void*)&stg_upd_frame_info; -#elif defined(MAP_32BIT) || DEFAULT_LINKER_ALWAYS_PIC -// Try to use MAP_32BIT -#define MMAP_32BIT_BASE_DEFAULT 0 -#else -// A guess: 1Gb. -#define MMAP_32BIT_BASE_DEFAULT 0x40000000 -#endif - -static void *mmap_32bit_base = (void *)MMAP_32BIT_BASE_DEFAULT; +static void *mmap_32bit_base = LINKER_LOAD_BASE; static void ghciRemoveSymbolTable(StrHashTable *table, const SymbolName* key, ObjectCode *owner) diff --git a/rts/LinkerInternals.h b/rts/LinkerInternals.h index 7058ad355b6..c4681e364bd 100644 --- a/rts/LinkerInternals.h +++ b/rts/LinkerInternals.h @@ -433,6 +433,66 @@ resolveSymbolAddr (pathchar* buffer, int size, #define USE_CONTIGUOUS_MMAP 0 #endif +/* Link objects into the lower 2Gb on x86_64 and AArch64. GHC assumes the + * small memory model on this architecture (see gcc docs, + * -mcmodel=small). + * + * MAP_32BIT not available on OpenBSD/amd64 + */ +#if defined(MAP_32BIT) && (defined(x86_64_HOST_ARCH) || (defined(aarch64_TARGET_ARCH) || defined(aarch64_HOST_ARCH))) +#define MAP_LOW_MEM +#define TRY_MAP_32BIT MAP_32BIT +#else +#define TRY_MAP_32BIT 0 +#endif + +#if defined(aarch64_HOST_ARCH) +// On AArch64 MAP_32BIT is not available but we are still bound by the small +// memory model. Consequently we still try using the MAP_LOW_MEM allocation +// strategy. +#define MAP_LOW_MEM +#endif + +/* + * Note [MAP_LOW_MEM] + * ~~~~~~~~~~~~~~~~~~ + * Due to the small memory model (see above), on x86_64 and AArch64 we have to + * map all our non-PIC object files into the low 2Gb of the address space (why + * 2Gb and not 4Gb? Because all addresses must be reachable using a 32-bit + * signed PC-relative offset). On x86_64 Linux we can do this using the + * MAP_32BIT flag to mmap(), however on other OSs (e.g. *BSD, see #2063, and + * also on Linux inside Xen, see #2512), we can't do this. So on these + * systems, we have to pick a base address in the low 2Gb of the address space + * and try to allocate memory from there. + * + * The same holds for aarch64, where the default, even with PIC, model + * is 4GB. The linker is free to emit AARCH64_ADR_PREL_PG_HI21 + * relocations. + * + * We pick a default address based on the OS, but also make this + * configurable via an RTS flag (+RTS -xm) + */ + +#if defined(aarch64_TARGET_ARCH) || defined(aarch64_HOST_ARCH) +// Try to use stg_upd_frame_info as the base. We need to be within +-4GB of that +// address, otherwise we violate the aarch64 memory model. Any object we load +// can potentially reference any of the ones we bake into the binary (and list) +// in RtsSymbols. Thus we'll need to be within +-4GB of those, +// stg_upd_frame_info is a good candidate as it's referenced often. +#define LINKER_LOAD_BASE ((void *) &stg_upd_frame_info) +#elif defined(x86_64_HOST_ARCH) && defined(mingw32_HOST_OS) +// On Windows (which now uses high-entropy ASLR by default) we need to ensure +// that we map code near the executable image. We use stg_upd_frame_info as a +// proxy for the image location. +#define LINKER_LOAD_BASE ((void *) &stg_upd_frame_info) +#elif defined(MAP_32BIT) || DEFAULT_LINKER_ALWAYS_PIC +// Try to use MAP_32BIT +#define LINKER_LOAD_BASE ((void *) 0x0) +#else +// A guess: 1 GB. +#define LINKER_LOAD_BASE ((void *) 0x40000000) +#endif + HsInt isAlreadyLoaded( pathchar *path ); OStatus getObjectLoadStatus_ (pathchar *path); HsInt loadOc( ObjectCode* oc ); diff --git a/rts/linker/M32Alloc.c b/rts/linker/M32Alloc.c index cd8751b3b04..6945f50a71b 100644 --- a/rts/linker/M32Alloc.c +++ b/rts/linker/M32Alloc.c @@ -149,6 +149,14 @@ The allocator is *not* thread-safe. /* Upper bound on the number of pages to keep in the free page pool */ #define M32_MAX_FREE_PAGE_POOL_SIZE 64 +/* A utility to verify that a given address is "acceptable" for use by m32. */ +static bool +is_okay_address(void *p) { + int8_t *here = LINKER_LOAD_BASE; + ssize_t displacement = (int8_t *) p - here; + return (displacement > -0x7fffffff) && (displacement < 0x7fffffff); +} + /** * Page header * @@ -161,8 +169,7 @@ struct m32_page_t { // unprotected_list or protected_list are linked together with this field. struct { uint32_t size; - uint32_t next; // this is a m32_page_t*, truncated to 32-bits. This is safe - // as we are only allocating in the bottom 32-bits + struct m32_page_t *next; } filled_page; // Pages in the small-allocation nursery encode their current allocation @@ -179,10 +186,10 @@ struct m32_page_t { static void m32_filled_page_set_next(struct m32_page_t *page, struct m32_page_t *next) { - if (next > (struct m32_page_t *) 0xffffffff) { - barf("m32_filled_page_set_next: Page not in lower 32-bits"); + if (! is_okay_address(next)) { + barf("m32_filled_page_set_next: Page not within 4GB of program text"); } - page->filled_page.next = (uint32_t) (uintptr_t) next; + page->filled_page.next = next; } static struct m32_page_t * @@ -246,8 +253,8 @@ m32_alloc_page(void) const size_t pgsz = getPageSize(); const size_t map_sz = pgsz * M32_MAP_PAGES; uint8_t *chunk = mmapAnonForLinker(map_sz); - if (chunk + map_sz > (uint8_t *) 0xffffffff) { - barf("m32_alloc_page: failed to get allocation in lower 32-bits"); + if (! is_okay_address(chunk + map_sz)) { + barf("m32_alloc_page: failed to allocate pages within 4GB of program text (got %p)", chunk); } #define GET_PAGE(i) ((struct m32_page_t *) (chunk + (i) * pgsz)) @@ -393,9 +400,9 @@ m32_alloc(struct m32_allocator_t *alloc, size_t size, size_t alignment) if (page == NULL) { sysErrorBelch("m32_alloc: Failed to map pages for %zd bytes", size); return NULL; - } else if (page > (struct m32_page_t *) 0xffffffff) { - debugBelch("m32_alloc: warning: Allocation of %zd bytes resulted in pages above 4GB (%p)", - size, page); + } else if (! is_okay_address(page)) { + barf("m32_alloc: warning: Allocation of %zd bytes resulted in pages above 4GB (%p)", + size, page); } page->filled_page.size = alsize + size; m32_allocator_push_filled_list(&alloc->unprotected_list, (struct m32_page_t *) page); -- GitLab From b15da5a9bcf837d53f46c8b3daea55e55b8e7f34 Mon Sep 17 00:00:00 2001 From: GHC GitLab CI Date: Fri, 28 Jan 2022 22:33:52 -0500 Subject: [PATCH 03/13] rts: Generalize mmapForLinkerMarkExecutable Renamed to mprotectForLinker and allowed setting of arbitrary protection modes. (cherry picked from commit 86589b893c092ae900723e76848525f20f6cafbf) --- rts/ExecPage.c | 2 +- rts/Linker.c | 56 ++++++++++++++++++++++++++++++++------- rts/LinkerInternals.h | 10 ++++++- rts/linker/Elf.c | 2 +- rts/linker/M32Alloc.c | 2 +- rts/linker/MachO.c | 4 +-- rts/linker/SymbolExtras.c | 2 +- 7 files changed, 61 insertions(+), 17 deletions(-) diff --git a/rts/ExecPage.c b/rts/ExecPage.c index 6f5b6e281ab..24d4d65bad4 100644 --- a/rts/ExecPage.c +++ b/rts/ExecPage.c @@ -15,7 +15,7 @@ ExecPage *allocateExecPage() { } void freezeExecPage(ExecPage *page) { - mmapForLinkerMarkExecutable(page, getPageSize()); + mprotectForLinker(page, getPageSize(), MEM_READ_EXECUTE); flushExec(getPageSize(), page); } diff --git a/rts/Linker.c b/rts/Linker.c index 51d87d05bc3..225457f24a9 100644 --- a/rts/Linker.c +++ b/rts/Linker.c @@ -1048,6 +1048,17 @@ resolveSymbolAddr (pathchar* buffer, int size, #endif /* OBJFORMAT_PEi386 */ } +static const char *memoryAccessDescription(MemoryAccess mode) +{ + switch (mode) { + case MEM_NO_ACCESS: return "no-access"; + case MEM_READ_ONLY: return "read-only"; + case MEM_READ_WRITE: return "read-write"; + case MEM_READ_EXECUTE: return "read-execute"; + default: barf("invalid MemoryAccess"); + } +} + #if defined(mingw32_HOST_OS) // @@ -1068,16 +1079,29 @@ munmapForLinker (void *addr, size_t bytes, const char *caller) } } +/** + * Change the allowed access modes of a region of memory previously allocated + * with mmapAnonForLinker. + */ void -mmapForLinkerMarkExecutable(void *start, size_t len) +mprotectForLinker(void *start, size_t len, MemoryAccess mode) { DWORD old; if (len == 0) { return; } - if (VirtualProtect(start, len, PAGE_EXECUTE_READ, &old) == 0) { - sysErrorBelch("mmapForLinkerMarkExecutable: failed to protect %zd bytes at %p", - len, start); + DWORD prot; + switch (mode) { + case MEM_NO_ACCESS: prot = PAGE_NOACCESS; break; + case MEM_READ_ONLY: prot = PAGE_READONLY; break; + case MEM_READ_WRITE: prot = PAGE_READWRITE; break; + case MEM_READ_EXECUTE: prot = PAGE_EXECUTE_READ; break; + default: barf("invalid MemoryAccess"); + } + + if (VirtualProtect(start, len, prot, &old) == 0) { + sysErrorBelch("mprotectForLinker: failed to protect %zd bytes at %p as %s", + len, start, memoryAccessDescription(mode)); ASSERT(false); } } @@ -1229,7 +1253,7 @@ void munmapForLinker (void *addr, size_t bytes, const char *caller) * * Consequently mmapForLinker now maps its memory with PROT_READ|PROT_WRITE. * After the linker has finished filling/relocating the mapping it must then - * call mmapForLinkerMarkExecutable on the sections of the mapping which + * call mprotectForLinker on the sections of the mapping which * contain executable code. * * Note that the m32 allocator handles protection of its allocations. For this @@ -1245,16 +1269,28 @@ void munmapForLinker (void *addr, size_t bytes, const char *caller) * Mark an portion of a mapping previously reserved by mmapForLinker * as executable (but not writable). */ -void mmapForLinkerMarkExecutable(void *start, size_t len) +void mprotectForLinker(void *start, size_t len, MemoryAccess mode) { if (len == 0) { return; } IF_DEBUG(linker, - debugBelch("mmapForLinkerMarkExecutable: protecting %" FMT_Word - " bytes starting at %p\n", (W_)len, start)); - if (mprotect(start, len, PROT_READ|PROT_EXEC) == -1) { - barf("mmapForLinkerMarkExecutable: mprotect: %s\n", strerror(errno)); + debugBelch("mprotectForLinker: protecting %" FMT_Word + " bytes starting at %p as %s\n", + (W_)len, start, memoryAccessDescription(mode))); + + int prot; + switch (mode) { + case MEM_NO_ACCESS: prot = 0; break; + case MEM_READ_ONLY: prot = PROT_READ; break; + case MEM_READ_WRITE: prot = PROT_READ | PROT_WRITE; break; + case MEM_READ_EXECUTE: prot = PROT_READ | PROT_EXEC; break; + default: barf("invalid MemoryAccess"); + } + + if (mprotect(start, len, prot) == -1) { + sysErrorBelch("mprotectForLinker: failed to protect %zd bytes at %p as %s", + len, start, memoryAccessDescription(mode)); } } #endif diff --git a/rts/LinkerInternals.h b/rts/LinkerInternals.h index c4681e364bd..3e6b3df9dab 100644 --- a/rts/LinkerInternals.h +++ b/rts/LinkerInternals.h @@ -374,9 +374,17 @@ void exitLinker( void ); void freeObjectCode (ObjectCode *oc); SymbolAddr* loadSymbol(SymbolName *lbl, RtsSymbolInfo *pinfo); +/** Access modes for mprotectForLinker */ +typedef enum { + MEM_NO_ACCESS, + MEM_READ_ONLY, + MEM_READ_WRITE, + MEM_READ_EXECUTE, +} MemoryAccess; + void *mmapAnonForLinker (size_t bytes); void *mmapForLinker (size_t bytes, uint32_t prot, uint32_t flags, int fd, int offset); -void mmapForLinkerMarkExecutable (void *start, size_t len); +void mprotectForLinker(void *start, size_t len, MemoryAccess mode); void munmapForLinker (void *addr, size_t bytes, const char *caller); void addProddableBlock ( ObjectCode* oc, void* start, int size ); diff --git a/rts/linker/Elf.c b/rts/linker/Elf.c index f6a1754257a..980d4b80f05 100644 --- a/rts/linker/Elf.c +++ b/rts/linker/Elf.c @@ -1877,7 +1877,7 @@ ocMprotect_Elf( ObjectCode *oc ) if (section->alloc != SECTION_M32) { // N.B. m32 handles protection of its allocations during // flushing. - mmapForLinkerMarkExecutable(section->mapped_start, section->mapped_size); + mprotectForLinker(section->mapped_start, section->mapped_size, MEM_READ_EXECUTE); } break; default: diff --git a/rts/linker/M32Alloc.c b/rts/linker/M32Alloc.c index 6945f50a71b..a40cc701c06 100644 --- a/rts/linker/M32Alloc.c +++ b/rts/linker/M32Alloc.c @@ -366,7 +366,7 @@ m32_allocator_flush(m32_allocator *alloc) { while (page != NULL) { struct m32_page_t *next = m32_filled_page_get_next(page); m32_allocator_push_filled_list(&alloc->protected_list, page); - mmapForLinkerMarkExecutable(page, page->filled_page.size); + mprotectForLinker(page, page->filled_page.size, MEM_READ_EXECUTE); page = next; } alloc->unprotected_list = NULL; diff --git a/rts/linker/MachO.c b/rts/linker/MachO.c index 1a18ee6a740..d037c82f458 100644 --- a/rts/linker/MachO.c +++ b/rts/linker/MachO.c @@ -1428,7 +1428,7 @@ ocMprotect_MachO( ObjectCode *oc ) if(segment->size == 0) continue; if(segment->prot == SEGMENT_PROT_RX) { - mmapForLinkerMarkExecutable(segment->start, segment->size); + mprotectForLinker(segment->start, segment->size, MEM_READ_EXECUTE); } } @@ -1443,7 +1443,7 @@ ocMprotect_MachO( ObjectCode *oc ) if(section->alloc == SECTION_M32) continue; switch (section->kind) { case SECTIONKIND_CODE_OR_RODATA: { - mmapForLinkerMarkExecutable(section->mapped_start, section->mapped_size); + mprotectForLinker(section->mapped_start, section->mapped_size, MEM_READ_EXECUTE); break; } default: diff --git a/rts/linker/SymbolExtras.c b/rts/linker/SymbolExtras.c index ddb58e4a4e8..5c04e9b3a87 100644 --- a/rts/linker/SymbolExtras.c +++ b/rts/linker/SymbolExtras.c @@ -142,7 +142,7 @@ void ocProtectExtras(ObjectCode* oc) * non-executable. */ } else if (USE_CONTIGUOUS_MMAP || RtsFlags.MiscFlags.linkerAlwaysPic) { - mmapForLinkerMarkExecutable(oc->symbol_extras, sizeof(SymbolExtra) * oc->n_symbol_extras); + mprotectForLinker(oc->symbol_extras, sizeof(SymbolExtra) * oc->n_symbol_extras, MEM_READ_EXECUTE); } else { /* * The symbol extras were allocated via m32. They will be protected when -- GitLab From aa3e68222dda906d3332e79cab74144b48241e20 Mon Sep 17 00:00:00 2001 From: GHC GitLab CI Date: Fri, 28 Jan 2022 21:02:23 -0500 Subject: [PATCH 04/13] rts/m32: Add consistency-checking infrastructure This adds logic, enabled in the `-debug` RTS for checking the internal consistency of the m32 allocator. This area has always made me a bit nervous so this should help me sleep better at night in exchange for very little overhead. (cherry picked from commit 88ef270aa0cecf2463396f93a273656de9df9433) --- rts/linker/M32Alloc.c | 107 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 96 insertions(+), 11 deletions(-) diff --git a/rts/linker/M32Alloc.c b/rts/linker/M32Alloc.c index a40cc701c06..7fcf2fc0e02 100644 --- a/rts/linker/M32Alloc.c +++ b/rts/linker/M32Alloc.c @@ -135,6 +135,11 @@ The allocator is *not* thread-safe. */ +// Enable internal consistency checking +#if defined(DEBUG) +#define M32_DEBUG +#endif + #define ROUND_UP(x,size) ((x + size - 1) & ~(size - 1)) #define ROUND_DOWN(x,size) (x & ~(size - 1)) @@ -157,6 +162,12 @@ is_okay_address(void *p) { return (displacement > -0x7fffffff) && (displacement < 0x7fffffff); } +enum m32_page_type { + FREE_PAGE, // a page in the free page pool + NURSERY_PAGE, // a nursery page + FILLED_PAGE, // a page on the filled list +}; + /** * Page header * @@ -181,13 +192,55 @@ struct m32_page_t { struct m32_page_t *next; } free_page; }; +#if defined(M32_DEBUG) + enum m32_page_type type; +#endif + uint8_t contents[]; }; +/* Consistency-checking infrastructure */ +#if defined(M32_DEBUG) +static void ASSERT_PAGE_ALIGNED(void *page) { + const size_t pgsz = getPageSize(); + if ((((uintptr_t) page) & (pgsz-1)) != 0) { + barf("m32: invalid page alignment"); + } +} +static void ASSERT_VALID_PAGE(struct m32_page_t *page) { + ASSERT_PAGE_ALIGNED(page); + switch (page->type) { + case FREE_PAGE: + case NURSERY_PAGE: + case FILLED_PAGE: + break; + default: + barf("m32: invalid page state\n"); + } +} +static void ASSERT_PAGE_TYPE(struct m32_page_t *page, enum m32_page_type ty) { + if (page->type != ty) { barf("m32: unexpected page type"); } +} +static void ASSERT_PAGE_NOT_FREE(struct m32_page_t *page) { + if (page->type == FREE_PAGE) { barf("m32: unexpected free page"); } +} +static void SET_PAGE_TYPE(struct m32_page_t *page, enum m32_page_type ty) { + page->type = ty; +} +#else +#define ASSERT_PAGE_ALIGNED(page) +#define ASSERT_VALID_PAGE(page) +#define ASSERT_PAGE_NOT_FREE(page) +#define ASSERT_PAGE_TYPE(page, ty) +#define SET_PAGE_TYPE(page, ty) +#endif + +/* Accessors */ static void m32_filled_page_set_next(struct m32_page_t *page, struct m32_page_t *next) { - if (! is_okay_address(next)) { - barf("m32_filled_page_set_next: Page not within 4GB of program text"); + ASSERT_PAGE_TYPE(page, FILLED_PAGE); + if (next != NULL && ! is_okay_address(next)) { + barf("m32_filled_page_set_next: Page %p not within 4GB of program text", next); } page->filled_page.next = next; } @@ -195,7 +248,8 @@ m32_filled_page_set_next(struct m32_page_t *page, struct m32_page_t *next) static struct m32_page_t * m32_filled_page_get_next(struct m32_page_t *page) { - return (struct m32_page_t *) (uintptr_t) page->filled_page.next; + ASSERT_PAGE_TYPE(page, FILLED_PAGE); + return (struct m32_page_t *) (uintptr_t) page->filled_page.next; } /** @@ -220,21 +274,42 @@ struct m32_allocator_t { * We keep a small pool of free pages around to avoid fragmentation. */ struct m32_page_t *m32_free_page_pool = NULL; +/** Number of pages in free page pool */ unsigned int m32_free_page_pool_size = 0; -// TODO /** - * Free a page or, if possible, place it in the free page pool. + * Free a filled page or, if possible, place it in the free page pool. */ static void m32_release_page(struct m32_page_t *page) { - if (m32_free_page_pool_size < M32_MAX_FREE_PAGE_POOL_SIZE) { - page->free_page.next = m32_free_page_pool; - m32_free_page_pool = page; - m32_free_page_pool_size ++; - } else { - munmapForLinker((void *) page, getPageSize(), "m32_release_page"); + // Some sanity-checking + ASSERT_VALID_PAGE(page); + ASSERT_PAGE_NOT_FREE(page); + + const size_t pgsz = getPageSize(); + ssize_t sz = page->filled_page.size; + IF_DEBUG(sanity, memset(page, 0xaa, sz)); + + // Break the page, which may be a large multi-page allocation, into + // individual pages for the page pool + while (sz > 0) { + if (m32_free_page_pool_size < M32_MAX_FREE_PAGE_POOL_SIZE) { + mprotectForLinker(page, pgsz, MEM_READ_WRITE); + SET_PAGE_TYPE(page, FREE_PAGE); + page->free_page.next = m32_free_page_pool; + m32_free_page_pool = page; + m32_free_page_pool_size ++; + } else { + break; + } + page = (struct m32_page_t *) ((uint8_t *) page + pgsz); + sz -= pgsz; + } + + // The free page pool is full, release the rest back to the system + if (sz > 0) { + munmapForLinker((void *) page, ROUND_UP(sz, pgsz), "m32_release_page"); } } @@ -256,10 +331,12 @@ m32_alloc_page(void) if (! is_okay_address(chunk + map_sz)) { barf("m32_alloc_page: failed to allocate pages within 4GB of program text (got %p)", chunk); } + IF_DEBUG(sanity, memset(chunk, 0xaa, map_sz)); #define GET_PAGE(i) ((struct m32_page_t *) (chunk + (i) * pgsz)) for (int i=0; i < M32_MAP_PAGES; i++) { struct m32_page_t *page = GET_PAGE(i); + SET_PAGE_TYPE(page, FREE_PAGE); page->free_page.next = GET_PAGE(i+1); } @@ -272,6 +349,7 @@ m32_alloc_page(void) struct m32_page_t *page = m32_free_page_pool; m32_free_page_pool = page->free_page.next; m32_free_page_pool_size --; + ASSERT_PAGE_TYPE(page, FREE_PAGE); return page; } @@ -297,6 +375,7 @@ static void m32_allocator_unmap_list(struct m32_page_t *head) { while (head != NULL) { + ASSERT_VALID_PAGE(head); struct m32_page_t *next = m32_filled_page_get_next(head); munmapForLinker((void *) head, head->filled_page.size, "m32_allocator_unmap_list"); head = next; @@ -355,6 +434,7 @@ m32_allocator_flush(m32_allocator *alloc) { m32_release_page(alloc->pages[i]); } else { // the page contains data, move it to the unprotected list + SET_PAGE_TYPE(alloc->pages[i], FILLED_PAGE); m32_allocator_push_filled_list(&alloc->unprotected_list, alloc->pages[i]); } alloc->pages[i] = NULL; @@ -364,6 +444,7 @@ m32_allocator_flush(m32_allocator *alloc) { if (alloc->executable) { struct m32_page_t *page = alloc->unprotected_list; while (page != NULL) { + ASSERT_PAGE_TYPE(page, FILLED_PAGE); struct m32_page_t *next = m32_filled_page_get_next(page); m32_allocator_push_filled_list(&alloc->protected_list, page); mprotectForLinker(page, page->filled_page.size, MEM_READ_EXECUTE); @@ -404,6 +485,7 @@ m32_alloc(struct m32_allocator_t *alloc, size_t size, size_t alignment) barf("m32_alloc: warning: Allocation of %zd bytes resulted in pages above 4GB (%p)", size, page); } + SET_PAGE_TYPE(page, FILLED_PAGE); page->filled_page.size = alsize + size; m32_allocator_push_filled_list(&alloc->unprotected_list, (struct m32_page_t *) page); return (char*) page + alsize; @@ -422,6 +504,8 @@ m32_alloc(struct m32_allocator_t *alloc, size_t size, size_t alignment) } // page can contain the buffer? + ASSERT_VALID_PAGE(alloc->pages[i]); + ASSERT_PAGE_TYPE(alloc->pages[i], NURSERY_PAGE); size_t alsize = ROUND_UP(alloc->pages[i]->current_size, alignment); if (size <= pgsz - alsize) { void * addr = (char*)alloc->pages[i] + alsize; @@ -449,6 +533,7 @@ m32_alloc(struct m32_allocator_t *alloc, size_t size, size_t alignment) if (page == NULL) { return NULL; } + SET_PAGE_TYPE(page, NURSERY_PAGE); alloc->pages[empty] = page; // Add header size and padding alloc->pages[empty]->current_size = -- GitLab From 4671c81888a8a3bd09140094cffa98ca8d83a3d7 Mon Sep 17 00:00:00 2001 From: Ben Gamari Date: Sat, 29 Jan 2022 10:41:18 -0500 Subject: [PATCH 05/13] rts/m32: Free large objects back to the free page pool Not entirely convinced that this is worth doing. (cherry picked from commit 2d6f0b17e3ce9326abd43e187910db0a5e519efa) --- rts/linker/M32Alloc.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/rts/linker/M32Alloc.c b/rts/linker/M32Alloc.c index 7fcf2fc0e02..6f1f8492d71 100644 --- a/rts/linker/M32Alloc.c +++ b/rts/linker/M32Alloc.c @@ -377,7 +377,7 @@ m32_allocator_unmap_list(struct m32_page_t *head) while (head != NULL) { ASSERT_VALID_PAGE(head); struct m32_page_t *next = m32_filled_page_get_next(head); - munmapForLinker((void *) head, head->filled_page.size, "m32_allocator_unmap_list"); + m32_release_page(head); head = next; } } @@ -392,10 +392,9 @@ void m32_allocator_free(m32_allocator *alloc) m32_allocator_unmap_list(alloc->protected_list); /* free partially-filled pages */ - const size_t pgsz = getPageSize(); for (int i=0; i < M32_MAX_PAGES; i++) { if (alloc->pages[i]) { - munmapForLinker(alloc->pages[i], pgsz, "m32_allocator_free"); + m32_release_page(alloc->pages[i]); } } -- GitLab From 13e7ebd81fa8144a756e327e24612e2e6a4cd074 Mon Sep 17 00:00:00 2001 From: GHC GitLab CI Date: Fri, 28 Jan 2022 21:05:53 -0500 Subject: [PATCH 06/13] rts/m32: Increase size of free page pool to 256 pages (cherry picked from commit e96f50beec172f5ff95769842cb9be724363311c) --- rts/linker/M32Alloc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rts/linker/M32Alloc.c b/rts/linker/M32Alloc.c index 6f1f8492d71..46bf72f52ed 100644 --- a/rts/linker/M32Alloc.c +++ b/rts/linker/M32Alloc.c @@ -152,7 +152,7 @@ The allocator is *not* thread-safe. /* How many pages should we map at once when re-filling the free page pool? */ #define M32_MAP_PAGES 32 /* Upper bound on the number of pages to keep in the free page pool */ -#define M32_MAX_FREE_PAGE_POOL_SIZE 64 +#define M32_MAX_FREE_PAGE_POOL_SIZE 256 /* A utility to verify that a given address is "acceptable" for use by m32. */ static bool -- GitLab From 5c31cd4ce13a980320fc44fd62c6984c7ed84ed2 Mon Sep 17 00:00:00 2001 From: Ben Gamari Date: Thu, 3 Feb 2022 10:06:35 -0500 Subject: [PATCH 07/13] rts: Dump memory map on memory mapping failures Fixes #20992. (cherry picked from commit fc083b480adedf26d47f880402f111680ec34183) --- rts/Linker.c | 3 + rts/MemoryMap.c | 138 ++++++++++++++++++++++++++++++++++++++++++ rts/MemoryMap.h | 13 ++++ rts/linker/M32Alloc.c | 3 + rts/rts.cabal.in | 1 + 5 files changed, 158 insertions(+) create mode 100644 rts/MemoryMap.c create mode 100644 rts/MemoryMap.h diff --git a/rts/Linker.c b/rts/Linker.c index 225457f24a9..4a59f187f24 100644 --- a/rts/Linker.c +++ b/rts/Linker.c @@ -33,6 +33,7 @@ #include "linker/SymbolExtras.h" #include "PathUtils.h" #include "CheckUnload.h" // createOCSectionIndices +#include "MemoryMap.h" #if !defined(mingw32_HOST_OS) #include "posix/Signals.h" @@ -1146,6 +1147,7 @@ mmap_again: MAP_PRIVATE|tryMap32Bit|fixed|flags, fd, offset); if (result == MAP_FAILED) { + reportMemoryMap(); sysErrorBelch("mmap %" FMT_Word " bytes at %p",(W_)size,map_addr); errorBelch("Try specifying an address with +RTS -xm -RTS"); return NULL; @@ -1168,6 +1170,7 @@ mmap_again: fixed = MAP_FIXED; goto mmap_again; #else + reportMemoryMap(); errorBelch("mmapForLinker: failed to mmap() memory below 2Gb; " "asked for %lu bytes at %p. " "Try specifying an address with +RTS -xm -RTS", diff --git a/rts/MemoryMap.c b/rts/MemoryMap.c new file mode 100644 index 00000000000..99273b7dc69 --- /dev/null +++ b/rts/MemoryMap.c @@ -0,0 +1,138 @@ +/* ----------------------------------------------------------------------------- + * + * (c) The GHC Team, 1998-2004 + * + * Memory-map dumping. + * + * This is intended to be used for reporting the process memory-map + * in diagnostics when the RTS fails to map a block of memory. + * + * ---------------------------------------------------------------------------*/ + +#include "PosixSource.h" +#include "Rts.h" + +#include + +#if defined(darwin_HOST_OS) +#include +#include +#include +#include +#endif + +#include "MemoryMap.h" + +#if defined(mingw32_HOST_OS) + +void reportMemoryMap() { + debugBelch("\nMemory map:\n"); + uint8_t *addr = NULL; + while (true) { + MEMORY_BASIC_INFORMATION info; + int res = VirtualQuery(addr, &info, sizeof(info)); + if (!res && GetLastError() == ERROR_INVALID_PARAMETER) { + return; + } else if (!res) { + sysErrorBelch("VirtualQuery failed"); + return; + } + + if (info.State & MEM_FREE) { + // free range + } else { + const char *protection; + switch (info.Protect) { + case PAGE_EXECUTE: protection = "--x"; break; + case PAGE_EXECUTE_READ: protection = "r-x"; break; + case PAGE_EXECUTE_READWRITE: protection = "rwx"; break; + case PAGE_EXECUTE_WRITECOPY: protection = "rcx"; break; + case PAGE_NOACCESS: protection = "---"; break; + case PAGE_READONLY: protection = "r--"; break; + case PAGE_READWRITE: protection = "rw-"; break; + case PAGE_WRITECOPY: protection = "rc-"; break; + default: protection = "???"; break; + } + + const char *type; + switch (info.Type) { + case MEM_IMAGE: type = "image"; break; + case MEM_MAPPED: type = "mapped"; break; + case MEM_PRIVATE: type = "private"; break; + default: type = "unknown"; break; + } + + debugBelch("%08llx-%08llx %8zuK %3s (%s)\n", + (uintptr_t) info.BaseAddress, + (uintptr_t) info.BaseAddress + info.RegionSize, + (size_t) info.RegionSize, + protection, type); + } + addr = (uint8_t *) info.BaseAddress + info.RegionSize; + } +} + +#elif defined(darwin_HOST_OS) + +void reportMemoryMap() { + // Inspired by MacFUSE /proc implementation + debugBelch("\nMemory map:\n"); + while (true) { + vm_size_t vmsize; + vm_address_t address; + vm_region_basic_info_data_t info; + vm_region_flavor_t flavor = VM_REGION_BASIC_INFO; + memory_object_name_t object; + mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT; + kern_return_t kr = + mach_vm_region(mach_task_self(), &address, &vmsize, flavor, + (vm_region_info_t)&info, &info_count, &object); + if (kr == KERN_SUCCESS) { + debugBelch("%08lx-%08lx %8zuK %c%c%c/%c%c%c\n", + address, (address + vmsize), (vmsize >> 10), + (info.protection & VM_PROT_READ) ? 'r' : '-', + (info.protection & VM_PROT_WRITE) ? 'w' : '-', + (info.protection & VM_PROT_EXECUTE) ? 'x' : '-', + (info.max_protection & VM_PROT_READ) ? 'r' : '-', + (info.max_protection & VM_PROT_WRITE) ? 'w' : '-', + (info.max_protection & VM_PROT_EXECUTE) ? 'x' : '-'); + address += vmsize; + } else if (kr == KERN_INVALID_ADDRESS) { + // We presumably reached the end of address space + break; + } else { + debugBelch(" Error: %s\n", mach_error_string(kr)); + break; + } + } +} + +#else + +// Linux et al. +void reportMemoryMap() { + debugBelch("\nMemory map:\n"); + FILE *f = fopen("/proc/self/maps", "r"); + if (f == NULL) { + debugBelch(" Could not open /proc/self/maps\n"); + return; + } + + while (true) { + char buf[256]; + size_t n = fread(buf, 1, sizeof(buf)-1, f); + if (n <= 0) { + debugBelch(" Error: %s\n", strerror(errno)); + break; + } + buf[n] = '\0'; + debugBelch("%s", buf); + if (n < sizeof(buf)-1) { + break; + } + } + debugBelch("\n"); + fclose(f); +} + +#endif diff --git a/rts/MemoryMap.h b/rts/MemoryMap.h new file mode 100644 index 00000000000..7d2c4a58b1d --- /dev/null +++ b/rts/MemoryMap.h @@ -0,0 +1,13 @@ +/* ----------------------------------------------------------------------------- + * + * (c) The GHC Team, 1998-2004 + * + * Memory-map dumping. + * + * This is intended to be used for reporting the process memory-map + * in diagnostics when the RTS fails to map a block of memory. + * + * ---------------------------------------------------------------------------*/ + +void reportMemoryMap(void); + diff --git a/rts/linker/M32Alloc.c b/rts/linker/M32Alloc.c index 46bf72f52ed..c0462d774b1 100644 --- a/rts/linker/M32Alloc.c +++ b/rts/linker/M32Alloc.c @@ -11,6 +11,7 @@ #include "RtsUtils.h" #include "linker/M32Alloc.h" #include "LinkerInternals.h" +#include "MemoryMap.h" #include #include @@ -329,6 +330,7 @@ m32_alloc_page(void) const size_t map_sz = pgsz * M32_MAP_PAGES; uint8_t *chunk = mmapAnonForLinker(map_sz); if (! is_okay_address(chunk + map_sz)) { + reportMemoryMap(); barf("m32_alloc_page: failed to allocate pages within 4GB of program text (got %p)", chunk); } IF_DEBUG(sanity, memset(chunk, 0xaa, map_sz)); @@ -481,6 +483,7 @@ m32_alloc(struct m32_allocator_t *alloc, size_t size, size_t alignment) sysErrorBelch("m32_alloc: Failed to map pages for %zd bytes", size); return NULL; } else if (! is_okay_address(page)) { + reportMemoryMap(); barf("m32_alloc: warning: Allocation of %zd bytes resulted in pages above 4GB (%p)", size, page); } diff --git a/rts/rts.cabal.in b/rts/rts.cabal.in index a2acf27cb5b..ed93800e574 100644 --- a/rts/rts.cabal.in +++ b/rts/rts.cabal.in @@ -475,6 +475,7 @@ library Libdw.c LibdwPool.c Linker.c + MemoryMap.c Messages.c OldARMAtomic.c PathUtils.c -- GitLab From 268fbed33274f1ec1c4ff02b1afe2c55a4a9916a Mon Sep 17 00:00:00 2001 From: Ben Gamari Date: Thu, 28 Apr 2022 23:03:32 -0400 Subject: [PATCH 08/13] rts/m32: Fix assertion failure This fixes an assertion failure in the m32 allocator due to the imprecisely specified preconditions of `m32_allocator_push_filled_list`. Specifically, the caller must ensure that the page type is set to filled prior to calling `m32_allocator_push_filled_list`. While this issue did result in an assertion failure in the debug RTS, the issue is in fact benign. (cherry picked from commit 37825ce283b6dbcb532f51fade090a69afc2d078) --- rts/linker/M32Alloc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rts/linker/M32Alloc.c b/rts/linker/M32Alloc.c index c0462d774b1..baec1039d5f 100644 --- a/rts/linker/M32Alloc.c +++ b/rts/linker/M32Alloc.c @@ -409,6 +409,8 @@ void m32_allocator_free(m32_allocator *alloc) static void m32_allocator_push_filled_list(struct m32_page_t **head, struct m32_page_t *page) { + ASSERT_PAGE_TYPE(page, FILLED_PAGE); + // N.B. it's the caller's responsibility to set the pagetype to FILLED_PAGE m32_filled_page_set_next(page, *head); *head = page; } @@ -525,6 +527,7 @@ m32_alloc(struct m32_allocator_t *alloc, size_t size, size_t alignment) // If we haven't found an empty page, flush the most filled one if (empty == -1) { + SET_PAGE_TYPE(alloc->pages[most_filled], FILLED_PAGE); m32_allocator_push_filled_list(&alloc->unprotected_list, alloc->pages[most_filled]); alloc->pages[most_filled] = NULL; empty = most_filled; -- GitLab From c8733945501ca6622f091a6f696de139bc5669e5 Mon Sep 17 00:00:00 2001 From: Ben Gamari Date: Mon, 7 Feb 2022 16:15:41 -0500 Subject: [PATCH 09/13] rts: Rename MemoryMap.[ch] -> ReportMemoryMap.[ch] (cherry picked from commit 3df06922f03191310ebee0547de1782eeb6bda67) --- rts/Linker.c | 2 +- rts/{MemoryMap.c => ReportMemoryMap.c} | 2 +- rts/{MemoryMap.h => ReportMemoryMap.h} | 0 rts/linker/M32Alloc.c | 2 +- rts/rts.cabal.in | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename rts/{MemoryMap.c => ReportMemoryMap.c} (99%) rename rts/{MemoryMap.h => ReportMemoryMap.h} (100%) diff --git a/rts/Linker.c b/rts/Linker.c index 4a59f187f24..55f8621e2cd 100644 --- a/rts/Linker.c +++ b/rts/Linker.c @@ -33,7 +33,7 @@ #include "linker/SymbolExtras.h" #include "PathUtils.h" #include "CheckUnload.h" // createOCSectionIndices -#include "MemoryMap.h" +#include "ReportMemoryMap.h" #if !defined(mingw32_HOST_OS) #include "posix/Signals.h" diff --git a/rts/MemoryMap.c b/rts/ReportMemoryMap.c similarity index 99% rename from rts/MemoryMap.c rename to rts/ReportMemoryMap.c index 99273b7dc69..c30c80070ee 100644 --- a/rts/MemoryMap.c +++ b/rts/ReportMemoryMap.c @@ -21,7 +21,7 @@ #include #endif -#include "MemoryMap.h" +#include "ReportMemoryMap.h" #if defined(mingw32_HOST_OS) diff --git a/rts/MemoryMap.h b/rts/ReportMemoryMap.h similarity index 100% rename from rts/MemoryMap.h rename to rts/ReportMemoryMap.h diff --git a/rts/linker/M32Alloc.c b/rts/linker/M32Alloc.c index baec1039d5f..b0a6ccfd58f 100644 --- a/rts/linker/M32Alloc.c +++ b/rts/linker/M32Alloc.c @@ -11,7 +11,7 @@ #include "RtsUtils.h" #include "linker/M32Alloc.h" #include "LinkerInternals.h" -#include "MemoryMap.h" +#include "ReportMemoryMap.h" #include #include diff --git a/rts/rts.cabal.in b/rts/rts.cabal.in index ed93800e574..77f3ee989dd 100644 --- a/rts/rts.cabal.in +++ b/rts/rts.cabal.in @@ -475,7 +475,7 @@ library Libdw.c LibdwPool.c Linker.c - MemoryMap.c + ReportMemoryMap.c Messages.c OldARMAtomic.c PathUtils.c -- GitLab From 49e546b73bcef8cbab310685fd3d05f6b1d2a294 Mon Sep 17 00:00:00 2001 From: Ben Gamari Date: Mon, 7 Feb 2022 16:21:50 -0500 Subject: [PATCH 10/13] rts: Move mmapForLinker and friends to linker/MMap.c They are not particularly related to linking. (cherry picked from commit e219ac826b05db833531028e0663f62f12eff010) --- rts/ExecPage.c | 2 +- rts/Linker.c | 252 +-------------------------------- rts/LinkerInternals.h | 88 ------------ rts/linker/Elf.c | 1 + rts/linker/LoadArchive.c | 1 + rts/linker/M32Alloc.c | 2 +- rts/linker/MMap.c | 290 ++++++++++++++++++++++++++++++++++++++ rts/linker/MMap.h | 79 +++++++++++ rts/linker/SymbolExtras.c | 1 + rts/linker/elf_got.c | 1 + rts/rts.cabal.in | 1 + 11 files changed, 377 insertions(+), 341 deletions(-) create mode 100644 rts/linker/MMap.c create mode 100644 rts/linker/MMap.h diff --git a/rts/ExecPage.c b/rts/ExecPage.c index 24d4d65bad4..0f83c8e1f59 100644 --- a/rts/ExecPage.c +++ b/rts/ExecPage.c @@ -6,8 +6,8 @@ */ #include "Rts.h" -#include "LinkerInternals.h" #include "sm/OSMem.h" +#include "linker/MMap.h" ExecPage *allocateExecPage() { ExecPage *page = (ExecPage *) mmapAnonForLinker(getPageSize()); diff --git a/rts/Linker.c b/rts/Linker.c index 55f8621e2cd..0d836a37a46 100644 --- a/rts/Linker.c +++ b/rts/Linker.c @@ -31,6 +31,7 @@ #include "linker/M32Alloc.h" #include "linker/CacheFlush.h" #include "linker/SymbolExtras.h" +#include "linker/MMap.h" #include "PathUtils.h" #include "CheckUnload.h" // createOCSectionIndices #include "ReportMemoryMap.h" @@ -199,8 +200,6 @@ Mutex linker_mutex; /* Generic wrapper function to try and Resolve and RunInit oc files */ int ocTryLoad( ObjectCode* oc ); -static void *mmap_32bit_base = LINKER_LOAD_BASE; - static void ghciRemoveSymbolTable(StrHashTable *table, const SymbolName* key, ObjectCode *owner) { @@ -1049,255 +1048,6 @@ resolveSymbolAddr (pathchar* buffer, int size, #endif /* OBJFORMAT_PEi386 */ } -static const char *memoryAccessDescription(MemoryAccess mode) -{ - switch (mode) { - case MEM_NO_ACCESS: return "no-access"; - case MEM_READ_ONLY: return "read-only"; - case MEM_READ_WRITE: return "read-write"; - case MEM_READ_EXECUTE: return "read-execute"; - default: barf("invalid MemoryAccess"); - } -} - -#if defined(mingw32_HOST_OS) - -// -// Returns NULL on failure. -// -void * -mmapAnonForLinker (size_t bytes) -{ - return VirtualAlloc(NULL, bytes, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); -} - -void -munmapForLinker (void *addr, size_t bytes, const char *caller) -{ - if (VirtualFree(addr, 0, MEM_RELEASE) == 0) { - sysErrorBelch("munmapForLinker: %s: Failed to unmap %zd bytes at %p", - caller, bytes, addr); - } -} - -/** - * Change the allowed access modes of a region of memory previously allocated - * with mmapAnonForLinker. - */ -void -mprotectForLinker(void *start, size_t len, MemoryAccess mode) -{ - DWORD old; - if (len == 0) { - return; - } - DWORD prot; - switch (mode) { - case MEM_NO_ACCESS: prot = PAGE_NOACCESS; break; - case MEM_READ_ONLY: prot = PAGE_READONLY; break; - case MEM_READ_WRITE: prot = PAGE_READWRITE; break; - case MEM_READ_EXECUTE: prot = PAGE_EXECUTE_READ; break; - default: barf("invalid MemoryAccess"); - } - - if (VirtualProtect(start, len, prot, &old) == 0) { - sysErrorBelch("mprotectForLinker: failed to protect %zd bytes at %p as %s", - len, start, memoryAccessDescription(mode)); - ASSERT(false); - } -} - -#elif RTS_LINKER_USE_MMAP -// -// Returns NULL on failure. -// -void * -mmapForLinker (size_t bytes, uint32_t prot, uint32_t flags, int fd, int offset) -{ - void *map_addr = NULL; - void *result; - size_t size; - uint32_t tryMap32Bit = RtsFlags.MiscFlags.linkerAlwaysPic - ? 0 - : TRY_MAP_32BIT; - static uint32_t fixed = 0; - - IF_DEBUG(linker, debugBelch("mmapForLinker: start\n")); - size = roundUpToPage(bytes); - -#if defined(MAP_LOW_MEM) -mmap_again: -#endif - - if (mmap_32bit_base != NULL) { - map_addr = mmap_32bit_base; - } - - IF_DEBUG(linker, - debugBelch("mmapForLinker: \tprotection %#0x\n", prot)); - IF_DEBUG(linker, - debugBelch("mmapForLinker: \tflags %#0x\n", - MAP_PRIVATE | tryMap32Bit | fixed | flags)); - IF_DEBUG(linker, - debugBelch("mmapForLinker: \tsize %#0zx\n", bytes)); - IF_DEBUG(linker, - debugBelch("mmapForLinker: \tmap_addr %p\n", map_addr)); - - result = mmap(map_addr, size, prot, - MAP_PRIVATE|tryMap32Bit|fixed|flags, fd, offset); - - if (result == MAP_FAILED) { - reportMemoryMap(); - sysErrorBelch("mmap %" FMT_Word " bytes at %p",(W_)size,map_addr); - errorBelch("Try specifying an address with +RTS -xm -RTS"); - return NULL; - } - -#if defined(MAP_LOW_MEM) - if (RtsFlags.MiscFlags.linkerAlwaysPic) { - /* make no attempt at mapping low memory if we are assuming PIC */ - } else if (mmap_32bit_base != NULL) { - if (result != map_addr) { - if ((W_)result > 0x80000000) { - // oops, we were given memory over 2Gb - munmap(result,size); -#if defined(freebsd_HOST_OS) || \ - defined(kfreebsdgnu_HOST_OS) || \ - defined(dragonfly_HOST_OS) - // Some platforms require MAP_FIXED. This is normally - // a bad idea, because MAP_FIXED will overwrite - // existing mappings. - fixed = MAP_FIXED; - goto mmap_again; -#else - reportMemoryMap(); - errorBelch("mmapForLinker: failed to mmap() memory below 2Gb; " - "asked for %lu bytes at %p. " - "Try specifying an address with +RTS -xm -RTS", - size, map_addr); - return NULL; -#endif - } else { - // hmm, we were given memory somewhere else, but it's - // still under 2Gb so we can use it. - } - } - } else { - if ((W_)result > 0x80000000) { - // oops, we were given memory over 2Gb - // ... try allocating memory somewhere else?; - debugTrace(DEBUG_linker, - "MAP_32BIT didn't work; gave us %lu bytes at 0x%p", - bytes, result); - munmap(result, size); - - // Set a base address and try again... (guess: 1Gb) - mmap_32bit_base = (void*)0x40000000; - goto mmap_again; - } - } -#elif (defined(aarch64_TARGET_ARCH) || defined(aarch64_HOST_ARCH)) - // for aarch64 we need to make sure we stay within 4GB of the - // mmap_32bit_base, and we also do not want to update it. - if (result != map_addr) { - // upper limit 4GB - size of the object file - 1mb wiggle room. - if(llabs((uintptr_t)result - (uintptr_t)&stg_upd_frame_info) > (2<<32) - size - (2<<20)) { - // not within range :( - debugTrace(DEBUG_linker, - "MAP_32BIT didn't work; gave us %lu bytes at 0x%p", - bytes, result); - munmap(result, size); - // TODO: some abort/mmap_32bit_base recomputation based on - // if mmap_32bit_base is changed, or still at stg_upd_frame_info - goto mmap_again; - } - } -#endif - - if (mmap_32bit_base != NULL) { - // Next time, ask for memory right after our new mapping to maximize the - // chance that we get low memory. - mmap_32bit_base = (void*) ((uintptr_t)result + size); - } - - IF_DEBUG(linker, - debugBelch("mmapForLinker: mapped %" FMT_Word - " bytes starting at %p\n", (W_)size, result)); - IF_DEBUG(linker, - debugBelch("mmapForLinker: done\n")); - - return result; -} - -/* - * Map read/write pages in low memory. Returns NULL on failure. - */ -void * -mmapAnonForLinker (size_t bytes) -{ - return mmapForLinker (bytes, PROT_READ|PROT_WRITE, MAP_ANONYMOUS, -1, 0); -} - -void munmapForLinker (void *addr, size_t bytes, const char *caller) -{ - int r = munmap(addr, bytes); - if (r == -1) { - // Should we abort here? - sysErrorBelch("munmap: %s", caller); - } -} - -/* Note [Memory protection in the linker] - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * For many years the linker would simply map all of its memory - * with PROT_READ|PROT_WRITE|PROT_EXEC. However operating systems have been - * becoming increasingly reluctant to accept this practice (e.g. #17353, - * #12657) and for good reason: writable code is ripe for exploitation. - * - * Consequently mmapForLinker now maps its memory with PROT_READ|PROT_WRITE. - * After the linker has finished filling/relocating the mapping it must then - * call mprotectForLinker on the sections of the mapping which - * contain executable code. - * - * Note that the m32 allocator handles protection of its allocations. For this - * reason the caller to m32_alloc() must tell the allocator whether the - * allocation needs to be executable. The caller must then ensure that they - * call m32_allocator_flush() after they are finished filling the region, which - * will cause the allocator to change the protection bits to - * PROT_READ|PROT_EXEC. - * - */ - -/* - * Mark an portion of a mapping previously reserved by mmapForLinker - * as executable (but not writable). - */ -void mprotectForLinker(void *start, size_t len, MemoryAccess mode) -{ - if (len == 0) { - return; - } - IF_DEBUG(linker, - debugBelch("mprotectForLinker: protecting %" FMT_Word - " bytes starting at %p as %s\n", - (W_)len, start, memoryAccessDescription(mode))); - - int prot; - switch (mode) { - case MEM_NO_ACCESS: prot = 0; break; - case MEM_READ_ONLY: prot = PROT_READ; break; - case MEM_READ_WRITE: prot = PROT_READ | PROT_WRITE; break; - case MEM_READ_EXECUTE: prot = PROT_READ | PROT_EXEC; break; - default: barf("invalid MemoryAccess"); - } - - if (mprotect(start, len, prot) == -1) { - sysErrorBelch("mprotectForLinker: failed to protect %zd bytes at %p as %s", - len, start, memoryAccessDescription(mode)); - } -} -#endif - /* * Remove symbols from the symbol table, and free oc->symbols. * This operation is idempotent. diff --git a/rts/LinkerInternals.h b/rts/LinkerInternals.h index 3e6b3df9dab..ccda39b0cf0 100644 --- a/rts/LinkerInternals.h +++ b/rts/LinkerInternals.h @@ -374,19 +374,6 @@ void exitLinker( void ); void freeObjectCode (ObjectCode *oc); SymbolAddr* loadSymbol(SymbolName *lbl, RtsSymbolInfo *pinfo); -/** Access modes for mprotectForLinker */ -typedef enum { - MEM_NO_ACCESS, - MEM_READ_ONLY, - MEM_READ_WRITE, - MEM_READ_EXECUTE, -} MemoryAccess; - -void *mmapAnonForLinker (size_t bytes); -void *mmapForLinker (size_t bytes, uint32_t prot, uint32_t flags, int fd, int offset); -void mprotectForLinker(void *start, size_t len, MemoryAccess mode); -void munmapForLinker (void *addr, size_t bytes, const char *caller); - void addProddableBlock ( ObjectCode* oc, void* start, int size ); void checkProddableBlock (ObjectCode *oc, void *addr, size_t size ); void freeProddableBlocks (ObjectCode *oc); @@ -441,65 +428,6 @@ resolveSymbolAddr (pathchar* buffer, int size, #define USE_CONTIGUOUS_MMAP 0 #endif -/* Link objects into the lower 2Gb on x86_64 and AArch64. GHC assumes the - * small memory model on this architecture (see gcc docs, - * -mcmodel=small). - * - * MAP_32BIT not available on OpenBSD/amd64 - */ -#if defined(MAP_32BIT) && (defined(x86_64_HOST_ARCH) || (defined(aarch64_TARGET_ARCH) || defined(aarch64_HOST_ARCH))) -#define MAP_LOW_MEM -#define TRY_MAP_32BIT MAP_32BIT -#else -#define TRY_MAP_32BIT 0 -#endif - -#if defined(aarch64_HOST_ARCH) -// On AArch64 MAP_32BIT is not available but we are still bound by the small -// memory model. Consequently we still try using the MAP_LOW_MEM allocation -// strategy. -#define MAP_LOW_MEM -#endif - -/* - * Note [MAP_LOW_MEM] - * ~~~~~~~~~~~~~~~~~~ - * Due to the small memory model (see above), on x86_64 and AArch64 we have to - * map all our non-PIC object files into the low 2Gb of the address space (why - * 2Gb and not 4Gb? Because all addresses must be reachable using a 32-bit - * signed PC-relative offset). On x86_64 Linux we can do this using the - * MAP_32BIT flag to mmap(), however on other OSs (e.g. *BSD, see #2063, and - * also on Linux inside Xen, see #2512), we can't do this. So on these - * systems, we have to pick a base address in the low 2Gb of the address space - * and try to allocate memory from there. - * - * The same holds for aarch64, where the default, even with PIC, model - * is 4GB. The linker is free to emit AARCH64_ADR_PREL_PG_HI21 - * relocations. - * - * We pick a default address based on the OS, but also make this - * configurable via an RTS flag (+RTS -xm) - */ - -#if defined(aarch64_TARGET_ARCH) || defined(aarch64_HOST_ARCH) -// Try to use stg_upd_frame_info as the base. We need to be within +-4GB of that -// address, otherwise we violate the aarch64 memory model. Any object we load -// can potentially reference any of the ones we bake into the binary (and list) -// in RtsSymbols. Thus we'll need to be within +-4GB of those, -// stg_upd_frame_info is a good candidate as it's referenced often. -#define LINKER_LOAD_BASE ((void *) &stg_upd_frame_info) -#elif defined(x86_64_HOST_ARCH) && defined(mingw32_HOST_OS) -// On Windows (which now uses high-entropy ASLR by default) we need to ensure -// that we map code near the executable image. We use stg_upd_frame_info as a -// proxy for the image location. -#define LINKER_LOAD_BASE ((void *) &stg_upd_frame_info) -#elif defined(MAP_32BIT) || DEFAULT_LINKER_ALWAYS_PIC -// Try to use MAP_32BIT -#define LINKER_LOAD_BASE ((void *) 0x0) -#else -// A guess: 1 GB. -#define LINKER_LOAD_BASE ((void *) 0x40000000) -#endif HsInt isAlreadyLoaded( pathchar *path ); OStatus getObjectLoadStatus_ (pathchar *path); @@ -512,20 +440,4 @@ ObjectCode* mkOc( ObjectType type, pathchar *path, char *image, int imageSize, void initSegment(Segment *s, void *start, size_t size, SegmentProt prot, int n_sections); void freeSegments(ObjectCode *oc); -/* MAP_ANONYMOUS is MAP_ANON on some systems, - e.g. OS X (before Sierra), OpenBSD etc */ -#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) -#define MAP_ANONYMOUS MAP_ANON -#endif - -/* In order to simplify control flow a bit, some references to mmap-related - definitions are blocked off by a C-level if statement rather than a CPP-level - #if statement. Since those are dead branches when !RTS_LINKER_USE_MMAP, we - just stub out the relevant symbols here -*/ -#if !RTS_LINKER_USE_MMAP -#define munmap(x,y) /* nothing */ -#define MAP_ANONYMOUS 0 -#endif - #include "EndPrivate.h" diff --git a/rts/linker/Elf.c b/rts/linker/Elf.c index 980d4b80f05..9956114264e 100644 --- a/rts/linker/Elf.c +++ b/rts/linker/Elf.c @@ -17,6 +17,7 @@ #include "RtsSymbolInfo.h" #include "CheckUnload.h" #include "LinkerInternals.h" +#include "linker/MMap.h" #include "linker/Elf.h" #include "linker/CacheFlush.h" #include "linker/M32Alloc.h" diff --git a/rts/linker/LoadArchive.c b/rts/linker/LoadArchive.c index 041ebef4b61..f9282f197ff 100644 --- a/rts/linker/LoadArchive.c +++ b/rts/linker/LoadArchive.c @@ -7,6 +7,7 @@ #include "LinkerInternals.h" #include "CheckUnload.h" // loaded_objects, insertOCSectionIndices #include "linker/M32Alloc.h" +#include "linker/MMap.h" /* Platform specific headers */ #if defined(OBJFORMAT_PEi386) diff --git a/rts/linker/M32Alloc.c b/rts/linker/M32Alloc.c index b0a6ccfd58f..2592599d92b 100644 --- a/rts/linker/M32Alloc.c +++ b/rts/linker/M32Alloc.c @@ -10,7 +10,7 @@ #include "sm/OSMem.h" #include "RtsUtils.h" #include "linker/M32Alloc.h" -#include "LinkerInternals.h" +#include "linker/MMap.h" #include "ReportMemoryMap.h" #include diff --git a/rts/linker/MMap.c b/rts/linker/MMap.c new file mode 100644 index 00000000000..c2edf78fd14 --- /dev/null +++ b/rts/linker/MMap.c @@ -0,0 +1,290 @@ +#include "Rts.h" + +#include "sm/OSMem.h" +#include "linker/MMap.h" +#include "Trace.h" +#include "ReportMemoryMap.h" + +#if RTS_LINKER_USE_MMAP +#include +#endif + +/* Link objects into the lower 2Gb on x86_64 and AArch64. GHC assumes the + * small memory model on this architecture (see gcc docs, + * -mcmodel=small). + * + * MAP_32BIT not available on OpenBSD/amd64 + */ +#if defined(MAP_32BIT) && (defined(x86_64_HOST_ARCH) || (defined(aarch64_TARGET_ARCH) || defined(aarch64_HOST_ARCH))) +#define MAP_LOW_MEM +#define TRY_MAP_32BIT MAP_32BIT +#else +#define TRY_MAP_32BIT 0 +#endif + +/* MAP_ANONYMOUS is MAP_ANON on some systems, + e.g. OS X (before Sierra), OpenBSD etc */ +#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) +#define MAP_ANONYMOUS MAP_ANON +#endif + +/* In order to simplify control flow a bit, some references to mmap-related + definitions are blocked off by a C-level if statement rather than a CPP-level + #if statement. Since those are dead branches when !RTS_LINKER_USE_MMAP, we + just stub out the relevant symbols here +*/ +#if !RTS_LINKER_USE_MMAP +#define munmap(x,y) /* nothing */ +#define MAP_ANONYMOUS 0 +#endif + +void *mmap_32bit_base = LINKER_LOAD_BASE; + +static const char *memoryAccessDescription(MemoryAccess mode) +{ + switch (mode) { + case MEM_NO_ACCESS: return "no-access"; + case MEM_READ_ONLY: return "read-only"; + case MEM_READ_WRITE: return "read-write"; + case MEM_READ_EXECUTE: return "read-execute"; + default: barf("invalid MemoryAccess"); + } +} + +#if defined(mingw32_HOST_OS) + +// +// Returns NULL on failure. +// +void * +mmapAnonForLinker (size_t bytes) +{ + return VirtualAlloc(NULL, bytes, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); +} + +void +munmapForLinker (void *addr, size_t bytes, const char *caller) +{ + if (VirtualFree(addr, 0, MEM_RELEASE) == 0) { + sysErrorBelch("munmapForLinker: %s: Failed to unmap %zd bytes at %p", + caller, bytes, addr); + } +} + +/** + * Change the allowed access modes of a region of memory previously allocated + * with mmapAnonForLinker. + */ +void +mprotectForLinker(void *start, size_t len, MemoryAccess mode) +{ + DWORD old; + if (len == 0) { + return; + } + DWORD prot; + switch (mode) { + case MEM_NO_ACCESS: prot = PAGE_NOACCESS; break; + case MEM_READ_ONLY: prot = PAGE_READONLY; break; + case MEM_READ_WRITE: prot = PAGE_READWRITE; break; + case MEM_READ_EXECUTE: prot = PAGE_EXECUTE_READ; break; + default: barf("invalid MemoryAccess"); + } + + if (VirtualProtect(start, len, prot, &old) == 0) { + sysErrorBelch("mprotectForLinker: failed to protect %zd bytes at %p as %s", + len, start, memoryAccessDescription(mode)); + ASSERT(false); + } +} + +#elif RTS_LINKER_USE_MMAP +// +// Returns NULL on failure. +// +void * +mmapForLinker (size_t bytes, uint32_t prot, uint32_t flags, int fd, int offset) +{ + void *map_addr = NULL; + void *result; + size_t size; + uint32_t tryMap32Bit = RtsFlags.MiscFlags.linkerAlwaysPic + ? 0 + : TRY_MAP_32BIT; + static uint32_t fixed = 0; + + IF_DEBUG(linker, debugBelch("mmapForLinker: start\n")); + size = roundUpToPage(bytes); + +#if defined(MAP_LOW_MEM) +mmap_again: +#endif + + if (mmap_32bit_base != NULL) { + map_addr = mmap_32bit_base; + } + + IF_DEBUG(linker, + debugBelch("mmapForLinker: \tprotection %#0x\n", prot)); + IF_DEBUG(linker, + debugBelch("mmapForLinker: \tflags %#0x\n", + MAP_PRIVATE | tryMap32Bit | fixed | flags)); + IF_DEBUG(linker, + debugBelch("mmapForLinker: \tsize %#0zx\n", bytes)); + IF_DEBUG(linker, + debugBelch("mmapForLinker: \tmap_addr %p\n", map_addr)); + + result = mmap(map_addr, size, prot, + MAP_PRIVATE|tryMap32Bit|fixed|flags, fd, offset); + + if (result == MAP_FAILED) { + reportMemoryMap(); + sysErrorBelch("mmap %" FMT_Word " bytes at %p",(W_)size,map_addr); + errorBelch("Try specifying an address with +RTS -xm -RTS"); + return NULL; + } + +#if defined(MAP_LOW_MEM) + if (RtsFlags.MiscFlags.linkerAlwaysPic) { + /* make no attempt at mapping low memory if we are assuming PIC */ + } else if (mmap_32bit_base != NULL) { + if (result != map_addr) { + if ((W_)result > 0x80000000) { + // oops, we were given memory over 2Gb + munmap(result,size); +#if defined(freebsd_HOST_OS) || \ + defined(kfreebsdgnu_HOST_OS) || \ + defined(dragonfly_HOST_OS) + // Some platforms require MAP_FIXED. This is normally + // a bad idea, because MAP_FIXED will overwrite + // existing mappings. + fixed = MAP_FIXED; + goto mmap_again; +#else + reportMemoryMap(); + errorBelch("mmapForLinker: failed to mmap() memory below 2Gb; " + "asked for %lu bytes at %p. " + "Try specifying an address with +RTS -xm -RTS", + size, map_addr); + return NULL; +#endif + } else { + // hmm, we were given memory somewhere else, but it's + // still under 2Gb so we can use it. + } + } + } else { + if ((W_)result > 0x80000000) { + // oops, we were given memory over 2Gb + // ... try allocating memory somewhere else?; + debugTrace(DEBUG_linker, + "MAP_32BIT didn't work; gave us %lu bytes at 0x%p", + bytes, result); + munmap(result, size); + + // Set a base address and try again... (guess: 1Gb) + mmap_32bit_base = (void*)0x40000000; + goto mmap_again; + } + } +#elif (defined(aarch64_TARGET_ARCH) || defined(aarch64_HOST_ARCH)) + // for aarch64 we need to make sure we stay within 4GB of the + // mmap_32bit_base, and we also do not want to update it. + if (result != map_addr) { + // upper limit 4GB - size of the object file - 1mb wiggle room. + if(llabs((uintptr_t)result - (uintptr_t)&stg_upd_frame_info) > (2<<32) - size - (2<<20)) { + // not within range :( + debugTrace(DEBUG_linker, + "MAP_32BIT didn't work; gave us %lu bytes at 0x%p", + bytes, result); + munmap(result, size); + // TODO: some abort/mmap_32bit_base recomputation based on + // if mmap_32bit_base is changed, or still at stg_upd_frame_info + goto mmap_again; + } + } +#endif + + if (mmap_32bit_base != NULL) { + // Next time, ask for memory right after our new mapping to maximize the + // chance that we get low memory. + mmap_32bit_base = (void*) ((uintptr_t)result + size); + } + + IF_DEBUG(linker, + debugBelch("mmapForLinker: mapped %" FMT_Word + " bytes starting at %p\n", (W_)size, result)); + IF_DEBUG(linker, + debugBelch("mmapForLinker: done\n")); + + return result; +} + +/* + * Map read/write pages in low memory. Returns NULL on failure. + */ +void * +mmapAnonForLinker (size_t bytes) +{ + return mmapForLinker (bytes, PROT_READ|PROT_WRITE, MAP_ANONYMOUS, -1, 0); +} + +void munmapForLinker (void *addr, size_t bytes, const char *caller) +{ + int r = munmap(addr, bytes); + if (r == -1) { + // Should we abort here? + sysErrorBelch("munmap: %s", caller); + } +} + +/* Note [Memory protection in the linker] + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * For many years the linker would simply map all of its memory + * with PROT_READ|PROT_WRITE|PROT_EXEC. However operating systems have been + * becoming increasingly reluctant to accept this practice (e.g. #17353, + * #12657) and for good reason: writable code is ripe for exploitation. + * + * Consequently mmapForLinker now maps its memory with PROT_READ|PROT_WRITE. + * After the linker has finished filling/relocating the mapping it must then + * call mprotectForLinker on the sections of the mapping which + * contain executable code. + * + * Note that the m32 allocator handles protection of its allocations. For this + * reason the caller to m32_alloc() must tell the allocator whether the + * allocation needs to be executable. The caller must then ensure that they + * call m32_allocator_flush() after they are finished filling the region, which + * will cause the allocator to change the protection bits to + * PROT_READ|PROT_EXEC. + * + */ + +/* + * Mark an portion of a mapping previously reserved by mmapForLinker + * as executable (but not writable). + */ +void mprotectForLinker(void *start, size_t len, MemoryAccess mode) +{ + if (len == 0) { + return; + } + IF_DEBUG(linker, + debugBelch("mprotectForLinker: protecting %" FMT_Word + " bytes starting at %p as %s\n", + (W_)len, start, memoryAccessDescription(mode))); + + int prot; + switch (mode) { + case MEM_NO_ACCESS: prot = 0; break; + case MEM_READ_ONLY: prot = PROT_READ; break; + case MEM_READ_WRITE: prot = PROT_READ | PROT_WRITE; break; + case MEM_READ_EXECUTE: prot = PROT_READ | PROT_EXEC; break; + default: barf("invalid MemoryAccess"); + } + + if (mprotect(start, len, prot) == -1) { + sysErrorBelch("mprotectForLinker: failed to protect %zd bytes at %p as %s", + len, start, memoryAccessDescription(mode)); + } +} +#endif diff --git a/rts/linker/MMap.h b/rts/linker/MMap.h new file mode 100644 index 00000000000..ed0baa68998 --- /dev/null +++ b/rts/linker/MMap.h @@ -0,0 +1,79 @@ +#pragma once + +#include "BeginPrivate.h" + +#if defined(aarch64_HOST_ARCH) +// On AArch64 MAP_32BIT is not available but we are still bound by the small +// memory model. Consequently we still try using the MAP_LOW_MEM allocation +// strategy. +#define MAP_LOW_MEM +#endif + +/* + * Note [MAP_LOW_MEM] + * ~~~~~~~~~~~~~~~~~~ + * Due to the small memory model (see above), on x86_64 and AArch64 we have to + * map all our non-PIC object files into the low 2Gb of the address space (why + * 2Gb and not 4Gb? Because all addresses must be reachable using a 32-bit + * signed PC-relative offset). On x86_64 Linux we can do this using the + * MAP_32BIT flag to mmap(), however on other OSs (e.g. *BSD, see #2063, and + * also on Linux inside Xen, see #2512), we can't do this. So on these + * systems, we have to pick a base address in the low 2Gb of the address space + * and try to allocate memory from there. + * + * The same holds for aarch64, where the default, even with PIC, model + * is 4GB. The linker is free to emit AARCH64_ADR_PREL_PG_HI21 + * relocations. + * + * We pick a default address based on the OS, but also make this + * configurable via an RTS flag (+RTS -xm) + */ + +#if defined(aarch64_TARGET_ARCH) || defined(aarch64_HOST_ARCH) +// Try to use stg_upd_frame_info as the base. We need to be within +-4GB of that +// address, otherwise we violate the aarch64 memory model. Any object we load +// can potentially reference any of the ones we bake into the binary (and list) +// in RtsSymbols. Thus we'll need to be within +-4GB of those, +// stg_upd_frame_info is a good candidate as it's referenced often. +#define LINKER_LOAD_BASE ((void *) &stg_upd_frame_info) +#elif defined(x86_64_HOST_ARCH) && defined(mingw32_HOST_OS) +// On Windows (which now uses high-entropy ASLR by default) we need to ensure +// that we map code near the executable image. We use stg_upd_frame_info as a +// proxy for the image location. +#define LINKER_LOAD_BASE ((void *) &stg_upd_frame_info) +#elif defined(MAP_32BIT) || DEFAULT_LINKER_ALWAYS_PIC +// Try to use MAP_32BIT +#define LINKER_LOAD_BASE ((void *) 0x0) +#else +// A guess: 1 GB. +#define LINKER_LOAD_BASE ((void *) 0x40000000) +#endif + +/** Access modes for mprotectForLinker */ +typedef enum { + MEM_NO_ACCESS, + MEM_READ_ONLY, + MEM_READ_WRITE, + MEM_READ_EXECUTE, +} MemoryAccess; + +extern void *mmap_32bit_base; + +// Map read/write anonymous memory. +void *mmapAnonForLinker (size_t bytes); + +// Change protection of previous mapping memory. +void mprotectForLinker(void *start, size_t len, MemoryAccess mode); + +// Release a mapping. +void munmapForLinker (void *addr, size_t bytes, const char *caller); + +#if !defined(mingw32_HOST_OS) +// Map a file. +// +// Note that this not available on Windows since file mapping on Windows is +// sufficiently different to warrant its own interface. +void *mmapForLinker (size_t bytes, uint32_t prot, uint32_t flags, int fd, int offset); +#endif + +#include "EndPrivate.h" diff --git a/rts/linker/SymbolExtras.c b/rts/linker/SymbolExtras.c index 5c04e9b3a87..88192d43d9c 100644 --- a/rts/linker/SymbolExtras.c +++ b/rts/linker/SymbolExtras.c @@ -10,6 +10,7 @@ */ #include "LinkerInternals.h" +#include "linker/MMap.h" #if defined(NEED_SYMBOL_EXTRAS) #if !defined(x86_64_HOST_ARCH) || !defined(mingw32_HOST_OS) diff --git a/rts/linker/elf_got.c b/rts/linker/elf_got.c index ae75329295b..eefdae34c68 100644 --- a/rts/linker/elf_got.c +++ b/rts/linker/elf_got.c @@ -1,5 +1,6 @@ #include "Rts.h" #include "elf_got.h" +#include "linker/MMap.h" #include diff --git a/rts/rts.cabal.in b/rts/rts.cabal.in index 77f3ee989dd..0a06414d95f 100644 --- a/rts/rts.cabal.in +++ b/rts/rts.cabal.in @@ -533,6 +533,7 @@ library linker/Elf.c linker/LoadArchive.c linker/M32Alloc.c + linker/MMap.c linker/MachO.c linker/macho/plt.c linker/macho/plt_aarch64.c -- GitLab From 6deb4d0de5428e85446f2a6312dac9b23d69bca8 Mon Sep 17 00:00:00 2001 From: Ben Gamari Date: Mon, 7 Feb 2022 19:56:22 -0500 Subject: [PATCH 11/13] rts/linker/MMap: Use MemoryAccess in mmapForLinker (cherry picked from commit 4d3a306dce59649b303ac7aba56758aff3dee077) --- rts/Linker.c | 5 ++--- rts/linker/Elf.c | 2 +- rts/linker/MMap.c | 54 ++++++++++++++++++++++++++++++---------------- rts/linker/MMap.h | 3 ++- rts/linker/MachO.c | 2 +- 5 files changed, 42 insertions(+), 24 deletions(-) diff --git a/rts/Linker.c b/rts/Linker.c index 0d836a37a46..9754bf9f4f2 100644 --- a/rts/Linker.c +++ b/rts/Linker.c @@ -1353,10 +1353,9 @@ preloadObjectFile (pathchar *path) * See also the misalignment logic for darwin below. */ #if defined(darwin_HOST_OS) || defined(openbsd_HOST_OS) - image = mmapForLinker(fileSize, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0); + image = mmapForLinker(fileSize, MEM_READ_WRITE, MAP_PRIVATE, fd, 0); #else - image = mmapForLinker(fileSize, PROT_READ|PROT_WRITE|PROT_EXEC, - MAP_PRIVATE, fd, 0); + image = mmapForLinker(fileSize, MEM_READ_WRITE_EXECUTE, MAP_PRIVATE, fd, 0); #endif if (image == MAP_FAILED) { diff --git a/rts/linker/Elf.c b/rts/linker/Elf.c index 9956114264e..9ae8b43cc4d 100644 --- a/rts/linker/Elf.c +++ b/rts/linker/Elf.c @@ -653,7 +653,7 @@ mapObjectFileSection (int fd, Elf_Word offset, Elf_Word size, pageOffset = roundDownToPage(offset); pageSize = roundUpToPage(offset-pageOffset+size); - p = mmapForLinker(pageSize, PROT_READ | PROT_WRITE, 0, fd, pageOffset); + p = mmapForLinker(pageSize, MEM_READ_WRITE, 0, fd, pageOffset); if (p == NULL) return NULL; *mapped_size = pageSize; *mapped_offset = pageOffset; diff --git a/rts/linker/MMap.c b/rts/linker/MMap.c index c2edf78fd14..6226609604e 100644 --- a/rts/linker/MMap.c +++ b/rts/linker/MMap.c @@ -47,12 +47,28 @@ static const char *memoryAccessDescription(MemoryAccess mode) case MEM_READ_ONLY: return "read-only"; case MEM_READ_WRITE: return "read-write"; case MEM_READ_EXECUTE: return "read-execute"; + case MEM_READ_WRITE_EXECUTE: + return "read-write-execute"; default: barf("invalid MemoryAccess"); } } #if defined(mingw32_HOST_OS) +static DWORD +memoryAccessToProt(MemoryAccess access) +{ + switch (access) { + case MEM_NO_ACCESS: return PAGE_NOACCESS; + case MEM_READ_ONLY: return PAGE_READONLY; + case MEM_READ_WRITE: return PAGE_READWRITE; + case MEM_READ_EXECUTE: return PAGE_EXECUTE_READ; + case MEM_READ_WRITE_EXECUTE: + return PAGE_EXECUTE_READWRITE; + default: barf("invalid MemoryAccess"); + } +} + // // Returns NULL on failure. // @@ -82,14 +98,7 @@ mprotectForLinker(void *start, size_t len, MemoryAccess mode) if (len == 0) { return; } - DWORD prot; - switch (mode) { - case MEM_NO_ACCESS: prot = PAGE_NOACCESS; break; - case MEM_READ_ONLY: prot = PAGE_READONLY; break; - case MEM_READ_WRITE: prot = PAGE_READWRITE; break; - case MEM_READ_EXECUTE: prot = PAGE_EXECUTE_READ; break; - default: barf("invalid MemoryAccess"); - } + DWORD prot = memoryAccessToProt(mode); if (VirtualProtect(start, len, prot, &old) == 0) { sysErrorBelch("mprotectForLinker: failed to protect %zd bytes at %p as %s", @@ -99,11 +108,26 @@ mprotectForLinker(void *start, size_t len, MemoryAccess mode) } #elif RTS_LINKER_USE_MMAP + +static int +memoryAccessToProt(MemoryAccess access) +{ + switch (access) { + case MEM_NO_ACCESS: return 0; + case MEM_READ_ONLY: return PROT_READ; + case MEM_READ_WRITE: return PROT_READ | PROT_WRITE; + case MEM_READ_EXECUTE: return PROT_READ | PROT_EXEC; + case MEM_READ_WRITE_EXECUTE: + return PROT_READ | PROT_WRITE | PROT_EXEC; + default: barf("invalid MemoryAccess"); + } +} + // // Returns NULL on failure. // void * -mmapForLinker (size_t bytes, uint32_t prot, uint32_t flags, int fd, int offset) +mmapForLinker (size_t bytes, MemoryAccess access, uint32_t flags, int fd, int offset) { void *map_addr = NULL; void *result; @@ -112,6 +136,7 @@ mmapForLinker (size_t bytes, uint32_t prot, uint32_t flags, int fd, int offset) ? 0 : TRY_MAP_32BIT; static uint32_t fixed = 0; + int prot = memoryAccessToProt(access); IF_DEBUG(linker, debugBelch("mmapForLinker: start\n")); size = roundUpToPage(bytes); @@ -226,7 +251,7 @@ mmap_again: void * mmapAnonForLinker (size_t bytes) { - return mmapForLinker (bytes, PROT_READ|PROT_WRITE, MAP_ANONYMOUS, -1, 0); + return mmapForLinker (bytes, MEM_READ_WRITE, MAP_ANONYMOUS, -1, 0); } void munmapForLinker (void *addr, size_t bytes, const char *caller) @@ -273,14 +298,7 @@ void mprotectForLinker(void *start, size_t len, MemoryAccess mode) " bytes starting at %p as %s\n", (W_)len, start, memoryAccessDescription(mode))); - int prot; - switch (mode) { - case MEM_NO_ACCESS: prot = 0; break; - case MEM_READ_ONLY: prot = PROT_READ; break; - case MEM_READ_WRITE: prot = PROT_READ | PROT_WRITE; break; - case MEM_READ_EXECUTE: prot = PROT_READ | PROT_EXEC; break; - default: barf("invalid MemoryAccess"); - } + int prot = memoryAccessToProt(mode); if (mprotect(start, len, prot) == -1) { sysErrorBelch("mprotectForLinker: failed to protect %zd bytes at %p as %s", diff --git a/rts/linker/MMap.h b/rts/linker/MMap.h index ed0baa68998..9eebc3c4b20 100644 --- a/rts/linker/MMap.h +++ b/rts/linker/MMap.h @@ -55,6 +55,7 @@ typedef enum { MEM_READ_ONLY, MEM_READ_WRITE, MEM_READ_EXECUTE, + MEM_READ_WRITE_EXECUTE, } MemoryAccess; extern void *mmap_32bit_base; @@ -73,7 +74,7 @@ void munmapForLinker (void *addr, size_t bytes, const char *caller); // // Note that this not available on Windows since file mapping on Windows is // sufficiently different to warrant its own interface. -void *mmapForLinker (size_t bytes, uint32_t prot, uint32_t flags, int fd, int offset); +void *mmapForLinker (size_t bytes, MemoryAccess prot, uint32_t flags, int fd, int offset); #endif #include "EndPrivate.h" diff --git a/rts/linker/MachO.c b/rts/linker/MachO.c index d037c82f458..805731ba56c 100644 --- a/rts/linker/MachO.c +++ b/rts/linker/MachO.c @@ -1210,7 +1210,7 @@ ocGetNames_MachO(ObjectCode* oc) unsigned nstubs = numberOfStubsForSection(oc, sec_idx); unsigned stub_space = STUB_SIZE * nstubs; - void * mem = mmapForLinker(section->size+stub_space, PROT_READ | PROT_WRITE, MAP_ANON, -1, 0); + void * mem = mmapForLinker(section->size+stub_space, MEM_READ_WRITE, MAP_ANON, -1, 0); if( mem == MAP_FAILED ) { sysErrorBelch("failed to mmap allocated memory to load section %d. " -- GitLab From 7bdb5766550257b5346dad65d4f946dac64739ad Mon Sep 17 00:00:00 2001 From: Ben Gamari Date: Sat, 5 Feb 2022 23:12:07 -0500 Subject: [PATCH 12/13] rts/linker: Catch archives masquerading as object files Check the file's header to catch static archive bearing the `.o` extension, as may happen on Windows after the Clang refactoring. See #21068 --- rts/Linker.c | 11 ++++++++++- rts/LinkerInternals.h | 4 ++++ rts/linker/LoadArchive.c | 20 +++++++++++++++++++- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/rts/Linker.c b/rts/Linker.c index 9754bf9f4f2..19545fd3db5 100644 --- a/rts/Linker.c +++ b/rts/Linker.c @@ -1394,7 +1394,7 @@ preloadObjectFile (pathchar *path) image = stgMallocBytes(fileSize, "loadObj(image)"); -#endif +#endif /* !defined(darwin_HOST_OS) */ int n; n = fread ( image, 1, fileSize, f ); @@ -1439,6 +1439,15 @@ static HsInt loadObj_ (pathchar *path) return 1; // success } + if (isArchive(path)) { + if (loadArchive_(path)) { + return 1; // success + } else { + IF_DEBUG(linker, + debugBelch("tried and failed to load %" PATH_FMT " as an archive\n", path)); + } + } + ObjectCode *oc = preloadObjectFile(path); if (oc == NULL) return 0; diff --git a/rts/LinkerInternals.h b/rts/LinkerInternals.h index ccda39b0cf0..f2c36e057a0 100644 --- a/rts/LinkerInternals.h +++ b/rts/LinkerInternals.h @@ -407,6 +407,10 @@ pathchar* resolveSymbolAddr (pathchar* buffer, int size, SymbolAddr* symbol, uintptr_t* top); +/* defined in LoadArchive.c */ +bool isArchive (pathchar *path); +HsInt loadArchive_ (pathchar *path); + /************************************************* * Various bits of configuration *************************************************/ diff --git a/rts/linker/LoadArchive.c b/rts/linker/LoadArchive.c index f9282f197ff..9804db38728 100644 --- a/rts/linker/LoadArchive.c +++ b/rts/linker/LoadArchive.c @@ -241,7 +241,7 @@ lookupGNUArchiveIndex(int gnuFileIndexSize, char **fileName_, return true; } -static HsInt loadArchive_ (pathchar *path) +HsInt loadArchive_ (pathchar *path) { char *image = NULL; HsInt retcode = 0; @@ -631,3 +631,21 @@ HsInt loadArchive (pathchar *path) RELEASE_LOCK(&linker_mutex); return r; } + +bool isArchive (pathchar *path) +{ + static const char ARCHIVE_HEADER[] = "!\n"; + char buffer[10]; + FILE *f = pathopen(path, WSTR("rb")); + if (f == NULL) { + return false; + } + + size_t ret = fread(buffer, 1, sizeof(buffer), f); + if (ret < sizeof(buffer)) { + return false; + } + fclose(f); + return strncmp(ARCHIVE_HEADER, buffer, sizeof(ARCHIVE_HEADER)-1) == 0; +} + -- GitLab From 69c02cbfaf8686ac7811f472aacb87415e29ce1f Mon Sep 17 00:00:00 2001 From: Ben Gamari Date: Mon, 7 Feb 2022 20:15:15 -0500 Subject: [PATCH 13/13] linker: Don't use MAP_FIXED As noted in #21057, we really shouldn't be using MAP_FIXED. I would much rather have the process crash with a "failed to map" error than randomly overwrite existing mappings. Closes #21057. (cherry picked from commit 1db4f1fe7603c338ead0ac7e1ecfd0d8354d37bf) --- rts/linker/MMap.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/rts/linker/MMap.c b/rts/linker/MMap.c index 6226609604e..941dc86452c 100644 --- a/rts/linker/MMap.c +++ b/rts/linker/MMap.c @@ -177,13 +177,10 @@ mmap_again: if ((W_)result > 0x80000000) { // oops, we were given memory over 2Gb munmap(result,size); -#if defined(freebsd_HOST_OS) || \ - defined(kfreebsdgnu_HOST_OS) || \ - defined(dragonfly_HOST_OS) - // Some platforms require MAP_FIXED. This is normally - // a bad idea, because MAP_FIXED will overwrite - // existing mappings. - fixed = MAP_FIXED; +#if defined(MAP_TRYFIXED) + // Some platforms require MAP_FIXED. We use MAP_TRYFIXED since + // MAP_FIXED will overwrite existing mappings. + fixed = MAP_TRYFIXED; goto mmap_again; #else reportMemoryMap(); -- GitLab