Skip to content

Conversation

JDevlieghere
Copy link
Member

When you are trying for instance to set a breakpoint on a function by name, but the SBFunction or SBSymbol are returning demangled names with argument lists, that match can be tedious to do. Internally, the base name of a symbol is something we handle all the time, so it's reasonable that there should be a way to get that info from the API as well.

rdar://159318791

@JDevlieghere JDevlieghere requested a review from jimingham August 28, 2025 22:51
@llvmbot llvmbot added the lldb label Aug 28, 2025
@llvmbot
Copy link
Member

llvmbot commented Aug 28, 2025

@llvm/pr-subscribers-lldb

Author: Jonas Devlieghere (JDevlieghere)

Changes

When you are trying for instance to set a breakpoint on a function by name, but the SBFunction or SBSymbol are returning demangled names with argument lists, that match can be tedious to do. Internally, the base name of a symbol is something we handle all the time, so it's reasonable that there should be a way to get that info from the API as well.

rdar://159318791


Full diff: https://github.com/llvm/llvm-project/pull/155939.diff

9 Files Affected:

  • (modified) lldb/include/lldb/API/SBFunction.h (+2)
  • (modified) lldb/include/lldb/API/SBSymbol.h (+2)
  • (modified) lldb/include/lldb/Core/Mangled.h (+12)
  • (modified) lldb/source/API/SBFunction.cpp (+9)
  • (modified) lldb/source/API/SBSymbol.cpp (+9)
  • (modified) lldb/source/Core/Mangled.cpp (+18)
  • (added) lldb/test/API/python_api/basename/Makefile (+3)
  • (added) lldb/test/API/python_api/basename/TestGetBaseName.py (+172)
  • (added) lldb/test/API/python_api/basename/main.cpp (+17)
