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