cpp/atrace/Atrace.cpp (327 lines of code) (raw):
/**
* Copyright 2004-present, Facebook, Inc.
*
* 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 <dlfcn.h>
#include <libgen.h>
#include <stdarg.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <array>
#include <atomic>
#include <sstream>
#include <string>
#include <system_error>
#include <unordered_set>
#include <fb/Build.h>
#include <fb/log.h>
#include <fb/xplat_init.h>
#include <fbjni/fbjni.h>
#include <linker/sharedlibs.h>
#include <plthooks/plthooks.h>
#include <profilo/util/common.h>
#include <profilo/util/hooks.h>
#include <profilo/LogEntry.h>
#include <profilo/TraceProviders.h>
#include <profilo/jni/JMultiBufferLogger.h>
#include <profilo/logger/BlockLogger.h>
#include "Atrace.h"
namespace fbjni = facebook::jni;
namespace facebook {
namespace profilo {
namespace atrace {
namespace {
int* atrace_marker_fd = nullptr;
std::atomic<uint64_t>* atrace_enabled_tags = nullptr;
std::atomic<uint64_t> original_tags(UINT64_MAX);
std::atomic<bool> systrace_installed;
std::atomic<uint32_t> provider_mask;
logger::MultiBufferLogger* logger;
bool first_enable = true;
bool atrace_enabled;
namespace {
ssize_t const kAtraceMessageLength = 1024;
// Magic FD to simply write to tracer logger and bypassing real write
int const kTracerMagicFd = -100;
// Libraries which log to ATRACE reference this function symbol. This symbol is
// used for early verification if a library should be hooked.
constexpr char kAtraceSymbol[] = "atrace_setup";
// Prefix for system libraries.
constexpr char kSysLibPrefix[] = "/system";
// Starting from SDK 27 it's enough to hook single "libcutils" lib to capture
// all ATRACE logging.
constexpr auto kSingleLibMinSdk = 27;
constexpr auto kLibWhitelistMinSdk = 23;
constexpr char kSingleLibName[] = "libcutils.so";
// Determine if this library should be hooked.
bool allowHookingCb(char const* libname, char const* full_libname, void* data) {
std::unordered_set<std::string>* seenLibs =
static_cast<std::unordered_set<std::string>*>(data);
if (seenLibs->find(libname) != seenLibs->cend()) {
// We already hooked (or saw and decided not to hook) this library.
return false;
}
seenLibs->insert(libname);
// Only allow to hook system libraries
if (strncmp(full_libname, kSysLibPrefix, sizeof(kSysLibPrefix) - 1)) {
return false;
}
// Verify if the library contains atrace indicator symbol, otherwise we
// don't need to install hooks.
auto result = linker::sharedLib(libname);
if (!result.success) {
return false;
}
ElfW(Sym) const* sym = result.data.find_symbol_by_name(kAtraceSymbol);
if (!sym) {
return false;
}
return true;
}
void log_systrace(const void* buf, size_t count) {
const char* msg = reinterpret_cast<const char*>(buf);
EntryType type;
switch (msg[0]) {
case 'B': { // begin synchronous event. format: "B|<pid>|<name>"
type = EntryType::MARK_PUSH;
break;
}
case 'E': { // end synchronous event. format: "E"
type = EntryType::MARK_POP;
break;
}
// the following events we don't currently log.
case 'S': // start async event. format: "S|<pid>|<name>|<cookie>"
case 'F': // finish async event. format: "F|<pid>|<name>|<cookie>"
case 'C': // counter. format: "C|<pid>|<name>|<value>"
default:
return;
}
StandardEntry entry{};
entry.tid = threadID();
entry.timestamp = monotonicTime();
entry.type = type;
int32_t id = logger->write(entry);
if (type != EntryType::MARK_POP) {
// Format is B|<pid>|<name>.
// Skip "B|" trivially, find next '|' with memchr. We cannot use strchr
// since we can't trust the message to have a null terminator.
constexpr auto kPrefixLength = 2; // length of "B|";
const char* name = reinterpret_cast<const char*>(
memchr(msg + kPrefixLength, '|', count - kPrefixLength));
if (name == nullptr) {
return;
}
name++; // skip '|' to the next character
ssize_t len = msg + count - name;
if (len > 0) {
logger->writeBytes(
EntryType::STRING_NAME,
id,
(const uint8_t*)name,
std::min(len, kAtraceMessageLength));
FBLOGV("systrace event: %s", name);
}
}
}
bool should_log_systrace(int fd, size_t count) {
return (
systrace_installed && fd == *atrace_marker_fd &&
TraceProviders::get().isEnabled(provider_mask.load()) && count > 0);
}
ssize_t write_hook(int fd, const void* buf, size_t count) {
if (should_log_systrace(fd, count)) {
log_systrace(buf, count);
return count;
}
return CALL_PREV(write_hook, fd, buf, count);
}
ssize_t
__write_chk_hook(int fd, const void* buf, size_t count, size_t buf_size) {
if (should_log_systrace(fd, count)) {
log_systrace(buf, count);
return count;
}
return CALL_PREV(__write_chk_hook, fd, buf, count, buf_size);
}
constexpr auto kWhitelistSize = 7;
std::array<std::string, kWhitelistSize>& getLibWhitelist() {
static std::array<std::string, kWhitelistSize> whitelist = {
{"libandroid_runtime.so",
"libui.so",
"libgui.so",
"libart.so",
"libhwui.so",
"libEGL.so",
"libcutils.so"}};
return whitelist;
}
} // namespace
std::vector<plt_hook_spec>& getFunctionHooks() {
static std::vector<plt_hook_spec> functionHooks = {
{"libc.so", "write", reinterpret_cast<void*>(&write_hook)},
{"libc.so", "__write_chk", reinterpret_cast<void*>(__write_chk_hook)},
};
return functionHooks;
}
plt_hook_spec& getSingleLibFunctionSpec() {
static plt_hook_spec spec{
"__write_chk", reinterpret_cast<void*>(__write_chk_hook)};
return spec;
}
// Returns the set of libraries that we don't want to hook.
std::unordered_set<std::string>& getSeenLibs() {
static std::unordered_set<std::string> seenLibs;
// Add this library's name to the set that we won't hook
if (seenLibs.size() == 0) {
seenLibs.insert("libc.so");
Dl_info info;
if (!dladdr((void*)&getSeenLibs, &info)) {
FBLOGV("Failed to find module name");
}
if (info.dli_fname == nullptr) {
// Not safe to continue as a thread may block trying to hook the current
// library
throw std::runtime_error("could not resolve current library");
}
seenLibs.insert(basename(info.dli_fname));
}
return seenLibs;
}
void hookLoadedLibs() {
logger::BlockLogger block(*logger, "hookLoadedLibs");
auto sdk = build::Build::getAndroidSdk();
if (sdk >= kSingleLibMinSdk) {
auto& spec = getSingleLibFunctionSpec();
hook_single_lib(kSingleLibName, &spec, 1);
return;
}
if (sdk >= kLibWhitelistMinSdk) {
auto& whitelist = getLibWhitelist();
auto& functionSpecs = getFunctionHooks();
for (auto& lib : whitelist) {
auto failures = hook_single_lib(
lib.c_str(), functionSpecs.data(), functionSpecs.size());
if (failures) {
throw std::runtime_error("Hook failed for library: " + lib);
}
}
return;
}
auto& functionHooks = getFunctionHooks();
auto& seenLibs = getSeenLibs();
facebook::profilo::hooks::hookLoadedLibs(
functionHooks, allowHookingCb, &seenLibs);
}
void unhookLoadedLibs() {
auto sdk = build::Build::getAndroidSdk();
if (sdk >= kSingleLibMinSdk) {
auto& spec = getSingleLibFunctionSpec();
unhook_single_lib(kSingleLibName, &spec, 1);
return;
}
if (sdk >= kLibWhitelistMinSdk) {
auto& whitelist = getLibWhitelist();
auto& functionSpecs = getFunctionHooks();
for (auto& lib : whitelist) {
auto failures = unhook_single_lib(
lib.c_str(), functionSpecs.data(), functionSpecs.size());
if (failures) {
throw std::runtime_error("Unhook failed for library: " + lib);
}
}
return;
}
auto& functionHooks = getFunctionHooks();
facebook::profilo::hooks::unhookLoadedLibs(functionHooks);
getSeenLibs().clear();
}
void installSystraceSnooper(int providerMask) {
auto sdk = build::Build::getAndroidSdk();
{
std::string lib_name("libcutils.so");
std::string enabled_tags_sym("atrace_enabled_tags");
std::string fd_sym("atrace_marker_fd");
if (sdk < 18) {
lib_name = "libutils.so";
// android::Tracer::sEnabledTags
enabled_tags_sym = "_ZN7android6Tracer12sEnabledTagsE";
// android::Tracer::sTraceFD
fd_sym = "_ZN7android6Tracer8sTraceFDE";
}
void* handle;
if (sdk < 21) {
handle = dlopen(lib_name.c_str(), RTLD_LOCAL);
} else {
handle = dlopen(nullptr, RTLD_GLOBAL);
}
atrace_enabled_tags = reinterpret_cast<std::atomic<uint64_t>*>(
dlsym(handle, enabled_tags_sym.c_str()));
if (atrace_enabled_tags == nullptr) {
throw std::runtime_error("Enabled Tags not defined");
}
atrace_marker_fd = reinterpret_cast<int*>(dlsym(handle, fd_sym.c_str()));
if (atrace_marker_fd == nullptr) {
throw std::runtime_error("Trace FD not defined");
}
if (*atrace_marker_fd == -1) {
// This is a case that can happen for older Android version i.e. 4.4
// in which scenario the marker fd is not initialized/opened by Zygote.
// Nevertheless for Profilo trace it is not necessary to have an open fd,
// since all we really need is to ensure that we 'know' it is marker
// fd to continue writing Profilo logs, thus the usage of marker fd
// acting really as a placeholder for magic id.
*atrace_marker_fd = kTracerMagicFd;
}
}
if (plthooks_initialize()) {
throw std::runtime_error("Could not initialize plthooks library");
}
hookLoadedLibs();
systrace_installed = true;
provider_mask = providerMask;
}
void enableSystrace() {
logger::BlockLogger block(*logger, "enableSystrace");
if (!systrace_installed) {
return;
}
if (!first_enable) {
// On every enable, except the first one, find if new libs were loaded
// and install systrace hook for them
try {
hookLoadedLibs();
} catch (...) {
// It's ok to continue if the refresh has failed
}
}
first_enable = false;
auto prev = atrace_enabled_tags->exchange(UINT64_MAX);
if (prev != UINT64_MAX) { // if we somehow call this twice in a row, don't
// overwrite the real tags
original_tags = prev;
}
atrace_enabled = true;
}
void restoreSystrace() {
logger::BlockLogger block(*logger, "restoreSystrace");
atrace_enabled = false;
if (!systrace_installed) {
return;
}
try {
unhookLoadedLibs();
} catch (...) {
}
uint64_t tags = original_tags;
if (tags != UINT64_MAX) { // if we somehow call this before enableSystrace,
// don't screw it up
atrace_enabled_tags->store(tags);
}
}
bool installSystraceHook(logger::JMultiBufferLogger* jlogger, int mask) {
logger = &jlogger->nativeInstance();
logger::BlockLogger block(*logger, "installSystraceHook");
try {
installSystraceSnooper(mask);
return true;
} catch (const std::runtime_error& e) {
FBLOGW("could not install hooks: %s", e.what());
return false;
}
}
} // namespace
bool JNI_installSystraceHook(
fbjni::alias_ref<jobject>,
logger::JMultiBufferLogger* logger,
jint mask) {
return installSystraceHook(logger, mask);
}
void JNI_enableSystraceNative(fbjni::alias_ref<jobject>) {
enableSystrace();
}
void JNI_restoreSystraceNative(fbjni::alias_ref<jobject>) {
restoreSystrace();
}
bool JNI_isEnabled(fbjni::alias_ref<jobject>) {
return atrace_enabled;
}
namespace fbjni = facebook::jni;
void registerNatives() {
fbjni::registerNatives(
"com/facebook/profilo/provider/atrace/Atrace",
{
makeNativeMethod("installSystraceHook", JNI_installSystraceHook),
makeNativeMethod("enableSystraceNative", JNI_enableSystraceNative),
makeNativeMethod("restoreSystraceNative", JNI_restoreSystraceNative),
makeNativeMethod("isEnabled", JNI_isEnabled),
});
}
} // namespace atrace
} // namespace profilo
} // namespace facebook