llvm / llvm/llvm-project

[LLDB] Template specializations from DWARF data can result in type definitions colliding

Open
#196,812 3 comments 0 reactions 0 assignees View on GitHub
lldb
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

Discovered [here](https://github.com/rust-lang/rust/pull/155336#issuecomment-4415434804). Minimal reproducible example, compiled with rust 1.94.1 for `x86_64-pc-windows-gnu`

```rust

struct A{
t: T
}

fn main() {
let a_u64 = A{t: 0u64};
let a_usize = A{t: 0usize};
println!("Hello, world!");
}
```

output:

```
(lldb) target create "C:\\Coding\\Projects\\mre\\target\\debug\\mre.exe"
warning: (x86_64) C:\Coding\Projects\mre\target\debug\mre.exe unable to locate separate debug file (dwo, dwp). Debugging will be degraded.
(lldb) Current executable set to 'C:\Coding\Projects\mre\target\debug\mre.exe' (x86_64).
(lldb) b main.rs:8
Breakpoint 1: where = mre.exe`mre::main::h56ec42a2ea15dc96 + 22 at main.rs:8:5, address = 0x0000000140001816
(lldb) run
Process 27624 launched: 'C:\Coding\Projects\mre\target\debug\mre.exe' (x86_64)
Process 27624 stopped
* thread #1, name = 'main', stop reason = breakpoint 1.1
frame #0: 0x00007ff7d33f1816 mre.exe`mre::main::h56ec42a2ea15dc96 at main.rs:8:5
5 fn main() {
6 let a_u64 = A{t: 0u64};
7 let a_usize = A{t: 0usize};
-> 8 println!("Hello, world!");
9 }
(lldb) v a_u64
(mre::A) a_u64 = (t = 0)
(lldb) v a_usize
error: Invalid type: Cannot determine size
```

At that point, `a_usize` is "broken" for the remainder of the session. LLDB must be fully exited and reopened and the target relaunched before `a_usize` can be inspected again.

**The order matters**. If you inspect `a_usize` first, `a_u64` breaks instead:

```
(lldb) target create "C:\\Coding\\Projects\\mre\\target\\debug\\mre.exe"
warning: (x86_64) C:\Coding\Projects\mre\target\debug\mre.exe unable to locate separate debug file (dwo, dwp). Debugging will be degraded.
(lldb) Current executable set to 'C:\Coding\Projects\mre\target\debug\mre.exe' (x86_64).
(lldb) b main.rs:8
Breakpoint 1: where = mre.exe`mre::main::h56ec42a2ea15dc96 + 22 at main.rs:8:5, address = 0x0000000140001816
(lldb) run
Process 33856 launched: 'C:\Coding\Projects\mre\target\debug\mre.exe' (x86_64)
Process 33856 stopped
* thread #1, name = 'main', stop reason = breakpoint 1.1
frame #0: 0x00007ff7d33f1816 mre.exe`mre::main::h56ec42a2ea15dc96 at main.rs:8:5
5 fn main() {
6 let a_u64 = A{t: 0u64};
7 let a_usize = A{t:0usize};
-> 8 println!("Hello, world!");
9 }
(lldb) v a_usize
(mre::A) a_usize = (t = 0)
(lldb) v a_u64
error: Invalid type: Cannot determine size
```

One additional quirk is that if you put the type inside an enum (in this case `Option`, a different error appears:

```
(lldb) run
Process 14184 launched: 'C:\Coding\Projects\mre\target\debug\mre.exe' (x86_64)
Process 14184 stopped
* thread #1, name = 'main', stop reason = breakpoint 1.1
frame #0: 0x00007ff78c251828 mre.exe`mre::main::h56ec42a2ea15dc96 at main.rs:9:5
6 let a_u64 = A{t: 0u64};
7 let a_usize = A{t:0usize};
8 let opt_a_usize = Some(A{t:0usize});
-> 9 println!("Hello, world!");
10 }
(lldb) v
error: mre.exe 0x000004f5: DW_TAG_member '__0' refers to type 0x000000000000046a which was unable to be parsed
(lldb) (mre::A) a_u64 = (t = 0)
(core::option::Option>) opt_a_usize = {
$variants$ = {
$variant$0 = ($discr$ = 1, value = core::option::Option>::None:64 @ 0x0000008fa44ff698)
$variant$1 = ($discr$ = 1, value = core::option::Option>::Some:64 @ 0x0000008fa44ff698)
}
}
error: Invalid type: Cannot determine size
```

It's been a while since i've worked on a `TypeSystem` (and I was never very familiar with `TypeSystemClang`), but my best guess is this has to do with the name normalization that's applied to built-ins. Since `usize` on 64-bit targets is equivalent to `u64`, LLDB normalizes them to the same name (`unsigned long long`). This normally isn't an issue for bare primitives because IIRC they're treated special. But when the name is normalized as part of a generic parameter, you get 2 types with identical identifiers (in this case `mre::A`) that point to 2 different DWARF nodes:

Image

Image

I'd imagine that causes some issues somewhere in the pipeline.

If that is the issue, would y'all maybe consider just not normalizing the names at all, even if it's just for non-C/C++ languages that use `TypeSystemClang`? DWARF has a name field, even for primitives. Throwing it out and assuming default C naming schemes defeats the purpose of the node and causes knock-on effects that can be really irritating to deal with. For example, we want to test the type name output of LLDB within Rust's test suite. On my computer `u64` = `unsigned long long`, on the `aarch64-apple` test runner, `u64` = `unsigned long`. Since you're normalizing generic names too, it makes it much more annoying to make target-agnostic test data for tuples and container types (e.g. `(u8, u64)`).

At the very least, the name normalization *is* breaking type lookup behavior. If you have an `A`, type lookups work fine. `A` fails, even when `A` is not present in the DWARF data:

```
Process 32904 launched: 'C:\Coding\Projects\mre\target\debug\mre.exe' (x86_64)
Process 32904 stopped
* thread #1, name = 'main', stop reason = breakpoint 1.1
frame #0: 0x00007ff6f0cc1812 mre.exe`mre::main::h56ec42a2ea15dc96 at main.rs:8:5
5 fn main() {
6 let a_u64 = A{t: 0u64};
7 let a_bool = A{t: true};
-> 8 println!("Hello, world!");
9 }
(lldb) type lookup mre::A
template<> struct A {
private:
bool t;
}
(lldb) script lldb.target.FindFirstType(lldb.frame.var("a_bool").GetTypeName())
template<> struct A {
private:
bool t;
}
(lldb) type lookup mre::A
no type was found in the current language rust matching 'mre::A'; performing a global search across all languages
no type was found matching 'mre::A'
(lldb) script lldb.frame.var("a_u64").GetTypeName()
'mre::A'
(lldb) script lldb.target.FindFirstType(lldb.frame.var("a_u64").GetTypeName())
No value
```

Note that this also shows that neither lookups for `mre::A` *nor* `mre::A` work, so the type effectively cannot be looked up at all.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.