[Lldb-commits] [lldb] [lldb] Log errors to the system log if they would otherwise get dropped (PR #111911)

2024-10-13 Thread Jonas Devlieghere via lldb-commits

https://github.com/JDevlieghere updated 
https://github.com/llvm/llvm-project/pull/111911

>From 55eab57c695d4be99305d2b6cee0c4f44261a473 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere 
Date: Thu, 10 Oct 2024 14:39:44 -0700
Subject: [PATCH 1/2] [lldb] Log errors to the system log if they would
 otherwise get dropped

Log errors to the (always-on) system log if they would otherwise get
dropped by LLDB_LOG_ERROR and LLDB_LOG_ERRORV.
---
 lldb/include/lldb/Utility/Log.h   | 15 +++
 lldb/source/API/SystemInitializerFull.cpp |  3 +++
 lldb/source/Utility/Log.cpp   |  8 
 3 files changed, 26 insertions(+)

diff --git a/lldb/include/lldb/Utility/Log.h b/lldb/include/lldb/Utility/Log.h
index ac6347153a1014..f8545cd59a18ba 100644
--- a/lldb/include/lldb/Utility/Log.h
+++ b/lldb/include/lldb/Utility/Log.h
@@ -335,6 +335,15 @@ template  Log *GetLog(Cat mask) {
   return LogChannelFor().GetLog(Log::MaskType(mask));
 }
 
+/// Getter and setter for the error log (see g_error_log).
+/// The error log is set to the system log in SystemInitializerFull. We can't
+/// use the system log directly because that would violate the layering between
+/// Utility and Host.
+/// @{
+void SetLLDBErrorLog(Log *log);
+Log *GetLLDBErrorLog();
+/// @}
+
 } // namespace lldb_private
 
 /// The LLDB_LOG* macros defined below are the way to emit log messages.
@@ -387,6 +396,9 @@ template  Log *GetLog(Cat mask) {
 if (log_private && error_private) {
\
   log_private->FormatError(::std::move(error_private), __FILE__, __func__, 
\
__VA_ARGS__);   
\
+} else if (::lldb_private::Log *log_error = GetLLDBErrorLog()) {   
\
+  log_error->FormatError(::std::move(error_private), __FILE__, __func__,   
\
+ __VA_ARGS__); 
\
 } else 
\
   ::llvm::consumeError(::std::move(error_private));
\
   } while (0)
@@ -401,6 +413,9 @@ template  Log *GetLog(Cat mask) {
 if (log_private && log_private->GetVerbose() && error_private) {   
\
   log_private->FormatError(::std::move(error_private), __FILE__, __func__, 
\
__VA_ARGS__);   
\
+} else if (::lldb_private::Log *log_error = GetLLDBErrorLog()) {   
\
+  log_error->FormatError(::std::move(error_private), __FILE__, __func__,   
\
+ __VA_ARGS__); 
\
 } else 
\
   ::llvm::consumeError(::std::move(error_private));
\
   } while (0)
