Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Android.bp
Original file line number Diff line number Diff line change
Expand Up @@ -16729,6 +16729,14 @@ filegroup {
],
}

// GN: //src/trace_processor/util/trace_enrichment:trace_enrichment
filegroup {
name: "perfetto_src_trace_processor_util_trace_enrichment_trace_enrichment",
srcs: [
"src/trace_processor/util/trace_enrichment/trace_enrichment.cc",
],
}

// GN: //src/trace_processor/util:trace_type
filegroup {
name: "perfetto_src_trace_processor_util_trace_type",
Expand Down Expand Up @@ -20015,6 +20023,7 @@ cc_binary_host {
":perfetto_src_trace_processor_util_symbolizer_symbolizer",
":perfetto_src_trace_processor_util_tar_writer",
":perfetto_src_trace_processor_util_trace_blob_view_reader",
":perfetto_src_trace_processor_util_trace_enrichment_trace_enrichment",
":perfetto_src_trace_processor_util_trace_type",
":perfetto_src_trace_processor_util_winscope_proto_mapping",
":perfetto_src_trace_processor_util_zip_reader",
Expand Down
10 changes: 10 additions & 0 deletions BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -4151,6 +4151,15 @@ perfetto_filegroup(
],
)

# GN target: //src/trace_processor/util/trace_enrichment:trace_enrichment
perfetto_filegroup(
name = "src_trace_processor_util_trace_enrichment_trace_enrichment",
srcs = [
"src/trace_processor/util/trace_enrichment/trace_enrichment.cc",
"src/trace_processor/util/trace_enrichment/trace_enrichment.h",
],
)

# GN target: //src/trace_processor/util:args_utils
perfetto_filegroup(
name = "src_trace_processor_util_args_utils",
Expand Down Expand Up @@ -8492,6 +8501,7 @@ perfetto_cc_binary(
":src_trace_processor_util_symbolizer_symbolizer",
":src_trace_processor_util_tar_writer",
":src_trace_processor_util_trace_blob_view_reader",
":src_trace_processor_util_trace_enrichment_trace_enrichment",
":src_trace_processor_util_trace_type",
":src_trace_processor_util_winscope_proto_mapping",
":src_trace_processor_util_zip_reader",
Expand Down
11 changes: 11 additions & 0 deletions include/perfetto/base/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,17 @@ extern "C" void __asan_unpoison_memory_region(void const volatile*, size_t);
#define PERFETTO_ASAN_UNPOISON(addr, size)
#endif // __clang__

#if defined(__clang__)
#if __has_feature(memory_sanitizer)
extern "C" void __msan_unpoison(void const volatile*, size_t);
#define PERFETTO_MSAN_UNPOISON(a, s) __msan_unpoison((a), (s))
#else
#define PERFETTO_MSAN_UNPOISON(addr, size)
#endif // __has_feature(memory_sanitizer)
#else
#define PERFETTO_MSAN_UNPOISON(addr, size)
#endif // __clang__

#if defined(__GNUC__) || defined(__clang__)
#define PERFETTO_IS_LITTLE_ENDIAN() __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#else
Expand Down
6 changes: 6 additions & 0 deletions include/perfetto/ext/base/file_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@ std::string Dirname(const std::string& path);
base::Status ListFilesRecursive(const std::string& dir_path,
std::vector<std::string>& output);

// Lists immediate subdirectories in |dir_path| (non-recursive). Directory names
// are relative to |dir_path| and do not include the path separator. Returns
// only directories, not files. Works on both Unix and Windows.
base::Status ListDirectories(const std::string& dir_path,
std::vector<std::string>& output);

// Sets |path|'s owner group to |group_name| and permission mode bits to
// |mode_bits|.
base::Status SetFilePermissions(const std::string& path,
Expand Down
53 changes: 53 additions & 0 deletions src/base/file_utils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,59 @@ base::Status ListFilesRecursive(const std::string& dir_path,
return base::OkStatus();
}

base::Status ListDirectories(const std::string& dir_path,
std::vector<std::string>& output) {
std::string normalized_path = dir_path;
if (!normalized_path.empty() && normalized_path.back() != '/' &&
normalized_path.back() != '\\') {
normalized_path.push_back('/');
}

#if PERFETTO_BUILDFLAG(PERFETTO_OS_NACL)
return base::ErrStatus("ListDirectories not supported yet");
#elif PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
std::string glob_path = normalized_path + "*";
if (glob_path.length() + 1 > MAX_PATH) {
return base::ErrStatus("Directory path %s is too long", dir_path.c_str());
}
WIN32_FIND_DATAA ffd;

base::ScopedResource<HANDLE, CloseFindHandle, nullptr, false,
base::PlatformHandleChecker>
hFind(FindFirstFileA(glob_path.c_str(), &ffd));
if (!hFind) {
return base::ErrStatus("Failed to open directory %s",
normalized_path.c_str());
}
do {
if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) {
continue;
}
if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
output.push_back(ffd.cFileName);
}
} while (FindNextFileA(*hFind, &ffd));
#else
ScopedDir dir = ScopedDir(opendir(normalized_path.c_str()));
if (!dir) {
return base::ErrStatus("Failed to open directory %s",
normalized_path.c_str());
}
for (auto* dirent = readdir(dir.get()); dirent != nullptr;
dirent = readdir(dir.get())) {
if (strcmp(dirent->d_name, ".") == 0 || strcmp(dirent->d_name, "..") == 0) {
continue;
}
std::string full_path = normalized_path + dirent->d_name;
struct stat dirstat;
if (stat(full_path.c_str(), &dirstat) == 0 && S_ISDIR(dirstat.st_mode)) {
output.push_back(dirent->d_name);
}
}
#endif
return base::OkStatus();
}

std::string GetFileExtension(const std::string& filename) {
auto ext_idx = filename.rfind('.');
if (ext_idx == std::string::npos)
Expand Down
24 changes: 11 additions & 13 deletions src/trace_processor/trace_processor_shell.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1365,19 +1365,17 @@ base::Status LoadTrace(TraceProcessor* trace_processor,
if (symbolizer) {
if (is_proto_trace) {
trace_processor->Flush();
profiling::SymbolizeDatabase(
trace_processor, symbolizer.get(),
[trace_processor](const std::string& trace_proto) {
std::unique_ptr<uint8_t[]> buf(new uint8_t[trace_proto.size()]);
memcpy(buf.get(), trace_proto.data(), trace_proto.size());
auto status =
trace_processor->Parse(std::move(buf), trace_proto.size());
if (!status.ok()) {
PERFETTO_DFATAL_OR_ELOG("Failed to parse: %s",
status.message().c_str());
return;
}
});
std::string symbols = profiling::SymbolizeDatabaseWithSymbolizer(
trace_processor, symbolizer.get());
if (!symbols.empty()) {
std::unique_ptr<uint8_t[]> buf(new uint8_t[symbols.size()]);
memcpy(buf.get(), symbols.data(), symbols.size());
auto status = trace_processor->Parse(std::move(buf), symbols.size());
if (!status.ok()) {
PERFETTO_DFATAL_OR_ELOG("Failed to parse: %s",
status.message().c_str());
}
}
} else {
// TODO(lalitm): support symbolization for non-proto traces.
PERFETTO_ELOG("Skipping symbolization for non-proto trace");
Expand Down
9 changes: 8 additions & 1 deletion src/trace_processor/util/symbolizer/filesystem_posix.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "src/trace_processor/util/symbolizer/filesystem.h"

#include "perfetto/base/build_config.h"
#include "perfetto/base/compiler.h"

#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
#if PERFETTO_BUILDFLAG(PERFETTO_LOCAL_SYMBOLIZER)
Expand Down Expand Up @@ -45,7 +46,13 @@ bool WalkDirectories(std::vector<std::string> dirs, FileCallback fn) {
}
FTSENT* ent;
while ((ent = fts_read(*fts))) {
if (ent->fts_info & FTS_F)
// glibc's fts_* functions don't have MSan interceptors, so we need to
// manually unpoison the returned FTSENT structure and the memory it
// points to.
PERFETTO_MSAN_UNPOISON(ent, sizeof(*ent));
PERFETTO_MSAN_UNPOISON(ent->fts_path, ent->fts_pathlen + 1);
PERFETTO_MSAN_UNPOISON(ent->fts_statp, sizeof(*ent->fts_statp));
if (ent->fts_info == FTS_F)
fn(ent->fts_path, static_cast<size_t>(ent->fts_statp->st_size));
}
return true;
Expand Down
63 changes: 55 additions & 8 deletions src/trace_processor/util/symbolizer/symbolize_database.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <functional>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <tuple>
#include <unordered_set>
#include <utility>
#include <vector>

Expand All @@ -34,8 +35,9 @@
#include "perfetto/trace_processor/basic_types.h"
#include "perfetto/trace_processor/iterator.h"
#include "perfetto/trace_processor/trace_processor.h"
#include "src/trace_processor/util/symbolizer/symbolizer.h"
#include "src/trace_processor/util/build_id.h"
#include "src/trace_processor/util/symbolizer/local_symbolizer.h"
#include "src/trace_processor/util/symbolizer/symbolizer.h"

#include "protos/perfetto/trace/profiling/profile_common.pbzero.h"
#include "protos/perfetto/trace/trace.pbzero.h"
Expand Down Expand Up @@ -63,8 +65,6 @@ constexpr const char* kQueryUnsymbolized =
and spf.symbol_set_id IS NULL
)";

using NameAndBuildIdPair = std::pair<std::string, std::string>;

struct UnsymbolizedMapping {
std::string name;
std::string build_id;
Expand Down Expand Up @@ -107,14 +107,41 @@ std::optional<std::string> GetOsRelease(trace_processor::TraceProcessor* tp) {
return std::nullopt;
}

// Creates a symbolizer based on provided config.
std::unique_ptr<Symbolizer> CreateSymbolizer(const SymbolizerConfig& config) {
std::unordered_set<std::string> dirs;
std::unordered_set<std::string> files;

// Always add paths from PERFETTO_BINARY_PATH environment variable.
std::vector<std::string> env_binary_paths = GetPerfettoBinaryPath();
if (!env_binary_paths.empty()) {
dirs.insert(env_binary_paths.begin(), env_binary_paths.end());
}

// Add user-provided paths.
if (!config.symbol_paths.empty()) {
dirs.insert(config.symbol_paths.begin(), config.symbol_paths.end());
}

// Add user-provided files.
if (!config.symbol_files.empty()) {
files.insert(config.symbol_files.begin(), config.symbol_files.end());
}

return MaybeLocalSymbolizer(
std::vector<std::string>(dirs.begin(), dirs.end()),
std::vector<std::string>(files.begin(), files.end()), "index");
}

} // namespace

void SymbolizeDatabase(trace_processor::TraceProcessor* tp,
Symbolizer* symbolizer,
std::function<void(const std::string&)> callback) {
std::string SymbolizeDatabaseWithSymbolizer(trace_processor::TraceProcessor* tp,
Symbolizer* symbolizer) {
PERFETTO_CHECK(symbolizer);
auto unsymbolized = GetUnsymbolizedFrames(tp);
Symbolizer::Environment env = {GetOsRelease(tp)};

std::string symbols_proto;
for (const auto& [unsymbolized_mapping, rel_pcs] : unsymbolized) {
auto res = symbolizer->Symbolize(env, unsymbolized_mapping.name,
unsymbolized_mapping.build_id,
Expand All @@ -139,8 +166,28 @@ void SymbolizeDatabase(trace_processor::TraceProcessor* tp,
line->set_line_number(frame.line);
}
}
callback(trace.SerializeAsString());
symbols_proto += trace.SerializeAsString();
}
return symbols_proto;
}

SymbolizerResult SymbolizeDatabase(trace_processor::TraceProcessor* tp,
const SymbolizerConfig& config) {
SymbolizerResult result;

// Create the symbolizer.
auto symbolizer = CreateSymbolizer(config);
if (!symbolizer) {
result.error = SymbolizerError::kSymbolizerNotAvailable;
result.error_details =
"Could not create symbolizer (llvm-symbolizer not found?)";
return result;
}

// Run symbolization.
result.symbols = SymbolizeDatabaseWithSymbolizer(tp, symbolizer.get());
result.error = SymbolizerError::kOk;
return result;
}

std::vector<std::string> GetPerfettoBinaryPath() {
Expand Down
81 changes: 68 additions & 13 deletions src/trace_processor/util/symbolizer/symbolize_database.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,79 @@
#ifndef SRC_TRACE_PROCESSOR_UTIL_SYMBOLIZER_SYMBOLIZE_DATABASE_H_
#define SRC_TRACE_PROCESSOR_UTIL_SYMBOLIZER_SYMBOLIZE_DATABASE_H_

#include "src/trace_processor/util/symbolizer/symbolizer.h"

#include <functional>
#include <string>
#include <vector>

namespace perfetto {
namespace trace_processor {
#include "src/trace_processor/util/symbolizer/symbolizer.h"

namespace perfetto::trace_processor {
class TraceProcessor;
}
namespace profiling {

namespace perfetto::profiling {

// Error codes for symbolization operations.
// Caller uses these to decide what user-facing message to show.
enum class SymbolizerError {
kOk,
kSymbolizerNotAvailable, // llvm-symbolizer not found
kSymbolizationFailed, // Symbolizer ran but failed
};

// Configuration for symbolization.
struct SymbolizerConfig {
// Directories to search for symbols. These are added to paths from
// PERFETTO_BINARY_PATH env var.
std::vector<std::string> symbol_paths;

// Specific files to check for symbols (e.g., binary paths from mappings
// that might contain embedded symbols).
std::vector<std::string> symbol_files;
};

// Result of symbolization operation.
struct SymbolizerResult {
SymbolizerError error = SymbolizerError::kOk;

// Machine-readable details about the error (e.g., missing path).
// Empty on success.
std::string error_details;

// Serialized TracePacket protos containing symbol data.
// Empty if no symbols were found or on error.
std::string symbols;
};

// Performs native symbolization on a trace.
//
// This function:
// 1. Queries the trace for stack_profile_mapping entries with build IDs
// 2. Discovers symbol paths from:
// - PERFETTO_BINARY_PATH environment variable (always)
// - User-provided paths in config
// - Binary paths from mappings (for embedded symbols)
// 3. Creates a symbolizer and runs symbolization
// 4. Returns the result as serialized TracePacket protos
//
// Args:
// tp: TraceProcessor instance with the trace already loaded
// config: Configuration for symbolization (paths)
//
// Returns:
// SymbolizerResult containing error code and symbols (if successful)
SymbolizerResult SymbolizeDatabase(trace_processor::TraceProcessor* tp,
const SymbolizerConfig& config);

// Returns paths from PERFETTO_BINARY_PATH environment variable.
// Used internally but exposed for callers that need to inspect paths.
std::vector<std::string> GetPerfettoBinaryPath();
// Generate ModuleSymbol protos for all unsymbolized frames in the database.
// Wrap them in proto-encoded TracePackets messages and call callback.
void SymbolizeDatabase(trace_processor::TraceProcessor* tp,
Symbolizer* symbolizer,
std::function<void(const std::string&)> callback);
} // namespace profiling
} // namespace perfetto

// Low-level symbolization with a pre-created symbolizer.
// Use this when you need custom symbolizer configuration (e.g., Breakpad).
// Returns serialized TracePacket protos containing symbol data.
std::string SymbolizeDatabaseWithSymbolizer(trace_processor::TraceProcessor* tp,
Symbolizer* symbolizer);

} // namespace perfetto::profiling

#endif // SRC_TRACE_PROCESSOR_UTIL_SYMBOLIZER_SYMBOLIZE_DATABASE_H_
Loading