Skip to content

Conversation

Michael137
Copy link
Member

Starting with #148877 we started encoding the module ID of the function DIE we are currently parsing into its AsmLabel in the AST. When the JIT asks LLDB to resolve our special mangled name, we would locate the module and resolve the function/symbol we found in it.

If we are debugging with a SymbolFileDWARFDebugMap, the module ID we encode is that of the .o file that is tracked by the debug-map. To resolve the address of the DIE in that .o file, we have to ask SymbolFileDWARFDebugMap::LinkOSOAddress to turn the address of the .o DIE into a real address in the linked executable. This will only work if the .o address was actually tracked by the debug-map. However, if the function definition appears in multiple .o files (which is the case for functions defined in headers), the linker will most likely de-deuplicate that definition. So most .o's definition DIEs for that function won't have a contribution in the debug-map, and thus we fail to resolve the address.

When debugging Clang on Darwin, e.g., you'd see:

(lldb) expr CXXDecl->getName()

error: Couldn't look up symbols:
  $__lldb_func::0x1:0x4000d000002359da:_ZNK5clang9NamedDecl7getNameEv
Hint: The expression tried to call a function that is not present in the target, perhaps because it was optimized out by the compiler.

unless you were stopped in the .o file whose definition of getName made it into the final executable.

The fix here is to error out if we fail to resolve the address, causing us to fall back on the old flow which did a lookup by mangled name, which the SymbolFileDWARFDebugMap will handle correctly.

An alternative fix to this would be to encode the
SymbolFileDWARFDebugMap's module-id. And implement SymbolFileDWARFDebugMap::ResolveFunctionCallLabel by doing a mangled name lookup. The proposed approach doesn't stop us from implementing that, so we could choose to do it in a follow-up.

rdar://161393045

…ddress

Starting with llvm#148877 we
started encoding the module ID of the function DIE we are currently
parsing into its `AsmLabel` in the AST. When the JIT asks LLDB to
resolve our special mangled name, we would locate the module and resolve
the function/symbol we found in it.

If we are debugging with a `SymbolFileDWARFDebugMap`, the module ID we
encode is that of the `.o` file that is tracked by the debug-map. To
resolve the address of the DIE in that `.o` file, we have to ask
`SymbolFileDWARFDebugMap::LinkOSOAddress` to turn the address of the
`.o` DIE into a real address in the linked executable. This will only
work if the `.o` address was actually tracked by the debug-map. However,
if the function definition appears in multiple `.o` files (which is the
case for functions defined in headers), the linker will most likely
de-deuplicate that definition. So most `.o`'s definition DIEs for that
function won't have a contribution in the debug-map, and thus we fail to
resolve the address.

When debugging Clang on Darwin, e.g., you'd see:
```
(lldb) expr CXXDecl->getName()

error: Couldn't look up symbols:
  $__lldb_func::0x1:0x4000d000002359da:_ZNK5clang9NamedDecl7getNameEv
Hint: The expression tried to call a function that is not present in the target, perhaps because it was optimized out by the compiler.
```
unless you were stopped in the `.o` file whose definition of `getName`
made it into the final executable.

The fix here is to error out if we fail to resolve the address, causing
us to fall back on the old flow which did a lookup by mangled name,
which the `SymbolFileDWARFDebugMap` will handle correctly.

An alternative fix to this would be to encode the
`SymbolFileDWARFDebugMap`'s module-id. And implement
`SymbolFileDWARFDebugMap::ResolveFunctionCallLabel` by doing a mangled
name lookup. The proposed approach doesn't stop us from implementing
that, so we could choose to do it in a follow-up.

rdar://161393045
@llvmbot
Copy link
Member

llvmbot commented Sep 30, 2025

@llvm/pr-subscribers-lldb

Author: Michael Buch (Michael137)

Changes

Starting with #148877 we started encoding the module ID of the function DIE we are currently parsing into its AsmLabel in the AST. When the JIT asks LLDB to resolve our special mangled name, we would locate the module and resolve the function/symbol we found in it.

If we are debugging with a SymbolFileDWARFDebugMap, the module ID we encode is that of the .o file that is tracked by the debug-map. To resolve the address of the DIE in that .o file, we have to ask SymbolFileDWARFDebugMap::LinkOSOAddress to turn the address of the .o DIE into a real address in the linked executable. This will only work if the .o address was actually tracked by the debug-map. However, if the function definition appears in multiple .o files (which is the case for functions defined in headers), the linker will most likely de-deuplicate that definition. So most .o's definition DIEs for that function won't have a contribution in the debug-map, and thus we fail to resolve the address.

When debugging Clang on Darwin, e.g., you'd see:

(lldb) expr CXXDecl->getName()

error: Couldn't look up symbols:
  $__lldb_func::0x1:0x4000d000002359da:_ZNK5clang9NamedDecl7getNameEv
Hint: The expression tried to call a function that is not present in the target, perhaps because it was optimized out by the compiler.

unless you were stopped in the .o file whose definition of getName made it into the final executable.

The fix here is to error out if we fail to resolve the address, causing us to fall back on the old flow which did a lookup by mangled name, which the SymbolFileDWARFDebugMap will handle correctly.

An alternative fix to this would be to encode the
SymbolFileDWARFDebugMap's module-id. And implement SymbolFileDWARFDebugMap::ResolveFunctionCallLabel by doing a mangled name lookup. The proposed approach doesn't stop us from implementing that, so we could choose to do it in a follow-up.

rdar://161393045


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

7 Files Affected:

  • (modified) lldb/source/Expression/IRExecutionUnit.cpp (+6-1)
  • (added) lldb/test/API/lang/cpp/function-call-from-object-file/Makefile (+3)
  • (added) lldb/test/API/lang/cpp/function-call-from-object-file/TestFunctionCallFromObjectFile.py (+29)
  • (added) lldb/test/API/lang/cpp/function-call-from-object-file/common.h (+8)
  • (added) lldb/test/API/lang/cpp/function-call-from-object-file/lib1.cpp (+8)
  • (added) lldb/test/API/lang/cpp/function-call-from-object-file/lib2.cpp (+6)
  • (added) lldb/test/API/lang/cpp/function-call-from-object-file/main.cpp (+10)
diff --git a/lldb/source/Expression/IRExecutionUnit.cpp b/lldb/source/Expression/IRExecutionUnit.cpp
index 25d4a87b89ef2..60b9de0d21b2e 100644
--- a/lldb/source/Expression/IRExecutionUnit.cpp
+++ b/lldb/source/Expression/IRExecutionUnit.cpp
@@ -751,7 +751,12 @@ ResolveFunctionCallLabel(FunctionCallLabel &label,
   sc_list.Append(*sc_or_err);
 
   LoadAddressResolver resolver(*sc.target_sp, symbol_was_missing_weak);
-  return resolver.Resolve(sc_list).value_or(LLDB_INVALID_ADDRESS);
+  lldb::addr_t resolved_addr =
+      resolver.Resolve(sc_list).value_or(LLDB_INVALID_ADDRESS);
+  if (resolved_addr == LLDB_INVALID_ADDRESS)
+    return llvm::createStringError("couldn't resolve address for function");
+
+  return resolved_addr;
 }
 
 lldb::addr_t
diff --git a/lldb/test/API/lang/cpp/function-call-from-object-file/Makefile b/lldb/test/API/lang/cpp/function-call-from-object-file/Makefile
new file mode 100644
index 0000000000000..285bbfbbca4fe
--- /dev/null
+++ b/lldb/test/API/lang/cpp/function-call-from-object-file/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp lib1.cpp lib2.cpp
+
+include Makefile.rules
diff --git a/lldb/test/API/lang/cpp/function-call-from-object-file/TestFunctionCallFromObjectFile.py b/lldb/test/API/lang/cpp/function-call-from-object-file/TestFunctionCallFromObjectFile.py
new file mode 100644
index 0000000000000..f0a7aef182a67
--- /dev/null
+++ b/lldb/test/API/lang/cpp/function-call-from-object-file/TestFunctionCallFromObjectFile.py
@@ -0,0 +1,29 @@
+"""
+Tests that we can call functions that have definitions in multiple
+CUs in the debug-info (which is the case for functions defined in headers).
+The linker will most likely de-duplicate the functiond definitions when linking
+the final executable. On Darwin, this will create a debug-map that LLDB will use
+to fix up object file addresses to addresses in the linked executable. However,
+if we parsed the DIE from the object file whose functiond definition got stripped
+by the linker, LLDB needs to ensure it can still resolve the function symbol it
+got for it.
+"""
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestFunctionCallFromObjectFile(TestBase):
+    def test_lib1(self):
+        self.build()
+        lldbutil.run_to_name_breakpoint(self, "lib1_func")
+
+        self.expect_expr("Foo{}.foo()", result_type="int", result_value="15")
+
+    def test_lib2(self):
+        self.build()
+        lldbutil.run_to_name_breakpoint(self, "lib2_func")
+
+        self.expect_expr("Foo{}.foo()", result_type="int", result_value="15")
diff --git a/lldb/test/API/lang/cpp/function-call-from-object-file/common.h b/lldb/test/API/lang/cpp/function-call-from-object-file/common.h
new file mode 100644
index 0000000000000..76e23be6b97a6
--- /dev/null
+++ b/lldb/test/API/lang/cpp/function-call-from-object-file/common.h
@@ -0,0 +1,8 @@
+#ifndef COMMON_H_IN
+#define COMMON_H_IN
+
+struct Foo {
+  int foo() { return 15; }
+};
+
+#endif // COMMON_H_IN
diff --git a/lldb/test/API/lang/cpp/function-call-from-object-file/lib1.cpp b/lldb/test/API/lang/cpp/function-call-from-object-file/lib1.cpp
new file mode 100644
index 0000000000000..b97bcc1b712b6
--- /dev/null
+++ b/lldb/test/API/lang/cpp/function-call-from-object-file/lib1.cpp
@@ -0,0 +1,8 @@
+#include "common.h"
+
+// Parameter "Foo*" forces LLDB to parse "Foo" from the object
+// file that it is stopped in.
+void lib1_func(Foo *) {
+  // Force definition into lib1.o debug-info.
+  Foo{}.foo();
+}
diff --git a/lldb/test/API/lang/cpp/function-call-from-object-file/lib2.cpp b/lldb/test/API/lang/cpp/function-call-from-object-file/lib2.cpp
new file mode 100644
index 0000000000000..2f9d81a8bdf4c
--- /dev/null
+++ b/lldb/test/API/lang/cpp/function-call-from-object-file/lib2.cpp
@@ -0,0 +1,6 @@
+#include "common.h"
+
+void lib2_func(Foo *) {
+  // Force definition into lib2.o debug-info.
+  Foo{}.foo();
+}
diff --git a/lldb/test/API/lang/cpp/function-call-from-object-file/main.cpp b/lldb/test/API/lang/cpp/function-call-from-object-file/main.cpp
new file mode 100644
index 0000000000000..61ca798daf1df
--- /dev/null
+++ b/lldb/test/API/lang/cpp/function-call-from-object-file/main.cpp
@@ -0,0 +1,10 @@
+struct Foo;
+
+extern void lib1_func(Foo *);
+extern void lib2_func(Foo *);
+
+int main() {
+  lib1_func(nullptr);
+  lib2_func(nullptr);
+  return 0;
+}

