Skip to content
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

lldb cannot print std::string #60994

Open
fonqL opened this issue Feb 25, 2023 · 19 comments
Open

lldb cannot print std::string #60994

fonqL opened this issue Feb 25, 2023 · 19 comments
Labels

Comments

@fonqL
Copy link

fonqL commented Feb 25, 2023

OS: Ubuntu 22.04

$ clang -v
Ubuntu clang version 15.0.6
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/11
Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/12
Selected GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/12
Candidate multilib: .;@m64
Candidate multilib: 32;@m32
Candidate multilib: x32;@mx32
Selected multilib: .;@m64
$ lldb --version
lldb version 15.0.6

main.cpp

#include <iostream>
#include <vector>
using namespace std;

int main(int argc, char **argv) {
  int a = 0;
  std::string s = "1234555";
  std::vector<int> c{1, 2, 3, 4, 5};

  s[0] = '9';
  std::cout << s;
}

Compile:

$ g++ -g -O0 main.cpp -o gcc_out
$ clang++ -g -O0 main.cpp -o clang_out

LLDB can print std::string when using gcc_out, but shows error when using clang_out.

(lldb) print s
error: expression failed to parse:
error: <user expression 0>:1:1: incomplete type 'std::string' (aka 'std::basic_string<>') where a complete type is required
s
^

When using CodeLLDB to debug in VScode, it shows error: summary string parsing error

I know a workaround is to add -fstandalone-debug flag, but that's a bit annoying.
It is said that another solution is to install a debug version of libstdc++. I think I have already installed it but it not works. I wonder how to make it work, or if there is another way to solve it.

$ dpkg --list|grep libstdc++
ii  libstdc++-11-dev:amd64               11.3.0-1ubuntu1~22.04                   amd64        GNU Standard C++ Library v3 (development files)
ii  libstdc++-12-dev:amd64               12.1.0-2ubuntu1~22.04                   amd64        GNU Standard C++ Library v3 (development files)
ii  libstdc++-12-doc                     12.1.0-2ubuntu1~22.04                   all          GNU Standard C++ Library v3 (documentation files)
ii  libstdc++6:amd64                     12.1.0-2ubuntu1~22.04                   amd64        GNU Standard C++ Library v3
ii  libstdc++6-12-dbg:amd64              12.1.0-2ubuntu1~22.04                   amd64        GNU Standard C++ Library v3 (debug build)
@llvmbot
Copy link
Collaborator

llvmbot commented Feb 25, 2023

@llvm/issue-subscribers-lldb

@EugeneZelenko
Copy link
Contributor

Could you please try 16 or main branch?

@fonqL
Copy link
Author

fonqL commented Feb 25, 2023

Yes.

$ clang -v
Ubuntu clang version 17.0.0 (++20230224031317+a52332df5d1f-1~exp1~20230224151434.767)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/11
Selected GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/11
Candidate multilib: .;@m64
Selected multilib: .;@m64
$ lldb --version
lldb version 17.0.0

Problem is still here. Only a little different: cmd lldb show (std::string) $0 = error: summary string parsing error and not incomplete type as before.

@Michael137
Copy link
Member

Could you put log enable lldb expr types -f /tmp/lldb.log into your ~/.lldbinit file and rerun the failing case. Then attach the /tmp/lldb.log?

@fonqL
Copy link
Author

fonqL commented Feb 25, 2023

lldb.log

@fonqL
Copy link
Author

fonqL commented Feb 26, 2023

This is the log when lldb debugging gcc_out. (std::string can be printed without error.)
gcc_lldb.log

@dwblaikie
Copy link
Collaborator

You're probably missing debug info for std::string as it's part of your standard library's package, not the code you built that uses std::string.

If you're using libstdc++ for instance, on ubuntu you'd need to install libstdc++6-dbgsym or similar.

Note that Clang does an optimization for debug info that GCC doesn't do, which is why the GCC/Clang divergence here - but GCC does similar/equivalent optimizations - for instance, if you try to debug/print a std::ifstream with GCC built debug info or clang built debug info (without -fstandalone-debug) you'll see it's "declared but not defined" too.

Without the -dbgsym package installed:

Compiled with clang (in is a std::ifstream and x is a std::string):

(gdb) p in
$1 = <incomplete type>
(gdb) p x
Python Exception <class 'gdb.error'> There is no member named _M_dataplus.:
$2 =

Built with gcc:

