brintos

brintos / llvm-project-archived public Read only

0
0
Text · 14.3 KiB · 3b1535a Raw
402 lines · cpp
1//===-- TraceIntelPTBundleSaver.cpp ---------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "TraceIntelPTBundleSaver.h"10#include "PerfContextSwitchDecoder.h"11#include "TraceIntelPT.h"12#include "TraceIntelPTConstants.h"13#include "TraceIntelPTJSONStructs.h"14#include "lldb/Core/Module.h"15#include "lldb/Core/ModuleList.h"16#include "lldb/Target/Process.h"17#include "lldb/Target/SectionLoadList.h"18#include "lldb/Target/Target.h"19#include "lldb/Target/ThreadList.h"20#include "lldb/lldb-types.h"21#include "llvm/Support/Error.h"22#include "llvm/Support/JSON.h"23#include <fstream>24#include <iostream>25#include <optional>26#include <sstream>27#include <string>28 29using namespace lldb;30using namespace lldb_private;31using namespace lldb_private::trace_intel_pt;32using namespace llvm;33 34/// Strip the \p directory component from the given \p path. It assumes that \p35/// directory is a prefix of \p path.36static std::string GetRelativePath(const FileSpec &directory,37                                   const FileSpec &path) {38  return path.GetPath().substr(directory.GetPath().size() + 1);39}40 41/// Write a stream of bytes from \p data to the given output file.42/// It creates or overwrites the output file, but not append.43static llvm::Error WriteBytesToDisk(FileSpec &output_file,44                                    ArrayRef<uint8_t> data) {45  std::basic_fstream<char> out_fs = std::fstream(46      output_file.GetPath().c_str(), std::ios::out | std::ios::binary);47  if (!data.empty())48    out_fs.write(reinterpret_cast<const char *>(&data[0]), data.size());49 50  out_fs.close();51  if (!out_fs)52    return createStringError(inconvertibleErrorCode(),53                             formatv("couldn't write to the file {0}",54                                     output_file.GetPath().c_str()));55  return Error::success();56}57 58/// Save the trace bundle description JSON object inside the given directory59/// as a file named \a trace.json.60///61/// \param[in] trace_bundle_description62///     The trace bundle description as JSON Object.63///64/// \param[in] directory65///     The directory where the JSON file will be saved.66///67/// \return68///     A \a FileSpec pointing to the bundle description file, or an \a69///     llvm::Error otherwise.70static Expected<FileSpec>71SaveTraceBundleDescription(const llvm::json::Value &trace_bundle_description,72                           const FileSpec &directory) {73  FileSpec trace_path = directory;74  trace_path.AppendPathComponent("trace.json");75  std::ofstream os(trace_path.GetPath());76  os << formatv("{0:2}", trace_bundle_description).str();77  os.close();78  if (!os)79    return createStringError(inconvertibleErrorCode(),80                             formatv("couldn't write to the file {0}",81                                     trace_path.GetPath().c_str()));82  return trace_path;83}84 85/// Build the threads sub-section of the trace bundle description file.86/// Any associated binary files are created inside the given directory.87///88/// \param[in] process89///     The process being traced.90///91/// \param[in] directory92///     The directory where files will be saved when building the threads93///     section.94///95/// \return96///     The threads section or \a llvm::Error in case of failures.97static llvm::Expected<std::vector<JSONThread>>98BuildThreadsSection(Process &process, FileSpec directory) {99  std::vector<JSONThread> json_threads;100  TraceSP trace_sp = process.GetTarget().GetTrace();101 102  FileSpec threads_dir = directory;103  threads_dir.AppendPathComponent("threads");104  sys::fs::create_directories(threads_dir.GetPath().c_str());105 106  for (ThreadSP thread_sp : process.Threads()) {107    lldb::tid_t tid = thread_sp->GetID();108    if (!trace_sp->IsTraced(tid))109      continue;110 111    JSONThread json_thread;112    json_thread.tid = tid;113 114    if (trace_sp->GetTracedCpus().empty()) {115      FileSpec output_file = threads_dir;116      output_file.AppendPathComponent(std::to_string(tid) + ".intelpt_trace");117      json_thread.ipt_trace = GetRelativePath(directory, output_file);118 119      llvm::Error err = process.GetTarget().GetTrace()->OnThreadBinaryDataRead(120          tid, IntelPTDataKinds::kIptTrace,121          [&](llvm::ArrayRef<uint8_t> data) -> llvm::Error {122            return WriteBytesToDisk(output_file, data);123          });124      if (err)125        return std::move(err);126    }127 128    json_threads.push_back(std::move(json_thread));129  }130  return json_threads;131}132 133/// \return134///   an \a llvm::Error in case of failures, \a std::nullopt if the trace is not135///   written to disk because the trace is empty and the \p compact flag is136///   present, or the FileSpec of the trace file on disk.137static Expected<std::optional<FileSpec>>138WriteContextSwitchTrace(TraceIntelPT &trace_ipt, lldb::cpu_id_t cpu_id,139                        const FileSpec &cpus_dir, bool compact) {140  FileSpec output_context_switch_trace = cpus_dir;141  output_context_switch_trace.AppendPathComponent(std::to_string(cpu_id) +142                                                  ".perf_context_switch_trace");143 144  bool should_skip = false;145 146  Error err = trace_ipt.OnCpuBinaryDataRead(147      cpu_id, IntelPTDataKinds::kPerfContextSwitchTrace,148      [&](llvm::ArrayRef<uint8_t> data) -> llvm::Error {149        if (!compact)150          return WriteBytesToDisk(output_context_switch_trace, data);151 152        std::set<lldb::pid_t> pids;153        for (Process *process : trace_ipt.GetAllProcesses())154          pids.insert(process->GetID());155 156        Expected<std::vector<uint8_t>> compact_context_switch_trace =157            FilterProcessesFromContextSwitchTrace(data, pids);158        if (!compact_context_switch_trace)159          return compact_context_switch_trace.takeError();160 161        if (compact_context_switch_trace->empty()) {162          should_skip = true;163          return Error::success();164        }165 166        return WriteBytesToDisk(output_context_switch_trace,167                                *compact_context_switch_trace);168      });169  if (err)170    return std::move(err);171 172  if (should_skip)173    return std::nullopt;174  return output_context_switch_trace;175}176 177static Expected<FileSpec> WriteIntelPTTrace(TraceIntelPT &trace_ipt,178                                            lldb::cpu_id_t cpu_id,179                                            const FileSpec &cpus_dir) {180  FileSpec output_trace = cpus_dir;181  output_trace.AppendPathComponent(std::to_string(cpu_id) + ".intelpt_trace");182 183  Error err = trace_ipt.OnCpuBinaryDataRead(184      cpu_id, IntelPTDataKinds::kIptTrace,185      [&](llvm::ArrayRef<uint8_t> data) -> llvm::Error {186        return WriteBytesToDisk(output_trace, data);187      });188  if (err)189    return std::move(err);190  return output_trace;191}192 193static llvm::Expected<std::optional<std::vector<JSONCpu>>>194BuildCpusSection(TraceIntelPT &trace_ipt, FileSpec directory, bool compact) {195  if (trace_ipt.GetTracedCpus().empty())196    return std::nullopt;197 198  std::vector<JSONCpu> json_cpus;199  FileSpec cpus_dir = directory;200  cpus_dir.AppendPathComponent("cpus");201  sys::fs::create_directories(cpus_dir.GetPath().c_str());202 203  for (lldb::cpu_id_t cpu_id : trace_ipt.GetTracedCpus()) {204    JSONCpu json_cpu;205    json_cpu.id = cpu_id;206    Expected<std::optional<FileSpec>> context_switch_trace_path =207        WriteContextSwitchTrace(trace_ipt, cpu_id, cpus_dir, compact);208    if (!context_switch_trace_path)209      return context_switch_trace_path.takeError();210    if (!*context_switch_trace_path)211      continue;212    json_cpu.context_switch_trace =213        GetRelativePath(directory, **context_switch_trace_path);214 215    if (Expected<FileSpec> ipt_trace_path =216            WriteIntelPTTrace(trace_ipt, cpu_id, cpus_dir))217      json_cpu.ipt_trace = GetRelativePath(directory, *ipt_trace_path);218    else219      return ipt_trace_path.takeError();220 221    json_cpus.push_back(std::move(json_cpu));222  }223  return json_cpus;224}225 226/// Build modules sub-section of the trace bundle. The original modules227/// will be copied over to the \a <directory/modules> folder. Invalid modules228/// are skipped.229/// Copying the modules has the benefit of making these230/// directories self-contained, as the raw traces and modules are part of the231/// output directory and can be sent to another machine, where lldb can load232/// them and replicate exactly the same trace session.233///234/// \param[in] process235///     The process being traced.236///237/// \param[in] directory238///     The directory where the modules files will be saved when building239///     the modules section.240///     Example: If a module \a libbar.so exists in the path241///     \a /usr/lib/foo/libbar.so, then it will be copied to242///     \a <directory>/modules/usr/lib/foo/libbar.so.243///244/// \return245///     The modules section or \a llvm::Error in case of failures.246static llvm::Expected<std::vector<JSONModule>>247BuildModulesSection(Process &process, FileSpec directory) {248  std::vector<JSONModule> json_modules;249  ModuleList module_list = process.GetTarget().GetImages();250  for (size_t i = 0; i < module_list.GetSize(); ++i) {251    ModuleSP module_sp(module_list.GetModuleAtIndex(i));252    if (!module_sp)253      continue;254    std::string system_path = module_sp->GetPlatformFileSpec().GetPath();255    // TODO: support memory-only libraries like [vdso]256    if (!module_sp->GetFileSpec().IsAbsolute())257      continue;258 259    std::string file = module_sp->GetFileSpec().GetPath();260    ObjectFile *objfile = module_sp->GetObjectFile();261    if (objfile == nullptr)262      continue;263 264    lldb::addr_t load_addr = LLDB_INVALID_ADDRESS;265    Address base_addr(objfile->GetBaseAddress());266    if (base_addr.IsValid() && process.GetTarget().HasLoadedSections())267      load_addr = base_addr.GetLoadAddress(&process.GetTarget());268 269    if (load_addr == LLDB_INVALID_ADDRESS)270      continue;271 272    FileSpec path_to_copy_module = directory;273    path_to_copy_module.AppendPathComponent("modules");274    path_to_copy_module.AppendPathComponent(system_path);275    sys::fs::create_directories(path_to_copy_module.GetDirectory().AsCString());276 277    if (std::error_code ec =278            llvm::sys::fs::copy_file(file, path_to_copy_module.GetPath()))279      return createStringError(280          inconvertibleErrorCode(),281          formatv("couldn't write to the file. {0}", ec.message()));282 283    json_modules.push_back(284        JSONModule{system_path, GetRelativePath(directory, path_to_copy_module),285                   JSONUINT64{load_addr}, module_sp->GetUUID().GetAsString()});286  }287  return json_modules;288}289 290/// 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) {305  Expected<std::vector<JSONThread>> json_threads =306      BuildThreadsSection(process, directory);307  if (!json_threads)308    return json_threads.takeError();309 310  Expected<std::vector<JSONModule>> json_modules =311      BuildModulesSection(process, directory);312  if (!json_modules)313    return json_modules.takeError();314 315  return JSONProcess{316      process.GetID(),317      process.GetTarget().GetArchitecture().GetTriple().getTriple(),318      json_threads.get(), json_modules.get()};319}320 321/// See BuildProcessSection()322static llvm::Expected<std::vector<JSONProcess>>323BuildProcessesSection(TraceIntelPT &trace_ipt, const FileSpec &directory) {324  std::vector<JSONProcess> processes;325  for (Process *process : trace_ipt.GetAllProcesses()) {326    if (llvm::Expected<JSONProcess> json_process =327            BuildProcessSection(*process, directory))328      processes.push_back(std::move(*json_process));329    else330      return json_process.takeError();331  }332  return processes;333}334 335static llvm::Expected<JSONKernel>336BuildKernelSection(TraceIntelPT &trace_ipt, const FileSpec &directory) {337  JSONKernel json_kernel;338  std::vector<Process *> processes = trace_ipt.GetAllProcesses();339  Process *kernel_process = processes[0];340 341  assert(processes.size() == 1 && "User processeses exist in kernel mode");342  assert(kernel_process->GetID() == kDefaultKernelProcessID &&343         "Kernel process not exist");344 345  Expected<std::vector<JSONModule>> json_modules =346      BuildModulesSection(*kernel_process, directory);347  if (!json_modules)348    return json_modules.takeError();349 350  JSONModule kernel_image = json_modules.get()[0];351  return JSONKernel{kernel_image.load_address, kernel_image.system_path};352}353 354Expected<FileSpec> TraceIntelPTBundleSaver::SaveToDisk(TraceIntelPT &trace_ipt,355                                                       FileSpec directory,356                                                       bool compact) {357  if (std::error_code ec =358          sys::fs::create_directories(directory.GetPath().c_str()))359    return llvm::errorCodeToError(ec);360 361  Expected<pt_cpu> cpu_info = trace_ipt.GetCPUInfo();362  if (!cpu_info)363    return cpu_info.takeError();364 365  FileSystem::Instance().Resolve(directory);366 367  Expected<std::optional<std::vector<JSONCpu>>> json_cpus =368      BuildCpusSection(trace_ipt, directory, compact);369  if (!json_cpus)370    return json_cpus.takeError();371 372  std::optional<std::vector<JSONProcess>> json_processes;373  std::optional<JSONKernel> json_kernel;374 375  if (trace_ipt.GetTraceMode() == TraceIntelPT::TraceMode::KernelMode) {376    Expected<std::optional<JSONKernel>> exp_json_kernel =377        BuildKernelSection(trace_ipt, directory);378    if (!exp_json_kernel)379      return exp_json_kernel.takeError();380    else381      json_kernel = *exp_json_kernel;382  } else {383    Expected<std::optional<std::vector<JSONProcess>>> exp_json_processes =384        BuildProcessesSection(trace_ipt, directory);385    if (!exp_json_processes)386      return exp_json_processes.takeError();387    else388      json_processes = *exp_json_processes;389  }390 391  JSONTraceBundleDescription json_intel_pt_bundle_desc{392      "intel-pt",393      *cpu_info,394      json_processes,395      *json_cpus,396      trace_ipt.GetPerfZeroTscConversion(),397      json_kernel};398 399  return SaveTraceBundleDescription(toJSON(json_intel_pt_bundle_desc),400                                    directory);401}402