Search code examples
cdebuggingautomationgdb

How to print every executed line in GDB automatically until a given breakpoint is reached?


I would like to be able to set a breakpoint in GDB, and have it run to that point - and in the process, print out lines it has "stepped through".

Here is an example, based on this simple file with a main and a function, and two breakpoints for each:

$ cat > test.c <<EOF
#include "stdio.h"

int count=0;

void doFunction(void) {
  // two steps forward
  count += 2;
  // one step back
  count--;
}

int main(void) {
  // some pointless init commands;
  count = 1;
  count += 2;
  count = 0;
  //main loop
  while(1) {
    doFunction();
    printf("%d\n", count);
  }
}
EOF

$ gcc -g -Wall test.c -o test.exe
$ chmod +x test.exe
$ gdb -se test.exe
...
Reading symbols from /path/to/test.exe...done.
(gdb) b main
Breakpoint 1 at 0x80483ec: file test.c, line 14.
(gdb) b doFunction
Breakpoint 2 at 0x80483c7: file test.c, line 7.

To start the session, I need to run (r) the program, which will then stop at first breakpoint (main):

(gdb) r
Starting program: /path/to/test.exe 

Breakpoint 1, main () at test.c:14
14    count = 1;
(gdb) 

At this point - I can, for instance, hit continue (c); and the process will run through, not outputing anything, and break at the requested line:

(gdb) c
Continuing.

Breakpoint 2, doFunction () at test.c:7
7     count += 2;
(gdb)

On the other hand, instead of continue - I can go line by line, either by using step (s) or next (n); for instance:

14    count = 1;
(gdb) n
15    count += 2;
(gdb) s
16    count = 0;
(gdb) s
19      doFunction();
(gdb) s

Breakpoint 2, doFunction () at test.c:7
7     count += 2;
(gdb) s
9     count--;
(gdb) s
10  }
(gdb) s
main () at test.c:20
20      printf("%d\n", count);
(gdb) s
...
(gdb) s
_IO_vfprintf_internal (s=Cannot access memory at address 0xe5853361
) at vfprintf.c:210
210 vfprintf.c: No such file or directory.
    in vfprintf.c
(gdb) s
245 in vfprintf.c
(gdb) s
210 in vfprintf.c
(gdb) n
245 in vfprintf.c
...
(gdb) n
2006    in vfprintf.c
(gdb) n
__printf (format=0x80484f0 "%d\n") at printf.c:39
39  printf.c: No such file or directory.
    in printf.c
(gdb) n
main () at test.c:21
21    }
(gdb) n
19      doFunction();
(gdb) n

Breakpoint 2, doFunction () at test.c:7
7     count += 2;
(gdb) 

Anyways, I am aware that I can keep Enter pressed, and the last entered command (step or next) will repeat (left a bit longer session in the second case, to show that 'next' remains on same level, 'step' steps inside the functions being called). However, as it can be seen, depending on whether step or next runs, it may take a while until a result is reached - and so, I don't want to sit for 10 minutes with my hand stuck on the Enter button :)

So, my question is - can I somehow instruct gdb to run to 'breakpoint 2' without further user intervention - while printing out the lines it goes through, as if step (or next) was pressed?


Solution

  • Well, this wasn't easy - but I think I somewhat got it :) I went through a bunch of failed attempts (posted here); relevant code is below.

    Basically, the problem in a "next/step until breakpoint" is how to determine whether you're "on" a breakpoint or not, if the debugger is stopped (at a step). Note also I use GDB 7.2-1ubuntu11 (current for Ubuntu 11.04). So, it went like this:

    • I first found about Convenience Variables, and thought - given there are program counters and such available, there must be some GDB convenience variable that gives the "breakpoint" status, and can be used directly in a GDB script. After looking through GDB reference Index for a while, however, I simply cannot find any such variables (my attempts are in nub.gdb)
    • In lack of such a "breakpoint status" internal variable - the only thing left to do, is to capture the ('stdout') command line output of GDB (in response to commands) as a string, and parse it (looking for "Breakpoint")
    • Then, I found out about Python API to GDB, and the gdb.execute("CMDSTR", toString=True) command - which is seemingly exactly what is needed to capture the output: "By default, any output produced by command is sent to gdb's standard output. If the to_string parameter is True, then output will be collected by gdb.execute and returned as a string[1]"!

    And finally, the approach that worked is: temporarily redirecting the GDB output from a gdb.execute to a logfile in RAM (Linux: /dev/shm); and then reading it back, parsing it and printing it from python - python also handles a simple while loop that steps until a breakpoint is reached.

    The irony is - most of these bugs, that caused this solution via redirecting the logfile, are actually recently fixed in SVN; meaning those will propagate to the distros in the near future, and one will be able to use gdb.execute("CMDSTR", toString=True) directly :/ Yet, as I cannot risk building GDB from source right now (and possibly bumping into possible new incompatibilites), this is good enough for me also :)

     

    Here are the relevant files (partially also in pygdb-fork.gdb,pygdb-fork.py):

    pygdb-logg.gdb is:

    # gdb script: pygdb-logg.gdb
    # easier interface for pygdb-logg.py stuff
    # from within gdb: (gdb) source -v pygdb-logg.gdb
    # from cdmline: gdb -x pygdb-logg.gdb -se test.exe
    
    # first, "include" the python file:
    source -v pygdb-logg.py
    
    # define shorthand for nextUntilBreakpoint():
    define nub
      python nextUntilBreakpoint()
    end
    
    # set up breakpoints for test.exe:
    b main
    b doFunction
    
    # go to main breakpoint
    run
    

    pygdb-logg.py is:

    # gdb will 'recognize' this as python
    #  upon 'source pygdb-logg.py'
    # however, from gdb functions still have
    #  to be called like:
    #  (gdb) python print logExecCapture("bt")
    
    import sys
    import gdb
    import os
    
    def logExecCapture(instr):
      # /dev/shm - save file in RAM
      ltxname="/dev/shm/c.log"
    
      gdb.execute("set logging file "+ltxname) # lpfname
      gdb.execute("set logging redirect on")
      gdb.execute("set logging overwrite on")
      gdb.execute("set logging on")
      gdb.execute(instr)
      gdb.execute("set logging off")
    
      replyContents = open(ltxname, 'r').read() # read entire file
      return replyContents
    
    # next until breakpoint
    def nextUntilBreakpoint():
      isInBreakpoint = -1;
      # as long as we don't find "Breakpoint" in report:
      while isInBreakpoint == -1:
        REP=logExecCapture("n")
        isInBreakpoint = REP.find("Breakpoint")
        print "LOOP:: ", isInBreakpoint, "\n", REP
    

     

    Basically, pygdb-logg.gdb loads the pygdb-logg.py python script, sets up the alias nub for nextUntilBreakpoint, and initializes the session - everything else is handled by the python script. And here is a sample session - in respect to the test source in OP:

    $ gdb -x pygdb-logg.gdb -se test.exe
    ...
    Reading symbols from /path/to/test.exe...done.
    Breakpoint 1 at 0x80483ec: file test.c, line 14.
    Breakpoint 2 at 0x80483c7: file test.c, line 7.
    
    Breakpoint 1, main () at test.c:14
    14    count = 1;
    (gdb) nub
    LOOP::  -1
    15    count += 2;
    
    LOOP::  -1
    16    count = 0;
    
    LOOP::  -1
    19      doFunction();
    
    LOOP::  1
    
    Breakpoint 2, doFunction () at test.c:7
    7     count += 2;
    
    (gdb) nub
    LOOP::  -1
    9     count--;
    
    LOOP::  -1
    10  }
    
    LOOP::  -1
    main () at test.c:20
    20      printf("%d\n", count);
    
    1
    LOOP::  -1
    21    }
    
    LOOP::  -1
    19      doFunction();
    
    LOOP::  1
    
    Breakpoint 2, doFunction () at test.c:7
    7     count += 2;
    
    (gdb)
    

    ... just as I wanted it :P Just don't know how reliable it is (and whether it will be possible to use in avr-gdb, which is what I need this for :) EDIT: version of avr-gdb in Ubuntu 11.04 is currently 6.4, which doesn't recognize the python command :()

     

    Well, hope this helps someone,
    Cheers!

     

    Here some references: