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