1// Copyright (c) 2018, Google Inc.
2//
3// Permission to use, copy, modify, and/or distribute this software for any
4// purpose with or without fee is hereby granted, provided that the above
5// copyright notice and this permission notice appear in all copies.
6//
7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
10// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
12// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
13// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
15// read_symbols scans one or more .a files and, for each object contained in
16// the .a files, reads the list of symbols in that object file.
17package main
18
19import (
20	"bytes"
21	"debug/elf"
22	"debug/macho"
23	"debug/pe"
24	"flag"
25	"fmt"
26	"os"
27	"runtime"
28	"sort"
29	"strings"
30
31	"boringssl.googlesource.com/boringssl/util/ar"
32)
33
34const (
35	ObjFileFormatELF   = "elf"
36	ObjFileFormatMachO = "macho"
37	ObjFileFormatPE    = "pe"
38)
39
40var (
41	outFlag       = flag.String("out", "-", "File to write output symbols")
42	objFileFormat = flag.String("obj-file-format", defaultObjFileFormat(runtime.GOOS), "Object file format to expect (options are elf, macho, pe)")
43)
44
45func defaultObjFileFormat(goos string) string {
46	switch goos {
47	case "linux":
48		return ObjFileFormatELF
49	case "darwin":
50		return ObjFileFormatMachO
51	case "windows":
52		return ObjFileFormatPE
53	default:
54		// By returning a value here rather than panicking, the user can still
55		// cross-compile from an unsupported platform to a supported platform by
56		// overriding this default with a flag. If the user doesn't provide the
57		// flag, we will panic during flag parsing.
58		return "unsupported"
59	}
60}
61
62func printAndExit(format string, args ...interface{}) {
63	s := fmt.Sprintf(format, args...)
64	fmt.Fprintln(os.Stderr, s)
65	os.Exit(1)
66}
67
68func main() {
69	flag.Parse()
70	if flag.NArg() < 1 {
71		printAndExit("Usage: %s [-out OUT] [-obj-file-format FORMAT] ARCHIVE_FILE [ARCHIVE_FILE [...]]", os.Args[0])
72	}
73	archiveFiles := flag.Args()
74
75	out := os.Stdout
76	if *outFlag != "-" {
77		var err error
78		out, err = os.Create(*outFlag)
79		if err != nil {
80			printAndExit("Error opening %q: %s", *outFlag, err)
81		}
82		defer out.Close()
83	}
84
85	var symbols []string
86	// Only add first instance of any symbol; keep track of them in this map.
87	added := make(map[string]struct{})
88	for _, archive := range archiveFiles {
89		f, err := os.Open(archive)
90		if err != nil {
91			printAndExit("Error opening %s: %s", archive, err)
92		}
93		objectFiles, err := ar.ParseAR(f)
94		f.Close()
95		if err != nil {
96			printAndExit("Error parsing %s: %s", archive, err)
97		}
98
99		for name, contents := range objectFiles {
100			syms, err := listSymbols(contents)
101			if err != nil {
102				printAndExit("Error listing symbols from %q in %q: %s", name, archive, err)
103			}
104			for _, s := range syms {
105				if _, ok := added[s]; !ok {
106					added[s] = struct{}{}
107					symbols = append(symbols, s)
108				}
109			}
110		}
111	}
112
113	sort.Strings(symbols)
114	for _, s := range symbols {
115		var skipSymbols = []string{
116			// Inline functions, etc., from the compiler or language
117			// runtime will naturally end up in the library, to be
118			// deduplicated against other object files. Such symbols
119			// should not be prefixed. It is a limitation of this
120			// symbol-prefixing strategy that we cannot distinguish
121			// our own inline symbols (which should be prefixed)
122			// from the system's (which should not), so we blacklist
123			// known system symbols.
124			"__local_stdio_printf_options",
125			"__local_stdio_scanf_options",
126			"_vscprintf",
127			"_vscprintf_l",
128			"_vsscanf_l",
129			"_xmm",
130			"sscanf",
131			"vsnprintf",
132			// sdallocx is a weak symbol and intended to merge with
133			// the real one, if present.
134			"sdallocx",
135		}
136		var skip bool
137		for _, sym := range skipSymbols {
138			if sym == s {
139				skip = true
140				break
141			}
142		}
143		if skip || isCXXSymbol(s) || strings.HasPrefix(s, "__real@") || strings.HasPrefix(s, "__x86.get_pc_thunk.") {
144			continue
145		}
146		if _, err := fmt.Fprintln(out, s); err != nil {
147			printAndExit("Error writing to %s: %s", *outFlag, err)
148		}
149	}
150}
151
152func isCXXSymbol(s string) bool {
153	if *objFileFormat == ObjFileFormatPE {
154		return strings.HasPrefix(s, "?")
155	}
156	return strings.HasPrefix(s, "_Z")
157}
158
159// listSymbols lists the exported symbols from an object file.
160func listSymbols(contents []byte) ([]string, error) {
161	switch *objFileFormat {
162	case ObjFileFormatELF:
163		return listSymbolsELF(contents)
164	case ObjFileFormatMachO:
165		return listSymbolsMachO(contents)
166	case ObjFileFormatPE:
167		return listSymbolsPE(contents)
168	default:
169		return nil, fmt.Errorf("unsupported object file format %q", *objFileFormat)
170	}
171}
172
173func listSymbolsELF(contents []byte) ([]string, error) {
174	f, err := elf.NewFile(bytes.NewReader(contents))
175	if err != nil {
176		return nil, err
177	}
178	syms, err := f.Symbols()
179	if err != nil {
180		return nil, err
181	}
182
183	var names []string
184	for _, sym := range syms {
185		// Only include exported, defined symbols
186		if elf.ST_BIND(sym.Info) != elf.STB_LOCAL && sym.Section != elf.SHN_UNDEF {
187			names = append(names, sym.Name)
188		}
189	}
190	return names, nil
191}
192
193func listSymbolsMachO(contents []byte) ([]string, error) {
194	f, err := macho.NewFile(bytes.NewReader(contents))
195	if err != nil {
196		return nil, err
197	}
198	if f.Symtab == nil {
199		return nil, nil
200	}
201	var names []string
202	for _, sym := range f.Symtab.Syms {
203		// Source: https://opensource.apple.com/source/xnu/xnu-3789.51.2/EXTERNAL_HEADERS/mach-o/nlist.h.auto.html
204		const (
205			N_PEXT uint8 = 0x10 // Private external symbol bit
206			N_EXT  uint8 = 0x01 // External symbol bit, set for external symbols
207			N_TYPE uint8 = 0x0e // mask for the type bits
208
209			N_UNDF uint8 = 0x0 // undefined, n_sect == NO_SECT
210			N_ABS  uint8 = 0x2 // absolute, n_sect == NO_SECT
211			N_SECT uint8 = 0xe // defined in section number n_sect
212			N_PBUD uint8 = 0xc // prebound undefined (defined in a dylib)
213			N_INDR uint8 = 0xa // indirect
214		)
215
216		// Only include exported, defined symbols.
217		if sym.Type&N_EXT != 0 && sym.Type&N_TYPE != N_UNDF {
218			if len(sym.Name) == 0 || sym.Name[0] != '_' {
219				return nil, fmt.Errorf("unexpected symbol without underscore prefix: %q", sym.Name)
220			}
221			names = append(names, sym.Name[1:])
222		}
223	}
224	return names, nil
225}
226
227func listSymbolsPE(contents []byte) ([]string, error) {
228	f, err := pe.NewFile(bytes.NewReader(contents))
229	if err != nil {
230		return nil, err
231	}
232	var ret []string
233	for _, sym := range f.Symbols {
234		const (
235			// https://docs.microsoft.com/en-us/windows/desktop/debug/pe-format#section-number-values
236			IMAGE_SYM_UNDEFINED = 0
237			// https://docs.microsoft.com/en-us/windows/desktop/debug/pe-format#storage-class
238			IMAGE_SYM_CLASS_EXTERNAL = 2
239		)
240		if sym.SectionNumber != IMAGE_SYM_UNDEFINED && sym.StorageClass == IMAGE_SYM_CLASS_EXTERNAL {
241			name := sym.Name
242			if f.Machine == pe.IMAGE_FILE_MACHINE_I386 {
243				// On 32-bit Windows, C symbols are decorated by calling
244				// convention.
245				// https://msdn.microsoft.com/en-us/library/56h2zst2.aspx#FormatC
246				if strings.HasPrefix(name, "_") || strings.HasPrefix(name, "@") {
247					// __cdecl, __stdcall, or __fastcall. Remove the prefix and
248					// suffix, if present.
249					name = name[1:]
250					if idx := strings.LastIndex(name, "@"); idx >= 0 {
251						name = name[:idx]
252					}
253				} else if idx := strings.LastIndex(name, "@@"); idx >= 0 {
254					// __vectorcall. Remove the suffix.
255					name = name[:idx]
256				}
257			}
258			ret = append(ret, name)
259		}
260	}
261	return ret, nil
262}
263