wallace updated this revision to Diff 288496. wallace added a comment. Addressed comments
- Now using StringRef correctly whenever possible. - Revert back to using "traceFile" only inside the thread section. That's how intel-pt works at the moment and, as Greg suggested, we can change the schema in the future if needed. - Now the global schema construction inlines the plug-in schema. - Simplified the CMake logic - Separated the parser from the plugin. Now new plugins need to derive their own Trace implementation along with their own Parser. - I'm still using StructuredData for the parsing. There's a chance that once we release this feature users will want support for other formats besides JSON, so for now I prefer to ask for JSON input but parse in a format-agnostic way. If eventually no one needs any other format, we can switch to JSON-only parsing. - Also, now the Targets created during parsing are preserved only if the parsing succeeded. Otherwise, the Debugger object is left unchanged. 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/Target/TraceSettingsParser.h lldb/include/lldb/Utility/StructuredData.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/Plugins/Trace/intel-pt/TraceIntelPTSettingsParser.cpp lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSettingsParser.h lldb/source/Target/CMakeLists.txt lldb/source/Target/Trace.cpp lldb/source/Target/TraceSettingsParser.cpp lldb/source/Utility/StructuredData.cpp lldb/test/API/commands/trace/TestTraceLoad.py lldb/test/API/commands/trace/TestTraceSchema.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 lldb/test/API/commands/trace/intelpt-trace/trace_bad.json lldb/test/API/commands/trace/intelpt-trace/trace_bad2.json lldb/test/API/commands/trace/intelpt-trace/trace_bad3.json
Index: lldb/test/API/commands/trace/intelpt-trace/trace_bad3.json =================================================================== --- /dev/null +++ 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 + } + }, + "triple": "x86_64-*-linux", + "processes": [ + { + "pid": 1234, + "threads": [ + { + "tid": 5678, + "traceFile": "3842849.trace" + } + ], + "modules": [ + { + "file": "a.out", + "systemPath": "a.out", + "loadAddress": "0x0000000000400000", + "uuid": "6AA9A4E2-6F28-2F33-377D-59FECE874C71-5B41261A" + } + ] + }, + {} + ] +} Index: lldb/test/API/commands/trace/intelpt-trace/trace_bad2.json =================================================================== --- /dev/null +++ lldb/test/API/commands/trace/intelpt-trace/trace_bad2.json @@ -0,0 +1,12 @@ +{ + "trace": { + "type": "intel-pt", + "pt_cpu": { + "vendor": "intel", + "family": 6, + "model": 79, + "stepping": 1 + } + }, + "processes": [] +} Index: lldb/test/API/commands/trace/intelpt-trace/trace_bad.json =================================================================== --- /dev/null +++ lldb/test/API/commands/trace/intelpt-trace/trace_bad.json @@ -0,0 +1,15 @@ +{ + "trace": { + "type": "intel-pt", + "pt_cpu": { + "vendor": "intel", + "family": 6, + "model": 79, + "stepping": 1 + } + }, + "triple": "x86_64-*-linux", + "processes": [ + 123 + ] +} Index: lldb/test/API/commands/trace/intelpt-trace/trace.json =================================================================== --- /dev/null +++ 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 + } + }, + "triple": "x86_64-*-linux", + "processes": [ + { + "pid": 1234, + "threads": [ + { + "tid": 5678, + "traceFile": "3842849.trace" + } + ], + "modules": [ + { + "file": "a.out", + "systemPath": "a.out", + "loadAddress": "0x0000000000400000", + "uuid": "6AA9A4E2-6F28-2F33-377D-59FECE874C71-5B41261A" + } + ] + } + ] +} 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/TestTraceSchema.py =================================================================== --- /dev/null +++ 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"']) Index: lldb/test/API/commands/trace/TestTraceLoad.py =================================================================== --- /dev/null +++ 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(), 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) + + 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: structured data is expected to be "dictionary"', "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: structured data is missing the "triple" field.', "Value", '"family": 6', '"vendor": "intel"', "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: structured data is missing the "pid" field.']) + self.assertEqual(self.dbg.GetNumTargets(), 0) 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/TraceSettingsParser.cpp =================================================================== --- /dev/null +++ lldb/source/Target/TraceSettingsParser.cpp @@ -0,0 +1,255 @@ +//===-- 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 <regex> + +#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; + +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"( triple": string, // llvm-triple + "processes": [ + { + "pid": integer, + "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 StructuredData::Dictionary &thread) { + llvm::Expected<lldb::tid_t> tid = + thread.FetchValueForKeyAsInteger<lldb::tid_t>("tid"); + if (!tid) + return tid.takeError(); + + if (llvm::Expected<StringRef> trace_file = + thread.FetchValueForKeyAsString("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 StructuredData::Dictionary &process) { + llvm::Expected<StructuredData::Array *> threads = + process.FetchValueForKeyAsArray("threads"); + if (!threads) + return threads.takeError(); + + for (size_t i = 0; i < (*threads)->GetSize(); i++) { + llvm::Expected<StructuredData::Dictionary *> thread = + (*threads)->GetItemAtIndex(i)->ToDictionary(); + if (!thread) + return thread.takeError(); + if (llvm::Error err = ParseThread(process_sp, **thread)) + return err; + } + return llvm::Error::success(); +} + +static llvm::Expected<addr_t> 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 StructuredData::Dictionary &module) { + llvm::Expected<StringRef> load_address_str = + module.FetchValueForKeyAsString("loadAddress"); + if (!load_address_str) + return load_address_str.takeError(); + llvm::Expected<addr_t> load_address = ParseAddress(*load_address_str); + if (!load_address) + return load_address.takeError(); + + llvm::Expected<StringRef> system_path = + module.FetchValueForKeyAsString("systemPath"); + if (!system_path) + return system_path.takeError(); + FileSpec system_file_spec(*system_path); + NormalizePath(system_file_spec); + + llvm::Expected<llvm::Optional<StringRef>> file = + module.FetchValueForKeyAsOptionalString("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.GetArchitecture() = m_arch; + module_spec.SetObjectOffset(*load_address); + + llvm::Expected<llvm::Optional<StringRef>> uuid_str = + module.FetchValueForKeyAsOptionalString("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 StructuredData::Dictionary &process) { + llvm::Expected<StructuredData::Array *> modules = + process.FetchValueForKeyAsArray("modules"); + if (!modules) + return modules.takeError(); + + for (size_t i = 0; i < (*modules)->GetSize(); i++) { + llvm::Expected<StructuredData::Dictionary *> module = + (*modules)->GetItemAtIndex(i)->ToDictionary(); + 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 StructuredData::Dictionary &process) { + llvm::Expected<lldb::pid_t> pid = + process.FetchValueForKeyAsInteger<lldb::pid_t>("pid"); + if (!pid) + return pid.takeError(); + + TargetSP target_sp; + Status error = debugger.GetTargetList().CreateTarget( + debugger, /*user_exe_path*/ llvm::StringRef(), + /*triple_str*/ llvm::StringRef(), 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(*pid); + + if (llvm::Error err = ParseThreads(process_sp, process)) + return err; + + return ParseModules(target_sp, process); +} + +llvm::Error TraceSettingsParser::ParseProcesses(Debugger &debugger) { + llvm::Expected<StructuredData::Array *> processes = + m_settings.FetchValueForKeyAsArray("processes"); + if (!processes) + return processes.takeError(); + + for (size_t i = 0; i < (*processes)->GetSize(); i++) { + llvm::Expected<StructuredData::Dictionary *> process = + (*processes)->GetItemAtIndex(i)->ToDictionary(); + if (!process) + return process.takeError(); + if (llvm::Error err = ParseProcess(debugger, **process)) + return err; + } + return llvm::Error::success(); +} + +llvm::Error TraceSettingsParser::ParseArchitecture() { + llvm::Expected<StringRef> triple = + m_settings.FetchValueForKeyAsString("triple"); + if (!triple) + return triple.takeError(); + m_arch = ArchSpec(*triple); + return llvm::Error::success(); +} + +llvm::Error TraceSettingsParser::ParseSettingsImpl(Debugger &debugger) { + if (llvm::Error err = ParseArchitecture()) + return err; + + if (llvm::Error err = ParseProcesses(debugger)) + return err; + return ParsePluginSettings(); +} + +llvm::Error TraceSettingsParser::ParseSettings(Debugger &debugger) { + 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()); + } + return llvm::Error::success(); +} Index: lldb/source/Target/Trace.cpp =================================================================== --- /dev/null +++ lldb/source/Target/Trace.cpp @@ -0,0 +1,51 @@ +//===-- 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 <regex> +#include <sstream> + +#include "llvm/Support/Format.h" + +#include "lldb/Core/PluginManager.h" + +using namespace lldb; +using namespace lldb_private; +using namespace llvm; + +llvm::Expected<lldb::TraceSP> +Trace::FindPlugin(const StructuredData::Dictionary &info, StringRef info_dir) { + llvm::Expected<StructuredData::Dictionary *> trace = + info.FetchValueForKeyAsDictionary("trace"); + if (!trace) + return trace.takeError(); + llvm::Expected<StringRef> type = (*trace)->FetchValueForKeyAsString("type"); + if (!type) + return type.takeError(); + 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()); +} + +llvm::Error Trace::ParseSettings(Debugger &debugger) { + if (llvm::Expected<std::shared_ptr<TraceSettingsParser>> parser = + ParsePluginSettings(debugger)) { + m_thread_to_trace_file_map = + std::move((*parser)->m_thread_to_trace_file_map); + m_arch = std::move((*parser)->m_arch); + return llvm::Error::success(); + } else + return parser.takeError(); +} Index: lldb/source/Target/CMakeLists.txt =================================================================== --- lldb/source/Target/CMakeLists.txt +++ 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 Index: lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSettingsParser.h =================================================================== --- /dev/null +++ lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSettingsParser.h @@ -0,0 +1,36 @@ +//===-- 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 "lldb/Target/TraceSettingsParser.h" +#include "lldb/Utility/StructuredData.h" + +class TraceIntelPTSettingsParser : public lldb_private::TraceSettingsParser { + +public: + TraceIntelPTSettingsParser(lldb_private::StructuredData::Dictionary settings, + llvm::StringRef settings_dir) + : lldb_private::TraceSettingsParser(settings, settings_dir) {} + +protected: + llvm::StringRef GetPluginSchema() override; + + llvm::Error ParsePluginSettings() override; + +private: + llvm::Error ParsePTCPU(const lldb_private::StructuredData::Dictionary &trace); + +public: + pt_cpu m_pt_cpu; +}; + +#endif // liblldb_TraceIntelPTSettingsParser_h_ Index: lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSettingsParser.cpp =================================================================== --- /dev/null +++ lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSettingsParser.cpp @@ -0,0 +1,65 @@ +//===-- 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 StructuredData::Dictionary &trace) { + llvm::Expected<StructuredData::Dictionary *> pt_cpu = + trace.FetchValueForKeyAsDictionary("pt_cpu"); + if (!pt_cpu) + return pt_cpu.takeError(); + + llvm::Expected<llvm::StringRef> vendor = + (*pt_cpu)->FetchValueForKeyAsString("vendor"); + if (!vendor) + return vendor.takeError(); + + llvm::Expected<uint16_t> family = + (*pt_cpu)->FetchValueForKeyAsInteger<uint16_t>("family"); + if (!family) + return family.takeError(); + + llvm::Expected<uint8_t> model = + (*pt_cpu)->FetchValueForKeyAsInteger<uint8_t>("model"); + if (!model) + return model.takeError(); + + llvm::Expected<uint8_t> stepping = + (*pt_cpu)->FetchValueForKeyAsInteger<uint8_t>("stepping"); + if (!stepping) + return stepping.takeError(); + + m_pt_cpu = {vendor->compare("intel") == 0 ? pcv_intel : pcv_unknown, *family, + *model, *stepping}; + return llvm::Error::success(); +} + +llvm::Error TraceIntelPTSettingsParser::ParsePluginSettings() { + llvm::Expected<StructuredData::Dictionary *> trace = + m_settings.FetchValueForKeyAsDictionary("trace"); + if (!trace) + return trace.takeError(); + return ParsePTCPU(**trace); +} Index: lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h =================================================================== --- /dev/null +++ lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h @@ -0,0 +1,54 @@ +//===-- 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; + + llvm::StringRef GetSchema() override; + + /// PluginInterface protocol + /// \{ + lldb_private::ConstString GetPluginName() override; + + static void Initialize(); + + static void Terminate(); + + static lldb::TraceSP + CreateInstance(lldb_private::StructuredData::Dictionary settings, + llvm::StringRef settings_dir); + + static lldb_private::ConstString GetPluginNameStatic(); + + uint32_t GetPluginVersion() override; + /// \} + +protected: + TraceIntelPT(lldb_private::StructuredData::Dictionary settings, + llvm::StringRef settings_dir) + : Trace(settings, settings_dir) {} + + llvm::Expected<std::shared_ptr<lldb_private::TraceSettingsParser>> + ParsePluginSettings(lldb_private::Debugger &debugger) override; + +private: + 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,62 @@ +//===-- 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; +} + +llvm::Expected<std::shared_ptr<TraceSettingsParser>> +TraceIntelPT::ParsePluginSettings(Debugger &debugger) { + auto parser = + std::make_shared<TraceIntelPTSettingsParser>(m_settings, m_settings_dir); + if (llvm::Error err = parser->ParseSettings(debugger)) + return std::move(err); + + m_pt_cpu = std::move(parser->m_pt_cpu); + return parser; +} + +StringRef TraceIntelPT::GetSchema() { + return TraceIntelPTSettingsParser(m_settings, m_settings_dir).GetSchema(); +} + +//------------------------------------------------------------------ +// 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 settings, + StringRef settings_dir) { + return lldb::TraceSP(new TraceIntelPT(settings, settings_dir)); +} Index: lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt =================================================================== --- /dev/null +++ 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 + ) Index: lldb/source/Plugins/Trace/CMakeLists.txt =================================================================== --- /dev/null +++ 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() 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,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.">; +} 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,295 @@ +//===-- 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 <sstream> + +#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\n", + trace_sp->GetPluginName().AsCString()); + error = trace_sp->ParseSettings(GetDebugger()); + } else { + 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; +}; + +// 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<OptionDefinition> 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 <plug-in>"), + 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; + } + + const char *plugin_name = command[0].c_str(); + + // We create a fake dictionary with the plugin name in order to create an + // instance of the Trace class. + std::shared_ptr<StructuredData::Dictionary> trace( + new StructuredData::Dictionary()); + trace->AddStringItem("type", plugin_name); + StructuredData::Dictionary dict; + dict.AddItem("trace", trace); + + if (Expected<lldb::TraceSP> traceOrErr = + Trace::FindPlugin(dict, /*settings_dir*/ "")) { + 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 [<sub-command-options>]") { + LoadSubCommand("load", + CommandObjectSP(new CommandObjectTraceLoad(interpreter))); + LoadSubCommand("dump", + CommandObjectSP(new CommandObjectTraceDump(interpreter))); + LoadSubCommand("schema", + CommandObjectSP(new CommandObjectTraceSchema(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, + llvm::StringRef 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,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<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/Utility/StructuredData.h =================================================================== --- lldb/include/lldb/Utility/StructuredData.h +++ lldb/include/lldb/Utility/StructuredData.h @@ -10,11 +10,14 @@ #define LLDB_UTILITY_STRUCTUREDDATA_H #include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" #include "llvm/Support/JSON.h" #include "lldb/Utility/ConstString.h" #include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/Status.h" #include "lldb/Utility/Stream.h" +#include "lldb/Utility/StreamString.h" #include "lldb/lldb-enumerations.h" #include <cassert> @@ -95,9 +98,9 @@ } Integer *GetAsInteger() { - return ((m_type == lldb::eStructuredDataTypeInteger) - ? static_cast<Integer *>(this) - : nullptr); + return (m_type == lldb::eStructuredDataTypeInteger) + ? static_cast<Integer *>(this) + : nullptr; } uint64_t GetIntegerValue(uint64_t fail_value = 0) { @@ -149,6 +152,37 @@ ObjectSP GetObjectForDotSeparatedPath(llvm::StringRef path); + /// \a llvm::Expected based accessors + /// \{ + llvm::Expected<Dictionary *> ToDictionary() { + Dictionary *ptr = GetAsDictionary(); + if (!ptr) + return CreateWrongTypeError("dictionary"); + return ptr; + } + + llvm::Expected<Array *> ToArray() { + Array *ptr = GetAsArray(); + if (!ptr) + return CreateWrongTypeError("array"); + return ptr; + } + + llvm::Expected<Integer *> ToInteger() { + Integer *ptr = GetAsInteger(); + if (!ptr) + return CreateWrongTypeError("integer"); + return ptr; + } + + llvm::Expected<String *> ToString() { + String *ptr = GetAsString(); + if (!ptr) + return CreateWrongTypeError("string"); + return ptr; + } + /// \} + void DumpToStdout(bool pretty_print = true) const; virtual void Serialize(llvm::json::OStream &s) const = 0; @@ -159,6 +193,17 @@ } private: + llvm::Error CreateWrongTypeError(llvm::StringRef type) { + StreamString object_stream; + Dump(object_stream); + object_stream.Flush(); + + return llvm::createStringError( + std::errc::invalid_argument, + "structured data is expected to be \"%s\".\nValue:\n%s", type.data(), + object_stream.GetData()); + } + lldb::StructuredDataType m_type; }; @@ -399,6 +444,7 @@ } return success; } + template <class IntType> bool GetValueForKeyAsInteger(llvm::StringRef key, IntType &result) const { ObjectSP value_sp = GetValueForKey(key); @@ -485,6 +531,61 @@ return false; } + /// \a llvm::Expected based accessors + /// \{ + llvm::Expected<ObjectSP> FetchValueForKey(llvm::StringRef key) const { + ObjectSP value = GetValueForKey(key); + if (!value) + return CreateMissingKeyError(key); + return value; + } + + template <class IntType> + llvm::Expected<IntType> + FetchValueForKeyAsInteger(llvm::StringRef key) const { + llvm::Expected<ObjectSP> value = FetchValueForKey(key); + if (!value) + return value.takeError(); + llvm::Expected<Integer *> int_value = (*value)->ToInteger(); + if (!int_value) + return int_value.takeError(); + return static_cast<IntType>((*int_value)->GetValue()); + } + + llvm::Expected<llvm::StringRef> + FetchValueForKeyAsString(llvm::StringRef key) const { + llvm::Expected<ObjectSP> value = FetchValueForKey(key); + if (!value) + return value.takeError(); + llvm::Expected<String *> string_value = (*value)->ToString(); + if (!string_value) + return value.takeError(); + return (*string_value)->GetValue(); + } + + llvm::Expected<llvm::Optional<llvm::StringRef>> + FetchValueForKeyAsOptionalString(llvm::StringRef key) const { + if (!HasKey(key)) + return llvm::None; + return FetchValueForKeyAsString(key); + } + + llvm::Expected<Dictionary *> + FetchValueForKeyAsDictionary(llvm::StringRef key) const { + llvm::Expected<ObjectSP> value = FetchValueForKey(key); + if (!value) + return value.takeError(); + return (*value)->ToDictionary(); + } + + llvm::Expected<Array *> FetchValueForKeyAsArray(llvm::StringRef key) const { + llvm::Expected<ObjectSP> value = FetchValueForKey(key); + if (!value) + return value.takeError(); + return (*value)->ToArray(); + } + /// \} + bool HasKey(llvm::StringRef key) const { ConstString key_cs(key); collection::const_iterator search = m_dict.find(key_cs); @@ -515,6 +616,16 @@ void Serialize(llvm::json::OStream &s) const override; protected: + llvm::Error CreateMissingKeyError(llvm::StringRef key) const { + StreamString object_stream; + Dump(object_stream); + object_stream.Flush(); + return llvm::createStringError(std::errc::invalid_argument, + "structured data is missing the \"%s\" " + "field.\nValue:\n%s", + key.data(), object_stream.GetData()); + } + typedef std::map<ConstString, ObjectSP> collection; collection m_dict; }; Index: lldb/include/lldb/Target/TraceSettingsParser.h =================================================================== --- /dev/null +++ lldb/include/lldb/Target/TraceSettingsParser.h @@ -0,0 +1,95 @@ +//===-- 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/Utility/ArchSpec.h" +#include "lldb/lldb-private.h" + +namespace lldb_private { + +/// \class TraceSettingsParser TraceSettingsParser.h +/// "lldb/Target/TraceSettingsParser.h" A plug-in interface definition class for +/// parsing \a Trace settings. +/// +/// A \a Trace plug-in is initialized with some settings in a structured data +/// format like JSON. This class offers basic parsing functionalities to convert +/// those settings into objects used for the \a Trace plug-in. +/// +/// 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(StructuredData::Dictionary settings, + llvm::StringRef settings_dir) + : m_settings(std::move(settings)), m_settings_dir(settings_dir) {} + + 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. + /// + /// \return + /// An error object containing the reason if there is a failure. + llvm::Error ParseSettings(Debugger &debugger); + +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 StructuredData::Dictionary &process); + llvm::Error ParseProcesses(lldb_private::Debugger &debugger); + llvm::Error ParseThread(lldb::ProcessSP &process_sp, + const StructuredData::Dictionary &thread); + llvm::Error ParseThreads(lldb::ProcessSP &process_sp, + const StructuredData::Dictionary &process); + llvm::Error ParseModule(lldb::TargetSP &target_sp, + const StructuredData::Dictionary &module); + llvm::Error ParseModules(lldb::TargetSP &target_sp, + const StructuredData::Dictionary &process); + llvm::Error ParseArchitecture(); + llvm::Error ParseSettingsImpl(lldb_private::Debugger &debugger); + +public: + StructuredData::Dictionary m_settings; + std::string m_settings_dir; + + /// Objects created as product of the parsing + /// \{ + std::map<lldb::pid_t, std::map<lldb::tid_t, lldb_private::FileSpec>> + m_thread_to_trace_file_map; + lldb_private::ArchSpec m_arch; + std::vector<lldb::TargetSP> m_targets; + /// \} +}; + +} // namespace lldb_private + +#endif // LLDB_TARGET_TRACE_SETTINGS_PARSER_H Index: lldb/include/lldb/Target/Trace.h =================================================================== --- /dev/null +++ lldb/include/lldb/Target/Trace.h @@ -0,0 +1,139 @@ +//===-- 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/Target/TraceSettingsParser.h" +#include "lldb/Utility/ArchSpec.h" +#include "lldb/Utility/StructuredData.h" +#include "lldb/lldb-private.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] settings + /// Structured data in the form of a dictionary. + /// + /// \param[in] settings_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(const StructuredData::Dictionary &settings, + llvm::StringRef settings_dir); + + const ArchSpec &GetArchitecture() const { return m_arch; } + + /// 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. + /// + /// \return + /// An error object containing the reason if there is a failure. + llvm::Error ParseSettings(Debugger &debugger); + + /// Get the JSON schema of the settings for the trace plug-in. + virtual llvm::StringRef GetSchema() = 0; + +protected: + Trace(StructuredData::Dictionary settings, llvm::StringRef settings_dir) + : m_settings(std::move(settings)), m_settings_dir(settings_dir) {} + + /// Method that should be overriden to parse plugin-specific settings. + /// + /// The plug-in implementation should define its own implementation of \a + /// TraceSettingsParser for its own plug-in specific parsing. + /// + /// \param[in] debugger + /// The debugger instance where the corresponding \a Target objects are + /// created. + /// + /// \return + /// A pointer to the \a TraceSettingsParser used by the plug-in + /// implementation, or an error object in case of failures. + virtual llvm::Expected<std::shared_ptr<TraceSettingsParser>> + ParsePluginSettings(Debugger &debugger) = 0; + +private: + Trace(const Trace &) = delete; + const Trace &operator=(const Trace &) = delete; + +protected: + /// Structured data that holds all settings for this trace session. + StructuredData::Dictionary m_settings; + /// The directory that contains the settings file. + std::string m_settings_dir; + + std::map<lldb::pid_t, std::map<lldb::tid_t, lldb_private::FileSpec>> + m_thread_to_trace_file_map; + lldb_private::ArchSpec m_arch; +}; + +} // 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