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