1 /*
2 * Copyright (C) 2019 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
17 #include "src/profiling/symbolizer/local_symbolizer.h"
18
19 #include <fcntl.h>
20
21 #include <cinttypes>
22 #include <memory>
23 #include <sstream>
24 #include <string>
25 #include <vector>
26
27 #include "perfetto/base/build_config.h"
28 #include "perfetto/base/compiler.h"
29 #include "perfetto/base/logging.h"
30 #include "perfetto/ext/base/file_utils.h"
31 #include "perfetto/ext/base/optional.h"
32 #include "perfetto/ext/base/scoped_file.h"
33 #include "perfetto/ext/base/string_utils.h"
34 #include "src/profiling/symbolizer/elf.h"
35 #include "src/profiling/symbolizer/scoped_read_mmap.h"
36
37 namespace perfetto {
38 namespace profiling {
39
40 // TODO(fmayer): Fix up name. This suggests it always returns a symbolizer or
41 // dies, which isn't the case.
LocalSymbolizerOrDie(std::vector<std::string> binary_path,const char * mode)42 std::unique_ptr<Symbolizer> LocalSymbolizerOrDie(
43 std::vector<std::string> binary_path,
44 const char* mode) {
45 std::unique_ptr<Symbolizer> symbolizer;
46
47 if (!binary_path.empty()) {
48 #if PERFETTO_BUILDFLAG(PERFETTO_LOCAL_SYMBOLIZER)
49 std::unique_ptr<BinaryFinder> finder;
50 if (!mode || strncmp(mode, "find", 4) == 0)
51 finder.reset(new LocalBinaryFinder(std::move(binary_path)));
52 else if (strncmp(mode, "index", 5) == 0)
53 finder.reset(new LocalBinaryIndexer(std::move(binary_path)));
54 else
55 PERFETTO_FATAL("Invalid symbolizer mode [find | index]: %s", mode);
56 symbolizer.reset(new LocalSymbolizer(std::move(finder)));
57 #else
58 base::ignore_result(mode);
59 PERFETTO_FATAL("This build does not support local symbolization.");
60 #endif
61 }
62 return symbolizer;
63 }
64
65 } // namespace profiling
66 } // namespace perfetto
67
68 #if PERFETTO_BUILDFLAG(PERFETTO_LOCAL_SYMBOLIZER)
69 #include "perfetto/ext/base/string_splitter.h"
70 #include "perfetto/ext/base/string_utils.h"
71 #include "perfetto/ext/base/utils.h"
72
73 #include <signal.h>
74 #include <sys/stat.h>
75 #include <sys/types.h>
76
77 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
78 constexpr const char* kDefaultSymbolizer = "llvm-symbolizer.exe";
79 #else
80 constexpr const char* kDefaultSymbolizer = "llvm-symbolizer";
81 #endif
82
83 namespace perfetto {
84 namespace profiling {
85
GetLines(std::function<int64_t (char *,size_t)> fn_read)86 std::vector<std::string> GetLines(
87 std::function<int64_t(char*, size_t)> fn_read) {
88 std::vector<std::string> lines;
89 char buffer[512];
90 int64_t rd = 0;
91 // Cache the partial line of the previous read.
92 std::string last_line;
93 while ((rd = fn_read(buffer, sizeof(buffer))) > 0) {
94 std::string data(buffer, static_cast<size_t>(rd));
95 // Create stream buffer of last partial line + new data
96 std::stringstream stream(last_line + data);
97 std::string line;
98 last_line = "";
99 while (std::getline(stream, line)) {
100 // Return from reading when we read an empty line.
101 if (line.empty()) {
102 return lines;
103 } else if (stream.eof()) {
104 // Cache off the partial line when we hit end of stream.
105 last_line += line;
106 break;
107 } else {
108 lines.push_back(line);
109 }
110 }
111 }
112 if (rd == -1) {
113 PERFETTO_ELOG("Failed to read data from subprocess.");
114 }
115 return lines;
116 }
117
118 namespace {
InRange(const void * base,size_t total_size,const void * ptr,size_t size)119 bool InRange(const void* base,
120 size_t total_size,
121 const void* ptr,
122 size_t size) {
123 return ptr >= base && static_cast<const char*>(ptr) + size <=
124 static_cast<const char*>(base) + total_size;
125 }
126
127 template <typename E>
GetLoadBias(void * mem,size_t size)128 base::Optional<uint64_t> GetLoadBias(void* mem, size_t size) {
129 const typename E::Ehdr* ehdr = static_cast<typename E::Ehdr*>(mem);
130 if (!InRange(mem, size, ehdr, sizeof(typename E::Ehdr))) {
131 PERFETTO_ELOG("Corrupted ELF.");
132 return base::nullopt;
133 }
134 for (size_t i = 0; i < ehdr->e_phnum; ++i) {
135 typename E::Phdr* phdr = GetPhdr<E>(mem, ehdr, i);
136 if (!InRange(mem, size, phdr, sizeof(typename E::Phdr))) {
137 PERFETTO_ELOG("Corrupted ELF.");
138 return base::nullopt;
139 }
140 if (phdr->p_type == PT_LOAD && phdr->p_flags & PF_X) {
141 return phdr->p_vaddr - phdr->p_offset;
142 }
143 }
144 return 0u;
145 }
146
147 template <typename E>
GetBuildId(void * mem,size_t size)148 base::Optional<std::string> GetBuildId(void* mem, size_t size) {
149 const typename E::Ehdr* ehdr = static_cast<typename E::Ehdr*>(mem);
150 if (!InRange(mem, size, ehdr, sizeof(typename E::Ehdr))) {
151 PERFETTO_ELOG("Corrupted ELF.");
152 return base::nullopt;
153 }
154 for (size_t i = 0; i < ehdr->e_shnum; ++i) {
155 typename E::Shdr* shdr = GetShdr<E>(mem, ehdr, i);
156 if (!InRange(mem, size, shdr, sizeof(typename E::Shdr))) {
157 PERFETTO_ELOG("Corrupted ELF.");
158 return base::nullopt;
159 }
160
161 if (shdr->sh_type != SHT_NOTE)
162 continue;
163
164 auto offset = shdr->sh_offset;
165 while (offset < shdr->sh_offset + shdr->sh_size) {
166 typename E::Nhdr* nhdr =
167 reinterpret_cast<typename E::Nhdr*>(static_cast<char*>(mem) + offset);
168
169 if (!InRange(mem, size, nhdr, sizeof(typename E::Nhdr))) {
170 PERFETTO_ELOG("Corrupted ELF.");
171 return base::nullopt;
172 }
173 if (nhdr->n_type == NT_GNU_BUILD_ID && nhdr->n_namesz == 4) {
174 char* name = reinterpret_cast<char*>(nhdr) + sizeof(*nhdr);
175 if (!InRange(mem, size, name, 4)) {
176 PERFETTO_ELOG("Corrupted ELF.");
177 return base::nullopt;
178 }
179 if (memcmp(name, "GNU", 3) == 0) {
180 const char* value = reinterpret_cast<char*>(nhdr) + sizeof(*nhdr) +
181 base::AlignUp<4>(nhdr->n_namesz);
182
183 if (!InRange(mem, size, value, nhdr->n_descsz)) {
184 PERFETTO_ELOG("Corrupted ELF.");
185 return base::nullopt;
186 }
187 return std::string(value, nhdr->n_descsz);
188 }
189 }
190 offset += sizeof(*nhdr) + base::AlignUp<4>(nhdr->n_namesz) +
191 base::AlignUp<4>(nhdr->n_descsz);
192 }
193 }
194 return base::nullopt;
195 }
196
SplitBuildID(const std::string & hex_build_id)197 std::string SplitBuildID(const std::string& hex_build_id) {
198 if (hex_build_id.size() < 3) {
199 PERFETTO_DFATAL_OR_ELOG("Invalid build-id (< 3 char) %s",
200 hex_build_id.c_str());
201 return {};
202 }
203
204 return hex_build_id.substr(0, 2) + "/" + hex_build_id.substr(2);
205 }
206
IsElf(const char * mem,size_t size)207 bool IsElf(const char* mem, size_t size) {
208 if (size <= EI_MAG3)
209 return false;
210 return (mem[EI_MAG0] == ELFMAG0 && mem[EI_MAG1] == ELFMAG1 &&
211 mem[EI_MAG2] == ELFMAG2 && mem[EI_MAG3] == ELFMAG3);
212 }
213
214 struct BuildIdAndLoadBias {
215 std::string build_id;
216 uint64_t load_bias;
217 };
218
GetBuildIdAndLoadBias(const std::string & fname)219 base::Optional<BuildIdAndLoadBias> GetBuildIdAndLoadBias(
220 const std::string& fname) {
221 base::Optional<size_t> size = base::GetFileSize(fname);
222 if (!size.has_value()) {
223 PERFETTO_PLOG("Failed to get file size %s", fname.c_str());
224 return base::nullopt;
225 }
226 static_assert(EI_CLASS > EI_MAG3, "mem[EI_MAG?] accesses are in range.");
227 if (*size <= EI_CLASS)
228 return base::nullopt;
229 ScopedReadMmap map(fname.c_str(), *size);
230 if (!map.IsValid()) {
231 PERFETTO_PLOG("mmap");
232 return base::nullopt;
233 }
234 char* mem = static_cast<char*>(*map);
235
236 if (!IsElf(mem, *size))
237 return base::nullopt;
238
239 base::Optional<std::string> build_id;
240 base::Optional<uint64_t> load_bias;
241 switch (mem[EI_CLASS]) {
242 case ELFCLASS32:
243 build_id = GetBuildId<Elf32>(mem, *size);
244 load_bias = GetLoadBias<Elf32>(mem, *size);
245 break;
246 case ELFCLASS64:
247 build_id = GetBuildId<Elf64>(mem, *size);
248 load_bias = GetLoadBias<Elf64>(mem, *size);
249 break;
250 default:
251 return base::nullopt;
252 }
253 if (build_id && load_bias) {
254 return BuildIdAndLoadBias{*build_id, *load_bias};
255 }
256 return base::nullopt;
257 }
258
StartsWithElfMagic(const std::string & fname)259 bool StartsWithElfMagic(const std::string& fname) {
260 base::ScopedFile fd(base::OpenFile(fname, O_RDONLY));
261 char magic[EI_MAG3 + 1];
262 if (!fd) {
263 PERFETTO_PLOG("Failed to open %s", fname.c_str());
264 return false;
265 }
266 ssize_t rd = base::Read(*fd, &magic, sizeof(magic));
267 if (rd != sizeof(magic)) {
268 PERFETTO_PLOG("Failed to read %s", fname.c_str());
269 return false;
270 }
271 if (!IsElf(magic, static_cast<size_t>(rd))) {
272 PERFETTO_DLOG("%s not an ELF.", fname.c_str());
273 return false;
274 }
275 return true;
276 }
277
BuildIdIndex(std::vector<std::string> dirs)278 std::map<std::string, FoundBinary> BuildIdIndex(std::vector<std::string> dirs) {
279 std::map<std::string, FoundBinary> result;
280 for (const std::string& dir : dirs) {
281 std::vector<std::string> files;
282 base::Status status = base::ListFilesRecursive(dir, files);
283 if (!status.ok()) {
284 PERFETTO_PLOG("Failed to list directory %s", dir.c_str());
285 continue;
286 }
287 for (const std::string& basename : files) {
288 std::string fname = dir + "/" + basename;
289 if (!StartsWithElfMagic(fname)) {
290 continue;
291 }
292 base::Optional<BuildIdAndLoadBias> build_id_and_load_bias =
293 GetBuildIdAndLoadBias(fname);
294 if (build_id_and_load_bias) {
295 result.emplace(build_id_and_load_bias->build_id,
296 FoundBinary{fname, build_id_and_load_bias->load_bias});
297 }
298 }
299 }
300
301 return result;
302 }
303
304 } // namespace
305
ParseLlvmSymbolizerLine(const std::string & line,std::string * file_name,uint32_t * line_no)306 bool ParseLlvmSymbolizerLine(const std::string& line,
307 std::string* file_name,
308 uint32_t* line_no) {
309 size_t col_pos = line.rfind(':');
310 if (col_pos == std::string::npos || col_pos == 0)
311 return false;
312 size_t row_pos = line.rfind(':', col_pos - 1);
313 if (row_pos == std::string::npos || row_pos == 0)
314 return false;
315 *file_name = line.substr(0, row_pos);
316 auto line_no_str = line.substr(row_pos + 1, col_pos - row_pos - 1);
317
318 base::Optional<int32_t> opt_parsed_line_no = base::StringToInt32(line_no_str);
319 if (!opt_parsed_line_no || *opt_parsed_line_no < 0)
320 return false;
321 *line_no = static_cast<uint32_t>(*opt_parsed_line_no);
322 return true;
323 }
324
325 BinaryFinder::~BinaryFinder() = default;
326
LocalBinaryIndexer(std::vector<std::string> roots)327 LocalBinaryIndexer::LocalBinaryIndexer(std::vector<std::string> roots)
328 : buildid_to_file_(BuildIdIndex(std::move(roots))) {}
329
FindBinary(const std::string & abspath,const std::string & build_id)330 base::Optional<FoundBinary> LocalBinaryIndexer::FindBinary(
331 const std::string& abspath,
332 const std::string& build_id) {
333 auto it = buildid_to_file_.find(build_id);
334 if (it != buildid_to_file_.end())
335 return it->second;
336 PERFETTO_ELOG("Could not find Build ID: %s (file %s).",
337 base::ToHex(build_id).c_str(), abspath.c_str());
338 return base::nullopt;
339 }
340
341 LocalBinaryIndexer::~LocalBinaryIndexer() = default;
342
LocalBinaryFinder(std::vector<std::string> roots)343 LocalBinaryFinder::LocalBinaryFinder(std::vector<std::string> roots)
344 : roots_(std::move(roots)) {}
345
FindBinary(const std::string & abspath,const std::string & build_id)346 base::Optional<FoundBinary> LocalBinaryFinder::FindBinary(
347 const std::string& abspath,
348 const std::string& build_id) {
349 auto p = cache_.emplace(abspath, base::nullopt);
350 if (!p.second)
351 return p.first->second;
352
353 base::Optional<FoundBinary>& cache_entry = p.first->second;
354
355 for (const std::string& root_str : roots_) {
356 cache_entry = FindBinaryInRoot(root_str, abspath, build_id);
357 if (cache_entry)
358 return cache_entry;
359 }
360 PERFETTO_ELOG("Could not find %s (Build ID: %s).", abspath.c_str(),
361 base::ToHex(build_id).c_str());
362 return cache_entry;
363 }
364
IsCorrectFile(const std::string & symbol_file,const std::string & build_id)365 base::Optional<FoundBinary> LocalBinaryFinder::IsCorrectFile(
366 const std::string& symbol_file,
367 const std::string& build_id) {
368 if (!base::FileExists(symbol_file)) {
369 return base::nullopt;
370 }
371 base::Optional<BuildIdAndLoadBias> build_id_and_load_bias =
372 GetBuildIdAndLoadBias(symbol_file);
373 if (!build_id_and_load_bias)
374 return base::nullopt;
375 if (build_id_and_load_bias->build_id != build_id) {
376 return base::nullopt;
377 }
378 return FoundBinary{symbol_file, build_id_and_load_bias->load_bias};
379 }
380
FindBinaryInRoot(const std::string & root_str,const std::string & abspath,const std::string & build_id)381 base::Optional<FoundBinary> LocalBinaryFinder::FindBinaryInRoot(
382 const std::string& root_str,
383 const std::string& abspath,
384 const std::string& build_id) {
385 constexpr char kApkPrefix[] = "base.apk!";
386
387 std::string filename;
388 std::string dirname;
389
390 for (base::StringSplitter sp(abspath, '/'); sp.Next();) {
391 if (!dirname.empty())
392 dirname += "/";
393 dirname += filename;
394 filename = sp.cur_token();
395 }
396
397 // Return the first match for the following options:
398 // * absolute path of library file relative to root.
399 // * absolute path of library file relative to root, but with base.apk!
400 // removed from filename.
401 // * only filename of library file relative to root.
402 // * only filename of library file relative to root, but with base.apk!
403 // removed from filename.
404 // * in the subdirectory .build-id: the first two hex digits of the build-id
405 // as subdirectory, then the rest of the hex digits, with ".debug"appended.
406 // See
407 // https://fedoraproject.org/wiki/RolandMcGrath/BuildID#Find_files_by_build_ID
408 //
409 // For example, "/system/lib/base.apk!foo.so" with build id abcd1234,
410 // is looked for at
411 // * $ROOT/system/lib/base.apk!foo.so
412 // * $ROOT/system/lib/foo.so
413 // * $ROOT/base.apk!foo.so
414 // * $ROOT/foo.so
415 // * $ROOT/.build-id/ab/cd1234.debug
416
417 base::Optional<FoundBinary> result;
418
419 std::string symbol_file = root_str + "/" + dirname + "/" + filename;
420 result = IsCorrectFile(symbol_file, build_id);
421 if (result) {
422 return result;
423 }
424
425 if (base::StartsWith(filename, kApkPrefix)) {
426 symbol_file = root_str + "/" + dirname + "/" +
427 filename.substr(sizeof(kApkPrefix) - 1);
428 result = IsCorrectFile(symbol_file, build_id);
429 if (result) {
430 return result;
431 }
432 }
433
434 symbol_file = root_str + "/" + filename;
435 result = IsCorrectFile(symbol_file, build_id);
436 if (result) {
437 return result;
438 }
439
440 if (base::StartsWith(filename, kApkPrefix)) {
441 symbol_file = root_str + "/" + filename.substr(sizeof(kApkPrefix) - 1);
442 result = IsCorrectFile(symbol_file, build_id);
443 if (result) {
444 return result;
445 }
446 }
447
448 std::string hex_build_id = base::ToHex(build_id.c_str(), build_id.size());
449 std::string split_hex_build_id = SplitBuildID(hex_build_id);
450 if (!split_hex_build_id.empty()) {
451 symbol_file =
452 root_str + "/" + ".build-id" + "/" + split_hex_build_id + ".debug";
453 result = IsCorrectFile(symbol_file, build_id);
454 if (result) {
455 return result;
456 }
457 }
458
459 return base::nullopt;
460 }
461
462 LocalBinaryFinder::~LocalBinaryFinder() = default;
463
LLVMSymbolizerProcess(const std::string & symbolizer_path)464 LLVMSymbolizerProcess::LLVMSymbolizerProcess(const std::string& symbolizer_path)
465 :
466 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
467 subprocess_(symbolizer_path, {}) {
468 }
469 #else
470 subprocess_(symbolizer_path, {"llvm-symbolizer"}) {
471 }
472 #endif
473
Symbolize(const std::string & binary,uint64_t address)474 std::vector<SymbolizedFrame> LLVMSymbolizerProcess::Symbolize(
475 const std::string& binary,
476 uint64_t address) {
477 std::vector<SymbolizedFrame> result;
478 char buffer[1024];
479 int size = sprintf(buffer, "\"%s\" 0x%" PRIx64 "\n", binary.c_str(), address);
480 if (subprocess_.Write(buffer, static_cast<size_t>(size)) < 0) {
481 PERFETTO_ELOG("Failed to write to llvm-symbolizer.");
482 return result;
483 }
484 auto lines = GetLines([&](char* read_buffer, size_t buffer_size) {
485 return subprocess_.Read(read_buffer, buffer_size);
486 });
487 // llvm-symbolizer writes out records in the form of
488 // Foo(Bar*)
489 // foo.cc:123
490 // This is why we should always get a multiple of two number of lines.
491 PERFETTO_DCHECK(lines.size() % 2 == 0);
492 result.resize(lines.size() / 2);
493 for (size_t i = 0; i < lines.size(); ++i) {
494 SymbolizedFrame& cur = result[i / 2];
495 if (i % 2 == 0) {
496 cur.function_name = lines[i];
497 } else {
498 if (!ParseLlvmSymbolizerLine(lines[i], &cur.file_name, &cur.line)) {
499 PERFETTO_ELOG("Failed to parse llvm-symbolizer line: %s",
500 lines[i].c_str());
501 cur.file_name = "";
502 cur.line = 0;
503 }
504 }
505 }
506
507 for (auto it = result.begin(); it != result.end();) {
508 if (it->function_name == "??")
509 it = result.erase(it);
510 else
511 ++it;
512 }
513 return result;
514 }
Symbolize(const std::string & mapping_name,const std::string & build_id,uint64_t load_bias,const std::vector<uint64_t> & addresses)515 std::vector<std::vector<SymbolizedFrame>> LocalSymbolizer::Symbolize(
516 const std::string& mapping_name,
517 const std::string& build_id,
518 uint64_t load_bias,
519 const std::vector<uint64_t>& addresses) {
520 base::Optional<FoundBinary> binary =
521 finder_->FindBinary(mapping_name, build_id);
522 if (!binary)
523 return {};
524 uint64_t load_bias_correction = 0;
525 if (binary->load_bias > load_bias) {
526 // On Android 10, there was a bug in libunwindstack that would incorrectly
527 // calculate the load_bias, and thus the relative PC. This would end up in
528 // frames that made no sense. We can fix this up after the fact if we
529 // detect this situation.
530 load_bias_correction = binary->load_bias - load_bias;
531 PERFETTO_LOG("Correcting load bias by %" PRIu64 " for %s",
532 load_bias_correction, mapping_name.c_str());
533 }
534 std::vector<std::vector<SymbolizedFrame>> result;
535 result.reserve(addresses.size());
536 for (uint64_t address : addresses)
537 result.emplace_back(llvm_symbolizer_.Symbolize(
538 binary->file_name, address + load_bias_correction));
539 return result;
540 }
541
LocalSymbolizer(const std::string & symbolizer_path,std::unique_ptr<BinaryFinder> finder)542 LocalSymbolizer::LocalSymbolizer(const std::string& symbolizer_path,
543 std::unique_ptr<BinaryFinder> finder)
544 : llvm_symbolizer_(symbolizer_path), finder_(std::move(finder)) {}
545
LocalSymbolizer(std::unique_ptr<BinaryFinder> finder)546 LocalSymbolizer::LocalSymbolizer(std::unique_ptr<BinaryFinder> finder)
547 : LocalSymbolizer(kDefaultSymbolizer, std::move(finder)) {}
548
549 LocalSymbolizer::~LocalSymbolizer() = default;
550
551 } // namespace profiling
552 } // namespace perfetto
553
554 #endif // PERFETTO_BUILDFLAG(PERFETTO_LOCAL_SYMBOLIZER)
555