https://github.com/JDevlieghere created https://github.com/llvm/llvm-project/pull/155939
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 >From f3247a0ee7b3b29a2238cf1d6a14d303654e194e Mon Sep 17 00:00:00 2001 From: Jonas Devlieghere <jo...@devlieghere.com> Date: Thu, 28 Aug 2025 15:50:29 -0700 Subject: [PATCH] [lldb] Add SBFunction::GetBaseName() & SBSymbol::GetBaseName() 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 --- lldb/include/lldb/API/SBFunction.h | 2 + lldb/include/lldb/API/SBSymbol.h | 2 + lldb/include/lldb/Core/Mangled.h | 12 ++ lldb/source/API/SBFunction.cpp | 9 + lldb/source/API/SBSymbol.cpp | 9 + lldb/source/Core/Mangled.cpp | 18 ++ lldb/test/API/python_api/basename/Makefile | 3 + .../python_api/basename/TestGetBaseName.py | 172 ++++++++++++++++++ lldb/test/API/python_api/basename/main.cpp | 17 ++ 9 files changed, 244 insertions(+) create mode 100644 lldb/test/API/python_api/basename/Makefile create mode 100644 lldb/test/API/python_api/basename/TestGetBaseName.py create mode 100644 lldb/test/API/python_api/basename/main.cpp 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 _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits