1// Copyright 2022 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package main 16 17import ( 18 "flag" 19 "fmt" 20 "io/ioutil" 21 "os" 22 "strings" 23 24 "android/soong/cmd/symbols_map/symbols_map_proto" 25 "android/soong/response" 26 27 "github.com/google/blueprint/pathtools" 28 "google.golang.org/protobuf/encoding/prototext" 29 "google.golang.org/protobuf/proto" 30) 31 32// This tool is used to extract a hash from an elf file or an r8 dictionary and store it as a 33// textproto, or to merge multiple textprotos together. 34 35func main() { 36 var expandedArgs []string 37 for _, arg := range os.Args[1:] { 38 if strings.HasPrefix(arg, "@") { 39 f, err := os.Open(strings.TrimPrefix(arg, "@")) 40 if err != nil { 41 fmt.Fprintln(os.Stderr, err.Error()) 42 os.Exit(1) 43 } 44 45 respArgs, err := response.ReadRspFile(f) 46 f.Close() 47 if err != nil { 48 fmt.Fprintln(os.Stderr, err.Error()) 49 os.Exit(1) 50 } 51 expandedArgs = append(expandedArgs, respArgs...) 52 } else { 53 expandedArgs = append(expandedArgs, arg) 54 } 55 } 56 57 flags := flag.NewFlagSet("flags", flag.ExitOnError) 58 59 // Hide the flag package to prevent accidental references to flag instead of flags. 60 flag := struct{}{} 61 _ = flag 62 63 flags.Usage = func() { 64 fmt.Fprintf(flags.Output(), "Usage of %s:\n", os.Args[0]) 65 fmt.Fprintf(flags.Output(), " %s -elf|-r8 <input file> [-write_if_changed] <output file>\n", os.Args[0]) 66 fmt.Fprintf(flags.Output(), " %s -merge <output file> [-write_if_changed] [-ignore_missing_files] [-strip_prefix <prefix>] [<input file>...]\n", os.Args[0]) 67 fmt.Fprintln(flags.Output()) 68 69 flags.PrintDefaults() 70 } 71 72 elfFile := flags.String("elf", "", "extract identifier from an elf file") 73 r8File := flags.String("r8", "", "extract identifier from an r8 dictionary") 74 merge := flags.String("merge", "", "merge multiple identifier protos") 75 76 writeIfChanged := flags.Bool("write_if_changed", false, "only write output file if it is modified") 77 ignoreMissingFiles := flags.Bool("ignore_missing_files", false, "ignore missing input files in merge mode") 78 stripPrefix := flags.String("strip_prefix", "", "prefix to strip off of the location field in merge mode") 79 80 flags.Parse(expandedArgs) 81 82 if *merge != "" { 83 // If merge mode was requested perform the merge and exit early. 84 err := mergeProtos(*merge, flags.Args(), *stripPrefix, *writeIfChanged, *ignoreMissingFiles) 85 if err != nil { 86 fmt.Fprintf(os.Stderr, "failed to merge protos: %s", err) 87 os.Exit(1) 88 } 89 os.Exit(0) 90 } 91 92 if *elfFile == "" && *r8File == "" { 93 fmt.Fprintf(os.Stderr, "-elf or -r8 argument is required\n") 94 flags.Usage() 95 os.Exit(1) 96 } 97 98 if *elfFile != "" && *r8File != "" { 99 fmt.Fprintf(os.Stderr, "only one of -elf or -r8 argument is allowed\n") 100 flags.Usage() 101 os.Exit(1) 102 } 103 104 if flags.NArg() != 1 { 105 flags.Usage() 106 os.Exit(1) 107 } 108 109 output := flags.Arg(0) 110 111 var identifier string 112 var location string 113 var typ symbols_map_proto.Mapping_Type 114 var err error 115 116 if *elfFile != "" { 117 typ = symbols_map_proto.Mapping_ELF 118 location = *elfFile 119 identifier, err = elfIdentifier(*elfFile, true) 120 if err != nil { 121 fmt.Fprintf(os.Stderr, "error reading elf identifier: %s\n", err) 122 os.Exit(1) 123 } 124 } else if *r8File != "" { 125 typ = symbols_map_proto.Mapping_R8 126 identifier, err = r8Identifier(*r8File) 127 location = *r8File 128 if err != nil { 129 fmt.Fprintf(os.Stderr, "error reading r8 identifier: %s\n", err) 130 os.Exit(1) 131 } 132 } else { 133 panic("shouldn't get here") 134 } 135 136 mapping := symbols_map_proto.Mapping{ 137 Identifier: proto.String(identifier), 138 Location: proto.String(location), 139 Type: typ.Enum(), 140 } 141 142 err = writeTextProto(output, &mapping, *writeIfChanged) 143 if err != nil { 144 fmt.Fprintf(os.Stderr, "error writing output: %s\n", err) 145 os.Exit(1) 146 } 147} 148 149// writeTextProto writes a proto to an output file as a textproto, optionally leaving the file 150// unmodified if it was already up to date. 151func writeTextProto(output string, message proto.Message, writeIfChanged bool) error { 152 marshaller := prototext.MarshalOptions{Multiline: true} 153 data, err := marshaller.Marshal(message) 154 if err != nil { 155 return fmt.Errorf("error marshalling textproto: %w", err) 156 } 157 158 if writeIfChanged { 159 err = pathtools.WriteFileIfChanged(output, data, 0666) 160 } else { 161 err = ioutil.WriteFile(output, data, 0666) 162 } 163 164 if err != nil { 165 return fmt.Errorf("error writing to %s: %w\n", output, err) 166 } 167 168 return nil 169} 170 171// mergeProtos merges a list of textproto files containing Mapping messages into a single textproto 172// containing a Mappings message. 173func mergeProtos(output string, inputs []string, stripPrefix string, writeIfChanged bool, ignoreMissingFiles bool) error { 174 mappings := symbols_map_proto.Mappings{} 175 for _, input := range inputs { 176 mapping := symbols_map_proto.Mapping{} 177 data, err := ioutil.ReadFile(input) 178 if err != nil { 179 if ignoreMissingFiles && os.IsNotExist(err) { 180 // Merge mode is used on a list of files in the packaging directory. If multiple 181 // goals are included on the build command line, for example `dist` and `tests`, 182 // then the symbols packaging rule for `dist` can run while a dependency of `tests` 183 // is modifying the symbols packaging directory. That can result in a file that 184 // existed when the file list was generated being deleted as part of updating it, 185 // resulting in sporadic ENOENT errors. Ignore them if -ignore_missing_files 186 // was passed on the command line. 187 continue 188 } 189 return fmt.Errorf("failed to read %s: %w", input, err) 190 } 191 err = prototext.Unmarshal(data, &mapping) 192 if err != nil { 193 return fmt.Errorf("failed to parse textproto %s: %w", input, err) 194 } 195 if stripPrefix != "" && mapping.Location != nil { 196 mapping.Location = proto.String(strings.TrimPrefix(*mapping.Location, stripPrefix)) 197 } 198 mappings.Mappings = append(mappings.Mappings, &mapping) 199 } 200 201 return writeTextProto(output, &mappings, writeIfChanged) 202} 203