diff --git a/lldb/include/lldb/API/SBFunction.h b/lldb/include/lldb/API/SBFunction.h
index 0a8aeeff1ea5a..e703ae5dd63c1 100644
--- a/lldb/include/lldb/API/SBFunction.h
+++ b/lldb/include/lldb/API/SBFunction.h
@@ -36,6 +36,8 @@ class LLDB_API SBFunction {
 
   const char *GetMangledName() const;
 
+  const char *GetBaseName() const;
+
   lldb::SBInstructionList GetInstructions(lldb::SBTarget target);
 
   lldb::SBInstructionList GetInstructions(lldb::SBTarget target,
diff --git a/lldb/include/lldb/API/SBSymbol.h b/lldb/include/lldb/API/SBSymbol.h
index a93bc7a7ae074..580458ede212d 100644
--- a/lldb/include/lldb/API/SBSymbol.h
+++ b/lldb/include/lldb/API/SBSymbol.h
@@ -36,6 +36,8 @@ class LLDB_API SBSymbol {
 
   const char *GetMangledName() const;
 
+  const char *GetBaseName() const;
+
   lldb::SBInstructionList GetInstructions(lldb::SBTarget target);
 
   lldb::SBInstructionList GetInstructions(lldb::SBTarget target,
diff --git a/lldb/include/lldb/Core/Mangled.h b/lldb/include/lldb/Core/Mangled.h
index eb9a58c568896..99a0d7c98543c 100644
--- a/lldb/include/lldb/Core/Mangled.h
+++ b/lldb/include/lldb/Core/Mangled.h
@@ -287,6 +287,18 @@ class Mangled {
   /// Retrieve \c DemangledNameInfo of the demangled name held by this object.
   const std::optional<DemangledNameInfo> &GetDemangledInfo() const;
 
+  /// Compute the base name (without namespace/class qualifiers) from the
+  /// demangled name.
+  ///
+  /// For a demangled name like "ns::MyClass<int>::templateFunc", this returns
+  /// just "templateFunc". If the demangled name is not available or the
+  /// basename range is invalid, this falls back to GetDisplayDemangledName().
+  ///
+  /// \return
+  ///     A ConstString containing the basename, or nullptr if computation
+  ///     fails.
+  ConstString GetBaseName() const;
+
 private:
   /// If \c force is \c false, this function will re-use the previously
   /// demangled name (if any). If \c force is \c true (or the mangled name
diff --git a/lldb/source/API/SBFunction.cpp b/lldb/source/API/SBFunction.cpp
index 19861f6af3645..65b02d6b309ca 100644
--- a/lldb/source/API/SBFunction.cpp
+++ b/lldb/source/API/SBFunction.cpp
@@ -79,6 +79,15 @@ const char *SBFunction::GetMangledName() const {
   return nullptr;
 }
 
+const char *SBFunction::GetBaseName() const {
+  LLDB_INSTRUMENT_VA(this);
+
+  if (!m_opaque_ptr)
+    return nullptr;
+
+  return m_opaque_ptr->GetMangled().GetBaseName().AsCString();
+}
+
 bool SBFunction::operator==(const SBFunction &rhs) const {
   LLDB_INSTRUMENT_VA(this, rhs);
 
diff --git a/lldb/source/API/SBSymbol.cpp b/lldb/source/API/SBSymbol.cpp
index 3b59119494f37..3030c83292127 100644
--- a/lldb/source/API/SBSymbol.cpp
+++ b/lldb/source/API/SBSymbol.cpp
@@ -79,6 +79,15 @@ const char *SBSymbol::GetMangledName() const {
   return name;
 }
 
+const char *SBSymbol::GetBaseName() const {
+  LLDB_INSTRUMENT_VA(this);
+
+  if (!m_opaque_ptr)
+    return nullptr;
+
+  return m_opaque_ptr->GetMangled().GetBaseName().AsCString();
+}
+
 bool SBSymbol::operator==(const SBSymbol &rhs) const {
   LLDB_INSTRUMENT_VA(this, rhs);
 
diff --git a/lldb/source/Core/Mangled.cpp b/lldb/source/Core/Mangled.cpp
index ce4db4e0daa8b..6e3c36f72155f 100644
--- a/lldb/source/Core/Mangled.cpp
+++ b/lldb/source/Core/Mangled.cpp
@@ -556,3 +556,21 @@ void Mangled::Encode(DataEncoder &file, ConstStringTable &strtab) const {
       break;
   }
 }
+
+ConstString Mangled::GetBaseName() const {
+  const auto &demangled_info = GetDemangledInfo();
+  if (!demangled_info.has_value())
+    return GetDisplayDemangledName();
+
+  ConstString demangled_name = GetDemangledName();
+  if (!demangled_name)
+    return GetDisplayDemangledName();
+
+  const char *name_str = demangled_name.AsCString();
+  const auto &range = demangled_info->BasenameRange;
+  if (range.first >= range.second || range.second > strlen(name_str))
+    return ConstString();
+
+  return ConstString(
+      llvm::StringRef(name_str + range.first, range.second - range.first));
+}
diff --git a/lldb/test/API/python_api/basename/Makefile b/lldb/test/API/python_api/basename/Makefile
new file mode 100644
index 0000000000000..2bb9ce046a907
--- /dev/null
+++ b/lldb/test/API/python_api/basename/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules
\ No newline at end of file
diff --git a/lldb/test/API/python_api/basename/TestGetBaseName.py b/lldb/test/API/python_api/basename/TestGetBaseName.py
new file mode 100644
index 0000000000000..e546c99e98323
--- /dev/null
+++ b/lldb/test/API/python_api/basename/TestGetBaseName.py
@@ -0,0 +1,172 @@
+"""
+Test SBFunction::GetBaseName() and SBSymbol::GetBaseName() APIs.
+"""
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class GetBaseNameTestCase(TestBase):
+    def setUp(self):
+        # Call super's setUp().
+        TestBase.setUp(self)
+        # Find the line number to break on.
+        self.line1 = line_number(
+            "main.cpp", "// Find the line number for breakpoint 1 here."
+        )
+
+    def test_function_basename(self):
+        """Test SBFunction.GetBaseName() API."""
+        self.build()
+        exe = self.getBuildArtifact("a.out")
+
+        # Create a target by the debugger.
+        target = self.dbg.CreateTarget(exe)
+        self.assertTrue(target, VALID_TARGET)
+
+        # Create a breakpoint inside the C++ namespaced function.
+        breakpoint1 = target.BreakpointCreateByLocation("main.cpp", self.line1)
+        self.trace("breakpoint1:", breakpoint1)
+        self.assertTrue(
+            breakpoint1 and breakpoint1.GetNumLocations() == 1, VALID_BREAKPOINT
+        )
+
+        # Now launch the process, and do not stop at entry point.
+        process = target.LaunchSimple(None, None, self.get_process_working_directory())
+        self.assertTrue(process, PROCESS_IS_VALID)
+
+        # Frame #0 should be on self.line1.
+        self.assertState(process.GetState(), lldb.eStateStopped)
+        thread = lldbutil.get_stopped_thread(process, lldb.eStopReasonBreakpoint)
+        self.assertTrue(
+            thread.IsValid(),
+            "There should be a thread stopped due to breakpoint condition",
+        )
+        frame0 = thread.GetFrameAtIndex(0)
+        function = frame0.GetFunction()
+
+        # Test the function name methods
+        full_name = function.GetName()
+        display_name = function.GetDisplayName()
+        basename = function.GetBaseName()
+
+        self.trace("Full name:", full_name)
+        self.trace("Display name:", display_name)
+        self.trace("Base name:", basename)
+
+        # For a C++ function like "ns::MyClass<int>::templateFunc",
+        # the basename should be just "templateFunc"
+        self.assertTrue(basename is not None, "GetBaseName() should not return None")
+        self.assertNotEqual(
+            basename, "", "GetBaseName() should not return empty string"
+        )
+
+        # The basename should not contain namespace qualifiers
+        self.assertNotIn(
+            "::", basename, "Basename should not contain namespace qualifiers"
+        )
+
+        # The basename should be shorter than or equal to the full name
+        if full_name:
+            self.assertLessEqual(
+                len(basename),
+                len(full_name),
+                "Basename should be shorter than or equal to full name",
+            )
+
+    def test_symbol_basename(self):
+        """Test SBSymbol.GetBaseName() API."""
+        self.build()
+        exe = self.getBuildArtifact("a.out")
+
+        # Create a target by the debugger.
+        target = self.dbg.CreateTarget(exe)
+        self.assertTrue(target, VALID_TARGET)
+
+        # Create a breakpoint inside the C++ namespaced function.
+        breakpoint1 = target.BreakpointCreateByLocation("main.cpp", self.line1)
+        self.trace("breakpoint1:", breakpoint1)
+        self.assertTrue(
+            breakpoint1 and breakpoint1.GetNumLocations() == 1, VALID_BREAKPOINT
+        )
+
+        # Now launch the process, and do not stop at entry point.
+        process = target.LaunchSimple(None, None, self.get_process_working_directory())
+        self.assertTrue(process, PROCESS_IS_VALID)
+
+        # Frame #0 should be on self.line1.
+        self.assertState(process.GetState(), lldb.eStateStopped)
+        thread = lldbutil.get_stopped_thread(process, lldb.eStopReasonBreakpoint)
+        self.assertTrue(
+            thread.IsValid(),
+            "There should be a thread stopped due to breakpoint condition",
+        )
+        frame0 = thread.GetFrameAtIndex(0)
+        symbol = frame0.GetSymbol()
+
+        # Test the symbol name methods
+        full_name = symbol.GetName()
+        display_name = symbol.GetDisplayName()
+        basename = symbol.GetBaseName()
+
+        self.trace("Symbol full name:", full_name)
+        self.trace("Symbol display name:", display_name)
+        self.trace("Symbol base name:", basename)
+
+        # For a C++ symbol like "ns::MyClass<int>::templateFunc",
+        # the basename should be just "templateFunc"
+        self.assertTrue(basename is not None, "GetBaseName() should not return None")
+        self.assertNotEqual(
+            basename, "", "GetBaseName() should not return empty string"
+        )
+
+        # The basename should not contain namespace qualifiers
+        self.assertNotIn(
+            "::", basename, "Basename should not contain namespace qualifiers"
+        )
+
+        # The basename should be shorter than or equal to the full name
+        if full_name:
+            self.assertLessEqual(
+                len(basename),
+                len(full_name),
+                "Basename should be shorter than or equal to full name",
+            )
+
+    def test_basename_consistency(self):
+        """Test that SBFunction.GetBaseName() and SBSymbol.GetBaseName() return consistent results."""
+        self.build()
+        exe = self.getBuildArtifact("a.out")
+
+        # Create a target by the debugger.
+        target = self.dbg.CreateTarget(exe)
+        self.assertTrue(target, VALID_TARGET)
+
+        # Create a breakpoint inside the C++ namespaced function.
+        breakpoint1 = target.BreakpointCreateByLocation("main.cpp", self.line1)
+
+        # Now launch the process, and do not stop at entry point.
+        process = target.LaunchSimple(None, None, self.get_process_working_directory())
+
+        # Get stopped thread and frame
+        thread = lldbutil.get_stopped_thread(process, lldb.eStopReasonBreakpoint)
+        frame0 = thread.GetFrameAtIndex(0)
+
+        # Get both function and symbol
+        function = frame0.GetFunction()
+        symbol = frame0.GetSymbol()
+
+        # Test consistency between function and symbol basename
+        function_basename = function.GetBaseName()
+        symbol_basename = symbol.GetBaseName()
+
+        self.trace("Function basename:", function_basename)
+        self.trace("Symbol basename:", symbol_basename)
+
+        # Both should return valid strings
+        self.assertTrue(function_basename is not None)
+        self.assertTrue(symbol_basename is not None)
+        self.assertNotEqual(function_basename, "")
+        self.assertNotEqual(symbol_basename, "")
diff --git a/lldb/test/API/python_api/basename/main.cpp b/lldb/test/API/python_api/basename/main.cpp
new file mode 100644
index 0000000000000..17df50b4852e2
--- /dev/null
+++ b/lldb/test/API/python_api/basename/main.cpp
@@ -0,0 +1,17 @@
+#include <iostream>
+
+namespace ns {
+template <typename T> class MyClass {
+public:
+  void templateFunc() {
+    std::cout << "In templateFunc"
+              << std::endl; // Find the line number for breakpoint 1 here.
+  }
+};
+} // namespace ns
+
+int main() {
+  ns::MyClass<int> obj;
+  obj.templateFunc();
+  return 0;
+}
\ No newline at end of file

When you are trying for instance to set a breakpoint on a function by
name, but the SBFunction or SBSymbol are returning demangled names with
argument lists, that match can be tedious to do.  Internally, the base
name of a symbol is something we handle all the time, so it's reasonable
that there should be a way to get that info from the API as well.

rdar://159318791
Copy link

github-actions bot commented Aug 28, 2025

✅ With the latest revision this PR passed the Python code formatter.

Copy link
Collaborator

@jimingham jimingham left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@JDevlieghere JDevlieghere merged commit 2a062d6 into llvm:main Aug 29, 2025
9 checks passed
@JDevlieghere JDevlieghere deleted the rdar159318791 branch August 29, 2025 02:10
Comment on lines +569 to +572
const char *name_str = demangled_name.AsCString();
const auto &range = demangled_info->BasenameRange;
return ConstString(
llvm::StringRef(name_str + range.first, range.second - range.first));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const char *name_str = demangled_name.AsCString();
const auto &range = demangled_info->BasenameRange;
return ConstString(
llvm::StringRef(name_str + range.first, range.second - range.first));
llvm::StringRef name_str = demangled_name.GetStringRef();
const auto &range = demangled_info->BasenameRange;
return ConstString(name_str.splice(range.first, range.second));

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants