wallace updated this revision to Diff 287079. wallace added a comment. - Added libipt as a dependency to build the new intel pt plugin. It's merely a
copy paste of what the old intel pt plugin does. - Added a test with a sample trace.json definition file. It includes a simple a.out object file, the source and an intel pt trace that goes from main to the final return 0. The test checks that the plugin does the parsing of the structured data correctly and creates the target, process and modules correctly. - Added the necessary parsing functionalities in Trace.cpp to make sense of the structured data. - Added a simple parsing of the intel pt-specific information in the structured data. I didn't implement Dump nor Decoding, as in this case the test is quite comprehensive regarding Load and I want to keep the diff small. I'll implement the other features later. Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D85705/new/ https://reviews.llvm.org/D85705 Files: lldb/include/lldb/Core/PluginManager.h lldb/include/lldb/Target/Trace.h lldb/include/lldb/lldb-forward.h lldb/include/lldb/lldb-private-interfaces.h lldb/source/Commands/CMakeLists.txt lldb/source/Commands/CommandObjectTrace.cpp lldb/source/Commands/CommandObjectTrace.h lldb/source/Commands/Options.td lldb/source/Core/PluginManager.cpp lldb/source/Interpreter/CommandInterpreter.cpp lldb/source/Plugins/CMakeLists.txt lldb/source/Plugins/Trace/CMakeLists.txt lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h lldb/source/Target/CMakeLists.txt lldb/source/Target/Trace.cpp lldb/source/Utility/StructuredData.cpp lldb/test/API/commands/trace/TestTraceLoad.py lldb/test/API/commands/trace/intelpt-trace/3842849.trace lldb/test/API/commands/trace/intelpt-trace/a.out lldb/test/API/commands/trace/intelpt-trace/main.cpp lldb/test/API/commands/trace/intelpt-trace/trace.json
Index: lldb/test/API/commands/trace/intelpt-trace/trace.json =================================================================== --- /dev/null +++ lldb/test/API/commands/trace/intelpt-trace/trace.json @@ -0,0 +1,26 @@ +{ + "type": "intel-pt", + "processes": [ + { + "pid": 1234, + "threads": [ + { + "tid": 5678, + "traceFile": "3842849.trace" + } + ], + "modules": [ + { + "filePath": "a.out", + "loadAddress": "0x0000000000400000" + } + ] + } + ], + "pt_cpu": { + "vendor": "intel", + "family": 6, + "model": 79, + "stepping": 1 + } +} Index: lldb/test/API/commands/trace/intelpt-trace/main.cpp =================================================================== --- /dev/null +++ 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; +} Index: lldb/test/API/commands/trace/TestTraceLoad.py =================================================================== --- /dev/null +++ lldb/test/API/commands/trace/TestTraceLoad.py @@ -0,0 +1,34 @@ +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(), 5678) + + 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) Index: lldb/source/Utility/StructuredData.cpp =================================================================== --- lldb/source/Utility/StructuredData.cpp +++ 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<json::Value> 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) { Index: lldb/source/Target/Trace.cpp =================================================================== --- /dev/null +++ lldb/source/Target/Trace.cpp @@ -0,0 +1,199 @@ +//===-- 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 "Plugins/Process/Utility/HistoryThread.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Target/Process.h" +#include "lldb/Utility/StreamString.h" +#include "llvm/Support/Format.h" + +using namespace lldb; +using namespace lldb_private; +using namespace llvm; + +llvm::Expected<lldb::TraceSP> Trace::FindPlugin(StructuredData::Dictionary info, + const char *info_dir) { + StringRef type; + if (!info.GetValueForKeyAsString("type", type)) + return createStringError(std::errc::invalid_argument, + "structured data is missing the \"type\" field " + "that identifies the trace plug-in"); + ConstString plugin_name(type); + auto create_callback = PluginManager::GetTraceCreateCallback(plugin_name); + if (create_callback) { + return create_callback(std::move(info), info_dir); + } + return createStringError( + std::errc::invalid_argument, + "no trace plug-in matches the specified type: \"%s\"", + type.str().c_str()); +} + +//------------------------------------------------------------------ +// Structured data parsing +//------------------------------------------------------------------ + +bool Trace::SetMissingFieldError(Status &error, const char *field, + const char *type, + StructuredData::Dictionary &dict) { + StreamString object_stream; + dict.Dump(object_stream); + object_stream.Flush(); + + error.SetErrorStringWithFormat( + "structured data is missing the \"%s\" \"%s\" field: %s", field, type, + object_stream.GetData()); + return false; +} + +bool Trace::SetWrongTypeError(Status &error, const char *type, + StructuredData::Object &object) { + StreamString object_stream; + object.Dump(object_stream); + object_stream.Flush(); + + error.SetErrorStringWithFormat("structured data is expected to be \"%s\": %s", + type, object_stream.GetData()); + return false; +} + +bool Trace::ParseThread(ProcessSP &process_sp, StructuredData::ObjectSP &item, + Status &error) { + StructuredData::Dictionary *thread = item->GetAsDictionary(); + if (!thread) + return SetWrongTypeError(error, "dictionary", *item); + + lldb::tid_t tid; + if (!thread->GetValueForKeyAsInteger("tid", tid)) + return SetMissingFieldError(error, "tid", "integer", *thread); + + ThreadSP thread_sp(new HistoryThread(*process_sp, tid, /*callstack*/ {})); + process_sp->GetThreadList().AddThread(thread_sp); + return true; +} + +bool Trace::ParseThreads(ProcessSP &process_sp, + StructuredData::Dictionary &process, Status &error) { + StructuredData::Array *threads = nullptr; + if (!process.GetValueForKeyAsArray("threads", threads)) + return SetMissingFieldError(error, "threads", "array", process); + + for (size_t i = 0; i < threads->GetSize(); i++) { + StructuredData::ObjectSP item = threads->GetItemAtIndex(i); + if (!ParseThread(process_sp, item, error)) + return false; + } + return true; +} + +bool Trace::ParseModule(TargetSP &target_sp, StructuredData::ObjectSP &item, + Status &error) { + StructuredData::Dictionary *module = item->GetAsDictionary(); + if (!module) + return SetWrongTypeError(error, "dictionary", *item); + + StringRef file_path; + if (!module->GetValueForKeyAsString("filePath", file_path)) + return SetMissingFieldError(error, "filePath", "string", *module); + + StringRef load_address_str; + if (!module->GetValueForKeyAsString("loadAddress", load_address_str)) + return SetMissingFieldError(error, "loadAddress", "string", *module); + addr_t load_address; + if (load_address_str.getAsInteger(0, load_address)) + return SetMissingFieldError(error, "loadAddress", "string integer", + *module); + + std::string full_path = m_info_dir; + full_path += "/"; + full_path += file_path; + FileSpec file_spec(full_path); + + if (!FileSystem::Instance().Exists(file_spec)) { + error.SetErrorStringWithFormat("path \"%s\" doesn't exists", + file_spec.GetCString()); + return false; + } + + ModuleSP module_sp(new Module(file_spec, ArchSpec())); + bool changed = true; + module_sp->SetLoadAddress(*target_sp, load_address, /*value_is_offset*/ true, + changed); + target_sp->GetImages().Append(module_sp); + return true; +} + +bool Trace::ParseModules(TargetSP &target_sp, + StructuredData::Dictionary &process, Status &error) { + StructuredData::Array *modules = nullptr; + if (!process.GetValueForKeyAsArray("modules", modules)) + return SetMissingFieldError(error, "modules", "array", process); + + for (size_t i = 0; i < modules->GetSize(); i++) { + StructuredData::ObjectSP item = modules->GetItemAtIndex(i); + if (!ParseModule(target_sp, item, error)) + return false; + } + return true; +} + +bool Trace::ParseProcess(Debugger &debugger, StructuredData::ObjectSP item, + Status &error) { + StructuredData::Dictionary *process = item->GetAsDictionary(); + if (!process) + return SetWrongTypeError(error, "dictionary", *item); + + lldb::pid_t pid; + if (!process->GetValueForKeyAsInteger("pid", pid)) + return SetMissingFieldError(error, "pid", "integer", *process); + + TargetSP target_sp; + error = debugger.GetTargetList().CreateTarget( + debugger, /*user_exe_path*/ llvm::StringRef(), + /*triple_str*/ llvm::StringRef(), eLoadDependentsNo, + /*platform_options*/ nullptr, target_sp); + + if (!target_sp) + return false; + + debugger.GetTargetList().SetSelectedTarget(target_sp.get()); + + ProcessSP process_sp(target_sp->CreateProcess( + /*listener*/ nullptr, /*plugin_name*/ llvm::StringRef(), + /*crash_file*/ nullptr)); + process_sp->SetID(pid); + + if (!ParseThreads(process_sp, *process, error)) + return false; + + return ParseModules(target_sp, *process, error); +} + +bool Trace::ParseProcesses(Debugger &debugger, StructuredData::Dictionary &dict, + Status &error) { + StructuredData::Array *processes = nullptr; + if (!dict.GetValueForKeyAsArray("processes", processes)) + return SetMissingFieldError(error, "processes", "array", dict); + + for (size_t i = 0; i < processes->GetSize(); i++) { + StructuredData::ObjectSP item = processes->GetItemAtIndex(i); + if (!ParseProcess(debugger, item, error)) + return false; + } + return true; +} + +bool Trace::Initialize(Debugger &debugger, Status &error) { + if (!ParseProcesses(debugger, m_info, error)) + return false; + return InitializeInstance(debugger, error); +} Index: lldb/source/Target/CMakeLists.txt =================================================================== --- lldb/source/Target/CMakeLists.txt +++ lldb/source/Target/CMakeLists.txt @@ -65,6 +65,7 @@ ThreadPlanTracer.cpp ThreadPlanStack.cpp ThreadSpec.cpp + Trace.cpp UnixSignals.cpp UnwindAssembly.cpp UnwindLLDB.cpp Index: lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h =================================================================== --- /dev/null +++ lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h @@ -0,0 +1,60 @@ +//===-- 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 "lldb/lldb-private.h" +#include <llvm/ADT/Optional.h> + +#include "lldb/Target/Trace.h" + +class TraceIntelPT : public lldb_private::Trace { +public: + void Dump(lldb_private::Stream *s) const override; + + /// Static Functions + /// \{ + static void Initialize(); + + static void Terminate(); + + static lldb::TraceSP + CreateInstance(lldb_private::StructuredData::Dictionary info, + const char *info_dir); + + static lldb_private::ConstString GetPluginNameStatic(); + /// \} + + /// PluginInterface protocol + /// \{ + lldb_private::ConstString GetPluginName() override; + + uint32_t GetPluginVersion() override; + /// \} + +protected: + TraceIntelPT(lldb_private::StructuredData::Dictionary info, + const char *info_dir) + : Trace(info, info_dir) {} + + bool InitializeInstance(lldb_private::Debugger &debugger, + lldb_private::Status &error) override; + +private: + /// Structured data parsing + /// \{ + bool ParsePTCPU(lldb_private::StructuredData::Dictionary &dict, + lldb_private::Status &error); + /// \} + + pt_cpu m_pt_cpu; +}; + +#endif // liblldb_TraceIntelPT_h_ Index: lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp =================================================================== --- /dev/null +++ lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp @@ -0,0 +1,81 @@ +//===-- 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 "lldb/Core/PluginManager.h" + +using namespace lldb; +using namespace lldb_private; + +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; +} + +//------------------------------------------------------------------ +// Structured data parsing +//------------------------------------------------------------------ + +bool TraceIntelPT::ParsePTCPU(StructuredData::Dictionary &dict, Status &error) { + StructuredData::Dictionary *pt_cpu = nullptr; + if (!dict.GetValueForKeyAsDictionary("pt_cpu", pt_cpu)) + return SetMissingFieldError(error, "pt_cpu", "dictionary", dict); + + llvm::StringRef vendor; + if (!pt_cpu->GetValueForKeyAsString("vendor", vendor)) + return SetMissingFieldError(error, "vendor", "string", *pt_cpu); + + uint16_t family; + if (!pt_cpu->GetValueForKeyAsInteger("family", family)) + return SetMissingFieldError(error, "family", "integer", *pt_cpu); + + uint8_t model; + if (!pt_cpu->GetValueForKeyAsInteger("model", model)) + return SetMissingFieldError(error, "model", "integer", *pt_cpu); + + uint8_t stepping; + if (!pt_cpu->GetValueForKeyAsInteger("stepping", stepping)) + return SetMissingFieldError(error, "stepping", "integer", *pt_cpu); + + m_pt_cpu = {vendor.compare("intel") == 0 ? pcv_intel : pcv_unknown, family, + model, stepping}; + return true; +} + +bool TraceIntelPT::InitializeInstance(lldb_private::Debugger &debugger, + Status &error) { + return ParsePTCPU(m_info, error); +} + +//------------------------------------------------------------------ +// 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(StructuredData::Dictionary info, + const char *info_dir) { + return lldb::TraceSP(new TraceIntelPT(info, info_dir)); +} Index: lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt =================================================================== --- /dev/null +++ lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt @@ -0,0 +1,39 @@ +option(LLDB_BUILD_INTEL_PT "Enable Building of Intel(R) Processor Trace Tool" OFF) + +if (NOT LLDB_BUILD_INTEL_PT) + return() +endif() + +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}) + +if (NOT LIBIPT_LIBRARY_PATH) + find_library(LIBIPT_LIBRARY ipt) +else() + if (NOT EXISTS "${LIBIPT_LIBRARY_PATH}") + message (FATAL_ERROR "invalid libipt library path provided") + endif() + find_library(LIBIPT_LIBRARY ipt PATHS ${LIBIPT_LIBRARY_PATH}) +endif() + +if (NOT LIBIPT_LIBRARY) + message (FATAL_ERROR "libipt library not found") +endif() + +add_lldb_library(lldbPluginTraceIntelPT PLUGIN + TraceIntelPT.cpp + + LINK_LIBS + lldbCore + lldbSymbol + lldbTarget + ${LIBIPT_LIBRARY} + LINK_COMPONENTS + Support + ) Index: lldb/source/Plugins/Trace/CMakeLists.txt =================================================================== --- /dev/null +++ lldb/source/Plugins/Trace/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(intel-pt) Index: lldb/source/Plugins/CMakeLists.txt =================================================================== --- lldb/source/Plugins/CMakeLists.txt +++ 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) Index: lldb/source/Interpreter/CommandInterpreter.cpp =================================================================== --- lldb/source/Interpreter/CommandInterpreter.cpp +++ lldb/source/Interpreter/CommandInterpreter.cpp @@ -38,6 +38,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" @@ -511,6 +512,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); Index: lldb/source/Core/PluginManager.cpp =================================================================== --- lldb/source/Core/PluginManager.cpp +++ lldb/source/Core/PluginManager.cpp @@ -1005,6 +1005,30 @@ return GetSymbolVendorInstances().GetCallbackAtIndex(idx); } +#pragma mark Trace + +typedef PluginInstance<TraceCreateInstance> TraceInstance; +typedef PluginInstances<TraceInstance> 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<UnwindAssemblyCreateInstance> UnwindAssemblyInstance; Index: lldb/source/Commands/Options.td =================================================================== --- lldb/source/Commands/Options.td +++ lldb/source/Commands/Options.td @@ -202,8 +202,8 @@ Desc<"Move breakpoints to nearest code. If not set the " "target.move-to-nearest-codesetting is used.">; def breakpoint_set_file_colon_line : Option<"joint-specifier", "y">, Group<12>, Arg<"FileLineColumn">, - Required, Completion<"SourceFile">, - Desc<"A specifier in the form filename:line[:column] for setting file & line breakpoints.">; + Required, Completion<"SourceFile">, + Desc<"A specifier in the form filename:line[:column] for setting file & line breakpoints.">; /* Don't add this option till it actually does something useful... def breakpoint_set_exception_typename : Option<"exception-typename", "O">, Arg<"TypeName">, Desc<"The breakpoint will only stop if an " @@ -751,7 +751,7 @@ def source_list_reverse : Option<"reverse", "r">, Group<4>, Desc<"Reverse the" " listing to look backwards from the last displayed block of source.">; def source_list_file_colon_line : Option<"joint-specifier", "y">, Group<5>, - Arg<"FileLineColumn">, Completion<"SourceFile">, + Arg<"FileLineColumn">, Completion<"SourceFile">, Desc<"A specifier in the form filename:line[:column] from which to display" " source.">; } @@ -1161,3 +1161,14 @@ 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.">; +} Index: lldb/source/Commands/CommandObjectTrace.h =================================================================== --- /dev/null +++ lldb/source/Commands/CommandObjectTrace.h @@ -0,0 +1,25 @@ +//===-- CommandObjectTrace.h ----------------------------------------------===// +// +// 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 Index: lldb/source/Commands/CommandObjectTrace.cpp =================================================================== --- /dev/null +++ lldb/source/Commands/CommandObjectTrace.cpp @@ -0,0 +1,202 @@ +//===-- 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 "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<OptionDefinition> 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; + } + + FileSpec json_file(command[0].ref()); + Status error; + auto data_sp = StructuredData::ParseJSONFromFile(json_file, error); + if (error.Success()) { + StructuredData::Dictionary *dict = data_sp->GetAsDictionary(); + if (dict) { + if (Expected<lldb::TraceSP> traceOrErr = Trace::FindPlugin( + *dict, json_file.GetDirectory().AsCString())) { + lldb::TraceSP trace_sp = traceOrErr.get(); + if (m_options.m_verbose) + result.AppendMessageWithFormat( + "loading trace with plugin %s", + trace_sp->GetPluginName().AsCString()); + trace_sp->Initialize(GetDebugger(), error); + } else { + result.AppendMessage("didn't get trace plugin..."); + error.SetErrorString(llvm::toString(traceOrErr.takeError())); + } + } else { + error.SetErrorString("JSON data must be a dictionary"); + } + } + if (error.Success()) { + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendErrorWithFormat("%s\n", error.AsCString()); + result.SetStatus(eReturnStatusFailed); + } + return result.Succeeded(); + } + + 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<OptionDefinition> 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; +}; + +// CommandObjectTrace + +CommandObjectTrace::CommandObjectTrace(CommandInterpreter &interpreter) + : CommandObjectMultiword(interpreter, "trace", + "Commands for loading and using processor " + "trace information.", + "trace [<sub-command-options>]") { + LoadSubCommand("load", + CommandObjectSP(new CommandObjectTraceLoad(interpreter))); + LoadSubCommand("dump", + CommandObjectSP(new CommandObjectTraceDump(interpreter))); +} + +CommandObjectTrace::~CommandObjectTrace() = default; Index: lldb/source/Commands/CMakeLists.txt =================================================================== --- lldb/source/Commands/CMakeLists.txt +++ lldb/source/Commands/CMakeLists.txt @@ -29,6 +29,7 @@ CommandObjectStats.cpp CommandObjectTarget.cpp CommandObjectThread.cpp + CommandObjectTrace.cpp CommandObjectType.cpp CommandObjectVersion.cpp CommandObjectWatchpoint.cpp Index: lldb/include/lldb/lldb-private-interfaces.h =================================================================== --- lldb/include/lldb/lldb-private-interfaces.h +++ lldb/include/lldb/lldb-private-interfaces.h @@ -11,6 +11,7 @@ #if defined(__cplusplus) +#include "lldb/Utility/StructuredData.h" #include "lldb/lldb-enumerations.h" #include "lldb/lldb-forward.h" #include "lldb/lldb-private-enumerations.h" @@ -104,6 +105,8 @@ const char *repl_options); typedef int (*ComparisonFunction)(const void *, const void *); typedef void (*DebuggerInitializeCallback)(Debugger &debugger); +typedef lldb::TraceSP (*TraceCreateInstance)(StructuredData::Dictionary info, + const char *info_dir); } // namespace lldb_private Index: lldb/include/lldb/lldb-forward.h =================================================================== --- lldb/include/lldb/lldb-forward.h +++ lldb/include/lldb/lldb-forward.h @@ -226,6 +226,7 @@ class ThreadPlanStepThrough; class ThreadPlanTracer; class ThreadSpec; +class Trace; class TraceOptions; class Type; class TypeAndOrName; @@ -432,6 +433,7 @@ typedef std::shared_ptr<lldb_private::ThreadPlan> ThreadPlanSP; typedef std::weak_ptr<lldb_private::ThreadPlan> ThreadPlanWP; typedef std::shared_ptr<lldb_private::ThreadPlanTracer> ThreadPlanTracerSP; +typedef std::shared_ptr<lldb_private::Trace> TraceSP; typedef std::shared_ptr<lldb_private::TraceOptions> TraceOptionsSP; typedef std::shared_ptr<lldb_private::Type> TypeSP; typedef std::weak_ptr<lldb_private::Type> TypeWP; Index: lldb/include/lldb/Target/Trace.h =================================================================== --- /dev/null +++ lldb/include/lldb/Target/Trace.h @@ -0,0 +1,195 @@ +//===-- 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 "lldb/Core/PluginInterface.h" +#include "lldb/Utility/Status.h" +#include "lldb/Utility/StructuredData.h" +#include "lldb/lldb-private.h" +#include "llvm/Support/Error.h" + +namespace lldb_private { + +class StructuredData; + +/// \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 structured 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 structured 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] info + /// Structured data in the form of a dictionary. + /// \param[in] info_dir + /// Path to a directory used to resolve relative paths in the structured + /// data. If the structured data is defined in a file, this should be the + /// folder containing it. + static llvm::Expected<lldb::TraceSP> + FindPlugin(StructuredData::Dictionary info, const char *info_dir); + + /// Method that should be invoked to perform the structured data parsing and + /// initialize the tracing information. + /// + /// \param[in] debugger + /// The debugger instance that can provide access to \a Targets, \a Modules, + /// etc. + /// + /// \param[in] info_dir + /// See \a Trace::FindPlugin. + /// + /// \param[out] error + /// The error object that would contain a reason if there is a failure. + /// + /// \return + /// \b false is there is an error, \b true otherwise. + bool Initialize(lldb_private::Debugger &debugger, Status &error); + +protected: + Trace(StructuredData::Dictionary info, const char *info_dir) + : m_info(std::move(info)), m_info_dir(info_dir) {} + + /// Method that should be used by the implementations of this class to do + /// specific structured data parsing and initialize any information they need. + /// + /// \param[in] debugger + /// The debugger instance that can provide access to \a Targets, \a Modules, + /// etc. + /// + /// \param[in] info_dir + /// See \a Trace::FindPlugin. + /// + /// \param[out] error + /// The error object that would contain a reason if there is a failure. + /// + /// \return + /// \b false is there is an error, \b true otherwise. + virtual bool InitializeInstance(lldb_private::Debugger &debugger, + Status &error) = 0; + +private: + Trace(const Trace &) = delete; + const Trace &operator=(const Trace &) = delete; + + /// Structured data parsing + /// \{ +protected: + /// Set a descriptive error message regarding a missing field in a \a + /// StructuredData::Dictionary. + /// + /// \param[out] error + /// The error object where the message is written to. + /// + /// \param[in] field + /// The field name that is missing in the dictionary. + /// + /// \param[in] type + /// The type of the missing field. + /// + /// \param[in] dictionary + /// The dictionary that is missing the provided field. It is printed in + /// the error object to make it more explanatory. + /// + /// \return + /// \b false. + bool SetMissingFieldError(Status &error, const char *field, const char *type, + StructuredData::Dictionary &dict); + + /// Set a descriptive error message regarding a \a StructuredData::Object with + /// incorrect type. + /// + /// \param[out] error + /// The error object where the message is written to. + /// + /// \param[in] type + /// The expected type. + /// + /// \param[in] object + /// The object that is of the wrong type. It is printed in the error + /// object to make it more explanatory. + /// + /// \return + /// \b false. + bool SetWrongTypeError(Status &error, const char *type, + StructuredData::Object &object); + +private: + bool ParseProcess(lldb_private::Debugger &debugger, + StructuredData::ObjectSP item, Status &error); + bool ParseProcesses(lldb_private::Debugger &debugger, + StructuredData::Dictionary &dict, Status &error); + bool ParseThread(lldb::ProcessSP &process_sp, StructuredData::ObjectSP &item, + Status &error); + bool ParseThreads(lldb::ProcessSP &process_sp, + StructuredData::Dictionary &process, Status &error); + bool ParseModule(lldb::TargetSP &target_sp, StructuredData::ObjectSP &item, + Status &error); + bool ParseModules(lldb::TargetSP &target_sp, + StructuredData::Dictionary &process, Status &error); + /// \} + +protected: + // Structured data that holds all settings for this trace session. + StructuredData::Dictionary m_info; + std::string m_info_dir; +}; + +} // namespace lldb_private + +#endif // LLDB_TARGET_TRACE_H Index: lldb/include/lldb/Core/PluginManager.h =================================================================== --- lldb/include/lldb/Core/PluginManager.h +++ 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);
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits