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