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));

@slydiman
Copy link
Contributor

The test lldb-api::TestGetBaseName.py is broken on lldb-x86_64-win after this patch. Please take a look.

@slydiman
Copy link
Contributor

@JDevlieghere

******************** TEST 'lldb-api :: python_api/basename/TestGetBaseName.py' FAILED ********************
Script:
--
C:/Python312/python.exe C:/buildbot/as-builder-10/lldb-x86-64/llvm-project/lldb\test\API\dotest.py -u CXXFLAGS -u CFLAGS --env LLVM_LIBS_DIR=C:/buildbot/as-builder-10/lldb-x86-64/build/./lib --env LLVM_INCLUDE_DIR=C:/buildbot/as-builder-10/lldb-x86-64/build/include --env LLVM_TOOLS_DIR=C:/buildbot/as-builder-10/lldb-x86-64/build/./bin --arch x86_64 --build-dir C:/buildbot/as-builder-10/lldb-x86-64/build/lldb-test-build.noindex --lldb-module-cache-dir C:/buildbot/as-builder-10/lldb-x86-64/build/lldb-test-build.noindex/module-cache-lldb\lldb-api --clang-module-cache-dir C:/buildbot/as-builder-10/lldb-x86-64/build/lldb-test-build.noindex/module-cache-clang\lldb-api --executable C:/buildbot/as-builder-10/lldb-x86-64/build/./bin/lldb.exe --compiler C:/buildbot/as-builder-10/lldb-x86-64/build/./bin/clang.exe --dsymutil C:/buildbot/as-builder-10/lldb-x86-64/build/./bin/dsymutil.exe --make C:/ninja/make.exe --llvm-tools-dir C:/buildbot/as-builder-10/lldb-x86-64/build/./bin --lldb-obj-root C:/buildbot/as-builder-10/lldb-x86-64/build/tools/lldb --lldb-libs-dir C:/buildbot/as-builder-10/lldb-x86-64/build/./lib --cmake-build-type Release --skip-category=lldb-dap C:\buildbot\as-builder-10\lldb-x86-64\llvm-project\lldb\test\API\python_api\basename -p TestGetBaseName.py
--
Exit Code: 1
Command Output (stdout):
--
lldb version 22.0.0git (https://github.com/llvm/llvm-project.git revision 88d075197e04a290f5a2d01bacd600e2bf9b3bf9)
  clang revision 88d075197e04a290f5a2d01bacd600e2bf9b3bf9
  llvm revision 88d075197e04a290f5a2d01bacd600e2bf9b3bf9
Skipping the following test categories: ['lldb-dap', 'libc++', 'libstdcxx', 'dwo', 'dsym', 'gmodules', 'debugserver', 'objc', 'fork', 'pexpect']
--
Command Output (stderr):
--
FAIL: LLDB (C:\buildbot\as-builder-10\lldb-x86-64\build\bin\clang.exe-x86_64) :: test (TestGetBaseName.GetBaseNameTestCase.test)
======================================================================
FAIL: test (TestGetBaseName.GetBaseNameTestCase.test)
   Test SBFunction.GetBaseName() and SBSymbol.GetBaseName()
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\buildbot\as-builder-10\lldb-x86-64\llvm-project\lldb\test\API\python_api\basename\TestGetBaseName.py", line 35, in test
    self.assertEqual(function_basename, "templateFunc")
AssertionError: None != 'templateFunc'
Config=x86_64-C:\buildbot\as-builder-10\lldb-x86-64\build\bin\clang.exe
----------------------------------------------------------------------
Ran 1 test in 3.932s
FAILED (failures=1)
--
********************

@omjavaid
Copy link
Contributor

Looks like GetDemangledName returns empty on Windows. I am marking this as XFail on windows for now to make the buildbot happy.

omjavaid added a commit that referenced this pull request Aug 31, 2025
TestGetBaseName.py introduced in PR #155939 is failing on windows
LLDB bots. This patch adds @expectedFailureAll(oslist=["windows"])
decorator to mark it as an expected failure on Windows to make
buildbots green while the underlying issue is investigated.

(see: https://lab.llvm.org/buildbot/#/builders/141/builds/11176).
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.

6 participants