如何正确链接用Haskell编写的目标文件?

lef*_*out 19 c++ linker haskell ffi ghc

大致遵循本教程,我设法让这个玩具项目工作.它从C++程序调用Haskell函数.

  • Foo.hs

    {-# LANGUAGE ForeignFunctionInterface #-}
    
    module Foo where
    
    foreign export ccall foo :: Int -> Int -> IO Int
    
    foo :: Int -> Int -> IO Int
    foo n m = return . sum $ f n ++ f m
    
    f :: Int -> [Int]
    f 0 = []
    f n = n : f (n-1)
    
    Run Code Online (Sandbox Code Playgroud)
  • bar.c++

    #include "HsFFI.h"
    #include FOO       // Haskell module (path defined in build script)
    
    #include <iostream>
    
    int main(int argc, char *argv[]) {
      hs_init(&argc, &argv);
    
      std::cout << foo(37, 19) << "\n";
    
      hs_exit();
      return 0;
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • call-haskell-from-cxx.cabal

    name:                call-haskell-from-cxx
    version:             0.1.0.0
    build-type:          Simple
    cabal-version:       >=1.10
    
    executable foo.so
      main-is:          Foo.hs   
      build-depends:       base >=4.10 && <4.11
      ghc-options:         -shared -fPIC -dynamic
      extra-libraries:     HSrts-ghc8.2.1
      default-language:    Haskell2010
    
    Run Code Online (Sandbox Code Playgroud)
  • 构建脚本

    #!/bin/bash
    
    hs_lib="foo.so"
    hs_obj="dist/build/$hs_lib/$hs_lib"
    
    ghc_version="8.2.1"                          # May need to be tweaked,
    ghc_libdir="/usr/local/lib/ghc-$ghc_version" # depending on system setup.
    
    set -x
    
    cabal build
    
    g++ -I "$ghc_libdir/include" -D"FOO=\"${hs_obj}-tmp/Foo_stub.h\"" -c bar.c++ -o test.o
    g++ test.o "$hs_obj" \
       -L "$ghc_libdir/rts" "-lHSrts-ghc$ghc_version" \
       -o test
    
    env LD_LIBRARY_PATH="dist/build/$hs_lib:$ghc_libdir/rts:$LD_LIBRARY_PATH" \
      ./test
    
    Run Code Online (Sandbox Code Playgroud)

这工作(Ubuntu 16.04,GCC 5.4.0),打印893- 但它不是很健壮,即,如果我删除 Haskell函数的实际调用,即std::cout << foo(37, 19) << "\n";行,那么它在链接步骤失败,错误信息

/usr/local/lib/ghc-8.2.1/rts/libHSrts-ghc8.2.1.so: undefined reference to `base_GHCziTopHandler_flushStdHandles_closure'
/usr/local/lib/ghc-8.2.1/rts/libHSrts-ghc8.2.1.so: undefined reference to `base_GHCziStable_StablePtr_con_info'
/usr/local/lib/ghc-8.2.1/rts/libHSrts-ghc8.2.1.so: undefined reference to `base_GHCziPtr_FunPtr_con_info'
/usr/local/lib/ghc-8.2.1/rts/libHSrts-ghc8.2.1.so: undefined reference to `base_GHCziWord_W8zh_con_info'
/usr/local/lib/ghc-8.2.1/rts/libHSrts-ghc8.2.1.so: undefined reference to `base_GHCziIOziException_cannotCompactPinned_closure'
...
Run Code Online (Sandbox Code Playgroud)

显然,包含Haskell项目需要额外的库文件.我如何明确依赖所有必要的东西,以避免这种脆弱?


foo包含调用时的构建脚本的输出,ldd以及最终的可执行文件:

++ cabal build
Preprocessing executable 'foo.so' for call-haskell-from-C-0.1.0.0..
Building executable 'foo.so' for call-haskell-from-C-0.1.0.0..
Linking a.out ...
Linking dist/build/foo.so/foo.so ...
++ g++ -I /usr/local/lib/ghc-8.2.1/include '-DFOO="dist/build/foo.so/foo.so-tmp/Foo_stub.h"' -c bar.c++ -o test.o
++ g++ test.o dist/build/foo.so/foo.so -L /usr/local/lib/ghc-8.2.1/rts -lHSrts-ghc8.2.1 -o test
++ env LD_LIBRARY_PATH=dist/build/foo.so:/usr/local/lib/ghc-8.2.1/rts: sh -c 'ldd ./test; ./test'
    linux-vdso.so.1 =>  (0x00007fff23105000)
    foo.so => dist/build/foo.so/foo.so (0x00007fdfc5360000)
    libHSrts-ghc8.2.1.so => /usr/local/lib/ghc-8.2.1/rts/libHSrts-ghc8.2.1.so (0x00007fdfc52f8000)
    libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fdfc4dbe000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fdfc49f4000)
    libHSbase-4.10.0.0-ghc8.2.1.so => /usr/local/lib/ghc-8.2.1/base-4.10.0.0/libHSbase-4.10.0.0-ghc8.2.1.so (0x00007fdfc4020000)
    libHSinteger-gmp-1.0.1.0-ghc8.2.1.so => /usr/local/lib/ghc-8.2.1/integer-gmp-1.0.1.0/libHSinteger-gmp-1.0.1.0-ghc8.2.1.so (0x00007fdfc528b000)
    libHSghc-prim-0.5.1.0-ghc8.2.1.so => /usr/local/lib/ghc-8.2.1/ghc-prim-0.5.1.0/libHSghc-prim-0.5.1.0-ghc8.2.1.so (0x00007fdfc3b80000)
    libgmp.so.10 => /usr/lib/x86_64-linux-gnu/libgmp.so.10 (0x00007fdfc3900000)
    libffi.so.6 => /usr/local/lib/ghc-8.2.1/rts/libffi.so.6 (0x00007fdfc36f3000)
    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fdfc33ea000)
    librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007fdfc31e2000)
    libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fdfc2fde000)
    libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fdfc2dc1000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fdfc5140000)
    libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fdfc2bab000)
Run Code Online (Sandbox Code Playgroud)

Yur*_*ras 2

通常ghc将可执行文件与-Wl,--no-as-needed选项链接起来,您也应该使用它。(您可以检查ghc链接如何可执行,例如使用cabal build --ghc-options=-v3。)

您可以在这里找到更多详细信息。我接下来的理解是:foo.so需要libHSbase-4.10.0.0-ghc8.2.1.so在运行时根据需要加载,即当我们需要其中的符号时(检查readelf -a dist/build/foo.so/foo.so | grep NEEDED)。因此,如果您不调用foo,则base.so不会加载。但 ghc 需要加载所有库(我不知道为什么)。该--no-as-needed选项强制加载所有库。

请注意,--no-as-needed选项与位置相关,因此请将其放在共享库之前。