pothosware / SoapySDR

Vendor and platform neutral SDR support library.
https://github.com/pothosware/SoapySDR/wiki
Boost Software License 1.0
1.13k stars 179 forks source link

Unable to load modules when libSoapySDR bundled with OSX .app #28

Closed cjcliffe closed 9 years ago

cjcliffe commented 9 years ago

Enjoying using SoapySDR so far; it's been quick to implement and looks like a good fit with my project.

I'm able to build and run my application (CubicSDR) with SoapySDR and run it from the terminal without issue; it finds and loads the modules and lists the factories and works with my rtl-sdr device.

Once bundled into an .app (libraries are imported with otool by cmake I believe) the installed soapy modules are found but none of the factories are available via 'SoapySDR::FindFunctions factories = SoapySDR::Registry::listFindFunctions();' except for 'null'.

Run from cli compiled binary:

SoapySDR init..
    API Version: v0.3.0-g603da6be
    ABI Version: v0.3-0
    Install root: /usr/local
    Module found: /usr/local/lib/SoapySDR/modules/libairspySupport.so
    Module found: /usr/local/lib/SoapySDR/modules/libbladerfSupport.so
    Module found: /usr/local/lib/SoapySDR/modules/libhackrfSupport.so
    Module found: /usr/local/lib/SoapySDR/modules/librfspaceSupport.so
    Module found: /usr/local/lib/SoapySDR/modules/librtlSupport.so
    Loading modules... done
    Available factories...airspy, bladerf, hackrf, null, rfspace, rtl
Found device 0
  driver = rtl
  label = Realtek RTL2838UHIDIR SN: 00000003
  rtl = 0
Make device driver=rtl
Using device #0 Realtek RTL2838UHIDIR SN: 00000003
Found Rafael Micro R820T tuner
[R82XX] PLL not locked!

Run from bundled app:

SoapySDR init..
    API Version: v0.3.0-g603da6be
    ABI Version: v0.3-0
    Install root: /usr/local
    Module found: /usr/local/lib/SoapySDR/modules/libairspySupport.so
    Module found: /usr/local/lib/SoapySDR/modules/libbladerfSupport.so
    Module found: /usr/local/lib/SoapySDR/modules/libhackrfSupport.so
    Module found: /usr/local/lib/SoapySDR/modules/librfspaceSupport.so
    Module found: /usr/local/lib/SoapySDR/modules/librtlSupport.so
    Loading modules... done
    Available factories...null
No devices found!

Any insight you might have would be great; let me know what I can do to help!

guruofquality commented 9 years ago

Cool app, thanks for the bug report!

The modules register their make/find functions using static initialization. Perhaps the action of bundling is stripping out the static initializers out of the modules or the dlopen() isn't calling static initializers when its bundled. Not sure why, but that may be a good place to look.

Possibly the initializers can be decorated with __attribute__((constructor)) https://developer.apple.com/library/mac/documentation/MacOSX/Conceptual/BPFrameworks/Tasks/InitializingFrameworks.html

like this:

diff --git a/GrOsmoSDRRegister.in.cpp b/GrOsmoSDRRegister.in.cpp
index a908ce4..03da16c 100644
--- a/GrOsmoSDRRegister.in.cpp
+++ b/GrOsmoSDRRegister.in.cpp
@@ -71,4 +71,5 @@ static SoapySDR::Device *make__@TARGET@(const SoapySDR::Kwargs &args)
     return device;
 }

+__attribute__((constructor))
 static SoapySDR::Registry register__@TARGET@("@TARGET@", &find__@TARGET@, &make__@TARGET@, SOAPY_SDR_ABI_VERSION);

or maybe:

diff --git a/GrOsmoSDRRegister.in.cpp b/GrOsmoSDRRegister.in.cpp
index a908ce4..098510d 100644
--- a/GrOsmoSDRRegister.in.cpp
+++ b/GrOsmoSDRRegister.in.cpp
@@ -71,4 +71,8 @@ static SoapySDR::Device *make__@TARGET@(const SoapySDR::Kwargs &args)
     return device;
 }

-static SoapySDR::Registry register__@TARGET@("@TARGET@", &find__@TARGET@, &make__@TARGET@, SOAPY_SDR_ABI_VERSION);
+__attribute__((constructor))
+void loadIt(void)
+{
+    SoapySDR::Registry register__@TARGET@("@TARGET@", &find__@TARGET@, &make__@TARGET@, SOAPY_SDR_ABI_VERSION);
+}

Ideally there might be a bundle option to fix this. But if not, let me know if one of these diffs helps -- maybe I will have to engineer a magic registry macro to make this work more portably if that's the case.

cjcliffe commented 9 years ago

No change with either patch; first one reports the following for each module while compiling:

