Temporary .so's created during dynamic linking in runtime need to be really unique
Summary
In the dyn
RTS, when runGhc
is invoked multiple times in the same process, sometimes getHValue
will fail with the following message:
load: ^^ Could not load 'B_x_closure', dependency unresolved. See top entry above.
load:
ByteCodeLink.lookupCE
During interactive linking, GHCi couldn't find the following symbol:
B_x_closure
This may be due to you not asking GHCi to load extra object files,
archives or DLLs needed by your current session. Restart GHCi, specifying
the missing library using the -L/path/to/object/dir and -lmissinglibname
flags, or simply by naming the relevant files on the GHCi command line.
Alternatively, this link failure might indicate a bug in GHCi.
If you suspect the latter, please report this as a GHC bug:
https://www.haskell.org/ghc/reportabug
Actually the problem comes from linkModule
, because the temporary .so
that was created doesn't end up loaded into the process, hence symbol resolution fails.
Steps to reproduce
import Control.Monad
import Data.Maybe
import GHC
import GHC.Paths
import GhcMonad
import DynFlags
import Linker
import Module
-- If the host RTS is dyn, we need to compile dyn too
copyHostDyn :: DynFlags -> DynFlags
copyHostDyn = if dynamicGhc then updateWays . addWay' WayDyn else id
main :: IO ()
main = do
forM_ ["A", "B"] $ \mod -> do
writeFile (mod <> ".hs") $ "module " <> mod <> " where x = x"
runGhc (Just GHC.Paths.libdir) $ do
setSessionDynFlags . (\df -> df{verbosity=3}) . copyHostDyn =<< getSessionDynFlags
-- compile $mod
target <- guessTarget (mod <> ".hs") Nothing
setTargets [target]
load LoadAllTargets
-- dynamically load $mod
withSession $ \env -> liftIO $ do
linkModule env $ mkModule mainUnitId (mkModuleName mod)
-- take a look at what's mapped into the process
liftIO $ putStrLn =<< readFile "/proc/self/maps"
-- try to lookup $mod.x
mi <- getModuleInfo $ mkModule mainUnitId (mkModuleName mod)
withSession $ \env -> liftIO $ do
forM_ (modInfoExports $ fromJust mi) $ \name -> do
getHValue env name
Compile the program with -dynamic
and run.
The complete output of the program is attached: output.txt
The A.hs
loads fine, but B.hs
fails with the message above.
Notably the following are important:
-- When loading A:
gcc ... -o /tmp/ghc119539_0/libghc_4.so A.o ...
-- After loading A:
7fa4b702b000-7fa4b702c000 r-xp 00000000 00:24 9854 /tmp/ghc119539_0/libghc_4.so
7fa4b702c000-7fa4b702d000 r--p 00000000 00:24 9854 /tmp/ghc119539_0/libghc_4.so
7fa4b702d000-7fa4b702e000 rw-p 00001000 00:24 9854 /tmp/ghc119539_0/libghc_4.so
-- When loading B:
gcc ... -o /tmp/ghc119539_0/libghc_4.so B.o ...
-- After loading B:
7fa4b702b000-7fa4b702c000 r-xp 00000000 00:24 9854 /tmp/ghc119539_0/libghc_4.so (deleted)
7fa4b702c000-7fa4b702d000 r--p 00000000 00:24 9854 /tmp/ghc119539_0/libghc_4.so (deleted)
7fa4b702d000-7fa4b702e000 rw-p 00001000 00:24 9854 /tmp/ghc119539_0/libghc_4.so (deleted)
The same pathname is used for two temporary .so
's, and glibc libdl doesn't like that.
On the second pass, the only loaded .so
is the old one from A
(its inode number is the same, but it is now deleted). The new .so
from B
is nowhere to be seen.
Expected behavior
The temporary .so
's have different names, and thus are both loaded, and the resolution of B.x
succeeds.
Environment
- GHC version used: 8.10.4
- Linux x86_64 with glibc. I believe the issue is relevant to all systems using glibc.
The same issue happens on GHC HEAD with a translated version of the program:
import Control.Monad
import Data.Maybe
import GHC
import GHC.Paths
import GHC.Driver.Session
import GHC.Driver.Monad
import GHC.Driver.Env
import GHC.Platform
import GHC.Platform.Ways
import GHC.Linker.Loader
import GHC.Unit.Types
-- If the host RTS is dyn, we need to compile dyn too
copyHostDyn :: DynFlags -> DynFlags
copyHostDyn df = if hostIsDynamic then foldl gopt_set df { targetWays_ = addWay WayDyn $ targetWays_ df } (wayGeneralFlags genericPlatform WayDyn) else df -- ??? why is this so hard?
main :: IO ()
main = do
forM_ ["A", "B"] $ \mod -> do
writeFile (mod <> ".hs") $ "module " <> mod <> " where x = x"
runGhc (Just GHC.Paths.libdir) $ do
setSessionDynFlags . copyHostDyn =<< getSessionDynFlags
-- compile $mod
target <- guessTarget (mod <> ".hs") Nothing Nothing
setTargets [target]
load LoadAllTargets
-- dynamically load $mod
withSession $ \env -> liftIO $ do
loadModule (hscInterp env) env Nothing $ mkModule mainUnit (mkModuleName mod)
-- take a look at what's mapped into the process
liftIO $ putStrLn =<< readFile "/proc/self/maps"
-- try to lookup $mod.x
mi <- getModuleInfo $ mkModule mainUnit (mkModuleName mod)
withSession $ \env -> liftIO $ do
forM_ (modInfoExports $ fromJust mi) $ \name -> do
loadName (hscInterp env) env Nothing name