Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 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 | |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 15 | // read_symbols scans one or more .a files and, for each object contained in |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 16 | // the .a files, reads the list of symbols in that object file. |
| 17 | package main |
| 18 | |
| 19 | import ( |
| 20 | "bytes" |
| 21 | "debug/elf" |
| 22 | "debug/macho" |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 23 | "debug/pe" |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 24 | "flag" |
| 25 | "fmt" |
| 26 | "os" |
| 27 | "runtime" |
| 28 | "sort" |
| 29 | "strings" |
| 30 | |
| 31 | "boringssl.googlesource.com/boringssl/util/ar" |
| 32 | ) |
| 33 | |
| 34 | const ( |
| 35 | ObjFileFormatELF = "elf" |
| 36 | ObjFileFormatMachO = "macho" |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 37 | ObjFileFormatPE = "pe" |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 38 | ) |
| 39 | |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 40 | var ( |
| 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 | ) |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 44 | |
| 45 | func defaultObjFileFormat(goos string) string { |
| 46 | switch goos { |
| 47 | case "linux": |
| 48 | return ObjFileFormatELF |
| 49 | case "darwin": |
| 50 | return ObjFileFormatMachO |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 51 | case "windows": |
| 52 | return ObjFileFormatPE |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 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 | |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 62 | func printAndExit(format string, args ...interface{}) { |
| 63 | s := fmt.Sprintf(format, args...) |
| 64 | fmt.Fprintln(os.Stderr, s) |
| 65 | os.Exit(1) |
| 66 | } |
| 67 | |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 68 | func main() { |
| 69 | flag.Parse() |
| 70 | if flag.NArg() < 1 { |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 71 | printAndExit("Usage: %s [-out OUT] [-obj-file-format FORMAT] ARCHIVE_FILE [ARCHIVE_FILE [...]]", os.Args[0]) |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 72 | } |
| 73 | archiveFiles := flag.Args() |
| 74 | |
| 75 | out := os.Stdout |
| 76 | if *outFlag != "-" { |
| 77 | var err error |
| 78 | out, err = os.Create(*outFlag) |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 79 | if err != nil { |
| 80 | printAndExit("Error opening %q: %s", *outFlag, err) |
| 81 | } |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 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. |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 87 | added := make(map[string]struct{}) |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 88 | for _, archive := range archiveFiles { |
| 89 | f, err := os.Open(archive) |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 90 | if err != nil { |
| 91 | printAndExit("Error opening %s: %s", archive, err) |
| 92 | } |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 93 | objectFiles, err := ar.ParseAR(f) |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 94 | f.Close() |
| 95 | if err != nil { |
| 96 | printAndExit("Error parsing %s: %s", archive, err) |
| 97 | } |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 98 | |
| 99 | for name, contents := range objectFiles { |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 100 | syms, err := listSymbols(contents) |
| 101 | if err != nil { |
| 102 | printAndExit("Error listing symbols from %q in %q: %s", name, archive, err) |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 103 | } |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 104 | for _, s := range syms { |
| 105 | if _, ok := added[s]; !ok { |
| 106 | added[s] = struct{}{} |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 107 | symbols = append(symbols, s) |
| 108 | } |
| 109 | } |
| 110 | } |
| 111 | } |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 112 | |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 113 | sort.Strings(symbols) |
| 114 | for _, s := range symbols { |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 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", |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 135 | } |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 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) |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 148 | } |
| 149 | } |
| 150 | } |
| 151 | |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 152 | func isCXXSymbol(s string) bool { |
| 153 | if *objFileFormat == ObjFileFormatPE { |
| 154 | return strings.HasPrefix(s, "?") |
| 155 | } |
| 156 | return strings.HasPrefix(s, "_Z") |
| 157 | } |
| 158 | |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 159 | // listSymbols lists the exported symbols from an object file. |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 160 | func listSymbols(contents []byte) ([]string, error) { |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 161 | switch *objFileFormat { |
| 162 | case ObjFileFormatELF: |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 163 | return listSymbolsELF(contents) |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 164 | case ObjFileFormatMachO: |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 165 | return listSymbolsMachO(contents) |
| 166 | case ObjFileFormatPE: |
| 167 | return listSymbolsPE(contents) |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 168 | default: |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 169 | return nil, fmt.Errorf("unsupported object file format %q", *objFileFormat) |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 170 | } |
| 171 | } |
| 172 | |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 173 | func listSymbolsELF(contents []byte) ([]string, error) { |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 174 | f, err := elf.NewFile(bytes.NewReader(contents)) |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 175 | if err != nil { |
| 176 | return nil, err |
| 177 | } |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 178 | syms, err := f.Symbols() |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 179 | if err != nil { |
| 180 | return nil, err |
| 181 | } |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 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 | } |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 190 | return names, nil |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 191 | } |
| 192 | |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 193 | func listSymbolsMachO(contents []byte) ([]string, error) { |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 194 | f, err := macho.NewFile(bytes.NewReader(contents)) |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 195 | if err != nil { |
| 196 | return nil, err |
| 197 | } |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 198 | if f.Symtab == nil { |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 199 | return nil, nil |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 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] != '_' { |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 219 | return nil, fmt.Errorf("unexpected symbol without underscore prefix: %q", sym.Name) |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 220 | } |
| 221 | names = append(names, sym.Name[1:]) |
| 222 | } |
| 223 | } |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 224 | return names, nil |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 225 | } |
| 226 | |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 227 | func listSymbolsPE(contents []byte) ([]string, error) { |
| 228 | f, err := pe.NewFile(bytes.NewReader(contents)) |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 229 | if err != nil { |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 230 | return nil, err |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 231 | } |
Robert Sloan | 6e8c959 | 2018-12-03 11:20:49 -0800 | [diff] [blame] | 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 |
Robert Sloan | f068def | 2018-10-10 18:45:40 -0700 | [diff] [blame] | 262 | } |