Add util/read_symbols.go
- Add util/read_symbols.go to read exported symbols from an AR
file for use with the symbol prefixing feature
- Move util/fipstools/fipscommon/ar.go -> util/ar/ar.go
- util/ar/ar.go: Support BSD-style AR files
Change-Id: I171b3b952e69c4b87ac04751b7dba3ea9bc2504e
Reviewed-on: https://boringssl-review.googlesource.com/32024
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/BUILDING.md b/BUILDING.md
index ab9b71d..01f5480 100644
--- a/BUILDING.md
+++ b/BUILDING.md
@@ -128,7 +128,11 @@
`/path/to/symbols.txt`.
It is currently the caller's responsibility to create and maintain the list of
-symbols to be prefixed.
+symbols to be prefixed. Alternatively, `util/read_symbols.go` reads the list of
+exported symbols from a `.a` file, and can be used in a build script to generate
+the symbol list on the fly (by building without prefixing, using
+`read_symbols.go` to construct a symbol list, and then building again with
+prefixing).
This mechanism is under development and may change over time. Please contact the
BoringSSL maintainers if making use of it.
diff --git a/util/fipstools/fipscommon/ar.go b/util/ar/ar.go
similarity index 79%
rename from util/fipstools/fipscommon/ar.go
rename to util/ar/ar.go
index 85b378d..f5dee62 100644
--- a/util/fipstools/fipscommon/ar.go
+++ b/util/ar/ar.go
@@ -14,11 +14,12 @@
// ar.go contains functions for parsing .a archive files.
-package fipscommon
+package ar
import (
"bytes"
"errors"
+ "fmt"
"io"
"strconv"
"strings"
@@ -113,6 +114,33 @@
name = strings.TrimRight(name, "/")
}
+ // Post-processing for BSD:
+ // https://en.wikipedia.org/wiki/Ar_(Unix)#BSD_variant
+ //
+ // If the name is of the form #1/XXX, XXX identifies the length of the
+ // name, and the name itself is stored as a prefix of the data, possibly
+ // null-padded.
+
+ var namelen uint
+ n, err := fmt.Sscanf(name, "#1/%d", &namelen)
+ if err == nil && n == 1 && len(contents) >= int(namelen) {
+ name = string(contents[:namelen])
+ contents = contents[namelen:]
+
+ // Names can be null padded; find the first null (if any). Note that
+ // this also handles the case of a null followed by non-null
+ // characters. It's not clear whether those can ever show up in
+ // practice, but we might as well handle them in case they can show
+ // up.
+ var null int
+ for ; null < len(name); null++ {
+ if name[null] == 0 {
+ break
+ }
+ }
+ name = name[:null]
+ }
+
ret[name] = contents
}
diff --git a/util/fipstools/delocate/delocate.go b/util/fipstools/delocate/delocate.go
index a8c4fd0..4734285 100644
--- a/util/fipstools/delocate/delocate.go
+++ b/util/fipstools/delocate/delocate.go
@@ -26,6 +26,7 @@
"strconv"
"strings"
+ "boringssl.googlesource.com/boringssl/util/ar"
"boringssl.googlesource.com/boringssl/util/fipstools/fipscommon"
)
@@ -1425,7 +1426,7 @@
}
defer arFile.Close()
- ar, err := fipscommon.ParseAR(arFile)
+ ar, err := ar.ParseAR(arFile)
if err != nil {
return err
}
diff --git a/util/fipstools/inject-hash/inject-hash.go b/util/fipstools/inject-hash/inject-hash.go
index 14418a3..d079654 100644
--- a/util/fipstools/inject-hash/inject-hash.go
+++ b/util/fipstools/inject-hash/inject-hash.go
@@ -29,6 +29,7 @@
"io/ioutil"
"os"
+ "boringssl.googlesource.com/boringssl/util/ar"
"boringssl.googlesource.com/boringssl/util/fipstools/fipscommon"
)
@@ -45,7 +46,7 @@
}
defer arFile.Close()
- ar, err := fipscommon.ParseAR(arFile)
+ ar, err := ar.ParseAR(arFile)
if err != nil {
return err
}
diff --git a/util/read_symbols.go b/util/read_symbols.go
new file mode 100644
index 0000000..5e3a177
--- /dev/null
+++ b/util/read_symbols.go
@@ -0,0 +1,170 @@
+// Copyright (c) 2018, Google Inc.
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+// read_symbols.go scans one or more .a files and, for each object contained in
+// the .a files, reads the list of symbols in that object file.
+package main
+
+import (
+ "bytes"
+ "debug/elf"
+ "debug/macho"
+ "flag"
+ "fmt"
+ "os"
+ "runtime"
+ "sort"
+ "strings"
+
+ "boringssl.googlesource.com/boringssl/util/ar"
+)
+
+const (
+ ObjFileFormatELF = "elf"
+ ObjFileFormatMachO = "macho"
+)
+
+var outFlag = flag.String("out", "-", "File to write output symbols")
+var objFileFormat = flag.String("obj-file-format", defaultObjFileFormat(runtime.GOOS), "Object file format to expect (options are elf, macho)")
+
+func defaultObjFileFormat(goos string) string {
+ switch goos {
+ case "linux":
+ return ObjFileFormatELF
+ case "darwin":
+ return ObjFileFormatMachO
+ default:
+ // By returning a value here rather than panicking, the user can still
+ // cross-compile from an unsupported platform to a supported platform by
+ // overriding this default with a flag. If the user doesn't provide the
+ // flag, we will panic during flag parsing.
+ return "unsupported"
+ }
+}
+
+func main() {
+ flag.Parse()
+ if flag.NArg() < 1 {
+ fmt.Fprintf(os.Stderr, "Usage: %s [-out OUT] [-obj-file-format FORMAT] ARCHIVE_FILE [ARCHIVE_FILE [...]]\n", os.Args[0])
+ os.Exit(1)
+ }
+ archiveFiles := flag.Args()
+
+ out := os.Stdout
+ if *outFlag != "-" {
+ var err error
+ out, err = os.Create(*outFlag)
+ nilOrPanic(err, "failed to open output file")
+ defer out.Close()
+ }
+
+ var symbols []string
+ // Only add first instance of any symbol; keep track of them in this map.
+ added := make(map[string]bool)
+ for _, archive := range archiveFiles {
+ f, err := os.Open(archive)
+ nilOrPanic(err, "failed to open archive file %s", archive)
+ objectFiles, err := ar.ParseAR(f)
+ nilOrPanic(err, "failed to read archive file %s", archive)
+
+ for name, contents := range objectFiles {
+ if !strings.HasSuffix(name, ".o") {
+ continue
+ }
+ for _, s := range listSymbols(name, contents) {
+ if !added[s] {
+ added[s] = true
+ symbols = append(symbols, s)
+ }
+ }
+ }
+ }
+ sort.Strings(symbols)
+ for _, s := range symbols {
+ // Filter out C++ mangled names.
+ prefix := "_Z"
+ if runtime.GOOS == "darwin" {
+ prefix = "__Z"
+ }
+ if !strings.HasPrefix(s, prefix) {
+ fmt.Fprintln(out, s)
+ }
+ }
+}
+
+// listSymbols lists the exported symbols from an object file.
+func listSymbols(name string, contents []byte) []string {
+ switch *objFileFormat {
+ case ObjFileFormatELF:
+ return listSymbolsELF(name, contents)
+ case ObjFileFormatMachO:
+ return listSymbolsMachO(name, contents)
+ default:
+ panic(fmt.Errorf("unsupported object file format %v", *objFileFormat))
+ }
+}
+
+func listSymbolsELF(name string, contents []byte) []string {
+ f, err := elf.NewFile(bytes.NewReader(contents))
+ nilOrPanic(err, "failed to parse ELF file %s", name)
+ syms, err := f.Symbols()
+ nilOrPanic(err, "failed to read symbol names from ELF file %s", name)
+
+ var names []string
+ for _, sym := range syms {
+ // Only include exported, defined symbols
+ if elf.ST_BIND(sym.Info) != elf.STB_LOCAL && sym.Section != elf.SHN_UNDEF {
+ names = append(names, sym.Name)
+ }
+ }
+ return names
+}
+
+func listSymbolsMachO(name string, contents []byte) []string {
+ f, err := macho.NewFile(bytes.NewReader(contents))
+ nilOrPanic(err, "failed to parse Mach-O file %s", name)
+ if f.Symtab == nil {
+ return nil
+ }
+ var names []string
+ for _, sym := range f.Symtab.Syms {
+ // Source: https://opensource.apple.com/source/xnu/xnu-3789.51.2/EXTERNAL_HEADERS/mach-o/nlist.h.auto.html
+ const (
+ N_PEXT uint8 = 0x10 // Private external symbol bit
+ N_EXT uint8 = 0x01 // External symbol bit, set for external symbols
+ N_TYPE uint8 = 0x0e // mask for the type bits
+
+ N_UNDF uint8 = 0x0 // undefined, n_sect == NO_SECT
+ N_ABS uint8 = 0x2 // absolute, n_sect == NO_SECT
+ N_SECT uint8 = 0xe // defined in section number n_sect
+ N_PBUD uint8 = 0xc // prebound undefined (defined in a dylib)
+ N_INDR uint8 = 0xa // indirect
+ )
+
+ // Only include exported, defined symbols.
+ if sym.Type&N_EXT != 0 && sym.Type&N_TYPE != N_UNDF {
+ if len(sym.Name) == 0 || sym.Name[0] != '_' {
+ panic(fmt.Errorf("unexpected symbol without underscore prefix: %v", sym.Name))
+ }
+ names = append(names, sym.Name[1:])
+ }
+ }
+ return names
+}
+
+func nilOrPanic(err error, f string, args ...interface{}) {
+ if err != nil {
+ panic(fmt.Errorf(f+": %v", append(args, err)...))
+ }
+}