Path: blob/main/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTBundleSaver.cpp
39648 views
//===-- TraceIntelPTBundleSaver.cpp ---------------------------------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78#include "TraceIntelPTBundleSaver.h"9#include "PerfContextSwitchDecoder.h"10#include "TraceIntelPT.h"11#include "TraceIntelPTConstants.h"12#include "TraceIntelPTJSONStructs.h"13#include "lldb/Core/Module.h"14#include "lldb/Core/ModuleList.h"15#include "lldb/Target/Process.h"16#include "lldb/Target/SectionLoadList.h"17#include "lldb/Target/Target.h"18#include "lldb/Target/ThreadList.h"19#include "lldb/lldb-types.h"20#include "llvm/Support/Error.h"21#include "llvm/Support/JSON.h"22#include <fstream>23#include <iostream>24#include <optional>25#include <sstream>26#include <string>2728using namespace lldb;29using namespace lldb_private;30using namespace lldb_private::trace_intel_pt;31using namespace llvm;3233/// Strip the \p directory component from the given \p path. It assumes that \p34/// directory is a prefix of \p path.35static std::string GetRelativePath(const FileSpec &directory,36const FileSpec &path) {37return path.GetPath().substr(directory.GetPath().size() + 1);38}3940/// Write a stream of bytes from \p data to the given output file.41/// It creates or overwrites the output file, but not append.42static llvm::Error WriteBytesToDisk(FileSpec &output_file,43ArrayRef<uint8_t> data) {44std::basic_fstream<char> out_fs = std::fstream(45output_file.GetPath().c_str(), std::ios::out | std::ios::binary);46if (!data.empty())47out_fs.write(reinterpret_cast<const char *>(&data[0]), data.size());4849out_fs.close();50if (!out_fs)51return createStringError(inconvertibleErrorCode(),52formatv("couldn't write to the file {0}",53output_file.GetPath().c_str()));54return Error::success();55}5657/// Save the trace bundle description JSON object inside the given directory58/// as a file named \a trace.json.59///60/// \param[in] trace_bundle_description61/// The trace bundle description as JSON Object.62///63/// \param[in] directory64/// The directory where the JSON file will be saved.65///66/// \return67/// A \a FileSpec pointing to the bundle description file, or an \a68/// llvm::Error otherwise.69static Expected<FileSpec>70SaveTraceBundleDescription(const llvm::json::Value &trace_bundle_description,71const FileSpec &directory) {72FileSpec trace_path = directory;73trace_path.AppendPathComponent("trace.json");74std::ofstream os(trace_path.GetPath());75os << formatv("{0:2}", trace_bundle_description).str();76os.close();77if (!os)78return createStringError(inconvertibleErrorCode(),79formatv("couldn't write to the file {0}",80trace_path.GetPath().c_str()));81return trace_path;82}8384/// Build the threads sub-section of the trace bundle description file.85/// Any associated binary files are created inside the given directory.86///87/// \param[in] process88/// The process being traced.89///90/// \param[in] directory91/// The directory where files will be saved when building the threads92/// section.93///94/// \return95/// The threads section or \a llvm::Error in case of failures.96static llvm::Expected<std::vector<JSONThread>>97BuildThreadsSection(Process &process, FileSpec directory) {98std::vector<JSONThread> json_threads;99TraceSP trace_sp = process.GetTarget().GetTrace();100101FileSpec threads_dir = directory;102threads_dir.AppendPathComponent("threads");103sys::fs::create_directories(threads_dir.GetPath().c_str());104105for (ThreadSP thread_sp : process.Threads()) {106lldb::tid_t tid = thread_sp->GetID();107if (!trace_sp->IsTraced(tid))108continue;109110JSONThread json_thread;111json_thread.tid = tid;112113if (trace_sp->GetTracedCpus().empty()) {114FileSpec output_file = threads_dir;115output_file.AppendPathComponent(std::to_string(tid) + ".intelpt_trace");116json_thread.ipt_trace = GetRelativePath(directory, output_file);117118llvm::Error err = process.GetTarget().GetTrace()->OnThreadBinaryDataRead(119tid, IntelPTDataKinds::kIptTrace,120[&](llvm::ArrayRef<uint8_t> data) -> llvm::Error {121return WriteBytesToDisk(output_file, data);122});123if (err)124return std::move(err);125}126127json_threads.push_back(std::move(json_thread));128}129return json_threads;130}131132/// \return133/// an \a llvm::Error in case of failures, \a std::nullopt if the trace is not134/// written to disk because the trace is empty and the \p compact flag is135/// present, or the FileSpec of the trace file on disk.136static Expected<std::optional<FileSpec>>137WriteContextSwitchTrace(TraceIntelPT &trace_ipt, lldb::cpu_id_t cpu_id,138const FileSpec &cpus_dir, bool compact) {139FileSpec output_context_switch_trace = cpus_dir;140output_context_switch_trace.AppendPathComponent(std::to_string(cpu_id) +141".perf_context_switch_trace");142143bool should_skip = false;144145Error err = trace_ipt.OnCpuBinaryDataRead(146cpu_id, IntelPTDataKinds::kPerfContextSwitchTrace,147[&](llvm::ArrayRef<uint8_t> data) -> llvm::Error {148if (!compact)149return WriteBytesToDisk(output_context_switch_trace, data);150151std::set<lldb::pid_t> pids;152for (Process *process : trace_ipt.GetAllProcesses())153pids.insert(process->GetID());154155Expected<std::vector<uint8_t>> compact_context_switch_trace =156FilterProcessesFromContextSwitchTrace(data, pids);157if (!compact_context_switch_trace)158return compact_context_switch_trace.takeError();159160if (compact_context_switch_trace->empty()) {161should_skip = true;162return Error::success();163}164165return WriteBytesToDisk(output_context_switch_trace,166*compact_context_switch_trace);167});168if (err)169return std::move(err);170171if (should_skip)172return std::nullopt;173return output_context_switch_trace;174}175176static Expected<FileSpec> WriteIntelPTTrace(TraceIntelPT &trace_ipt,177lldb::cpu_id_t cpu_id,178const FileSpec &cpus_dir) {179FileSpec output_trace = cpus_dir;180output_trace.AppendPathComponent(std::to_string(cpu_id) + ".intelpt_trace");181182Error err = trace_ipt.OnCpuBinaryDataRead(183cpu_id, IntelPTDataKinds::kIptTrace,184[&](llvm::ArrayRef<uint8_t> data) -> llvm::Error {185return WriteBytesToDisk(output_trace, data);186});187if (err)188return std::move(err);189return output_trace;190}191192static llvm::Expected<std::optional<std::vector<JSONCpu>>>193BuildCpusSection(TraceIntelPT &trace_ipt, FileSpec directory, bool compact) {194if (trace_ipt.GetTracedCpus().empty())195return std::nullopt;196197std::vector<JSONCpu> json_cpus;198FileSpec cpus_dir = directory;199cpus_dir.AppendPathComponent("cpus");200sys::fs::create_directories(cpus_dir.GetPath().c_str());201202for (lldb::cpu_id_t cpu_id : trace_ipt.GetTracedCpus()) {203JSONCpu json_cpu;204json_cpu.id = cpu_id;205Expected<std::optional<FileSpec>> context_switch_trace_path =206WriteContextSwitchTrace(trace_ipt, cpu_id, cpus_dir, compact);207if (!context_switch_trace_path)208return context_switch_trace_path.takeError();209if (!*context_switch_trace_path)210continue;211json_cpu.context_switch_trace =212GetRelativePath(directory, **context_switch_trace_path);213214if (Expected<FileSpec> ipt_trace_path =215WriteIntelPTTrace(trace_ipt, cpu_id, cpus_dir))216json_cpu.ipt_trace = GetRelativePath(directory, *ipt_trace_path);217else218return ipt_trace_path.takeError();219220json_cpus.push_back(std::move(json_cpu));221}222return json_cpus;223}224225/// Build modules sub-section of the trace bundle. The original modules226/// will be copied over to the \a <directory/modules> folder. Invalid modules227/// are skipped.228/// Copying the modules has the benefit of making these229/// directories self-contained, as the raw traces and modules are part of the230/// output directory and can be sent to another machine, where lldb can load231/// them and replicate exactly the same trace session.232///233/// \param[in] process234/// The process being traced.235///236/// \param[in] directory237/// The directory where the modules files will be saved when building238/// the modules section.239/// Example: If a module \a libbar.so exists in the path240/// \a /usr/lib/foo/libbar.so, then it will be copied to241/// \a <directory>/modules/usr/lib/foo/libbar.so.242///243/// \return244/// The modules section or \a llvm::Error in case of failures.245static llvm::Expected<std::vector<JSONModule>>246BuildModulesSection(Process &process, FileSpec directory) {247std::vector<JSONModule> json_modules;248ModuleList module_list = process.GetTarget().GetImages();249for (size_t i = 0; i < module_list.GetSize(); ++i) {250ModuleSP module_sp(module_list.GetModuleAtIndex(i));251if (!module_sp)252continue;253std::string system_path = module_sp->GetPlatformFileSpec().GetPath();254// TODO: support memory-only libraries like [vdso]255if (!module_sp->GetFileSpec().IsAbsolute())256continue;257258std::string file = module_sp->GetFileSpec().GetPath();259ObjectFile *objfile = module_sp->GetObjectFile();260if (objfile == nullptr)261continue;262263lldb::addr_t load_addr = LLDB_INVALID_ADDRESS;264Address base_addr(objfile->GetBaseAddress());265if (base_addr.IsValid() &&266!process.GetTarget().GetSectionLoadList().IsEmpty())267load_addr = base_addr.GetLoadAddress(&process.GetTarget());268269if (load_addr == LLDB_INVALID_ADDRESS)270continue;271272FileSpec path_to_copy_module = directory;273path_to_copy_module.AppendPathComponent("modules");274path_to_copy_module.AppendPathComponent(system_path);275sys::fs::create_directories(path_to_copy_module.GetDirectory().AsCString());276277if (std::error_code ec =278llvm::sys::fs::copy_file(file, path_to_copy_module.GetPath()))279return createStringError(280inconvertibleErrorCode(),281formatv("couldn't write to the file. {0}", ec.message()));282283json_modules.push_back(284JSONModule{system_path, GetRelativePath(directory, path_to_copy_module),285JSONUINT64{load_addr}, module_sp->GetUUID().GetAsString()});286}287return json_modules;288}289290/// Build the processes section of the trace bundle description object. Besides291/// returning the processes information, this method saves to disk all modules292/// and raw traces corresponding to the traced threads of the given process.293///294/// \param[in] process295/// The process being traced.296///297/// \param[in] directory298/// The directory where files will be saved when building the processes299/// section.300///301/// \return302/// The processes section or \a llvm::Error in case of failures.303static llvm::Expected<JSONProcess>304BuildProcessSection(Process &process, const FileSpec &directory) {305Expected<std::vector<JSONThread>> json_threads =306BuildThreadsSection(process, directory);307if (!json_threads)308return json_threads.takeError();309310Expected<std::vector<JSONModule>> json_modules =311BuildModulesSection(process, directory);312if (!json_modules)313return json_modules.takeError();314315return JSONProcess{316process.GetID(),317process.GetTarget().GetArchitecture().GetTriple().getTriple(),318json_threads.get(), json_modules.get()};319}320321/// See BuildProcessSection()322static llvm::Expected<std::vector<JSONProcess>>323BuildProcessesSection(TraceIntelPT &trace_ipt, const FileSpec &directory) {324std::vector<JSONProcess> processes;325for (Process *process : trace_ipt.GetAllProcesses()) {326if (llvm::Expected<JSONProcess> json_process =327BuildProcessSection(*process, directory))328processes.push_back(std::move(*json_process));329else330return json_process.takeError();331}332return processes;333}334335static llvm::Expected<JSONKernel>336BuildKernelSection(TraceIntelPT &trace_ipt, const FileSpec &directory) {337JSONKernel json_kernel;338std::vector<Process *> processes = trace_ipt.GetAllProcesses();339Process *kernel_process = processes[0];340341assert(processes.size() == 1 && "User processeses exist in kernel mode");342assert(kernel_process->GetID() == kDefaultKernelProcessID &&343"Kernel process not exist");344345Expected<std::vector<JSONModule>> json_modules =346BuildModulesSection(*kernel_process, directory);347if (!json_modules)348return json_modules.takeError();349350JSONModule kernel_image = json_modules.get()[0];351return JSONKernel{kernel_image.load_address, kernel_image.system_path};352}353354Expected<FileSpec> TraceIntelPTBundleSaver::SaveToDisk(TraceIntelPT &trace_ipt,355FileSpec directory,356bool compact) {357if (std::error_code ec =358sys::fs::create_directories(directory.GetPath().c_str()))359return llvm::errorCodeToError(ec);360361Expected<pt_cpu> cpu_info = trace_ipt.GetCPUInfo();362if (!cpu_info)363return cpu_info.takeError();364365FileSystem::Instance().Resolve(directory);366367Expected<std::optional<std::vector<JSONCpu>>> json_cpus =368BuildCpusSection(trace_ipt, directory, compact);369if (!json_cpus)370return json_cpus.takeError();371372std::optional<std::vector<JSONProcess>> json_processes;373std::optional<JSONKernel> json_kernel;374375if (trace_ipt.GetTraceMode() == TraceIntelPT::TraceMode::KernelMode) {376Expected<std::optional<JSONKernel>> exp_json_kernel =377BuildKernelSection(trace_ipt, directory);378if (!exp_json_kernel)379return exp_json_kernel.takeError();380else381json_kernel = *exp_json_kernel;382} else {383Expected<std::optional<std::vector<JSONProcess>>> exp_json_processes =384BuildProcessesSection(trace_ipt, directory);385if (!exp_json_processes)386return exp_json_processes.takeError();387else388json_processes = *exp_json_processes;389}390391JSONTraceBundleDescription json_intel_pt_bundle_desc{392"intel-pt",393*cpu_info,394json_processes,395*json_cpus,396trace_ipt.GetPerfZeroTscConversion(),397json_kernel};398399return SaveTraceBundleDescription(toJSON(json_intel_pt_bundle_desc),400directory);401}402403404