diff --git a/lldb/source/API/SystemInitializerFull.cpp 
b/lldb/source/API/SystemInitializerFull.cpp
index 8a992a6889a91b..31f3a9f30b81f0 100644
--- a/lldb/source/API/SystemInitializerFull.cpp
+++ b/lldb/source/API/SystemInitializerFull.cpp
@@ -84,6 +84,9 @@ llvm::Error SystemInitializerFull::Initialize() {
   // Use the Debugger's LLDBAssert callback.
   SetLLDBAssertCallback(Debugger::AssertCallback);
 
+  // Use the system log to report errors that would otherwise get dropped.
+  SetLLDBErrorLog(GetLog(SystemLog::System));
+
   LLDB_LOG(GetLog(SystemLog::System), "{0}", GetVersion());
 
   return llvm::Error::success();
diff --git a/lldb/source/Utility/Log.cpp b/lldb/source/Utility/Log.cpp
index 3798f406476370..a57d415be033ba 100644
--- a/lldb/source/Utility/Log.cpp
+++ b/lldb/source/Utility/Log.cpp
@@ -43,6 +43,10 @@ char TeeLogHandler::ID;
 
 llvm::ManagedStatic Log::g_channel_map;
 
+// The error log is used by LLDB_LOG_ERROR and LLDB_LOG_ERRORV. If the given
+// log channel is not enabled, error messages are logged to the error log.
+static std::atomic g_error_log = nullptr;
+
 void Log::ForEachCategory(
 const Log::ChannelMap::value_type &entry,
 llvm::function_ref lambda) {
@@ -460,3 +464,7 @@ void TeeLogHandler::Emit(llvm::StringRef message) {
   m_first_log_handler->Emit(message);
   m_second_log_handler->Emit(message);
 }
+
+void lldb_private::SetLLDBErrorLog(Log *log) { g_error_log.exchange(log); }
+
+Log *lldb_private::GetLLDBErrorLog() { return g_error_log; }

>From 99b70b38050344115c8a6b17929cfd8c93b55571 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere 
Date: Sun, 13 Oct 2024 09:01:08 -0700
Subject: [PATCH 2/2] Address Pavel's feedback

---
 lldb/include/lldb/Utility/Log.h | 8 ++--
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/lldb/include/lldb/Utility/Log.h b/lldb/include/lldb/Utility/Log.h
index f8545cd59a18ba..1529178cb83b4b 100644
--- a/lldb/include/lldb/Utility/Log.h
+++ b/lldb/include/lldb/Utility/Log.h
@@ -393,12 +393,11 @@ Log *GetLLDBErrorLog();
   do { 
\
 ::lldb_private::Log *l

[Lldb-commits] [lldb] [lldb] Log errors to the system log if they would otherwise get dropped (PR #111911)

2024-10-13 Thread Jonas Devlieghere via lldb-commits

https://github.com/JDevlieghere updated 
https://github.com/llvm/llvm-project/pull/111911

>From 55eab57c695d4be99305d2b6cee0c4f44261a473 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere 
Date: Thu, 10 Oct 2024 14:39:44 -0700
Subject: [PATCH 1/3] [lldb] Log errors to the system log if they would
 otherwise get dropped

Log errors to the (always-on) system log if they would otherwise get
dropped by LLDB_LOG_ERROR and LLDB_LOG_ERRORV.
---
 lldb/include/lldb/Utility/Log.h   | 15 +++
 lldb/source/API/SystemInitializerFull.cpp |  3 +++
 lldb/source/Utility/Log.cpp   |  8 
 3 files changed, 26 insertions(+)

diff --git a/lldb/include/lldb/Utility/Log.h b/lldb/include/lldb/Utility/Log.h
index ac6347153a1014..f8545cd59a18ba 100644
--- a/lldb/include/lldb/Utility/Log.h
+++ b/lldb/include/lldb/Utility/Log.h
@@ -335,6 +335,15 @@ template  Log *GetLog(Cat mask) {
   return LogChannelFor().GetLog(Log::MaskType(mask));
 }
 
+/// Getter and setter for the error log (see g_error_log).
+/// The error log is set to the system log in SystemInitializerFull. We can't
+/// use the system log directly because that would violate the layering between
+/// Utility and Host.
+/// @{
+void SetLLDBErrorLog(Log *log);
+Log *GetLLDBErrorLog();
+/// @}
+
 } // namespace lldb_private
 
 /// The LLDB_LOG* macros defined below are the way to emit log messages.
@@ -387,6 +396,9 @@ template  Log *GetLog(Cat mask) {
 if (log_private && error_private) {
\
   log_private->FormatError(::std::move(error_private), __FILE__, __func__, 
\
__VA_ARGS__);   
\
+} else if (::lldb_private::Log *log_error = GetLLDBErrorLog()) {   
\
+  log_error->FormatError(::std::move(error_private), __FILE__, __func__,   
\
+ __VA_ARGS__); 
\
 } else 
\
   ::llvm::consumeError(::std::move(error_private));
\
   } while (0)
@@ -401,6 +413,9 @@ template  Log *GetLog(Cat mask) {
 if (log_private && log_private->GetVerbose() && error_private) {   
\
   log_private->FormatError(::std::move(error_private), __FILE__, __func__, 
\
__VA_ARGS__);   
\
+} else if (::lldb_private::Log *log_error = GetLLDBErrorLog()) {   
\
+  log_error->FormatError(::std::move(error_private), __FILE__, __func__,   
\
+ __VA_ARGS__); 
\
 } else 
\
   ::llvm::consumeError(::std::move(error_private));
\
   } while (0)
diff --git a/lldb/source/API/SystemInitializerFull.cpp 
b/lldb/source/API/SystemInitializerFull.cpp
index 8a992a6889a91b..31f3a9f30b81f0 100644
--- a/lldb/source/API/SystemInitializerFull.cpp
+++ b/lldb/source/API/SystemInitializerFull.cpp
@@ -84,6 +84,9 @@ llvm::Error SystemInitializerFull::Initialize() {
   // Use the Debugger's LLDBAssert callback.
   SetLLDBAssertCallback(Debugger::AssertCallback);
 
+  // Use the system log to report errors that would otherwise get dropped.
+  SetLLDBErrorLog(GetLog(SystemLog::System));
+
   LLDB_LOG(GetLog(SystemLog::System), "{0}", GetVersion());
 
   return llvm::Error::success();
diff --git a/lldb/source/Utility/Log.cpp b/lldb/source/Utility/Log.cpp
index 3798f406476370..a57d415be033ba 100644
--- a/lldb/source/Utility/Log.cpp
+++ b/lldb/source/Utility/Log.cpp
@@ -43,6 +43,10 @@ char TeeLogHandler::ID;
 
 llvm::ManagedStatic Log::g_channel_map;
 
+// The error log is used by LLDB_LOG_ERROR and LLDB_LOG_ERRORV. If the given
+// log channel is not enabled, error messages are logged to the error log.
+static std::atomic g_error_log = nullptr;
+
 void Log::ForEachCategory(
 const Log::ChannelMap::value_type &entry,
 llvm::function_ref lambda) {
@@ -460,3 +464,7 @@ void TeeLogHandler::Emit(llvm::StringRef message) {
   m_first_log_handler->Emit(message);
   m_second_log_handler->Emit(message);
 }
+
+void lldb_private::SetLLDBErrorLog(Log *log) { g_error_log.exchange(log); }
+
+Log *lldb_private::GetLLDBErrorLog() { return g_error_log; }

>From 99b70b38050344115c8a6b17929cfd8c93b55571 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere 
Date: Sun, 13 Oct 2024 09:01:08 -0700
Subject: [PATCH 2/3] Address Pavel's feedback

---
 lldb/include/lldb/Utility/Log.h | 8 ++--
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/lldb/include/lldb/Utility/Log.h b/lldb/include/lldb/Utility/Log.h
index f8545cd59a18ba..1529178cb83b4b 100644
--- a/lldb/include/lldb/Utility/Log.h
+++ b/lldb/include/lldb/Utility/Log.h
@@ -393,12 +393,11 @@ Log *GetLLDBErrorLog();
   do { 
\
 ::lldb_private::Log *l

[Lldb-commits] [lldb] DynamicLoaderDarwin load images in parallel (PR #110439)

2024-10-13 Thread Jason Molenda via lldb-commits

jasonmolenda wrote:

Thanks for all the work you've done on this, and updating the setting.  I 
looked over the implementations, and they all look like reasonable changes to 
me - I did laugh a little when I realized that 2/3rds of all the changes were 
related to adding the setting :) that's always a bit of boilerplate for the 
first setting in a plugin.

In a process launch scenario, or attaching to a process when it was launched in 
a stopped state, there will only be two binaries, dyld and the main app binary, 
and I don't think there would be any much perf benefit to the "preload" 
approach where we can parallelize special binaries (dyld, main executable) -- 
dyld is just a little guy.  But when we attach to a launched app, where we have 
the two special binaries and a thousand others, if we load those two special 
binaries sequentially, all the other binaries are blocked until a 
possibly-expensive main binary has been parsed, and I think that's where the 
real perf difference you were measuring kicked in (you showed a 10.5 second 
versus 13.4 second time difference for preload versus parallel in the 
beginning), do I have that right?  Or were you attaching to a stopped process 
with just dyld+main binary, I can't imagine doing those two in parallel would 
lead to the savings you measured.

I think the preload approach where we can parallelize the two special binaries 
along with all the others on a "fully launched process" attach scenario is the 
right choice, thanks for investigating both of them and presenting both 
options.  Do you prefer the non-preload approach?  I know it is a simpler 
patch, but I can see how the perf benefit of preload could be significant with 
attaching to a fully launched process.

https://github.com/llvm/llvm-project/pull/110439
___
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits


[Lldb-commits] [lldb] [lldb] Make the system log a NOOP on Windows (PR #112052)

2024-10-13 Thread Noel Grandin via lldb-commits

grandinj wrote:

Windows system log : 
https://learn.microsoft.com/en-us/windows/win32/Events/windows-events

https://github.com/llvm/llvm-project/pull/112052
___
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits


[Lldb-commits] [lldb] Improve namespace lookup using .debug_names parent chain (PR #110062)

2024-10-13 Thread via lldb-commits


@@ -151,3 +151,19 @@ bool DWARFIndex::ProcessTypeDIEMatchQuery(
 return true;
   return callback(die);
 }
+
+void DWARFIndex::GetNamespacesWithParents(
+ConstString name, const CompilerDeclContext &parent_decl_ctx,
+llvm::function_ref callback) {
+  GetNamespaces(name, [&](DWARFDIE die) {
+return ProcessNamespaceDieMatchParents(parent_decl_ctx, die, callback);

jeffreytan81 wrote:

Yes

https://github.com/llvm/llvm-project/pull/110062
___
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits


[Lldb-commits] [lldb] Improve namespace lookup using .debug_names parent chain (PR #110062)

2024-10-13 Thread via lldb-commits


@@ -565,6 +565,48 @@ void DebugNamesDWARFIndex::GetTypesWithQuery(
   m_fallback.GetTypesWithQuery(query, callback);
 }
 
+void DebugNamesDWARFIndex::GetNamespacesWithParents(
+ConstString name, const CompilerDeclContext &parent_decl_ctx,
+llvm::function_ref callback) {
+  std::vector parent_contexts =
+  parent_decl_ctx.GetCompilerContext();
+  if (parent_contexts.empty())
+return GetNamespaces(name, callback);
+
+  llvm::SmallVector parent_named_contexts;
+  std::copy_if(parent_contexts.rbegin(), parent_contexts.rend(),
+   std::back_inserter(parent_named_contexts),
+   [](const CompilerContext &ctx) { return !ctx.name.IsEmpty(); });

jeffreytan81 wrote:

Thanks for sharing, good to know. Did not find any lldb code using this API, so 
will keep existing code. 

https://github.com/llvm/llvm-project/pull/110062
___
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits


[Lldb-commits] [lldb] Improve namespace lookup using .debug_names parent chain (PR #110062)

2024-10-13 Thread via lldb-commits


@@ -71,6 +71,14 @@ class DWARFIndex {
   virtual void
   GetTypesWithQuery(TypeQuery &query,
 llvm::function_ref callback);
+  /// Get namespace DIEs whose base name match \param name with \param

jeffreytan81 wrote:

There is no `query` in `GetNamespacesWithParents` API. Are you reading 
`GetTypesWithQuery()` API whose comment has mentioned `query` above. 

https://github.com/llvm/llvm-project/pull/110062
___
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits


[Lldb-commits] [lldb] Improve namespace lookup using .debug_names parent chain (PR #110062)

2024-10-13 Thread via lldb-commits

https://github.com/jeffreytan81 updated 
https://github.com/llvm/llvm-project/pull/110062

>From c5bbc5f17dd5039fb9d5a01ade2397afd5d4c967 Mon Sep 17 00:00:00 2001
From: jeffreytan81 
Date: Tue, 24 Sep 2024 14:43:44 -0700
Subject: [PATCH 1/2] Improve namespace lookup

---
 .../Plugins/SymbolFile/DWARF/DWARFIndex.cpp   | 16 +++
 .../Plugins/SymbolFile/DWARF/DWARFIndex.h | 11 +
 .../SymbolFile/DWARF/DebugNamesDWARFIndex.cpp | 42 +++
 .../SymbolFile/DWARF/DebugNamesDWARFIndex.h   |  4 +-
 .../SymbolFile/DWARF/SymbolFileDWARF.cpp  |  2 +-
 5 files changed, 73 insertions(+), 2 deletions(-)

diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFIndex.cpp 
b/lldb/source/Plugins/SymbolFile/DWARF/DWARFIndex.cpp
index dee90804c52584..c18edd10b96819 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFIndex.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFIndex.cpp
@@ -151,3 +151,19 @@ bool DWARFIndex::ProcessTypeDIEMatchQuery(
 return true;
   return callback(die);
 }
+
+void DWARFIndex::GetNamespacesWithParents(
+ConstString name, const CompilerDeclContext &parent_decl_ctx,
+llvm::function_ref callback) {
+  GetNamespaces(name, [&](DWARFDIE die) {
+return ProcessNamespaceDieMatchParents(parent_decl_ctx, die, callback);
+  });
+}
+
+bool DWARFIndex::ProcessNamespaceDieMatchParents(
+const CompilerDeclContext &parent_decl_ctx, DWARFDIE die,
+llvm::function_ref callback) {
+  if (!SymbolFileDWARF::DIEInDeclContext(parent_decl_ctx, die))
+return true;
+  return callback(die);
+}
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFIndex.h 
b/lldb/source/Plugins/SymbolFile/DWARF/DWARFIndex.h
index fea3a4fd697389..ac1f75e91c2195 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFIndex.h
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFIndex.h
@@ -71,6 +71,14 @@ class DWARFIndex {
   virtual void
   GetTypesWithQuery(TypeQuery &query,
 llvm::function_ref callback);
+  /// Get namespace DIEs whose base name match \param name with \param
+  /// parent_decl_ctx in its decl parent chain.  A base implementation
+  /// is provided. Specializations should override this if they are able to
+  /// provide a faster implementation.
+  virtual void
+  GetNamespacesWithParents(ConstString name,
+   const CompilerDeclContext &parent_decl_ctx,
+   llvm::function_ref callback);
   virtual void
   GetFunctions(const Module::LookupInfo &lookup_info, SymbolFileDWARF &dwarf,
const CompilerDeclContext &parent_decl_ctx,
@@ -127,6 +135,9 @@ class DWARFIndex {
   bool
   ProcessTypeDIEMatchQuery(TypeQuery &query, DWARFDIE die,
llvm::function_ref callback);
+  bool ProcessNamespaceDieMatchParents(
+  const CompilerDeclContext &parent_decl_ctx, DWARFDIE die,
+  llvm::function_ref callback);
 };
 } // namespace dwarf
 } // namespace lldb_private::plugin
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DebugNamesDWARFIndex.cpp 
b/lldb/source/Plugins/SymbolFile/DWARF/DebugNamesDWARFIndex.cpp
index c809e5ff7f8535..aeeb6b91c80fa3 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DebugNamesDWARFIndex.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DebugNamesDWARFIndex.cpp
@@ -565,6 +565,48 @@ void DebugNamesDWARFIndex::GetTypesWithQuery(
   m_fallback.GetTypesWithQuery(query, callback);
 }
 
+void DebugNamesDWARFIndex::GetNamespacesWithParents(
+ConstString name, const CompilerDeclContext &parent_decl_ctx,
+llvm::function_ref callback) {
+  std::vector parent_contexts =
+  parent_decl_ctx.GetCompilerContext();
+  if (parent_contexts.empty())
+return GetNamespaces(name, callback);
+
+  llvm::SmallVector parent_named_contexts;
+  std::copy_if(parent_contexts.rbegin(), parent_contexts.rend(),
+   std::back_inserter(parent_named_contexts),
+   [](const CompilerContext &ctx) { return !ctx.name.IsEmpty(); });
+  for (const DebugNames::Entry &entry :
+   m_debug_names_up->equal_range(name.GetStringRef())) {
+lldb_private::dwarf::Tag entry_tag = entry.tag();
+if (entry_tag == DW_TAG_namespace ||
+entry_tag == DW_TAG_imported_declaration) {
+  std::optional> parent_chain =
+  getParentChain(entry);
+  if (!parent_chain) {
+// Fallback: use the base class implementation.
+if (!ProcessEntry(entry, [&](DWARFDIE die) {
+  return ProcessNamespaceDieMatchParents(parent_decl_ctx, die,
+ callback);
+}))
+  return;
+continue;
+  }
+
+  if (WithinParentChain(parent_named_contexts, *parent_chain) &&
+  !ProcessEntry(entry, [&](DWARFDIE die) {
+// After .debug_names filtering still sending to base class for
+// further filtering before calling the callback.
+return ProcessNamespaceDieMatchParents(parent_decl_ctx, die,
+

[Lldb-commits] [lldb] [lldb][AIX] Added XCOFF Object File Header for AIX (PR #111814)

2024-10-13 Thread Dhruv Srivastava via lldb-commits

DhruvSrivastavaX wrote:

Okay sure. 
Thats not a problem Pavel, better to have it in smaller understandable pieces, 
so that it can be organised and reviewed properly. 

There are a couple of incoming code with such huge changes,  it will be good to 
adhere to this systematic approach. 

So, I will go ahead and modify this PR to drop a smaller piece of 
ObjectFileXCOFF.h and ObjectFileXCOFF.cpp instead, 
which can be reviewed easily (some base skeleton and declaration of parent 
virtual functions etc). 
Once that gets pulled in, I will build my next few PRs on that in order to 
complete these new additions. 
Will also try to add relevant test cases. 

Thanks!

https://github.com/llvm/llvm-project/pull/111814
___
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits


[Lldb-commits] [lldb] [lldb] Expose structured command diagnostics via the SBAPI. (PR #112109)

2024-10-13 Thread Adrian Prantl via lldb-commits

https://github.com/adrian-prantl updated 
https://github.com/llvm/llvm-project/pull/112109

>From e60c7d3313748bc3147be188e2243f271d71d19a Mon Sep 17 00:00:00 2001
From: Adrian Prantl 
Date: Fri, 11 Oct 2024 19:27:37 -0700
Subject: [PATCH] [lldb] Expose structured command diagnostics via the SBAPI.

This allows IDEs to render LLDB expression diagnostics to their liking
without relying on characterprecise ASCII art from LLDB. It is exposed
as a versioned SBStructuredData object, since it is expected that this
may need to be tweaked based on actual usage.
---
 lldb/include/lldb/API/SBCommandReturnObject.h |  1 +
 lldb/include/lldb/API/SBStructuredData.h  |  2 +
 .../lldb/Interpreter/CommandReturnObject.h| 13 ++-
 lldb/source/API/SBCommandReturnObject.cpp | 11 +++
 .../Commands/CommandObjectDWIMPrint.cpp   |  2 +-
 .../Commands/CommandObjectExpression.cpp  | 42 +++---
 .../source/Interpreter/CommandInterpreter.cpp | 23 +++--
 .../Interpreter/CommandReturnObject.cpp   | 84 +++
 .../diagnostics/TestExprDiagnostics.py| 61 --
 .../Shell/Commands/Inputs/multiline-expr.txt  |  6 ++
 .../Commands/command-expr-diagnostics.test| 28 +++
 11 files changed, 167 insertions(+), 106 deletions(-)
 create mode 100644 lldb/test/Shell/Commands/Inputs/multiline-expr.txt
 create mode 100644 lldb/test/Shell/Commands/command-expr-diagnostics.test

diff --git a/lldb/include/lldb/API/SBCommandReturnObject.h 
b/lldb/include/lldb/API/SBCommandReturnObject.h
index f96384a4710b16..e8e20a3f3016b8 100644
--- a/lldb/include/lldb/API/SBCommandReturnObject.h
+++ b/lldb/include/lldb/API/SBCommandReturnObject.h
@@ -45,6 +45,7 @@ class LLDB_API SBCommandReturnObject {
   const char *GetOutput();
 
   const char *GetError();
+  SBStructuredData GetErrorData();
 
 #ifndef SWIG
   LLDB_DEPRECATED_FIXME("Use PutOutput(SBFile) or PutOutput(FileSP)",
diff --git a/lldb/include/lldb/API/SBStructuredData.h 
b/lldb/include/lldb/API/SBStructuredData.h
index fc6e1ec95c7b86..ccdd12cab94b2f 100644
--- a/lldb/include/lldb/API/SBStructuredData.h
+++ b/lldb/include/lldb/API/SBStructuredData.h
@@ -9,6 +9,7 @@
 #ifndef LLDB_API_SBSTRUCTUREDDATA_H
 #define LLDB_API_SBSTRUCTUREDDATA_H
 
+#include "lldb/API/SBCommandReturnObject.h"
 #include "lldb/API/SBDefines.h"
 #include "lldb/API/SBModule.h"
 #include "lldb/API/SBScriptObject.h"
@@ -110,6 +111,7 @@ class SBStructuredData {
 
 protected:
   friend class SBAttachInfo;
+  friend class SBCommandReturnObject;
   friend class SBLaunchInfo;
   friend class SBDebugger;
   friend class SBTarget;
diff --git a/lldb/include/lldb/Interpreter/CommandReturnObject.h 
b/lldb/include/lldb/Interpreter/CommandReturnObject.h
index eda841869ba432..a491a6c1535b11 100644
--- a/lldb/include/lldb/Interpreter/CommandReturnObject.h
+++ b/lldb/include/lldb/Interpreter/CommandReturnObject.h
@@ -13,6 +13,7 @@
 #include "lldb/Utility/DiagnosticsRendering.h"
 #include "lldb/Utility/StreamString.h"
 #include "lldb/Utility/StreamTee.h"
+#include "lldb/Utility/StructuredData.h"
 #include "lldb/lldb-private.h"
 
 #include "llvm/ADT/StringRef.h"
@@ -31,7 +32,7 @@ class CommandReturnObject {
   ~CommandReturnObject() = default;
 
   /// Format any inline diagnostics with an indentation of \c indent.
-  llvm::StringRef GetInlineDiagnosticString(unsigned indent);
+  std::string GetInlineDiagnosticString(unsigned indent);
 
   llvm::StringRef GetOutputString() {
 lldb::StreamSP 
stream_sp(m_out_stream.GetStreamAtIndex(eStreamStringIndex));
@@ -40,7 +41,13 @@ class CommandReturnObject {
 return llvm::StringRef();
   }
 
-  llvm::StringRef GetErrorString();
+  /// Return the errors as a string.
+  ///
+  /// If \c with_diagnostics is true, all diagnostics are also
+  /// rendered into the string. Otherwise the expectation is that they
+  /// are fetched with \ref GetInlineDiagnosticString().
+  std::string GetErrorString(bool with_diagnostics = true);
+  StructuredData::ObjectSP GetErrorData();
 
   Stream &GetOutputStream() {
 // Make sure we at least have our normal string stream output stream
@@ -168,7 +175,6 @@ class CommandReturnObject {
   StreamTee m_out_stream;
   StreamTee m_err_stream;
   std::vector m_diagnostics;
-  StreamString m_diag_stream;
   std::optional m_diagnostic_indent;
 
   lldb::ReturnStatus m_status = lldb::eReturnStatusStarted;
@@ -178,6 +184,7 @@ class CommandReturnObject {
 
   /// If true, then the input handle from the debugger will be hooked up.
   bool m_interactive = true;
+  bool m_colors;
 };
 
 } // namespace lldb_private
diff --git a/lldb/source/API/SBCommandReturnObject.cpp 
b/lldb/source/API/SBCommandReturnObject.cpp
index a94eff75ffcb9e..9df8aa48b99366 100644
--- a/lldb/source/API/SBCommandReturnObject.cpp
+++ b/lldb/source/API/SBCommandReturnObject.cpp
@@ -11,6 +11,8 @@
 #include "lldb/API/SBError.h"
 #include "lldb/API/SBFile.h"
 #include "lldb/API/SBStream.h"
+#include "lldb/API/SBStructuredData.h"
+#include "lldb/Core/Structured