Skip to content

Commit

Permalink
Update cargo dependencies, fix key engine LTO (Azure#499)
Browse files Browse the repository at this point in the history
LTO is enabled by default for .deb packages starting in Ubuntu
21.04[^1], causing build failures on Ubuntu 22.04.  Previously, we
hacked around this by compiling outside of the `dpkg-buildpackage`
environment[^2].  However, this hack stopped working when the `cc` crate
began emitting `rerun-if-env-changed` in version `1.0.74`[^3].  Hence,
would need to set `emit_rerun_if_env_changed(false)` for all `cc::Build`
instances in `aziot-key-openssl-engine-shared` and its dependency graph
to restore our hack for `cc > 1.0.73`.  This is not remotely practical.

LTO is incompatible with `__asm__(".symver ..")` because it uses the
linker-script-provided exported symbols to determine what is safe to
optimize out, and the linker script is naturally unaware of
manually-exported symbols.  We can work around this by adding
`__attribute__((used))` to the functions we want to keep in the final
object.

Another option would be to use GCC's
`__attribute__((symver("..@")))`[^4] directive, but this relies on too
new of a toolchain version (GCC 10).

Addendum 1: The fundamental reason for why LTO is a problem for
`aziot-key-openssl-engine-shared` in the first place is that this crate
uses what is, in effect, a "symbol stub" to hook Rust code into
OpenSSL's engine macros.  First, `build/engine.c` declares the engine
function signature and uses OpenSSL's macros to expand the dynamic
engine binding.  This file is then compiled (but not linked) into an
object that will become the dynamic library's public interface.  The way
this is accomplished is by linking the whole object into the `cdylib`
(as opposed to only linking referenced functions).  LTO requires us to
go one step further by preventing the linker from optimizing out symbols
not declared as globally-exported in `rustc`'s linker script, which does
not know of the symbol declaration in the stub object.  There is an open
RFC request for allowing re-export of C symbols from `cdylib` crates:
rust-lang/rfcs#2771.

Addendum 2: When our supported platforms start shipping with GNU as >=
2.35 and/or Clang 13, we may want to add `,remove` to the `.symver`
directive arguments to lift the restriction that
`aziot-key-openssl-engine-shared` cannot be included in tests[^5].

[^1]: https://wiki.ubuntu.com/ToolChain/LTO
[^2]: Azure@f66c155
[^3]: https://github.com/rust-lang/cc-rs/releases/tag/1.0.74
[^4]: `..@` instead of `..@@` since we do not define a version
    node name, meaning `..@@` leads to symbol duplication.
[^5]: https://maskray.me/blog/2020-11-26-all-about-symbol-versioning
  • Loading branch information
gordonwang0 authored and damonbarry committed Feb 9, 2023
1 parent 84694c8 commit e9babdc
Show file tree
Hide file tree
Showing 5 changed files with 15 additions and 21 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 1 addition & 8 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -310,14 +310,7 @@ deb: dist
cp -R contrib/debian /tmp/aziot-identity-service-$(PACKAGE_VERSION)/
sed -i -e 's/@version@/$(PACKAGE_VERSION)/g; s/@release@/$(PACKAGE_RELEASE)/g' /tmp/aziot-identity-service-$(PACKAGE_VERSION)/debian/changelog

# Build package
# Note: This builds the `default` target before the normal Debian packaging (instead
# of as part of it) to workaround linker errors on Ubuntu 22.04 when building
# aziot-key-openssl-engine-shared. The extra flags to dpkg-buildpackage are to
# circumvent the default clean and to ignore build artifacts that will already exist.
cd /tmp/aziot-identity-service-$(PACKAGE_VERSION) && \
make RELEASE=1 V=1 ARCH=$(ARCH) && \
dpkg-buildpackage -us -uc $(DPKG_ARCH_FLAGS) -nc -tc -F -I=vendor -I=target --source-option=--extend-diff-ignore="target|vendor|third-party|keys\.generated\.rs"
cd /tmp/aziot-identity-service-$(PACKAGE_VERSION) && dpkg-buildpackage -us -uc $(DPKG_ARCH_FLAGS)

# rpm
#
Expand Down
2 changes: 1 addition & 1 deletion ci/install-build-deps.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ case "$OS:$ARCH" in

yum install -y centos-release-scl epel-release
yum install -y \
autoconf autoconf-archive automake clang curl devtoolset-9-gcc devtoolset-9-gcc-c++ \
autoconf autoconf-archive automake curl devtoolset-9-gcc devtoolset-9-gcc-c++ \
git jq libcurl-devel libtool llvm-toolset-7-clang llvm-toolset-7-llvm-devel \
make openssl openssl-devel pkgconfig

Expand Down
11 changes: 9 additions & 2 deletions key/aziot-key-openssl-engine-shared/build/engine.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,19 @@ int aziot_key_openssl_engine_shared_bind(ENGINE* e, const char* id);
* So the bind_engine and v_check functions generated by these two macros will not actually be exported by the final cdylib. It's not possible
* to provide a custom version script via `cargo:rustc-cdylib-link-arg` because it conflicts with the one generated by rustc.
*
* So we have to use the other way mentioned by `ld`'s docs, `__asm__(".symver")`
* So we have to use the other way mentioned by `ld`'s docs[1], `__asm__(".symver")`. We also need to add `__attribute__((used))` since otherwise
* the versioned symbol is optimized out by LTO.
*
* Unfortunately this trick means this crate cannot be compiled for tests since the linker will see duplicate symbols, so every invocation of
* `cargo test --all` or `cargo clippy --tests --all` has to also exclude this crate with `--exclude`.
* `cargo test --all` or `cargo clippy --tests --all` has to also exclude this crate with `--exclude`. When all of our platforms provide
* GNU as >= 2.35 and/or Clang >= 13, we can lift this restriction by appending `,remove` to the `.symver` directive arguments[2].
*
* [1]: https://sourceware.org/binutils/docs/ld/VERSION.html
* [2]: https://maskray.me/blog/2020-11-26-all-about-symbol-versioning
*/
__attribute__((used))
IMPLEMENT_DYNAMIC_BIND_FN(aziot_key_openssl_engine_shared_bind);
__asm__(".symver bind_engine,bind_engine@@");
__attribute__((used))
IMPLEMENT_DYNAMIC_CHECK_FN();
__asm__(".symver v_check,v_check@@");
10 changes: 2 additions & 8 deletions key/aziot-key-openssl-engine-shared/build/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#![warn(clippy::all, clippy::pedantic)]

fn main() {
println!("cargo:rerun-if-changed=build/engine.c");
println!("cargo:rerun-if-changed=build/");

openssl_build::define_version_number_cfg();

Expand All @@ -13,13 +13,7 @@ fn main() {
// Since we are going to use the generated archive in a shared
// library, we need +whole-archive to be set. See:
// https://github.com/rust-lang/rust/blob/1.61.0/RELEASES.md#compatibility-notes
.cargo_metadata(false)
.link_lib_modifier("+whole-archive")
.file("build/engine.c")
.compile("aziot_key_openssl_shared_engine_wrapper");

println!("cargo:rustc-link-lib=static:+whole-archive=aziot_key_openssl_shared_engine_wrapper");
println!(
"cargo:rustc-link-search=native={}",
std::env::var("OUT_DIR").unwrap()
);
}

0 comments on commit e9babdc

Please sign in to comment.