Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
From: Guinevere Larsen <guinevere@redhat.com>
To: gdb-patches@sourceware.org, Guinevere Larsen <guinevere@redhat.com>
Cc: Eli Zaretskii <eliz@gnu.org>
Subject: Re: [PATCH v3] gdb: add tutorial command
Date: Thu, 5 Mar 2026 09:23:03 -0300	[thread overview]
Message-ID: <6aff4ca8-6a78-4a6b-8ed8-83a43bf3acda@redhat.com> (raw)
In-Reply-To: <20260128193344.2907827-1-guinevere@redhat.com>

Ping :)
On 1/28/26 4:33 PM, Guinevere Larsen wrote:
> Before this commit, there is little way for a new user to learn how to
> use GDB on their own.  The documentation contains an example session,
> but that isn't contained in GDB itself, and the "help" and "apropos"
> commands exist, but they aren't the best to really teach what GDB can
> do, only to describe commands on their own.
>
> This commit changes this by introducing a command called "tutorial",
> which takes a page out of common design from the last few decades and
> provides a self-contained tutorial for users, walking them through a
> simple bug in C code, and explaining several commands in context.
>
> The tutorial is mostly implemented (ab)using the before_prompt hook to
> print the messages, so that users can have completion, history and so
> on, and it is implemented in python to make maintaining it in the future
> as simple as possible.
>
> A C fallback command is also implemented so that, if GDB was compiled
> without python support, the user can understand why the tutorial is not
> available, but knowing that the python implementation of the command
> will take precedence over the C one.
>
> Reviewed-By: Eli Zaretskii <eliz@gnu.org>
> ---
>   gdb/NEWS                               |   6 +
>   gdb/cli/cli-cmds.c                     |  12 +
>   gdb/data-directory/Makefile.in         |   1 +
>   gdb/doc/gdb.texinfo                    |  38 +++
>   gdb/python/lib/gdb/command/tutorial.py | 430 +++++++++++++++++++++++++
>   gdb/top.c                              |   5 +-
>   6 files changed, 491 insertions(+), 1 deletion(-)
>   create mode 100644 gdb/python/lib/gdb/command/tutorial.py
>
> diff --git a/gdb/NEWS b/gdb/NEWS
> index fa6e7ca6121..28280361180 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -91,6 +91,12 @@ show progress-bars enabled
>     content, to be disabled (the set command), or to see if
>     progress-bars are currently enabled or not (the show command).
>   
> +tutorial
> +  Start a guided example session with a generated C file, to teach
> +  an entirely new user how to use GDB for basic debugging.
> +  This command is only available if GDB was configured with
> +  '--with-python'
> +
>   * Changed commands
>   
>   maintenance info program-spaces
> diff --git a/gdb/cli/cli-cmds.c b/gdb/cli/cli-cmds.c
> index a37e38e9709..80ca2372fc9 100644
> --- a/gdb/cli/cli-cmds.c
> +++ b/gdb/cli/cli-cmds.c
> @@ -2657,6 +2657,12 @@ shell_internal_fn (struct gdbarch *gdbarch,
>       return value::allocate_optimized_out (int_type);
>   }
>   
> +static void
> +tutorial_fallback_command (const char *arg, int from_tty)
> +{
> +  gdb_printf (_("Python support missing, tutorial can't be run."));
> +}
> +
>   INIT_GDB_FILE (cli_cmds)
>   {
>     struct cmd_list_element *c;
> @@ -3070,4 +3076,10 @@ when GDB is started."), GDBINIT).release ();
>     c = add_cmd ("source", class_support, source_command,
>   	       source_help_text, &cmdlist);
>     set_cmd_completer (c, deprecated_filename_completer);
> +
> +  add_cmd ("tutorial", class_essential, tutorial_fallback_command, _("\
> +This command is not available.\n\
> +\n\
> +This command requires python support on GDB, which was disabled on\n\
> +this build."), &cmdlist);
>   }
> diff --git a/gdb/data-directory/Makefile.in b/gdb/data-directory/Makefile.in
> index 7257da9cd11..572b7e25e48 100644
> --- a/gdb/data-directory/Makefile.in
> +++ b/gdb/data-directory/Makefile.in
> @@ -93,6 +93,7 @@ PYTHON_FILE_LIST = \
>   	gdb/command/missing_files.py \
>   	gdb/command/pretty_printers.py \
>   	gdb/command/prompt.py \
> +	gdb/command/tutorial.py \
>   	gdb/command/type_printers.py \
>   	gdb/command/unwinders.py \
>   	gdb/command/xmethods.py \
> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index 80f49e21b7e..b8b6cae9992 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -2577,6 +2577,44 @@ system Readline library, or @samp{(internal)} indicating @value{GDBN}
>   is using a statically linked in version of the Readline library.
>   @end table
>   
> +Finally, if you want to learn how to use GDB for simple debug session,
> +you can use the command @code{tutorial}.
> +
> +@table @code
> +@kindex tutorial
> +@item tutorial
> +This command starts an interactive tutorial, teaching you basic commands
> +and how you can chain them together for a successful debugging session.
> +@emph{Warning}: If you have an ongoing debug session, this command will
> +kill that session and you'll have to start from scratch.
> +@emph{Warning}: This command is only available if GDB was compiled with
> +python support.
> +
> +@smallexample
> +@group
> +(@value{GDBP}) tutorial
> +Welcome to GDB!  This quick tutorial should be enough to get
> +you acquainted with essential commands for basic debugging.
> +At any point, you can feel free to use 'help <command>' to
> +learn more about the commands that are being suggested, or
> +any other commands that you may have heard of.  You may also
> +type 'quit' at any point to quit this tutorial, and use it
> +again to quit GDB entirely.
> +
> +First, GDB will generate a C file with example code for the
> +debug session.  The code will contain a slightly flawed
> +implementation of a bubble sort, which is used to sort an
> +array of size 6.  After the function is called, the program
> +will print either "sorted" or "not sorted" depending on
> +the result.  Finally, the program will return 0 if the array
> +is *not* sorted, and 1 if it is sorted.
> +
> +Enter the desired name for the file.  This is important to
> +avoid a collision that could overwrite your local data.
> +@end group
> +@end smallexample
> +@end table
> +
>   @node Running
>   @chapter Running Programs Under @value{GDBN}
>   
> diff --git a/gdb/python/lib/gdb/command/tutorial.py b/gdb/python/lib/gdb/command/tutorial.py
> new file mode 100644
> index 00000000000..f7b871a123e
> --- /dev/null
> +++ b/gdb/python/lib/gdb/command/tutorial.py
> @@ -0,0 +1,430 @@
> +# GDB 'tutorial' command.
> +# Copyright (C) 2025-2026 Free Software Foundation, Inc.
> +
> +# This program is free software; you can redistribute it and/or modify
> +# it under the terms of the GNU General Public License as published by
> +# the Free Software Foundation; either version 3 of the License, or
> +# (at your option) any later version.
> +#
> +# This program is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +# GNU General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +
> +"""Implementation of the GDB 'tutorial' command using the GDB Python API."""
> +
> +import os
> +
> +import gdb
> +
> +tutorial_state = 0
> +display_counter = 0
> +
> +generated_code = None
> +generated_binary = None
> +
> +state_string = [
> +    """
> +To start properly debugging, first the program needs to be
> +loaded.  The collection of the binary, loaded shared objects
> +and other related things is called "inferior" by GDB, and
> +that is how this tutorial will refer to it from now on.
> +
> +Use the following command to load the inferior:""",
> +    """
> +Next, you should verify that there really is a bug in the
> +example, by running the entire program.  One way to do that
> +is by using the command '**start**', which runs the inferior
> +until the first line of the main function, and then use the
> +command '**continue**', which runs the inferior until it is
> +forced to stop, such as by a breakpoint, signal or finishing.
> +""",
> +    """
> +Notice the "__not sorted__" message, that is output of the inferior
> +showing the bug.
> +
> +You've verified that the bug is there, now you should check
> +if the bug is happening in the sort function or in the
> +verification function.  Stop after the sorting function, to
> +be able to confirm if the array is sorted or not.
> +You can do so by using '**list main**' to find the line number
> +after the call to '**sort**', then using the command '**break <num>**'
> +to break right before executing line <num>.  Then, execute
> +the inferior again.
> +If the call to the function "__sort__" was not visible, following
> +invocations of '**list**' will print following lines of the source
> +code.
> +""",
> +    """
> +You can check the value of the variable with the command
> +'**print vec**'.  Do it now to see if the result of "__sort__" is
> +as expected.
> +""",
> +    """
> +GDB can be used to prototype solutions or test different
> +scenarios.  You can use the following command to set "__vec__"
> +to a sorted array, to see how "__sorted__" would behave:
> +    **set variable vec = {0, 1, 2, 3, 4, 5}**
> +
> +Advanced tip: Some users use '**print**' instead of '**set**' for
> +setting inferior variables.  This works because '**print**'
> +evaluates any arbitrary expression that is valid in the
> +current language, including all side effects.
> +
> +Once you have set the variable to a sorted array, finish
> +the execution of the program with '**continue**' again, to see
> +if the "__sorted__" function works as expected.
> +""",
> +    """
> +Now that you're sure that "__sorted__" is working correctly, you
> +should get to the start of the "__sort__" function.  You can set a
> +breakpoint using the function name, or you can repeatedly use
> +the command '**step**' after '**start**', to move one line at a time,
> +and entering any function calls that happen.
> +""",
> +    """
> +This is the end of the guided section, from now on, try and
> +debug the inferior on your own, you should have all the needed
> +tools.
> +
> +Some extra commands that may be helpful are:
> +    '**next**' - like step, but skips functions
> +    '**display**' - like print, but shows every time the inferior stops
> +    '**watch**' - like breakpoint, but when a variable changes
> +Feel free to use '**help <command>**' to get more information on those
> +commands.
> +
> +Additionally, if you'd like to see the entire array like it
> +was printed in the "main" function, use the following syntax:
> +    **print (*vec)@6**
> +
> +Once you understand the bug, or would like to quit the tutorial,
> +run the inferior until it exits and GDB will clean up after itself.
> +""",
> +]
> +
> +tutorial_reminders = [
> +    "Please load the file with the previous command",
> +    "You should run the inferior to make sure the bug exists",
> +    'You need to stop in main, before the "__sorted__" function is run',
> +    "",
> +    "Use the '**set**' command to fix vec and see if \"__sorted__\" works",
> +    'Stop inside the "__sort__" function',
> +    "When done, run the inferior till the end to exit the tutorial",
> +]
> +
> +
> +# Print a message with styling, to highlight noteworthy output.
> +# The following format is accepted:
> +# * Using double asterisk ('**') to mark a command;
> +# * Using double underscore ('__') to mark an inferior symbol.
> +def styled_print(message):
> +    delimiters = ["**", "__"]
> +    styles = [gdb.Style("command"), gdb.Style("function")]
> +    for delim, style in zip(delimiters, styles):
> +        temp = message.split(delim)
> +        message = ""
> +        apply_style = False
> +        for m in temp:
> +            if apply_style:
> +                m = style.apply(m)
> +            message += m
> +            apply_style = not apply_style
> +    print(message)
> +
> +
> +# Print a message then get an input from the user.
> +# The input must be one of: y, Y, n, N.
> +# Return True if the user entered (y or Y) False otherwise.
> +def get_yn_opt(message):
> +    message += " [y/n]: "
> +    valid = "yYnN"
> +    opt = input(message)
> +    while opt not in valid:
> +        input("Please enter y or n: ")
> +    return opt in "yY"
> +
> +
> +def inferior_in_location(first_line, last_line=None):
> +    # This is in a try block because if the inferior isn't running,
> +    # a "No stack" exception is thrown.
> +    try:
> +        loc = gdb.execute("frame", to_string=True)
> +        loc = int(loc.split("\n")[1].split()[0])
> +        if loc > first_line:
> +            if last_line is None or loc <= last_line:
> +                return True
> +    except gdb.error:
> +        # Exception is fine.  This just means the inferior isn't
> +        # running, so we can return false.
> +        pass
> +    return False
> +
> +
> +# Test if a binary is loaded with the expected name.
> +def inferior_is_loaded():
> +    loaded = gdb.current_progspace().filename
> +    if loaded is not None:
> +        return loaded.endswith(generated_binary)
> +    return False
> +
> +
> +# Increment state and reset counter since a tutorial message
> +# was last displayed.
> +def increment_state():
> +    global tutorial_state, display_counter
> +    tutorial_state += 1
> +    display_counter = 0
> +
> +
> +# Function that holds all the logic to whether a step of the tutorial
> +# has been completed.
> +def should_advance_state():
> +    # Tutorial has just started, immediately increment to print the
> +    # first message.
> +    if tutorial_state == -1:
> +        return True
> +    elif tutorial_state == 0:
> +        return inferior_is_loaded()
> +    # The only way to increment past states 1 and 3 is with the exit hook.
> +    elif tutorial_state == 1 or tutorial_state == 4:
> +        return False
> +    elif tutorial_state == 2:
> +        return inferior_in_location(25)
> +    # There is no way to verify that the user actually printed "vec"
> +    # so we just assume they did and move along.
> +    elif tutorial_state == 3:
> +        return True
> +    elif tutorial_state == 5:
> +        return inferior_in_location(13, 20)
> +    else:
> +        return False
> +
> +
> +def cleanup():
> +    styled_print("""
> +Thank you for taking this tutorial.  We hope it has been
> +helpful for you.  If you found any bugs or would like to
> +provide feedback, feel free to send do so through IRC, in
> +the #gdb room of libera.chat, or send an email to
> +gdb@sourceware.org.
> +To recap, these were the commands explained in the tutorial
> +  * **file**
> +  * **start**
> +  * **continue**
> +  * **list**
> +  * **break**
> +  * **print**
> +  * **set**
> +  * **step**
> +  * **next**
> +  * **display**
> +  * **watch**
> +  * **quit**
> +  * **help**
> +  """)
> +    gdb.events.before_prompt.disconnect(tutorial_hook)
> +    gdb.events.exited.disconnect(tutorial_exit_hook)
> +    # Clean up example code.
> +    try:
> +        f = open(generated_code)
> +        f.close()
> +        os.remove(generated_code)
> +    except FileNotFoundError:
> +        # File doesn't exist, nothing to do.
> +        pass
> +    try:
> +        f = open(generated_binary)
> +        f.close()
> +        os.remove(generated_binary)
> +    except FileNotFoundError:
> +        # File doesn't exist, nothing to do.
> +        pass
> +
> +
> +# Hook for the "inferior exit" event.  This is used to progress
> +# a few states of the tutorial, and to finish it when the user is done.
> +def tutorial_exit_hook(event):
> +    global tutorial_state
> +    if tutorial_state == 1:
> +        increment_state()
> +    elif tutorial_state == 4:
> +        if event.exit_code == 1:
> +            increment_state()
> +        else:
> +            print("The solution didn't work.  Try again!")
> +    elif tutorial_state == (len(state_string) - 1):
> +        tutorial_state += 1
> +    else:
> +        opt = get_yn_opt(
> +            "The tutorial program exited unexpectedly, "
> +            + "would you like to quit the tutorial?"
> +        )
> +        if opt:
> +            tutorial_state = len(state_string)
> +
> +
> +# Main way that the tutorial functionality is implemented.
> +# This is done by abusing before_prompt hooks so that we use GDB's
> +# readline implementation, history, completion and so on.
> +def tutorial_hook():
> +    global display_counter
> +
> +    if should_advance_state():
> +        increment_state()
> +    elif display_counter == 10:
> +        styled_print("Reminder:", tutorial_reminders[tutorial_state])
> +        display_counter = 1
> +
> +    if tutorial_state >= len(state_string):
> +        print(
> +            "The tutorial is complete.  Exiting tutorial and cleaning up the artifacts"
> +        )
> +        cleanup()
> +    elif display_counter == 0:
> +        styled_print(state_string[tutorial_state])
> +        if tutorial_state == 0:
> +            print("    file ", generated_binary)
> +
> +    display_counter += 1
> +
> +
> +# Main implementation of the tutorial command.
> +class Tutorial(gdb.Command):
> +    """Tutorial on the usage of the core commands of GDB.
> +
> +    usage: tutorial
> +
> +    This tutorial does not aim to be comprehensive.  On the contrary,
> +    it aims to be a quick way for you to start using GDB and learn
> +    how to search for more commands that you may find useful.
> +    """
> +
> +    def __init__(self):
> +        super(Tutorial, self).__init__(
> +            name="tutorial", command_class=gdb.COMMAND_ESSENTIAL, prefix=False
> +        )
> +
> +    def invoke(self, arg_str, from_tty):
> +        global tutorial_state, generated_code
> +        styled_print("""
> +Welcome to GDB!  This quick tutorial should be enough to get
> +you acquainted with essential commands for basic debugging.
> +At any point, you can feel free to use '**help <command>**' to
> +learn more about the commands that are being suggested, or
> +any other commands that you may have heard of.
> +If you would like to exit the tutorial early, you may run the
> +example program to completion (when the task is not to do that)
> +and you'll be prompted to leave.  Finally, you may also
> +type '**quit**' at any point to quit GDB entirely.
> +
> +First, GDB will generate a C file with example code for the
> +debug session.  The code will contain a slightly flawed
> +implementation of a bubble sort, which is used to sort an
> +array of size 6.  After the function is called, the program
> +will print either "__sorted__" or "__not sorted__" depending on
> +the result.  Finally, the program will return 0 if the array
> +is *not* sorted, and 1 if it is sorted.
> +
> +Enter the desired name for the file.  This is important to
> +avoid a collision that could overwrite your local data.
> +""")
> +        generated_code = input("Leave empty for default, example_code.c: ")
> +
> +        if generated_code == "":
> +            generated_code = "example_code.c"
> +
> +        self._generateFile()
> +        tutorial_state = -1
> +        gdb.events.before_prompt.connect(tutorial_hook)
> +        gdb.events.exited.connect(tutorial_exit_hook)
> +        return
> +
> +    def _generateFile(self):
> +        global generated_binary
> +        code = """
> +#include <stdio.h>
> +
> +int sorted(int *vec, int size);
> +
> +void
> +swap (int *a, int *b)
> +{
> +  int tmp = *a;
> +  *a = *b;
> +  *b = tmp;
> +}
> +
> +int
> +sort (int *vec, int size)
> +{
> +  for (int i = 0; i < size; i++)
> +    for (int j = i; j < size - 1; j++)
> +      if (vec[j] >= vec[j+1])
> +        swap (&vec[j], &vec[j+1]);
> +}
> +
> +int
> +main ()
> +{
> +  int vec[6] = {5, 4, 3, 2, 1, 0};
> +
> +  sort (vec, 6);
> +  return !sorted (vec, 6);
> +}
> +
> +int
> +sorted (int *vec, int size)
> +{
> +  int ret = 0;
> +  for (int i = 0; i < size - 1; i++)
> +    {
> +      if (vec[i] > vec[i+1])
> +        {
> +          printf ("not ");
> +          ret = 1;
> +          break;
> +        }
> +    }
> +  printf ("sorted\\n");
> +  return ret;
> +}"""
> +        try:
> +            with open(generated_code, "w") as f:
> +                f.write(code)
> +            print("Example code was created successfully")
> +        except Exception:
> +            print("Unable to automate creation of example code.")
> +            print("Please paste the following in a file named")
> +            print(f'"{generated_code}"')
> +            print(code)
> +
> +        print("""
> +Please enter the desired binary name for the example program.
> +""")
> +        generated_binary = input("Leave empty for the default name, a.out: ")
> +        if generated_binary == "":
> +            generated_binary = "a.out"
> +
> +        print("""
> +Finally, please enter the compilation line for your compiler of
> +choice.  If you don't know how to compile via command line, you
> +can try using:""")
> +        print(f"  gcc {generated_code} -g -o {generated_binary}\n")
> +        inp = input("Enter the compilation command: ")
> +        if inp == "":
> +            inp = "gcc " + generated_code + " -g -o " + generated_binary
> +        gdb.execute(f"shell {inp}")
> +
> +        if gdb.convenience_variable("_shell_exitcode") != 0:
> +            print("Compilation failed.  This might have been because of")
> +            print("a typo in your input, or the compiler not being in the")
> +            print("path.  Please ensure that the command line is correct:")
> +            print(inp)
> +            raise Exception("compilation fail")
> +
> +
> +Tutorial()
> diff --git a/gdb/top.c b/gdb/top.c
> index f1d0baeb3f4..d811d718b65 100644
> --- a/gdb/top.c
> +++ b/gdb/top.c
> @@ -1451,7 +1451,8 @@ print_gdb_hints (struct ui_file *stream)
>     gdb_assert (width > 0);
>   
>     std::string docs_url = "http://www.gnu.org/software/gdb/documentation/";
> -  std::array<string_file, 4> styled_msg {
> +  std::array<string_file, 5> styled_msg {
> +    string_file (true),
>       string_file (true),
>       string_file (true),
>       string_file (true),
> @@ -1466,6 +1467,8 @@ print_gdb_hints (struct ui_file *stream)
>     gdb_printf (&styled_msg[3],
>   	      _("Type \"%ps\" to search for commands related to \"word\"."),
>   	      styled_string (command_style.style (), "apropos word"));
> +  gdb_printf (&styled_msg[4], _("To learn how to use GDB, type \"%ps\"."),
> +	      styled_string (command_style.style (), "tutorial"));
>   
>     /* If there isn't enough space to display the longest URL in a boxed
>        style, then don't use the box, the terminal will break the output
>
> base-commit: 449035c35f2169e0c690d83f28306275ab7f7463


-- 
Cheers,
Guinevere Larsen
It/she


  parent reply	other threads:[~2026-03-05 12:24 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-01-28 19:33 Guinevere Larsen
2026-01-28 19:44 ` Eli Zaretskii
2026-03-05 12:23 ` Guinevere Larsen [this message]
2026-03-05 13:31   ` Eli Zaretskii
2026-03-05 13:35     ` Guinevere Larsen

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=6aff4ca8-6a78-4a6b-8ed8-83a43bf3acda@redhat.com \
    --to=guinevere@redhat.com \
    --cc=eliz@gnu.org \
    --cc=gdb-patches@sourceware.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox