• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #include <memory>
17 
18 #include "src/profiling/symbolizer/breakpad_parser.h"
19 
20 #include "perfetto/base/logging.h"
21 #include "perfetto/ext/base/file_utils.h"
22 #include "perfetto/ext/base/string_splitter.h"
23 #include "perfetto/ext/base/string_utils.h"
24 #include "perfetto/ext/base/string_writer.h"
25 
26 namespace perfetto {
27 namespace profiling {
28 
29 namespace {
30 
SymbolComparator(const uint64_t i,const BreakpadParser::Symbol & sym)31 bool SymbolComparator(const uint64_t i, const BreakpadParser::Symbol& sym) {
32   return i < sym.start_address;
33 }
34 
GetFileContents(const std::string & file_path)35 std::optional<std::string> GetFileContents(const std::string& file_path) {
36   std::string file_contents;
37   base::ScopedFile fd = base::OpenFile(file_path, O_RDONLY);
38   // Read the contents of the file into |file_contents|.
39   if (!fd) {
40     return std::nullopt;
41   }
42   if (!base::ReadFileDescriptor(fd.get(), &file_contents)) {
43     return std::nullopt;
44   }
45 
46   return std::make_optional(std::move(file_contents));
47 }
48 
49 // Parses the given string and determines if it begins with the label
50 // 'MODULE'. Returns an ok status if it does begin with this label and a fail
51 // status otherwise.
ParseIfModuleRecord(base::StringView first_line)52 base::Status ParseIfModuleRecord(base::StringView first_line) {
53   // Split the given line by spaces.
54   const char kModuleLabel[] = "MODULE";
55   // Check to see if the line starts with 'MODULE'.
56   if (!base::StartsWith(first_line.ToStdString(), kModuleLabel)) {
57     return base::Status("Breakpad file not formatted correctly.");
58   }
59   return base::OkStatus();
60 }
61 
62 }  // namespace
63 
BreakpadParser(const std::string & file_path)64 BreakpadParser::BreakpadParser(const std::string& file_path)
65     : file_path_(file_path) {}
66 
ParseFile()67 bool BreakpadParser::ParseFile() {
68   std::optional<std::string> file_contents = GetFileContents(file_path_);
69   if (!file_contents) {
70     PERFETTO_ELOG("Could not get file contents of %s.", file_path_.c_str());
71     return false;
72   }
73 
74   // TODO(uwemwilson): Extract a build id and store it in the Symbol object.
75 
76   if (!ParseFromString(*file_contents)) {
77     PERFETTO_ELOG("Could not parse file contents.");
78     return false;
79   }
80 
81   return true;
82 }
83 
ParseFromString(const std::string & file_contents)84 bool BreakpadParser::ParseFromString(const std::string& file_contents) {
85   // Create StringSplitter objects for each line so that specific lines can be
86   // used to create StringSplitter objects for words on that line.
87   base::StringSplitter lines(file_contents, '\n');
88   if (!lines.Next()) {
89     // File must be empty, so just return true and continue.
90     return true;
91   }
92 
93   // TODO(crbug/1239750): Extract a build id and store it in the Symbol object.
94   base::StringView first_line(lines.cur_token(), lines.cur_token_size());
95   base::Status parse_record_status = ParseIfModuleRecord(first_line);
96   if (!parse_record_status.ok()) {
97     PERFETTO_ELOG("%s Breakpad files should begin with a MODULE record",
98                   parse_record_status.message().c_str());
99     return false;
100   }
101 
102   // Parse each line.
103   while (lines.Next()) {
104     parse_record_status = ParseIfRecord(lines.cur_token(), RecordType::FUNC);
105     if (!parse_record_status.ok()) {
106       PERFETTO_ELOG("%s", parse_record_status.message().c_str());
107       return false;
108     }
109 
110     parse_record_status = ParseIfRecord(lines.cur_token(), RecordType::PUBLIC);
111     if (!parse_record_status.ok()) {
112       PERFETTO_ELOG("%s", parse_record_status.message().c_str());
113       return false;
114     }
115   }
116 
117   return true;
118 }
119 
GetSymbol(uint64_t address) const120 std::optional<std::string> BreakpadParser::GetSymbol(uint64_t address) const {
121   // Returns an iterator pointing to the first element where the symbol's start
122   // address is greater than |address|.
123   auto it = std::upper_bound(symbols_.begin(), symbols_.end(), address,
124                              &SymbolComparator);
125   // If the first symbol's address is greater than |address| then |address| is
126   // too low to appear in |symbols_|.
127   if (it == symbols_.begin()) {
128     return std::nullopt;
129   }
130   // upper_bound() returns the first symbol who's start address is greater than
131   // |address|. Therefore to find the symbol with a range of addresses that
132   // |address| falls into, we check the previous symbol.
133   it--;
134   // Check to see if the address is in the function's range.
135   if (address >= it->start_address &&
136       address < it->start_address + it->function_size) {
137     return it->symbol_name;
138   }
139   return std::nullopt;
140 }
141 
GetPublicSymbol(uint64_t address) const142 std::optional<std::string> BreakpadParser::GetPublicSymbol(
143     uint64_t address) const {
144   // Returns an iterator pointing to the first element where the symbol's start
145   // address is greater than |address|.
146   auto it = std::upper_bound(public_symbols_.begin(), public_symbols_.end(),
147                              address, &SymbolComparator);
148   // If the first symbol's address is greater than |address| then |address| is
149   // too low to appear in |public_symbols_|.
150   if (it == public_symbols_.begin() || it == public_symbols_.end()) {
151     return std::nullopt;
152   }
153   it--;
154   // Check to see if the address is in the function's range, since the PUBLIC
155   // record only store the parameter size, but not the function size. we use
156   // 0xffff to do the sanity check.
157   if (address >= it->start_address && address < it->start_address + 0xFFFF) {
158     return it->symbol_name;
159   }
160   return std::nullopt;
161 }
162 
GetRecordLabel(RecordType type) const163 std::string BreakpadParser::GetRecordLabel(RecordType type) const {
164   switch (type) {
165     case RecordType::FUNC:
166       return "FUNC";
167     case RecordType::PUBLIC:
168       return "PUBLIC";
169   }
170   PERFETTO_FATAL("For GCC");
171 }
172 
StoreSymbol(Symbol & symbol,RecordType type)173 void BreakpadParser::StoreSymbol(Symbol& symbol, RecordType type) {
174   switch (type) {
175     case RecordType::FUNC:
176       symbols_.emplace_back(std::move(symbol));
177       break;
178     case RecordType::PUBLIC:
179       public_symbols_.emplace_back(std::move(symbol));
180       break;
181   }
182 }
183 
ParseIfRecord(base::StringView current_line,const RecordType type)184 base::Status BreakpadParser::ParseIfRecord(base::StringView current_line,
185                                            const RecordType type) {
186   // The parser currently supports FUNC and PUBLIC records.
187   // FUNC   [m] address size parameter_size name
188   // PUBLIC [m] address      parameter_size name
189   const std::string typeLabel = GetRecordLabel(type);
190   base::StringSplitter words(current_line.ToStdString(), ' ');
191 
192   // Check to see if the first word indicates a {RecordType} record. If it is,
193   // create a Symbol struct and add tokens from words. If it isn't the function
194   // can just return true and resume parsing file.
195   if (!words.Next() || strcmp(words.cur_token(), typeLabel.c_str()) != 0) {
196     return base::OkStatus();
197   }
198 
199   Symbol new_symbol;
200   // The second token, 'm' is optional.
201   const char kOptionalArg[] = "m";
202   // Get the first argument on the line.
203   words.Next();
204 
205   // If the optional argument is present, skip to the next token.
206   if (strcmp(words.cur_token(), kOptionalArg) == 0) {
207     words.Next();
208   }
209 
210   // Get the start address.
211   std::optional<uint64_t> optional_address =
212       base::CStringToUInt64(words.cur_token(), 16);
213   if (!optional_address) {
214     return base::Status("Address should be hexadecimal.");
215   }
216   new_symbol.start_address = *optional_address;
217 
218   // The function size is only stored in the FUNC record, so only parse it if
219   // the record type is FUNC.
220   if (type == RecordType::FUNC) {
221     // Get the function size.
222     words.Next();
223     std::optional<size_t> optional_func_size =
224         base::CStringToUInt32(words.cur_token(), 16);
225     if (!optional_func_size) {
226       return base::Status("Function size should be hexadecimal.");
227     }
228     new_symbol.function_size = *optional_func_size;
229   }
230 
231   // Skip the parameter size.
232   words.Next();
233 
234   // Get the function name. Function names can have spaces, so any token is now
235   // considered a part of the function name and will be appended to the buffer
236   // in |func_name_writer|.
237   std::unique_ptr<char[]> joined_string(new char[current_line.size()]);
238   base::StringWriter func_name_writer(joined_string.get(), current_line.size());
239   bool first_token = true;
240   while (words.Next()) {
241     if (!first_token) {
242       func_name_writer.AppendChar(' ');
243     } else {
244       first_token = false;
245     }
246     func_name_writer.AppendString(words.cur_token(), strlen(words.cur_token()));
247   }
248 
249   new_symbol.symbol_name = func_name_writer.GetStringView().ToStdString();
250 
251   StoreSymbol(new_symbol, type);
252 
253   return base::OkStatus();
254 }
255 
256 }  // namespace profiling
257 }  // namespace perfetto
258