通过 FFI 块从 Haskell 调用共享库,而从 C 程序链接时则不会

Calling a shared library from Haskell via FFI blocks, while it doesn't when linked from a C program

本文关键字:链接 程序 而从 块从 FFI Haskell 调用 共享 通过      更新时间:2023-10-16

我正试图从Haskell应用程序连接Basler USB3相机,但我遇到了一些困难。相机附带了一个c++库,使其相当直接。以下代码可用于获取相机源:

extern "C" {
  void basler_init() {
    PylonAutoInitTerm pylon;
    CInstantCamera camera( CTlFactory::GetInstance().CreateFirstDevice());
    camera.RegisterConfiguration( (CConfigurationEventHandler*) NULL, RegistrationMode_ReplaceAll, Cleanup_None);
    cout << "Using device " << camera.GetDeviceInfo().GetModelName() << endl;
  }
}

我已经使用这个源代码来构建一个共享库- libbasler.so。为了确认它的工作,这里有一个基本的C程序链接到它,并确认一切都在工作:

void basler_init();
int main () {
  basler_init();
}

我编译并运行它:

$ gcc Test2.c -lbasler -Llib -Wl,--enable-new-dtags -Wl,-rpath,pylon5/lib64 -Wl,-E -lpylonbase -o Test2-c
$ PYLON_CAMEMU=1 LD_LIBRARY_PATH=lib ./Test2-c
Using device Emulation

这是预期的输出。

然而,当我尝试使用Haskell时,行为会发生变化,程序会无限期地阻塞。下面是Haskell的源代码:

