[Lldb-commits] [lldb] [lldb] Support overriding the disassembly CPU & features (PR #115382)

via lldb-commits lldb-commits at lists.llvm.org
Thu Nov 7 14:30:07 PST 2024


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-lldb

Author: Jonas Devlieghere (JDevlieghere)

<details>
<summary>Changes</summary>

Add the ability to override the disassembly CPU and CPU features through a target setting (`target.disassembly-cpu` and
`target.disassembly-features`) and a `disassemble` command option (`--cpu` and `--features`).

This is especially relevant for architectures like RISC-V which relies heavily on CPU extensions.

The majority of this patch is plumbing the options through. I recommend looking at DisassemblerLLVMC and the test for the observable change in behavior.

---

Patch is 49.22 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/115382.diff


30 Files Affected:

- (modified) lldb/include/lldb/Core/Disassembler.h (+17-15) 
- (modified) lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h (+2) 
- (modified) lldb/include/lldb/Target/Target.h (+4) 
- (modified) lldb/include/lldb/lldb-enumerations.h (+2) 
- (modified) lldb/include/lldb/lldb-private-interfaces.h (+3-2) 
- (modified) lldb/source/API/SBFunction.cpp (+2-2) 
- (modified) lldb/source/API/SBSymbol.cpp (+2-2) 
- (modified) lldb/source/API/SBTarget.cpp (+7-6) 
- (modified) lldb/source/Commands/CommandObjectDisassemble.cpp (+28-10) 
- (modified) lldb/source/Commands/CommandObjectDisassemble.h (+14) 
- (modified) lldb/source/Commands/Options.td (+4) 
- (modified) lldb/source/Core/Disassembler.cpp (+25-17) 
- (modified) lldb/source/Core/DumpDataExtractor.cpp (+4-3) 
- (modified) lldb/source/Expression/IRExecutionUnit.cpp (+4-2) 
- (modified) lldb/source/Plugins/Architecture/Mips/ArchitectureMips.cpp (+1-1) 
- (modified) lldb/source/Plugins/Disassembler/LLVMC/DisassemblerLLVMC.cpp (+65-55) 
- (modified) lldb/source/Plugins/Disassembler/LLVMC/DisassemblerLLVMC.h (+5-3) 
- (modified) lldb/source/Plugins/DynamicLoader/Windows-DYLD/DynamicLoaderWindowsDYLD.cpp (+1-1) 
- (modified) lldb/source/Plugins/Process/Utility/StopInfoMachException.cpp (+5-2) 
- (modified) lldb/source/Plugins/UnwindAssembly/InstEmulation/UnwindAssemblyInstEmulation.cpp (+2-2) 
- (modified) lldb/source/Symbol/Function.cpp (+3-3) 
- (modified) lldb/source/Symbol/Symbol.cpp (+3-3) 
- (modified) lldb/source/Target/Process.cpp (+4-1) 
- (modified) lldb/source/Target/StackFrame.cpp (+11-6) 
- (modified) lldb/source/Target/Target.cpp (+14) 
- (modified) lldb/source/Target/TargetProperties.td (+6) 
- (modified) lldb/source/Target/ThreadPlanStepRange.cpp (+4-2) 
- (modified) lldb/source/Target/ThreadPlanTracer.cpp (+3-2) 
- (modified) lldb/source/Target/TraceDumper.cpp (+3-3) 
- (added) lldb/test/Shell/Commands/command-disassemble-cpu-features.yaml (+230) 


``````````diff
diff --git a/lldb/include/lldb/Core/Disassembler.h b/lldb/include/lldb/Core/Disassembler.h
index 21969aed03c209..e0ad4316e02497 100644
--- a/lldb/include/lldb/Core/Disassembler.h
+++ b/lldb/include/lldb/Core/Disassembler.h
@@ -409,35 +409,37 @@ class Disassembler : public std::enable_shared_from_this<Disassembler>,
   // flavor string gets set wrong. Instead, if you get a flavor string you
   // don't understand, use the default.  Folks who care to check can use the
   // FlavorValidForArchSpec method on the disassembler they got back.
-  static lldb::DisassemblerSP
-  FindPlugin(const ArchSpec &arch, const char *flavor, const char *plugin_name);
+  static lldb::DisassemblerSP FindPlugin(const ArchSpec &arch,
+                                         const char *flavor, const char *cpu,
+                                         const char *features,
+                                         const char *plugin_name);
 
   // This version will use the value in the Target settings if flavor is NULL;
-  static lldb::DisassemblerSP FindPluginForTarget(const Target &target,
-                                                  const ArchSpec &arch,
-                                                  const char *flavor,
-                                                  const char *plugin_name);
+  static lldb::DisassemblerSP
+  FindPluginForTarget(const Target &target, const ArchSpec &arch,
+                      const char *flavor, const char *cpu, const char *features,
+                      const char *plugin_name);
 
   struct Limit {
     enum { Bytes, Instructions } kind;
     lldb::addr_t value;
   };
 
-  static lldb::DisassemblerSP DisassembleRange(const ArchSpec &arch,
-                                               const char *plugin_name,
-                                               const char *flavor,
-                                               Target &target,
-                                               const AddressRange &disasm_range,
-                                               bool force_live_memory = false);
+  static lldb::DisassemblerSP
+  DisassembleRange(const ArchSpec &arch, const char *plugin_name,
+                   const char *flavor, const char *cpu, const char *features,
+                   Target &target, const AddressRange &disasm_range,
+                   bool force_live_memory = false);
 
   static lldb::DisassemblerSP
   DisassembleBytes(const ArchSpec &arch, const char *plugin_name,
-                   const char *flavor, const Address &start, const void *bytes,
-                   size_t length, uint32_t max_num_instructions,
-                   bool data_from_file);
+                   const char *flavor, const char *cpu, const char *features,
+                   const Address &start, const void *bytes, size_t length,
+                   uint32_t max_num_instructions, bool data_from_file);
 
   static bool Disassemble(Debugger &debugger, const ArchSpec &arch,
                           const char *plugin_name, const char *flavor,
+                          const char *cpu, const char *features,
                           const ExecutionContext &exe_ctx, const Address &start,
                           Limit limit, bool mixed_source_and_assembly,
                           uint32_t num_mixed_context_lines, uint32_t options,
diff --git a/lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h b/lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h
index b5e989633ea3fc..1875ff6a048d4c 100644
--- a/lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h
+++ b/lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h
@@ -312,6 +312,8 @@ static constexpr CommandObject::ArgumentTableEntry g_argument_table[] = {
     { lldb::eArgTypeRemotePath, "remote-path", lldb::CompletionType::eRemoteDiskFileCompletion, {}, { nullptr, false }, "A path on the system managed by the current platform." },
     { lldb::eArgTypeRemoteFilename, "remote-filename", lldb::CompletionType::eRemoteDiskFileCompletion, {}, { nullptr, false }, "A file on the system managed by the current platform." },
     { lldb::eArgTypeModule, "module", lldb::CompletionType::eModuleCompletion, {}, { nullptr, false }, "The name of a module loaded into the current target." },
+    { lldb::eArgTypeCPUName, "cpu-name", lldb::CompletionType::eNoCompletion, {}, { nullptr, false }, "The name of a CPU." },
+    { lldb::eArgTypeCPUFeatures, "cpu-features", lldb::CompletionType::eNoCompletion, {}, { nullptr, false }, "The CPU feature string." },
     // clang-format on
 };
 
diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index cab21c29a7486f..f4ca5df44b7878 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -119,6 +119,10 @@ class TargetProperties : public Properties {
 
   const char *GetDisassemblyFlavor() const;
 
+  const char *GetDisassemblyCPU() const;
+
+  const char *GetDisassemblyFeatures() const;
+
   InlineStrategy GetInlineStrategy() const;
 
   RealpathPrefixes GetSourceRealpathPrefixes() const;
diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h
index 938f6e3abe8f2a..a9a66ea2662f39 100644
--- a/lldb/include/lldb/lldb-enumerations.h
+++ b/lldb/include/lldb/lldb-enumerations.h
@@ -655,6 +655,8 @@ enum CommandArgumentType {
   eArgTypeRemotePath,
   eArgTypeRemoteFilename,
   eArgTypeModule,
+  eArgTypeCPUName,
+  eArgTypeCPUFeatures,
   eArgTypeLastArg // Always keep this entry as the last entry in this
                   // enumeration!!
 };
diff --git a/lldb/include/lldb/lldb-private-interfaces.h b/lldb/include/lldb/lldb-private-interfaces.h
index 5bac5cd3e86b59..d366dbd1d78329 100644
--- a/lldb/include/lldb/lldb-private-interfaces.h
+++ b/lldb/include/lldb/lldb-private-interfaces.h
@@ -29,8 +29,9 @@ typedef lldb::ABISP (*ABICreateInstance)(lldb::ProcessSP process_sp,
                                          const ArchSpec &arch);
 typedef std::unique_ptr<Architecture> (*ArchitectureCreateInstance)(
     const ArchSpec &arch);
-typedef lldb::DisassemblerSP (*DisassemblerCreateInstance)(const ArchSpec &arch,
-                                                           const char *flavor);
+typedef lldb::DisassemblerSP (*DisassemblerCreateInstance)(
+    const ArchSpec &arch, const char *flavor, const char *cpu,
+    const char *features);
 typedef DynamicLoader *(*DynamicLoaderCreateInstance)(Process *process,
                                                       bool force);
 typedef lldb::JITLoaderSP (*JITLoaderCreateInstance)(Process *process,
diff --git a/lldb/source/API/SBFunction.cpp b/lldb/source/API/SBFunction.cpp
index 6a97352fc2c2fd..c07d48fe5b499d 100644
--- a/lldb/source/API/SBFunction.cpp
+++ b/lldb/source/API/SBFunction.cpp
@@ -125,8 +125,8 @@ SBInstructionList SBFunction::GetInstructions(SBTarget target,
       lock = std::unique_lock<std::recursive_mutex>(target_sp->GetAPIMutex());
       const bool force_live_memory = true;
       sb_instructions.SetDisassembler(Disassembler::DisassembleRange(
-          module_sp->GetArchitecture(), nullptr, flavor, *target_sp,
-          m_opaque_ptr->GetAddressRange(), force_live_memory));
+          module_sp->GetArchitecture(), nullptr, flavor, nullptr, nullptr,
+          *target_sp, m_opaque_ptr->GetAddressRange(), force_live_memory));
     }
   }
   return sb_instructions;
