libredex/Trace.cpp (201 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #include "Trace.h" #include <array> #include <cstdarg> #include <cstdio> #include <cstdlib> #include <cstring> #include <ctime> #include <iostream> #include <mutex> #include <string> #include <unordered_map> #include <utility> #include "ControlFlow.h" #include "Debug.h" #include "DexClass.h" #include "IRCode.h" #include "Macros.h" #include "Show.h" #include "TraceContextAccess.h" namespace { struct Tracer { bool m_show_timestamps{false}; bool m_show_tracemodule{false}; const char* m_method_filter; std::unordered_map<int /*TraceModule*/, std::string> m_module_id_name_map; Tracer() { const char* traceenv = getenv("TRACE"); const char* envfile = getenv("TRACEFILE"); const char* show_timestamps = getenv("SHOW_TIMESTAMPS"); const char* show_tracemodule = getenv("SHOW_TRACEMODULE"); m_method_filter = getenv("TRACE_METHOD_FILTER"); if (!traceenv) { init_trace_file(nullptr); return; } std::cerr << "Trace settings:" << std::endl; std::cerr << "TRACEFILE=" << (envfile == nullptr ? "" : envfile) << std::endl; std::cerr << "SHOW_TIMESTAMPS=" << (show_timestamps == nullptr ? "" : show_timestamps) << std::endl; std::cerr << "SHOW_TRACEMODULE=" << (show_tracemodule == nullptr ? "" : show_tracemodule) << std::endl; std::cerr << "TRACE_METHOD_FILTER=" << (m_method_filter == nullptr ? "" : m_method_filter) << std::endl; init_trace_modules(traceenv); init_trace_file(envfile); if (show_timestamps) { m_show_timestamps = true; } if (show_tracemodule) { m_show_tracemodule = true; } #define TM(x) m_module_id_name_map[static_cast<int>(x)] = #x; TMS #undef TM } ~Tracer() { if (m_file != nullptr && m_file != stderr) { fclose(m_file); } } bool check_trace_context() const { #if !IS_WINDOWS if (m_method_filter == nullptr) { return true; } const TraceContext* context = TraceContextAccess::get_s_context(); if (context == nullptr) { return true; } return context->get_string_value().find(m_method_filter) != std::string::npos; #else return true; #endif } bool traceEnabled(TraceModule module, int level) const { bool by_level = level <= m_level || level <= m_traces[module]; if (!by_level) { return false; } return check_trace_context(); } void trace(TraceModule module, int level, bool suppress_newline, const char* fmt, va_list ap) { // Assume that `trace` is never called without `traceEnabled`, so we // do not need to check anything (including context) here. std::lock_guard<std::mutex> guard(m_trace_mutex); if (m_show_timestamps) { auto t = std::time(nullptr); struct tm local_tm; #if IS_WINDOWS localtime_s(&local_tm, &t); #else localtime_r(&t, &local_tm); #endif std::array<char, 40> buf; std::strftime(buf.data(), sizeof(buf), "%c", &local_tm); fprintf(m_file, "[%s]", buf.data()); if (!m_show_tracemodule) { fprintf(m_file, " "); } } if (m_show_tracemodule) { fprintf(m_file, "[%s:%d] ", m_module_id_name_map[module].c_str(), level); } vfprintf(m_file, fmt, ap); if (!suppress_newline) { fprintf(m_file, "\n"); } fflush(m_file); } private: void init_trace_modules(const char* traceenv) { std::unordered_map<std::string, int> module_id_map{{ #define TM(x) {std::string(#x), x}, TMS #undef TM }}; char* tracespec = strdup(traceenv); const char* sep = ",: "; const char* module = nullptr; for (const char* tok = strtok(tracespec, sep); tok != nullptr; tok = strtok(nullptr, sep)) { auto level = strtol(tok, nullptr, 10); if (level) { if (module) { if (module_id_map.count(module) == 0) { if (strcmp(module, "REDEX") == 0) { continue; // Ignore REDEX. } fprintf(stderr, "Unknown trace level %s\n", module); abort(); } m_traces[module_id_map[module]] = level; } else { m_level = level; } module = nullptr; } else { module = tok; } } free(tracespec); } void init_trace_file(const char* envfile) { if (!envfile) { m_file = stderr; return; } try { // If redex-all is called from redex.py, the tracefile has been created // already. And TRACEFILE is replaced by the file descriptor instead. // Refer to update_trace_file in pyredex/logger.py. auto fd = std::stoi(envfile); m_file = fdopen(fd, "w"); } catch (std::invalid_argument&) { // Not an integer file descriptor; real file name. m_file = fopen(envfile, "w"); } if (!m_file) { fprintf(stderr, "Unable to open TRACEFILE, falling back to stderr\n"); m_file = stderr; } } private: FILE* m_file{nullptr}; long m_level{0}; std::array<long, N_TRACE_MODULES> m_traces; std::mutex m_trace_mutex; }; static Tracer tracer; } // namespace #ifndef NDEBUG bool traceEnabled(TraceModule module, int level) { return tracer.traceEnabled(module, level); } #endif void trace(TraceModule module, int level, bool suppress_newline, const char* fmt, ...) { va_list ap; va_start(ap, fmt); tracer.trace(module, level, suppress_newline, fmt, ap); va_end(ap); } #if !IS_WINDOWS const std::string& TraceContext::get_string_value() const { if (string_value->empty()) { if (method != nullptr) { string_value_cache = show_deobfuscated(method); } else if (type != nullptr) { string_value_cache = show_deobfuscated(type); } } return *string_value; } thread_local const TraceContext* TraceContext::s_context = nullptr; #endif