开发者

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

开发者 https://www.devze.com 2023-03-25 03:57 出处:网络
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\".

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 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


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:

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

... 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()

0

精彩评论

暂无评论...
验证码 换一张
取 消