summaryrefslogtreecommitdiff
path: root/src/util/read_symbols.go
blob: 5e3a17763dddb4adba6b1c2fef9158878e1f3ef4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
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)...))
	}
}