// 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. //go:build ignore // 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 ...any) { 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 skip 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.") || strings.HasPrefix(s, "DW.") { 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 == elf.ErrNoSymbols { return nil, nil } 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 }