| Issue |
161458
|
| Summary |
lldb gcc generated external global DW_TAG_variable with no DW_AT_location fails to print
|
| Labels |
new issue
|
| Assignees |
|
| Reporter |
Mortal42
|
Found using latest LLDB git, GCC 11.5, & GDB 14.2. Behavior appears to differ depending on GCC (or GDB) version, but the basic issue can be seen with newer versions (re-tested with LLDB 20.1.1, GCC 14.2, and GDB 16.2).
Test case is 3 libraries.
testlib provides extern definitions of some variables (testobj1 through testobj4) in and out of a testcase namespace, this library is not built with debug symbols
testlib2 uses 2 of these externs (testcase::testobj1 and testobj3) in a run() function, called by main in testexe, this library is built with debug symbols
testlib3 uses 2 of these externs (testcase::testobj2 and testobj4) in a lib3() function, not called anywhere, this library is built with debug symbols.
This ends up with testlib2 and testlib3 with DWARF info like:
```
0x00000053: DW_TAG_variable
DW_AT_name ("testobj1")
DW_AT_decl_file ("/testcase/lib.h")
DW_AT_decl_line (7)
DW_AT_decl_column (0x17)
DW_AT_linkage_name ("_ZN8testcase8testobj1E")
DW_AT_type (0x0000003a "testcase::TestStruct")
DW_AT_external (true)
DW_AT_declaration (true)
```
And:
```
0x0000006b: DW_TAG_variable
DW_AT_name ("testobj3")
DW_AT_decl_file ("/testcase/lib.h")
DW_AT_decl_line (11)
DW_AT_decl_column (0x1d)
DW_AT_type (0x0000003a "testcase::TestStruct")
DW_AT_external (true)
DW_AT_declaration (true)
```
Where no DW_AT_location is defined, as these are externally defined symbols.
When built with GCC11, attaching with lldb and breaking on lib2.cpp before it returns, we get something like the following:
```
(lldb) b lib2.cpp:11
Breakpoint 1: where = libtestlib2.so`run() + 52 at lib2.cpp:11:15, address = 0x000000000000112d
(lldb) r
Process 2920018 launched: '/testcase/build/testexe' (x86_64)
Process 2920018 stopped
* thread #1, name = 'testexe', stop reason = breakpoint 1.1
frame #0: 0x00007ffff7fbb12d libtestlib2.so`run() at lib2.cpp:11:15
8 using namespace testcase;
9 auto a3 = testobj1.a;
10 auto a4 = testobj3.a;
-> 11 return a1 + a2 + a3 + a4;
12 }
(lldb) p testobj1
˄
╰─ error: use of undeclared identifier 'testobj1'
(lldb) p testobj2
˄
╰─ error: use of undeclared identifier 'testobj2'
(lldb) p testobj3
(void *) 0x0000000200000003
(lldb) p testobj4
(void *) 0x0000000300000004
```
When attaching with GDB (specifically with gcc11 and gdb 14), we get something more useful:
```
(gdb) b lib2.cpp:11
No symbol table is loaded. Use the "file" command.
Breakpoint 3 (lib2.cpp:11) pending.
(gdb) r
Starting program: /testcase/build/testexe
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Breakpoint 3, run () at /testcase/lib2.cpp:11
11 return a1 + a2 + a3 + a4;
Missing separate debuginfos, use: dnf debuginfo-install glibc-2.34-125.el9_5.3.alma.1.x86_64 libgcc-11.5.0-5.el9_5.alma.1.x86_64 libstdc++-11.5.0-5.el9_5.alma.1.x86_64
(gdb) p testobj1
$1 = {a = 1}
(gdb) p testobj2
$2 = {a = 2}
(gdb) p testobj3
$3 = {a = 3}
(gdb) p testobj4
$4 = {a = 4}
```
This behavior changes with gcc14 and gdb16, but is still providing printing of variables directly used in the current scope:
```
(gdb) b lib2.cpp:11
No symbol table is loaded. Use the "file" command.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (lib2.cpp:11) pending.
(gdb) r
Starting program: /testcase/build/testexe
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Breakpoint 1, run () at /testcase/lib2.cpp:11
11 return a1 + a2 + a3 + a4;
(gdb) p testobj1
$1 = {a = 1}
(gdb) p testobj2
No symbol "testobj2" in current context.
(gdb) p testobj3
$2 = {a = 3}
(gdb) p testobj4
'testobj4' has unknown type; cast it to its declared type
```
I did not investigate further with the newer versions of GCC/GDB.
I dove into LLDB a bit and found that ManualDWARFIndex::IndexUnitImpl does not report these variables as has_location_or_const_value and is_global_or_static_variable do not get set to true. I tested with adding a new case for DW_AT_external and allowed the global to be inserted if it is external. I could not process the location here as no target symtab is available here.
Eventually I found my way to DIEEval.cpp LookupGlobalIdentifier and added some extra logic to the first return of value_sp which would attempt to use symbol_context.FindBestGlobalDataSymbol to find the global symbol address and re-create value_sp with that address. This kinda works, providing the same display as gdb for testobj1 and testobj3, but does not work for testobj2 and testobj4. I know this is not the correct place to "fix" this, but it was one place that had a target symbol_context available.
I did not locate a better place to post-process the DWARF DIEs while also having access to the target symbol context / symtab data to find the external variable addresses.
This might be intended behavior and not a bug, but being able to debug extern objects where we have the type data but the variable is defined in a unit built without debug symbols seems useful. I could potentially generate a JSON symbol file for these symbols and pass it to LLDB, but the data is all there already... would be convenient if it just worked.
As a note, using clang instead of gcc does not work with this case at all as it does not output any DW_TAG_variable entries for these external variables, which is unfortunate but possible intended.
testcase files:
CMakeLists.txt:
```
cmake_minimum_required(VERSION 3.10)
project(testcase)
add_library(testlib SHARED lib.cpp)
add_library(testlib2 SHARED lib2.cpp)
target_link_libraries(testlib2 PRIVATE testlib)
target_compile_options(testlib2 PRIVATE -O0 -g)
add_library(testlib3 SHARED lib3.cpp)
target_link_libraries(testlib3 PRIVATE testlib)
target_compile_options(testlib3 PRIVATE -O0 -g)
add_executable(testexe main.cpp)
target_link_libraries(testexe PRIVATE testlib2 testlib3)
target_compile_options(testexe PRIVATE)
```
main.cpp:
```
#include "lib2.h"
int main() {
return run();
}
```
lib.h:
```
#pragma once
namespace testcase {
struct TestStruct {
int a;
};
extern TestStruct testobj1; // Accessed from lib2
extern TestStruct testobj2; // accessed from lib3
}
extern testcase::TestStruct testobj3; // accessed from lib2
extern testcase::TestStruct testobj4; // accessed from lib3
```
lib.cpp:
```
#include "lib.h"
namespace testcase {
TestStruct testobj1 { .a = 1 };
TestStruct testobj2 { .a = 2 };
}
testcase::TestStruct testobj3 { .a = 3 };
testcase::TestStruct testobj4 { .a = 4 };
```
lib2.h:
```
#pragma once
int run();
```
lib2.cpp:
```
#include "lib2.h"
#include "lib.h"
int run() {
auto a1 = testcase::testobj1.a;
auto a2 = testobj3.a;
using namespace testcase;
auto a3 = testobj1.a;
auto a4 = testobj3.a;
return a1 + a2 + a3 + a4;
}
```
lib3.cpp:
```
#include "lib.h"
int lib3()
{
auto a1 = testcase::testobj2.a;
auto a2 = testobj4.a;
using namespace testcase;
auto a3 = testobj2.a;
auto a4 = testobj4.a;
return a1 + a2 + a3 + a4;
}
```
_______________________________________________
llvm-bugs mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs