Monday, June 26, 2017

GDB-QuickStart -- 'Things That Make You Go Hmm'

Seems over the years I find myself in and out of using a debugger.  Largely driven by the system and tools available, sometimes the use of a debugger is essential, other times a reliance on debug logs is the way to go.

I found myself back into the GDB thick of things over the past couple weeks, shaking off a good deal of GDB rust.  I figured a worthwhile effort would be to document a quick re-introduction, by no means intended on being exhaustive but a starting point for potentially follow-on posts.

Let's start with the simplest of examples, a single source file consisting of a series of functions making up a call stack;

Let's start with a simple main source file;
$ cat main.cpp
#include <stdio.h>

void function05()
{
  printf("(%s:%d) function05()\n",__FILE__,__LINE__);
}

void function04()
{
  printf("(%s:%d) function04()\n",__FILE__,__LINE__);
  function05();
}

void function03()
{
  printf("(%s:%d) function03()\n",__FILE__,__LINE__);
  function04();
}

void function02()
{
  printf("(%s:%d) function02()\n",__FILE__,__LINE__);
  function03();
}

void function01()
{
  printf("(%s:%d) function01()\n",__FILE__,__LINE__);
  function02();
}

int main(int argc, char* argv[])
{
  printf("(%s:%d) main process initializing\n",__FILE__,__LINE__);
  function01();
  printf("(%s:%d) main process terminating\n",__FILE__,__LINE__);
}

Compile it with debugging symbols (e.g. '-g' g++ flag);
$ g++ -g -o3 main.cpp -o main
And now run the debugger with the executable; 1st listing all functions, then set a breakpoint on a couple function, followed by running the executable and continuing on after each encountered breakpoint;
$ gdb main
Reading symbols from main...done.
(gdb) info functions
All defined functions:

File main.cpp:
void function01();
void function02();
void function03();
void function04();
void function05();
int main(int, char**);

Non-debugging symbols:
0x00000000004003c8  _init
0x0000000000400400  printf@plt
0x0000000000400410  __libc_start_main@plt
0x0000000000400430  _start
(gdb) break main
Breakpoint 1 at 0x4005e9: file main.cpp, line 34.
(gdb) break function01
Breakpoint 2 at 0x4005b9: file main.cpp, line 28.
(gdb) run
Starting program: /home/lipeltgm/svn/mal/main/trunk/personal/lipeltgm/blog/Gdb-Qs/main 

Breakpoint 1, main (argc=1, argv=0x7fffffffdf18) at main.cpp:34
34  printf("(%s:%d) main process initializing\n",__FILE__,__LINE__);
(gdb) cont
Continuing.
(main.cpp:34) main process initializing

Breakpoint 2, function01 () at main.cpp:28
28  printf("(%s:%d) function01()\n",__FILE__,__LINE__);
(gdb) cont
Continuing.
(main.cpp:28) function01()
(main.cpp:22) function02()
(main.cpp:16) function03()
(main.cpp:10) function04()
(main.cpp:5) function05()
(main.cpp:36) main process terminating
[Inferior 1 (process 7371) exited normally]
(gdb) quit
$

Once all the symbol tables are loaded, the 'info functions' command may very well be your best friend.  For small and simple programs, sorting through a list of a dozen or so methods is easy pickings, but for more complex and larger programs you'll find it burden-some.  The 'info functions' allows specifying a regular expression to provide a filter for just such a reason.  Issuing a 'info functions function0' will show all functions that satisfy the 'function0' regular expression, namely the functions 'function01(), function02(), function03(), function04(), function05()'.  A regular expression can take the form of a namespace, a class, a function name,....and you'll likely find it one of the more useful functions at your disposal to make sure you're setting good breakpoints.
(gdb) info functions function0
All functions matching regular expression "function0":

File main.cpp:
void function01();
void function02();
void function03();
void function04();
void function05();
(gdb) 

