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)
+}