Helpex - Trao đổi & giúp đỡ Đăng nhập
21

Gần như làm theo hướng dẫn này , tôi đã quản lý để dự án đồ chơi này hoạt động. Nó gọi một hàm Haskell từ một chương trình C ++.

  • 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
    
  • xây dựng kịch bản

    #!/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
    

Điều này hoạt động (Ubuntu 16.04, GCC 5.4.0), in 893- nhưng nó không thực sự mạnh mẽ, cụ thể là, nếu tôi loại bỏ lệnh gọi thực sự của hàm Haskell, tức là std::cout << foo(37, 19) << "\n";dòng, thì nó không thành công ở bước liên kết, với lỗi thông điệp

/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'
...

Rõ ràng, việc bao gồm dự án Haskell kéo theo các tệp thư viện bổ sung cần thiết. Làm thế nào để tôi phụ thuộc một cách rõ ràng vào mọi thứ cần thiết, để tránh độ giòn như vậy?


Đầu ra của tập lệnh xây dựng khi foocuộc gọi được bao gồm, với lddtệp thực thi cuối cùng:

++ 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)
21 hữu ích 5 bình luận 1.9k xem chia sẻ
2

Thông thường ghcliên kết các tệp thực thi với -Wl,--no-as-neededtùy chọn và bạn cũng nên sử dụng nó. (Bạn có thể kiểm tra xem ghccác liên kết có thể thực thi như thế nào, ví dụ như sử dụng cabal build --ghc-options=-v3.)

Bạn có thể tìm thêm thông tin chi tiết tại đây . Sự hiểu biết của tôi về nó tiếp theo: foo.soyêu cầu libHSbase-4.10.0.0-ghc8.2.1.sođược tải trong thời gian chạy khi cần thiết, tức là khi chúng ta cần biểu tượng từ nó (kiểm tra readelf -a dist/build/foo.so/foo.so | grep NEEDED). Vì vậy, nếu bạn không gọi foo, sau đó base.sokhông được tải. Nhưng ghc cần tất cả các thư viện được tải (tôi không biết tại sao). Các --no-as-neededtùy chọn bắt buộc tất cả các thư viện để được nạp.

Lưu ý rằng --no-as-neededcác tùy chọn phụ thuộc vào vị trí, vì vậy hãy đặt nó trước thư viện được chia sẻ.

2 hữu ích 2 bình luận chia sẻ
2

Câu trả lời này giải thích những gì xảy ra trong quá trình liên kết, tại sao giải pháp có hiệu -Wl,--no-as-neededquả và thay vào đó nên làm gì để có một cách tiếp cận mạnh mẽ hơn.

Tóm lại: -lHSrts-ghcXXX.sophụ thuộc vào libHSbaseXXX.so, libHSinteger-gmpXXX.solibHSghc-primXXX.socái nào phải được cung cấp cho trình liên kết trong quá trình liên kết.

Giải pháp được đề xuất ở đây phụ thuộc vào rất nhiều công việc thủ công và không có khả năng mở rộng. Tuy nhiên, tôi không biết đầy đủ về cách cabalcho bạn biết cách tự động hóa điều này, nhưng tôi hy vọng bạn có thể thực hiện bước cuối cùng.

Hoặc có thể bạn sẽ ổn với việc sử dụng -Wl,--no-as-needed-solution, bởi vì bạn biết những gì xảy ra đằng sau hậu trường.


