Skip to content

[lldb][Format] Make function name frame-format variables work without debug-info #137408

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 28, 2025

Conversation

Michael137
Copy link
Member

This patch makes the frame-format variables introduced in #131836 also work when no debug-info is available. Previously, we assumed sc.function was available, but without debug-info we might only have sc.symbol. We don't really need the sc.function apart from when formatting arguments.

For the function arguments case I added a fallback that will just print the arguments we get from the demangler (which is what LLDB does for stacktraces with no debug-info anyway). Ideally we'd have a separate FormatEntity::Entry::Type::FunctionArguments that will just print the arguments from the demangler and have something like the following in the plugin.cplusplus.display.function-name-format:

{ ${function.formatted-arguments} || ${function.arguments} }

I.e., when we can't format the arguments, print the ones from the demangler. But we currently don't have the || operator in the frame-format language yet.

@llvmbot
Copy link
Member

llvmbot commented Apr 25, 2025

@llvm/pr-subscribers-lldb

Author: Michael Buch (Michael137)

Changes

This patch makes the frame-format variables introduced in #131836 also work when no debug-info is available. Previously, we assumed sc.function was available, but without debug-info we might only have sc.symbol. We don't really need the sc.function apart from when formatting arguments.

For the function arguments case I added a fallback that will just print the arguments we get from the demangler (which is what LLDB does for stacktraces with no debug-info anyway). Ideally we'd have a separate FormatEntity::Entry::Type::FunctionArguments that will just print the arguments from the demangler and have something like the following in the plugin.cplusplus.display.function-name-format:

{ ${function.formatted-arguments} || ${function.arguments} }

I.e., when we can't format the arguments, print the ones from the demangler. But we currently don't have the || operator in the frame-format language yet.


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

8 Files Affected:

  • (modified) lldb/source/Core/FormatEntity.cpp (+5-4)
  • (modified) lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp (+36-2)
  • (modified) lldb/test/Shell/Settings/TestFrameFormatFunctionBasename.test (+5-1)
  • (modified) lldb/test/Shell/Settings/TestFrameFormatFunctionFormattedArguments.test (+10-1)
  • (modified) lldb/test/Shell/Settings/TestFrameFormatFunctionQualifiers.test (+5-1)
  • (modified) lldb/test/Shell/Settings/TestFrameFormatFunctionReturn.test (+5-1)
  • (modified) lldb/test/Shell/Settings/TestFrameFormatFunctionScope.test (+6-3)
  • (modified) lldb/test/Shell/Settings/TestFrameFormatFunctionTemplateArguments.test (+6-2)
diff --git a/lldb/source/Core/FormatEntity.cpp b/lldb/source/Core/FormatEntity.cpp
index e352d07fe487d..4ac50e2d30f3c 100644
--- a/lldb/source/Core/FormatEntity.cpp
+++ b/lldb/source/Core/FormatEntity.cpp
@@ -1809,11 +1809,12 @@ bool FormatEntity::Format(const Entry &entry, Stream &s,
   case Entry::Type::FunctionReturnRight:
   case Entry::Type::FunctionReturnLeft:
   case Entry::Type::FunctionQualifiers: {
-    if (!sc->function)
-      return false;
+    Language *language_plugin = nullptr;
+    if (sc->function)
+      language_plugin = Language::FindPlugin(sc->function->GetLanguage());
+    else if (sc->symbol)
+      language_plugin = Language::FindPlugin(sc->symbol->GetLanguage());
 
-    Language *language_plugin =
-        Language::FindPlugin(sc->function->GetLanguage());
     if (!language_plugin)
       return false;
 
diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
index 283e867d53bb7..ab8e9883868ce 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
@@ -381,6 +381,34 @@ GetDemangledScope(const SymbolContext &sc) {
   return demangled_name.slice(info->ScopeRange.first, info->ScopeRange.second);
 }
 
+static bool PrintDemangledArgumentList(Stream &s, const SymbolContext &sc) {
+  assert(sc.symbol);
+
+  Mangled mangled = sc.GetPossiblyInlinedFunctionName();
+  if (!mangled)
+    return false;
+
+  auto demangled_name = mangled.GetDemangledName().GetStringRef();
+  if (demangled_name.empty())
+    return false;
+
+  const std::optional<DemangledNameInfo> &info = mangled.GetDemangledInfo();
+  if (!info)
+    return false;
+
+  // Function without a basename is nonsense.
+  if (!info->hasBasename())
+    return false;
+
+  if (info->ArgumentsRange.second < info->ArgumentsRange.first)
+    return false;
+
+  s << demangled_name.slice(info->ArgumentsRange.first,
+                            info->ArgumentsRange.second);
+
+  return true;
+}
+
 bool CPlusPlusLanguage::CxxMethodName::TrySimplifiedParse() {
   // This method tries to parse simple method definitions which are presumably
   // most comman in user programs. Definitions that can be parsed by this
@@ -1890,8 +1918,6 @@ bool CPlusPlusLanguage::GetFunctionDisplayName(
 bool CPlusPlusLanguage::HandleFrameFormatVariable(
     const SymbolContext &sc, const ExecutionContext *exe_ctx,
     FormatEntity::Entry::Type type, Stream &s) {
-  assert(sc.function);
-
   switch (type) {
   case FormatEntity::Entry::Type::FunctionScope: {
     std::optional<llvm::StringRef> scope = GetDemangledScope(sc);
@@ -1925,6 +1951,14 @@ bool CPlusPlusLanguage::HandleFrameFormatVariable(
   }
 
   case FormatEntity::Entry::Type::FunctionFormattedArguments: {
+    // This ensures we print the arguments even when no debug-info is available.
+    //
+    // FIXME: we should have a Entry::Type::FunctionArguments and
+    // use it in the plugin.cplusplus.display.function-name-format
+    // once we have a "fallback operator" in the frame-format language.
+    if (!sc.function && sc.symbol)
+      return PrintDemangledArgumentList(s, sc);
+
     VariableList args;
     if (auto variable_list_sp = GetFunctionVariableList(sc))
       variable_list_sp->AppendVariablesWithScope(eValueTypeVariableArgument,
diff --git a/lldb/test/Shell/Settings/TestFrameFormatFunctionBasename.test b/lldb/test/Shell/Settings/TestFrameFormatFunctionBasename.test
index 249a5fac5b55e..35d443d872c7d 100644
--- a/lldb/test/Shell/Settings/TestFrameFormatFunctionBasename.test
+++ b/lldb/test/Shell/Settings/TestFrameFormatFunctionBasename.test
@@ -1,10 +1,14 @@
 # Test the ${function.basename} frame-format variable.
 
 # RUN: split-file %s %t
-# RUN: %build %t/main.cpp -o %t.out
+# RUN: %clang_host -g %t/main.cpp -o %t.out
 # RUN: %lldb -x -b -s %t/commands.input %t.out -o exit 2>&1 \
 # RUN:       | FileCheck %s
 #
+# RUN: %clang_host -O0 %t/main.cpp -o %t-nodebug.out
+# RUN: %lldb -x -b -s %t/commands.input %t-nodebug.out -o exit 2>&1 \
+# RUN:       | FileCheck %s
+
 #--- main.cpp
 namespace ns {
 template<typename T>
diff --git a/lldb/test/Shell/Settings/TestFrameFormatFunctionFormattedArguments.test b/lldb/test/Shell/Settings/TestFrameFormatFunctionFormattedArguments.test
index 5554830d3a247..46fb518158ce2 100644
--- a/lldb/test/Shell/Settings/TestFrameFormatFunctionFormattedArguments.test
+++ b/lldb/test/Shell/Settings/TestFrameFormatFunctionFormattedArguments.test
@@ -1,9 +1,13 @@
 # Test the ${function.formatted-arguments} frame-format variable.
 
 # RUN: split-file %s %t
-# RUN: %build %t/main.cpp -o %t.out
+# RUN: %clang_host -g %t/main.cpp -o %t.out
 # RUN: %lldb -x -b -s %t/commands.input %t.out -o exit 2>&1 \
 # RUN:       | FileCheck %s
+#
+# RUN: %clang_host -O0 %t/main.cpp -o %t-nodebug.out
+# RUN: %lldb -x -b -s %t/commands.input %t-nodebug.out -o exit 2>&1 \
+# RUN:       | FileCheck %s --check-prefix=CHECK-NODEBUG
 
 #--- main.cpp
 struct Foo {
@@ -40,3 +44,8 @@ bt
 # CHECK: custom-frame '((null)=5, x=10)'
 # CHECK: custom-frame '(str="hello", fptr=({{.*}}.out`foo(int, int) at main.cpp:{{[0-9]+}}))'
 # CHECK: custom-frame '(argc=1, argv={{.*}})'
+
+# CHECK-NODEBUG: custom-frame '()'
+# CHECK-NODEBUG: custom-frame '()'
+# CHECK-NODEBUG: custom-frame '(int, int)'
+# CHECK-NODEBUG: custom-frame '(char const*, void (*)(int, int))'
diff --git a/lldb/test/Shell/Settings/TestFrameFormatFunctionQualifiers.test b/lldb/test/Shell/Settings/TestFrameFormatFunctionQualifiers.test
index 95a3be3811d85..ad7ea65d0a261 100644
--- a/lldb/test/Shell/Settings/TestFrameFormatFunctionQualifiers.test
+++ b/lldb/test/Shell/Settings/TestFrameFormatFunctionQualifiers.test
@@ -1,9 +1,13 @@
 # Test the ${function.qualifiers} frame-format variable.
 
 # RUN: split-file %s %t
-# RUN: %build %t/main.cpp -o %t.out
+# RUN: %clang_host -g %t/main.cpp -o %t.out
 # RUN: %lldb -x -b -s %t/commands.input %t.out -o exit 2>&1 \
 # RUN:       | FileCheck %s
+#
+# RUN: %clang_host -O0 %t/main.cpp -o %t-nodebug.out
+# RUN: %lldb -x -b -s %t/commands.input %t-nodebug.out -o exit 2>&1 \
+# RUN:       | FileCheck %s
 
 #--- main.cpp
 struct Foo {
diff --git a/lldb/test/Shell/Settings/TestFrameFormatFunctionReturn.test b/lldb/test/Shell/Settings/TestFrameFormatFunctionReturn.test
index a5e49a1054c86..dd4cc68817712 100644
--- a/lldb/test/Shell/Settings/TestFrameFormatFunctionReturn.test
+++ b/lldb/test/Shell/Settings/TestFrameFormatFunctionReturn.test
@@ -2,9 +2,13 @@
 # frame-format variables.
 
 # RUN: split-file %s %t
-# RUN: %build %t/main.cpp -o %t.out
+# RUN: %clang_host -g %t/main.cpp -o %t.out
 # RUN: %lldb -x -b -s %t/commands.input %t.out -o exit 2>&1 \
 # RUN:       | FileCheck %s
+#
+# RUN: %clang_host -O0 %t/main.cpp -o %t-nodebug.out
+# RUN: %lldb -x -b -s %t/commands.input %t-nodebug.out -o exit 2>&1 \
+# RUN:       | FileCheck %s
 
 #--- main.cpp
 namespace ns::ns2 {
diff --git a/lldb/test/Shell/Settings/TestFrameFormatFunctionScope.test b/lldb/test/Shell/Settings/TestFrameFormatFunctionScope.test
index 28f0ab7ca39e3..a1d4cc01054db 100644
--- a/lldb/test/Shell/Settings/TestFrameFormatFunctionScope.test
+++ b/lldb/test/Shell/Settings/TestFrameFormatFunctionScope.test
@@ -1,9 +1,12 @@
 # Test the ${function.scope} frame-format variable.
 
 # RUN: split-file %s %t
-# RUN: %build %t/main.cpp -o %t.out
-# RUN: %lldb -o "settings set interpreter.stop-command-source-on-error false" \
-# RUN:       -x -b -s %t/commands.input %t.out -o exit 2>&1 \
+# RUN: %clang_host -g %t/main.cpp -o %t.out
+# RUN: %lldb -x -b -s %t/commands.input %t.out -o exit 2>&1 \
+# RUN:       | FileCheck %s
+#
+# RUN: %clang_host -O0 %t/main.cpp -o %t-nodebug.out
+# RUN: %lldb -x -b -s %t/commands.input %t-nodebug.out -o exit 2>&1 \
 # RUN:       | FileCheck %s
 
 #--- main.cpp
diff --git a/lldb/test/Shell/Settings/TestFrameFormatFunctionTemplateArguments.test b/lldb/test/Shell/Settings/TestFrameFormatFunctionTemplateArguments.test
index 396dd29dfd02c..384ba0adc4ce9 100644
--- a/lldb/test/Shell/Settings/TestFrameFormatFunctionTemplateArguments.test
+++ b/lldb/test/Shell/Settings/TestFrameFormatFunctionTemplateArguments.test
@@ -1,8 +1,12 @@
 # Test the ${function.template-arguments} frame-format variable.
 
 # RUN: split-file %s %t
-# RUN: %build %t/main.cpp -o %t.cxx.out
-# RUN: %lldb -x -b -s %t/commands.input %t.cxx.out -o exit 2>&1 \
+# RUN: %clang_host -g %t/main.cpp -o %t.out
+# RUN: %lldb -x -b -s %t/commands.input %t.out -o exit 2>&1 \
+# RUN:       | FileCheck %s
+#
+# RUN: %clang_host -O0 %t/main.cpp -o %t-nodebug.out
+# RUN: %lldb -x -b -s %t/commands.input %t-nodebug.out -o exit 2>&1 \
 # RUN:       | FileCheck %s
 
 #--- main.cpp

@Michael137 Michael137 changed the title [lldb][CPlusPlus] Make C++ frame-format work without debug-info [lldb][CPlusPlus] Make C++-specific frame-format variables work without debug-info Apr 25, 2025
@Michael137 Michael137 changed the title [lldb][CPlusPlus] Make C++-specific frame-format variables work without debug-info [lldb][CPlusPlus] Make function name frame-format variables work without debug-info Apr 25, 2025
@Michael137 Michael137 changed the title [lldb][CPlusPlus] Make function name frame-format variables work without debug-info [lldb][Format] Make function name frame-format variables work without debug-info Apr 25, 2025
@adrian-prantl
Copy link
Collaborator

That sounds useful!
Another way to spell || would be ?: which might be more intuitive to C programmers?

@Michael137
Copy link
Member Author

That sounds useful! Another way to spell || would be ?: which might be more intuitive to C programmers?

@JDevlieghere was working on this separately. Not sure what syntax was settled on

@Michael137 Michael137 force-pushed the lldb/highlight-no-debuginfo branch from 63a74fe to db417e8 Compare April 26, 2025 12:30
Copy link
Collaborator

@labath labath left a comment

Choose a reason for hiding this comment

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

Having separate variables for these makes sense since they are kind of showing completely different information (demangler will show you the types, while the formatter will show you names and values), but I think that a single variable makes sense as well ("just show me something about the arguments").

As for the syntax for alternatives, I was suggesting (privately, I think) to use :-, as that what bash uses for conditional expansion (and the formatter language feels more like bash than C). However, I am having some doubts about that now since the way you're thinking of using it is not exactly bash-like (the :- thingy only works for a single variable, but I think you want to mostly use it on the block level (and bash doesn't have blocks))

@Michael137
Copy link
Member Author

As for the syntax for alternatives, I was suggesting (privately, I think) to use :-, as that what bash uses for conditional expansion (and the formatter language feels more like bash than C). However, I am having some doubts about that now since the way you're thinking of using it is not exactly bash-like (the :- thingy only works for a single variable, but I think you want to mostly use it on the block level (and bash doesn't have blocks))

Would the syntax be ${some.variable :- fallback.variable}? Which would try the first and if that fails try the fallback? This would work for my use-case. I.e., ${function.formatted-arguments :- function.arguments}

@Michael137 Michael137 merged commit cebf86e into llvm:main Apr 28, 2025
10 checks passed
@Michael137 Michael137 deleted the lldb/highlight-no-debuginfo branch April 28, 2025 09:46
@labath
Copy link
Collaborator

labath commented Apr 28, 2025

As for the syntax for alternatives, I was suggesting (privately, I think) to use :-, as that what bash uses for conditional expansion (and the formatter language feels more like bash than C). However, I am having some doubts about that now since the way you're thinking of using it is not exactly bash-like (the :- thingy only works for a single variable, but I think you want to mostly use it on the block level (and bash doesn't have blocks))

Would the syntax be ${some.variable :- fallback.variable}? Which would try the first and if that fails try the fallback? This would work for my use-case. I.e., ${function.formatted-arguments :- function.arguments}

So, the bash syntax requires another level of ${} if you want to treat the RHS as a variable (I guess, because something like echo "FOO is ${FOO:-unset}" is a common pattern). It also requires there be no spaces around the :- thingy.

$ FOO=bar
$ echo ${NOTSET:-FOO}
FOO
$ echo ${NOTSET:-${FOO}}
bar
$ echo ${NOTSET :- ${FOO}}
bash: ${NOTSET :- ${FOO}}: bad substitution

If it were to be used only on a single variable, then I think this syntax is great (including the nested expansion, since you also might want to replace the expansion with a constant string). For blocks (which I think makes sense), I'm less sure.

There's also a question of how to parse that (i.e., how to print a literal :-). With a single variable that's not an issue since variable names can't contain :. Though that's kind of a question regardless of the string...

@slackito
Copy link
Collaborator

I think this change broke lldb/test/Shell/Unwind/split-machine-functions.test. I can reproduce locally at HEAD with ninja check-lldb-shell-unwind.

The test binary has a symbol named _Z3foov.cold and the test expects the backtrace to print the name of the cold part of the function like this:

# SPLIT: frame #1: {{.*}}`foo() (.cold) +

but now it gets

frame #1: 0x000055555555514f split-machine-functions.test.tmp`foo() + 12

@JDevlieghere
Copy link
Member

As for the syntax for alternatives, I was suggesting (privately, I think) to use :-, as that what bash uses for conditional expansion (and the formatter language feels more like bash than C). However, I am having some doubts about that now since the way you're thinking of using it is not exactly bash-like (the :- thingy only works for a single variable, but I think you want to mostly use it on the block level (and bash doesn't have blocks))

Would the syntax be ${some.variable :- fallback.variable}? Which would try the first and if that fails try the fallback? This would work for my use-case. I.e., ${function.formatted-arguments :- function.arguments}

So, the bash syntax requires another level of ${} if you want to treat the RHS as a variable (I guess, because something like echo "FOO is ${FOO:-unset}" is a common pattern). It also requires there be no spaces around the :- thingy.

$ FOO=bar
$ echo ${NOTSET:-FOO}
FOO
$ echo ${NOTSET:-${FOO}}
bar
$ echo ${NOTSET :- ${FOO}}
bash: ${NOTSET :- ${FOO}}: bad substitution

If it were to be used only on a single variable, then I think this syntax is great (including the nested expansion, since you also might want to replace the expansion with a constant string). For blocks (which I think makes sense), I'm less sure.

There's also a question of how to parse that (i.e., how to print a literal :-). With a single variable that's not an issue since variable names can't contain :. Though that's kind of a question regardless of the string...

FWIW, I have a little prototype that uses | in the context of a scope that makes things a lot easier. I'll clean up the patch and put it up later tonight or tomorrow to give folks an idea. I would've preferred the bash syntax but it would be a lot more intrusive, so I'd like to get some input on the simple approach and see how folks feel about that.

@Michael137
Copy link
Member Author

I think this change broke lldb/test/Shell/Unwind/split-machine-functions.test. I can reproduce locally at HEAD with ninja check-lldb-shell-unwind.

The test binary has a symbol named _Z3foov.cold and the test expects the backtrace to print the name of the cold part of the function like this:

# SPLIT: frame #1: {{.*}}`foo() (.cold) +

but now it gets

frame #1: 0x000055555555514f split-machine-functions.test.tmp`foo() + 12

Ah thanks for the ping. I can see it's failing on the x86 Linux LLDB ubuntu

Yea this case is kind of weird because it's a non-standard mangling extension where the FunctionEncoding node gets wrapped in a DotSuffix. Let me revert this for now. One way to handle this would be to add a ${function.label-suffix} frame-format variable.

Michael137 added a commit that referenced this pull request Apr 29, 2025
… without debug-info" (#137757)

Reverts #137408

This change broke `lldb/test/Shell/Unwind/split-machine-functions.test`.

The test binary has a symbol named `_Z3foov.cold` and the test expects
the backtrace to print the name of the cold part of the function like
this:

```
# SPLIT: frame #1: {{.*}}`foo() (.cold) +
```
but now it gets

```
frame #1: 0x000055555555514f split-machine-functions.test.tmp`foo() + 12
```
@Michael137
Copy link
Member Author

Proposed fix: #137763

gizmondo pushed a commit to gizmondo/llvm-project that referenced this pull request Apr 29, 2025
… without debug-info" (llvm#137757)

Reverts llvm#137408

This change broke `lldb/test/Shell/Unwind/split-machine-functions.test`.

The test binary has a symbol named `_Z3foov.cold` and the test expects
the backtrace to print the name of the cold part of the function like
this:

```
# SPLIT: frame llvm#1: {{.*}}`foo() (.cold) +
```
but now it gets

```
frame llvm#1: 0x000055555555514f split-machine-functions.test.tmp`foo() + 12
```
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