diff --git a/lldb/source/API/SBSymbol.cpp b/lldb/source/API/SBSymbol.cpp
index 3f940538d1240a..90920060594143 100644
--- a/lldb/source/API/SBSymbol.cpp
+++ b/lldb/source/API/SBSymbol.cpp
@@ -126,8 +126,8 @@ SBInstructionList SBSymbol::GetInstructions(SBTarget target,
         AddressRange symbol_range(symbol_addr, m_opaque_ptr->GetByteSize());
         const bool force_live_memory = true;
         sb_instructions.SetDisassembler(Disassembler::DisassembleRange(
-            module_sp->GetArchitecture(), nullptr, flavor_string, *target_sp,
-            symbol_range, force_live_memory));
+            module_sp->GetArchitecture(), nullptr, flavor_string, nullptr,
+            nullptr, *target_sp, symbol_range, force_live_memory));
       }
     }
   }
diff --git a/lldb/source/API/SBTarget.cpp b/lldb/source/API/SBTarget.cpp
index 28bdf47a34137a..ff752a519d951e 100644
--- a/lldb/source/API/SBTarget.cpp
+++ b/lldb/source/API/SBTarget.cpp
@@ -2013,8 +2013,9 @@ lldb::SBInstructionList SBTarget::ReadInstructions(lldb::SBAddress base_addr,
                                 error, force_live_memory, &load_addr);
       const bool data_from_file = load_addr == LLDB_INVALID_ADDRESS;
       sb_instructions.SetDisassembler(Disassembler::DisassembleBytes(
-          target_sp->GetArchitecture(), nullptr, flavor_string, *addr_ptr,
-          data.GetBytes(), bytes_read, count, data_from_file));
+          target_sp->GetArchitecture(), nullptr, nullptr, nullptr,
+          flavor_string, *addr_ptr, data.GetBytes(), bytes_read, count,
+          data_from_file));
     }
   }
 
@@ -2038,8 +2039,8 @@ lldb::SBInstructionList SBTarget::ReadInstructions(lldb::SBAddress start_addr,
       AddressRange range(start_load_addr, size);
       const bool force_live_memory = true;
       sb_instructions.SetDisassembler(Disassembler::DisassembleRange(
-          target_sp->GetArchitecture(), nullptr, flavor_string, *target_sp,
-          range, force_live_memory));
+          target_sp->GetArchitecture(), nullptr, flavor_string, nullptr,
+          nullptr, *target_sp, range, force_live_memory));
     }
   }
   return sb_instructions;
@@ -2071,8 +2072,8 @@ SBTarget::GetInstructionsWithFlavor(lldb::SBAddress base_addr,
     const bool data_from_file = true;
 
     sb_instructions.SetDisassembler(Disassembler::DisassembleBytes(
-        target_sp->GetArchitecture(), nullptr, flavor_string, addr, buf, size,
-        UINT32_MAX, data_from_file));
+        target_sp->GetArchitecture(), nullptr, flavor_string, nullptr, nullptr,
+        addr, buf, size, UINT32_MAX, data_from_file));
   }
 
   return sb_instructions;
diff --git a/lldb/source/Commands/CommandObjectDisassemble.cpp b/lldb/source/Commands/CommandObjectDisassemble.cpp
index 250f849ac04c55..6db4b2665bd84a 100644
--- a/lldb/source/Commands/CommandObjectDisassemble.cpp
+++ b/lldb/source/Commands/CommandObjectDisassemble.cpp
@@ -120,6 +120,14 @@ Status CommandObjectDisassemble::CommandOptions::SetOptionValue(
     break;
   }
 
