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 = ParseIfFuncRecord(lines.cur_token());
105 if (!parse_record_status.ok()) {
106 PERFETTO_ELOG("%s", parse_record_status.message().c_str());
107 return false;
108 }
109 }
110
111 return true;
112 }
113
GetSymbol(uint64_t address) const114 std::optional<std::string> BreakpadParser::GetSymbol(uint64_t address) const {
115 // Returns an iterator pointing to the first element where the symbol's start
116 // address is greater than |address|.
117 auto it = std::upper_bound(symbols_.begin(), symbols_.end(), address,
118 &SymbolComparator);
119 // If the first symbol's address is greater than |address| then |address| is
120 // too low to appear in |symbols_|.
121 if (it == symbols_.begin()) {
122 return std::nullopt;
123 }
124 // upper_bound() returns the first symbol who's start address is greater than
125 // |address|. Therefore to find the symbol with a range of addresses that
126 // |address| falls into, we check the previous symbol.
127 it--;
128 // Check to see if the address is in the function's range.
129 if (address >= it->start_address &&
130 address < it->start_address + it->function_size) {
131 return it->symbol_name;
132 }
133 return std::nullopt;
134 }
135
ParseIfFuncRecord(base::StringView current_line)136 base::Status BreakpadParser::ParseIfFuncRecord(base::StringView current_line) {
137 // Parses a FUNC record from a file. Structure of a FUNC record:
138 // FUNC [m] address size parameter_size name
139 // m: The m field is optional. If present it indicates that multiple symbols
140 // reference this function's instructions. (In which case, only one symbol
141 // name is mentioned within the breakpad file.)
142 // address: The start address of the function relative to the module's load
143 // address.
144 // size: The length in bytes of function's instructions.
145 // parameter_size: A hexadecimal number indicating the size, in bytes, of the
146 // arguments pushed on the stack for this function.
147 // name: The function name. This field may contain spaces.
148 // More info at
149 // https://chromium.googlesource.com/breakpad/breakpad/+/HEAD/docs/symbol_files.md
150
151 const char kFuncLabel[] = "FUNC";
152 base::StringSplitter words(current_line.ToStdString(), ' ');
153 // Check to see if the first word indicates a FUNC record. If it is, create a
154 // Symbol struct and add tokens from words. If it isn't the function can just
155 // return true and resume parsing file.
156 if (!words.Next() || strcmp(words.cur_token(), kFuncLabel) != 0) {
157 return base::OkStatus();
158 }
159
160 Symbol new_symbol;
161 // There can be either 4 or 5 FUNC record tokens. The second token, 'm' is
162 // optional.
163 const char kOptionalArg[] = "m";
164 // Get the first argument on the line.
165 words.Next();
166
167 // If the optional argument is present, skip to the next token.
168 if (strcmp(words.cur_token(), kOptionalArg) == 0) {
169 words.Next();
170 }
171
172 // Get the start address.
173 std::optional<uint64_t> optional_address =
174 base::CStringToUInt64(words.cur_token(), 16);
175 if (!optional_address) {
176 return base::Status("Address should be hexadecimal.");
177 }
178 new_symbol.start_address = *optional_address;
179
180 // Get the function size.
181 words.Next();
182 std::optional<size_t> optional_func_size =
183 base::CStringToUInt32(words.cur_token(), 16);
184 if (!optional_func_size) {
185 return base::Status("Function size should be hexadecimal.");
186 }
187 new_symbol.function_size = *optional_func_size;
188
189 // Skip the parameter size.
190 words.Next();
191
192 // Get the function name. Function names can have spaces, so any token is now
193 // considered a part of the function name and will be appended to the buffer
194 // in |func_name_writer|.
195 std::unique_ptr<char[]> joined_string(new char[current_line.size()]);
196 base::StringWriter func_name_writer(joined_string.get(), current_line.size());
197 bool first_token = true;
198 while (words.Next()) {
199 if (!first_token) {
200 func_name_writer.AppendChar(' ');
201 } else {
202 first_token = false;
203 }
204 func_name_writer.AppendString(words.cur_token(), strlen(words.cur_token()));
205 }
206
207 new_symbol.symbol_name = func_name_writer.GetStringView().ToStdString();
208
209 symbols_.push_back(std::move(new_symbol));
210
211 return base::OkStatus();
212 }
213
214 } // namespace profiling
215 } // namespace perfetto
216