如何正确地将link目标文件写入Haskell?
How to properly link object files written in Haskell?
大致按照 this tutorial,我设法让这个玩具项目开始工作。它从 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)
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;
}
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
构建脚本
#!/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
这有效(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'
...
显然,包含 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)
通常 ghc
使用 -Wl,--no-as-needed
选项链接可执行文件,您也应该使用它。 (您可以检查 ghc
如何链接可执行文件,例如使用 cabal build --ghc-options=-v3
。)
您可以找到更多详细信息 here。接下来我的理解是: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
选项是依赖于位置的,所以把它放在共享库之前。
这个答案解释了在 linkage 期间发生的事情,为什么 -Wl,--no-as-needed
的解决方案有效,以及应该做些什么来获得更稳健的方法。
简而言之:-lHSrts-ghcXXX.so
取决于 libHSbaseXXX.so
、libHSinteger-gmpXXX.so
和 libHSghc-primXXX.so
,必须在 link 期间提供给 linker =]年龄。
这里提出的解决方案依赖于大量的手动工作,而且可扩展性不是很好。但是,我对 cabal
的了解还不够多,无法告诉您如何将其自动化,但我希望您能完成最后一步。
或者您可能会使用 -Wl,--no-as-needed
解决方案,因为您知道幕后发生的事情。
让我们从不调用 foo
的情况下逐步完成版本的 linking 过程开始,以一种稍微简化的方式(here 是 Eli Bendersky 的一篇很棒的文章,即使它是关于静态 linkage):
linker 维护着 table 个符号,必须为所有符号找到 definitions/machine-code。让我们简化并假设,一开始它在table中只有符号main
并且这个符号的定义是未知的。
符号 main
的定义是在目标文件 test.o
中找到的。但是,函数 main
使用函数 hs_init
和 hs_exit
。因此我们找到了main
的定义,但是除非我们知道hs_init
和hs_exit
的定义,否则它不起作用。所以现在我们必须寻找它们的定义。
在下一步中,linker 查看 foo.so
,但是 foo.so
没有定义我们感兴趣的任何符号(foo
未使用!)并且 linker 只是跳过 foo.so
并且永远不会回头看。
link人看着-lHSrts-ghcXXX.so
。它在那里找到 hs_init
和 hs_exit
的定义。因此,共享库的全部内容都被使用,但它需要定义诸如 base_GHCziTopHandler_flushStdHandles_closure
之类的符号。这意味着 linker 开始寻找这些符号的定义。
但是命令行中没有更多的库,因此 linker 没有什么可看的,linkage fails/is 没有成功,因为缺少某些符号的定义。
使用foo
的情况有什么不同?在第 2 步之后,不仅要 hs_init
和 hs_exit
,还要 foo
,在 foo.so
中找到。所以必须包含foo.so
。
由于库 foo.so
的构建方式,包含以下信息:
>>> readelf -d dist/build/foo.so/foo.so | grep NEEDED
0x0000000000000001 (NEEDED) Shared library: [libHSrts-ghc7.10.3.so]
0x0000000000000001 (NEEDED) Shared library: [libHSbase-4.8.2.0-HQfYBxpPvuw8OunzQu6JGM-ghc7.10.3.so]
0x0000000000000001 (NEEDED) Shared library: [libHSinteger-gmp-1.0.0.0-2aU3IZNMF9a7mQ0OzsZ0dS-ghc7.10.3.so]
0x0000000000000001 (NEEDED) Shared library: [libHSghc-prim-0.4.0.0-8TmvWUcS1U1IKHT0levwg3-ghc7.10.3.so]
0x0000000000000001 (NEEDED) Shared library: [libgmp.so.10]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
>>> readelf -d dist/build/foo.so/foo.so | grep RPATH
0x000000000000000f (RPATH) Library rpath: [
/usr/lib/ghc/base_HQfYBxpPvuw8OunzQu6JGM:
/usr/lib/ghc/rts:
/usr/lib/ghc/ghcpr_8TmvWUcS1U1IKHT0levwg3:
/usr/lib/ghc/integ_2aU3IZNMF9a7mQ0OzsZ0dS]
根据此信息,linker 知道需要哪些共享库(NEEDED
-flag)以及它们在系统中的什么位置(RPATH
)。这些库是 found/opened/processed(即标记为需要),因此所有必要的定义都存在。
您可以通过添加
来了解整个过程
g++ ...
-Wl,--trace-symbol=base_GHCziTopHandler_flushStdHandles_closure \
-Wl,--verbose \
-o test
进入link年龄阶段。
如果我们按照@Yuras 的建议强制将 foo.so
通过 -Wl,--no-as-needed
包含到生成的 executable 中,也会发生同样的事情。
这个分析的结果是什么?
我们应该在命令行上提供所需的库(在 -lHSrts-ghcXXX.so
之后),而不是依赖于通过其他共享库偶然添加它们。显然,有些神秘的名称仅对我的安装有效:
g++ ...
-L/usr/lib/ghc/base_HQfYBxpPvuw8OunzQu6JGM -lHSbase-4.8.2.0-HQfYBxpPvuw8OunzQu6JGM-ghc7.10.3 \
-L/usr/lib/ghc/integ_2aU3IZNMF9a7mQ0OzsZ0dS -lHSinteger-gmp-1.0.0.0-2aU3IZNMF9a7mQ0OzsZ0dS-ghc7.10.3 \
-L/usr/lib/ghc/ghcpr_8TmvWUcS1U1IKHT0levwg3 -lHSghc-prim-0.4.0.0-8TmvWUcS1U1IKHT0levwg3-ghc7.10.3 \
...
-o test
现在构建,但在 运行 时不加载(毕竟正确的 rpath
仅在 foo.so
中设置,但 foo.so
不是用过的)。要修复它,我们可以扩展 LD_LIBRARY_PATH
或添加 -rpath
link-命令行:
g++ ...
-L... -lHSbase-... -Wl,-rpath,/usr/lib/ghc/base_HQfYBxpPvuw8OunzQu6JGM \
-L... -lHSinteger-gmp-... -Wl,-rpath,/usr/lib/ghc/integ_2aU3IZNMF9a7mQ0OzsZ0dS \
-L... -lHSghc-prim-... -Wl,-rpath,/usr/lib/ghc/ghcpr_8TmvWUcS1U1IKHT0levwg3 \
...
-o test
必须有一个实用程序来自动获取路径和库名称(cabal 似乎在构建 foo.so
时这样做),但我不知道该怎么做,因为我没有使用 haskell/cabal.
大致按照 this tutorial,我设法让这个玩具项目开始工作。它从 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)
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; }
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
构建脚本
#!/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
这有效(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'
...
显然,包含 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)
通常 ghc
使用 -Wl,--no-as-needed
选项链接可执行文件,您也应该使用它。 (您可以检查 ghc
如何链接可执行文件,例如使用 cabal build --ghc-options=-v3
。)
您可以找到更多详细信息 here。接下来我的理解是: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
选项是依赖于位置的,所以把它放在共享库之前。
这个答案解释了在 linkage 期间发生的事情,为什么 -Wl,--no-as-needed
的解决方案有效,以及应该做些什么来获得更稳健的方法。
简而言之:-lHSrts-ghcXXX.so
取决于 libHSbaseXXX.so
、libHSinteger-gmpXXX.so
和 libHSghc-primXXX.so
,必须在 link 期间提供给 linker =]年龄。
这里提出的解决方案依赖于大量的手动工作,而且可扩展性不是很好。但是,我对 cabal
的了解还不够多,无法告诉您如何将其自动化,但我希望您能完成最后一步。
或者您可能会使用 -Wl,--no-as-needed
解决方案,因为您知道幕后发生的事情。
让我们从不调用 foo
的情况下逐步完成版本的 linking 过程开始,以一种稍微简化的方式(here 是 Eli Bendersky 的一篇很棒的文章,即使它是关于静态 linkage):
linker 维护着 table 个符号,必须为所有符号找到 definitions/machine-code。让我们简化并假设,一开始它在table中只有符号
main
并且这个符号的定义是未知的。符号
main
的定义是在目标文件test.o
中找到的。但是,函数main
使用函数hs_init
和hs_exit
。因此我们找到了main
的定义,但是除非我们知道hs_init
和hs_exit
的定义,否则它不起作用。所以现在我们必须寻找它们的定义。在下一步中,linker 查看
foo.so
,但是foo.so
没有定义我们感兴趣的任何符号(foo
未使用!)并且 linker 只是跳过foo.so
并且永远不会回头看。link人看着
-lHSrts-ghcXXX.so
。它在那里找到hs_init
和hs_exit
的定义。因此,共享库的全部内容都被使用,但它需要定义诸如base_GHCziTopHandler_flushStdHandles_closure
之类的符号。这意味着 linker 开始寻找这些符号的定义。但是命令行中没有更多的库,因此 linker 没有什么可看的,linkage fails/is 没有成功,因为缺少某些符号的定义。
使用foo
的情况有什么不同?在第 2 步之后,不仅要 hs_init
和 hs_exit
,还要 foo
,在 foo.so
中找到。所以必须包含foo.so
。
由于库 foo.so
的构建方式,包含以下信息:
>>> readelf -d dist/build/foo.so/foo.so | grep NEEDED
0x0000000000000001 (NEEDED) Shared library: [libHSrts-ghc7.10.3.so]
0x0000000000000001 (NEEDED) Shared library: [libHSbase-4.8.2.0-HQfYBxpPvuw8OunzQu6JGM-ghc7.10.3.so]
0x0000000000000001 (NEEDED) Shared library: [libHSinteger-gmp-1.0.0.0-2aU3IZNMF9a7mQ0OzsZ0dS-ghc7.10.3.so]
0x0000000000000001 (NEEDED) Shared library: [libHSghc-prim-0.4.0.0-8TmvWUcS1U1IKHT0levwg3-ghc7.10.3.so]
0x0000000000000001 (NEEDED) Shared library: [libgmp.so.10]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
>>> readelf -d dist/build/foo.so/foo.so | grep RPATH
0x000000000000000f (RPATH) Library rpath: [
/usr/lib/ghc/base_HQfYBxpPvuw8OunzQu6JGM:
/usr/lib/ghc/rts:
/usr/lib/ghc/ghcpr_8TmvWUcS1U1IKHT0levwg3:
/usr/lib/ghc/integ_2aU3IZNMF9a7mQ0OzsZ0dS]
根据此信息,linker 知道需要哪些共享库(NEEDED
-flag)以及它们在系统中的什么位置(RPATH
)。这些库是 found/opened/processed(即标记为需要),因此所有必要的定义都存在。
您可以通过添加
来了解整个过程g++ ...
-Wl,--trace-symbol=base_GHCziTopHandler_flushStdHandles_closure \
-Wl,--verbose \
-o test
进入link年龄阶段。
如果我们按照@Yuras 的建议强制将 foo.so
通过 -Wl,--no-as-needed
包含到生成的 executable 中,也会发生同样的事情。
这个分析的结果是什么?
我们应该在命令行上提供所需的库(在 -lHSrts-ghcXXX.so
之后),而不是依赖于通过其他共享库偶然添加它们。显然,有些神秘的名称仅对我的安装有效:
g++ ...
-L/usr/lib/ghc/base_HQfYBxpPvuw8OunzQu6JGM -lHSbase-4.8.2.0-HQfYBxpPvuw8OunzQu6JGM-ghc7.10.3 \
-L/usr/lib/ghc/integ_2aU3IZNMF9a7mQ0OzsZ0dS -lHSinteger-gmp-1.0.0.0-2aU3IZNMF9a7mQ0OzsZ0dS-ghc7.10.3 \
-L/usr/lib/ghc/ghcpr_8TmvWUcS1U1IKHT0levwg3 -lHSghc-prim-0.4.0.0-8TmvWUcS1U1IKHT0levwg3-ghc7.10.3 \
...
-o test
现在构建,但在 运行 时不加载(毕竟正确的 rpath
仅在 foo.so
中设置,但 foo.so
不是用过的)。要修复它,我们可以扩展 LD_LIBRARY_PATH
或添加 -rpath
link-命令行:
g++ ...
-L... -lHSbase-... -Wl,-rpath,/usr/lib/ghc/base_HQfYBxpPvuw8OunzQu6JGM \
-L... -lHSinteger-gmp-... -Wl,-rpath,/usr/lib/ghc/integ_2aU3IZNMF9a7mQ0OzsZ0dS \
-L... -lHSghc-prim-... -Wl,-rpath,/usr/lib/ghc/ghcpr_8TmvWUcS1U1IKHT0levwg3 \
...
-o test
必须有一个实用程序来自动获取路径和库名称(cabal 似乎在构建 foo.so
时这样做),但我不知道该怎么做,因为我没有使用 haskell/cabal.