Tip of the iceberg stuff here; refer to https://sourceware.org/gdb/onlinedocs/gdb/Symbols.html for a more complete reference.
It may be good practice to get in the habit of establishing a breakpoint within the main function and establish your breakpoints and such after triggering the breakpoint.  While not necessary for statically linked executables, waiting to establish breakpoints after reaching the main function will work for static and run-time libraries and avoid 'pending breakpoints' which can be error-prone.  Let's look at the use of a run-time library to better demonstrate what I mean.
$ more MyClass.h MyClass.cpp
::::::::::::::
MyClass.h
::::::::::::::
#ifndef MYCLASS_H
#define MYCLASS_H
  class MyClass
  {
    public:
      MyClass();
      ~MyClass();
    private:
  };
#endif
::::::::::::::
MyClass.cpp
::::::::::::::
#include "MyClass.h"
#include <stdio.h>

MyClass::MyClass()
{
  printf("(%s:%d) constructor\n",__FILE__,__LINE__);
}

MyClass::~MyClass()
{
  printf("(%s:%d) destructor\n",__FILE__,__LINE__);
}

Create a shared library;
$ g++  -g -o3 -fPIC  -c -o MyClass.o MyClass.cpp
$ g++ -shared MyClass.o -o libMyLib.so -fPIC -Wl,--whole-archive -Wl,--no-whole-archive 
$ g++   mainRt.o libMyLib.so   -o mainRt

Attempts to run will meet you with an error of the fashion;
$ ./mainRt 
./mainRt: error while loading shared libraries: libMyLib.so: cannot open shared object file: No such file or directory

Tell the shared library loader how to locate the libMyLib.so file and you're back in the saddle.
$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
$ ./mainRt
(mainRt.cpp:6) main process initializing
(MyClass.cpp:6) constructor
(mainRt.cpp:8) main process terminating
(MyClass.cpp:11) destructor

Ok, fine, now we have a shared library and can run it, let's move on to the use of library in GDB.  Attempts to use the 'info functions' may fail, dependent on whether the libraries are loaded.  To insure the libraries are loaded a good practice is to set a breakpoint in main and establish the rest of the breakpoints after hitting it.
$ gdb mainRt
(gdb) break main
Breakpoint 1 at 0x4008b6: file mainRt.cpp, line 5.
(gdb) r
Starting program: /home/lipeltgm/svn/mal/main/trunk/personal/lipeltgm/blog/Gdb-Qs/mainRt 

Breakpoint 1, main (argc=1, argv=0x7fffffffdf18) at mainRt.cpp:5
5 {
(gdb) info functions MyClass
All functions matching regular expression "MyClass":

File MyClass.cpp:
void MyClass::MyClass();
void MyClass::~MyClass();

Non-debugging symbols:
0x0000000000400760  MyClass::~MyClass()@plt
0x0000000000400770  MyClass::MyClass()@plt
(gdb) rbreak MyClass
Breakpoint 2 at 0x7ffff7bd573c: file MyClass.cpp, line 6.
void MyClass::MyClass();
Breakpoint 3 at 0x7ffff7bd5768: file MyClass.cpp, line 11.
void MyClass::~MyClass();
Breakpoint 4 at 0x400760
<function, no debug info> MyClass::~MyClass()@plt;
Breakpoint 5 at 0x400770
<function, no debug info> MyClass::MyClass()@plt;
(gdb) cont
Continuing.
(mainRt.cpp:6) main process initializing

Breakpoint 5, 0x0000000000400770 in MyClass::MyClass()@plt ()
(gdb) cont
Continuing.

Breakpoint 2, MyClass::MyClass (this=0x7fffffffde17) at MyClass.cpp:6
6  printf("(%s:%d) constructor\n",__FILE__,__LINE__);
(gdb) cont
Continuing.
(MyClass.cpp:6) constructor
(mainRt.cpp:8) main process terminating

Breakpoint 4, 0x0000000000400760 in MyClass::~MyClass()@plt ()
(gdb) cont
Continuing.

Breakpoint 3, MyClass::~MyClass (this=0x7fffffffde17, 
    __in_chrg=<optimized out>) at MyClass.cpp:11
11  printf("(%s:%d) destructor\n",__FILE__,__LINE__);
(gdb) cont
Continuing.
(MyClass.cpp:11) destructor
[Inferior 1 (process 8191) exited normally]
(gdb) quit

Albeit just a starting point, but a starting point just the same.

Cheers.



No comments:

Post a Comment