I’m super interested in seeing good support for loadable modules, but we should make sure we get ABI-safety right, so I have a couple of questions about the overall approach.
dlopen() brings the plugin library into the running process, any symbols present in the main program binary are made available to the plugin code, allowing plugins to call function and access
static variables defined in the main program. As a result, it is unnecessary to link a library to the plugin that is also used by the main program, as these symbols will be overriden upon runtime linking.
I’m worried about the forwards-compatibility of this. For example, if my host program happens to use OpenSSL, and my plugin also wants to use OpenSSL, but the plugin interface doesn’t have anything to do with SSL, the plugin should also explicitly link (declare a dynamic library dependency on) OpenSSL. Otherwise, if my host program decides to switch to BoringSSL or NSS or something, the plugin will stop working for no good reason. On the other hand, if OpenSSL is explicitly part of the plugin interface (e.g. the interface permits passing an open SSL connection into the plugin), then yes, the plugin should rely on the host program’s loading of OpenSSL, but then the host program ought to explicitly re-export some or all of OpenSSL’s symbols with something like
pub use openssl.
#![export_symbols], applied at the crate level, will cause all symbols of the target crate to be exported to the dynamic symbol table.
Is there a reason
pub can’t just do this? Does
pub at a top-level binary crate have any existing meaning? (Either way, it absolutely shouldn’t do this for non-
pub symbols. GNU ld’s
--dynamic-list to do the equivalent privacy thing.)
Alternatively, instead of expecting to find global items in the binary crate itself, maybe the plugin crate and the binary crate should both depend on a plugin-API crate. Then the plugins’ dependencies are just normal dynamic-library dependencies; libprogram depends libapi, libprogram depends libplugin depends libapi. This is no different from, say, libprogram and libplugin both depending libstd. It also allows linking the program and plugin against a relatively small shared ABI, so that you don’t have to recompile the plugin if the program gets updated in a backwards-compatible way. Given how easy the Rust ecosystem makes having multiple crates, I’m not seeing the advantage of letting binary crates export symbols, but maybe I’m missing an intended use case.
#[plugin_link], applied to extern crate items, will import names into the enclosing scope, but skip linking the named crate into the host crate.
If you do the plugin-API-library-crate thing, then it’s correct for the plugin to declare a dynamic-linkage dependency on the interface. (Even if you don’t, it’s still correct, but I’m less sure about whether dynamic linkers let you declare dependencies on binaries. But for those that do, that linkage dependency should be stated.)
This attribute should be present within plugin crates on any crate items which are expected to be present in the main program.
As above, this is part of the ABI, so if you intend to use a re-exported crate, you should just do e.g.
use host_program::openssl; instead of
#[plugin_link] extern crate openssl;. (Even on platforms without two-level namespacing, Rust’s name mangling makes this work without symbol conflicts, since
host_program is exporting these items under a different symbol name than it imports them.)