1 /*
2 * Copyright (C) 2020 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 "report_utils.h"
18
19 #include <android-base/strings.h>
20
21 #include "JITDebugReader.h"
22 #include "utils.h"
23
24 namespace simpleperf {
25
IsArtEntry(const CallChainReportEntry & entry,bool * is_jni_trampoline)26 static bool IsArtEntry(const CallChainReportEntry& entry, bool* is_jni_trampoline) {
27 if (entry.execution_type == CallChainExecutionType::NATIVE_METHOD) {
28 if (android::base::EndsWith(entry.dso->Path(), "/libart.so") ||
29 android::base::EndsWith(entry.dso->Path(), "/libartd.so")) {
30 *is_jni_trampoline = false;
31 return true;
32 }
33 if (strcmp(entry.symbol->Name(), "art_jni_trampoline") == 0) {
34 // art_jni_trampoline is a trampoline used to call jni methods in art runtime.
35 // We want to hide it when hiding art frames.
36 *is_jni_trampoline = true;
37 return true;
38 }
39 }
40 return false;
41 };
42
AddProguardMappingFile(std::string_view mapping_file)43 bool CallChainReportBuilder::AddProguardMappingFile(std::string_view mapping_file) {
44 // The mapping file format is described in
45 // https://www.guardsquare.com/en/products/proguard/manual/retrace.
46 LineReader reader(mapping_file);
47 if (!reader.Ok()) {
48 PLOG(ERROR) << "failed to read " << mapping_file;
49 return false;
50 }
51 ProguardMappingClass* cur_class = nullptr;
52 std::string* line;
53 while ((line = reader.ReadLine()) != nullptr) {
54 std::string_view s = *line;
55 if (s.empty() || s[0] == '#') {
56 continue;
57 }
58 auto arrow_pos = s.find(" -> ");
59 if (arrow_pos == s.npos) {
60 continue;
61 }
62 auto arrow_end_pos = arrow_pos + strlen(" -> ");
63
64 if (s[0] != ' ') {
65 // Match line "original_classname -> obfuscated_classname:".
66 if (auto colon_pos = s.find(':', arrow_end_pos); colon_pos != s.npos) {
67 std::string_view original_classname = s.substr(0, arrow_pos);
68 std::string obfuscated_classname(s.substr(arrow_end_pos, colon_pos - arrow_end_pos));
69 cur_class = &proguard_class_map_[obfuscated_classname];
70 cur_class->original_classname = original_classname;
71 }
72 } else if (cur_class != nullptr) {
73 // Match line "... [original_classname.]original_methodname(...)... ->
74 // obfuscated_methodname".
75 if (auto left_brace_pos = s.rfind('(', arrow_pos); left_brace_pos != s.npos) {
76 if (auto space_pos = s.rfind(' ', left_brace_pos); space_pos != s.npos) {
77 auto original_methodname = s.substr(space_pos + 1, left_brace_pos - space_pos - 1);
78 if (android::base::StartsWith(original_methodname, cur_class->original_classname)) {
79 original_methodname.remove_prefix(cur_class->original_classname.size() + 1);
80 }
81 std::string obfuscated_methodname(s.substr(arrow_end_pos));
82 cur_class->method_map[obfuscated_methodname] = original_methodname;
83 }
84 }
85 }
86 }
87 return true;
88 }
89
Build(const ThreadEntry * thread,const std::vector<uint64_t> & ips,size_t kernel_ip_count)90 std::vector<CallChainReportEntry> CallChainReportBuilder::Build(const ThreadEntry* thread,
91 const std::vector<uint64_t>& ips,
92 size_t kernel_ip_count) {
93 std::vector<CallChainReportEntry> result;
94 result.reserve(ips.size());
95 for (size_t i = 0; i < ips.size(); i++) {
96 const MapEntry* map = thread_tree_.FindMap(thread, ips[i], i < kernel_ip_count);
97 Dso* dso = map->dso;
98 uint64_t vaddr_in_file;
99 const Symbol* symbol = thread_tree_.FindSymbol(map, ips[i], &vaddr_in_file, &dso);
100 CallChainExecutionType execution_type = CallChainExecutionType::NATIVE_METHOD;
101 if (dso->IsForJavaMethod()) {
102 if (dso->type() == DSO_DEX_FILE) {
103 execution_type = CallChainExecutionType::INTERPRETED_JVM_METHOD;
104 } else {
105 execution_type = CallChainExecutionType::JIT_JVM_METHOD;
106 }
107 }
108 result.resize(result.size() + 1);
109 auto& entry = result.back();
110 entry.ip = ips[i];
111 entry.symbol = symbol;
112 entry.dso = dso;
113 entry.vaddr_in_file = vaddr_in_file;
114 entry.map = map;
115 entry.execution_type = execution_type;
116 }
117 MarkArtFrame(result);
118 if (remove_art_frame_) {
119 auto it = std::remove_if(result.begin(), result.end(), [](const CallChainReportEntry& entry) {
120 return entry.execution_type == CallChainExecutionType::ART_METHOD;
121 });
122 result.erase(it, result.end());
123 }
124 if (convert_jit_frame_) {
125 ConvertJITFrame(result);
126 }
127 if (!proguard_class_map_.empty()) {
128 DeObfuscateJavaMethods(result);
129 }
130 return result;
131 }
132
MarkArtFrame(std::vector<CallChainReportEntry> & callchain)133 void CallChainReportBuilder::MarkArtFrame(std::vector<CallChainReportEntry>& callchain) {
134 // Mark art methods before or after a JVM method.
135 bool near_java_method = false;
136 bool is_jni_trampoline = false;
137 std::vector<size_t> jni_trampoline_positions;
138 for (size_t i = 0; i < callchain.size(); ++i) {
139 auto& entry = callchain[i];
140 if (entry.execution_type == CallChainExecutionType::INTERPRETED_JVM_METHOD ||
141 entry.execution_type == CallChainExecutionType::JIT_JVM_METHOD) {
142 near_java_method = true;
143
144 // Mark art frames before this entry.
145 for (int j = static_cast<int>(i) - 1; j >= 0; j--) {
146 if (!IsArtEntry(callchain[j], &is_jni_trampoline)) {
147 break;
148 }
149 callchain[j].execution_type = CallChainExecutionType::ART_METHOD;
150 if (is_jni_trampoline) {
151 jni_trampoline_positions.push_back(j);
152 }
153 }
154 } else if (near_java_method && IsArtEntry(entry, &is_jni_trampoline)) {
155 entry.execution_type = CallChainExecutionType::ART_METHOD;
156 if (is_jni_trampoline) {
157 jni_trampoline_positions.push_back(i);
158 }
159 } else {
160 near_java_method = false;
161 }
162 }
163 // Functions called by art_jni_trampoline are jni methods. And we don't want to hide them.
164 for (auto i : jni_trampoline_positions) {
165 if (i > 0 && callchain[i - 1].execution_type == CallChainExecutionType::ART_METHOD) {
166 callchain[i - 1].execution_type = CallChainExecutionType::NATIVE_METHOD;
167 }
168 }
169 }
170
ConvertJITFrame(std::vector<CallChainReportEntry> & callchain)171 void CallChainReportBuilder::ConvertJITFrame(std::vector<CallChainReportEntry>& callchain) {
172 CollectJavaMethods();
173 for (size_t i = 0; i < callchain.size();) {
174 auto& entry = callchain[i];
175 if (entry.dso->IsForJavaMethod() && entry.dso->type() == DSO_ELF_FILE) {
176 // This is a JIT java method, merge it with the interpreted java method having the same
177 // name if possible. Otherwise, merge it with other JIT java methods having the same name
178 // by assigning a common dso_name.
179 if (auto it = java_method_map_.find(entry.symbol->Name()); it != java_method_map_.end()) {
180 entry.dso = it->second.dso;
181 entry.symbol = it->second.symbol;
182 // Not enough info to map an offset in a JIT method to an offset in a dex file. So just
183 // use the symbol_addr.
184 entry.vaddr_in_file = entry.symbol->addr;
185
186 // ART may call from an interpreted Java method into its corresponding JIT method. To
187 // avoid showing the method calling itself, remove the JIT frame.
188 if (i + 1 < callchain.size() && callchain[i + 1].dso == entry.dso &&
189 callchain[i + 1].symbol == entry.symbol) {
190 callchain.erase(callchain.begin() + i);
191 continue;
192 }
193
194 } else if (!JITDebugReader::IsPathInJITSymFile(entry.dso->Path())) {
195 // Old JITSymFiles use names like "TemporaryFile-XXXXXX". So give them a better name.
196 entry.dso_name = "[JIT cache]";
197 }
198 }
199 i++;
200 }
201 }
202
CollectJavaMethods()203 void CallChainReportBuilder::CollectJavaMethods() {
204 if (!java_method_initialized_) {
205 java_method_initialized_ = true;
206 for (Dso* dso : thread_tree_.GetAllDsos()) {
207 if (dso->type() == DSO_DEX_FILE) {
208 dso->LoadSymbols();
209 for (auto& symbol : dso->GetSymbols()) {
210 java_method_map_.emplace(symbol.Name(), JavaMethod(dso, &symbol));
211 }
212 }
213 }
214 }
215 }
216
DeObfuscateJavaMethods(std::vector<CallChainReportEntry> & callchain)217 void CallChainReportBuilder::DeObfuscateJavaMethods(std::vector<CallChainReportEntry>& callchain) {
218 for (auto& entry : callchain) {
219 if (entry.execution_type != CallChainExecutionType::JIT_JVM_METHOD &&
220 entry.execution_type != CallChainExecutionType::INTERPRETED_JVM_METHOD) {
221 continue;
222 }
223 std::string_view name = entry.symbol->DemangledName();
224 if (auto split_pos = name.rfind('.'); split_pos != name.npos) {
225 std::string obfuscated_classname(name.substr(0, split_pos));
226 if (auto it = proguard_class_map_.find(obfuscated_classname);
227 it != proguard_class_map_.end()) {
228 const ProguardMappingClass& proguard_class = it->second;
229 std::string obfuscated_methodname(name.substr(split_pos + 1));
230 if (auto method_it = proguard_class.method_map.find(obfuscated_methodname);
231 method_it != proguard_class.method_map.end()) {
232 std::string new_symbol_name = proguard_class.original_classname + "." + method_it->second;
233 entry.symbol->SetDemangledName(new_symbol_name);
234 }
235 }
236 }
237 }
238 }
239
240 } // namespace simpleperf
241