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