|
4 | 4 |
|
5 | 5 | #include <HsFFI.h> |
6 | 6 |
|
| 7 | +#if !defined(mingw32_HOST_OS) |
| 8 | +#include <dlfcn.h> |
| 9 | +#endif |
| 10 | + |
| 11 | +/* |
| 12 | + * Note [Promoting ghc-internal to RTLD_GLOBAL] |
| 13 | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 14 | + * When ghc-iserv loads user code that depends on ghc-internal, the dynamic |
| 15 | + * linker may load a second copy of libHSghc-internal.so if the original copy |
| 16 | + * was loaded with RTLD_LOCAL (the default). |
| 17 | + * |
| 18 | + * This causes heap corruption because: |
| 19 | + * 1. The first copy's init_ghc_hs_iface() initializes ghc_hs_iface in the RTS |
| 20 | + * 2. User code compiled against the second copy creates closures with info |
| 21 | + * tables from the second copy |
| 22 | + * 3. The GC uses ghc_hs_iface (pointing to first copy's info tables) to |
| 23 | + * validate closures |
| 24 | + * 4. The mismatch causes "strange closure type" errors during garbage collection |
| 25 | + * |
| 26 | + * To fix this, we use dladdr to find the shared library containing a known |
| 27 | + * symbol from ghc-internal (init_ghc_hs_iface), then use dlopen with |
| 28 | + * RTLD_NOLOAD | RTLD_GLOBAL to promote it to global scope. This ensures |
| 29 | + * that when user code's dependencies are resolved, the dynamic linker uses |
| 30 | + * the already-loaded copy instead of loading a fresh one. |
| 31 | + * |
| 32 | + * Note: RTLD_NOLOAD is a GNU extension (and available on macOS since 10.3) |
| 33 | + * that returns a handle to an already-loaded library without incrementing |
| 34 | + * its reference count. Combined with RTLD_GLOBAL, it changes the library's |
| 35 | + * visibility to global. |
| 36 | + * |
| 37 | + * See also: |
| 38 | + * - Note [RTLD_LOCAL] in rts/Linker.c |
| 39 | + * - Note [RTS/ghc-internal interface] in rts/RtsToHsIface.c |
| 40 | + * - Note [ghc-iserv and dynamic symbol export] in utils/ghc-iserv/ghc-iserv.cabal.in |
| 41 | + */ |
| 42 | +#if !defined(mingw32_HOST_OS) && defined(RTLD_NOLOAD) |
| 43 | +static void promote_ghc_internal_to_global(void) |
| 44 | +{ |
| 45 | + /* |
| 46 | + * Find ghc-internal's shared library by looking up init_ghc_hs_iface, |
| 47 | + * which is a symbol we know exists in ghc-internal. Then use dladdr |
| 48 | + * to get the library path, and reopen it with RTLD_GLOBAL. |
| 49 | + */ |
| 50 | + |
| 51 | + /* init_ghc_hs_iface is defined in ghc-internal's cbits/RtsIface.c */ |
| 52 | + extern void init_ghc_hs_iface(void); |
| 53 | + |
| 54 | + Dl_info info; |
| 55 | + if (dladdr((void*)&init_ghc_hs_iface, &info) == 0) { |
| 56 | + /* dladdr failed - symbol might be in the executable itself (static link). |
| 57 | + * In that case, symbols are already global via -rdynamic. */ |
| 58 | + return; |
| 59 | + } |
| 60 | + |
| 61 | + if (info.dli_fname == NULL) { |
| 62 | + /* No filename available - shouldn't happen but handle gracefully */ |
| 63 | + return; |
| 64 | + } |
| 65 | + |
| 66 | + /* Reopen the library with RTLD_GLOBAL to promote its symbols. |
| 67 | + * RTLD_NOLOAD ensures we don't load a new copy - we just change |
| 68 | + * the visibility of the already-loaded library. */ |
| 69 | + void *handle = dlopen(info.dli_fname, RTLD_NOW | RTLD_NOLOAD | RTLD_GLOBAL); |
| 70 | + if (handle != NULL) { |
| 71 | + /* Successfully promoted to global scope. |
| 72 | + * Don't dlclose - we want the library to stay loaded and global. */ |
| 73 | +#if defined(DEBUG) |
| 74 | + fprintf(stderr, "ghc-iserv: promoted %s to RTLD_GLOBAL\n", info.dli_fname); |
| 75 | +#endif |
| 76 | + } |
| 77 | + /* If dlopen failed, that's OK - might be statically linked or |
| 78 | + * the -rdynamic/-flat_namespace linker flags should help. */ |
| 79 | +} |
| 80 | +#endif /* !mingw32_HOST_OS && RTLD_NOLOAD */ |
| 81 | + |
7 | 82 | int main (int argc, char *argv[]) |
8 | 83 | { |
9 | 84 | RtsConfig conf = defaultRtsConfig; |
10 | 85 |
|
| 86 | +#if !defined(mingw32_HOST_OS) && defined(RTLD_NOLOAD) |
| 87 | + /* Promote ghc-internal to RTLD_GLOBAL before running any Haskell code. |
| 88 | + * See Note [Promoting ghc-internal to RTLD_GLOBAL] */ |
| 89 | + promote_ghc_internal_to_global(); |
| 90 | +#endif |
| 91 | + |
11 | 92 | // We never know what symbols GHC will look up in the future, so |
12 | 93 | // we must retain CAFs for running interpreted code. |
13 | 94 | conf.keep_cafs = 1; |
|
0 commit comments