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 <charconv>
22 #include <cinttypes>
23 #include <limits>
24 #include <memory>
25 #include <optional>
26 #include <sstream>
27 #include <string>
28 #include <vector>
29
30 #include "perfetto/base/build_config.h"
31 #include "perfetto/base/compiler.h"
32 #include "perfetto/base/logging.h"
33 #include "perfetto/ext/base/file_utils.h"
34 #include "perfetto/ext/base/scoped_file.h"
35 #include "perfetto/ext/base/scoped_mmap.h"
36 #include "perfetto/ext/base/string_utils.h"
37 #include "src/profiling/symbolizer/elf.h"
38 #include "src/profiling/symbolizer/filesystem.h"
39
40 namespace perfetto {
41 namespace profiling {
42
43 // TODO(fmayer): Fix up name. This suggests it always returns a symbolizer or
44 // dies, which isn't the case.
LocalSymbolizerOrDie(std::vector<std::string> binary_path,const char * mode)45 std::unique_ptr<Symbolizer> LocalSymbolizerOrDie(
46 std::vector<std::string> binary_path,
47 const char* mode) {
48 std::unique_ptr<Symbolizer> symbolizer;
49
50 if (!binary_path.empty()) {
51 #if PERFETTO_BUILDFLAG(PERFETTO_LOCAL_SYMBOLIZER)
52 std::unique_ptr<BinaryFinder> finder;
53 if (!mode || strncmp(mode, "find", 4) == 0)
54 finder.reset(new LocalBinaryFinder(std::move(binary_path)));
55 else if (strncmp(mode, "index", 5) == 0)
56 finder.reset(new LocalBinaryIndexer(std::move(binary_path)));
57 else
58 PERFETTO_FATAL("Invalid symbolizer mode [find | index]: %s", mode);
59 symbolizer.reset(new LocalSymbolizer(std::move(finder)));
60 #else
61 base::ignore_result(mode);
62 PERFETTO_FATAL("This build does not support local symbolization.");
63 #endif
64 }
65 return symbolizer;
66 }
67
68 } // namespace profiling
69 } // namespace perfetto
70
71 #if PERFETTO_BUILDFLAG(PERFETTO_LOCAL_SYMBOLIZER)
72 #include "perfetto/ext/base/string_splitter.h"
73 #include "perfetto/ext/base/string_utils.h"
74 #include "perfetto/ext/base/utils.h"
75
76 #include <signal.h>
77 #include <sys/stat.h>
78 #include <sys/types.h>
79
80 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
81 constexpr const char* kDefaultSymbolizer = "llvm-symbolizer.exe";
82 #else
83 constexpr const char* kDefaultSymbolizer = "llvm-symbolizer";
84 #endif
85
86 namespace perfetto {
87 namespace profiling {
88
89 namespace {
90
GetLine(std::function<int64_t (char *,size_t)> fn_read)91 std::string GetLine(std::function<int64_t(char*, size_t)> fn_read) {
92 std::string line;
93 char buffer[512];
94 int64_t rd = 0;
95 while ((rd = fn_read(buffer, sizeof(buffer))) > 0) {
96 std::string data(buffer, static_cast<size_t>(rd));
97 line += data;
98 if (line.back() == '\n') {
99 break;
100 }
101 // There should be no intermediate new lines in the read data.
102 PERFETTO_DCHECK(line.find('\n') == std::string::npos);
103 }
104 if (rd == -1) {
105 PERFETTO_ELOG("Failed to read data from subprocess.");
106 }
107 return line;
108 }
109
InRange(const void * base,size_t total_size,const void * ptr,size_t size)110 bool InRange(const void* base,
111 size_t total_size,
112 const void* ptr,
113 size_t size) {
114 return ptr >= base && static_cast<const char*>(ptr) + size <=
115 static_cast<const char*>(base) + total_size;
116 }
117
118 template <typename E>
GetElfLoadBias(void * mem,size_t size)119 std::optional<uint64_t> GetElfLoadBias(void* mem, size_t size) {
120 const typename E::Ehdr* ehdr = static_cast<typename E::Ehdr*>(mem);
121 if (!InRange(mem, size, ehdr, sizeof(typename E::Ehdr))) {
122 PERFETTO_ELOG("Corrupted ELF.");
123 return std::nullopt;
124 }
125 for (size_t i = 0; i < ehdr->e_phnum; ++i) {
126 typename E::Phdr* phdr = GetPhdr<E>(mem, ehdr, i);
127 if (!InRange(mem, size, phdr, sizeof(typename E::Phdr))) {
128 PERFETTO_ELOG("Corrupted ELF.");
129 return std::nullopt;
130 }
131 if (phdr->p_type == PT_LOAD && phdr->p_flags & PF_X) {
132 return phdr->p_vaddr - phdr->p_offset;
133 }
134 }
135 return 0u;
136 }
137
138 template <typename E>
GetElfBuildId(void * mem,size_t size)139 std::optional<std::string> GetElfBuildId(void* mem, size_t size) {
140 const typename E::Ehdr* ehdr = static_cast<typename E::Ehdr*>(mem);
141 if (!InRange(mem, size, ehdr, sizeof(typename E::Ehdr))) {
142 PERFETTO_ELOG("Corrupted ELF.");
143 return std::nullopt;
144 }
145 for (size_t i = 0; i < ehdr->e_shnum; ++i) {
146 typename E::Shdr* shdr = GetShdr<E>(mem, ehdr, i);
147 if (!InRange(mem, size, shdr, sizeof(typename E::Shdr))) {
148 PERFETTO_ELOG("Corrupted ELF.");
149 return std::nullopt;
150 }
151
152 if (shdr->sh_type != SHT_NOTE)
153 continue;
154
155 auto offset = shdr->sh_offset;
156 while (offset < shdr->sh_offset + shdr->sh_size) {
157 typename E::Nhdr* nhdr =
158 reinterpret_cast<typename E::Nhdr*>(static_cast<char*>(mem) + offset);
159
160 if (!InRange(mem, size, nhdr, sizeof(typename E::Nhdr))) {
161 PERFETTO_ELOG("Corrupted ELF.");
162 return std::nullopt;
163 }
164 if (nhdr->n_type == NT_GNU_BUILD_ID && nhdr->n_namesz == 4) {
165 char* name = reinterpret_cast<char*>(nhdr) + sizeof(*nhdr);
166 if (!InRange(mem, size, name, 4)) {
167 PERFETTO_ELOG("Corrupted ELF.");
168 return std::nullopt;
169 }
170 if (memcmp(name, "GNU", 3) == 0) {
171 const char* value = reinterpret_cast<char*>(nhdr) + sizeof(*nhdr) +
172 base::AlignUp<4>(nhdr->n_namesz);
173
174 if (!InRange(mem, size, value, nhdr->n_descsz)) {
175 PERFETTO_ELOG("Corrupted ELF.");
176 return std::nullopt;
177 }
178 return std::string(value, nhdr->n_descsz);
179 }
180 }
181 offset += sizeof(*nhdr) + base::AlignUp<4>(nhdr->n_namesz) +
182 base::AlignUp<4>(nhdr->n_descsz);
183 }
184 }
185 return std::nullopt;
186 }
187
SplitBuildID(const std::string & hex_build_id)188 std::string SplitBuildID(const std::string& hex_build_id) {
189 if (hex_build_id.size() < 3) {
190 PERFETTO_DFATAL_OR_ELOG("Invalid build-id (< 3 char) %s",
191 hex_build_id.c_str());
192 return {};
193 }
194
195 return hex_build_id.substr(0, 2) + "/" + hex_build_id.substr(2);
196 }
197
IsElf(const char * mem,size_t size)198 bool IsElf(const char* mem, size_t size) {
199 if (size <= EI_MAG3)
200 return false;
201 return (mem[EI_MAG0] == ELFMAG0 && mem[EI_MAG1] == ELFMAG1 &&
202 mem[EI_MAG2] == ELFMAG2 && mem[EI_MAG3] == ELFMAG3);
203 }
204
205 constexpr uint32_t kMachO64Magic = 0xfeedfacf;
206
IsMachO64(const char * mem,size_t size)207 bool IsMachO64(const char* mem, size_t size) {
208 if (size < sizeof(kMachO64Magic))
209 return false;
210 return memcmp(mem, &kMachO64Magic, sizeof(kMachO64Magic)) == 0;
211 }
212
213 struct mach_header_64 {
214 uint32_t magic; /* mach magic number identifier */
215 int32_t cputype; /* cpu specifier */
216 int32_t cpusubtype; /* machine specifier */
217 uint32_t filetype; /* type of file */
218 uint32_t ncmds; /* number of load commands */
219 uint32_t sizeofcmds; /* the size of all the load commands */
220 uint32_t flags; /* flags */
221 uint32_t reserved; /* reserved */
222 };
223
224 struct load_command {
225 uint32_t cmd; /* type of load command */
226 uint32_t cmdsize; /* total size of command in bytes */
227 };
228
229 struct segment_64_command {
230 uint32_t cmd; /* LC_SEGMENT_64 */
231 uint32_t cmdsize; /* includes sizeof section_64 structs */
232 char segname[16]; /* segment name */
233 uint64_t vmaddr; /* memory address of this segment */
234 uint64_t vmsize; /* memory size of this segment */
235 uint64_t fileoff; /* file offset of this segment */
236 uint64_t filesize; /* amount to map from the file */
237 uint32_t maxprot; /* maximum VM protection */
238 uint32_t initprot; /* initial VM protection */
239 uint32_t nsects; /* number of sections in segment */
240 uint32_t flags; /* flags */
241 };
242
243 struct BinaryInfo {
244 std::string build_id;
245 uint64_t load_bias;
246 BinaryType type;
247 };
248
GetMachOBinaryInfo(char * mem,size_t size)249 std::optional<BinaryInfo> GetMachOBinaryInfo(char* mem, size_t size) {
250 if (size < sizeof(mach_header_64))
251 return {};
252
253 mach_header_64 header;
254 memcpy(&header, mem, sizeof(mach_header_64));
255
256 if (size < sizeof(mach_header_64) + header.sizeofcmds)
257 return {};
258
259 std::optional<std::string> build_id;
260 uint64_t load_bias = 0;
261
262 char* pcmd = mem + sizeof(mach_header_64);
263 char* pcmds_end = pcmd + header.sizeofcmds;
264 while (pcmd < pcmds_end) {
265 load_command cmd_header;
266 memcpy(&cmd_header, pcmd, sizeof(load_command));
267
268 constexpr uint32_t LC_SEGMENT_64 = 0x19;
269 constexpr uint32_t LC_UUID = 0x1b;
270
271 switch (cmd_header.cmd) {
272 case LC_UUID: {
273 build_id = std::string(pcmd + sizeof(load_command),
274 cmd_header.cmdsize - sizeof(load_command));
275 break;
276 }
277 case LC_SEGMENT_64: {
278 segment_64_command seg_cmd;
279 memcpy(&seg_cmd, pcmd, sizeof(segment_64_command));
280 if (strcmp(seg_cmd.segname, "__TEXT") == 0) {
281 load_bias = seg_cmd.vmaddr;
282 }
283 break;
284 }
285 default:
286 break;
287 }
288
289 pcmd += cmd_header.cmdsize;
290 }
291
292 if (build_id) {
293 constexpr uint32_t MH_DSYM = 0xa;
294 BinaryType type = header.filetype == MH_DSYM ? BinaryType::kMachODsym
295 : BinaryType::kMachO;
296 return BinaryInfo{*build_id, load_bias, type};
297 }
298 return {};
299 }
300
GetBinaryInfo(const char * fname,size_t size)301 std::optional<BinaryInfo> GetBinaryInfo(const char* fname, size_t size) {
302 static_assert(EI_CLASS > EI_MAG3, "mem[EI_MAG?] accesses are in range.");
303 if (size <= EI_CLASS)
304 return std::nullopt;
305 base::ScopedMmap map = base::ReadMmapFilePart(fname, size);
306 if (!map.IsValid()) {
307 PERFETTO_PLOG("Failed to mmap %s", fname);
308 return std::nullopt;
309 }
310 char* mem = static_cast<char*>(map.data());
311
312 std::optional<std::string> build_id;
313 std::optional<uint64_t> load_bias;
314 if (IsElf(mem, size)) {
315 switch (mem[EI_CLASS]) {
316 case ELFCLASS32:
317 build_id = GetElfBuildId<Elf32>(mem, size);
318 load_bias = GetElfLoadBias<Elf32>(mem, size);
319 break;
320 case ELFCLASS64:
321 build_id = GetElfBuildId<Elf64>(mem, size);
322 load_bias = GetElfLoadBias<Elf64>(mem, size);
323 break;
324 default:
325 return std::nullopt;
326 }
327 if (build_id && load_bias) {
328 return BinaryInfo{*build_id, *load_bias, BinaryType::kElf};
329 }
330 } else if (IsMachO64(mem, size)) {
331 return GetMachOBinaryInfo(mem, size);
332 }
333 return std::nullopt;
334 }
335
BuildIdIndex(std::vector<std::string> dirs)336 std::map<std::string, FoundBinary> BuildIdIndex(std::vector<std::string> dirs) {
337 std::map<std::string, FoundBinary> result;
338 WalkDirectories(std::move(dirs), [&result](const char* fname, size_t size) {
339 static_assert(EI_MAG3 + 1 == sizeof(kMachO64Magic));
340 char magic[EI_MAG3 + 1];
341 // Scope file access. On windows OpenFile opens an exclusive lock.
342 // This lock needs to be released before mapping the file.
343 {
344 base::ScopedFile fd(base::OpenFile(fname, O_RDONLY));
345 if (!fd) {
346 PERFETTO_PLOG("Failed to open %s", fname);
347 return;
348 }
349 ssize_t rd = base::Read(*fd, &magic, sizeof(magic));
350 if (rd != sizeof(magic) || (!IsElf(magic, static_cast<size_t>(rd)) &&
351 !IsMachO64(magic, static_cast<size_t>(rd)))) {
352 PERFETTO_DLOG("%s not an ELF or Mach-O 64.", fname);
353 return;
354 }
355 }
356 std::optional<BinaryInfo> binary_info = GetBinaryInfo(fname, size);
357 if (!binary_info) {
358 PERFETTO_DLOG("Failed to extract build id from %s.", fname);
359 return;
360 }
361 auto it = result.emplace(
362 binary_info->build_id,
363 FoundBinary{fname, binary_info->load_bias, binary_info->type});
364
365 // If there was already an existing FoundBinary, the emplace wouldn't insert
366 // anything. But, for Mac binaries, we prefer dSYM files over the original
367 // binary, so make sure these overwrite the FoundBinary entry.
368 bool has_existing = it.second == false;
369 if (has_existing) {
370 if (it.first->second.type == BinaryType::kMachO &&
371 binary_info->type == BinaryType::kMachODsym) {
372 PERFETTO_LOG("Overwriting index entry for %s to %s.",
373 base::ToHex(binary_info->build_id).c_str(), fname);
374 it.first->second =
375 FoundBinary{fname, binary_info->load_bias, binary_info->type};
376 } else {
377 PERFETTO_DLOG("Ignoring %s, index entry for %s already exists.", fname,
378 base::ToHex(binary_info->build_id).c_str());
379 }
380 } else {
381 PERFETTO_LOG("Indexed: %s (%s)", fname,
382 base::ToHex(binary_info->build_id).c_str());
383 }
384 });
385 return result;
386 }
387
ParseJsonString(const char * & it,const char * end,std::string * out)388 bool ParseJsonString(const char*& it, const char* end, std::string* out) {
389 *out = "";
390 if (it == end) {
391 return false;
392 }
393 if (*it++ != '"') {
394 return false;
395 }
396 while (true) {
397 if (it == end) {
398 return false;
399 }
400 char c = *it++;
401 if (c == '"') {
402 return true;
403 }
404 if (c == '\\') {
405 if (it == end) {
406 return false;
407 }
408 c = *it++;
409 switch (c) {
410 case '"':
411 case '\\':
412 case '/':
413 out->push_back(c);
414 break;
415 case 'b':
416 out->push_back('\b');
417 break;
418 case 'f':
419 out->push_back('\f');
420 break;
421 case 'n':
422 out->push_back('\n');
423 break;
424 case 'r':
425 out->push_back('\r');
426 break;
427 case 't':
428 out->push_back('\t');
429 break;
430 // Pass-through \u escape codes without re-encoding to utf-8, for
431 // simplicity.
432 case 'u':
433 out->push_back('\\');
434 out->push_back('u');
435 break;
436 default:
437 return false;
438 }
439 } else {
440 out->push_back(c);
441 }
442 }
443 }
444
ParseJsonNumber(const char * & it,const char * end,double * out)445 bool ParseJsonNumber(const char*& it, const char* end, double* out) {
446 bool is_minus = false;
447 double ret = 0;
448 if (it == end) {
449 return false;
450 }
451 if (*it == '-') {
452 ++it;
453 is_minus = true;
454 }
455 while (true) {
456 if (it == end) {
457 return false;
458 }
459 char c = *it++;
460 if (isdigit(c)) {
461 ret = ret * 10 + (c - '0');
462 } else if (c == 'e') {
463 // Scientific syntax is not supported.
464 return false;
465 } else {
466 // Unwind the iterator to point at the end of the number.
467 it--;
468 break;
469 }
470 }
471 *out = is_minus ? -ret : ret;
472 return true;
473 }
474
ParseJsonArray(const char * & it,const char * end,std::function<bool (const char * &,const char *)> process_value)475 bool ParseJsonArray(
476 const char*& it,
477 const char* end,
478 std::function<bool(const char*&, const char*)> process_value) {
479 if (it == end) {
480 return false;
481 }
482 char c = *it++;
483 if (c != '[') {
484 return false;
485 }
486 while (true) {
487 if (!process_value(it, end)) {
488 return false;
489 }
490 if (it == end) {
491 return false;
492 }
493 c = *it++;
494 if (c == ']') {
495 return true;
496 }
497 if (c != ',') {
498 return false;
499 }
500 }
501 }
502
ParseJsonObject(const char * & it,const char * end,std::function<bool (const char * &,const char *,const std::string &)> process_value)503 bool ParseJsonObject(
504 const char*& it,
505 const char* end,
506 std::function<bool(const char*&, const char*, const std::string&)>
507 process_value) {
508 if (it == end) {
509 return false;
510 }
511 char c = *it++;
512 if (c != '{') {
513 return false;
514 }
515 while (true) {
516 std::string key;
517 if (!ParseJsonString(it, end, &key)) {
518 return false;
519 }
520 if (*it++ != ':') {
521 return false;
522 }
523 if (!process_value(it, end, key)) {
524 return false;
525 }
526 if (it == end) {
527 return false;
528 }
529 c = *it++;
530 if (c == '}') {
531 return true;
532 }
533 if (c != ',') {
534 return false;
535 }
536 }
537 }
538
SkipJsonValue(const char * & it,const char * end)539 bool SkipJsonValue(const char*& it, const char* end) {
540 if (it == end) {
541 return false;
542 }
543 char c = *it;
544 if (c == '"') {
545 std::string ignored;
546 return ParseJsonString(it, end, &ignored);
547 }
548 if (isdigit(c) || c == '-') {
549 double ignored;
550 return ParseJsonNumber(it, end, &ignored);
551 }
552 if (c == '[') {
553 return ParseJsonArray(it, end, [](const char*& it, const char* end) {
554 return SkipJsonValue(it, end);
555 });
556 }
557 if (c == '{') {
558 return ParseJsonObject(
559 it, end, [](const char*& it, const char* end, const std::string&) {
560 return SkipJsonValue(it, end);
561 });
562 }
563 return false;
564 }
565
566 } // namespace
567
ParseLlvmSymbolizerJsonLine(const std::string & line,std::vector<SymbolizedFrame> * result)568 bool ParseLlvmSymbolizerJsonLine(const std::string& line,
569 std::vector<SymbolizedFrame>* result) {
570 // Parse Json of the format:
571 // ```
572 // {"Address":"0x1b72f","ModuleName":"...","Symbol":[{"Column":0,
573 // "Discriminator":0,"FileName":"...","FunctionName":"...","Line":0,
574 // "StartAddress":"","StartFileName":"...","StartLine":0},...]}
575 // ```
576 const char* it = line.data();
577 const char* end = it + line.size();
578 return ParseJsonObject(
579 it, end, [&](const char*& it, const char* end, const std::string& key) {
580 if (key == "Symbol") {
581 return ParseJsonArray(it, end, [&](const char*& it, const char* end) {
582 SymbolizedFrame frame;
583 if (!ParseJsonObject(
584 it, end,
585 [&](const char*& it, const char* end,
586 const std::string& key) {
587 if (key == "FileName") {
588 return ParseJsonString(it, end, &frame.file_name);
589 }
590 if (key == "FunctionName") {
591 return ParseJsonString(it, end, &frame.function_name);
592 }
593 if (key == "Line") {
594 double number;
595 if (!ParseJsonNumber(it, end, &number)) {
596 return false;
597 }
598 frame.line = static_cast<unsigned int>(number);
599 return true;
600 }
601 return SkipJsonValue(it, end);
602 })) {
603 return false;
604 }
605 // Use "??" for empty filenames, to match non-JSON output.
606 if (frame.file_name.empty()) {
607 frame.file_name = "??";
608 }
609 result->push_back(frame);
610 return true;
611 });
612 }
613 if (key == "Error") {
614 std::string message;
615 if (!ParseJsonObject(it, end,
616 [&](const char*& it, const char* end,
617 const std::string& key) {
618 if (key == "Message") {
619 return ParseJsonString(it, end, &message);
620 }
621 return SkipJsonValue(it, end);
622 })) {
623 return false;
624 }
625 PERFETTO_ELOG("Failed to symbolize: %s.", message.c_str());
626 return true;
627 }
628 return SkipJsonValue(it, end);
629 });
630 }
631
632 BinaryFinder::~BinaryFinder() = default;
633
LocalBinaryIndexer(std::vector<std::string> roots)634 LocalBinaryIndexer::LocalBinaryIndexer(std::vector<std::string> roots)
635 : buildid_to_file_(BuildIdIndex(std::move(roots))) {}
636
FindBinary(const std::string & abspath,const std::string & build_id)637 std::optional<FoundBinary> LocalBinaryIndexer::FindBinary(
638 const std::string& abspath,
639 const std::string& build_id) {
640 auto it = buildid_to_file_.find(build_id);
641 if (it != buildid_to_file_.end())
642 return it->second;
643 PERFETTO_ELOG("Could not find Build ID: %s (file %s).",
644 base::ToHex(build_id).c_str(), abspath.c_str());
645 return std::nullopt;
646 }
647
648 LocalBinaryIndexer::~LocalBinaryIndexer() = default;
649
LocalBinaryFinder(std::vector<std::string> roots)650 LocalBinaryFinder::LocalBinaryFinder(std::vector<std::string> roots)
651 : roots_(std::move(roots)) {}
652
FindBinary(const std::string & abspath,const std::string & build_id)653 std::optional<FoundBinary> LocalBinaryFinder::FindBinary(
654 const std::string& abspath,
655 const std::string& build_id) {
656 auto p = cache_.emplace(abspath, std::nullopt);
657 if (!p.second)
658 return p.first->second;
659
660 std::optional<FoundBinary>& cache_entry = p.first->second;
661
662 // Try the absolute path first.
663 if (base::StartsWith(abspath, "/")) {
664 cache_entry = IsCorrectFile(abspath, build_id);
665 if (cache_entry)
666 return cache_entry;
667 }
668
669 for (const std::string& root_str : roots_) {
670 cache_entry = FindBinaryInRoot(root_str, abspath, build_id);
671 if (cache_entry)
672 return cache_entry;
673 }
674 PERFETTO_ELOG("Could not find %s (Build ID: %s).", abspath.c_str(),
675 base::ToHex(build_id).c_str());
676 return cache_entry;
677 }
678
IsCorrectFile(const std::string & symbol_file,const std::string & build_id)679 std::optional<FoundBinary> LocalBinaryFinder::IsCorrectFile(
680 const std::string& symbol_file,
681 const std::string& build_id) {
682 if (!base::FileExists(symbol_file)) {
683 return std::nullopt;
684 }
685 // Openfile opens the file with an exclusive lock on windows.
686 std::optional<uint64_t> file_size = base::GetFileSize(symbol_file);
687 if (!file_size.has_value()) {
688 PERFETTO_PLOG("Failed to get file size %s", symbol_file.c_str());
689 return std::nullopt;
690 }
691
692 static_assert(sizeof(size_t) <= sizeof(uint64_t));
693 size_t size = static_cast<size_t>(
694 std::min<uint64_t>(std::numeric_limits<size_t>::max(), *file_size));
695
696 if (size == 0) {
697 return std::nullopt;
698 }
699
700 std::optional<BinaryInfo> binary_info =
701 GetBinaryInfo(symbol_file.c_str(), size);
702 if (!binary_info)
703 return std::nullopt;
704 if (binary_info->build_id != build_id) {
705 return std::nullopt;
706 }
707 return FoundBinary{symbol_file, binary_info->load_bias, binary_info->type};
708 }
709
FindBinaryInRoot(const std::string & root_str,const std::string & abspath,const std::string & build_id)710 std::optional<FoundBinary> LocalBinaryFinder::FindBinaryInRoot(
711 const std::string& root_str,
712 const std::string& abspath,
713 const std::string& build_id) {
714 constexpr char kApkPrefix[] = "base.apk!";
715
716 std::string filename;
717 std::string dirname;
718
719 for (base::StringSplitter sp(abspath, '/'); sp.Next();) {
720 if (!dirname.empty())
721 dirname += "/";
722 dirname += filename;
723 filename = sp.cur_token();
724 }
725
726 // Return the first match for the following options:
727 // * absolute path of library file relative to root.
728 // * absolute path of library file relative to root, but with base.apk!
729 // removed from filename.
730 // * only filename of library file relative to root.
731 // * only filename of library file relative to root, but with base.apk!
732 // removed from filename.
733 // * in the subdirectory .build-id: the first two hex digits of the build-id
734 // as subdirectory, then the rest of the hex digits, with ".debug"appended.
735 // See
736 // https://fedoraproject.org/wiki/RolandMcGrath/BuildID#Find_files_by_build_ID
737 //
738 // For example, "/system/lib/base.apk!foo.so" with build id abcd1234,
739 // is looked for at
740 // * $ROOT/system/lib/base.apk!foo.so
741 // * $ROOT/system/lib/foo.so
742 // * $ROOT/base.apk!foo.so
743 // * $ROOT/foo.so
744 // * $ROOT/.build-id/ab/cd1234.debug
745
746 std::optional<FoundBinary> result;
747
748 std::string symbol_file = root_str + "/" + dirname + "/" + filename;
749 result = IsCorrectFile(symbol_file, build_id);
750 if (result) {
751 return result;
752 }
753
754 if (base::StartsWith(filename, kApkPrefix)) {
755 symbol_file = root_str + "/" + dirname + "/" +
756 filename.substr(sizeof(kApkPrefix) - 1);
757 result = IsCorrectFile(symbol_file, build_id);
758 if (result) {
759 return result;
760 }
761 }
762
763 symbol_file = root_str + "/" + filename;
764 result = IsCorrectFile(symbol_file, build_id);
765 if (result) {
766 return result;
767 }
768
769 if (base::StartsWith(filename, kApkPrefix)) {
770 symbol_file = root_str + "/" + filename.substr(sizeof(kApkPrefix) - 1);
771 result = IsCorrectFile(symbol_file, build_id);
772 if (result) {
773 return result;
774 }
775 }
776
777 std::string hex_build_id = base::ToHex(build_id.c_str(), build_id.size());
778 std::string split_hex_build_id = SplitBuildID(hex_build_id);
779 if (!split_hex_build_id.empty()) {
780 symbol_file =
781 root_str + "/" + ".build-id" + "/" + split_hex_build_id + ".debug";
782 result = IsCorrectFile(symbol_file, build_id);
783 if (result) {
784 return result;
785 }
786 }
787
788 return std::nullopt;
789 }
790
791 LocalBinaryFinder::~LocalBinaryFinder() = default;
792
LLVMSymbolizerProcess(const std::string & symbolizer_path)793 LLVMSymbolizerProcess::LLVMSymbolizerProcess(const std::string& symbolizer_path)
794 :
795 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
796 subprocess_(symbolizer_path, {"--output-style=JSON"}) {
797 }
798 #else
799 subprocess_(symbolizer_path, {"llvm-symbolizer", "--output-style=JSON"}) {
800 }
801 #endif
802
Symbolize(const std::string & binary,uint64_t address)803 std::vector<SymbolizedFrame> LLVMSymbolizerProcess::Symbolize(
804 const std::string& binary,
805 uint64_t address) {
806 std::vector<SymbolizedFrame> result;
807 base::StackString<1024> buffer("\"%s\" 0x%" PRIx64 "\n", binary.c_str(),
808 address);
809 if (subprocess_.Write(buffer.c_str(), buffer.len()) < 0) {
810 PERFETTO_ELOG("Failed to write to llvm-symbolizer.");
811 return result;
812 }
813 auto line = GetLine([&](char* read_buffer, size_t buffer_size) {
814 return subprocess_.Read(read_buffer, buffer_size);
815 });
816 // llvm-symbolizer writes out records as one JSON per line.
817 if (!ParseLlvmSymbolizerJsonLine(line, &result)) {
818 PERFETTO_ELOG("Failed to parse llvm-symbolizer JSON: %s", line.c_str());
819 return {};
820 }
821 return result;
822 }
Symbolize(const std::string & mapping_name,const std::string & build_id,uint64_t load_bias,const std::vector<uint64_t> & addresses)823 std::vector<std::vector<SymbolizedFrame>> LocalSymbolizer::Symbolize(
824 const std::string& mapping_name,
825 const std::string& build_id,
826 uint64_t load_bias,
827 const std::vector<uint64_t>& addresses) {
828 std::optional<FoundBinary> binary =
829 finder_->FindBinary(mapping_name, build_id);
830 if (!binary)
831 return {};
832 uint64_t load_bias_correction = 0;
833 if (binary->load_bias > load_bias) {
834 // On Android 10, there was a bug in libunwindstack that would incorrectly
835 // calculate the load_bias, and thus the relative PC. This would end up in
836 // frames that made no sense. We can fix this up after the fact if we
837 // detect this situation.
838 load_bias_correction = binary->load_bias - load_bias;
839 PERFETTO_LOG("Correcting load bias by %" PRIu64 " for %s",
840 load_bias_correction, mapping_name.c_str());
841 }
842 std::vector<std::vector<SymbolizedFrame>> result;
843 result.reserve(addresses.size());
844 for (uint64_t address : addresses)
845 result.emplace_back(llvm_symbolizer_.Symbolize(
846 binary->file_name, address + load_bias_correction));
847 return result;
848 }
849
LocalSymbolizer(const std::string & symbolizer_path,std::unique_ptr<BinaryFinder> finder)850 LocalSymbolizer::LocalSymbolizer(const std::string& symbolizer_path,
851 std::unique_ptr<BinaryFinder> finder)
852 : llvm_symbolizer_(symbolizer_path), finder_(std::move(finder)) {}
853
LocalSymbolizer(std::unique_ptr<BinaryFinder> finder)854 LocalSymbolizer::LocalSymbolizer(std::unique_ptr<BinaryFinder> finder)
855 : LocalSymbolizer(kDefaultSymbolizer, std::move(finder)) {}
856
857 LocalSymbolizer::~LocalSymbolizer() = default;
858
859 } // namespace profiling
860 } // namespace perfetto
861
862 #endif // PERFETTO_BUILDFLAG(PERFETTO_LOCAL_SYMBOLIZER)
863