blob: f8c4748fc3db82a75b6718633fff89730e39656e [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "report_utils.h"
#include <android-base/strings.h>
#include "JITDebugReader.h"
namespace simpleperf {
static bool IsArtEntry(const CallChainReportEntry& entry) {
return entry.execution_type == CallChainExecutionType::NATIVE_METHOD &&
(android::base::EndsWith(entry.dso->Path(), "/libart.so") ||
android::base::EndsWith(entry.dso->Path(), "/libartd.so") ||
strcmp(entry.symbol->Name(), "art_jni_trampoline") == 0);
};
#define ART_JNI_METHOD(java_name, native_name) java_name, native_name,
static const char* art_jni_method_array[] = {
#include "art_jni_method_table.h"
};
CallChainReportBuilder::CallChainReportBuilder(ThreadTree& thread_tree)
: thread_tree_(thread_tree) {
size_t n = sizeof(art_jni_method_array) / sizeof(art_jni_method_array[0]);
for (size_t i = 0; i + 1 < n; i += 2) {
art_jni_method_map_[art_jni_method_array[i + 1]] = art_jni_method_array[i];
}
}
static std::string_view GetFunctionName(std::string_view symbol_name) {
// Remove parameters.
if (auto pos = symbol_name.find('('); pos != symbol_name.npos) {
symbol_name = symbol_name.substr(0, pos);
}
// Remove return type.
if (auto pos = symbol_name.rfind(' '); pos != symbol_name.npos) {
symbol_name = symbol_name.substr(pos + 1);
}
return symbol_name;
}
std::vector<CallChainReportEntry> CallChainReportBuilder::Build(const ThreadEntry* thread,
const std::vector<uint64_t>& ips,
size_t kernel_ip_count) {
std::vector<CallChainReportEntry> result;
result.reserve(ips.size());
for (size_t i = 0; i < ips.size(); i++) {
const MapEntry* map = thread_tree_.FindMap(thread, ips[i], i < kernel_ip_count);
Dso* dso = map->dso;
uint64_t vaddr_in_file;
const Symbol* symbol = thread_tree_.FindSymbol(map, ips[i], &vaddr_in_file, &dso);
const char* symbol_name = nullptr;
CallChainExecutionType execution_type = CallChainExecutionType::NATIVE_METHOD;
if (dso->IsForJavaMethod()) {
if (dso->type() == DSO_DEX_FILE) {
execution_type = CallChainExecutionType::INTERPRETED_JVM_METHOD;
} else {
execution_type = CallChainExecutionType::JIT_JVM_METHOD;
}
} else if (auto it = art_jni_method_map_.find(GetFunctionName(symbol->DemangledName()));
it != art_jni_method_map_.end()) {
execution_type = CallChainExecutionType::ART_JNI_METHOD;
if (convert_art_jni_method_) {
symbol_name = it->second;
}
}
result.resize(result.size() + 1);
auto& entry = result.back();
entry.ip = ips[i];
entry.symbol = symbol;
entry.symbol_name = symbol_name;
entry.dso = dso;
entry.vaddr_in_file = vaddr_in_file;
entry.map = map;
entry.execution_type = execution_type;
}
MarkArtFrame(result);
if (remove_art_frame_) {
auto it = std::remove_if(result.begin(), result.end(), [](const CallChainReportEntry& entry) {
return entry.execution_type == CallChainExecutionType::ART_METHOD;
});
result.erase(it, result.end());
}
if (convert_jit_frame_) {
ConvertJITFrame(result);
}
return result;
}
void CallChainReportBuilder::MarkArtFrame(std::vector<CallChainReportEntry>& callchain) {
// Mark art methods before or after a JVM method.
bool near_java_method = false;
for (size_t i = 0; i < callchain.size(); ++i) {
auto& entry = callchain[i];
if (entry.execution_type == CallChainExecutionType::INTERPRETED_JVM_METHOD ||
entry.execution_type == CallChainExecutionType::JIT_JVM_METHOD ||
entry.execution_type == CallChainExecutionType::ART_JNI_METHOD) {
near_java_method = true;
// Mark art frames before this entry.
for (int j = static_cast<int>(i) - 1; j >= 0; j--) {
if (!IsArtEntry(callchain[j])) {
break;
}
callchain[j].execution_type = CallChainExecutionType::ART_METHOD;
}
} else if (near_java_method && IsArtEntry(entry)) {
entry.execution_type = CallChainExecutionType::ART_METHOD;
} else {
near_java_method = false;
}
}
}
void CallChainReportBuilder::ConvertJITFrame(std::vector<CallChainReportEntry>& callchain) {
CollectJavaMethods();
for (size_t i = 0; i < callchain.size();) {
auto& entry = callchain[i];
if (entry.dso->IsForJavaMethod() && entry.dso->type() == DSO_ELF_FILE) {
// This is a JIT java method, merge it with the interpreted java method having the same
// name if possible. Otherwise, merge it with other JIT java methods having the same name
// by assigning a common dso_name.
if (auto it = java_method_map_.find(entry.symbol->Name()); it != java_method_map_.end()) {
entry.dso = it->second.dso;
entry.symbol = it->second.symbol;
// Not enough info to map an offset in a JIT method to an offset in a dex file. So just
// use the symbol_addr.
entry.vaddr_in_file = entry.symbol->addr;
// ART may call from an interpreted Java method into its corresponding JIT method. To avoid
// showing the method calling itself, remove the JIT frame.
if (i + 1 < callchain.size() && callchain[i + 1].dso == entry.dso &&
callchain[i + 1].symbol == entry.symbol) {
callchain.erase(callchain.begin() + i);
continue;
}
} else if (!JITDebugReader::IsPathInJITSymFile(entry.dso->Path())) {
// Old JITSymFiles use names like "TemporaryFile-XXXXXX". So give them a better name.
entry.dso_name = "[JIT cache]";
}
}
i++;
}
}
void CallChainReportBuilder::CollectJavaMethods() {
if (!java_method_initialized_) {
java_method_initialized_ = true;
for (Dso* dso : thread_tree_.GetAllDsos()) {
if (dso->type() == DSO_DEX_FILE) {
dso->LoadSymbols();
for (auto& symbol : dso->GetSymbols()) {
java_method_map_.emplace(symbol.Name(), JavaMethod(dso, &symbol));
}
}
}
}
}
} // namespace simpleperf