From 74c93956e1c1f1054dfb040ce26830016e0f3095 Mon Sep 17 00:00:00 2001 From: Walter Erquinigo Date: Mon, 17 Aug 2020 17:21:52 -0700 Subject: [PATCH] Add a "Trace" plug-in to LLDB to add process trace support in stages. This is the first in a series of patches that will adds a new processor trace plug-in to LLDB. The idea for this first patch to to add the plug-in interface with simple commands for the trace files that can "load" and "dump" the trace information. We can test the functionality and ensure people are happy with the way things are done and how things are organized before moving on to adding more functionality. Processor trace information can be view in a few different ways: - post mortem where a trace is saved off that can be viewed later in the debugger - gathered while a process is running and allow the user to step back in time (with no variables, memory or registers) to see how each thread arrived at where it is currently stopped. This patch attempts to start with the first solution of loading a trace file after the fact. The idea is that we will use a JSON file to load the trace information. JSON allows us to specify information about the trace like: - plug-in name in LLDB - path to trace file - shared library load information so we can re-create a target and symbolicate the information in the trace - any other info that the trace plug-in will need to be able to successfully parse the trace information - cpu type - version info - ??? A new "trace" command was added at the top level of the LLDB commmands: - "trace load" - "trace dump" I did this because if we load trace information we don't need to have a process and we might end up creating a new target for the trace information that will become active. If anyone has any input on where this would be better suited, please let me know. Walter Erquinigo will end up filling in the Intel PT specific plug-in so that it works and is tested once we can agree that the direction of this patch is the correct one, so please feel free to chime in with ideas on comments! Reviewed By: clayborg Differential Revision: https://reviews.llvm.org/D85705 --- lldb/include/lldb/Core/PluginManager.h | 8 + lldb/include/lldb/Target/Trace.h | 145 +++++++++ lldb/include/lldb/Target/TraceSettingsParser.h | 136 ++++++++ lldb/include/lldb/lldb-forward.h | 3 + lldb/include/lldb/lldb-private-interfaces.h | 7 + lldb/source/Commands/CMakeLists.txt | 1 + lldb/source/Commands/CommandObjectTrace.cpp | 292 +++++++++++++++++ lldb/source/Commands/CommandObjectTrace.h | 25 ++ lldb/source/Commands/Options.td | 16 + lldb/source/Core/PluginManager.cpp | 24 ++ lldb/source/Interpreter/CommandInterpreter.cpp | 2 + lldb/source/Plugins/CMakeLists.txt | 1 + lldb/source/Plugins/Trace/CMakeLists.txt | 5 + lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt | 23 ++ .../source/Plugins/Trace/intel-pt/TraceIntelPT.cpp | 51 +++ lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h | 48 +++ .../Trace/intel-pt/TraceIntelPTSettingsParser.cpp | 69 ++++ .../Trace/intel-pt/TraceIntelPTSettingsParser.h | 39 +++ lldb/source/Target/CMakeLists.txt | 2 + lldb/source/Target/Trace.cpp | 78 +++++ lldb/source/Target/TraceSettingsParser.cpp | 351 +++++++++++++++++++++ lldb/source/Utility/StructuredData.cpp | 7 +- lldb/test/API/commands/trace/TestTraceLoad.py | 57 ++++ lldb/test/API/commands/trace/TestTraceSchema.py | 22 ++ .../API/commands/trace/intelpt-trace/3842849.trace | Bin 0 -> 4096 bytes lldb/test/API/commands/trace/intelpt-trace/a.out | Bin 0 -> 9344 bytes .../test/API/commands/trace/intelpt-trace/main.cpp | 8 + .../API/commands/trace/intelpt-trace/trace.json | 31 ++ .../commands/trace/intelpt-trace/trace_bad.json | 14 + .../commands/trace/intelpt-trace/trace_bad2.json | 41 +++ .../commands/trace/intelpt-trace/trace_bad3.json | 32 ++ 31 files changed, 1537 insertions(+), 1 deletion(-) create mode 100644 lldb/include/lldb/Target/Trace.h create mode 100644 lldb/include/lldb/Target/TraceSettingsParser.h create mode 100644 lldb/source/Commands/CommandObjectTrace.cpp create mode 100644 lldb/source/Commands/CommandObjectTrace.h create mode 100644 lldb/source/Plugins/Trace/CMakeLists.txt create mode 100644 lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt create mode 100644 lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp create mode 100644 lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h create mode 100644 lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSettingsParser.cpp create mode 100644 lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSettingsParser.h create mode 100644 lldb/source/Target/Trace.cpp create mode 100644 lldb/source/Target/TraceSettingsParser.cpp create mode 100644 lldb/test/API/commands/trace/TestTraceLoad.py create mode 100644 lldb/test/API/commands/trace/TestTraceSchema.py create mode 100644 lldb/test/API/commands/trace/intelpt-trace/3842849.trace create mode 100755 lldb/test/API/commands/trace/intelpt-trace/a.out create mode 100644 lldb/test/API/commands/trace/intelpt-trace/main.cpp create mode 100644 lldb/test/API/commands/trace/intelpt-trace/trace.json create mode 100644 lldb/test/API/commands/trace/intelpt-trace/trace_bad.json create mode 100644 lldb/test/API/commands/trace/intelpt-trace/trace_bad2.json create mode 100644 lldb/test/API/commands/trace/intelpt-trace/trace_bad3.json diff --git a/lldb/include/lldb/Core/PluginManager.h b/lldb/include/lldb/Core/PluginManager.h index 5e0c939..cd962b6 100644 --- a/lldb/include/lldb/Core/PluginManager.h +++ b/lldb/include/lldb/Core/PluginManager.h @@ -330,6 +330,14 @@ public: static SymbolVendorCreateInstance GetSymbolVendorCreateCallbackAtIndex(uint32_t idx); + // Trace + static bool RegisterPlugin(ConstString name, const char *description, + TraceCreateInstance create_callback); + + static bool UnregisterPlugin(TraceCreateInstance create_callback); + + static TraceCreateInstance GetTraceCreateCallback(ConstString plugin_name); + // UnwindAssembly static bool RegisterPlugin(ConstString name, const char *description, UnwindAssemblyCreateInstance create_callback); diff --git a/lldb/include/lldb/Target/Trace.h b/lldb/include/lldb/Target/Trace.h new file mode 100644 index 0000000..2328fba --- /dev/null +++ b/lldb/include/lldb/Target/Trace.h @@ -0,0 +1,145 @@ +//===-- Trace.h -------------------------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_TARGET_TRACE_H +#define LLDB_TARGET_TRACE_H + +#include "llvm/Support/JSON.h" + +#include "lldb/Core/PluginInterface.h" +#include "lldb/Target/TraceSettingsParser.h" +#include "lldb/Utility/ArchSpec.h" +#include "lldb/lldb-private.h" + +namespace lldb_private { + +/// \class Trace Trace.h "lldb/Target/Trace.h" +/// A plug-in interface definition class for trace information. +/// +/// Trace plug-ins allow processor trace information to be loaded into LLDB so +/// that the data can be dumped, used for reverse and forward stepping to allow +/// introspection into the reason your process crashed or found its way to its +/// current state. +/// +/// Trace information can be loaded into a target without a process to allow +/// introspection of the trace information during post mortem analysis, such as +/// when loading core files. +/// +/// Processor trace information can also be fetched through the process +/// interfaces during a live debug session if your process supports gathering +/// this information. +class Trace : public PluginInterface { +public: + ~Trace() override = default; + + /// Dump the trace data that this plug-in has access to. + /// + /// This function will dump all of the trace data for all threads in a user + /// readable format. Options for dumping can be added as this API is iterated + /// on. + /// + /// \param[in] s + /// A stream object to dump the information to. + virtual void Dump(Stream *s) const = 0; + + /// Find a trace plug-in using JSON data. + /// + /// When loading trace data from disk, the information for the trace data + /// can be contained in multiple files and require plug-in specific + /// information about the CPU. Using data like JSON provides an + /// easy way to specify all of the settings and information that we will need + /// to load trace data into LLDB. This structured data can include: + /// - The plug-in name (this allows a specific plug-in to be selected) + /// - Architecture or target triple + /// - one or more paths to the trace data file on disk + /// - core trace data + /// - thread events or related information + /// - shared library load information to use for this trace data that + /// allows a target to be created so the trace information can be + /// symbolicated so that the trace information can be displayed to the + /// user + /// - shared library path + /// - load address + /// - information on how to fetch the shared library + /// - path to locally cached file on disk + /// - URL to download the file + /// - Any information needed to load the trace file + /// - CPU information + /// - Custom plug-in information needed to decode the trace information + /// correctly. + /// + /// \param[in] debugger + /// The debugger instance were new Target will be created as part of the + /// JSON data parsing. + /// + /// \param[in] settings + /// JSON object describing a trace. + /// + /// \param[in] settings_dir + /// Path to a directory used to resolve relative paths in the JSON data. + /// If the JSON data is defined in a file, this should be the + /// folder containing it. + static llvm::Expected + FindPlugin(Debugger &debugger, const llvm::json::Value &settings, + llvm::StringRef settings_dir); + + /// Create an instance of trace plug-in by name. + /// + /// \param[in] plugin_name + /// Name of the trace plugin. + static llvm::Expected FindPlugin(llvm::StringRef plugin_name); + + /// Parse the JSON settings and create the corresponding \a Target + /// objects. In case of an error, no targets are created. + /// + /// \param[in] debugger + /// The debugger instance where the targets are created. + /// + /// \param[in] settings + /// JSON object describing a trace. + /// + /// \param[in] settings_dir + /// Path to a directory used to resolve relative paths in the JSON data. + /// If the JSON data is defined in a file, this should be the + /// folder containing it. + /// + /// \return + /// An error object containing the reason if there is a failure. + llvm::Error ParseSettings(Debugger &debugger, + const llvm::json::Object &settings, + llvm::StringRef settings_dir); + + /// Get the JSON schema of the settings for the trace plug-in. + llvm::StringRef GetSchema(); + +protected: + Trace() {} + + /// The actual plug-in should define its own implementation of \a + /// TraceSettingsParser for doing any custom parsing. + virtual std::unique_ptr CreateParser() = 0; + +private: + Trace(const Trace &) = delete; + const Trace &operator=(const Trace &) = delete; + +protected: + friend class TraceSettingsParser; + /// JSON object that holds all settings for this trace session. + llvm::json::Object m_settings; + /// The directory that contains the settings file. + std::string m_settings_dir; + + std::map> + m_thread_to_trace_file_map; + std::vector m_targets; +}; + +} // namespace lldb_private + +#endif // LLDB_TARGET_TRACE_H diff --git a/lldb/include/lldb/Target/TraceSettingsParser.h b/lldb/include/lldb/Target/TraceSettingsParser.h new file mode 100644 index 0000000..bc18c10 --- /dev/null +++ b/lldb/include/lldb/Target/TraceSettingsParser.h @@ -0,0 +1,136 @@ +//===-- TraceSettingsParser.h -----------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_TARGET_TRACE_SETTINGS_PARSER_H +#define LLDB_TARGET_TRACE_SETTINGS_PARSER_H + +#include "llvm/ADT/Optional.h" + +#include "lldb/Target/Trace.h" +#include "lldb/lldb-private.h" + +namespace lldb_private { + +/// \class TraceSettingsParser TraceSettingsParser.h +/// A plug-in interface definition class for parsing \a Trace settings. +/// +/// As \a Trace plug-ins support plug-in specific settings, this class should be +/// overriden and implement the plug-in specific parsing logic. +class TraceSettingsParser { +public: + TraceSettingsParser(Trace &trace) : m_trace(trace) {} + + virtual ~TraceSettingsParser() = default; + + /// Get the JSON schema of the settings for the trace plug-in. + llvm::StringRef GetSchema(); + + /// Parse the structured data settings and create the corresponding \a Target + /// objects. In case of and error, no targets are created. + /// + /// \param[in] debugger + /// The debugger instance where the targets are created. + /// + /// \param[in] settings + /// The settings to parse. + /// + /// \param[in] settings_dir + /// The directory that contains the settings file used to resolve relative + /// paths. + /// + /// \return + /// An error object containing the reason if there is a failure. + llvm::Error ParseSettings(Debugger &debugger, + const llvm::json::Object &settings, + llvm::StringRef settings_dir); + +protected: + /// Method that should be overriden by implementations of this class to + /// provide the specific plug-in schema inside the "trace" section of the + /// global schema. + virtual llvm::StringRef GetPluginSchema() = 0; + + /// Method that should be overriden to parse the plug-in specific settings. + /// + /// \return + /// An error object containing the reason if there is a failure. + virtual llvm::Error ParsePluginSettings() = 0; + +private: + /// Resolve non-absolute paths relativejto the settings folder + void NormalizePath(lldb_private::FileSpec &file_spec); + llvm::Error ParseProcess(lldb_private::Debugger &debugger, + const llvm::json::Object &process); + llvm::Error ParseProcesses(lldb_private::Debugger &debugger); + llvm::Error ParseThread(lldb::ProcessSP &process_sp, + const llvm::json::Object &thread); + llvm::Error ParseThreads(lldb::ProcessSP &process_sp, + const llvm::json::Object &process); + llvm::Error ParseModule(lldb::TargetSP &target_sp, + const llvm::json::Object &module); + llvm::Error ParseModules(lldb::TargetSP &target_sp, + const llvm::json::Object &process); + llvm::Error ParseSettingsImpl(lldb_private::Debugger &debugger); + + Trace &m_trace; + +protected: + /// Objects created as product of the parsing + /// \{ + /// JSON object that holds all settings for this trace session. + llvm::json::Object m_settings; + /// The directory that contains the settings file. + std::string m_settings_dir; + + std::map> + m_thread_to_trace_file_map; + std::vector m_targets; + /// \} +}; + +} // namespace lldb_private + +namespace json_helpers { +/// JSON parsing helpers based on \a llvm::Expected. +/// \{ +llvm::Error CreateWrongTypeError(const llvm::json::Value &value, + llvm::StringRef type); + +llvm::Expected ToIntegerOrError(const llvm::json::Value &value); + +llvm::Expected ToStringOrError(const llvm::json::Value &value); + +llvm::Expected +ToArrayOrError(const llvm::json::Value &value); + +llvm::Expected +ToObjectOrError(const llvm::json::Value &value); + +llvm::Error CreateMissingKeyError(llvm::json::Object obj, llvm::StringRef key); + +llvm::Expected +GetValueOrError(const llvm::json::Object &obj, llvm::StringRef key); + +llvm::Expected GetIntegerOrError(const llvm::json::Object &obj, + llvm::StringRef key); + +llvm::Expected GetStringOrError(const llvm::json::Object &obj, + llvm::StringRef key); + +llvm::Expected +GetArrayOrError(const llvm::json::Object &obj, llvm::StringRef key); + +llvm::Expected +GetObjectOrError(const llvm::json::Object &obj, llvm::StringRef key); + +llvm::Expected> +GetOptionalStringOrError(const llvm::json::Object &obj, llvm::StringRef key); +/// \} +} // namespace json_helpers + +#endif // LLDB_TARGET_TRACE_SETTINGS_PARSER_H diff --git a/lldb/include/lldb/lldb-forward.h b/lldb/include/lldb/lldb-forward.h index 6682413..0a7f08c 100644 --- a/lldb/include/lldb/lldb-forward.h +++ b/lldb/include/lldb/lldb-forward.h @@ -226,6 +226,8 @@ class ThreadPlanStepRange; class ThreadPlanStepThrough; class ThreadPlanTracer; class ThreadSpec; +class Trace; +class TraceSettingsParser; class TraceOptions; class Type; class TypeAndOrName; @@ -432,6 +434,7 @@ typedef std::shared_ptr ThreadCollectionSP; typedef std::shared_ptr ThreadPlanSP; typedef std::weak_ptr ThreadPlanWP; typedef std::shared_ptr ThreadPlanTracerSP; +typedef std::shared_ptr TraceSP; typedef std::shared_ptr TraceOptionsSP; typedef std::shared_ptr TypeSP; typedef std::weak_ptr TypeWP; diff --git a/lldb/include/lldb/lldb-private-interfaces.h b/lldb/include/lldb/lldb-private-interfaces.h index 1568e7a..9b47b73 100644 --- a/lldb/include/lldb/lldb-private-interfaces.h +++ b/lldb/include/lldb/lldb-private-interfaces.h @@ -18,6 +18,12 @@ #include #include +namespace llvm { +namespace json { +class Object; +} +} // namespace llvm + namespace lldb_private { typedef lldb::ABISP (*ABICreateInstance)(lldb::ProcessSP process_sp, const ArchSpec &arch); @@ -104,6 +110,7 @@ typedef lldb::REPLSP (*REPLCreateInstance)(Status &error, const char *repl_options); typedef int (*ComparisonFunction)(const void *, const void *); typedef void (*DebuggerInitializeCallback)(Debugger &debugger); +typedef lldb::TraceSP (*TraceCreateInstance)(); } // namespace lldb_private diff --git a/lldb/source/Commands/CMakeLists.txt b/lldb/source/Commands/CMakeLists.txt index 3e57670..e29a241 100644 --- a/lldb/source/Commands/CMakeLists.txt +++ b/lldb/source/Commands/CMakeLists.txt @@ -31,6 +31,7 @@ add_lldb_library(lldbCommands CommandObjectStats.cpp CommandObjectTarget.cpp CommandObjectThread.cpp + CommandObjectTrace.cpp CommandObjectType.cpp CommandObjectVersion.cpp CommandObjectWatchpoint.cpp diff --git a/lldb/source/Commands/CommandObjectTrace.cpp b/lldb/source/Commands/CommandObjectTrace.cpp new file mode 100644 index 0000000..c622914 --- /dev/null +++ b/lldb/source/Commands/CommandObjectTrace.cpp @@ -0,0 +1,292 @@ +//===-- CommandObjectTrace.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 "CommandObjectTrace.h" + +#include "llvm/Support/JSON.h" +#include "llvm/Support/MemoryBuffer.h" + +#include "lldb/Core/Debugger.h" +#include "lldb/Host/OptionParser.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandObject.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/OptionArgParser.h" +#include "lldb/Interpreter/OptionGroupFormat.h" +#include "lldb/Interpreter/OptionValueBoolean.h" +#include "lldb/Interpreter/OptionValueLanguage.h" +#include "lldb/Interpreter/OptionValueString.h" +#include "lldb/Interpreter/Options.h" +#include "lldb/Target/Trace.h" + +using namespace lldb; +using namespace lldb_private; +using namespace llvm; + +// CommandObjectTraceLoad +#define LLDB_OPTIONS_trace_load +#include "CommandOptions.inc" + +#pragma mark CommandObjectTraceLoad + +class CommandObjectTraceLoad : public CommandObjectParsed { +public: + class CommandOptions : public Options { + public: + CommandOptions() : Options() { OptionParsingStarting(nullptr); } + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'v': { + m_verbose = true; + break; + } + default: + llvm_unreachable("Unimplemented option"); + } + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_verbose = false; + } + + ArrayRef GetDefinitions() override { + return makeArrayRef(g_trace_load_options); + } + + bool m_verbose; // Enable verbose logging for debugging purposes. + }; + + CommandObjectTraceLoad(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "trace load", + "Load processor trace data from a JSON file.", + "trace load"), + m_options() {} + + ~CommandObjectTraceLoad() override = default; + + Options *GetOptions() override { return &m_options; } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + if (command.size() != 1) { + result.AppendError("a single path to a JSON file containing trace " + "information is required"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + auto end_with_failure = [&result](llvm::Error err) -> bool { + result.AppendErrorWithFormat("%s\n", + llvm::toString(std::move(err)).c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + }; + + FileSpec json_file(command[0].ref()); + + auto buffer_or_error = llvm::MemoryBuffer::getFile(json_file.GetPath()); + if (!buffer_or_error) { + return end_with_failure(llvm::createStringError( + std::errc::invalid_argument, "could not open input file: %s - %s.", + json_file.GetPath().c_str(), + buffer_or_error.getError().message().c_str())); + } + + llvm::Expected settings = + json::parse(buffer_or_error.get()->getBuffer().str()); + if (!settings) + return end_with_failure(settings.takeError()); + + if (Expected traceOrErr = Trace::FindPlugin( + GetDebugger(), *settings, json_file.GetDirectory().AsCString())) { + lldb::TraceSP trace_sp = traceOrErr.get(); + if (m_options.m_verbose) + result.AppendMessageWithFormat("loading trace with plugin %s\n", + trace_sp->GetPluginName().AsCString()); + } else + return end_with_failure(traceOrErr.takeError()); + + result.SetStatus(eReturnStatusSuccessFinishResult); + return true; + } + + CommandOptions m_options; +}; + +// CommandObjectTraceDump +#define LLDB_OPTIONS_trace_dump +#include "CommandOptions.inc" + +#pragma mark CommandObjectTraceDump + +class CommandObjectTraceDump : public CommandObjectParsed { +public: + class CommandOptions : public Options { + public: + CommandOptions() : Options() { OptionParsingStarting(nullptr); } + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'v': { + m_verbose = true; + break; + } + default: + llvm_unreachable("Unimplemented option"); + } + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_verbose = false; + } + + llvm::ArrayRef GetDefinitions() override { + return llvm::makeArrayRef(g_trace_dump_options); + } + + bool m_verbose; // Enable verbose logging for debugging purposes. + }; + + CommandObjectTraceDump(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "trace dump", + "Dump the loaded processor trace data.", + "trace dump"), + m_options() {} + + ~CommandObjectTraceDump() override = default; + + Options *GetOptions() override { return &m_options; } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Status error; + // TODO: fill in the dumping code here! + if (error.Success()) { + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendErrorWithFormat("%s\n", error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } + + CommandOptions m_options; +}; + +// CommandObjectTraceSchema +#define LLDB_OPTIONS_trace_schema +#include "CommandOptions.inc" + +#pragma mark CommandObjectTraceSchema + +class CommandObjectTraceSchema : public CommandObjectParsed { +public: + class CommandOptions : public Options { + public: + CommandOptions() : Options() { OptionParsingStarting(nullptr); } + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'v': { + m_verbose = true; + break; + } + default: + llvm_unreachable("Unimplemented option"); + } + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_verbose = false; + } + + llvm::ArrayRef GetDefinitions() override { + return llvm::makeArrayRef(g_trace_schema_options); + } + + bool m_verbose; // Enable verbose logging for debugging purposes. + }; + + CommandObjectTraceSchema(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "trace schema", + "Show the schema of the given trace plugin.", + "trace schema "), + m_options() {} + + ~CommandObjectTraceSchema() override = default; + + Options *GetOptions() override { return &m_options; } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Status error; + if (command.empty()) { + result.SetError( + "trace schema cannot be invoked without a plug-in as argument"); + return false; + } + + StringRef plugin_name(command[0].c_str()); + + if (Expected traceOrErr = Trace::FindPlugin(plugin_name)) { + lldb::TraceSP trace_sp = traceOrErr.get(); + result.AppendMessage(trace_sp->GetSchema()); + } else { + error.SetErrorString(llvm::toString(traceOrErr.takeError())); + } + + if (error.Success()) { + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendErrorWithFormat("%s\n", error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } + + CommandOptions m_options; +}; + +// CommandObjectTrace + +CommandObjectTrace::CommandObjectTrace(CommandInterpreter &interpreter) + : CommandObjectMultiword(interpreter, "trace", + "Commands for loading and using processor " + "trace information.", + "trace []") { + LoadSubCommand("load", + CommandObjectSP(new CommandObjectTraceLoad(interpreter))); + LoadSubCommand("dump", + CommandObjectSP(new CommandObjectTraceDump(interpreter))); + LoadSubCommand("schema", + CommandObjectSP(new CommandObjectTraceSchema(interpreter))); +} + +CommandObjectTrace::~CommandObjectTrace() = default; diff --git a/lldb/source/Commands/CommandObjectTrace.h b/lldb/source/Commands/CommandObjectTrace.h new file mode 100644 index 0000000..2dca0e2 --- /dev/null +++ b/lldb/source/Commands/CommandObjectTrace.h @@ -0,0 +1,25 @@ +//===-- CommandObjectTrace.h ------------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_COMMANDS_COMMANDOBJECTTRACE_H +#define LLDB_SOURCE_COMMANDS_COMMANDOBJECTTRACE_H + +#include "lldb/Interpreter/CommandObjectMultiword.h" + +namespace lldb_private { + +class CommandObjectTrace : public CommandObjectMultiword { +public: + CommandObjectTrace(CommandInterpreter &interpreter); + + ~CommandObjectTrace() override; +}; + +} // namespace lldb_private + +#endif // LLDB_SOURCE_COMMANDS_COMMANDOBJECTTRACE_H diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td index b41b187..f2401dc 100644 --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -1175,3 +1175,19 @@ let Command = "watchpoint delete" in { def watchpoint_delete_force : Option<"force", "f">, Group<1>, Desc<"Delete all watchpoints without querying for confirmation.">; } + +let Command = "trace load" in { + def trace_load_verbose : Option<"verbose", "v">, Group<1>, + Desc<"Show verbose trace load logging for debugging the plug-in " + "implementation.">; +} + +let Command = "trace dump" in { + def trace_dump_verbose : Option<"verbose", "v">, Group<1>, + Desc<"Show verbose trace information.">; +} + +let Command = "trace schema" in { + def trace_schema_verbose : Option<"verbose", "v">, Group<1>, + Desc<"Show verbose trace schema logging for debugging the plug-in.">; +} diff --git a/lldb/source/Core/PluginManager.cpp b/lldb/source/Core/PluginManager.cpp index 3545ef6..e025aa1 100644 --- a/lldb/source/Core/PluginManager.cpp +++ b/lldb/source/Core/PluginManager.cpp @@ -1005,6 +1005,30 @@ PluginManager::GetSymbolVendorCreateCallbackAtIndex(uint32_t idx) { return GetSymbolVendorInstances().GetCallbackAtIndex(idx); } +#pragma mark Trace + +typedef PluginInstance TraceInstance; +typedef PluginInstances TraceInstances; + +static TraceInstances &GetTraceInstances() { + static TraceInstances g_instances; + return g_instances; +} + +bool PluginManager::RegisterPlugin(ConstString name, const char *description, + TraceCreateInstance create_callback) { + return GetTraceInstances().RegisterPlugin(name, description, create_callback); +} + +bool PluginManager::UnregisterPlugin(TraceCreateInstance create_callback) { + return GetTraceInstances().UnregisterPlugin(create_callback); +} + +TraceCreateInstance +PluginManager::GetTraceCreateCallback(ConstString plugin_name) { + return GetTraceInstances().GetCallbackForName(plugin_name); +} + #pragma mark UnwindAssembly typedef PluginInstance UnwindAssemblyInstance; diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp index 1f67468..88b07d5 100644 --- a/lldb/source/Interpreter/CommandInterpreter.cpp +++ b/lldb/source/Interpreter/CommandInterpreter.cpp @@ -37,6 +37,7 @@ #include "Commands/CommandObjectStats.h" #include "Commands/CommandObjectTarget.h" #include "Commands/CommandObjectThread.h" +#include "Commands/CommandObjectTrace.h" #include "Commands/CommandObjectType.h" #include "Commands/CommandObjectVersion.h" #include "Commands/CommandObjectWatchpoint.h" @@ -512,6 +513,7 @@ void CommandInterpreter::LoadCommandDictionary() { REGISTER_COMMAND_OBJECT("statistics", CommandObjectStats); REGISTER_COMMAND_OBJECT("target", CommandObjectMultiwordTarget); REGISTER_COMMAND_OBJECT("thread", CommandObjectMultiwordThread); + REGISTER_COMMAND_OBJECT("trace", CommandObjectTrace); REGISTER_COMMAND_OBJECT("type", CommandObjectType); REGISTER_COMMAND_OBJECT("version", CommandObjectVersion); REGISTER_COMMAND_OBJECT("watchpoint", CommandObjectMultiwordWatchpoint); diff --git a/lldb/source/Plugins/CMakeLists.txt b/lldb/source/Plugins/CMakeLists.txt index d91ba74..3da23ec 100644 --- a/lldb/source/Plugins/CMakeLists.txt +++ b/lldb/source/Plugins/CMakeLists.txt @@ -19,6 +19,7 @@ add_subdirectory(StructuredData) add_subdirectory(SymbolFile) add_subdirectory(SystemRuntime) add_subdirectory(SymbolVendor) +add_subdirectory(Trace) add_subdirectory(TypeSystem) add_subdirectory(UnwindAssembly) diff --git a/lldb/source/Plugins/Trace/CMakeLists.txt b/lldb/source/Plugins/Trace/CMakeLists.txt new file mode 100644 index 0000000..edbb5f1 --- /dev/null +++ b/lldb/source/Plugins/Trace/CMakeLists.txt @@ -0,0 +1,5 @@ +option(LLDB_BUILD_INTEL_PT "Enable Building of Intel(R) Processor Trace Tool" OFF) + +if (LLDB_BUILD_INTEL_PT) + add_subdirectory(intel-pt) +endif() diff --git a/lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt b/lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt new file mode 100644 index 0000000..aeaba0c --- /dev/null +++ b/lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt @@ -0,0 +1,23 @@ +if (NOT LIBIPT_INCLUDE_PATH) + message (FATAL_ERROR "libipt include path not provided") +endif() + +if (NOT EXISTS "${LIBIPT_INCLUDE_PATH}") + message (FATAL_ERROR "invalid libipt include path provided") +endif() +include_directories(${LIBIPT_INCLUDE_PATH}) + +find_library(LIBIPT_LIBRARY ipt PATHS ${LIBIPT_LIBRARY_PATH} REQUIRED) + +add_lldb_library(lldbPluginTraceIntelPT PLUGIN + TraceIntelPT.cpp + TraceIntelPTSettingsParser.cpp + + LINK_LIBS + lldbCore + lldbSymbol + lldbTarget + ${LIBIPT_LIBRARY} + LINK_COMPONENTS + Support + ) diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp new file mode 100644 index 0000000..13a9904 --- /dev/null +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp @@ -0,0 +1,51 @@ +//===-- TraceIntelPT.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 "TraceIntelPT.h" + +#include "TraceIntelPTSettingsParser.h" +#include "lldb/Core/PluginManager.h" + +using namespace lldb; +using namespace lldb_private; +using namespace llvm; + +LLDB_PLUGIN_DEFINE_ADV(TraceIntelPT, TraceIntelPT) + +void TraceIntelPT::Initialize() { + PluginManager::RegisterPlugin(GetPluginNameStatic(), "Intel Processor Trace", + CreateInstance); +} + +void TraceIntelPT::Terminate() { + PluginManager::UnregisterPlugin(CreateInstance); +} + +ConstString TraceIntelPT::GetPluginNameStatic() { + static ConstString g_name("intel-pt"); + return g_name; +} + +std::unique_ptr +TraceIntelPT::CreateParser() { + return std::make_unique(*this); +} + +//------------------------------------------------------------------ +// PluginInterface protocol +//------------------------------------------------------------------ + +ConstString TraceIntelPT::GetPluginName() { return GetPluginNameStatic(); } + +uint32_t TraceIntelPT::GetPluginVersion() { return 1; } + +void TraceIntelPT::Dump(lldb_private::Stream *s) const {} + +lldb::TraceSP TraceIntelPT::CreateInstance() { + return lldb::TraceSP(new TraceIntelPT()); +} diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h new file mode 100644 index 0000000..edc781e --- /dev/null +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h @@ -0,0 +1,48 @@ +//===-- TraceIntelPT.h ------------------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_TraceIntelPT_h_ +#define liblldb_TraceIntelPT_h_ + +#include "intel-pt.h" +#include "llvm/ADT/Optional.h" + +#include "TraceIntelPTSettingsParser.h" +#include "lldb/Target/Trace.h" +#include "lldb/lldb-private.h" + +class TraceIntelPT : public lldb_private::Trace { +public: + void Dump(lldb_private::Stream *s) const override; + + /// PluginInterface protocol + /// \{ + lldb_private::ConstString GetPluginName() override; + + static void Initialize(); + + static void Terminate(); + + static lldb::TraceSP CreateInstance(); + + static lldb_private::ConstString GetPluginNameStatic(); + + uint32_t GetPluginVersion() override; + /// \} + +protected: + TraceIntelPT() : Trace() {} + + std::unique_ptr CreateParser() override; + +private: + friend class TraceIntelPTSettingsParser; + pt_cpu m_pt_cpu; +}; + +#endif // liblldb_TraceIntelPT_h_ diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSettingsParser.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSettingsParser.cpp new file mode 100644 index 0000000..c8f90c9 --- /dev/null +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSettingsParser.cpp @@ -0,0 +1,69 @@ +//===-- TraceIntelPTSettingsParser.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 "TraceIntelPTSettingsParser.h" + +using namespace lldb; +using namespace lldb_private; +using namespace llvm; + +StringRef TraceIntelPTSettingsParser::GetPluginSchema() { + return R"({ + "type": "intel-pt", + "pt_cpu": { + "vendor": "intel" | "unknown", + "family": integer, + "model": integer, + "stepping": integer + } +})"; +} + +llvm::Error TraceIntelPTSettingsParser::ParsePTCPU(const json::Object &trace) { + llvm::Expected pt_cpu = + json_helpers::GetObjectOrError(trace, "pt_cpu"); + if (!pt_cpu) + return pt_cpu.takeError(); + + llvm::Expected vendor = + json_helpers::GetStringOrError(*pt_cpu, "vendor"); + if (!vendor) + return vendor.takeError(); + + llvm::Expected family = + json_helpers::GetIntegerOrError(*pt_cpu, "family"); + if (!family) + return family.takeError(); + + llvm::Expected model = + json_helpers::GetIntegerOrError(*pt_cpu, "model"); + if (!model) + return model.takeError(); + + llvm::Expected stepping = + json_helpers::GetIntegerOrError(*pt_cpu, "stepping"); + if (!stepping) + return stepping.takeError(); + + m_pt_cpu = {vendor->compare("intel") == 0 ? pcv_intel : pcv_unknown, + static_cast(*family), static_cast(*model), + static_cast(*stepping)}; + return llvm::Error::success(); +} + +llvm::Error TraceIntelPTSettingsParser::ParsePluginSettings() { + llvm::Expected trace = + json_helpers::GetObjectOrError(m_settings, "trace"); + if (!trace) + return trace.takeError(); + if (llvm::Error err = ParsePTCPU(*trace)) + return err; + + m_trace.m_pt_cpu = m_pt_cpu; + return llvm::Error::success(); +} diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSettingsParser.h b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSettingsParser.h new file mode 100644 index 0000000..b8aaa10 --- /dev/null +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSettingsParser.h @@ -0,0 +1,39 @@ +//===-- TraceIntelPTSettingsParser.h ----------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_TraceIntelPTSettingsParser_h_ +#define liblldb_TraceIntelPTSettingsParser_h_ + +#include "intel-pt.h" + +#include "TraceIntelPT.h" +#include "lldb/Target/TraceSettingsParser.h" +#include "lldb/Utility/StructuredData.h" + +class TraceIntelPT; + +class TraceIntelPTSettingsParser : public lldb_private::TraceSettingsParser { + +public: + TraceIntelPTSettingsParser(TraceIntelPT &trace) + : lldb_private::TraceSettingsParser((lldb_private::Trace &)trace), + m_trace(trace) {} + +protected: + llvm::StringRef GetPluginSchema() override; + + llvm::Error ParsePluginSettings() override; + +private: + llvm::Error ParsePTCPU(const llvm::json::Object &trace); + + TraceIntelPT &m_trace; + pt_cpu m_pt_cpu; +}; + +#endif // liblldb_TraceIntelPTSettingsParser_h_ diff --git a/lldb/source/Target/CMakeLists.txt b/lldb/source/Target/CMakeLists.txt index ca80b5b..c3a09c6 100644 --- a/lldb/source/Target/CMakeLists.txt +++ b/lldb/source/Target/CMakeLists.txt @@ -65,6 +65,8 @@ add_lldb_library(lldbTarget ThreadPlanTracer.cpp ThreadPlanStack.cpp ThreadSpec.cpp + Trace.cpp + TraceSettingsParser.cpp UnixSignals.cpp UnwindAssembly.cpp UnwindLLDB.cpp diff --git a/lldb/source/Target/Trace.cpp b/lldb/source/Target/Trace.cpp new file mode 100644 index 0000000..6a1f8ea --- /dev/null +++ b/lldb/source/Target/Trace.cpp @@ -0,0 +1,78 @@ +//===-- Trace.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 "lldb/Target/Trace.h" + +#include +#include + +#include "llvm/Support/Format.h" + +#include "lldb/Core/PluginManager.h" + +using namespace lldb; +using namespace lldb_private; +using namespace llvm; + +llvm::Expected Trace::FindPlugin(Debugger &debugger, + const json::Value &settings, + StringRef info_dir) { + llvm::Expected settings_obj = + json_helpers::ToObjectOrError(settings); + if (!settings_obj) + return settings_obj.takeError(); + + llvm::Expected trace = + json_helpers::GetObjectOrError(*settings_obj, "trace"); + if (!trace) + return trace.takeError(); + + llvm::Expected type = + json_helpers::GetStringOrError(*trace, "type"); + if (!type) + return type.takeError(); + + ConstString plugin_name(*type); + auto create_callback = PluginManager::GetTraceCreateCallback(plugin_name); + if (create_callback) { + TraceSP instance = create_callback(); + if (llvm::Error err = + instance->ParseSettings(debugger, *settings_obj, info_dir)) + return std::move(err); + return instance; + } + + return createStringError( + std::errc::invalid_argument, + "no trace plug-in matches the specified type: \"%s\"", + plugin_name.AsCString()); +} + +llvm::Expected Trace::FindPlugin(StringRef name) { + ConstString plugin_name(name); + auto create_callback = PluginManager::GetTraceCreateCallback(plugin_name); + if (create_callback) + return create_callback(); + + return createStringError( + std::errc::invalid_argument, + "no trace plug-in matches the specified type: \"%s\"", + plugin_name.AsCString()); +} + +llvm::Error Trace::ParseSettings(Debugger &debugger, + const llvm::json::Object &settings, + llvm::StringRef settings_dir) { + if (llvm::Error err = + CreateParser()->ParseSettings(debugger, settings, settings_dir)) + return err; + + return llvm::Error::success(); +} + +llvm::StringRef Trace::GetSchema() { return CreateParser()->GetSchema(); } diff --git a/lldb/source/Target/TraceSettingsParser.cpp b/lldb/source/Target/TraceSettingsParser.cpp new file mode 100644 index 0000000..09e60bb --- /dev/null +++ b/lldb/source/Target/TraceSettingsParser.cpp @@ -0,0 +1,351 @@ +//===-- TraceSettingParser.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 "lldb/Target/TraceSettingsParser.h" + +#include + +#include "Plugins/Process/Utility/HistoryThread.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Target/Process.h" + +using namespace lldb; +using namespace lldb_private; +using namespace llvm; + +namespace json_helpers { + +llvm::Error CreateWrongTypeError(const json::Value &value, + llvm::StringRef type) { + std::string s; + llvm::raw_string_ostream os(s); + os << llvm::formatv("JSON value is expected to be \"{0}\".\nValue:\n{1:2}", + type, value); + os.flush(); + + return llvm::createStringError(std::errc::invalid_argument, os.str().c_str()); +} + +llvm::Expected ToIntegerOrError(const json::Value &value) { + llvm::Optional v = value.getAsInteger(); + if (v.hasValue()) + return *v; + return CreateWrongTypeError(value, "integer"); +} + +llvm::Expected ToStringOrError(const json::Value &value) { + llvm::Optional v = value.getAsString(); + if (v.hasValue()) + return *v; + return CreateWrongTypeError(value, "string"); +} + +llvm::Expected ToArrayOrError(const json::Value &value) { + if (const json::Array *v = value.getAsArray()) + return *v; + return CreateWrongTypeError(value, "array"); +} + +llvm::Expected ToObjectOrError(const json::Value &value) { + if (const json::Object *v = value.getAsObject()) + return *v; + return CreateWrongTypeError(value, "object"); +} + +llvm::Error CreateMissingKeyError(json::Object obj, llvm::StringRef key) { + std::string str; + llvm::raw_string_ostream os(str); + os << llvm::formatv( + "JSON object is missing the \"{0}\" field.\nValue:\n{1:2}", key, + json::Value(std::move(obj))); + os.flush(); + + return llvm::createStringError(std::errc::invalid_argument, os.str().c_str()); +} + +llvm::Expected GetValueOrError(const json::Object &obj, + StringRef key) { + if (const json::Value *v = obj.get(key)) + return *v; + else + return CreateMissingKeyError(obj, key); +} + +llvm::Expected GetIntegerOrError(const json::Object &obj, + StringRef key) { + if (llvm::Expected v = GetValueOrError(obj, key)) + return ToIntegerOrError(*v); + else + return v.takeError(); +} + +llvm::Expected GetStringOrError(const json::Object &obj, + StringRef key) { + if (llvm::Expected v = GetValueOrError(obj, key)) + return ToStringOrError(*v); + else + return v.takeError(); +} + +llvm::Expected GetArrayOrError(const json::Object &obj, + StringRef key) { + if (llvm::Expected v = GetValueOrError(obj, key)) + return ToArrayOrError(*v); + else + return v.takeError(); +} + +llvm::Expected GetObjectOrError(const json::Object &obj, + StringRef key) { + if (llvm::Expected v = GetValueOrError(obj, key)) + return ToObjectOrError(*v); + else + return v.takeError(); +} + +llvm::Expected> +GetOptionalStringOrError(const json::Object &obj, StringRef key) { + if (const json::Value *v = obj.get(key)) + return ToStringOrError(*v); + return llvm::None; +} + +} // namespace json_helpers + +StringRef TraceSettingsParser::GetSchema() { + static std::string schema; + if (schema.empty()) { + std::ostringstream schema_builder; + schema_builder << "{\n \"trace\": "; + + // We need to add spaces to indent correctly the plugin schema + std::string plugin_schema(GetPluginSchema()); + plugin_schema = std::regex_replace(plugin_schema, std::regex("\n"), "\n "); + schema_builder << plugin_schema << ",\n"; + + schema_builder << R"( "processes": [ + { + "pid": integer, + "triple": string, // llvm-triple + "threads": [ + { + "tid": integer, + "traceFile": string + } + ], + "modules": [ + { + "systemPath": string, // original path of the module at runtime + "file"?: string, // copy of the file if not available at "systemPath" + "loadAddress": string, // string address in hex or decimal form + "uuid"?: string, + } + ] + } + ] +} +// Notes: +// All paths are either absolute or relative to the settings file.)"; + schema = schema_builder.str(); + } + return schema; +} + +void TraceSettingsParser::NormalizePath(FileSpec &file_spec) { + if (file_spec.IsRelative()) + file_spec.PrependPathComponent(m_settings_dir); +} + +llvm::Error TraceSettingsParser::ParseThread(ProcessSP &process_sp, + const json::Object &thread) { + llvm::Expected raw_tid = + json_helpers::GetIntegerOrError(thread, "tid"); + if (!raw_tid) + return raw_tid.takeError(); + lldb::tid_t tid = static_cast(*raw_tid); + + if (llvm::Expected trace_file = + json_helpers::GetStringOrError(thread, "traceFile")) { + FileSpec spec(*trace_file); + NormalizePath(spec); + m_thread_to_trace_file_map[process_sp->GetID()][tid] = spec; + } else + return trace_file.takeError(); + + ThreadSP thread_sp(new HistoryThread(*process_sp, tid, /*callstack*/ {})); + process_sp->GetThreadList().AddThread(thread_sp); + return llvm::Error::success(); +} + +llvm::Error TraceSettingsParser::ParseThreads(ProcessSP &process_sp, + const json::Object &process) { + llvm::Expected threads = + json_helpers::GetArrayOrError(process, "threads"); + if (!threads) + return threads.takeError(); + + for (const json::Value &thread_val : *threads) { + llvm::Expected thread = + json_helpers::ToObjectOrError(thread_val); + if (!thread) + return thread.takeError(); + if (llvm::Error err = ParseThread(process_sp, *thread)) + return err; + } + return llvm::Error::success(); +} + +static llvm::Expected ParseAddress(StringRef address_str) { + addr_t address; + if (address_str.getAsInteger(0, address)) + return createStringError(std::errc::invalid_argument, + "\"%s\" does not represent an integer", + address_str.data()); + return address; +} + +llvm::Error TraceSettingsParser::ParseModule(TargetSP &target_sp, + const json::Object &module) { + llvm::Expected load_address_str = + json_helpers::GetStringOrError(module, "loadAddress"); + if (!load_address_str) + return load_address_str.takeError(); + llvm::Expected load_address = ParseAddress(*load_address_str); + if (!load_address) + return load_address.takeError(); + + llvm::Expected system_path = + json_helpers::GetStringOrError(module, "systemPath"); + if (!system_path) + return system_path.takeError(); + FileSpec system_file_spec(*system_path); + NormalizePath(system_file_spec); + + llvm::Expected> file = + json_helpers::GetOptionalStringOrError(module, "file"); + if (!file) + return file.takeError(); + FileSpec local_file_spec(file->hasValue() ? **file : *system_path); + NormalizePath(local_file_spec); + + ModuleSpec module_spec; + module_spec.GetFileSpec() = local_file_spec; + module_spec.GetPlatformFileSpec() = system_file_spec; + module_spec.SetObjectOffset(*load_address); + + llvm::Expected> uuid_str = + json_helpers::GetOptionalStringOrError(module, "uuid"); + if (!uuid_str) + return uuid_str.takeError(); + if (uuid_str->hasValue()) + module_spec.GetUUID().SetFromStringRef(**uuid_str); + + Status error; + ModuleSP module_sp = + target_sp->GetOrCreateModule(module_spec, /*notify*/ false, &error); + return error.ToError(); +} + +llvm::Error TraceSettingsParser::ParseModules(TargetSP &target_sp, + const json::Object &process) { + llvm::Expected modules = + json_helpers::GetArrayOrError(process, "modules"); + if (!modules) + return modules.takeError(); + + for (const json::Value &module_val : *modules) { + llvm::Expected module = + json_helpers::ToObjectOrError(module_val); + if (!module) + return module.takeError(); + if (llvm::Error err = ParseModule(target_sp, *module)) + return err; + } + return llvm::Error::success(); +} + +llvm::Error TraceSettingsParser::ParseProcess(Debugger &debugger, + const json::Object &process) { + llvm::Expected pid = json_helpers::GetIntegerOrError(process, "pid"); + if (!pid) + return pid.takeError(); + + llvm::Expected triple = + json_helpers::GetStringOrError(process, "triple"); + if (!triple) + return triple.takeError(); + + TargetSP target_sp; + Status error = debugger.GetTargetList().CreateTarget( + debugger, /*user_exe_path*/ llvm::StringRef(), *triple, eLoadDependentsNo, + /*platform_options*/ nullptr, target_sp); + + if (!target_sp) + return error.ToError(); + + m_targets.push_back(target_sp); + debugger.GetTargetList().SetSelectedTarget(target_sp.get()); + + ProcessSP process_sp(target_sp->CreateProcess( + /*listener*/ nullptr, /*plugin_name*/ llvm::StringRef(), + /*crash_file*/ nullptr)); + process_sp->SetID(static_cast(*pid)); + + if (llvm::Error err = ParseThreads(process_sp, process)) + return err; + + return ParseModules(target_sp, process); +} + +llvm::Error TraceSettingsParser::ParseProcesses(Debugger &debugger) { + llvm::Expected processes = + json_helpers::GetArrayOrError(m_settings, "processes"); + if (!processes) + return processes.takeError(); + + for (const json::Value &process_val : *processes) { + llvm::Expected process = + json_helpers::ToObjectOrError(process_val); + if (!process) + return process.takeError(); + if (llvm::Error err = ParseProcess(debugger, *process)) + return err; + } + return llvm::Error::success(); +} + +llvm::Error TraceSettingsParser::ParseSettingsImpl(Debugger &debugger) { + if (llvm::Error err = ParseProcesses(debugger)) + return err; + return ParsePluginSettings(); +} + +llvm::Error +TraceSettingsParser::ParseSettings(Debugger &debugger, + const llvm::json::Object &settings, + llvm::StringRef settings_dir) { + m_settings = settings; + m_settings_dir = settings_dir.str(); + if (llvm::Error err = ParseSettingsImpl(debugger)) { + // We clean all the targets that were created internally, which should leave + // the debugger unchanged + for (auto target_sp : m_targets) + debugger.GetTargetList().DeleteTarget(target_sp); + + return createStringError(std::errc::invalid_argument, "%s\nSchema:\n%s", + llvm::toString(std::move(err)).c_str(), + GetSchema().data()); + } + + m_trace.m_settings = m_settings; + m_trace.m_settings_dir = m_settings_dir; + m_trace.m_thread_to_trace_file_map = m_thread_to_trace_file_map; + m_trace.m_targets = m_targets; + + return llvm::Error::success(); +} diff --git a/lldb/source/Utility/StructuredData.cpp b/lldb/source/Utility/StructuredData.cpp index 359c49a..cb26329 100644 --- a/lldb/source/Utility/StructuredData.cpp +++ b/lldb/source/Utility/StructuredData.cpp @@ -42,7 +42,12 @@ StructuredData::ParseJSONFromFile(const FileSpec &input_spec, Status &error) { buffer_or_error.getError().message()); return return_sp; } - return ParseJSON(buffer_or_error.get()->getBuffer().str()); + llvm::Expected value = + json::parse(buffer_or_error.get()->getBuffer().str()); + if (value) + return ParseJSONValue(*value); + error.SetErrorString(toString(value.takeError())); + return StructuredData::ObjectSP(); } static StructuredData::ObjectSP ParseJSONValue(json::Value &value) { diff --git a/lldb/test/API/commands/trace/TestTraceLoad.py b/lldb/test/API/commands/trace/TestTraceLoad.py new file mode 100644 index 0000000..f418b8d --- /dev/null +++ b/lldb/test/API/commands/trace/TestTraceLoad.py @@ -0,0 +1,57 @@ +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +from lldbsuite.test.decorators import * + +class TestTraceLoad(TestBase): + + mydir = TestBase.compute_mydir(__file__) + NO_DEBUG_INFO_TESTCASE = True + + def setUp(self): + TestBase.setUp(self) + if 'intel-pt' not in configuration.enabled_plugins: + self.skipTest("The intel-pt test plugin is not enabled") + + + def testLoadTrace(self): + src_dir = self.getSourceDir() + trace_definition_file = os.path.join(src_dir, "intelpt-trace", "trace.json") + self.expect("trace load -v " + trace_definition_file, substrs=["intel-pt"]) + + target = self.dbg.GetSelectedTarget() + process = target.GetProcess() + self.assertEqual(process.GetProcessID(), 1234) + + self.assertEqual(process.GetNumThreads(), 1) + self.assertEqual(process.GetThreadAtIndex(0).GetThreadID(), 3842849) + + self.assertEqual(target.GetNumModules(), 1) + module = target.GetModuleAtIndex(0) + path = module.GetFileSpec() + self.assertEqual(path.fullpath, os.path.join(src_dir, "intelpt-trace", "a.out")) + self.assertGreater(module.GetNumSections(), 0) + self.assertEqual(module.GetSectionAtIndex(0).GetFileAddress(), 0x400000) + + self.assertEqual("6AA9A4E2-6F28-2F33-377D-59FECE874C71-5B41261A", module.GetUUIDString()) + + + def testLoadInvalidTraces(self): + src_dir = self.getSourceDir() + # We test first an invalid type + trace_definition_file = os.path.join(src_dir, "intelpt-trace", "trace_bad.json") + self.expect("trace load -v " + trace_definition_file, error=True, + substrs=['error: JSON value is expected to be "object"', "Value", "123", "Schema"]) + + # Now we test a missing field + trace_definition_file2 = os.path.join(src_dir, "intelpt-trace", "trace_bad2.json") + self.expect("trace load -v " + trace_definition_file2, error=True, + substrs=['error: JSON object is missing the "triple" field.', "Value", "pid", "12345", "Schema"]) + + # The following wrong schema will have a valid target and an invalid one. In the case of failure, + # no targets should be created. + self.assertEqual(self.dbg.GetNumTargets(), 0) + trace_definition_file2 = os.path.join(src_dir, "intelpt-trace", "trace_bad3.json") + self.expect("trace load -v " + trace_definition_file2, error=True, + substrs=['error: JSON object is missing the "pid" field.']) + self.assertEqual(self.dbg.GetNumTargets(), 0) diff --git a/lldb/test/API/commands/trace/TestTraceSchema.py b/lldb/test/API/commands/trace/TestTraceSchema.py new file mode 100644 index 0000000..1549325 --- /dev/null +++ b/lldb/test/API/commands/trace/TestTraceSchema.py @@ -0,0 +1,22 @@ +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +from lldbsuite.test.decorators import * + +class TestTraceLoad(TestBase): + + mydir = TestBase.compute_mydir(__file__) + NO_DEBUG_INFO_TESTCASE = True + + def setUp(self): + TestBase.setUp(self) + if 'intel-pt' not in configuration.enabled_plugins: + self.skipTest("The intel-pt test plugin is not enabled") + + + def testSchema(self): + self.expect("trace schema intel-pt", substrs=["trace", "triple", "threads", "traceFile"]) + + def testInvalidPluginSchema(self): + self.expect("trace schema invalid-plugin", error=True, + substrs=['error: no trace plug-in matches the specified type: "invalid-plugin"']) diff --git a/lldb/test/API/commands/trace/intelpt-trace/3842849.trace b/lldb/test/API/commands/trace/intelpt-trace/3842849.trace new file mode 100644 index 0000000000000000000000000000000000000000..14f7b5ef4aed2bb3a4a7934fbce2e7916af9531e GIT binary patch literal 4096 zcmZQz7zLvtFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0=PnesR<1-%Y&6N zDMNIi$u=)*69Y2r+TLUwoWwW|Edn!M?~d(-eVEjuVS}8)MN}x!gP_$|T+wa`D zXFWS!7o@6xsvK+P-1GSExv#me^_OFVdwiNEn0(@1K~ktLB2~^?i23zWRJv}lLPW%^ z;ug^WO+B87v=Dbv%u6b3=6>a;gV*8d_Bo1fUqmLDf6h@0=3GMJ6mO9tD(Ud6R3H#@ z)I`dJBh$rZDT0s51oJ^vjv>VvM3(0C1}6%Huz?pfV4uc+0g*NR^*C_d{noHNJai zXFA!DPGyTnJC1hm?%2IET*!sD%X(A(B zJMMh?p-+DI{NNK0M?b%gu>2tTnTg_tYIsMXTUA3ZP>+J0Ol;jsW#27Kb25|5ngu(a zw@s6*8OMOsWFlb}NZLmF1Oq6o0GW6yEA|c!^!AzC!`s8VoPbn!4S#onbGsMUrB(GqnZBWU>)&w?5?3t1gm$#?0UR#*6hgtqcj{*?GJE5`DME%y0bX zq*;`At%9k%c@5qz8zV@OJVnOK7t5f{*O5%2E-ygb_TIqk8;=^ZZy7UxxiT~|aDM(L z7>UODvk}S8H-lX|562HDS~m6zF?S~lcG&3X0yAbx0b};n0Pt*h3ITK`ryFqN72Irvww(= z44&NlfnYIQvmQ@+hzQlQDBn zGwyn~_zrd0!=uqhqNCAAqvpAn7bAXvew2Nzx>T!7K(atWTd&_ka|2BuAp|=PJ_J4l z-h(Q>0zM6X0i60fs0I~%k3`y2!$Lb6)Yh+T^uMe%21!qIaOvZ6`F3(pRNG&OO|X;y zY4~@MZy>lQ(6+Co=}G@_ao@UoZr`!#7E+TBhKo3fvfW0CXdw8Uudlgb9D$WB34R84 zi|oCD;8%UIK-)`tED$Tx)^<*A}=~7m-z)SDfp~dr2?fs2o_o#Us-m zmT0BJ#DX4?sb(d=iv1wbTKT5))|gE3@sZYcqIC`vmz16M14L~9$0y|+$)|l0VvA=! z6_57GM036fF>#%I3t=kWqw2))T2gjh7o-<7RJP-`$~hoY=O6 zc80sdyF%N$w(aWLy?sZheb`EdjJWOSJ9gZ)Mcjc5okn$X?+l7faAN zmCIP2PsY>fc*5#Tr;lVh3h`{R(>iKf`D{GxsHXF|$E<`6AvxY@TLrr_IyBIk$YnC{ zEp*y>0;3(R^t9a}C8ui3y>&D~w=c59tfR?BYU*`i*VNZl<_&jdI%bi%n*NwgYJ7LP zXcV=+M;)^if5+ANuMj*RRO_!3XKLo(RGB}k{a1=BHT|0__j1+#0m1WbwZ28ntASpv z4_5Azs`ahH-w<)DWLn3~3D4thqb^og=I?6#8bK#)FX`fX+@saf-yrz@!;R5t)>ZR! zwZ2WX*VL~SchuDLYU9p@?w@#7^6Ggt^6GD@Rwzjq80(JY2s^DrT(*=KPpAM-o&HT7(eezhgD0) ztxq<{(s6!0^jeK^n}I$gmX5;>(62(f(JdJf?<<~A`jEH3WIoch>eVj&mwwkhQeWjN zg-4)o6^q}yvy=+)IP}391zlrgP)*w3)PX`GOfQ9u-)rrqh;fCBwUS?Ale^$(%@K zQ)WD$j~_Fwtern5Ci3x&WhRT6%rQV*k_k_{%C)kho93S3=>C`)JJ63JI_2dw)^Ias zCeU*A73psNNi+$R1hopi+ z9xoIaWKj;z9z%o+$1-+&9Nf-3e2P=FSIAF`a5iUK;b`wbhaI0((qy(69xtZwdqXNI z!X!<_3sWMTJeEa%4!84;<%pFpq;lCR!GtYurQ-xpsp+&W!m_Mk8yg!Drmlu9Ckq?4 zrc~!nC4pthK{&naAUOS40&d7Xo=GJTI)@DL3d<-?j%W}OrtJ|15?rnE;v~TF>?B60 z5>wfU9H-;s<9X`{OZb_^;xyWeznlM-(>j7rtTIpW{)>N?^R9&C>!w>FG~(fTj`wN& zyI08Vj5NRU*-@w^XzRW%sj1F*lh{AU!N+n@LA^9t{c$L+`Yz2d?1K8Sxm-pP)Nd*lDi zgXjIfUvOlbdaGuyYRS)@f~g@(j1?;9|=yoT^~~M zz4>~5Z(IgHtMK1&XX=X2>RXq=Z)kNq9$-hsz45m#gU=~E-D0`Pi>LiO>Ady)mBRnj z4Mv*rZq9Ru+jUy~vxA`q?#(dnz{BH)=XIK&xbNNgIpy$k4y2485oeCt_$SoFZFm44 GQT!Vx{&U>` literal 0 HcmV?d00001 diff --git a/lldb/test/API/commands/trace/intelpt-trace/main.cpp b/lldb/test/API/commands/trace/intelpt-trace/main.cpp new file mode 100644 index 0000000..3e4ad1c --- /dev/null +++ b/lldb/test/API/commands/trace/intelpt-trace/main.cpp @@ -0,0 +1,8 @@ +int main() { + int ret = 0; + + for (int i = 0; i < 4; i++) + ret ^= 1; + + return ret; +} diff --git a/lldb/test/API/commands/trace/intelpt-trace/trace.json b/lldb/test/API/commands/trace/intelpt-trace/trace.json new file mode 100644 index 0000000..543b71d --- /dev/null +++ b/lldb/test/API/commands/trace/intelpt-trace/trace.json @@ -0,0 +1,31 @@ +{ + "trace": { + "type": "intel-pt", + "pt_cpu": { + "vendor": "intel", + "family": 6, + "model": 79, + "stepping": 1 + } + }, + "processes": [ + { + "pid": 1234, + "triple": "x86_64-*-linux", + "threads": [ + { + "tid": 3842849, + "traceFile": "3842849.trace" + } + ], + "modules": [ + { + "file": "a.out", + "systemPath": "a.out", + "loadAddress": "0x0000000000400000", + "uuid": "6AA9A4E2-6F28-2F33-377D-59FECE874C71-5B41261A" + } + ] + } + ] +} diff --git a/lldb/test/API/commands/trace/intelpt-trace/trace_bad.json b/lldb/test/API/commands/trace/intelpt-trace/trace_bad.json new file mode 100644 index 0000000..9d00a82 --- /dev/null +++ b/lldb/test/API/commands/trace/intelpt-trace/trace_bad.json @@ -0,0 +1,14 @@ +{ + "trace": { + "type": "intel-pt", + "pt_cpu": { + "vendor": "intel", + "family": 6, + "model": 79, + "stepping": 1 + } + }, + "processes": [ + 123 + ] +} diff --git a/lldb/test/API/commands/trace/intelpt-trace/trace_bad2.json b/lldb/test/API/commands/trace/intelpt-trace/trace_bad2.json new file mode 100644 index 0000000..16ece12 --- /dev/null +++ b/lldb/test/API/commands/trace/intelpt-trace/trace_bad2.json @@ -0,0 +1,41 @@ +{ + "trace": { + "type": "intel-pt", + "pt_cpu": { + "vendor": "intel", + "family": 6, + "model": 79, + "stepping": 1 + } + }, + "processes": [ + { + "pid": 1234, + "triple": "x86_64-*-linux", + "threads": [ + { + "tid": 5678, + "traceFile": "3842849.trace" + } + ], + "modules": [ + { + "file": "a.out", + "systemPath": "a.out", + "loadAddress": "0x0000000000400000", + "uuid": "6AA9A4E2-6F28-2F33-377D-59FECE874C71-5B41261A" + } + ] + }, + { + "pid": 12345, + "threads": [ + { + "tid": 56789, + "traceFile": "3842849.trace" + } + ], + "modules": [] + } + ] +} diff --git a/lldb/test/API/commands/trace/intelpt-trace/trace_bad3.json b/lldb/test/API/commands/trace/intelpt-trace/trace_bad3.json new file mode 100644 index 0000000..dc587a8 --- /dev/null +++ b/lldb/test/API/commands/trace/intelpt-trace/trace_bad3.json @@ -0,0 +1,32 @@ +{ + "trace": { + "type": "intel-pt", + "pt_cpu": { + "vendor": "intel", + "family": 6, + "model": 79, + "stepping": 1 + } + }, + "processes": [ + { + "pid": 1234, + "triple": "x86_64-*-linux", + "threads": [ + { + "tid": 5678, + "traceFile": "3842849.trace" + } + ], + "modules": [ + { + "file": "a.out", + "systemPath": "a.out", + "loadAddress": "0x0000000000400000", + "uuid": "6AA9A4E2-6F28-2F33-377D-59FECE874C71-5B41261A" + } + ] + }, + {} + ] +} -- 2.7.4