@Michael137
Copy link
Member Author

@labath Bit of a tricky case that I missed in #148877

While the proposed fix works, it feels like maybe in the presence of debug-maps the flow should be slightly different. E.g., SymbolFileDWARFDebugMap::ResolveFunctionCallLabel could just be a plain mangled name lookup? After talking to @jasonmolenda it sounded like we usually would never want to expose the debug-map's .o files to anything outside SymbolFileDWARFDebugMap. Because all the lookups should be orchestrated through the debug-map.

@jasonmolenda
Copy link
Collaborator

After talking to @jasonmolenda it sounded like we usually would never want to expose the debug-map's .o files to anything outside SymbolFileDWARFDebugMap.

If I remember from our discussions, if SymbolFileDWARFDebugMap::LinkOSOAddress succeeds, then we're surfacing the linked binary's Section and offsets. It's only in this unusual case you found, where a function exists in the .o file that didn't make it into the linked binary and we've picked this .o file's copy of this function, where we get into this pickle and fall back to trying to use the .o file's Section and offset, which immediately leads to the bug you found.

@Michael137 Michael137 merged commit 332b4de into llvm:main Oct 1, 2025
11 checks passed
@Michael137 Michael137 deleted the lldb/function-label-with-debug-map branch October 1, 2025 07:37
mahesh-attarde pushed a commit to mahesh-attarde/llvm-project that referenced this pull request Oct 3, 2025
…ddress (llvm#161363)

Starting with llvm#148877 we
started encoding the module ID of the function DIE we are currently
parsing into its `AsmLabel` in the AST. When the JIT asks LLDB to
resolve our special mangled name, we would locate the module and resolve
the function/symbol we found in it.

If we are debugging with a `SymbolFileDWARFDebugMap`, the module ID we
encode is that of the `.o` file that is tracked by the debug-map. To
resolve the address of the DIE in that `.o` file, we have to ask
`SymbolFileDWARFDebugMap::LinkOSOAddress` to turn the address of the
`.o` DIE into a real address in the linked executable. This will only
work if the `.o` address was actually tracked by the debug-map. However,
if the function definition appears in multiple `.o` files (which is the
case for functions defined in headers), the linker will most likely
de-deuplicate that definition. So most `.o`'s definition DIEs for that
function won't have a contribution in the debug-map, and thus we fail to
resolve the address.

When debugging Clang on Darwin, e.g., you'd see:
```
(lldb) expr CXXDecl->getName()

error: Couldn't look up symbols:
  $__lldb_func::0x1:0x4000d000002359da:_ZNK5clang9NamedDecl7getNameEv
Hint: The expression tried to call a function that is not present in the target, perhaps because it was optimized out by the compiler.
```
unless you were stopped in the `.o` file whose definition of `getName`
made it into the final executable.

The fix here is to error out if we fail to resolve the address, causing
us to fall back on the old flow which did a lookup by mangled name,
which the `SymbolFileDWARFDebugMap` will handle correctly.

An alternative fix to this would be to encode the
`SymbolFileDWARFDebugMap`'s module-id. And implement
`SymbolFileDWARFDebugMap::ResolveFunctionCallLabel` by doing a mangled
name lookup. The proposed approach doesn't stop us from implementing
that, so we could choose to do it in a follow-up.

rdar://161393045
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