SoapyOsmo/build/register_airspy.cc:75:16: warning: 'constructor' attribute only applies to functions [-Wignored-attributes]
__attribute__((constructor))

Second patch doesn't complain but there's no change in the app bundle list of factories.

I'll poke around and see what bundle options are available.

guruofquality commented 9 years ago

It might be interesting to add in a test initializer with a print. if its not printed, then the problem is with the initializers not getting called. But if this print is happening, I guess thats a different can or worms... but it might be nice to narrow it down.

diff --git a/GrOsmoSDRRegister.in.cpp b/GrOsmoSDRRegister.in.cpp
index 4f4e605..5c73bf9 100644
--- a/GrOsmoSDRRegister.in.cpp
+++ b/GrOsmoSDRRegister.in.cpp
@@ -90,3 +90,14 @@ static SoapySDR::Device *make__@TARGET@(const SoapySDR::Kwargs &args)
 }

 static SoapySDR::Registry register__@TARGET@("@TARGET@", &find__@TARGET@, &make__@TARGET@, SOAPY_SDR_ABI_VERSION);
+
+class FooInitializer
+{
+public:
+    FooInitializer(const std::string &name)
+    {
+        std::cout << "Hello from FooInitializer " << name << std::endl;
+    }
+};
+
+static FooInitializer register_foo_test__@TARGET@("@TARGET@");

Example output:

SoapySDRUtil --find="rtl=0"
######################################################
## Soapy SDR -- the SDR abstraction library
######################################################

Hello from FooInitializer airspy
Hello from FooInitializer hackrf
Hello from FooInitializer miri
Hello from FooInitializer rfspace
Hello from FooInitializer rtl
linux; GNU C++ version 4.8.4; Boost_105400; UHD_003.008.004-0-unknown

Found device 0
  driver = rtl
  label = Realtek RTL2838UHIDIR SN: 00000001
  rtl = 0
cjcliffe commented 9 years ago

Hmmm, we may have a different can of worms :)

Third patch results running from CLI build works as expected:

SoapySDR init..
    API Version: v0.3.0-g603da6be
    ABI Version: v0.3-0
    Install root: /usr/local
    Module found: /usr/local/lib/SoapySDR/modules/libairspySupport.so
    Module found: /usr/local/lib/SoapySDR/modules/libbladeRFSupport.so
    Module found: /usr/local/lib/SoapySDR/modules/libhackrfSupport.so
    Module found: /usr/local/lib/SoapySDR/modules/librfspaceSupport.so
    Module found: /usr/local/lib/SoapySDR/modules/librtlSupport.so
    Loading modules... Hello from FooInitializer airspy
Hello from FooInitializer hackrf
Hello from FooInitializer rfspace
Hello from FooInitializer rtl
done
    Available factories...airspy, bladerf, hackrf, null, rfspace, rtl
Found device 0
  driver = rtl
  label = Realtek RTL2838UHIDIR SN: 00000003
  rtl = 0

Running from bundled .app still says Hello, but no factories..

SoapySDR init..
    API Version: v0.3.0-g603da6be
    ABI Version: v0.3-0
    Install root: /usr/local
    Module found: /usr/local/lib/SoapySDR/modules/libairspySupport.so
    Module found: /usr/local/lib/SoapySDR/modules/libbladeRFSupport.so
    Module found: /usr/local/lib/SoapySDR/modules/libhackrfSupport.so
    Module found: /usr/local/lib/SoapySDR/modules/librfspaceSupport.so
    Module found: /usr/local/lib/SoapySDR/modules/librtlSupport.so
    Loading modules... Hello from FooInitializer airspy
Hello from FooInitializer hackrf
Hello from FooInitializer rfspace
Hello from FooInitializer rtl
done
    Available factories...null
No devices found!
guruofquality commented 9 years ago

Interesting, it might be that the modules each get a different library data section or something. This time, this is a patch on SoapySDR itself, it prints the registry table address and its size. I'm curious if the addresses differ...

diff --git a/lib/Registry.cpp b/lib/Registry.cpp
index b8d0f37..688f032 100644
--- a/lib/Registry.cpp
+++ b/lib/Registry.cpp
@@ -28,6 +28,9 @@ SoapySDR::Registry::Registry(const std::string &name, const FindFunction &find,
         std::cerr << "  Rebuild module against installed library..." << std::endl;
         return;
     }
+    std::cout << "Registering " << name << std::endl;
+    std::cout << "Table size " << getFunctionTable().size() << std::endl;
+    std::cout << "Table addr " << std::hex << size_t(&getFunctionTable()) << std::dec << std::endl;
     FunctionsEntry entry;
     entry.find = find;
     entry.make = make;

The prints might look like this