+  case 'X':
+    cpu_string = std::string(option_arg);
+    break;
+
+  case 'Y':
+    features_string = std::string(option_arg);
+    break;
+
   case 'r':
     raw = true;
     break;
@@ -176,20 +184,27 @@ void CommandObjectDisassemble::CommandOptions::OptionParsingStarting(
   Target *target =
       execution_context ? execution_context->GetTargetPtr() : nullptr;
 
-  // This is a hack till we get the ability to specify features based on
-  // architecture.  For now GetDisassemblyFlavor is really only valid for x86
-  // (and for the llvm assembler plugin, but I'm papering over that since that
-  // is the only disassembler plugin we have...
   if (target) {
+    // This is a hack till we get the ability to specify features based on
+    // architecture.  For now GetDisassemblyFlavor is really only valid for x86
+    // (and for the llvm assembler plugin, but I'm papering over that since that
+    // is the only disassembler plugin we have...
     if (target->GetArchitecture().GetTriple().getArch() == llvm::Triple::x86 ||
         target->GetArchitecture().GetTriple().getArch() ==
             llvm::Triple::x86_64) {
       flavor_string.assign(target->GetDisassemblyFlavor());
-    } else
+    } else {
       flavor_string.assign("default");
-
-  } else
+    }
+    if (const char *cpu = target->GetDisassemblyCPU())
+      cpu_string.assign(cpu);
+    if (const char *features = target->GetDisassemblyFeatures())
+      features_string.assign(features);
+  } else {
     flavor_string.assign("default");
+    cpu_string.assign("default");
+    features_string.assign("default");
+  }
 
   arch.Clear();
   some_location_specified = false;
