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

automation, c, debugging, gdb

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]"!

- So, first I tried to make a script (pygdb-nub.py,gdbwrap) that would utilize `gdb.execute` in the recommended manner; failed here - because of this:

- Bug 627506 – python: gdb.execute([...], to_string=True) partly prints to stdout/stderr

- Bug 10808 – Allow GDB/Python API to capture and store GDB output

- Then, I thought I'd use a python script to `subprocess.Popen` the GDB program, while replacing its stdin and stdout; and then proceed controlling GDB from there (pygdb-sub.py) - that failed too... (apparently, because I didn't redirect stdin/out right)

- Then, I thought I'd use python scripts to be called from GDB (via `source`) which would internally fork into a pty whenever `gdb.execute` should be called, so as to capture its output (pygdb-fork.gdb,pygdb-fork.py)... This almost worked - as there are strings returned; however GDB notices something ain't right: "[tcsetpgrp failed in terminal_inferior: Operation not permitted]", and the subsequent return strings don't seem to change.

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:

- GDB: error detected on stdin

- GDB has problems with getting commands piped to STDIN

- Re: [Gdb] How do i use GDB other input?

- gdb doesn't accept input on stdin

- Using gdb in an IDE - comp.os.linux.development.apps | Google Groups

- rmathew: Terminal Sickness

- [TUTORIAL] Calling an external program in C (Linux) - GIDForums

- shell - how to use multiple arguments with a shebang (i.e. #!)? - Stack Overflow

- Redirecting/storing output of shell into GDB variable? - Stack Overflow

- Corey Goldberg: Python - Redirect or Turn Off STDOUT and STDERR

- The Cliffs of Inanity › 9. Scripting gdb

- gdb python scripting: where has `parse_and_eval` gone? - Stack Overflow

- shell - Invoke gdb to automatically pass arguments to the program being debugged - Stack Overflow

- Storing Files/Directories In Memory With tmpfs | HowtoForge - Linux Howtos and Tutorials

- simple way to touch a file if it does not exist | Python | Python

- os.fork() different in cgi-script? - Python

- java - Writing tests that use GDB - how to capture output? - Stack Overflow

- Debugging with GDB: How to create GDB Commands in Python - Wiki

- GDB reference card

Problem

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?

Original source

Related problems