diff options
author | Ray Shih <rayshih@fb.com> | 2020-07-09 06:48:55 -0700 |
---|---|---|
committer | Marge Bot <ben+marge-bot@smart-cactus.org> | 2020-11-11 03:20:35 -0500 |
commit | 2782487f5f6ad9df4dc8725226a47f07fec77f9f (patch) | |
tree | c0605f2cfd8228586eb108598d021ce0c40d9976 /rts/Linker.c | |
parent | c34a4b98b1f09ea3096d39a839a86f2d7185c796 (diff) | |
download | haskell-2782487f5f6ad9df4dc8725226a47f07fec77f9f.tar.gz |
Add loadNativeObj and unloadNativeObj
(This change is originally written by niteria)
This adds two functions:
* `loadNativeObj`
* `unloadNativeObj`
and implements them for Linux.
They are useful if you want to load a shared object with Haskell code
using the system linker and have GHC call dlclose() after the
code is no longer referenced from the heap.
Using the system linker allows you to load the shared object
above outside the low-mem region. It also loads the DWARF sections
in a way that `perf` understands.
`dl_iterate_phdr` is what makes this implementation Linux specific.
Diffstat (limited to 'rts/Linker.c')
-rw-r--r-- | rts/Linker.c | 196 |
1 files changed, 194 insertions, 2 deletions
diff --git a/rts/Linker.c b/rts/Linker.c index f6a38e08dd..96d25fb741 100644 --- a/rts/Linker.c +++ b/rts/Linker.c @@ -64,6 +64,7 @@ # include "linker/Elf.h" # include <regex.h> // regex is already used by dlopen() so this is OK // to use here without requiring an additional lib +# include <link.h> #elif defined(OBJFORMAT_PEi386) # include "linker/PEi386.h" # include <windows.h> @@ -170,6 +171,8 @@ Mutex linker_mutex; /* Generic wrapper function to try and Resolve and RunInit oc files */ int ocTryLoad( ObjectCode* oc ); +static void freeNativeCode_ELF (ObjectCode *nc); + /* 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). @@ -1246,6 +1249,16 @@ freePreloadObjectFile (ObjectCode *oc) */ void freeObjectCode (ObjectCode *oc) { + if (oc->type == DYNAMIC_OBJECT) { +#if defined(OBJFORMAT_ELF) + ACQUIRE_LOCK(&dl_mutex); + freeNativeCode_ELF(oc); + RELEASE_LOCK(&dl_mutex); +#else + barf("freeObjectCode: This shouldn't happen"); +#endif + } + freePreloadObjectFile(oc); if (oc->symbols != NULL) { @@ -1328,7 +1341,7 @@ void freeObjectCode (ObjectCode *oc) } ObjectCode* -mkOc( pathchar *path, char *image, int imageSize, +mkOc( ObjectType type, pathchar *path, char *image, int imageSize, bool mapped, pathchar *archiveMemberName, int misalignment ) { ObjectCode* oc; @@ -1336,6 +1349,7 @@ mkOc( pathchar *path, char *image, int imageSize, oc = stgMallocBytes(sizeof(ObjectCode), "mkOc(oc)"); oc->info = NULL; + oc->type = type; # if defined(OBJFORMAT_ELF) oc->formatName = "ELF"; @@ -1396,6 +1410,10 @@ mkOc( pathchar *path, char *image, int imageSize, oc->rx_m32 = m32_allocator_new(true); #endif + oc->l_addr = NULL; + oc->nc_ranges = NULL; + oc->dlopen_handle = NULL; + IF_DEBUG(linker, debugBelch("mkOc: done\n")); return oc; } @@ -1524,7 +1542,7 @@ preloadObjectFile (pathchar *path) IF_DEBUG(linker, debugBelch("loadObj: preloaded image at %p\n", (void *) image)); /* FIXME (AP): =mapped= parameter unconditionally set to true */ - oc = mkOc(path, image, fileSize, true, NULL, misalignment); + oc = mkOc(STATIC_OBJECT, path, image, fileSize, true, NULL, misalignment); #if defined(OBJFORMAT_MACHO) if (ocVerifyImage_MachO( oc )) @@ -1943,6 +1961,180 @@ addSection (Section *s, SectionKind kind, SectionAlloc alloc, size, kind )); } + +# if defined(OBJFORMAT_ELF) +static int loadNativeObjCb_(struct dl_phdr_info *info, + size_t _size GNUC3_ATTRIBUTE(__unused__), void *data) { + ObjectCode* nc = (ObjectCode*) data; + + // This logic mimicks _dl_addr_inside_object from glibc + // For reference: + // int + // internal_function + // _dl_addr_inside_object (struct link_map *l, const ElfW(Addr) addr) + // { + // int n = l->l_phnum; + // const ElfW(Addr) reladdr = addr - l->l_addr; + // + // while (--n >= 0) + // if (l->l_phdr[n].p_type == PT_LOAD + // && reladdr - l->l_phdr[n].p_vaddr >= 0 + // && reladdr - l->l_phdr[n].p_vaddr < l->l_phdr[n].p_memsz) + // return 1; + // return 0; + // } + + if ((void*) info->dlpi_addr == nc->l_addr) { + int n = info->dlpi_phnum; + while (--n >= 0) { + if (info->dlpi_phdr[n].p_type == PT_LOAD) { + NativeCodeRange* ncr = + stgMallocBytes(sizeof(NativeCodeRange), "loadNativeObjCb_"); + ncr->start = (void*) ((char*) nc->l_addr + info->dlpi_phdr[n].p_vaddr); + ncr->end = (void*) ((char*) ncr->start + info->dlpi_phdr[n].p_memsz); + + ncr->next = nc->nc_ranges; + nc->nc_ranges = ncr; + } + } + } + return 0; +} + +static void copyErrmsg(char** errmsg_dest, char* errmsg) { + if (errmsg == NULL) errmsg = "loadNativeObj_ELF: unknown error"; + *errmsg_dest = stgMallocBytes(strlen(errmsg)+1, "loadNativeObj_ELF"); + strcpy(*errmsg_dest, errmsg); +} + +// need dl_mutex +static void freeNativeCode_ELF (ObjectCode *nc) { + dlclose(nc->dlopen_handle); + + NativeCodeRange *ncr = nc->nc_ranges; + while (ncr) { + NativeCodeRange* last_ncr = ncr; + ncr = ncr->next; + stgFree(last_ncr); + } +} + +static void * loadNativeObj_ELF (pathchar *path, char **errmsg) +{ + ObjectCode* nc; + void *hdl, *retval; + + IF_DEBUG(linker, debugBelch("loadNativeObj_ELF %" PATH_FMT "\n", path)); + + retval = NULL; + ACQUIRE_LOCK(&dl_mutex); + + nc = mkOc(DYNAMIC_OBJECT, path, NULL, 0, true, NULL, 0); + + foreignExportsLoadingObject(nc); + hdl = dlopen(path, RTLD_NOW|RTLD_LOCAL); + foreignExportsFinishedLoadingObject(); + if (hdl == NULL) { + /* dlopen failed; save the message in errmsg */ + copyErrmsg(errmsg, dlerror()); + goto dlopen_fail; + } + + struct link_map *map; + if (dlinfo(hdl, RTLD_DI_LINKMAP, &map) == -1) { + /* dlinfo failed; save the message in errmsg */ + copyErrmsg(errmsg, dlerror()); + goto dlinfo_fail; + } + + nc->l_addr = (void*) map->l_addr; + nc->dlopen_handle = hdl; + hdl = NULL; // pass handle ownership to nc + + dl_iterate_phdr(loadNativeObjCb_, nc); + if (!nc->nc_ranges) { + copyErrmsg(errmsg, "dl_iterate_phdr failed to find obj"); + goto dl_iterate_phdr_fail; + } + + insertOCSectionIndices(nc); + + nc->next_loaded_object = loaded_objects; + loaded_objects = nc; + + retval = nc->dlopen_handle; + goto success; + +dl_iterate_phdr_fail: + // already have dl_mutex + freeNativeCode_ELF(nc); +dlinfo_fail: + if (hdl) dlclose(hdl); +dlopen_fail: +success: + + RELEASE_LOCK(&dl_mutex); + IF_DEBUG(linker, debugBelch("loadNativeObj_ELF result=%p\n", retval)); + + return retval; +} + +# endif + +#define UNUSED(x) (void)(x) + +void * loadNativeObj (pathchar *path, char **errmsg) +{ +#if defined(OBJFORMAT_ELF) + ACQUIRE_LOCK(&linker_mutex); + void *r = loadNativeObj_ELF(path, errmsg); + RELEASE_LOCK(&linker_mutex); + return r; +#else + UNUSED(path); + UNUSED(errmsg); + barf("loadNativeObj: not implemented on this platform"); +#endif +} + +HsInt unloadNativeObj (void *handle) +{ + bool unloadedAnyObj = false; + + IF_DEBUG(linker, debugBelch("unloadNativeObj: %p\n", handle)); + + ObjectCode *prev = NULL, *next; + for (ObjectCode *nc = loaded_objects; nc; nc = next) { + next = nc->next_loaded_object; // we might move nc + + if (nc->type == DYNAMIC_OBJECT && nc->dlopen_handle == handle) { + nc->status = OBJECT_UNLOADED; + n_unloaded_objects += 1; + + // dynamic objects have no symbols + ASSERT(nc->symbols == NULL); + freeOcStablePtrs(nc); + + // Remove object code from root set + if (prev == NULL) { + loaded_objects = nc->next_loaded_object; + } else { + prev->next_loaded_object = nc->next_loaded_object; + } + unloadedAnyObj = true; + } else { + prev = nc; + } + } + + if (unloadedAnyObj) { + return 1; + } else { + errorBelch("unloadObjNativeObj_ELF: can't find `%p' to unload", handle); + return 0; + } +} + /* ----------------------------------------------------------------------------- * Segment management */ |