@@ -453,9 +468,11 @@ void CommandObjectDisassemble::DoExecute(Args &command,
 
   const char *plugin_name = m_options.GetPluginName();
   const char *flavor_string = m_options.GetFlavorString();
+  const char *cpu_string = m_options.GetCPUString();
+  const char *features_string = m_options.GetFeaturesString();
 
-  DisassemblerSP disassembler =
-      Disassembler::FindPlugin(m_options.arch, flavor_string, plugin_name);
+  DisassemblerSP disassembler = Disassembler::FindPlugin(
+      m_options.arch, flavor_string, cpu_string, features_string, plugin_name);
 
   if (!disassembler) {
     if (plugin_name) {
@@ -524,7 +541,8 @@ void CommandObjectDisassemble::DoExecute(Args &command,
     }
     if (Disassembler::Disassemble(
             GetDebugger(), m_options.arch, plugin_name, flavor_string,
-            m_exe_ctx, cur_range.GetBaseAddress(), limit, m_options.show_mixed,
+            cpu_string, features_string, m_exe_ctx, cur_range.GetBaseAddress(),
+            limit, m_options.show_mixed,
             m_options.show_mixed ? m_options.num_lines_context : 0, options,
             result.GetOutputStream())) {
       result.SetStatus(eReturnStatusSuccessFinishResult);
diff --git a/lldb/source/Commands/CommandObjectDisassemble.h b/lldb/source/Commands/CommandObjectDisassemble.h
index 2e4d46dd0ec586..f9cba1e5ae9cb6 100644
--- a/lldb/source/Commands/CommandObjectDisassemble.h
+++ b/lldb/source/Commands/CommandObjectDisassemble.h
@@ -42,6 +42,18 @@ class CommandObjectDisassemble : public CommandObjectParsed {
       return flavor_string.c_str();
     }
 
+    const char *GetCPUString() {
+      if (cpu_string.empty() || cpu_string == "default")
+        return nullptr;
+      return cpu_string.c_str();
+    }
+
+    const char *GetFeaturesString() {
+      if (features_string.empty() || features_string == "default")
+        return nullptr;
+      return features_string.c_str();
+    }
+
     Status OptionParsingFinished(ExecutionContext *execution_context) override;
 
     bool show_mixed; // Show mixed source/assembly
@@ -58,6 +70,8 @@ class CommandObjectDisassemble : public CommandObjectParsed {
     bool frame_line = false;
     std::string plugin_name;
     std::string flavor_string;
+    std::string cpu_string;
+    std::string features_string;
     ArchSpec arch;
     bool some_location_specified = false; // If no location was specified, we'll
                                           // select "at_pc".  This should be set
diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td
index 9d8d45d083eca4..777f8c36c4916c 100644
--- a/lldb/source/Commands/Options.td
+++ b/lldb/source/Commands/Options.td
@@ -324,6 +324,10 @@ let Command = "disassemble" in {
     Arg<"DisassemblyFlavor">, Desc<"Name of the disassembly flavor you want to "
     "use. Currently the only valid options are default, and for Intel "
     "architectures, att and intel.">;
+  def disassemble_options_cpu : Option<"cpu", "X">, Arg<"CPUName">,
+    Desc<"Override the CPU for disassembling.">;
+  def disassemble_options_features : Option<"features", "Y">, Arg<"CPUFeatures">,
+    Desc<"Specify additional CPU features for disassembling.">;
   def disassemble_options_arch : Option<"arch", "A">, Arg<"Architecture">,
     Desc<"Specify the architecture to use from cross disassembly.">;
   def disassemble_options_start_address : Option<"start-address", "s">,
diff --git a/lldb/source/Core/Disassembler.cpp b/lldb/source/Core/Disassembler.cpp
index d071e3bfe4f77d..522a3ef2efc334 100644
--- a/lldb/source/Core/Disassembler.cpp
+++ b/lldb/source/Core/Disassembler.cpp
@@ -56,7 +56,8 @@ using namespace lldb;
 using namespace lldb_private;
 
 DisassemblerSP Disassembler::FindPlugin(const ArchSpec &arch,
-                                        const char *flavor,
+                                        const char *flavor, const char *cpu,
+                                        const char *features,
                                         const char *plugin_name) {
   LLDB_SCOPED_TIMERF("Disassembler::FindPlugin (arch = %s, plugin_name = %s)",
                      arch.GetArchitectureName(), plugin_name);
@@ -67,7 +68,7 @@ DisassemblerSP Disassembler::FindPlugin(const ArchSpec &arch,
     create_callback =
         PluginManager::GetDisassemblerCreateCallbackForPluginName(plugin_name);
     if (create_callback) {
-      if (auto disasm_sp = create_callback(arch, flavor))
+      if (auto disasm_sp = create_callback(arch, flavor, cpu, features))
         return disasm_sp;
     }
   } else {
@@ -75,18 +76,17 @@ DisassemblerSP Disassembler::FindPlugin(const ArchSpec &arch,
          (create_callback = PluginManager::GetDisassemblerCreateCallbackAtIndex(
               idx)) != nullptr;
          ++idx) {
-      if (auto disasm_sp = create_callback(arch, flavor))
+      if (auto disasm_sp = create_callback(arch, flavor, cpu, features))
         return disasm_sp;
     }
   }
   return DisassemblerSP();
 }
 
-DisassemblerSP Disassembler::FindPluginForTarget(const Target &target,
-                                                 const ArchSpec &arch,
-                                                 const char *flavor,
-                                                 const char *plugin_name) {
-  if (flavor == nullptr) {
+DisassemblerSP Disassembler::FindPluginForTarget(
+    const Target &target, const ArchSpec &arch, const char *flavor,
+    const char *cpu, const char *features, const char *plugin_name) {
+  if (!flavor) {
     // FIXME - we don't have the mechanism in place to do per-architecture
     // settings.  But since we know that for now we only support flavors on x86
     // & x86_64,
@@ -94,7 +94,12 @@ DisassemblerSP Disassembler::FindPluginForTarget(const Target &target,
         arch.GetTriple().getArch() == llvm::Triple::x86_64)
       flavor = target.GetDisassemblyFlavor();
   }
-  return FindPlugin(arch, flavor, plugin_name);
+  if (!cpu)
+    cpu = target.GetDisassemblyCPU();
+  if (!features)
+    features = target.GetDisassemblyFeatures();
+
+  return FindPlugin(arch, flavor, cpu, features, plugin_name);
 }
 
 static Address ResolveAddress(Target &target, const Address &addr) {
@@ -117,15 +122,16 @@ static Address ResolveAddress(Target &target, const Address &addr) {
 
 lldb::DisassemblerSP Disassembler::DisassembleRange(
     const ArchSpec &arch, const char *plugin_name, const char *flavor,
-    Target &target, const AddressRange &range, bool force_live_memory) {
+    const char *cpu, const char *features, Target &target,
+    const AddressRange &range, bool force_live_memory) {
   if (range.GetByteSize() <= 0)
     return {};
 
   if (!range.GetBaseAddress().IsValid())
     return {};
 
-  lldb::DisassemblerSP disasm_sp =
-      Disassembler::FindPluginForTarget(target, arch, flavor, plugin_name);
+  lldb::DisassemblerSP disasm_sp = Disassembler::FindPluginForTarget(
+      target, arch, flavor, cpu, features, plugin_name);
 
   if (!disasm_sp)
     return {};
@@ -141,14 +147,15 @@ lldb::DisassemblerSP Disassembler::DisassembleRange(
 
 lldb::DisassemblerSP
 Disassembler::DisassembleBytes(const ArchSpec &arch, const char *plugin_name,
-                               const char *flavor, const Address &start,
+                               const char *flavor, const char *cpu,
+                               const char *features, const Address &start,
                                const void *src, size_t src_len,
                                uint32_t num_instructions, bool data_from_file) {
   if (!src)
     return {};
 
   lldb::DisassemblerSP disasm_sp =
-      Disassembler::FindPlugin(arch, flavor, plugin_name);
+      Disassembler::FindPlugin(arch, flavor, cpu, features, plugin_name);
 
   if (!disasm_sp)
     return {};
@@ -163,6 +170,7 @@ Disassembler::DisassembleBytes(const ArchSpec &arch, const char *plugin_name,
 
 bool Disassembler::Disassemble(Debugger &debugger, const ArchSpec &arch,
                                const char *plugin_name, const char *flavor,
+                               const char *cpu, const char *features,
                                const ExecutionContext &exe_ctx,
                                const Address &address, Limit limit,
                                bool mixed_source_and_assembly,
@@ -172,7 +180,7 @@ bool Disassembler::Disassemble(Debugger &debugger, const ArchSpec &arch,
     return false;
 
   lldb::Disassemb...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/115382


More information about the lldb-commits mailing list