Loading modules... Registering airspy
Table size 1
Table addr 7f4081d9b4e0
Registering bladerf
Table size 2
Table addr 7f4081d9b4e0
Registering hackrf
Table size 3
Table addr 7f4081d9b4e0
Registering miri
Table size 4
Table addr 7f4081d9b4e0
Registering remote
Table size 5
Table addr 7f4081d9b4e0
Registering rfspace
Table size 6
Table addr 7f4081d9b4e0
Registering rtl
Table size 7
Table addr 7f4081d9b4e0
cjcliffe commented 9 years ago

Interesting result, CLI shows a consistent table addr and table size increments correctly:

Registering null
Table size 0
Table addr 100c5fd30

[..CubicSDR init happens here..]

SoapySDR init..
    API Version: v0.3.0-g603da6be
    ABI Version: v0.3-0
    Install root: /usr/local
    Module found: /usr/local/lib/SoapySDR/modules/libairspySupport.so
    Module found: /usr/local/lib/SoapySDR/modules/libbladeRFSupport.so
    Module found: /usr/local/lib/SoapySDR/modules/libhackrfSupport.so
    Module found: /usr/local/lib/SoapySDR/modules/librfspaceSupport.so
    Module found: /usr/local/lib/SoapySDR/modules/librtlSupport.so
    Loading modules... Registering airspy
Table size 1
Table addr 100c5fd30
Hello from FooInitializer airspy
Registering bladerf
Table size 2
Table addr 100c5fd30
Registering hackrf
Table size 3
Table addr 100c5fd30
Hello from FooInitializer hackrf
Registering rfspace
Table size 4
Table addr 100c5fd30
Hello from FooInitializer rfspace
Registering rtl
Table size 5
Table addr 100c5fd30
Hello from FooInitializer rtl
done
    Available factories...airspy, bladerf, hackrf, null, rfspace, rtl
Found device 0
  driver = rtl
  label = Realtek RTL2838UHIDIR SN: 00000003
  rtl = 0

Bundle shows two different addresses between static initializer and loadModules and the table size returns to 0:

Registering null
Table size 0
Table addr 110586d30

[..CubicSDR init happens here..]

SoapySDR init..
    API Version: v0.3.0-g603da6be
    ABI Version: v0.3-0
    Install root: /usr/local
    Module found: /usr/local/lib/SoapySDR/modules/libairspySupport.so
    Module found: /usr/local/lib/SoapySDR/modules/libbladeRFSupport.so
    Module found: /usr/local/lib/SoapySDR/modules/libhackrfSupport.so
    Module found: /usr/local/lib/SoapySDR/modules/librfspaceSupport.so
    Module found: /usr/local/lib/SoapySDR/modules/librtlSupport.so
    Loading modules... Registering null
Table size 0
Table addr 11551ed30
Registering airspy
Table size 1
Table addr 11551ed30
Hello from FooInitializer airspy
Registering bladerf
Table size 2
Table addr 11551ed30
Registering hackrf
Table size 3
Table addr 11551ed30
Hello from FooInitializer hackrf
Registering rfspace
Table size 4
Table addr 11551ed30
Hello from FooInitializer rfspace
Registering rtl
Table size 5
Table addr 11551ed30
Hello from FooInitializer rtl
done
    Available factories...null
No devices found!
guruofquality commented 9 years ago

Thats rich, so the library and modules have two distinct copies of the library's static variables. There's probably some crazy linker flag to fix whatever is going on. Example -flat_namespace

cjcliffe commented 9 years ago

Hah! brilliant -- that was it precisely; setting CMAKE_SHARED_LINKER_FLAGS="-flat_namespace" did the trick; looks there's several similar issues on Google related to Qt Modules and that flag..

Works with both CLI and Bundled version; I'd say it's probably safe to set that as a default for IF(APPLE) .. in CMakeLists.

Thanks!

guruofquality commented 9 years ago

Lucky guess, glad that worked. This is the patch that I am going to apply to maint and master. Can you give it a final test?

diff --git a/cmake/SoapySDRConfig.cmake b/cmake/SoapySDRConfig.cmake
index 516336a..20cb017 100644
--- a/cmake/SoapySDRConfig.cmake
+++ b/cmake/SoapySDRConfig.cmake
@@ -68,6 +68,12 @@ if(CMAKE_COMPILER_IS_GNUCXX)
     set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility-inlines-hidden")
 endif()

+if(APPLE)
+    #fixes issue with duplicate module registry when using application bundle
+    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -flat_namespace")
+    set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -flat_namespace")
+endif()
+
 if(MSVC)
     add_compile_options(/wd4503) #'identifier' : decorated name length exceeded, name was truncated
cjcliffe commented 9 years ago

Worked here, I'd say it's good to go

guruofquality commented 9 years ago

Cool, patched. Thanks https://github.com/pothosware/SoapySDR/commit/0efce23e0b1d8217607c877434fc19c525e85af1