7

I am making a nice presentation of ARM assembly code execution and I would need GDB to step the code every 1 second infinitely long (well until I press CTRL+C). Has anyone got solution?

I don't want to keep on standing next to the keyboard and stepping the program when visitors come visit my stall.

4 Answers 4

14

Gdb's CLI supports a while loop. There's no builtin sleep command, but you can either call out to the shell to run the sleep program, or use gdb's builtin python interpreter, if it has one. It's interruptible with Control-C.

Method 1:

(gdb) while (1)
 >step
 >shell sleep 1
 >end

Method 2:

(gdb) python import time
(gdb) while (1)
 >step
 >python time.sleep(1)
 >end

Method 3 (define a macro):

(gdb) define runslowly
Type commands for definition of "runslowly".
End with a line saying just "end".
>python import time
>while (1)
 >step
 >python time.sleep(1)
 >end
>end
(gdb) document runslowly
Type documentation for "runslowly".
End with a line saying just "end".
>step a line at a time, every 1 second
>end

(gdb) runslowly
4
  • This answer likely "works" for the OP but has some culprits, most important: "steps over" any event, including changes in watchpioints, reached breakpoints, catched signal, inferior stopped. For a "general purpose" solution the gdb python api can be used, see unix.stackexchange.com/a/649046/173030 Commented May 10, 2021 at 11:28
  • @SimonSobisch Thanks for the improvements in the other answer. In which version of GDB did you see the loop continue to run even after the inferior ended? I will need to add it to my test collection. Commented May 10, 2021 at 16:11
  • I think it was "any GDB version" - the loop still continued after GDB informed on the exit and then issues a single exception "No stack." (But I could be wrong and this only happens when the complete loop was done by python (issuing a gdb.execute("step")as this was one if my first changes). If you think the other answer is useful please upvote ;-) Commented May 10, 2021 at 20:27
  • Rechecked: with a gdb loop the while is automatically aborted on the first error and there's one error raised at end "The program is not being run.", if executed in a python loop the error is shown as python trace with gdb.error: The program is not being run (the same error, just wrapped in a python stacktrace as ling as no exceptions are catched [if gdb.execute is used then it would be reasonable to check and abort the loop and/or re-raise that error as gdb.GdbError to have the same result as your code: a single error at end`]) Commented May 10, 2021 at 21:07
7

expect can automate this

#!/usr/bin/env expect
spawn -noecho gdb -q ls
expect -ex {(gdb)}
send -- "break main\r"
expect -ex {(gdb)}
send -- "run\r"
while {1} {
    expect -ex {(gdb)}
    send -- "s\r"
    sleep 1
}

or if there's a risk of the program running out of s you can repeatedly gdb it with a little more complication

#!/usr/bin/env expect

while {1} {
    spawn -noecho gdb -q ls
    expect -ex {(gdb)}
    send -- "break main\r"
    expect -ex {(gdb)}
    send -- "run\r"
    expect {
        -ex {The program is not being run} {}
        eof {}
        -ex {(gdb)} {
            send -- "s\r"
            sleep 1
            exp_continue
        }
    }
}
2
  • So how do I use this? Can you add a description on how to start it.
    – 71GA
    Commented Feb 8, 2018 at 23:39
  • install expect, save the scripts to a file, chmod +x them, run them like any other script with a shebang line
    – thrig
    Commented Feb 8, 2018 at 23:50
4

You could have the shell pipe in commands; here's the idea:

while :; do echo step; sleep 1; done | gdb arm-program

gdb reads the commands from the pipe; it sees a "step" command every second ad infinitum.

You may want to set up some break-points and run the program; adjust to taste:

(echo br 1; echo run; while :; do echo step; sleep 1; done ) | gdb arm-program
0

The currently accepted answer does always step, once started and therefore also "skips" over breakpoints, signals and even the program end (in the last case raising a single error "The program is not being run" before gdb internally aborting the while where that happened).
As an additional culprit it stops if pagination is on (which is the default) as soon as the GDB output is "full".

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 (replaced here for simplicity by a fixed value)
  • 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

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

        try:
            frame = gdb.newest_frame()
        except gdb.error:
            raise gdb.GdbError("The program is not being run.")

        number = 5 # optional: use a parameter for the default
        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 / (number * 1.4)

        global last_stop_was_simple
        last_stop_was_simple = True

        pagination = gdb.execute("show pagination", False, True).find("on")
        if pagination:
            gdb.execute("set pagination off", False, False)
        
        try:
            while (last_stop_was_simple):
                gdb.execute ("step")
                time.sleep(sleep_time)
        except KeyboardInterrupt as ki:
            if pagination:
                gdb.execute("set pagination on", False, False)
        except gdb.GdbError as user_error:
            if pagination:
                gdb.execute("set pagination on", False, False)
            # pass user errors unchanged
            raise user_error
        except:
            if pagination:
                gdb.execute("set pagination on", False, False)
            traceback.print_exc()

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

See https://stackoverflow.com/a/67470615/5027456 for a more complete (and updated) code including a parameter for the speed, both auto-next and auto-step and better error handling.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .