Implement symbol listing during build in Rust
With this change Mundane's build system itself is no longer dependent on
Go (it's still required to build BoringSSL)
Change-Id: Ib060d8463680d7af266ed6bbb87ec732631e2ef0
diff --git a/Cargo.toml b/Cargo.toml
index c12ea0a..c669a89 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -27,6 +27,10 @@
"boringssl/boringssl/crypto/cipher_extra/test/*"
]
+build = "build/main.rs"
+[build-dependencies]
+goblin = "0.0.24"
+
# If you edit this list, make sure to update test.sh!
[features]
default = ["rsa-test-generate-large-keys"]
diff --git a/boringssl/README.md b/boringssl/README.md
index f097532..7f9a6fe 100644
--- a/boringssl/README.md
+++ b/boringssl/README.md
@@ -66,10 +66,10 @@
dynamically at build time by doing a two-phase build.
In the first phase, we build BoringSSL as normal, with no symbol prefixing.
-Then, using a Go program provided by BoringSSL, we scrape the list of symbols
-from the build artifacts. Using this list, we run the build again - the second
-phase - this time using BoringSSL's symbol prefixing feature. We use the
-artifacts from the second build when performing the final Rust build.
+Then, the build script scrapes the list of symbols from the build artifacts.
+Using this list, we run the build again - the second phase - this time using
+BoringSSL's symbol prefixing feature. We use the artifacts from the second
+build when performing the final Rust build.
### Library names
@@ -93,4 +93,4 @@
### Testing
In order to test that symbol prefixing is working properly, use the
-`test_symbol_conflicts.sh` script in this directory.
\ No newline at end of file
+`test_symbol_conflicts.sh` script in this directory.
diff --git a/build.rs b/build/main.rs
similarity index 84%
rename from build.rs
rename to build/main.rs
index 4f5c438..3c4f026 100644
--- a/build.rs
+++ b/build/main.rs
@@ -6,9 +6,11 @@
// This build script is responsible for building BoringSSL with the appropriate
// symbol prefix. See boringssl/README.md for details.
+mod obj;
use std::env;
use std::fs;
+use std::io::Write;
use std::process::{Command, Stdio};
// Relative to CARGO_MANIFEST_DIR
@@ -90,24 +92,35 @@
build(&abs_build_dir_1, &[&abs_boringssl_src]);
- // 'go run' requires that we're cd'd into a subdirectory of the Go module
- // root in order for Go modules to work
- let orig = env::current_dir().expect("could not get current directory");
- env::set_current_dir(&format!("{}", &abs_boringssl_src))
- .expect("could not set current directory");
- // GOPATH should not be respected; we want the borringssl go.mod.
- env::remove_var("GOPATH");
- run(
- "go",
- &[
- "run",
- "util/read_symbols.go",
- "-out",
- &abs_symbol_file,
- &format!("{}/crypto/libcrypto.a", &abs_build_dir_1),
- ],
- );
- env::set_current_dir(orig).expect("could not set current directory");
+ let mut symbols = obj::exported_symbols(&format!("{}/crypto/libcrypto.a", &abs_build_dir_1))
+ .unwrap_or_else(|e| panic!("failed to read list of symbols exported by libcrypto: {}", e));
+ if symbols.is_empty() {
+ panic!("no exported symbols found in libcrypto");
+ }
+ // Inlined functions from the compiler or runtime, should not be prefixed.
+ let symbol_blacklist = [
+ // Present in Windows builds.
+ "__local_stdio_printf_options",
+ "__local_stdio_scanf_options",
+ "_vscprintf",
+ "_vscprintf_l",
+ "_vsscanf_l",
+ "_xmm",
+ "sscanf",
+ "vsnprintf",
+ // Present in Linux and macOS builds.
+ "sdallocx",
+ ];
+ for blacklisted_symbol in &symbol_blacklist {
+ symbols.remove(*blacklisted_symbol);
+ }
+ let mut symbols_file = fs::File::create(&abs_symbol_file)
+ .expect("could not create symbols file");
+ for symbol in symbols {
+ write!(symbols_file, "{}\n", symbol).expect("write to symbols file failed");
+ }
+ // Make sure the file is fully written to disc before pasing it to BoringSSL's build system.
+ symbols_file.sync_all().expect("failed to sync the symbols file to filesystem");
build(
&abs_build_dir_2,
diff --git a/build/obj.rs b/build/obj.rs
new file mode 100644
index 0000000..9df92a2
--- /dev/null
+++ b/build/obj.rs
@@ -0,0 +1,92 @@
+// Copyright 2018 Google LLC
+//
+// Use of this source code is governed by an MIT-style
+// license that can be found in the LICENSE file or at
+// https://opensource.org/licenses/MIT.
+
+use std::collections::BTreeSet;
+use std::convert::TryFrom;
+use std::fs::File;
+use std::io::Read;
+extern crate goblin;
+use self::goblin::elf;
+use self::goblin::mach;
+
+use std::error;
+
+/// List symbols exported by the file (expected to be either a static library or an object file).
+pub fn exported_symbols(file: &str) -> Result<BTreeSet<String>, Box<error::Error>> {
+ let mut bytes = Vec::new();
+ File::open(file)?.read_to_end(&mut bytes)?;
+ binary_exported_symbols(&bytes)
+}
+
+fn binary_exported_symbols(bytes: &[u8]) -> Result<BTreeSet<String>, Box<error::Error>> {
+ let mut symbols = BTreeSet::new();
+ match goblin::Object::parse(bytes)? {
+ goblin::Object::Archive(archive) => {
+ for (_member_name, member, _symbol_table) in archive.summarize() {
+ // Member size is likely to be reported incorrectly by its header.
+ assert!(
+ member.offset + (member.size() as u64) <= (bytes.len() as u64),
+ format!(
+ "archive member is outside of boundaries; offset: {}, size: {}",
+ member.offset,
+ member.size()
+ )
+ );
+ symbols.extend(binary_exported_symbols(
+ &bytes[member.offset as usize..member.offset as usize + member.size()],
+ )?);
+ }
+ }
+ goblin::Object::Elf(elf) => {
+ for symbol in elf.syms.iter() {
+ let name = elf
+ .strtab
+ .get(symbol.st_name)
+ .unwrap_or_else(|| {
+ panic!(
+ "incorrect symbol name table offset {} for: {:?}",
+ symbol.st_name, symbol
+ )
+ })
+ .expect("failed to read symbol name");
+ if !name.is_empty()
+ && symbol.st_bind() != elf::sym::STB_LOCAL
+ && u32::try_from(symbol.st_shndx).unwrap() != elf::section_header::SHN_UNDEF
+ {
+ symbols.insert(name.to_string());
+ }
+ }
+ }
+ goblin::Object::Mach(mach) => match mach {
+ mach::Mach::Binary(obj) => {
+ for symbol in obj.symbols() {
+ let (name, nlist) = symbol?;
+ if nlist.is_global() && !nlist.is_undefined() {
+ // Strip underscore symbol prefix.
+ symbols.insert(name[1..].to_string());
+ }
+ }
+ }
+ mach::Mach::Fat(_obj) => panic!("unexpected multiarch Mach-O binary found in archive"),
+ },
+ // Symbols are stripped out of PE file.
+ goblin::Object::PE(_pe) => panic!("unexpected PE executable found in archive"),
+ // goblin::Object::parse doesn't detect COFF binaries.
+ goblin::Object::Unknown(_magic) => {
+ let coff = goblin::pe::Coff::parse(bytes)?;
+ for (_size, _name, symbol) in coff.symbols.iter() {
+ if symbol.section_number != goblin::pe::symbol::IMAGE_SYM_UNDEFINED
+ && symbol.storage_class == goblin::pe::symbol::IMAGE_SYM_CLASS_EXTERNAL
+ {
+ // _name will only be populated for names no longer than 8 characters,
+ // otherwise string table lookup is necessary.
+ symbols.insert(symbol.name(&coff.strings)?.to_string());
+ }
+ }
+ }
+ };
+ Ok(symbols)
+}