草庐IT

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

coder 2023-05-31 原文

大致关注 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)
    

    最佳答案

    这个答案解释了链接过程中发生了什么,为什么使用 -Wl,--no-as-needed 的解决方案工作以及应该做些什么来获得更强大的方法。

    简而言之:-lHSrts-ghcXXX.so取决于 libHSbaseXXX.so , libHSinteger-gmpXXX.solibHSghc-primXXX.so必须在链接期间提供给链接器。

    这里提出的解决方案依赖于大量的手动工作,并且可扩展性不强。但是我对 cabal 的了解还不够。告诉你如何自动化这个,但我希望你能迈出最后一步。

    或者也许你会很好地使用 -Wl,--no-as-needed -解决方案,因为您知道幕后发生的事情。

    让我们从不调用 foo 的情况下逐步完成版本的链接过程开始。 ,以某种简化的方式(here 是 Eli Bendersky 的一篇很棒的文章,即使它是关于静态链接的):

  • 链接器维护一个符号表,并且必须找到所有符号的定义/机器代码。让我们简化并假设,一开始它只有符号 main在表中,这个符号的定义是未知的。
  • 符号定义main找到它的目标文件test.o .但是,函数 main使用函数 hs_iniths_exit .因此我们找到了main 的定义。 ,但除非我们知道 hs_init 的定义,否则它不起作用和 hs_exit .所以现在我们必须寻找它们的定义。
  • 在下一步中,链接器将查看 foo.so , 但是 foo.so没有定义任何我们感兴趣的符号(foo 没有被使用!)并且链接器只是跳过 foo.so并且永远不会回头。
  • 链接器查看 -lHSrts-ghcXXX.so .在那里它找到了 hs_init 的定义。和 hs_exit .因此,使用了共享库的全部内容,但需要定义诸如 base_GHCziTopHandler_flushStdHandles_closure 之类的符号。 .这意味着链接器开始寻找这些符号的定义。
  • 然而,命令行中没有更多的库,因此链接器没有什么可看的,并且链接失败/不成功,因为缺少某些符号的定义。
  • foo 的情况有什么不同?用来? 2.步骤后不仅hs_iniths_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]
    

    从这些信息中,链接器知道需要哪些共享库(NEEDED -flag)以及它们在系统上的什么位置(RPATH)。这些库被找到/打开/处理(即根据需要标记),因此存在所有必要的定义。

    您可以通过添加来跟踪整个过程
    g++ ...
        -Wl,--trace-symbol=base_GHCziTopHandler_flushStdHandles_closure \
        -Wl,--verbose \
        -o test
    

    到联动步骤。

    如果我们强制执行 foo.so通过 -Wl,--no-as-needed 包含在生成的可执行文件中正如@Yuras 所建议的那样。

    这种分析的结果是什么?

    我们应该在命令行上提供所需的库(在 -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链接命令行:
    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 的经验。

    关于c++ - 如何正确链接用 Haskell 编写的目标文件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50569661/

    有关c++ - 如何正确链接用 Haskell 编写的目标文件?的更多相关文章

    1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

      我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

    2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

      总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

    3. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

      我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

    4. ruby - 其他文件中的 Rake 任务 - 2

      我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

    5. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

      我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

    6. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

      关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

    7. ruby-on-rails - Rails 3 中的多个路由文件 - 2

      Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

    8. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

      给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

    9. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

      我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

    10. ruby - 将差异补丁应用于字符串/文件 - 2

      对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

    随机推荐