(gdb) p in
$1 = <incomplete type>
(gdb) p x
$2 = ""

And then with the package installed:
Clang:

(gdb) p in
$1 = (std::basic_ifstream<char, std::char_traits<char> >) {
  <std::basic_istream<char, std::char_traits<char> >> = {
    <std::basic_ios<char, std::char_traits<char> >> = {
      <std::ios_base> = {
        _vptr.ios_base = 0x7ffff7e0fe68 <vtable for std::basic_ifstream<char, std::char_traits<char> >+64>,
        _M_precision = 6,
        ...
(gdb) p x
$2 = ""

GCC:

(gdb) p in
$1 = (std::basic_ifstream<char, std::char_traits<char> >) {
  <std::basic_istream<char, std::char_traits<char> >> = {
    <std::basic_ios<char, std::char_traits<char> >> = {
      <std::ios_base> = {
        _vptr.ios_base = 0x7ffff7e0fe68 <vtable for std::basic_ifstream<char, std::char_traits<char> >+64>,
        _M_precision = 6,
        ...
(gdb) p x
$2 = ""

LLDB does have some more fussy issues about needing definitions to be compiled into the same library - but I /think/ at least this case should be ironed out by now/should work... but maybe not? (at least my internal release lldb crashed, and my just-built debug lldb crashed in a different way)

@dwblaikie
Copy link
Collaborator

Here's a lightning talk I gave that discusses these issues pretty closely: https://www.youtube.com/watch?v=XvkLHIASlp8

@jimingham
Copy link
Collaborator

jimingham commented Feb 28, 2023 via email

@fonqL
Copy link
Author

fonqL commented Feb 28, 2023

Now I have installed the libstdc++6-dbgsym following this guide: Debug Symbol Package

$ dpkg --list | grep libstdc++
ii  libstdc++-11-dev:amd64            11.3.0-1ubuntu1~22.04            amd64        GNU Standard C++ Library v3 (development files)
ii  libstdc++6:amd64                  12-20220319-1ubuntu1             amd64        GNU Standard C++ Library v3
ii  libstdc++6-11-dbg:amd64           11.3.0-1ubuntu1~22.04            amd64        GNU Standard C++ Library v3 (debug build)
ii  libstdc++6-dbgsym:amd64           12-20220319-1ubuntu1             amd64        debug symbols for libstdc++6

But it still cannot print std::string and reports (std::string) $0 = error: summary string parsing error.
But gdb can print std::string when gdb clang_out now.

I'm confused.

Here is lldb log:
lldb.log

@dwblaikie
Copy link
Collaborator

(std::string) $0 = error: summary string parsing error

I'm guessing this might be related to lldb not having a pretty printer for this version of libstdc++`s std::string, maybe?

If you run image lookup --type "basic_string<char, std::char_traits<char>, std::allocator<char> >" what does that print?

@jimingham
Copy link
Collaborator

jimingham commented Feb 28, 2023 via email

@fonqL
Copy link
Author

fonqL commented Mar 1, 2023

If you run image lookup --type "basic_string<char, std::char_traits, std::allocator >" what does that print?

$ lldb clang_out
(lldb) image lookup --type "basic_string<char, std::char_traits<char>, std::allocator<char> >"
1 match found in /home/ubuntu/clang_out:
id = {0x00000040}, name = "basic_string<char, std::char_traits<char>, std::allocator<char> >", qualified = "std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >", byte-size = 32, decl = basic_string.tcc:1627, compiler_type = "template<> class basic_string<char, std::char_traits<char>, std::allocator<char>> {
    typedef __gnu_cxx::__alloc_traits<std::allocator<char> >::size_type size_type;
    static const std::basic_string<char>::size_type npos = 18446744073709551615UL;
    struct _Alloc_hider {
    };
    std::basic_string<char>::_Alloc_hider _M_dataplus;
    std::basic_string<char>::size_type _M_string_length;
    union {
        char _M_local_buf[16];
        std::basic_string<char>::size_type _M_allocated_capacity;
    };
    typedef std::basic_string_view<char> __sv_type;
public:
    static std::basic_string<char>::__sv_type _S_to_string_view(std::basic_string<char>::__sv_type);
    struct __sv_wrapper;
    explicit basic_string(std::basic_string<char>::__sv_wrapper, const std::allocator<char> &);
    typedef __gnu_cxx::__alloc_traits<std::allocator<char> >::pointer pointer;
    void _M_data(std::basic_string<char>::pointer);
    void _M_length(std::basic_string<char>::size_type);
    std::basic_string<char>::pointer _M_data() const;
    std::basic_string<char>::pointer _M_local_data();
    typedef __gnu_cxx::__alloc_traits<std::allocator<char> >::const_pointer const_pointer;
    std::basic_string<char>::const_pointer _M_local_data() const;
    void _M_capacity(std::basic_string<char>::size_type);
    void _M_set_length(std::basic_string<char>::size_type);
    bool _M_is_local() const;
    std::basic_string<char>::pointer _M_create(std::basic_string<char>::size_type &, std::basic_string<char>::size_type);
    void _M_dispose();
    void _M_destroy(std::basic_string<char>::size_type);
    void _M_construct_aux_2(std::basic_string<char>::size_type, char);
    void _M_construct(std::basic_string<char>::size_type, char);
    typedef __gnu_cxx::__alloc_traits<std::allocator<char> >::rebind<char>::other _Char_alloc_type;
    typedef std::basic_string<char>::_Char_alloc_type allocator_type;
    std::basic_string<char>::allocator_type &_M_get_allocator();
    const std::basic_string<char>::allocator_type &_M_get_allocator() const;
    std::basic_string<char>::size_type _M_check(std::basic_string<char>::size_type, const char *) const;
    void _M_check_length(std::basic_string<char>::size_type, std::basic_string<char>::size_type, const char *) const;
    std::basic_string<char>::size_type _M_limit(std::basic_string<char>::size_type, std::basic_string<char>::size_type) const;
    bool _M_disjunct(const char *) const;
    static void _S_copy(char *, const char *, std::basic_string<char>::size_type);
    static void _S_move(char *, const char *, std::basic_string<char>::size_type);
    static void _S_assign(char *, std::basic_string<char>::size_type, char);
    typedef __gnu_cxx::__normal_iterator<char *, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > iterator;
    static void _S_copy_chars(char *, std::basic_string<char>::iterator, std::basic_string<char>::iterator);
    typedef __gnu_cxx::__normal_iterator<const char *, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > const_iterator;
    static void _S_copy_chars(char *, std::basic_string<char>::const_iterator, std::basic_string<char>::const_iterator);
    static void _S_copy_chars(char *, char *, char *);
    static void _S_copy_chars(char *, const char *, const char *);
    static int _S_compare(std::basic_string<char>::size_type, std::basic_string<char>::size_type);
    void _M_assign(const std::basic_string<char> &);
    void _M_mutate(std::basic_string<char>::size_type, std::basic_string<char>::size_type, const char *, std::basic_string<char>::size_type);
    void _M_erase(std::basic_string<char>::size_type, std::basic_string<char>::size_type);
    basic_string();
    explicit basic_string(const std::allocator<char> &);
    basic_string(const std::basic_string<char> &);
    basic_string(const std::basic_string<char> &, std::basic_string<char>::size_type, const std::allocator<char> &);
    basic_string(const std::basic_string<char> &, std::basic_string<char>::size_type, std::basic_string<char>::size_type);
    basic_string(const std::basic_string<char> &, std::basic_string<char>::size_type, std::basic_string<char>::size_type, const std::allocator<char> &);
    basic_string(const char *, std::basic_string<char>::size_type, const std::allocator<char> &);
    basic_string(std::basic_string<char> &&);
    basic_string(std::initializer_list<char>, const std::allocator<char> &);
    basic_string(const std::basic_string<char> &, const std::allocator<char> &);
    basic_string(std::basic_string<char> &&, const std::allocator<char> &);
    ~basic_string();
    std::basic_string<char> &operator=(const std::basic_string<char> &);
    std::basic_string<char> &operator=(const char *);
    std::basic_string<char> &operator=(char);
    std::basic_string<char> &operator=(std::basic_string<char> &&);
    std::basic_string<char> &operator=(std::initializer_list<char>);
    operator basic_string_view() const;
    std::basic_string<char>::iterator begin();
    std::basic_string<char>::const_iterator begin() const;
    std::basic_string<char>::iterator end();
    std::basic_string<char>::const_iterator end() const;
    typedef std::reverse_iterator<__gnu_cxx::__normal_iterator<char *, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > reverse_iterator;
    std::basic_string<char>::reverse_iterator rbegin();
    typedef std::reverse_iterator<__gnu_cxx::__normal_iterator<const char *, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > const_reverse_iterator;
    std::basic_string<char>::const_reverse_iterator rbegin() const;
    std::basic_string<char>::reverse_iterator rend();
    std::basic_string<char>::const_reverse_iterator rend() const;
    std::basic_string<char>::const_iterator cbegin() const;
    std::basic_string<char>::const_iterator cend() const;
    std::basic_string<char>::const_reverse_iterator crbegin() const;
    std::basic_string<char>::const_reverse_iterator crend() const;
    std::basic_string<char>::size_type size() const;
    std::basic_string<char>::size_type length() const;
    std::basic_string<char>::size_type max_size() const;
    void resize(std::basic_string<char>::size_type, char);
    void resize(std::basic_string<char>::size_type);
    void shrink_to_fit();
    std::basic_string<char>::size_type capacity() const;
    void reserve(std::basic_string<char>::size_type);
    void reserve();
    void clear();
    bool empty() const;
    typedef __gnu_cxx::__alloc_traits<std::allocator<char> >::const_reference const_reference;
    std::basic_string<char>::const_reference operator[](std::basic_string<char>::size_type) const;
    typedef __gnu_cxx::__alloc_traits<std::allocator<char> >::reference reference;
    std::basic_string<char>::reference operator[](std::basic_string<char>::size_type);
    std::basic_string<char>::const_reference at(std::basic_string<char>::size_type) const;
    std::basic_string<char>::reference at(std::basic_string<char>::size_type);
    std::basic_string<char>::reference front();
    std::basic_string<char>::const_reference front() const;
    std::basic_string<char>::reference back();
    std::basic_string<char>::const_reference back() const;
    std::basic_string<char> &operator+=(const std::basic_string<char> &);
    std::basic_string<char> &operator+=(const char *);
    std::basic_string<char> &operator+=(char);
    std::basic_string<char> &operator+=(std::initializer_list<char>);
    std::basic_string<char> &append(const std::basic_string<char> &);
    std::basic_string<char> &append(const std::basic_string<char> &, std::basic_string<char>::size_type, std::basic_string<char>::size_type);
    std::basic_string<char> &append(const char *, std::basic_string<char>::size_type);
    std::basic_string<char> &append(const char *);
    std::basic_string<char> &append(std::basic_string<char>::size_type, char);
    std::basic_string<char> &append(std::initializer_list<char>);
    void push_back(char);
    std::basic_string<char> &assign(const std::basic_string<char> &);
    std::basic_string<char> &assign(std::basic_string<char> &&);
    std::basic_string<char> &assign(const std::basic_string<char> &, std::basic_string<char>::size_type, std::basic_string<char>::size_type);
    std::basic_string<char> &assign(const char *, std::basic_string<char>::size_type);
    std::basic_string<char> &assign(const char *);
    std::basic_string<char> &assign(std::basic_string<char>::size_type, char);
    std::basic_string<char> &assign(std::initializer_list<char>);
    std::basic_string<char>::iterator insert(std::basic_string<char>::const_iterator, std::basic_string<char>::size_type, char);
    std::basic_string<char>::iterator insert(std::basic_string<char>::const_iterator, std::initializer_list<char>);
    std::basic_string<char> &insert(std::basic_string<char>::size_type, const std::basic_string<char> &);
    std::basic_string<char> &insert(std::basic_string<char>::size_type, const std::basic_string<char> &, std::basic_string<char>::size_type, std::basic_string<char>::size_type);
    std::basic_string<char> &insert(std::basic_string<char>::size_type, const char *, std::basic_string<char>::size_type);
    std::basic_string<char> &insert(std::basic_string<char>::size_type, const char *);
    std::basic_string<char> &insert(std::basic_string<char>::size_type, std::basic_string<char>::size_type, char);
    typedef std::basic_string<char>::const_iterator __const_iterator;
    std::basic_string<char>::iterator insert(std::basic_string<char>::__const_iterator, char);
    std::basic_string<char> &erase(std::basic_string<char>::size_type, std::basic_string<char>::size_type);
    std::basic_string<char>::iterator erase(std::basic_string<char>::__const_iterator);
    std::basic_string<char>::iterator erase(std::basic_string<char>::__const_iterator, std::basic_string<char>::__const_iterator);
    void pop_back();
    std::basic_string<char> &replace(std::basic_string<char>::size_type, std::basic_string<char>::size_type, const std::basic_string<char> &);
    std::basic_string<char> &replace(std::basic_string<char>::size_type, std::basic_string<char>::size_type, const std::basic_string<char> &, std::basic_string<char>::size_type, std::basic_string<char>::size_type);
    std::basic_string<char> &replace(std::basic_string<char>::size_type, std::basic_string<char>::size_type, const char *, std::basic_string<char>::size_type);
    std::basic_string<char> &replace(std::basic_string<char>::size_type, std::basic_string<char>::size_type, const char *);
    std::basic_string<char> &replace(std::basic_string<char>::size_type, std::basic_string<char>::size_type, std::basic_string<char>::size_type, char);
    std::basic_string<char> &replace(std::basic_string<char>::__const_iterator, std::basic_string<char>::__const_iterator, const std::basic_string<char> &);
    std::basic_string<char> &replace(std::basic_string<char>::__const_iterator, std::basic_string<char>::__const_iterator, const char *, std::basic_string<char>::size_type);
    std::basic_string<char> &replace(std::basic_string<char>::__const_iterator, std::basic_string<char>::__const_iterator, const char *);
    std::basic_string<char> &replace(std::basic_string<char>::__const_iterator, std::basic_string<char>::__const_iterator, std::basic_string<char>::size_type, char);
    std::basic_string<char> &replace(std::basic_string<char>::__const_iterator, std::basic_string<char>::__const_iterator, char *, char *);
    std::basic_string<char> &replace(std::basic_string<char>::__const_iterator, std::basic_string<char>::__const_iterator, const char *, const char *);
    std::basic_string<char> &replace(std::basic_string<char>::__const_iterator, std::basic_string<char>::__const_iterator, std::basic_string<char>::iterator, std::basic_string<char>::iterator);
    std::basic_string<char> &replace(std::basic_string<char>::__const_iterator, std::basic_string<char>::__const_iterator, std::basic_string<char>::const_iterator, std::basic_string<char>::const_iterator);
    std::basic_string<char> &replace(std::basic_string<char>::const_iterator, std::basic_string<char>::const_iterator, std::initializer_list<char>);
    std::basic_string<char> &_M_replace_aux(std::basic_string<char>::size_type, std::basic_string<char>::size_type, std::basic_string<char>::size_type, char);
    std::basic_string<char> &_M_replace(std::basic_string<char>::size_type, std::basic_string<char>::size_type, const char *, const std::basic_string<char>::size_type);
    std::basic_string<char> &_M_append(const char *, std::basic_string<char>::size_type);
    std::basic_string<char>::size_type copy(char *, std::basic_string<char>::size_type, std::basic_string<char>::size_type) const;
    void swap(std::basic_string<char> &);
    const char *c_str() const;
    const char *data() const;
    char *data();
    std::basic_string<char>::allocator_type get_allocator() const;
    std::basic_string<char>::size_type find(const char *, std::basic_string<char>::size_type, std::basic_string<char>::size_type) const;
    std::basic_string<char>::size_type find(const std::basic_string<char> &, std::basic_string<char>::size_type) const;
    std::basic_string<char>::size_type find(const char *, std::basic_string<char>::size_type) const;
    std::basic_string<char>::size_type find(char, std::basic_string<char>::size_type) const;
    std::basic_string<char>::size_type rfind(const std::basic_string<char> &, std::basic_string<char>::size_type) const;
    std::basic_string<char>::size_type rfind(const char *, std::basic_string<char>::size_type, std::basic_string<char>::size_type) const;
    std::basic_string<char>::size_type rfind(const char *, std::basic_string<char>::size_type) const;
    std::basic_string<char>::size_type rfind(char, std::basic_string<char>::size_type) const;
    std::basic_string<char>::size_type find_first_of(const std::basic_string<char> &, std::basic_string<char>::size_type) const;
    std::basic_string<char>::size_type find_first_of(const char *, std::basic_string<char>::size_type, std::basic_string<char>::size_type) const;
    std::basic_string<char>::size_type find_first_of(const char *, std::basic_string<char>::size_type) const;
    std::basic_string<char>::size_type find_first_of(char, std::basic_string<char>::size_type) const;
    std::basic_string<char>::size_type find_last_of(const std::basic_string<char> &, std::basic_string<char>::size_type) const;
    std::basic_string<char>::size_type find_last_of(const char *, std::basic_string<char>::size_type, std::basic_string<char>::size_type) const;
    std::basic_string<char>::size_type find_last_of(const char *, std::basic_string<char>::size_type) const;
    std::basic_string<char>::size_type find_last_of(char, std::basic_string<char>::size_type) const;
    std::basic_string<char>::size_type find_first_not_of(const std::basic_string<char> &, std::basic_string<char>::size_type) const;
    std::basic_string<char>::size_type find_first_not_of(const char *, std::basic_string<char>::size_type, std::basic_string<char>::size_type) const;
    std::basic_string<char>::size_type find_first_not_of(const char *, std::basic_string<char>::size_type) const;
    std::basic_string<char>::size_type find_first_not_of(char, std::basic_string<char>::size_type) const;
    std::basic_string<char>::size_type find_last_not_of(const std::basic_string<char> &, std::basic_string<char>::size_type) const;
    std::basic_string<char>::size_type find_last_not_of(const char *, std::basic_string<char>::size_type, std::basic_string<char>::size_type) const;
    std::basic_string<char>::size_type find_last_not_of(const char *, std::basic_string<char>::size_type) const;
    std::basic_string<char>::size_type find_last_not_of(char, std::basic_string<char>::size_type) const;
    std::basic_string<char> substr(std::basic_string<char>::size_type, std::basic_string<char>::size_type) const;
    int compare(const std::basic_string<char> &) const;
    int compare(std::basic_string<char>::size_type, std::basic_string<char>::size_type, const std::basic_string<char> &) const;
    int compare(std::basic_string<char>::size_type, std::basic_string<char>::size_type, const std::basic_string<char> &, std::basic_string<char>::size_type, std::basic_string<char>::size_type) const;
    int compare(const char *) const;
    int compare(std::basic_string<char>::size_type, std::basic_string<char>::size_type, const char *) const;
    int compare(std::basic_string<char>::size_type, std::basic_string<char>::size_type, const char *, std::basic_string<char>::size_type) const;
}"

I find this is different from lldb gcc_out.
lldb_gcc_image_lookup.txt

If you have a variable of a type you suspect has a formatter, you can find out whether that's true and what the formatter is with

Result of lldb clang_out and lldb gcc_out are the same.

(lldb) type summary info s
summary applied to (std::string) s is: `${var._M_dataplus._M_p}` (hide value)

@dwblaikie
Copy link
Collaborator

Yep, so it looks like you got the debug info working and the debugger is finding the definition in the library from the declaration in the main program.

Oh, hmm, but the _Alloc_hider isn't being resolved to a definition? That's noteworthy.

I can reproduce the failure on my machine, and the same image lookup result.

It looks like the _Alloc_hider data is in the DWARF somewhere, but lldb hasn't found it/resolved it from the declaration. Looking up the type directly does hit:

(lldb)  image lookup --type "basic_string<char, std::char_traits<char>, std::allocator<char> >::_Alloc_hider"
2 matches found in /lib/x86_64-linux-gnu/libstdc++.so.6:
id = {0x00000dab}, name = "_Alloc_hider", qualified = "std::basic_string<char, std::char_traits<char>, std::allocator<char> >::_Alloc_hider", byte-size = 8, decl = cow_string.h:315:14, compiler_type = "struct _Alloc_hider : public std::allocator<char> {
    char *_M_p;
    _Alloc_hider(char *, const std::allocator<char> &);
}"
id = {0x0005f910}, name = "_Alloc_hider", qualified = "std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_Alloc_hider", byte-size = 8, decl = basic_string.h:192:14, compiler_type = "struct _Alloc_hider : public std::allocator<char> {
    std::__cxx11::basic_string<char>::pointer _M_p;
    _Alloc_hider(std::__cxx11::basic_string<char>::pointer, const std::allocator<char> &);
}"

But the failure to associate that definition with the declaration inside basic_string is a problem/probably a bug in lldb, I'd think. Let's see about a smaller repro...

@dwblaikie
Copy link
Collaborator

(oh, and I should say - it works for gdb, for me, once I've got the -debug/dev/whatever package installed - which does point to an lldb bug, regardless of the specifics, probably)

But still, good to figure out the specifics...

@dwblaikie
Copy link
Collaborator

Oh, poked around in more detail - I guess libstdc++ doesn't have an explicit instantiation decl/def for basic_string, so we do get the definition of basic_string in the clang-built usage code, but the _Alloc_hider is maybe homed into the .so/debug file maybe because of ctor homing.

So... I guess that's the lldb issue - a nested type defined in a shared library where the outer type is defined in the immediate program.

Let's test that...

@dwblaikie
Copy link
Collaborator

Yep. Looks like that's it - lldb doesn't do a good job resolving nested types when defined across dynamic library/executable boundaries:

(lldb) p o
(outer) $0 = (i = outer::inner @ 0x000014133fc7ed88)
(lldb) im loo --type outer
Best match found in /usr/local/google/home/blaikie/dev/scratch/llvm60994/a.out:
id = {0x0000002b}, name = "outer", byte-size = 4, decl = outer.h:1, compiler_type = "struct outer {
    struct inner {
    };
    outer::inner i;
}"

(lldb) im loo --type outer::inner
1 match found in /usr/local/google/home/blaikie/dev/scratch/llvm60994/libouter.so:
id = {0x00000032}, name = "inner", qualified = "outer::inner", byte-size = 4, decl = outer.h:2, compiler_type = "struct inner {
    int i;
    inner();
}"

Reproduced with this:
outer.h

struct outer {
  struct inner {
    int i;
    inner();
  };
  inner i;
};

inner.cpp:

#include "outer.h"
outer::inner::inner() {}

main.cpp:

#include "outer.h"
int main() {
  outer o;
}
 clang++-tot -shared -o libouter.so inner.cpp -g
 clang++-tot -L. main.cpp -g -louter

I was going to suggest this would be tru efor non-nested types too, but that doesn't seem to be the case - they get resolved correctly.

But also the failure mode is different from the non-nested type case, where the type is still declared-but-not-defined in the current CU/executable, but the definition is missing elsewhere. In that case you usually get a "error: <user expression 1>:1:1: incomplete type 't1' where a complete type is required" Whereas in this nested type isn't linked to its definition case, you get this:

(lldb) p i(outer::inner) $0 = {}

If the shared library doesn't have debug info - /then/ you get the "complete type is required" thing... oooh, I think I know what's going on:

It also looks like it's somehow relevant that outer has a member variable of type inner. Without that... oh, without that there's no declaration of... no, there's still a declaration of "inner" in "outer" either way.

I think I know why that member variable is load bearing - it's causing lldb to synthesize a definition for the inner type. We can reproduce this without nested types, maybe..

Yeah:

struct t1 { t1(); };
struct t2 { t1 v1; };
int main() {
  t2 *v1;
  t1 *v2 = &v1->v1;
}

So this causes t2 to be defined (owing to the pointer dereference of a v2*) which requires a definition of t1, but there is none, so lldb imagines an empty one.

my guess is that imagined empty definition then inhibits resolving the definition correctly to one contained in another dynamic library.

@safinaskar
Copy link

I get exactly same bug. Here are steps to reproduce the bug in the form of Dockerfile:

# 20230227 = 20230227T000000Z = 20230226T090712Z
FROM debian:sid-20230227
ENV LC_ALL C.UTF-8

# https://github.com/fepitre/debian-snapshot , https://debconf21.debconf.org/talks/22-making-use-of-snapshotdebianorg-for-fun-and-profit/
RUN sed   -i 's~^URIs:.*$~URIs: http://snapshot.notset.fr/archive/debian/20230226T090712Z~' /etc/apt/sources.list.d/debian.sources

RUN echo 'Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/02acquire-check-valid-until
RUN apt-get update && apt-get install -y apt-utils whiptail
RUN apt-get update && apt-get install -y less mc aptitude man-db wget ca-certificates gnupg
RUN wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add -
RUN echo deb http://apt.llvm.org/unstable/ llvm-toolchain-16 main >> /etc/apt/sources.list
RUN apt-get update && apt-get install -y clang-16 lldb-16 libstdc++6-12-dbg libc++-16-dev libc++abi-16-dev g++ gdb

# https://michael.stapelberg.ch/posts/2019-02-15-debian-debugging-devex/
RUN echo deb http://snapshot.debian.org/archive/debian-debug/20230226T090712Z sid-debug main >> /etc/apt/sources.list

RUN apt-get update && apt-get install -y libc6-dbg libgcc-s1-dbgsym libstdc++6-dbgsym
RUN echo '#include <string>' >> o.cpp
RUN echo 'int' >> o.cpp
RUN echo 'main (void)' >> o.cpp
RUN echo '{' >> o.cpp
RUN echo '  std::string a = "a";' >> o.cpp
RUN echo '  a = "b";' >> o.cpp
RUN echo '}' >> o.cpp
RUN clang++-16 -g -o o o.cpp
RUN lldb-16 ./o -o 'br set -l 6' -o 'r' -o 'p a' < /dev/null # "summary string parsing error"
RUN clang++-16 -std=gnu++17 -g -o o o.cpp
RUN lldb-16 ./o -o 'br set -l 6' -o 'r' -o 'p a' < /dev/null # "summary string parsing error"
RUN clang++-16 -std=gnu++20 -g -o o o.cpp
RUN lldb-16 ./o -o 'br set -l 6' -o 'r' -o 'p a' < /dev/null # Works
RUN clang++-16 -std=gnu++14 -g -o o o.cpp
RUN lldb-16 ./o -o 'br set -l 6' -o 'r' -o 'p a' < /dev/null || : # Backtrace
RUN clang++-16 -fstandalone-debug -g -o o o.cpp
RUN lldb-16 ./o -o 'br set -l 6' -o 'r' -o 'p a' < /dev/null # Works
RUN clang++-16 -stdlib=libc++ -g -o o o.cpp
RUN lldb-16 ./o -o 'br set -l 6' -o 'r' -o 'p a' < /dev/null # Works
RUN g++ -g -o o o.cpp
RUN lldb-16 ./o -o 'br set -l 6' -o 'r' -o 'p a' < /dev/null # Works
RUN clang++-16 -g -o o o.cpp
RUN printf 'br 6\nr\np a\n' | gdb ./o # Works
  • Dockerfile is written in absolutely reproducible way. It is tied to particular Debian repo snapshot. But llvm is installed from apt.llvm.org , i. e. minor llvm version is not fixed
  • I installed not only libstdc++6-12-dbg, but also libstdc++6-dbgsym using instructions from https://michael.stapelberg.ch/posts/2019-02-15-debian-debugging-devex/ . And despite this I still get the bug
  • Everything was tested using LLVM 16

As you can see, there are zillions of various workarounds, i. e. zillions of way to make this bug disappear. Here we go:

  • Using -std=gnu++20 instead of default -std=gnu++17 (default for clang 16 is -std=gnu++17). Also note that -std=gnu++14 makes lldb crash
  • Using -fstandalone-debug
  • Using -stdlib=libc++ instead of default -stdlib=libstdc++
  • Using g++ (with lldb) instead of clang++. (Default for my version of g++ is -std=gnu++17, too)
  • Using gdb (with clang) instead of lldb

@dwblaikie
Copy link
Collaborator

Using -std=gnu++20 instead of default -std=gnu++17 (default for clang 16 is -std=gnu++17). Also note that -std=gnu++14 makes lldb crash

Ah, in that case the _Alloc_hider type doesn't get omitted from the usage - not sure about the exact usage that leads to this difference between the 17 and 20 versions of the code.

Ah, the ctor becomes constexpr - so it isn't subject to Clang's DWARF ctor homing strategy.

That doesn't quite explain it - the 17 version does have the ctor for the type - possible there's a clang bug here about type homing.

Yeah, looks like this is being omitted due to ctor homing - _Alloc_hider's ctor (guess that's the allocation it's hiding... ) is defined in the library, not in the usage.

Using -fstandalone-debug

Includes extra info that avoids the "type defined in this binary/shared library depends on a type defined in another binary/shared library".

Using -stdlib=libc++ instead of default -stdlib=libstdc++

Different implementation details about what ends up where for sure.

Using g++ (with lldb) instead of clang++. (Default for my version of g++ is -std=gnu++17, too)

Yep, g++ won't omit types related to ctor definitions (it does do this with vtables, which is similar but doesn't happen to tickle this particular instance) decls/defs, so the extra type info is enough for lldb to work.

Using gdb (with clang) instead of lldb

Yep, gdb doesn't have a problem going looking for a type definition in another shared library at any point - lldb won't do it (& will instead synthesize a fictional/incorrect/trivial type definition) when it's needed to complete the AST of one binary (it'll do it in other cases - like during expression evaluation - printing a dereferenced pointer, etc).

arichardson added a commit to arichardson/upstream-llvm-project that referenced this issue Feb 26, 2024
Currently, it is not possible to sensibly debug LLVM using shared library
builds with LLDB since types are not resolved across shared library
boundaries. As a workaround we can enable -glldb to emit standalone debug
info which ensures we get a working debugging experience at the expense of
increased disk usage.

See llvm#60994 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

7 participants