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开发者_StackOverflow 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?
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 whenevergdb.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.
- So, first I tried to make a script (pygdb-nub.py,gdbwrap) that would utilize
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
What about doing it like this in gdb, using a command file. Change file argument, and loop count as required.
gdb -x run.gdb
run.gdb:
set pagination off
set logging file gdb.log
set logging on
set $i = 0
file main
break main
break WriteData
# sadly, commands not getting executed on reaching breakpoint 2
commands 2
set $i=1000
print "commands 2 : %d",$i
end
run
while ( $i < 1000 )
step
# next
# continue
set $i = $i + 1
end
Based on the link in @sdaau's answer (http://www.mail-archive.com/gdb@gnu.org/msg00031.html), I created my own script to simply keep sending 's' and reading the output of gdb continuously, while printing output to textfile and terminal, of course, my script can be modified to fit anyone else's needs, however, I hope that the modification I made should fit most people needs.
http://www.codeground.net/coding/gdb-step-into-all-lines-to-get-full-application-flow/
wget http://www.codeground.net/downloads/gdbwalkthrough.c
gcc gdbwalkthrough.c -o gdbwalkthrough
./gdbwalkthrough <application full path> [application arguments]
As a new answer, since the previous is already hogged :) Basically, if the point is to observe execution of source (and/or assembly) code lines as the program as running - as the motivation is often for me when looking into "automatic printout" -- then, basically, a very quick way is to use GDB TUI mode; I quote:
c - gdb behavior : value optimized out - Stack Overflow #1354762
Use the GDB TUI mode. My copy of GDB enables it when I type the minus and Enter. Then type C-x 2 (that is hold down Control and press X, release both and then press 2). That will put it into split source and disassembly display. Then use stepi and nexti to move one machine instruction at a time. Use C-x o to switch between the TUI windows.
The trick here is that, even if you hit continue
- this time source will be shown and indicated on the TUI; and followed as the program runs:
... and this for me avoids many situations where I'd have to script the breakpoints in "auto-stepping context" (although there are still such situations).. Docs about TUI: TUI - Debugging with GDB
Cheers!
Actually, I have a Github repo with a Python-GDB extension, which does exactly the same thing as You have described, but with some more functionality.
You can just clone the repo:
git clone https://github.com/Viaceslavus/gdb-debug-until.git
and feed the python script to GDB with the following command inside GDB:
source gdb-debug-until/debug_until.py
(Change python script path if necessary)
And now you can use the following command to run through each line of your code until a breakpoint:
debug-until somefile.c:100 --args="" --end="somefile.c:200"
"somefile.c:100" here is a starting breakpoint, and "somefile.c:200" is the final breakpoint.
"--args" specifies a set of arguments to your program (you can omit it if there are no arguments).
With this extension you can also run few times through the code (with '-r' option) and even specify some events that should be handled while debugging. For more info see:
https://github.com/Viaceslavus/gdb-debug-until
The currently accepted answer includes a lot of file io and does only stop on breakpoints, but watchpoints, signals and possibly even the program end is ignored.
Using the python api this can be handled nicely:
- define a user command (with additional argument to say how fast to auto-step)
- optional: define a parameter for the default (both variants below)
- do the while loop within python, handle the "expected" keyboard interrupt of CTRL-C
- register a
stop
event handler that checks for the stop reason and store the kind of step there - adjust the while loop to stop for a "not simple" stop (breakpoint/watchpoint/signal/...)
The following code may be placed in a gdb-auto-step.py which can be made active with source gdb-auto-step.py
whenever you want that (or include in the .gdbinit file to make it always available):
import gdb
import time
import traceback
class CmdAutoStep (gdb.Command):
"""Auto-Step through the code until something happens or manually interrupted.
An argument says how fast auto stepping is done (1-19, default 5)."""
def __init__(self):
print('Registering command auto-step')
super(CmdAutoStep, self).__init__("auto-step", gdb.COMMAND_RUNNING)
gdb.events.stop.connect(stop_handler_auto_step)
def invoke(self, argument, from_tty):
# sanity check - are we even active, prevents a spurious "no registers" exception
try:
gdb.newest_frame()
except gdb.error:
raise gdb.GdbError("The program is not being run.")
# calculate sleep time
if argument:
if not argument.isdigit():
raise gdb.GdbError("argument must be a digit, not " + argument)
number = int(argument)
if number == 0 or number > 19:
raise gdb.GdbError("argument must be a digit between 1 and 19")
sleep_time = 3.0 / (1.4 ** number)
# activate GDB scrolling, otherwise we'd auto-step only one page
pagination = gdb.parameter("pagination")
if pagination:
gdb.execute("set pagination off", False, False)
# recognize the kind of stop via stop_handler_auto_step
global last_stop_was_simple
last_stop_was_simple = True
# actual auto-stepping
try:
while last_stop_was_simple:
gdb.execute("step")
time.sleep(sleep_time)
# we just quit the loop as requested
# pass keyboard and user errors unchanged
except (KeyboardInterrupt, gdb.GdbError):
raise
# that exception is unexpected, but we never know...
except Exception:
traceback.print_exc()
# never leave without cleanup...
finally:
if pagination:
gdb.execute("set pagination on", False, False)
def stop_handler_auto_step(event):
# check the type of stop, the following is the common one after step/next,
# a more complex one would be a subclass (for example breakpoint or signal)
global last_stop_was_simple
last_stop_was_simple = type(event) is gdb.StopEvent
CmdAutoStep()
To specify the default with a parameter (aka "the gdb way") add a new parameter via api and use it as follows (comes also with 0 = unlimited, handling process exit, an additional auto-next command and more class wrapping):
import gdb
import time
import traceback
class ParameterAutoSpeed (gdb.Parameter):
"""default speed for auto-step and auto-next commands (0-19, default 5)"""
def __init__(self):
self.set_doc = """Set speed for "auto-step" and "auto-next",
internally used to calculate sleep time between iterations of "step" / "next";
set "auto-speed 0" causes there to be no sleeping."""
self.show_doc = "Speed value for auto-step/auto-next."
super(ParameterAutoSpeed, self).__init__("auto-speed", gdb.COMMAND_RUNNING, gdb.PARAM_UINTEGER)
self.value = 5
self.backup = self.value
def get_set_string (self):
try:
self.value = int(self.validate(self.value))
except gdb.GdbError:
self.value = int (self.backup)
raise
self.backup = self.value
return ""
def validate (self, argument):
"""validation for auto-step/auto-next speed"""
try:
speed = int(argument)
if speed < 0 or speed > 19:
raise ValueError()
except (TypeError, ValueError):
raise gdb.GdbError("speed argument must be an integer between 1 and 19, or 0")
return speed
class CmdAutoNext (gdb.Command):
"""Auto-Next through the code until something happens or manually interrupted.
An argument says how fast auto stepping is done (see parameter "auto-speed")."""
def __init__(self, worker):
self.worker = worker
super(CmdAutoNext, self).__init__("auto-next", gdb.COMMAND_RUNNING)
def invoke(self, argument, from_tty):
self.worker.invoke (argument)
class CmdAutoStep (gdb.Command):
"""Auto-Step through the code until something happens or manually interrupted.
An argument says how fast auto stepping is done (see parameter "auto-speed").
Note: To be usable you likely need several "skip" setup for not stepping into functions of
the C library and other system libraries which may have no debug symbols available
or are of no interest.
You may press [CTRL]+[C] and execute "skip file", then "finish" to leave those."""
def __init__(self, worker):
self.worker = worker
super(CmdAutoStep, self).__init__("auto-step", gdb.COMMAND_RUNNING)
def invoke(self, argument, from_tty):
self.worker.invoke (argument, do_step=True)
class AutoWorker ():
def __init__(self):
print('Registering parameter auto-speed and commands auto-step, auto-next')
self.speed = ParameterAutoSpeed()
CmdAutoStep(self)
CmdAutoNext(self)
gdb.events.stop.connect(self.stop_handler_auto)
gdb.events.exited.connect(self.exit_handler_auto)
def invoke(self, argument, do_step=False):
# calculate sleep time
if argument:
number = self.speed.validate(argument) # raises an error if not valid
else:
number = self.speed.value
if number:
sleep_time = 3.0 / (1.4 ** number)
else:
sleep_time = 0
# activate GDB scrolling, otherwise we'd auto-step/next only one page
pagination = gdb.parameter("pagination")
if pagination:
gdb.execute("set pagination off", False, False)
# recognize the kind of stop via stop_handler_auto_step
self.last_stop_was_simple = True
# actual auto-stepping
try:
while self.last_stop_was_simple:
if do_step:
gdb.execute ("step")
else:
gdb.execute ("next")
time.sleep(sleep_time)
# we just quit the loop as requested
# pass keyboard and user errors unchanged
except (KeyboardInterrupt, gdb.GdbError):
raise
# wrap GDB errors like "the program is not being run" to let them be
# handled via default invoke error reporting, not as a python error
except gdb.error as err:
raise gdb.GdbError(err)
# that exception is unexpected, but we never know...
except Exception:
traceback.print_exc()
# never leave without cleanup...
finally:
if pagination:
gdb.execute("set pagination on", False, False)
def stop_handler_auto(self, event):
# check the type of stop, the following is the common one after step/next,
# a more complex one would be a subclass (for example breakpoint or signal)
self.last_stop_was_simple = type(event) is gdb.StopEvent
def exit_handler_auto(self, event):
self.last_stop_was_simple = False
AutoWorker()
精彩评论