403 lines
14 KiB
C++
403 lines
14 KiB
C++
//===-- TraceIntelPTBundleSaver.cpp ---------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "TraceIntelPTBundleSaver.h"
|
|
#include "PerfContextSwitchDecoder.h"
|
|
#include "TraceIntelPT.h"
|
|
#include "TraceIntelPTConstants.h"
|
|
#include "TraceIntelPTJSONStructs.h"
|
|
#include "lldb/Core/Module.h"
|
|
#include "lldb/Core/ModuleList.h"
|
|
#include "lldb/Target/Process.h"
|
|
#include "lldb/Target/SectionLoadList.h"
|
|
#include "lldb/Target/Target.h"
|
|
#include "lldb/Target/ThreadList.h"
|
|
#include "lldb/lldb-types.h"
|
|
#include "llvm/ADT/None.h"
|
|
#include "llvm/Support/Error.h"
|
|
#include "llvm/Support/JSON.h"
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <string>
|
|
|
|
using namespace lldb;
|
|
using namespace lldb_private;
|
|
using namespace lldb_private::trace_intel_pt;
|
|
using namespace llvm;
|
|
|
|
/// Strip the \p directory component from the given \p path. It assumes that \p
|
|
/// directory is a prefix of \p path.
|
|
static std::string GetRelativePath(const FileSpec &directory,
|
|
const FileSpec &path) {
|
|
return path.GetPath().substr(directory.GetPath().size() + 1);
|
|
}
|
|
|
|
/// Write a stream of bytes from \p data to the given output file.
|
|
/// It creates or overwrites the output file, but not append.
|
|
static llvm::Error WriteBytesToDisk(FileSpec &output_file,
|
|
ArrayRef<uint8_t> data) {
|
|
std::basic_fstream<char> out_fs = std::fstream(
|
|
output_file.GetPath().c_str(), std::ios::out | std::ios::binary);
|
|
if (!data.empty())
|
|
out_fs.write(reinterpret_cast<const char *>(&data[0]), data.size());
|
|
|
|
out_fs.close();
|
|
if (!out_fs)
|
|
return createStringError(inconvertibleErrorCode(),
|
|
formatv("couldn't write to the file {0}",
|
|
output_file.GetPath().c_str()));
|
|
return Error::success();
|
|
}
|
|
|
|
/// Save the trace bundle description JSON object inside the given directory
|
|
/// as a file named \a trace.json.
|
|
///
|
|
/// \param[in] trace_bundle_description
|
|
/// The trace bundle description as JSON Object.
|
|
///
|
|
/// \param[in] directory
|
|
/// The directory where the JSON file will be saved.
|
|
///
|
|
/// \return
|
|
/// A \a FileSpec pointing to the bundle description file, or an \a
|
|
/// llvm::Error otherwise.
|
|
static Expected<FileSpec>
|
|
SaveTraceBundleDescription(const llvm::json::Value &trace_bundle_description,
|
|
const FileSpec &directory) {
|
|
FileSpec trace_path = directory;
|
|
trace_path.AppendPathComponent("trace.json");
|
|
std::ofstream os(trace_path.GetPath());
|
|
os << formatv("{0:2}", trace_bundle_description).str();
|
|
os.close();
|
|
if (!os)
|
|
return createStringError(inconvertibleErrorCode(),
|
|
formatv("couldn't write to the file {0}",
|
|
trace_path.GetPath().c_str()));
|
|
return trace_path;
|
|
}
|
|
|
|
/// Build the threads sub-section of the trace bundle description file.
|
|
/// Any associated binary files are created inside the given directory.
|
|
///
|
|
/// \param[in] process
|
|
/// The process being traced.
|
|
///
|
|
/// \param[in] directory
|
|
/// The directory where files will be saved when building the threads
|
|
/// section.
|
|
///
|
|
/// \return
|
|
/// The threads section or \a llvm::Error in case of failures.
|
|
static llvm::Expected<std::vector<JSONThread>>
|
|
BuildThreadsSection(Process &process, FileSpec directory) {
|
|
std::vector<JSONThread> json_threads;
|
|
TraceSP trace_sp = process.GetTarget().GetTrace();
|
|
|
|
FileSpec threads_dir = directory;
|
|
threads_dir.AppendPathComponent("threads");
|
|
sys::fs::create_directories(threads_dir.GetPath().c_str());
|
|
|
|
for (ThreadSP thread_sp : process.Threads()) {
|
|
lldb::tid_t tid = thread_sp->GetID();
|
|
if (!trace_sp->IsTraced(tid))
|
|
continue;
|
|
|
|
JSONThread json_thread;
|
|
json_thread.tid = tid;
|
|
|
|
if (trace_sp->GetTracedCpus().empty()) {
|
|
FileSpec output_file = threads_dir;
|
|
output_file.AppendPathComponent(std::to_string(tid) + ".intelpt_trace");
|
|
json_thread.ipt_trace = GetRelativePath(directory, output_file);
|
|
|
|
llvm::Error err = process.GetTarget().GetTrace()->OnThreadBinaryDataRead(
|
|
tid, IntelPTDataKinds::kIptTrace,
|
|
[&](llvm::ArrayRef<uint8_t> data) -> llvm::Error {
|
|
return WriteBytesToDisk(output_file, data);
|
|
});
|
|
if (err)
|
|
return std::move(err);
|
|
}
|
|
|
|
json_threads.push_back(std::move(json_thread));
|
|
}
|
|
return json_threads;
|
|
}
|
|
|
|
/// \return
|
|
/// an \a llvm::Error in case of failures, \a None if the trace is not written
|
|
/// to disk because the trace is empty and the \p compact flag is present, or
|
|
/// the FileSpec of the trace file on disk.
|
|
static Expected<Optional<FileSpec>>
|
|
WriteContextSwitchTrace(TraceIntelPT &trace_ipt, lldb::cpu_id_t cpu_id,
|
|
const FileSpec &cpus_dir, bool compact) {
|
|
FileSpec output_context_switch_trace = cpus_dir;
|
|
output_context_switch_trace.AppendPathComponent(std::to_string(cpu_id) +
|
|
".perf_context_switch_trace");
|
|
|
|
bool should_skip = false;
|
|
|
|
Error err = trace_ipt.OnCpuBinaryDataRead(
|
|
cpu_id, IntelPTDataKinds::kPerfContextSwitchTrace,
|
|
[&](llvm::ArrayRef<uint8_t> data) -> llvm::Error {
|
|
if (!compact)
|
|
return WriteBytesToDisk(output_context_switch_trace, data);
|
|
|
|
std::set<lldb::pid_t> pids;
|
|
for (Process *process : trace_ipt.GetAllProcesses())
|
|
pids.insert(process->GetID());
|
|
|
|
Expected<std::vector<uint8_t>> compact_context_switch_trace =
|
|
FilterProcessesFromContextSwitchTrace(data, pids);
|
|
if (!compact_context_switch_trace)
|
|
return compact_context_switch_trace.takeError();
|
|
|
|
if (compact_context_switch_trace->empty()) {
|
|
should_skip = true;
|
|
return Error::success();
|
|
}
|
|
|
|
return WriteBytesToDisk(output_context_switch_trace,
|
|
*compact_context_switch_trace);
|
|
});
|
|
if (err)
|
|
return std::move(err);
|
|
|
|
if (should_skip)
|
|
return None;
|
|
return output_context_switch_trace;
|
|
}
|
|
|
|
static Expected<FileSpec> WriteIntelPTTrace(TraceIntelPT &trace_ipt,
|
|
lldb::cpu_id_t cpu_id,
|
|
const FileSpec &cpus_dir) {
|
|
FileSpec output_trace = cpus_dir;
|
|
output_trace.AppendPathComponent(std::to_string(cpu_id) + ".intelpt_trace");
|
|
|
|
Error err = trace_ipt.OnCpuBinaryDataRead(
|
|
cpu_id, IntelPTDataKinds::kIptTrace,
|
|
[&](llvm::ArrayRef<uint8_t> data) -> llvm::Error {
|
|
return WriteBytesToDisk(output_trace, data);
|
|
});
|
|
if (err)
|
|
return std::move(err);
|
|
return output_trace;
|
|
}
|
|
|
|
static llvm::Expected<llvm::Optional<std::vector<JSONCpu>>>
|
|
BuildCpusSection(TraceIntelPT &trace_ipt, FileSpec directory, bool compact) {
|
|
if (trace_ipt.GetTracedCpus().empty())
|
|
return None;
|
|
|
|
std::vector<JSONCpu> json_cpus;
|
|
FileSpec cpus_dir = directory;
|
|
cpus_dir.AppendPathComponent("cpus");
|
|
sys::fs::create_directories(cpus_dir.GetPath().c_str());
|
|
|
|
for (lldb::cpu_id_t cpu_id : trace_ipt.GetTracedCpus()) {
|
|
JSONCpu json_cpu;
|
|
json_cpu.id = cpu_id;
|
|
Expected<Optional<FileSpec>> context_switch_trace_path =
|
|
WriteContextSwitchTrace(trace_ipt, cpu_id, cpus_dir, compact);
|
|
if (!context_switch_trace_path)
|
|
return context_switch_trace_path.takeError();
|
|
if (!*context_switch_trace_path)
|
|
continue;
|
|
json_cpu.context_switch_trace =
|
|
GetRelativePath(directory, **context_switch_trace_path);
|
|
|
|
if (Expected<FileSpec> ipt_trace_path =
|
|
WriteIntelPTTrace(trace_ipt, cpu_id, cpus_dir))
|
|
json_cpu.ipt_trace = GetRelativePath(directory, *ipt_trace_path);
|
|
else
|
|
return ipt_trace_path.takeError();
|
|
|
|
json_cpus.push_back(std::move(json_cpu));
|
|
}
|
|
return json_cpus;
|
|
}
|
|
|
|
/// Build modules sub-section of the trace bundle. The original modules
|
|
/// will be copied over to the \a <directory/modules> folder. Invalid modules
|
|
/// are skipped.
|
|
/// Copying the modules has the benefit of making these
|
|
/// directories self-contained, as the raw traces and modules are part of the
|
|
/// output directory and can be sent to another machine, where lldb can load
|
|
/// them and replicate exactly the same trace session.
|
|
///
|
|
/// \param[in] process
|
|
/// The process being traced.
|
|
///
|
|
/// \param[in] directory
|
|
/// The directory where the modules files will be saved when building
|
|
/// the modules section.
|
|
/// Example: If a module \a libbar.so exists in the path
|
|
/// \a /usr/lib/foo/libbar.so, then it will be copied to
|
|
/// \a <directory>/modules/usr/lib/foo/libbar.so.
|
|
///
|
|
/// \return
|
|
/// The modules section or \a llvm::Error in case of failures.
|
|
static llvm::Expected<std::vector<JSONModule>>
|
|
BuildModulesSection(Process &process, FileSpec directory) {
|
|
std::vector<JSONModule> json_modules;
|
|
ModuleList module_list = process.GetTarget().GetImages();
|
|
for (size_t i = 0; i < module_list.GetSize(); ++i) {
|
|
ModuleSP module_sp(module_list.GetModuleAtIndex(i));
|
|
if (!module_sp)
|
|
continue;
|
|
std::string system_path = module_sp->GetPlatformFileSpec().GetPath();
|
|
// TODO: support memory-only libraries like [vdso]
|
|
if (!module_sp->GetFileSpec().IsAbsolute())
|
|
continue;
|
|
|
|
std::string file = module_sp->GetFileSpec().GetPath();
|
|
ObjectFile *objfile = module_sp->GetObjectFile();
|
|
if (objfile == nullptr)
|
|
continue;
|
|
|
|
lldb::addr_t load_addr = LLDB_INVALID_ADDRESS;
|
|
Address base_addr(objfile->GetBaseAddress());
|
|
if (base_addr.IsValid() &&
|
|
!process.GetTarget().GetSectionLoadList().IsEmpty())
|
|
load_addr = base_addr.GetLoadAddress(&process.GetTarget());
|
|
|
|
if (load_addr == LLDB_INVALID_ADDRESS)
|
|
continue;
|
|
|
|
FileSpec path_to_copy_module = directory;
|
|
path_to_copy_module.AppendPathComponent("modules");
|
|
path_to_copy_module.AppendPathComponent(system_path);
|
|
sys::fs::create_directories(path_to_copy_module.GetDirectory().AsCString());
|
|
|
|
if (std::error_code ec =
|
|
llvm::sys::fs::copy_file(file, path_to_copy_module.GetPath()))
|
|
return createStringError(
|
|
inconvertibleErrorCode(),
|
|
formatv("couldn't write to the file. {0}", ec.message()));
|
|
|
|
json_modules.push_back(
|
|
JSONModule{system_path, GetRelativePath(directory, path_to_copy_module),
|
|
JSONUINT64{load_addr}, module_sp->GetUUID().GetAsString()});
|
|
}
|
|
return json_modules;
|
|
}
|
|
|
|
/// Build the processes section of the trace bundle description object. Besides
|
|
/// returning the processes information, this method saves to disk all modules
|
|
/// and raw traces corresponding to the traced threads of the given process.
|
|
///
|
|
/// \param[in] process
|
|
/// The process being traced.
|
|
///
|
|
/// \param[in] directory
|
|
/// The directory where files will be saved when building the processes
|
|
/// section.
|
|
///
|
|
/// \return
|
|
/// The processes section or \a llvm::Error in case of failures.
|
|
static llvm::Expected<JSONProcess>
|
|
BuildProcessSection(Process &process, const FileSpec &directory) {
|
|
Expected<std::vector<JSONThread>> json_threads =
|
|
BuildThreadsSection(process, directory);
|
|
if (!json_threads)
|
|
return json_threads.takeError();
|
|
|
|
Expected<std::vector<JSONModule>> json_modules =
|
|
BuildModulesSection(process, directory);
|
|
if (!json_modules)
|
|
return json_modules.takeError();
|
|
|
|
return JSONProcess{
|
|
process.GetID(),
|
|
process.GetTarget().GetArchitecture().GetTriple().getTriple(),
|
|
json_threads.get(), json_modules.get()};
|
|
}
|
|
|
|
/// See BuildProcessSection()
|
|
static llvm::Expected<std::vector<JSONProcess>>
|
|
BuildProcessesSection(TraceIntelPT &trace_ipt, const FileSpec &directory) {
|
|
std::vector<JSONProcess> processes;
|
|
for (Process *process : trace_ipt.GetAllProcesses()) {
|
|
if (llvm::Expected<JSONProcess> json_process =
|
|
BuildProcessSection(*process, directory))
|
|
processes.push_back(std::move(*json_process));
|
|
else
|
|
return json_process.takeError();
|
|
}
|
|
return processes;
|
|
}
|
|
|
|
static llvm::Expected<JSONKernel>
|
|
BuildKernelSection(TraceIntelPT &trace_ipt, const FileSpec &directory) {
|
|
JSONKernel json_kernel;
|
|
std::vector<Process *> processes = trace_ipt.GetAllProcesses();
|
|
Process *kernel_process = processes[0];
|
|
|
|
assert(processes.size() == 1 && "User processeses exist in kernel mode");
|
|
assert(kernel_process->GetID() == kDefaultKernelProcessID &&
|
|
"Kernel process not exist");
|
|
|
|
Expected<std::vector<JSONModule>> json_modules =
|
|
BuildModulesSection(*kernel_process, directory);
|
|
if (!json_modules)
|
|
return json_modules.takeError();
|
|
|
|
JSONModule kernel_image = json_modules.get()[0];
|
|
return JSONKernel{kernel_image.load_address, kernel_image.system_path};
|
|
}
|
|
|
|
Expected<FileSpec> TraceIntelPTBundleSaver::SaveToDisk(TraceIntelPT &trace_ipt,
|
|
FileSpec directory,
|
|
bool compact) {
|
|
if (std::error_code ec =
|
|
sys::fs::create_directories(directory.GetPath().c_str()))
|
|
return llvm::errorCodeToError(ec);
|
|
|
|
Expected<pt_cpu> cpu_info = trace_ipt.GetCPUInfo();
|
|
if (!cpu_info)
|
|
return cpu_info.takeError();
|
|
|
|
FileSystem::Instance().Resolve(directory);
|
|
|
|
Expected<Optional<std::vector<JSONCpu>>> json_cpus =
|
|
BuildCpusSection(trace_ipt, directory, compact);
|
|
if (!json_cpus)
|
|
return json_cpus.takeError();
|
|
|
|
Optional<std::vector<JSONProcess>> json_processes;
|
|
Optional<JSONKernel> json_kernel;
|
|
|
|
if (trace_ipt.GetTraceMode() == TraceIntelPT::TraceMode::KernelMode) {
|
|
Expected<Optional<JSONKernel>> exp_json_kernel =
|
|
BuildKernelSection(trace_ipt, directory);
|
|
if (!exp_json_kernel)
|
|
return exp_json_kernel.takeError();
|
|
else
|
|
json_kernel = *exp_json_kernel;
|
|
} else {
|
|
Expected<Optional<std::vector<JSONProcess>>> exp_json_processes =
|
|
BuildProcessesSection(trace_ipt, directory);
|
|
if (!exp_json_processes)
|
|
return exp_json_processes.takeError();
|
|
else
|
|
json_processes = *exp_json_processes;
|
|
}
|
|
|
|
JSONTraceBundleDescription json_intel_pt_bundle_desc{
|
|
"intel-pt",
|
|
*cpu_info,
|
|
json_processes,
|
|
*json_cpus,
|
|
trace_ipt.GetPerfZeroTscConversion(),
|
|
json_kernel};
|
|
|
|
return SaveTraceBundleDescription(toJSON(json_intel_pt_bundle_desc),
|
|
directory);
|
|
}
|