[Lldb-commits] [lldb] [lldb] Implement a formatter bytecode interpreter in C++ (PR #114333)
Adrian Prantl via lldb-commits
lldb-commits at lists.llvm.org
Wed Oct 30 17:03:50 PDT 2024
https://github.com/adrian-prantl created https://github.com/llvm/llvm-project/pull/114333
Compared to the python version, this also does type checking and error
handling, so it's slightly longer, however, it's still comfortably
under 500 lines.
See https://discourse.llvm.org/t/a-bytecode-for-lldb-data-formatters/82696 for more context!
This is currently a draft, I still want to add more tests and also extend the metadata with (show children) flag and potentially others.
>From c9a4d24f222a70c7c108deebb6c25222893d7159 Mon Sep 17 00:00:00 2001
From: Dave Lee <davelee.com at gmail.com>
Date: Wed, 24 Jan 2024 12:42:45 -0800
Subject: [PATCH 1/2] [lldb] Load embedded type summary section (#7859) (#8040)
Add support for type summaries embedded into the binary.
These embedded summaries will typically be generated by Swift macros,
but can also be generated by any other means.
rdar://115184658
---
lldb/include/lldb/lldb-enumerations.h | 1 +
lldb/source/Core/Section.cpp | 3 +
.../Plugins/ObjectFile/ELF/ObjectFileELF.cpp | 1 +
.../ObjectFile/Mach-O/ObjectFileMachO.cpp | 4 ++
.../ObjectFile/PECOFF/ObjectFilePECOFF.cpp | 1 +
lldb/source/Symbol/ObjectFile.cpp | 1 +
lldb/source/Target/Target.cpp | 72 +++++++++++++++++++
.../data-formatter/embedded-summary/Makefile | 2 +
.../TestEmbeddedTypeSummary.py | 12 ++++
.../data-formatter/embedded-summary/main.c | 22 ++++++
10 files changed, 119 insertions(+)
create mode 100644 lldb/test/API/functionalities/data-formatter/embedded-summary/Makefile
create mode 100644 lldb/test/API/functionalities/data-formatter/embedded-summary/TestEmbeddedTypeSummary.py
create mode 100644 lldb/test/API/functionalities/data-formatter/embedded-summary/main.c
diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h
index 938f6e3abe8f2a..1ca4aa62218c09 100644
--- a/lldb/include/lldb/lldb-enumerations.h
+++ b/lldb/include/lldb/lldb-enumerations.h
@@ -761,6 +761,7 @@ enum SectionType {
eSectionTypeDWARFDebugLocListsDwo,
eSectionTypeDWARFDebugTuIndex,
eSectionTypeCTF,
+ eSectionTypeLLDBTypeSummaries,
eSectionTypeSwiftModules,
};
diff --git a/lldb/source/Core/Section.cpp b/lldb/source/Core/Section.cpp
index 0763e88d4608f4..ee01b4ce06ca1e 100644
--- a/lldb/source/Core/Section.cpp
+++ b/lldb/source/Core/Section.cpp
@@ -147,6 +147,8 @@ const char *Section::GetTypeAsCString() const {
return "dwarf-gnu-debugaltlink";
case eSectionTypeCTF:
return "ctf";
+ case eSectionTypeLLDBTypeSummaries:
+ return "lldb-type-summaries";
case eSectionTypeOther:
return "regular";
case eSectionTypeSwiftModules:
@@ -457,6 +459,7 @@ bool Section::ContainsOnlyDebugInfo() const {
case eSectionTypeDWARFAppleObjC:
case eSectionTypeDWARFGNUDebugAltLink:
case eSectionTypeCTF:
+ case eSectionTypeLLDBTypeSummaries:
case eSectionTypeSwiftModules:
return true;
}
diff --git a/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp b/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp
index 10d09662c0a47a..ad4a84ef02bf72 100644
--- a/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp
+++ b/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp
@@ -1678,6 +1678,7 @@ static SectionType GetSectionTypeFromName(llvm::StringRef Name) {
.Case(".gnu_debugaltlink", eSectionTypeDWARFGNUDebugAltLink)
.Case(".gosymtab", eSectionTypeGoSymtab)
.Case(".text", eSectionTypeCode)
+ .Case(".lldbsummaries", lldb::eSectionTypeLLDBTypeSummaries)
.Case(".swift_ast", eSectionTypeSwiftModules)
.Default(eSectionTypeOther);
}
diff --git a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
index b542e237f023d4..d6bec5d84aea19 100644
--- a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
+++ b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
@@ -1209,6 +1209,7 @@ AddressClass ObjectFileMachO::GetAddressClass(lldb::addr_t file_addr) {
case eSectionTypeDWARFAppleObjC:
case eSectionTypeDWARFGNUDebugAltLink:
case eSectionTypeCTF:
+ case eSectionTypeLLDBTypeSummaries:
case eSectionTypeSwiftModules:
return AddressClass::eDebug;
@@ -1484,6 +1485,7 @@ static lldb::SectionType GetSectionType(uint32_t flags,
static ConstString g_sect_name_data("__data");
static ConstString g_sect_name_go_symtab("__gosymtab");
static ConstString g_sect_name_ctf("__ctf");
+ static ConstString g_sect_name_lldb_summaries("__lldbsummaries");
static ConstString g_sect_name_swift_ast("__swift_ast");
if (section_name == g_sect_name_dwarf_debug_abbrev)
@@ -1564,6 +1566,8 @@ static lldb::SectionType GetSectionType(uint32_t flags,
return eSectionTypeGoSymtab;
if (section_name == g_sect_name_ctf)
return eSectionTypeCTF;
+ if (section_name == g_sect_name_lldb_summaries)
+ return lldb::eSectionTypeLLDBTypeSummaries;
if (section_name == g_sect_name_swift_ast)
return eSectionTypeSwiftModules;
if (section_name == g_sect_name_objc_data ||
diff --git a/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp b/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp
index 8d9c919bc9b101..bb712da7f6d67d 100644
--- a/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp
+++ b/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp
@@ -1010,6 +1010,7 @@ SectionType ObjectFilePECOFF::GetSectionType(llvm::StringRef sect_name,
// .eh_frame can be truncated to 8 chars.
.Cases(".eh_frame", ".eh_fram", eSectionTypeEHFrame)
.Case(".gosymtab", eSectionTypeGoSymtab)
+ .Case(".lldbsummaries", lldb::eSectionTypeLLDBTypeSummaries)
.Case("swiftast", eSectionTypeSwiftModules)
.Default(eSectionTypeInvalid);
if (section_type != eSectionTypeInvalid)
diff --git a/lldb/source/Symbol/ObjectFile.cpp b/lldb/source/Symbol/ObjectFile.cpp
index 35317d209de1f9..3100e6b813b631 100644
--- a/lldb/source/Symbol/ObjectFile.cpp
+++ b/lldb/source/Symbol/ObjectFile.cpp
@@ -366,6 +366,7 @@ AddressClass ObjectFile::GetAddressClass(addr_t file_addr) {
case eSectionTypeDWARFAppleObjC:
case eSectionTypeDWARFGNUDebugAltLink:
case eSectionTypeCTF:
+ case eSectionTypeLLDBTypeSummaries:
case eSectionTypeSwiftModules:
return AddressClass::eDebug;
case eSectionTypeEHFrame:
diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index 04395e37f0425d..37670b108d9631 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -26,6 +26,7 @@
#include "lldb/Core/StructuredDataImpl.h"
#include "lldb/Core/ValueObject.h"
#include "lldb/Core/ValueObjectConstResult.h"
+#include "lldb/DataFormatters/DataVisualization.h"
#include "lldb/Expression/DiagnosticManager.h"
#include "lldb/Expression/ExpressionVariable.h"
#include "lldb/Expression/REPL.h"
@@ -1537,6 +1538,76 @@ static void LoadScriptingResourceForModule(const ModuleSP &module_sp,
feedback_stream.GetData());
}
+// Load type summaries embedded in the binary. These are type summaries provided
+// by the authors of the code.
+static void LoadTypeSummariesForModule(ModuleSP module_sp) {
+ auto *sections = module_sp->GetSectionList();
+ if (!sections)
+ return;
+
+ auto summaries_sp =
+ sections->FindSectionByType(eSectionTypeLLDBTypeSummaries, true);
+ if (!summaries_sp)
+ return;
+
+ Log *log = GetLog(LLDBLog::DataFormatters);
+ const char *module_name = module_sp->GetObjectName().GetCString();
+
+ TypeCategoryImplSP category;
+ DataVisualization::Categories::GetCategory(ConstString("default"), category);
+
+ // The type summary record is serialized as follows.
+ //
+ // Each record contains, in order:
+ // * Version number of the record format
+ // * The remaining size of the record
+ // * The size of the type identifier
+ // * The type identifier, either a type name, or a regex
+ // * The size of the summary string
+ // * The summary string
+ //
+ // Integers are encoded using ULEB.
+ //
+ // Strings are encoded with first a length (ULEB), then the string contents,
+ // and lastly a null terminator. The length includes the null.
+
+ DataExtractor extractor;
+ auto section_size = summaries_sp->GetSectionData(extractor);
+ lldb::offset_t offset = 0;
+ while (offset < section_size) {
+ uint64_t version = extractor.GetULEB128(&offset);
+ uint64_t record_size = extractor.GetULEB128(&offset);
+ if (version == 1) {
+ uint64_t type_size = extractor.GetULEB128(&offset);
+ llvm::StringRef type_name = extractor.GetCStr(&offset, type_size);
+ uint64_t summary_size = extractor.GetULEB128(&offset);
+ llvm::StringRef summary_string = extractor.GetCStr(&offset, summary_size);
+ if (!type_name.empty() && !summary_string.empty()) {
+ TypeSummaryImpl::Flags flags;
+ auto summary_sp =
+ std::make_shared<StringSummaryFormat>(flags, summary_string.data());
+ FormatterMatchType match_type = eFormatterMatchExact;
+ if (summary_string.front() == '^' && summary_string.back() == '$')
+ match_type = eFormatterMatchRegex;
+ category->AddTypeSummary(type_name, match_type, summary_sp);
+ LLDB_LOGF(log, "Loaded embedded type summary for '%s' from %s.",
+ type_name.data(), module_name);
+ } else {
+ if (type_name.empty())
+ LLDB_LOGF(log, "Missing string(s) in embedded type summary in %s.",
+ module_name);
+ }
+ } else {
+ // Skip unsupported record.
+ offset += record_size;
+ LLDB_LOGF(
+ log,
+ "Skipping unsupported embedded type summary of version %llu in %s.",
+ version, module_name);
+ }
+ }
+}
+
void Target::ClearModules(bool delete_locations) {
ModulesDidUnload(m_images, delete_locations);
m_section_load_history.Clear();
@@ -1775,6 +1846,7 @@ void Target::ModulesDidLoad(ModuleList &module_list) {
for (size_t idx = 0; idx < num_images; ++idx) {
ModuleSP module_sp(module_list.GetModuleAtIndex(idx));
LoadScriptingResourceForModule(module_sp, this);
+ LoadTypeSummariesForModule(module_sp);
}
m_breakpoint_list.UpdateBreakpoints(module_list, true, false);
m_internal_breakpoint_list.UpdateBreakpoints(module_list, true, false);
diff --git a/lldb/test/API/functionalities/data-formatter/embedded-summary/Makefile b/lldb/test/API/functionalities/data-formatter/embedded-summary/Makefile
new file mode 100644
index 00000000000000..c9319d6e6888a4
--- /dev/null
+++ b/lldb/test/API/functionalities/data-formatter/embedded-summary/Makefile
@@ -0,0 +1,2 @@
+C_SOURCES := main.c
+include Makefile.rules
diff --git a/lldb/test/API/functionalities/data-formatter/embedded-summary/TestEmbeddedTypeSummary.py b/lldb/test/API/functionalities/data-formatter/embedded-summary/TestEmbeddedTypeSummary.py
new file mode 100644
index 00000000000000..b8ce7d9f76eb9e
--- /dev/null
+++ b/lldb/test/API/functionalities/data-formatter/embedded-summary/TestEmbeddedTypeSummary.py
@@ -0,0 +1,12 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestCase(TestBase):
+ @skipUnlessDarwin
+ def test(self):
+ self.build()
+ lldbutil.run_to_source_breakpoint(self, "break here", lldb.SBFileSpec("main.c"))
+ self.expect("v player", substrs=['"Dirk" (41)'])
diff --git a/lldb/test/API/functionalities/data-formatter/embedded-summary/main.c b/lldb/test/API/functionalities/data-formatter/embedded-summary/main.c
new file mode 100644
index 00000000000000..9ddd64246f726c
--- /dev/null
+++ b/lldb/test/API/functionalities/data-formatter/embedded-summary/main.c
@@ -0,0 +1,22 @@
+#include <stdio.h>
+
+struct Player {
+ char *name;
+ int number;
+};
+
+__attribute__((used, section("__DATA_CONST,__lldbsummaries"))) unsigned char
+ _Player_type_summary[] = "\x01" // version
+ "\x25" // record size
+ "\x07" // type name size
+ "Player\0" // type name
+ "\x1c" // summary string size
+ "${var.name} (${var.number})"; // summary string
+
+int main() {
+ struct Player player;
+ player.name = "Dirk";
+ player.number = 41;
+ puts("break here");
+ return 0;
+}
>From 49660c301fee8c7177b400608bf0b91abd5ea93e Mon Sep 17 00:00:00 2001
From: Adrian Prantl <aprantl at apple.com>
Date: Mon, 28 Oct 2024 17:18:34 -0700
Subject: [PATCH 2/2] [lldb] Implement a formatter bytecode interpreter in C++
Compared to the python version, this also does type checking and error
handling, so it's slightly longer, however, it's still comfortably
under 500 lines.
---
.../include/lldb/DataFormatters/TypeSummary.h | 22 +-
lldb/include/lldb/lldb-enumerations.h | 1 +
lldb/source/Core/Section.cpp | 1 +
lldb/source/DataFormatters/CMakeLists.txt | 1 +
.../DataFormatters/FormatterBytecode.cpp | 576 ++++++++++++++++++
.../DataFormatters/FormatterBytecode.def | 101 +++
.../source/DataFormatters/FormatterBytecode.h | 63 ++
lldb/source/DataFormatters/TypeSummary.cpp | 74 ++-
.../Plugins/ObjectFile/ELF/ObjectFileELF.cpp | 1 +
.../ObjectFile/Mach-O/ObjectFileMachO.cpp | 4 +
.../ObjectFile/PECOFF/ObjectFilePECOFF.cpp | 1 +
lldb/source/Target/Target.cpp | 163 +++--
.../data-formatter/bytecode-summary/Makefile | 2 +
.../bytecode-summary/TestBytecodeSummary.py | 14 +
.../data-formatter/bytecode-summary/main.cpp | 36 ++
15 files changed, 1014 insertions(+), 46 deletions(-)
create mode 100644 lldb/source/DataFormatters/FormatterBytecode.cpp
create mode 100644 lldb/source/DataFormatters/FormatterBytecode.def
create mode 100644 lldb/source/DataFormatters/FormatterBytecode.h
create mode 100644 lldb/test/API/functionalities/data-formatter/bytecode-summary/Makefile
create mode 100644 lldb/test/API/functionalities/data-formatter/bytecode-summary/TestBytecodeSummary.py
create mode 100644 lldb/test/API/functionalities/data-formatter/bytecode-summary/main.cpp
diff --git a/lldb/include/lldb/DataFormatters/TypeSummary.h b/lldb/include/lldb/DataFormatters/TypeSummary.h
index 382824aa2813da..0d8e46fa0b1598 100644
--- a/lldb/include/lldb/DataFormatters/TypeSummary.h
+++ b/lldb/include/lldb/DataFormatters/TypeSummary.h
@@ -22,6 +22,10 @@
#include "lldb/Utility/Status.h"
#include "lldb/Utility/StructuredData.h"
+namespace llvm {
+class MemoryBuffer;
+}
+
namespace lldb_private {
class TypeSummaryOptions {
public:
@@ -44,7 +48,7 @@ class TypeSummaryOptions {
class TypeSummaryImpl {
public:
- enum class Kind { eSummaryString, eScript, eCallback, eInternal };
+ enum class Kind { eSummaryString, eScript, eBytecode, eCallback, eInternal };
virtual ~TypeSummaryImpl() = default;
@@ -409,6 +413,22 @@ struct ScriptSummaryFormat : public TypeSummaryImpl {
ScriptSummaryFormat(const ScriptSummaryFormat &) = delete;
const ScriptSummaryFormat &operator=(const ScriptSummaryFormat &) = delete;
};
+
+/// A summary formatter that is defined in LLDB formmater bytecode.
+class BytecodeSummaryFormat : public TypeSummaryImpl {
+ std::unique_ptr<llvm::MemoryBuffer> m_bytecode;
+public:
+ BytecodeSummaryFormat(const TypeSummaryImpl::Flags &flags,
+ std::unique_ptr<llvm::MemoryBuffer> bytecode);
+ bool FormatObject(ValueObject *valobj, std::string &dest,
+ const TypeSummaryOptions &options) override;
+ std::string GetDescription() override;
+ std::string GetName() override;
+ static bool classof(const TypeSummaryImpl *S) {
+ return S->GetKind() == Kind::eBytecode;
+ }
+};
+
} // namespace lldb_private
#endif // LLDB_DATAFORMATTERS_TYPESUMMARY_H
diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h
index 1ca4aa62218c09..b2f0943d5a9260 100644
--- a/lldb/include/lldb/lldb-enumerations.h
+++ b/lldb/include/lldb/lldb-enumerations.h
@@ -762,6 +762,7 @@ enum SectionType {
eSectionTypeDWARFDebugTuIndex,
eSectionTypeCTF,
eSectionTypeLLDBTypeSummaries,
+ eSectionTypeLLDBFormatters,
eSectionTypeSwiftModules,
};
diff --git a/lldb/source/Core/Section.cpp b/lldb/source/Core/Section.cpp
index ee01b4ce06ca1e..3b5ca2c6785ef0 100644
--- a/lldb/source/Core/Section.cpp
+++ b/lldb/source/Core/Section.cpp
@@ -460,6 +460,7 @@ bool Section::ContainsOnlyDebugInfo() const {
case eSectionTypeDWARFGNUDebugAltLink:
case eSectionTypeCTF:
case eSectionTypeLLDBTypeSummaries:
+ case eSectionTypeLLDBFormatters:
case eSectionTypeSwiftModules:
return true;
}
diff --git a/lldb/source/DataFormatters/CMakeLists.txt b/lldb/source/DataFormatters/CMakeLists.txt
index 7f48a2785c73f5..17da138227d4f1 100644
--- a/lldb/source/DataFormatters/CMakeLists.txt
+++ b/lldb/source/DataFormatters/CMakeLists.txt
@@ -5,6 +5,7 @@ add_lldb_library(lldbDataFormatters NO_PLUGIN_DEPENDENCIES
FormatCache.cpp
FormatClasses.cpp
FormatManager.cpp
+ FormatterBytecode.cpp
FormattersHelpers.cpp
LanguageCategory.cpp
StringPrinter.cpp
diff --git a/lldb/source/DataFormatters/FormatterBytecode.cpp b/lldb/source/DataFormatters/FormatterBytecode.cpp
new file mode 100644
index 00000000000000..7e8bfd3a370ce7
--- /dev/null
+++ b/lldb/source/DataFormatters/FormatterBytecode.cpp
@@ -0,0 +1,576 @@
+//===-- FormatterBytecode.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 "FormatterBytecode.h"
+#include "lldb/Core/ValueObject.h"
+#include "lldb/Utility/LLDBLog.h"
+#include "llvm/Support/DataExtractor.h"
+#include "llvm/Support/Format.h"
+#include "llvm/Support/FormatVariadicDetails.h"
+#include "llvm/Support/FormatProviders.h"
+#include "llvm/ADT/StringExtras.h"
+
+using namespace lldb;
+namespace lldb_private {
+
+std::string toString(FormatterBytecode::OpCodes op) {
+ switch (op) {
+#define DEFINE_OPCODE(OP, MNEMONIC, NAME) \
+ case OP: { \
+ const char *s = MNEMONIC; \
+ return s ? s : #NAME; \
+ }
+#include "FormatterBytecode.def"
+#undef DEFINE_SIGNATURE
+ }
+ return llvm::utostr(op);
+}
+
+std::string toString(FormatterBytecode::Selectors sel) {
+ switch (sel) {
+#define DEFINE_SELECTOR(ID, NAME) \
+ case ID: \
+ return "@" #NAME;
+#include "FormatterBytecode.def"
+#undef DEFINE_SIGNATURE
+ }
+ return "@"+llvm::utostr(sel);
+}
+
+std::string toString(FormatterBytecode::Signatures sig) {
+ switch (sig) {
+#define DEFINE_SIGNATURE(ID, NAME) \
+ case ID: \
+ return "@" #NAME;
+#include "FormatterBytecode.def"
+#undef DEFINE_SIGNATURE
+ }
+ return llvm::utostr(sig);
+}
+
+std::string toString(const FormatterBytecode::DataStack &data) {
+ std::string s;
+ llvm::raw_string_ostream os(s);
+ os << "[ ";
+ for (auto &d : data) {
+ if (auto s = std::get_if<std::string>(&d))
+ os << '"' << *s << '"';
+ else if (auto u = std::get_if<uint64_t>(&d))
+ os << *u << 'u';
+ else if (auto i = std::get_if<int64_t>(&d))
+ os << *i;
+ else if (auto valobj = std::get_if<ValueObjectSP>(&d)) {
+ if (!valobj->get())
+ os << "null";
+ else
+ os << "object(" << valobj->get()->GetValueAsCString() << ')';
+ } else if (auto type = std::get_if<CompilerType>(&d)) {
+ os << '(' << type->GetTypeName(true) << ')';
+ } else if (auto sel = std::get_if<FormatterBytecode::Selectors>(&d)) {
+ os << toString(*sel);
+ }
+ os << ' ';
+ }
+ os << ']';
+ return s;
+}
+
+namespace FormatterBytecode {
+
+/// Implement the @format function.
+static llvm::Error FormatImpl(DataStack &data) {
+ auto fmt = data.Pop<std::string>();
+ auto replacements =
+ llvm::formatv_object_base::parseFormatString(fmt, 0, false);
+ std::string s;
+ llvm::raw_string_ostream os(s);
+ unsigned num_args = 0;
+ for (const auto &r : replacements)
+ if (r.Type == llvm::ReplacementType::Format)
+ num_args = std::max(num_args, r.Index);
+
+ if (data.size() < num_args)
+ return llvm::createStringError("not enough arguments");
+
+ for (const auto &r : replacements) {
+ if (r.Type == llvm::ReplacementType::Literal) {
+ os << r.Spec;
+ continue;
+ }
+ using namespace llvm::support::detail;
+ auto arg = data[data.size() - num_args + r.Index];
+ auto format = [&](format_adapter &&adapter) {
+ llvm::FmtAlign Align(adapter, r.Where, r.Width, r.Pad);
+ Align.format(os, r.Options);
+ };
+
+ if (auto s = std::get_if<std::string>(&arg))
+ format(build_format_adapter(s));
+ else if (auto u = std::get_if<uint64_t>(&arg))
+ format(build_format_adapter(u));
+ else if (auto i = std::get_if<int64_t>(&arg))
+ format(build_format_adapter(i));
+ else if (auto valobj = std::get_if<ValueObjectSP>(&arg)) {
+ if (!valobj->get())
+ format(build_format_adapter("null object"));
+ else
+ format(build_format_adapter(valobj->get()->GetValueAsCString()));
+ } else if (auto type = std::get_if<CompilerType>(&arg))
+ format(build_format_adapter(type->GetDisplayTypeName()));
+ else if (auto sel = std::get_if<FormatterBytecode::Selectors>(&arg))
+ format(build_format_adapter(toString(*sel)));
+ }
+ data.Push(s);
+ return llvm::Error::success();
+}
+
+static llvm::Error TypeCheck(llvm::ArrayRef<DataStackElement> data,
+ DataType type) {
+ if (data.size() < 1)
+ return llvm::createStringError("not enough elements on data stack");
+
+ auto &elem = data.back();
+ switch (type) {
+ case Any:
+ break;
+ case String:
+ if (!std::holds_alternative<std::string>(elem))
+ return llvm::createStringError("expected String");
+ break;
+ case UInt:
+ if (!std::holds_alternative<uint64_t>(elem))
+ return llvm::createStringError("expected UInt");
+ break;
+ case Int:
+ if (!std::holds_alternative<int64_t>(elem))
+ return llvm::createStringError("expected Int");
+ break;
+ case Object:
+ if (!std::holds_alternative<ValueObjectSP>(elem))
+ return llvm::createStringError("expected Object");
+ break;
+ case Type:
+ if (!std::holds_alternative<CompilerType>(elem))
+ return llvm::createStringError("expected Type");
+ break;
+ case Selector:
+ if (!std::holds_alternative<Selectors>(elem))
+ return llvm::createStringError("expected Selector");
+ break;
+ }
+ return llvm::Error::success();
+}
+
+static llvm::Error TypeCheck(llvm::ArrayRef<DataStackElement> data,
+ DataType type1, DataType type2) {
+ if (auto error = TypeCheck(data, type2))
+ return error;
+ return TypeCheck(data.drop_back(), type1);
+}
+
+static llvm::Error TypeCheck(llvm::ArrayRef<DataStackElement> data,
+ DataType type1, DataType type2, DataType type3) {
+ if (auto error = TypeCheck(data, type3))
+ return error;
+ return TypeCheck(data.drop_back(1), type2, type1);
+}
+
+llvm::Error Interpret(std::vector<ControlStackElement> &control,
+ DataStack &data, Selectors sel) {
+ if (control.empty())
+ return llvm::Error::success();
+ // Since the only data types are single endian and ULEBs, the
+ // endianness should not matter.
+ llvm::DataExtractor cur_block(control.back(), true, 64);
+ llvm::DataExtractor::Cursor pc(0);
+
+ while (!control.empty()) {
+ /// Activate the top most block from the control stack.
+ auto activate_block = [&]() {
+ // Save the return address.
+ if (control.size() > 1)
+ control[control.size() - 2] = cur_block.getData().drop_front(pc.tell());
+ cur_block = llvm::DataExtractor(control.back(), true, 64);
+ if (pc)
+ pc = llvm::DataExtractor::Cursor(0);
+ };
+
+ /// Fetch the next byte in the instruction stream.
+ auto next_byte = [&]() -> uint8_t {
+ // At the end of the current block?
+ while (pc.tell() >= cur_block.size() && !control.empty()) {
+ if (control.size() == 1) {
+ control.pop_back();
+ return 0;
+ }
+ control.pop_back();
+ activate_block();
+ }
+
+ // Fetch the next instruction.
+ return cur_block.getU8(pc);
+ };
+
+ // Fetch the next opcode.
+ OpCodes opcode = (OpCodes)next_byte();
+ if (control.empty() || !pc)
+ return pc.takeError();
+
+ LLDB_LOGV(GetLog(LLDBLog::DataFormatters),
+ "[eval {0}] opcode={1}, control={2}, data={3}", toString(sel),
+ toString(opcode), control.size(), toString(data));
+
+
+ // Various shorthands to improve the readability of error handling.
+#define TYPE_CHECK(...) \
+ if (auto error = TypeCheck(data, __VA_ARGS__)) \
+ return error;
+
+ auto error = [&](const char *msg) {
+ return llvm::createStringError("{0} (opcode={1})", msg, toString(opcode).c_str());
+ };
+
+ switch (opcode) {
+ // Data stack manipulation.
+ case op_dup:
+ TYPE_CHECK(Any);
+ data.Push(data.back());
+ break;
+ case op_drop:
+ TYPE_CHECK(Any);
+ data.pop_back();
+ break;
+ case op_pick: {
+ TYPE_CHECK(UInt);
+ uint64_t idx = data.Pop<uint64_t>();
+ if (idx >= data.size())
+ return error("index out of bounds");
+ data.Push(data[idx]);
+ break;
+ }
+ case op_over:
+ TYPE_CHECK(Any, Any);
+ data.Push(data[data.size() - 2]);
+ break;
+ case op_swap: {
+ TYPE_CHECK(Any, Any);
+ auto x = data.PopAny();
+ auto y = data.PopAny();
+ data.Push(x);
+ data.Push(y);
+ break;
+ }
+ case op_rot: {
+ TYPE_CHECK(Any, Any, Any);
+ auto z = data.PopAny();
+ auto y = data.PopAny();
+ auto x = data.PopAny();
+ data.Push(z);
+ data.Push(x);
+ data.Push(y);
+ break;
+ }
+ // Control stack manipulation.
+ case op_begin: {
+ uint64_t length = cur_block.getULEB128(pc);
+ if (!pc)
+ return pc.takeError();
+ llvm::StringRef block = cur_block.getBytes(pc, length);
+ if (!pc)
+ return pc.takeError();
+ control.push_back(block);
+ break;
+ }
+ case op_if:
+ TYPE_CHECK(UInt);
+ if (data.Pop<uint64_t>() != 0) {
+ if (!cur_block.size())
+ return error("empty control stack");
+ activate_block();
+ }
+ break;
+ case op_ifelse:
+ TYPE_CHECK(UInt);
+ if (cur_block.size() < 2)
+ return error("empty control stack");
+ if (data.Pop<uint64_t>() == 0)
+ control[control.size()-2] = control.back();
+ control.pop_back();
+ activate_block();
+ break;
+ // Literals.
+ case op_lit_uint:
+ data.Push(cur_block.getULEB128(pc));
+ break;
+ case op_lit_int:
+ data.Push(cur_block.getSLEB128(pc));
+ break;
+ case op_lit_selector:
+ data.Push(Selectors(cur_block.getU8(pc)));
+ break;
+ case op_lit_string: {
+ uint64_t length = cur_block.getULEB128(pc);
+ llvm::StringRef bytes = cur_block.getBytes(pc, length);
+ data.Push(bytes.str());
+ break;
+ }
+ case op_as_uint: {
+ TYPE_CHECK(Int);
+ uint64_t casted;
+ int64_t val = data.Pop<int64_t>();
+ memcpy(&casted, &val, sizeof(val));
+ data.Push(casted);
+ break;
+ }
+ case op_as_int: {
+ TYPE_CHECK(UInt);
+ int64_t casted;
+ uint64_t val = data.Pop<uint64_t>();
+ memcpy(&casted, &val, sizeof(val));
+ data.Push(casted);
+ break;
+ }
+ case op_is_null: {
+ TYPE_CHECK(Object);
+ data.Push(data.Pop<ValueObjectSP>() ? 0ULL : 1ULL);
+ break;
+ }
+ // Arithmetic, logic, etc.
+#define BINOP_IMPL(OP, CHECK_ZERO) \
+ { \
+ TYPE_CHECK(Any, Any); \
+ auto y = data.PopAny(); \
+ if (std::holds_alternative<uint64_t>(y)) { \
+ if (CHECK_ZERO && !std::get<uint64_t>(y)) \
+ return error(#OP " by zero"); \
+ TYPE_CHECK(UInt); \
+ data.Push((uint64_t)(data.Pop<uint64_t>() OP std::get<uint64_t>(y))); \
+ } else if (std::holds_alternative<int64_t>(y)) { \
+ if (CHECK_ZERO && !std::get<int64_t>(y)) \
+ return error(#OP " by zero"); \
+ TYPE_CHECK(Int); \
+ data.Push((int64_t)(data.Pop<int64_t>() OP std::get<int64_t>(y))); \
+ } else \
+ return error("unsupported data types"); \
+ }
+#define BINOP(OP) BINOP_IMPL(OP, false)
+#define BINOP_CHECKZERO(OP) BINOP_IMPL(OP, true)
+ case op_plus:
+ BINOP(+);
+ break;
+ case op_minus:
+ BINOP(-);
+ break;
+ case op_mul:
+ BINOP(*);
+ break;
+ case op_div:
+ BINOP_CHECKZERO(/);
+ break;
+ case op_mod:
+ BINOP_CHECKZERO(%);
+ break;
+ case op_shl:
+#define SHIFTOP(OP) \
+ { \
+ TYPE_CHECK(Any, Any); \
+ if (std::holds_alternative<uint64_t>(data.back())) { \
+ uint64_t y = data.Pop<uint64_t>(); \
+ TYPE_CHECK(UInt); \
+ uint64_t x = data.Pop<uint64_t>(); \
+ if (y > 64) \
+ return error("shift out of bounds"); \
+ data.Push(x OP y); \
+ } else if (std::holds_alternative<int64_t>(data.back())) { \
+ uint64_t y = data.Pop<int64_t>(); \
+ TYPE_CHECK(Int); \
+ uint64_t x = data.Pop<int64_t>(); \
+ if (y > 64) \
+ return error("shift out of bounds"); \
+ if (y < 0) \
+ return error("shift out of bounds"); \
+ data.Push(x OP y); \
+ } else \
+ return error("unsupported data types"); \
+ }
+ SHIFTOP(<<);
+ break;
+ case op_shr:
+ SHIFTOP(<<);
+ break;
+ case op_and:
+ BINOP(&);
+ break;
+ case op_or:
+ BINOP(|);
+ break;
+ case op_xor:
+ BINOP(^);
+ break;
+ case op_not:
+ TYPE_CHECK(UInt);
+ data.Push(~data.Pop<uint64_t>());
+ break;
+ case op_eq:
+ BINOP(==);
+ break;
+ case op_neq:
+ BINOP(!=);
+ break;
+ case op_lt:
+ BINOP(<);
+ break;
+ case op_gt:
+ BINOP(>);
+ break;
+ case op_le:
+ BINOP(<=);
+ break;
+ case op_ge:
+ BINOP(>=);
+ break;
+ case op_call: {
+ TYPE_CHECK(Selector);
+ Selectors sel = data.Pop<Selectors>();
+
+ // Shorthand to improve readability.
+#define POP_VALOBJ(VALOBJ) \
+ auto VALOBJ = data.Pop<ValueObjectSP>(); \
+ if (!VALOBJ) \
+ return error("null object");
+
+ auto sel_error = [&](const char *msg) {
+ return llvm::createStringError("{0} (opcode={1}, selector={2})", msg,
+ toString(opcode).c_str(),
+ toString(sel).c_str());
+ };
+
+ switch (sel) {
+ case sel_summary: {
+ TYPE_CHECK(Object);
+ POP_VALOBJ(valobj);
+ const char *summary = valobj->GetSummaryAsCString();
+ data.Push(summary ? std::string(valobj->GetSummaryAsCString())
+ : std::string());
+ break;
+ }
+ case sel_get_num_children: {
+ TYPE_CHECK(Object);
+ POP_VALOBJ(valobj);
+ auto result = valobj->GetNumChildren();
+ if (!result)
+ return result.takeError();
+ data.Push((uint64_t)*result);
+ break;
+ }
+ case sel_get_child_at_index: {
+ TYPE_CHECK(Object, UInt);
+ auto index = data.Pop<uint64_t>();
+ POP_VALOBJ(valobj);
+ data.Push(valobj->GetChildAtIndex(index));
+ break;
+ }
+ case sel_get_child_with_name: {
+ TYPE_CHECK(Object, String);
+ auto name = data.Pop<std::string>();
+ POP_VALOBJ(valobj);
+ data.Push(valobj->GetChildMemberWithName(name));
+ break;
+ }
+ case sel_get_child_index: {
+ TYPE_CHECK(Object, String);
+ auto name = data.Pop<std::string>();
+ POP_VALOBJ(valobj);
+ data.Push(valobj->GetIndexOfChildWithName(name));
+ break;
+ }
+ case sel_get_type: {
+ TYPE_CHECK(Object);
+ POP_VALOBJ(valobj);
+ // FIXME: do we need to control dynamic type resolution?
+ data.Push(valobj->GetTypeImpl().GetCompilerType(false));
+ break;
+ }
+ case sel_get_template_argument_type: {
+ TYPE_CHECK(Type, UInt);
+ auto index = data.Pop<uint64_t>();
+ auto type = data.Pop<CompilerType>();
+ // FIXME: There is more code in SBType::GetTemplateArgumentType().
+ data.Push(type.GetTypeTemplateArgument(index, true));
+ break;
+ }
+ case sel_get_value: {
+ TYPE_CHECK(Object);
+ POP_VALOBJ(valobj);
+ data.Push(std::string(valobj->GetValueAsCString()));
+ break;
+ }
+ case sel_get_value_as_unsigned: {
+ TYPE_CHECK(Object);
+ POP_VALOBJ(valobj);
+ bool success;
+ uint64_t val = valobj->GetValueAsUnsigned(0, &success);
+ data.Push(val);
+ if (!success)
+ return sel_error("failed to get value");
+ break;
+ }
+ case sel_get_value_as_signed: {
+ TYPE_CHECK(Object);
+ POP_VALOBJ(valobj);
+ bool success;
+ int64_t val = valobj->GetValueAsSigned(0, &success);
+ data.Push(val);
+ if (!success)
+ return sel_error("failed to get value");
+ break;
+ }
+ case sel_get_value_as_address: {
+ TYPE_CHECK(Object);
+ POP_VALOBJ(valobj);
+ bool success;
+ uint64_t addr = valobj->GetValueAsUnsigned(0, &success);
+ if (!success)
+ return sel_error("failed to get value");
+ if (auto process_sp = valobj->GetProcessSP())
+ addr = process_sp->FixDataAddress(addr);
+ data.Push(addr);
+ break;
+ }
+ case sel_cast: {
+ TYPE_CHECK(Object, Type);
+ auto type = data.Pop<CompilerType>();
+ POP_VALOBJ(valobj);
+ data.Push(valobj->Cast(type));
+ break;
+ }
+ case sel_strlen: {
+ TYPE_CHECK(String);
+ data.Push(data.Pop<std::string>().size());
+ break;
+ }
+ case sel_fmt: {
+ TYPE_CHECK(String);
+ if (auto error = FormatImpl(data))
+ return error;
+ break;
+ }
+ default:
+ return sel_error("selector not implemented");
+ }
+ break;
+ }
+ default:
+ return error("opcode not implemented");
+ }
+ }
+ return pc.takeError();
+}
+} // namespace FormatterBytecode
+
+} // namespace lldb_private
diff --git a/lldb/source/DataFormatters/FormatterBytecode.def b/lldb/source/DataFormatters/FormatterBytecode.def
new file mode 100644
index 00000000000000..c6645631fa0065
--- /dev/null
+++ b/lldb/source/DataFormatters/FormatterBytecode.def
@@ -0,0 +1,101 @@
+//===-- FormatterBytecode.def -----------------------------------*- 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 DEFINE_OPCODE
+#define DEFINE_OPCODE(OP, MNEMONIC, NAME)
+#endif
+#ifndef DEFINE_SELECTOR
+#define DEFINE_SELECTOR(ID, NAME)
+#endif
+#ifndef DEFINE_SIGNATURE
+#define DEFINE_SIGNATURE(ID, NAME)
+#endif
+
+// Opcodes.
+DEFINE_OPCODE(0x01, "dup", dup)
+DEFINE_OPCODE(0x02, "drop", drop)
+DEFINE_OPCODE(0x03, "pick", pick)
+DEFINE_OPCODE(0x04, "over", over)
+DEFINE_OPCODE(0x05, "swap", swap)
+DEFINE_OPCODE(0x06, "rot", rot)
+
+DEFINE_OPCODE(0x10, "{", begin)
+DEFINE_OPCODE(0x11, "if", if)
+DEFINE_OPCODE(0x12, "ifelse", ifelse)
+
+DEFINE_OPCODE(0x20, nullptr, lit_uint)
+DEFINE_OPCODE(0x21, nullptr, lit_int)
+DEFINE_OPCODE(0x22, nullptr, lit_string)
+DEFINE_OPCODE(0x23, nullptr, lit_selector)
+
+DEFINE_OPCODE(0x2a, "as_int", as_int)
+DEFINE_OPCODE(0x2b, "as_uint", as_uint)
+DEFINE_OPCODE(0x2c, "is_null", is_null)
+
+DEFINE_OPCODE(0x30, "+", plus)
+DEFINE_OPCODE(0x31, "-", minus)
+DEFINE_OPCODE(0x32, "*", mul)
+DEFINE_OPCODE(0x33, "/", div)
+DEFINE_OPCODE(0x34, "%", mod)
+DEFINE_OPCODE(0x35, "<<", shl)
+DEFINE_OPCODE(0x36, ">>", shr)
+
+DEFINE_OPCODE(0x40, "&", and)
+DEFINE_OPCODE(0x41, "|", or)
+DEFINE_OPCODE(0x42, "^", xor)
+DEFINE_OPCODE(0x43, "~", not)
+
+DEFINE_OPCODE(0x50, "=", eq)
+DEFINE_OPCODE(0x51, "!=", neq)
+DEFINE_OPCODE(0x52, "<", lt)
+DEFINE_OPCODE(0x53, ">", gt)
+DEFINE_OPCODE(0x54, "=<", le)
+DEFINE_OPCODE(0x55, ">=", ge)
+
+DEFINE_OPCODE(0x60, "call", call)
+
+// Selectors.
+DEFINE_SELECTOR(0x00, summary)
+DEFINE_SELECTOR(0x01, type_summary)
+
+DEFINE_SELECTOR(0x10, get_num_children)
+DEFINE_SELECTOR(0x11, get_child_at_index)
+DEFINE_SELECTOR(0x12, get_child_with_name)
+DEFINE_SELECTOR(0x13, get_child_index)
+DEFINE_SELECTOR(0x15, get_type)
+DEFINE_SELECTOR(0x16, get_template_argument_type)
+DEFINE_SELECTOR(0x17, cast)
+
+DEFINE_SELECTOR(0x20, get_value)
+DEFINE_SELECTOR(0x21, get_value_as_unsigned)
+DEFINE_SELECTOR(0x22, get_value_as_signed)
+DEFINE_SELECTOR(0x23, get_value_as_address)
+
+DEFINE_SELECTOR(0x40, read_memory_byte)
+DEFINE_SELECTOR(0x41, read_memory_uint32)
+DEFINE_SELECTOR(0x42, read_memory_int32)
+DEFINE_SELECTOR(0x43, read_memory_unsigned)
+DEFINE_SELECTOR(0x44, read_memory_signed)
+DEFINE_SELECTOR(0x45, read_memory_address)
+DEFINE_SELECTOR(0x46, read_memory)
+
+DEFINE_SELECTOR(0x50, fmt)
+DEFINE_SELECTOR(0x51, sprintf)
+DEFINE_SELECTOR(0x52, strlen)
+
+// Formatter signatures.
+DEFINE_SIGNATURE(0, summary)
+DEFINE_SIGNATURE(1, init)
+DEFINE_SIGNATURE(2, get_num_children)
+DEFINE_SIGNATURE(3, get_child_index)
+DEFINE_SIGNATURE(4, get_child_at_index)
+DEFINE_SIGNATURE(5, get_value)
+
+#undef DEFINE_OPCODE
+#undef DEFINE_SELECTOR
+#undef DEFINE_SIGNATURE
diff --git a/lldb/source/DataFormatters/FormatterBytecode.h b/lldb/source/DataFormatters/FormatterBytecode.h
new file mode 100644
index 00000000000000..af8979a8311aa2
--- /dev/null
+++ b/lldb/source/DataFormatters/FormatterBytecode.h
@@ -0,0 +1,63 @@
+//===-- FormatterBytecode.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
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/DataFormatters/TypeSummary.h"
+#include "lldb/Symbol/CompilerType.h"
+
+namespace lldb_private {
+
+namespace FormatterBytecode {
+
+enum DataType : uint8_t { Any, String, Int, UInt, Object, Type, Selector };
+
+enum OpCodes : uint8_t {
+#define DEFINE_OPCODE(OP, MNEMONIC, NAME) op_##NAME = OP,
+#include "FormatterBytecode.def"
+#undef DEFINE_OPCODE
+};
+
+enum Selectors : uint8_t {
+#define DEFINE_SELECTOR(ID, NAME) sel_##NAME = ID,
+#include "FormatterBytecode.def"
+#undef DEFINE_SELECTOR
+};
+
+enum Signatures : uint8_t {
+#define DEFINE_SIGNATURE(ID, NAME) sig_##NAME = ID,
+#include "FormatterBytecode.def"
+#undef DEFINE_SIGNATURE
+};
+
+using ControlStackElement = llvm::StringRef;
+using DataStackElement =
+ std::variant<std::string, uint64_t, int64_t, lldb::ValueObjectSP,
+ CompilerType, Selectors>;
+struct DataStack : public std::vector<DataStackElement> {
+ DataStack(lldb::ValueObjectSP initial_value)
+ : std::vector<DataStackElement>({initial_value}) {}
+ void Push(DataStackElement el) { push_back(el); }
+ template <typename T> T Pop() {
+ T el = std::get<T>(back());
+ pop_back();
+ return el;
+ }
+ DataStackElement PopAny() {
+ DataStackElement el = back();
+ pop_back();
+ return el;
+ }
+};
+llvm::Error Interpret(std::vector<ControlStackElement> &control,
+ DataStack &data, Selectors sel);
+} // namespace FormatterBytecode
+
+std::string toString(FormatterBytecode::OpCodes op);
+std::string toString(FormatterBytecode::Selectors sel);
+std::string toString(FormatterBytecode::Signatures sig);
+
+} // namespace lldb_private
diff --git a/lldb/source/DataFormatters/TypeSummary.cpp b/lldb/source/DataFormatters/TypeSummary.cpp
index 339068e8cc6aa6..482834a755b54d 100644
--- a/lldb/source/DataFormatters/TypeSummary.cpp
+++ b/lldb/source/DataFormatters/TypeSummary.cpp
@@ -8,9 +8,7 @@
#include "lldb/DataFormatters/TypeSummary.h"
-
-
-
+#include "FormatterBytecode.h"
#include "lldb/lldb-enumerations.h"
#include "lldb/lldb-public.h"
@@ -58,6 +56,8 @@ std::string TypeSummaryImpl::GetSummaryKindName() {
return "python";
case Kind::eInternal:
return "c++";
+ case Kind::eBytecode:
+ return "bytecode";
}
}
@@ -230,3 +230,71 @@ std::string ScriptSummaryFormat::GetDescription() {
}
std::string ScriptSummaryFormat::GetName() { return m_script_formatter_name; }
+
+BytecodeSummaryFormat::BytecodeSummaryFormat(
+ const TypeSummaryImpl::Flags &flags,
+ std::unique_ptr<llvm::MemoryBuffer> bytecode)
+ : TypeSummaryImpl(Kind::eBytecode, flags), m_bytecode(std::move(bytecode)) {
+}
+
+bool BytecodeSummaryFormat::FormatObject(ValueObject *valobj, std::string &retval,
+ const TypeSummaryOptions &options) {
+ if (!valobj)
+ return false;
+
+ TargetSP target_sp(valobj->GetTargetSP());
+
+ if (!target_sp) {
+ retval.assign("error: no target");
+ return false;
+ }
+
+ std::vector<FormatterBytecode::ControlStackElement> control(
+ {m_bytecode->getBuffer()});
+ FormatterBytecode::DataStack data({valobj->GetSP()});
+ llvm::Error error = FormatterBytecode::Interpret(
+ control, data, FormatterBytecode::sel_summary);
+ if (error) {
+ retval = llvm::toString(std::move(error));
+ return false;
+ }
+ if (!data.size()) {
+ retval = "empty stack";
+ return false;
+ }
+ auto &top = data.back();
+ retval = "";
+ llvm::raw_string_ostream os(retval);
+ if (auto s = std::get_if<std::string>(&top))
+ os << *s;
+ else if (auto u = std::get_if<uint64_t>(&top))
+ os << *u;
+ else if (auto i = std::get_if<int64_t>(&top))
+ os << *i;
+ else if (auto valobj = std::get_if<ValueObjectSP>(&top)) {
+ if (!valobj->get())
+ os << "empty object";
+ else
+ os << valobj->get()->GetValueAsCString();
+ } else if (auto type = std::get_if<CompilerType>(&top)) {
+ os<<type->TypeDescription();
+ } else if (auto sel = std::get_if<FormatterBytecode::Selectors>(&top)) {
+ os<< toString(*sel);
+ }
+ return true;
+}
+
+std::string BytecodeSummaryFormat::GetDescription() {
+ StreamString sstr;
+ sstr.Printf("%s%s%s%s%s%s%s\n ", Cascades() ? "" : " (not cascading)",
+ !DoesPrintChildren(nullptr) ? "" : " (show children)",
+ !DoesPrintValue(nullptr) ? " (hide value)" : "",
+ IsOneLiner() ? " (one-line printout)" : "",
+ SkipsPointers() ? " (skip pointers)" : "",
+ SkipsReferences() ? " (skip references)" : "",
+ HideNames(nullptr) ? " (hide member names)" : "");
+ // FIXME: sstr.PutCString(disassembly);
+ return std::string(sstr.GetString());
+}
+
+std::string BytecodeSummaryFormat::GetName() { return "LLDB bytecode formatter"; }
diff --git a/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp b/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp
index ad4a84ef02bf72..0a03bde7967444 100644
--- a/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp
+++ b/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp
@@ -1679,6 +1679,7 @@ static SectionType GetSectionTypeFromName(llvm::StringRef Name) {
.Case(".gosymtab", eSectionTypeGoSymtab)
.Case(".text", eSectionTypeCode)
.Case(".lldbsummaries", lldb::eSectionTypeLLDBTypeSummaries)
+ .Case(".lldbformatters", lldb::eSectionTypeLLDBFormatters)
.Case(".swift_ast", eSectionTypeSwiftModules)
.Default(eSectionTypeOther);
}
diff --git a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
index d6bec5d84aea19..ab26b7fa6648dc 100644
--- a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
+++ b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp
@@ -1210,6 +1210,7 @@ AddressClass ObjectFileMachO::GetAddressClass(lldb::addr_t file_addr) {
case eSectionTypeDWARFGNUDebugAltLink:
case eSectionTypeCTF:
case eSectionTypeLLDBTypeSummaries:
+ case eSectionTypeLLDBFormatters:
case eSectionTypeSwiftModules:
return AddressClass::eDebug;
@@ -1486,6 +1487,7 @@ static lldb::SectionType GetSectionType(uint32_t flags,
static ConstString g_sect_name_go_symtab("__gosymtab");
static ConstString g_sect_name_ctf("__ctf");
static ConstString g_sect_name_lldb_summaries("__lldbsummaries");
+ static ConstString g_sect_name_lldb_formatters("__lldbformatters");
static ConstString g_sect_name_swift_ast("__swift_ast");
if (section_name == g_sect_name_dwarf_debug_abbrev)
@@ -1568,6 +1570,8 @@ static lldb::SectionType GetSectionType(uint32_t flags,
return eSectionTypeCTF;
if (section_name == g_sect_name_lldb_summaries)
return lldb::eSectionTypeLLDBTypeSummaries;
+ if (section_name == g_sect_name_lldb_formatters)
+ return lldb::eSectionTypeLLDBFormatters;
if (section_name == g_sect_name_swift_ast)
return eSectionTypeSwiftModules;
if (section_name == g_sect_name_objc_data ||
diff --git a/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp b/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp
index bb712da7f6d67d..bfdb8140e40aff 100644
--- a/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp
+++ b/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp
@@ -1011,6 +1011,7 @@ SectionType ObjectFilePECOFF::GetSectionType(llvm::StringRef sect_name,
.Cases(".eh_frame", ".eh_fram", eSectionTypeEHFrame)
.Case(".gosymtab", eSectionTypeGoSymtab)
.Case(".lldbsummaries", lldb::eSectionTypeLLDBTypeSummaries)
+ .Case(".lldbformatters", lldb::eSectionTypeLLDBFormatters)
.Case("swiftast", eSectionTypeSwiftModules)
.Default(eSectionTypeInvalid);
if (section_type != eSectionTypeInvalid)
diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index 37670b108d9631..679f5cac24809c 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -1538,21 +1538,17 @@ static void LoadScriptingResourceForModule(const ModuleSP &module_sp,
feedback_stream.GetData());
}
-// Load type summaries embedded in the binary. These are type summaries provided
-// by the authors of the code.
-static void LoadTypeSummariesForModule(ModuleSP module_sp) {
- auto *sections = module_sp->GetSectionList();
+static void ForEachFormatterInModule(
+ Module &module, SectionType section_type,
+ std::function<void(llvm::DataExtractor, llvm::StringRef)> fn) {
+ auto *sections = module.GetSectionList();
if (!sections)
return;
- auto summaries_sp =
- sections->FindSectionByType(eSectionTypeLLDBTypeSummaries, true);
- if (!summaries_sp)
+ auto section_sp = sections->FindSectionByType(section_type, true);
+ if (!section_sp)
return;
- Log *log = GetLog(LLDBLog::DataFormatters);
- const char *module_name = module_sp->GetObjectName().GetCString();
-
TypeCategoryImplSP category;
DataVisualization::Categories::GetCategory(ConstString("default"), category);
@@ -1563,49 +1559,131 @@ static void LoadTypeSummariesForModule(ModuleSP module_sp) {
// * The remaining size of the record
// * The size of the type identifier
// * The type identifier, either a type name, or a regex
- // * The size of the summary string
- // * The summary string
+ // * The size of the entry
+ // * The entry
//
// Integers are encoded using ULEB.
//
// Strings are encoded with first a length (ULEB), then the string contents,
// and lastly a null terminator. The length includes the null.
- DataExtractor extractor;
- auto section_size = summaries_sp->GetSectionData(extractor);
- lldb::offset_t offset = 0;
- while (offset < section_size) {
- uint64_t version = extractor.GetULEB128(&offset);
- uint64_t record_size = extractor.GetULEB128(&offset);
+ DataExtractor lldb_extractor;
+ auto section_size = section_sp->GetSectionData(lldb_extractor);
+ llvm::DataExtractor section = lldb_extractor.GetAsLLVM();
+ bool le = section.isLittleEndian();
+ uint8_t addr_size = section.getAddressSize();
+ llvm::DataExtractor::Cursor cursor(0);
+ while (cursor && cursor.tell() < section_size) {
+ uint64_t version = section.getULEB128(cursor);
+ uint64_t record_size = section.getULEB128(cursor);
if (version == 1) {
- uint64_t type_size = extractor.GetULEB128(&offset);
- llvm::StringRef type_name = extractor.GetCStr(&offset, type_size);
- uint64_t summary_size = extractor.GetULEB128(&offset);
- llvm::StringRef summary_string = extractor.GetCStr(&offset, summary_size);
- if (!type_name.empty() && !summary_string.empty()) {
- TypeSummaryImpl::Flags flags;
- auto summary_sp =
- std::make_shared<StringSummaryFormat>(flags, summary_string.data());
- FormatterMatchType match_type = eFormatterMatchExact;
- if (summary_string.front() == '^' && summary_string.back() == '$')
- match_type = eFormatterMatchRegex;
- category->AddTypeSummary(type_name, match_type, summary_sp);
- LLDB_LOGF(log, "Loaded embedded type summary for '%s' from %s.",
- type_name.data(), module_name);
- } else {
- if (type_name.empty())
- LLDB_LOGF(log, "Missing string(s) in embedded type summary in %s.",
- module_name);
- }
+ llvm::DataExtractor record(section.getData().drop_front(cursor.tell()),
+ le, addr_size);
+ llvm::DataExtractor::Cursor cursor(0);
+ uint64_t type_size = record.getULEB128(cursor);
+ llvm::StringRef type_name = record.getBytes(cursor, type_size);
+ llvm::Error error = cursor.takeError();
+ if (!error)
+ fn(llvm::DataExtractor(record.getData().drop_front(cursor.tell()), le,
+ addr_size), type_name);
+ else
+ LLDB_LOG_ERROR(GetLog(LLDBLog::DataFormatters), std::move(error),
+ "{0}");
} else {
// Skip unsupported record.
- offset += record_size;
- LLDB_LOGF(
- log,
- "Skipping unsupported embedded type summary of version %llu in %s.",
- version, module_name);
+ LLDB_LOG(
+ GetLog(LLDBLog::DataFormatters),
+ "Skipping unsupported embedded type summary of version {0} in {1}.",
+ version, module.GetFileSpec());
}
+ section.skip(cursor, record_size);
}
+ if (!cursor)
+ LLDB_LOG_ERROR(GetLog(LLDBLog::DataFormatters), cursor.takeError(), "{0}");
+}
+
+/// Load type summaries embedded in the binary. These are type summaries provided
+/// by the authors of the code.
+static void LoadTypeSummariesForModule(ModuleSP module_sp) {
+ ForEachFormatterInModule(
+ *module_sp, eSectionTypeLLDBTypeSummaries,
+ [&](llvm::DataExtractor extractor, llvm::StringRef type_name) {
+ TypeCategoryImplSP category;
+ DataVisualization::Categories::GetCategory(ConstString("default"),
+ category);
+ // The type summary record is serialized as follows.
+ //
+ // * The size of the summary string
+ // * The summary string
+ //
+ // Integers are encoded using ULEB.
+ llvm::DataExtractor::Cursor cursor(0);
+ uint64_t summary_size = extractor.getULEB128(cursor);
+ llvm::StringRef summary_string =
+ extractor.getBytes(cursor, summary_size);
+ if (!cursor) {
+ LLDB_LOG_ERROR(GetLog(LLDBLog::DataFormatters), cursor.takeError(),
+ "{0}");
+ return;
+ }
+ if (type_name.empty() || summary_string.empty()) {
+ LLDB_LOG(GetLog(LLDBLog::DataFormatters),
+ "Missing string(s) in embedded type summary in {0}.",
+ module_sp->GetFileSpec());
+ return;
+ }
+ TypeSummaryImpl::Flags flags;
+ auto summary_sp = std::make_shared<StringSummaryFormat>(
+ flags, summary_string.str().c_str());
+ FormatterMatchType match_type = eFormatterMatchExact;
+ if (type_name.front() == '^')
+ match_type = eFormatterMatchRegex;
+ category->AddTypeSummary(type_name, match_type, summary_sp);
+ LLDB_LOG(GetLog(LLDBLog::DataFormatters),
+ "Loaded embedded type summary for '{0}' from {1}.", type_name,
+ module_sp->GetFileSpec());
+ });
+}
+
+/// Load data formatters embedded in the binary. These are type summaries provided
+/// by the authors of the code.
+static void LoadFormattersForModule(ModuleSP module_sp) {
+ ForEachFormatterInModule(
+ *module_sp, eSectionTypeLLDBFormatters,
+ [&](llvm::DataExtractor extractor, llvm::StringRef type_name) {
+ // * Function signature (1 byte)
+ // * Length of the program (ULEB128)
+ // * The program bytecode
+ TypeCategoryImplSP category;
+ DataVisualization::Categories::GetCategory(ConstString("default"),
+ category);
+ llvm::DataExtractor::Cursor cursor(0);
+ while (cursor && cursor.tell() < extractor.size()) {
+ uint8_t signature = extractor.getU8(cursor);
+ uint64_t size = extractor.getULEB128(cursor);
+ llvm::StringRef bytecode = extractor.getBytes(cursor, size);
+ if (!cursor) {
+ LLDB_LOG_ERROR(GetLog(LLDBLog::DataFormatters), cursor.takeError(),
+ "{0}");
+ return;
+ }
+ if (signature == 0) {
+ TypeSummaryImpl::Flags flags;
+ auto summary_sp = std::make_shared<BytecodeSummaryFormat>(
+ flags, llvm::MemoryBuffer::getMemBufferCopy(bytecode));
+ FormatterMatchType match_type = eFormatterMatchExact;
+ if (type_name.front() == '^')
+ match_type = eFormatterMatchRegex;
+ category->AddTypeSummary(type_name, match_type, summary_sp);
+ LLDB_LOG(GetLog(LLDBLog::DataFormatters),
+ "Loaded embedded type summary for '{0}' from {1}.",
+ type_name, module_sp->GetFileSpec());
+ } else
+ LLDB_LOG(GetLog(LLDBLog::DataFormatters),
+ "Unsupported formatter signature {0} for '{1}' in {2}",
+ signature, type_name, module_sp->GetFileSpec());
+ }
+ });
}
void Target::ClearModules(bool delete_locations) {
@@ -1847,6 +1925,7 @@ void Target::ModulesDidLoad(ModuleList &module_list) {
ModuleSP module_sp(module_list.GetModuleAtIndex(idx));
LoadScriptingResourceForModule(module_sp, this);
LoadTypeSummariesForModule(module_sp);
+ LoadFormattersForModule(module_sp);
}
m_breakpoint_list.UpdateBreakpoints(module_list, true, false);
m_internal_breakpoint_list.UpdateBreakpoints(module_list, true, false);
diff --git a/lldb/test/API/functionalities/data-formatter/bytecode-summary/Makefile b/lldb/test/API/functionalities/data-formatter/bytecode-summary/Makefile
new file mode 100644
index 00000000000000..c9319d6e6888a4
--- /dev/null
+++ b/lldb/test/API/functionalities/data-formatter/bytecode-summary/Makefile
@@ -0,0 +1,2 @@
+C_SOURCES := main.c
+include Makefile.rules
diff --git a/lldb/test/API/functionalities/data-formatter/bytecode-summary/TestBytecodeSummary.py b/lldb/test/API/functionalities/data-formatter/bytecode-summary/TestBytecodeSummary.py
new file mode 100644
index 00000000000000..7c47ddcdfc13c9
--- /dev/null
+++ b/lldb/test/API/functionalities/data-formatter/bytecode-summary/TestBytecodeSummary.py
@@ -0,0 +1,14 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestCase(TestBase):
+ @skipUnlessDarwin
+ def test(self):
+ self.build()
+ self.expect('log enable -v lldb formatters')
+ lldbutil.run_to_source_breakpoint(self, "break here", lldb.SBFileSpec("main.cpp"))
+ self.expect("v x", substrs=['(MyOptional<int>) x = None'])
+ self.expect("v y", substrs=['(MyOptional<int>) y = 42'])
diff --git a/lldb/test/API/functionalities/data-formatter/bytecode-summary/main.cpp b/lldb/test/API/functionalities/data-formatter/bytecode-summary/main.cpp
new file mode 100644
index 00000000000000..d77d17acb0d0ec
--- /dev/null
+++ b/lldb/test/API/functionalities/data-formatter/bytecode-summary/main.cpp
@@ -0,0 +1,36 @@
+// A bare-bones llvm::Optional reimplementation.
+
+template <typename T> struct MyOptionalStorage {
+ MyOptionalStorage(T val) : value(val), hasVal(true) {}
+ MyOptionalStorage() {}
+ T value;
+ bool hasVal = false;
+};
+
+template <typename T> struct MyOptional {
+ MyOptionalStorage<T> Storage;
+ MyOptional(T val) : Storage(val) {}
+ MyOptional() {}
+ T &operator*() { return Storage.value; }
+};
+
+void stop() {}
+
+int main(int argc, char **argv) {
+ MyOptional<int> x, y = 42;
+ stop(); // break here
+ return *y;
+}
+
+__attribute__((used, section("__DATA_CONST,__lldbformatters"))) unsigned char
+ _MyOptional_type_summary[] =
+ "\x01" // version
+ "\xa3" // record size
+ "\x01" // record size
+ "\x10" // type name size
+ "^MyOptional<.+>$" // type name
+ "\x00" // sig_summary
+ "\x8d" // program size
+ "\x01" // program size
+ "\x1\x22\x7Storage#\x12\x60\x1,C\x10\x1\x5\x11\x2\x1\x22\x6hasVal#\x12\x60\x1,\x10\x1e\x2\x22\x1b<could not read MyOptional>\x10G#!\x60 \x0P\x10\x6\x22\x4None\x10\x36\x1#\x15\x60 \x0#\x16\x60\x5\x22\x5value#\x12\x60\x5#\x17\x60\x1,\x10\x6\x22\x4None\x10\x11\x1#\x0\x60\x1#R\x60\x10\x3# \x60\x10\x1\x2\x12\x12\x12\x12"
+ ; // summary function
More information about the lldb-commits
mailing list