Skip to content

gdb

incomplete type

This happens generally with std::stringstream values. There is no way of printing the value other than to add code to convert it and recompile.

example
#include <iostream>
#include <sstream>

int main(int argc, char *argv[]) {
    std::stringstream ss;
    ss << "Hello world\n";
    std::string s = ss.str();
    std::cout << ss.str();
    return 0;
}

In gdb :

stringstream
1
2
3
4
5
6
p ss
$1 = <incomplete type>
p s 
$2 = "Hello world\n"
p ss.str()
Couldn't find method std::stringstream::str

Mostly you only use the .str() conversion at the very end of operations so in the middle you can never see it's value !?

The solution is to install the -dbg version of the stdlib package and of course make sure you link against it.

First find out what libraries your executable is using :

install debug versions of libc etc
1
2
3
4
5
6
7
ldd ./a.out
linux-vdso.so.1 (0x00007fff888fc000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f2429688000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f2429668000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f24294a0000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f2429358000)
/lib64/ld-linux-x86-64.so.2 (0x00007f24298a0000)

This clearly shows the non-dbg version is still used.

check if we use the dbg versions
1
2
3
4
5
6
7
sudo su 
apt-get install libstdc++6-10-dbg # match the version of libstdc++
dpkg-query -L libstdc++6-10-dbg
# last command will show amongst others :
/usr/lib/x86_64-linux-gnu/debug/libstdc++.a
/usr/lib/x86_64-linux-gnu/debug/libstdc++.so.6.0.28
/usr/lib/x86_64-linux-gnu/debug/libstdc++fs.a

Now don't make the mistake of thinking you have to link against these !! You should start gdb with the correct path to it :

start gdb like this
1
2
3
4
export LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/debug/
ldd ./a.out # see it now find the debug version 
linux-vdso.so.1 (0x00007fff727dc000)
libstdc++.so.6 => //usr/lib/x86_64-linux-gnu/debug/libstdc++.so.6 (0x00007febb1878000)

Without recompiling anything, so this would also work for deep debugging (slower)

start like
LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/debug gdb ./a.out

You will see more messages when running as well ! Now be aware you get a LOT more info now, but now ss.str() will work so it is manageable :

print stringstream content
p ss.str() 
# you will be able to see the buffer somewhere.

Start it normally for basic (faster) debugging !

In normal mode print std::string just prints the content, which is normally what you want, use deep debugging only when needed.

register_libstdcxx_printers

If this message is printed during startup :

error
from libstdcxx.v6 import register_libstdcxx_printers
ModuleNotFoundError: No module named 'libstdcxx'

It seems this is a bug, so without explanation, open up the python file :

gdb python wrapper
vi /usr/share/gdb/auto-load/usr/lib/x86_64-linux-gnu/debug/libstdc++.so.6.0.28-gdb.py

And add this line after the definition of pythondir.

alter
1
2
3
4
5
6
7
8
9
import sys
import gdb
import os
import os.path

pythondir = '/usr/share/gcc-10/python'
libdir = '/usr/lib/x86_64-linux-gnu'

sys.path.append(pythondir)

After this the message is gone and you will probably notice the MUCH more readable objects after this.

gdbinit

Again without explanation, some site recommended these settings for c++ debugging in you .gdbinit :

.gdbinit
1
2
3
4
5
6
7
set confirm off
set print pretty on
set print object on
set print static-members on
set print vtbl on
set print demangle on
set demangle-style gnu-v3

Find out what each one does.

disassemble

Just a neat option inside gdb, you can print the disassembly of a function.

dissambly
(gdb) disassable main
Dump of assembler code for function main(int, char**):
0x00000000000011de <+0>:     push   %rbp
0x00000000000011df <+1>:     mov    %rsp,%rbp
0x00000000000011e2 <+4>:     push   %rbx
0x00000000000011e3 <+5>:     sub    $0x1e8,%rsp
0x00000000000011ea <+12>:    mov    %edi,-0x1e4(%rbp)
0x00000000000011f0 <+18>:    mov    %rsi,-0x1f0(%rbp)
0x00000000000011f7 <+25>:    lea    -0x1c0(%rbp),%rax
0x00000000000011fe <+32>:    mov    %rax,%rdi
0x0000000000001201 <+35>:    callq  0x1040 <_ZNSt7__cxx1118basic_stringstreamIcSt11char_traitsIcESaIcEEC1Ev@plt>
0x0000000000001206 <+40>:    lea    -0x1c0(%rbp),%rax
0x000000000000120d <+47>:    add    $0x10,%rax
0x0000000000001211 <+51>:    lea    0xded(%rip),%rsi        # 0x2005
0x0000000000001218 <+58>:    mov    %rax,%rdi
0x000000000000121b <+61>:    callq  0x1090 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
0x0000000000001220 <+66>:    lea    -0x1e0(%rbp),%rax
0x0000000000001227 <+73>:    lea    -0x1c0(%rbp),%rdx
0x000000000000122e <+80>:    mov    %rdx,%rsi
0x0000000000001231 <+83>:    mov    %rax,%rdi
0x0000000000001234 <+86>:    callq  0x1080 <_ZNKSt7__cxx1118basic_stringstreamIcSt11char_traitsIcESaIcEE3strEv@plt>

watchpoints

In all their wisdom the gdb maintainers have made watchpoints follow scope rules. This is of course a ridiculous decision. This simple example will not work anymore :

example
1
2
3
4
5
6
type_t *new_type()
{
   type_t *newobj = malloc(sizeof(type_t));

   return newobj;
}

Setting watch newobj will say this when leaving scope at the closing }

watchpoints
Watchpoint 2 deleted because the program has left the block in
which its expression is valid.

WTF !?!

You cannot watch an address outside of the function scope in which it was created, actually the EXACT purpose of the watch function.

You now have to set a watch point on the address itself.

watchpoint on the address
1
2
3
4
5
6
7
8
# break somewhere in you program
p newobj
# or if you need a member 
p &newobj->member
(type) 0x555555772e78
# directly will not work, you need to prepend a *
watch *0x555555772e78
c

This works !