Hãy bắt đầu bằng cách thực hiện quá trình liên kết cho phiên bản mà không cần gọi foo, theo cách hơi đơn giản ( đây là một bài viết tuyệt vời của Eli Bendersky, ngay cả khi nó là về liên kết tĩnh):

  1. Trình liên kết duy trì một bảng các ký hiệu và phải tìm định nghĩa / mã máy cho tất cả chúng. Hãy đơn giản hóa và giả sử rằng lúc đầu nó chỉ có ký hiệu maintrong bảng và định nghĩa của ký hiệu này là không xác định.

  2. Định nghĩa của biểu tượng mainđược tìm thấy nó là tệp đối tượng test.o. Tuy nhiên, hàm mainsử dụng các hàm hs_iniths_exit. Vì vậy, chúng tôi đã tìm thấy định nghĩa của main, nhưng nó không hoạt động trừ khi chúng tôi biết các định nghĩa của hs_iniths_exit. Vì vậy, bây giờ chúng ta phải tìm kiếm các định nghĩa của chúng.

  3. Trong bước tiếp theo, trình liên kết sẽ xem xét foo.so, nhưng foo.sokhông xác định bất kỳ ký hiệu nào mà chúng tôi quan tâm ( fookhông được sử dụng!) Và trình liên kết chỉ bỏ qua foo.sovà sẽ không bao giờ nhìn lại.

  4. Trình liên kết xem xét -lHSrts-ghcXXX.so. Ở đó nó tìm thấy các định nghĩa của hs_iniths_exit. Do đó, toàn bộ nội dung của thư viện chia sẻ được sử dụng, nhưng nó cần các định nghĩa của các ký hiệu như vậy chẳng hạn base_GHCziTopHandler_flushStdHandles_closure. Điều đó có nghĩa là trình liên kết bắt đầu tìm kiếm các định nghĩa của các ký hiệu này.

  5. Tuy nhiên, không có thêm thư viện nào ở dòng lệnh, do đó trình liên kết không có gì để xem và liên kết không thành công / không thành công, bởi vì định nghĩa của một số ký hiệu bị thiếu.

Điều gì là khác biệt đối với trường hợp foođược sử dụng? Sau bước 2. không chỉ hs_iniths_exitđược muốn mà còn foo, được tìm thấy trong foo.so. Vì vậy, foo.sophải được bao gồm.

Do cách foo.soxây dựng thư viện , có những thông tin sau:

>>> 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]

Từ thông tin này, trình liên kết biết những thư viện được chia sẻ nào là cần thiết ( NEEDED-flag) và chúng có thể được tìm thấy ở đâu trên hệ thống của bạn ( RPATH). Các thư viện này được tìm thấy / mở / xử lý (nghĩa là được đánh dấu khi cần thiết) và do đó tất cả các định nghĩa cần thiết đều có mặt.

Bạn có thể theo dõi toàn bộ quá trình bằng cách thêm

g++ ...
    -Wl,--trace-symbol=base_GHCziTopHandler_flushStdHandles_closure \
    -Wl,--verbose \
    -o test

đến bước liên kết.

Điều tương tự cũng xảy ra nếu chúng tôi thực thi rằng foo.sotệp được đưa vào tệp thực thi kết quả thông qua -Wl,--no-as-needednhư được đề xuất bởi @Yuras.

Hệ quả của phân tích này là gì?

Chúng ta nên cung cấp các thư viện cần thiết trên dòng lệnh (sau -lHSrts-ghcXXX.so) và không phụ thuộc vào việc chúng được thêm vào mỗi khi có cơ hội thông qua các thư viện dùng chung khác. Rõ ràng, những cái tên hơi khó hiểu chỉ hợp lệ cho cài đặt của tôi:

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

Bây giờ nó xây dựng, nhưng không tải tại thời điểm chạy (sau khi tất cả các quyền rpathchỉ được thiết lập foo.sonhưng foo.sokhông được sử dụng). Để khắc phục, chúng ta có thể mở rộng LD_LIBRARY_PATHhoặc thêm -rpathliên kết-dòng lệnh:

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

Phải có một tiện ích để lấy các đường dẫn và tên thư viện tự động (dường như cabal làm điều đó khi xây dựng foo.so), nhưng tôi không biết làm thế nào vì tôi không có kinh nghiệm với haskell / cabal.

2 hữu ích 0 bình luận chia sẻ
loading
Không tìm thấy câu trả lời bạn tìm kiếm? Duyệt qua các câu hỏi được gắn thẻ c++ haskell linker ghc ffi , hoặc hỏi câu hỏi của bạn.

Có thể bạn quan tâm

loading