{-# LANGUAGE ForeignFunctionInterface #-}
foreign import ccall "basler_init" baslerInit :: IO ()
main :: IO ()
main = baslerInit

我编译并运行它:

$ ghc --make Test2.hs -o Test2-haskell -Llib -lbasler -optl-Wl,--enable-new-dtags -optl-Wl,-rpath,pylon5/lib64 -optl-Wl,-E -lpylonbase
$ PYLON_CAMEMU=1 LD_LIBRARY_PATH=lib ./Test2-haskell

应用程序现在无限期挂起。

我已经通过strace试着了解正在发生的事情,但我无法真正理解它。输出太长,不能在这里添加,但请查看这两个粘贴:

  • C应用程序的strace输出:https://gist.github.com/ocharles/001b5f42c09229bc7a8482a22cadf486
  • Haskell应用程序的strace输出:https://gist.github.com/ocharles/4c1c45a9ee78f75cd723f1a2910998f3

最重要的是,我使用gdb来尝试确定Haskell应用程序卡住的地方:

$ PYLON_CAMEMU=1 LD_LIBRARY_PATH=lib gdb Test2-haskell 
GNU gdb (GDB) 7.11
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-pc-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from Test2-haskell...done.
(gdb) run
Starting program: /home/ollie/work/circuithub/receiving-station/Test2-haskell 
warning: File "/nix/store/9ljgbhb26ca0j9shwh8bwsa77h42izr2-gcc-5.4.0-lib/lib/libstdc++.so.6.0.21-gdb.py" auto-loading has been declined by your `auto-load safe-path' set to "$debugdir:$datadir/auto-load".
To enable execution of this file add
        add-auto-load-safe-path /nix/store/9ljgbhb26ca0j9shwh8bwsa77h42izr2-gcc-5.4.0-lib/lib/libstdc++.so.6.0.21-gdb.py
line to your configuration file "/home/ollie/.gdbinit".
To completely disable this security protection add
        set auto-load safe-path /
line to your configuration file "/home/ollie/.gdbinit".
For more information about this security protection see the
"Auto-loading safe path" section in the GDB manual.  E.g., run from the shell:
        info "(gdb)Auto-loading safe path"
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/nix/store/bb32xf954imhdrzn7j8h82xs1bx7p3fr-glibc-2.23/lib/libthread_db.so.1".
^C
Program received signal SIGINT, Interrupt.
0x00007ffff6c6fb33 in __recvfrom_nocancel () from /nix/store/98s2znxww6x7h2ch7cj1w5givahxmdna-glibc-2.23/lib/libc.so.6
(gdb) bt
#0  0x00007ffff6c6fb33 in __recvfrom_nocancel () from /nix/store/98s2znxww6x7h2ch7cj1w5givahxmdna-glibc-2.23/lib/libc.so.6
#1  0x00007fffedb885c2 in GxImp::CEnumCollector::OnReady(unsigned int, _GX_SOCKET_INTERFACE_INFO const*) () from /home/ollie/work/circuithub/receiving-station/pylon5/lib64/libgxapi-5.0.1.so
#2  0x00007fffedb8d54d in CCollector::Collect(GxImp::CSocket*, unsigned int, unsigned int, _GX_SOCKET_INTERFACE_INFO const*) () from /home/ollie/work/circuithub/receiving-station/pylon5/lib64/libgxapi-5.0.1.so
#3  0x00007fffedb8817b in CBroadcastSocketCollection::Collect(CCollector&, unsigned int) () from /home/ollie/work/circuithub/receiving-station/pylon5/lib64/libgxapi-5.0.1.so
#4  0x00007fffedb889ab in Gx::Enumerator::Discover(Gx::Enumerator::Callee*, unsigned int, unsigned int, sockaddr const*) () from /home/ollie/work/circuithub/receiving-station/pylon5/lib64/libgxapi-5.0.1.so
#5  0x00007fffeddeaca0 in Pylon::CBaslerGigETl::DoDeviceEnumeration(Pylon::DeviceInfoList&, bool, sockaddr const*) () from pylon5/lib64/libpylon_TL_gige-5.0.1.so
#6  0x00007fffeddeaebc in Pylon::CBaslerGigETl::InternalEnumerateDevices(Pylon::DeviceInfoList&) () from pylon5/lib64/libpylon_TL_gige-5.0.1.so
#7  0x00007fffeddf3c99 in Pylon::CTransportLayerBase<Pylon::IGigETransportLayer>::EnumerateDevices(Pylon::DeviceInfoList&, Pylon::DeviceInfoList const&, bool) () from pylon5/lib64/libpylon_TL_gige-5.0.1.so
#8  0x00007ffff7949669 in Pylon::CTlFactory::EnumerateDevices(Pylon::DeviceInfoList&, Pylon::DeviceInfoList const&, bool) () from pylon5/lib64/libpylonbase-5.0.1.so
#9  0x00007ffff7949c8f in Pylon::CTlFactory::InternalCreateDevice(Pylon::CDeviceInfo const&, GenICam_3_0_Basler_pylon_v5_0::gcstring_vector const&, bool) () from pylon5/lib64/libpylonbase-5.0.1.so
#10 0x00007ffff794a655 in Pylon::CTlFactory::CreateFirstDevice(Pylon::CDeviceInfo const&) () from pylon5/lib64/libpylonbase-5.0.1.so
#11 0x00007ffff7bd7dc5 in basler_init () from lib/libbasler.so
#12 0x0000000000438415 in rFl_info ()
#13 0x0000000000000000 in ?? ()

对于C程序:

Reading symbols from Test2-c...done.
(gdb) run
Starting program: /home/ollie/work/circuithub/receiving-station/Test2-c 
warning: File "/nix/store/9ljgbhb26ca0j9shwh8bwsa77h42izr2-gcc-5.4.0-lib/lib/libstdc++.so.6.0.21-gdb.py" auto-loading has been declined by your `auto-load safe-path' set to "$debugdir:$datadir/auto-load".
To enable execution of this file add
        add-auto-load-safe-path /nix/store/9ljgbhb26ca0j9shwh8bwsa77h42izr2-gcc-5.4.0-lib/lib/libstdc++.so.6.0.21-gdb.py
line to your configuration file "/home/ollie/.gdbinit".
To completely disable this security protection add
        set auto-load safe-path /
line to your configuration file "/home/ollie/.gdbinit".
For more information about this security protection see the
"Auto-loading safe path" section in the GDB manual.  E.g., run from the shell:
        info "(gdb)Auto-loading safe path"
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/nix/store/bb32xf954imhdrzn7j8h82xs1bx7p3fr-glibc-2.23/lib/libthread_db.so.1".
[New Thread 0x7fffed4ae700 (LWP 13792)]
[New Thread 0x7fffeccad700 (LWP 13793)]
Using device Emulation
[Thread 0x7fffeccad700 (LWP 13793) exited]
[Thread 0x7fffed4ae700 (LWP 13792) exited]
[Inferior 1 (process 13788) exited normally]

我的猜测是GHC的运行时正在做一些事情,导致pthread有不同的行为,但我不确定那可能是什么。

我相信这个TRAC评论是相关的:

https://ghc.haskell.org/trac/ghc/wiki/Commentary/Rts/Signals

差异出现在C字符输出的第437行开始对比Haskell字符串输出中的第495行

此时,库创建两个UDP套接字并发送两个UDP数据报(C 448-449行/Haskell 506-507行)。数据包被广播到两个本地网络:192.168.1.0/24和192.168.56.0/24

然后等待其中一个套接字的响应,超时为(显然)25微秒(450 C行/508 Haskell行)。在C的情况下,select调用超时。在Haskell的情况下,select调用被反复中断由GHC RTS使用的SIGVTALRM信号。这是相同的模式如上述TRAC注释所示。

对于一个潜在的修复,看看mysql包是如何实现和使用block_rts_signals()unblock_rts_signals()宏的:

  • mysql_signals.c