Skip to content

Commit 4c3ca81

Browse files
committed
🍒 [lldb][IRExecutionUnit] Return error on failure to resolve function address
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 (cherry-picked from 332b4de)
1 parent 5b88483 commit 4c3ca81

File tree

7 files changed

+70
-1
lines changed

7 files changed

+70
-1
lines changed

‎lldb/source/Expression/IRExecutionUnit.cpp‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -789,7 +789,12 @@ ResolveFunctionCallLabel(FunctionCallLabel &label,
789789
sc_list.Append(*sc_or_err);
790790

791791
LoadAddressResolver resolver(*sc.target_sp, symbol_was_missing_weak);
792-
return resolver.Resolve(sc_list).value_or(LLDB_INVALID_ADDRESS);
792+
lldb::addr_t resolved_addr =
793+
resolver.Resolve(sc_list).value_or(LLDB_INVALID_ADDRESS);
794+
if (resolved_addr == LLDB_INVALID_ADDRESS)
795+
return llvm::createStringError("couldn't resolve address for function");
796+
797+
return resolved_addr;
793798
}
794799

795800
lldb::addr_t
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
CXX_SOURCES := main.cpp lib1.cpp lib2.cpp
2+
3+
include Makefile.rules
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"""
2+
Tests that we can call functions that have definitions in multiple
3+
CUs in the debug-info (which is the case for functions defined in headers).
4+
The linker will most likely de-duplicate the functiond definitions when linking
5+
the final executable. On Darwin, this will create a debug-map that LLDB will use
6+
to fix up object file addresses to addresses in the linked executable. However,
7+
if we parsed the DIE from the object file whose functiond definition got stripped
8+
by the linker, LLDB needs to ensure it can still resolve the function symbol it
9+
got for it.
10+
"""
11+
12+
import lldb
13+
from lldbsuite.test.decorators import *
14+
from lldbsuite.test.lldbtest import *
15+
from lldbsuite.test import lldbutil
16+
17+
18+
class TestFunctionCallFromObjectFile(TestBase):
19+
def test_lib1(self):
20+
self.build()
21+
lldbutil.run_to_name_breakpoint(self, "lib1_func")
22+
23+
self.expect_expr("Foo{}.foo()", result_type="int", result_value="15")
24+
25+
def test_lib2(self):
26+
self.build()
27+
lldbutil.run_to_name_breakpoint(self, "lib2_func")
28+
29+
self.expect_expr("Foo{}.foo()", result_type="int", result_value="15")
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#ifndef COMMON_H_IN
2+
#define COMMON_H_IN
3+
4+
struct Foo {
5+
int foo() { return 15; }
6+
};
7+
8+
#endif // COMMON_H_IN
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#include "common.h"
2+
3+
// Parameter "Foo*" forces LLDB to parse "Foo" from the object
4+
// file that it is stopped in.
5+
void lib1_func(Foo *) {
6+
// Force definition into lib1.o debug-info.
7+
Foo{}.foo();
8+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#include "common.h"
2+
3+
void lib2_func(Foo *) {
4+
// Force definition into lib2.o debug-info.
5+
Foo{}.foo();
6+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
struct Foo;
2+
3+
extern void lib1_func(Foo *);
4+
extern void lib2_func(Foo *);
5+
6+
int main() {
7+
lib1_func(nullptr);
8+
lib2_func(nullptr);
9+
return 0;
10+
}

0 commit comments

Comments
 (0)