1 /*
2 * Copyright (C) 2024 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 "tombstone_symbolize.h"
18
19 #include <fcntl.h>
20 #include <inttypes.h>
21 #include <unistd.h>
22
23 #include <string>
24 #include <vector>
25
26 #include "android-base/stringprintf.h"
27 #include "android-base/unique_fd.h"
28
29 #include "tombstone.pb.h"
30
31 using android::base::StringPrintf;
32 using android::base::unique_fd;
33
Start(const std::vector<std::string> & debug_file_directories)34 bool Symbolizer::Start(const std::vector<std::string>& debug_file_directories) {
35 unique_fd parent_in, parent_out, child_in, child_out;
36 if (!Pipe(&parent_in, &child_out) || !Pipe(&child_in, &parent_out)) {
37 return false;
38 }
39
40 std::vector<const char *> args;
41 args.push_back("llvm-symbolizer");
42 for (const std::string &dir : debug_file_directories) {
43 args.push_back("--debug-file-directory");
44 args.push_back(dir.c_str());
45 }
46 args.push_back(0);
47
48 int pid = fork();
49 if (pid == -1) {
50 return false;
51 } else if (pid == 0) {
52 parent_in.reset();
53 parent_out.reset();
54
55 dup2(child_in.get(), STDIN_FILENO);
56 dup2(child_out.get(), STDOUT_FILENO);
57
58 execvp("llvm-symbolizer", const_cast<char *const *>(args.data()));
59
60 fprintf(stderr, "unable to start llvm-symbolizer: %s\n", strerror(errno));
61 _exit(1);
62 } else {
63 child_in.reset();
64 child_out.reset();
65
66 // TODO: Check that llvm-symbolizer started up successfully.
67 // There used to be an easy way to do this, but it was removed in:
68 // https://github.com/llvm/llvm-project/commit/1792852f86dc75efa1f44d46b1a0daf386d64afa
69
70 in_fd = std::move(parent_in);
71 out_fd = std::move(parent_out);
72 return true;
73 }
74 }
75
read_response()76 std::string Symbolizer::read_response() {
77 std::string resp;
78
79 while (resp.size() < 2 || resp[resp.size() - 2] != '\n' || resp[resp.size() - 1] != '\n') {
80 char buf[4096];
81 ssize_t size = read(in_fd, buf, 4096);
82 if (size <= 0) {
83 return "";
84 }
85 resp.append(buf, size);
86 }
87
88 return resp;
89 }
90
SymbolizeCode(std::string path,uint64_t rel_pc)91 std::vector<Symbolizer::Frame> Symbolizer::SymbolizeCode(std::string path, uint64_t rel_pc) {
92 std::string request = StringPrintf("CODE %s 0x%" PRIx64 "\n", path.c_str(), rel_pc);
93 if (write(out_fd, request.c_str(), request.size()) != static_cast<ssize_t>(request.size())) {
94 return {};
95 }
96
97 std::string response = read_response();
98 if (response.empty()) {
99 return {};
100 }
101
102 std::vector<Symbolizer::Frame> frames;
103
104 size_t frame_start = 0;
105 while (frame_start < response.size() - 1) {
106 Symbolizer::Frame frame;
107
108 size_t second_line_start = response.find('\n', frame_start) + 1;
109 if (second_line_start == std::string::npos + 1) {
110 return {};
111 }
112
113 size_t third_line_start = response.find('\n', second_line_start) + 1;
114 if (third_line_start == std::string::npos + 1) {
115 return {};
116 }
117
118 frame.function_name = response.substr(frame_start, second_line_start - frame_start - 1);
119
120 size_t column_number_start = response.rfind(':', third_line_start);
121 if (column_number_start == std::string::npos) {
122 return {};
123 }
124
125 size_t line_number_start = response.rfind(':', column_number_start - 1);
126 if (line_number_start == std::string::npos) {
127 return {};
128 }
129
130 frame.file = response.substr(second_line_start, line_number_start - second_line_start);
131
132 errno = 0;
133 frame.line = strtoull(response.c_str() + line_number_start + 1, 0, 10);
134 frame.column = strtoull(response.c_str() + column_number_start + 1, 0, 10);
135 if (errno != 0) {
136 return {};
137 }
138
139 frames.push_back(frame);
140
141 frame_start = third_line_start;
142 }
143
144 if (frames.size() == 1 && frames[0].file == "??") {
145 return {};
146 }
147
148 return frames;
149 }
150
symbolize_backtrace_frame(const BacktraceFrame & frame,Symbolizer & sym)151 void symbolize_backtrace_frame(const BacktraceFrame& frame, Symbolizer& sym) {
152 if (frame.build_id().empty()) {
153 return;
154 }
155
156 for (Symbolizer::Frame f : sym.SymbolizeCode("BUILDID:" + frame.build_id(), frame.rel_pc())) {
157 printf(" %s:%" PRId64 ":%" PRId64 " (%s)\n", f.file.c_str(), f.line, f.column,
158 f.function_name.c_str());
159 }
160 }
161