Statically Linking Parts of a Shared Library?

I have kind of a complicated setup. It is documented here:

In a nutshell, the problem is this: I am forced to compile a shared library for interoperability with Python to use the shared library as a Rust AWS Lambda function. I am building my shared library in the same environment as much as possible as the Amazon Linux execution environment used by AWS Lambda. Attempting to install openssl-devel in this environment, however, results in upgrading OpenSSL from the default version of 1.0.1 to 1.0.2. Since my build environment links against 1.0.2 and the runtime environment is the older 1.0.1, my library crashes on startup with an invalid/unexpected OpenSSL version. I must make a shared library for Python/Lambda integration, but I'd like to statically compile OpenSSL in so I don't run into this issue. Redoing everything in musl doesn't give me anything as I think a shared library in musl would still want to link against OpenSSL.

Please see the GitHub above as it demonstrates the exact subtle nature of the issue.

How can I tell Cargo/rustc that I would like to statically compile OpenSSL into my shared library?

1 Like

I think this should do it:

cargo clean
OPENSSL_STATIC=1 cargo build

See https://github.com/sfackler/rust-openssl/#manual-configuration for details.

Edit: looks like you already tried that.

One approach is to have Cargo produce a static library, then manually link that into a shared library. (This works because a static library is mostly just a collection of object files.)

How can I do that? It's been hard to Google for.

What you're looking for is the staticlib crate type.

After I have created the static library, how do I then transform it into a shared library? Due to Python reasons, I must produce a shared library.

gcc -shared mystaticlib.a

I have done the following:

  1. change the Cargo build type to staticlib
  2. recompile and attempt to link:
$ cargo clean
$ cargo build --lib
$ gcc -shared target/debug/liblambda.a -o target/debug/liblambda.so
$ du -bh target/debug/liblambda.{a,so}
23M     /home/vagrant/.cache/cargo/target/debug/liblambda.a
7.6K    /home/vagrant/.cache/cargo/target/debug/liblambda.so

Somehow I think that this isn't working as expected.

You might need to pass -Wl,--whole-archive before the .a file.

Unfortunately it's not working:

$ gcc -Wl --whole-archive -shared ~/.cache/cargo/target/debug/liblambda.a  -o ~/.cache/cargo/target/debug/liblambda.so
gcc: error: unrecognized command line option ‘-Wl’
gcc: error: unrecognized command line option ‘--whole-archive’

The comma in my command was not a typo, you need to pass it exactly as I stated.

Thanks, progress but still a nonzero return code: https://gist.github.com/naftulikay/4a6be9ceddc1a0391a3a4958931e23bd

The cargo build command should've output a list of libraries that you need to link, like so:

note: link against the following native artifacts when linking against this static library
note: the order and any duplication can be significant on some platforms, and so may need to be preserved
note: library: dl
note: library: rt
note: library: pthread
note: library: gcc_s
note: library: c
note: library: m
note: library: rt
note: library: pthread
note: library: util

You need to pass each of those using -l.

Cargo doesn't emit anything like this, running cargo build --lib -vvv

IIRC you must pass --print=native-static-libs to cargo build to get this output on recent toolchains.

Also, make sure you clean before doing this - it will only output on actual builds.

Cargo complains: Unknown flag: --print:

$ cargo --version
cargo 0.25.0 (96d8071da 2018-02-26)
$ rustc --version
rustc 1.24.1 (d3ae9a9e0 2018-02-27)
$ cargo build --print=native-static-libs --lib
error: Unknown flag: '--print'

Usage:
    cargo build [options]

Ah, sorry, it's apparently a rustc flag, so you need to use RUSTFLAGS or cargo rustc.

Off a clean build:

note: native-static-libs: -lpython3.4m -lutil -lutil -ldl -lrt -lpthread -lgcc_s -lc -lm -lrt -lpthread -lutil -lutil
gcc -Wl,--whole-archive -lpython3.4m -lutil -lutil -ldl -lrt -lpthread -lgcc_s -lc -lm -lrt -lpthread -lutil -lutil -shared ~/.cache/cargo/target/debug/liblambda.a  -o ~/.cache/cargo/target/debug/liblambda.so

Output:

/usr/bin/ld: /home/vagrant/.cache/cargo/target/debug/liblambda.so: version node not found for symbol SSLeay_version@OPENSSL_1.0.1
/usr/bin/ld: failed to set dynamic section sizes: Bad value

I have abandoned trying to make a static library in favor of a shared library which static links in OpenSSL. I'm now getting some really weird issues:

OPENSSL_STATIC=1 \
  OPENSSL_LIB_DIR=/usr/lib64 \
  OPENSSL_INCLUDE_DIR=/usr/include/openssl \
  RUSTFLAGS=" \
      -lcom_err \
      -lc \
      -ldl \
      -lgssapi_krb5 \
      -lk5crypto \
      -lkrb5 \
      -lkrb5support \
      -lpcre \
      -lpthread \
      -lresolv \
      -lselinux \
      -lz \
    " \
    cargo build --lib --release

Interestingly enough, I can compile my library:

extern crate openssl;

fn init() {
  openssl::init()
}

However, as soon as I try to include rusoto, I get compiler errors:

extern crate rusoto_core;
extern crate rusoto_kms;

use rusoto_core::region::Region;
use rusoto_kms::Kms;
use rusoto_kms::KmsClient;
use rusoto_kms::DecryptRequest;

impl SecureConfig {

    pub fn new() -> Self {
        SecureConfig {
            client: Arc::new(KmsClient::simple(Region::UsEast1))
        }
    }

    /// Decrypt and return the Google OAuth Client Secret
    pub fn google_oauth_client_secret(&self) -> String {
        String::from_utf8(
            self.client.decrypt(&DecryptRequest {
                ciphertext_blob: Vec::from(Config::enc_google_oauth_client_secret().as_bytes()),
                encryption_context: None,
                grant_tokens: None
            }).sync().unwrap().plaintext.unwrap()
        ).unwrap()
    }

    /// Decrypt and return the Google OAuth Signing Token
    pub fn google_oauth_signing_token(&self) -> String {
        String::from_utf8(
            self.client.decrypt(&DecryptRequest {
                ciphertext_blob: Vec::from(Config::enc_google_oauth_signing_token().as_bytes()),
                encryption_context: None,
                grant_tokens: None
            }).sync().unwrap().plaintext.unwrap()
        ).unwrap()
    }
}

Now, when I compile: https://gist.github.com/naftulikay/568e0363d6b9aa8e7f9e0b4f840d82b5

  = note: /usr/bin/ld: /home/circleci/project/target/release/deps/liblambda.so: version node not found for symbol SSLeay_version@OPENSSL_1.0.1
          /usr/bin/ld: failed to set dynamic section sizes: Bad value
          collect2: error: ld returned 1 exit status

I'm kind of at the end of my rope here. Haven't yet spent this much time in linker hell.

I suppose musl might be a requirement at this point. This gets back to my original inquiry: is there a way to statically compile a shared library so that Python can still use it, yet bake in all dependencies in statically? I don't want to keep fussing around with linker flags.