|
- // 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 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"
- "debug/pe"
- "flag"
- "fmt"
- "os"
- "runtime"
- "sort"
- "strings"
-
- "boringssl.googlesource.com/boringssl/util/ar"
- )
-
- const (
- ObjFileFormatELF = "elf"
- ObjFileFormatMachO = "macho"
- ObjFileFormatPE = "pe"
- )
-
- var (
- outFlag = flag.String("out", "-", "File to write output symbols")
- objFileFormat = flag.String("obj-file-format", defaultObjFileFormat(runtime.GOOS), "Object file format to expect (options are elf, macho, pe)")
- )
-
- func defaultObjFileFormat(goos string) string {
- switch goos {
- case "linux":
- return ObjFileFormatELF
- case "darwin":
- return ObjFileFormatMachO
- case "windows":
- return ObjFileFormatPE
- 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 printAndExit(format string, args ...interface{}) {
- s := fmt.Sprintf(format, args...)
- fmt.Fprintln(os.Stderr, s)
- os.Exit(1)
- }
-
- func main() {
- flag.Parse()
- if flag.NArg() < 1 {
- printAndExit("Usage: %s [-out OUT] [-obj-file-format FORMAT] ARCHIVE_FILE [ARCHIVE_FILE [...]]", os.Args[0])
- }
- archiveFiles := flag.Args()
-
- out := os.Stdout
- if *outFlag != "-" {
- var err error
- out, err = os.Create(*outFlag)
- if err != nil {
- printAndExit("Error opening %q: %s", *outFlag, err)
- }
- defer out.Close()
- }
-
- var symbols []string
- // Only add first instance of any symbol; keep track of them in this map.
- added := make(map[string]struct{})
- for _, archive := range archiveFiles {
- f, err := os.Open(archive)
- if err != nil {
- printAndExit("Error opening %s: %s", archive, err)
- }
- objectFiles, err := ar.ParseAR(f)
- f.Close()
- if err != nil {
- printAndExit("Error parsing %s: %s", archive, err)
- }
-
- for name, contents := range objectFiles {
- syms, err := listSymbols(contents)
- if err != nil {
- printAndExit("Error listing symbols from %q in %q: %s", name, archive, err)
- }
- for _, s := range syms {
- if _, ok := added[s]; !ok {
- added[s] = struct{}{}
- symbols = append(symbols, s)
- }
- }
- }
- }
-
- sort.Strings(symbols)
- for _, s := range symbols {
- var skipSymbols = []string{
- // Inline functions, etc., from the compiler or language
- // runtime will naturally end up in the library, to be
- // deduplicated against other object files. Such symbols
- // should not be prefixed. It is a limitation of this
- // symbol-prefixing strategy that we cannot distinguish
- // our own inline symbols (which should be prefixed)
- // from the system's (which should not), so we blacklist
- // known system symbols.
- "__local_stdio_printf_options",
- "__local_stdio_scanf_options",
- "_vscprintf",
- "_vscprintf_l",
- "_vsscanf_l",
- "_xmm",
- "sscanf",
- "vsnprintf",
- // sdallocx is a weak symbol and intended to merge with
- // the real one, if present.
- "sdallocx",
- }
- var skip bool
- for _, sym := range skipSymbols {
- if sym == s {
- skip = true
- break
- }
- }
- if skip || isCXXSymbol(s) || strings.HasPrefix(s, "__real@") || strings.HasPrefix(s, "__x86.get_pc_thunk.") {
- continue
- }
- if _, err := fmt.Fprintln(out, s); err != nil {
- printAndExit("Error writing to %s: %s", *outFlag, err)
- }
- }
- }
-
- func isCXXSymbol(s string) bool {
- if *objFileFormat == ObjFileFormatPE {
- return strings.HasPrefix(s, "?")
- }
- return strings.HasPrefix(s, "_Z")
- }
-
- // listSymbols lists the exported symbols from an object file.
- func listSymbols(contents []byte) ([]string, error) {
- switch *objFileFormat {
- case ObjFileFormatELF:
- return listSymbolsELF(contents)
- case ObjFileFormatMachO:
- return listSymbolsMachO(contents)
- case ObjFileFormatPE:
- return listSymbolsPE(contents)
- default:
- return nil, fmt.Errorf("unsupported object file format %q", *objFileFormat)
- }
- }
-
- func listSymbolsELF(contents []byte) ([]string, error) {
- f, err := elf.NewFile(bytes.NewReader(contents))
- if err != nil {
- return nil, err
- }
- syms, err := f.Symbols()
- if err != nil {
- return nil, err
- }
-
- 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, nil
- }
-
- func listSymbolsMachO(contents []byte) ([]string, error) {
- f, err := macho.NewFile(bytes.NewReader(contents))
- if err != nil {
- return nil, err
- }
- if f.Symtab == nil {
- return nil, 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] != '_' {
- return nil, fmt.Errorf("unexpected symbol without underscore prefix: %q", sym.Name)
- }
- names = append(names, sym.Name[1:])
- }
- }
- return names, nil
- }
-
- func listSymbolsPE(contents []byte) ([]string, error) {
- f, err := pe.NewFile(bytes.NewReader(contents))
- if err != nil {
- return nil, err
- }
- var ret []string
- for _, sym := range f.Symbols {
- const (
- // https://docs.microsoft.com/en-us/windows/desktop/debug/pe-format#section-number-values
- IMAGE_SYM_UNDEFINED = 0
- // https://docs.microsoft.com/en-us/windows/desktop/debug/pe-format#storage-class
- IMAGE_SYM_CLASS_EXTERNAL = 2
- )
- if sym.SectionNumber != IMAGE_SYM_UNDEFINED && sym.StorageClass == IMAGE_SYM_CLASS_EXTERNAL {
- name := sym.Name
- if f.Machine == pe.IMAGE_FILE_MACHINE_I386 {
- // On 32-bit Windows, C symbols are decorated by calling
- // convention.
- // https://msdn.microsoft.com/en-us/library/56h2zst2.aspx#FormatC
- if strings.HasPrefix(name, "_") || strings.HasPrefix(name, "@") {
- // __cdecl, __stdcall, or __fastcall. Remove the prefix and
- // suffix, if present.
- name = name[1:]
- if idx := strings.LastIndex(name, "@"); idx >= 0 {
- name = name[:idx]
- }
- } else if idx := strings.LastIndex(name, "@@"); idx >= 0 {
- // __vectorcall. Remove the suffix.
- name = name[:idx]
- }
- }
- ret = append(ret, name)
- }
- }
- return ret, nil
- }
|