diff --git a/lldb/include/lldb/Core/PluginManager.h b/lldb/include/lldb/Core/PluginManager.h --- a/lldb/include/lldb/Core/PluginManager.h +++ b/lldb/include/lldb/Core/PluginManager.h @@ -330,6 +330,14 @@ 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 --- /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 --- /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 --- a/lldb/include/lldb/lldb-forward.h +++ b/lldb/include/lldb/lldb-forward.h @@ -226,6 +226,8 @@ class ThreadPlanStepThrough; class ThreadPlanTracer; class ThreadSpec; +class Trace; +class TraceSettingsParser; class TraceOptions; class Type; class TypeAndOrName; @@ -432,6 +434,7 @@ 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 --- 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 @@ 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 --- a/lldb/source/Commands/CMakeLists.txt +++ b/lldb/source/Commands/CMakeLists.txt @@ -31,6 +31,7 @@ CommandObjectStats.cpp CommandObjectTarget.cpp CommandObjectThread.cpp + CommandObjectTrace.cpp CommandObjectType.cpp CommandObjectVersion.cpp CommandObjectWatchpoint.cpp diff --git a/lldb/source/Commands/CommandObjectTrace.h b/lldb/source/Commands/CommandObjectTrace.h new file mode 100644 --- /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/CommandObjectTrace.cpp b/lldb/source/Commands/CommandObjectTrace.cpp new file mode 100644 --- /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/Options.td b/lldb/source/Commands/Options.td --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -1175,3 +1175,19 @@ 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 --- a/lldb/source/Core/PluginManager.cpp +++ b/lldb/source/Core/PluginManager.cpp @@ -1005,6 +1005,30 @@ 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 --- 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 @@ 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 --- a/lldb/source/Plugins/CMakeLists.txt +++ b/lldb/source/Plugins/CMakeLists.txt @@ -19,6 +19,7 @@ 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 --- /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 --- /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.h b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h new file mode 100644 --- /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/TraceIntelPT.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp new file mode 100644 --- /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/TraceIntelPTSettingsParser.h b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSettingsParser.h new file mode 100644 --- /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/Plugins/Trace/intel-pt/TraceIntelPTSettingsParser.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSettingsParser.cpp new file mode 100644 --- /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/Target/CMakeLists.txt b/lldb/source/Target/CMakeLists.txt --- a/lldb/source/Target/CMakeLists.txt +++ b/lldb/source/Target/CMakeLists.txt @@ -65,6 +65,8 @@ 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 --- /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 --- /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 --- a/lldb/source/Utility/StructuredData.cpp +++ b/lldb/source/Utility/StructuredData.cpp @@ -42,7 +42,12 @@ 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 --- /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 --- /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..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@