From: Guinevere Larsen <guinevere@redhat.com>
To: gdb-patches@sourceware.org
Cc: Guinevere Larsen <guinevere@redhat.com>, Eli Zaretskii <eliz@gnu.org>
Subject: [PATCH v3] gdb: add tutorial command
Date: Wed, 28 Jan 2026 16:33:44 -0300 [thread overview]
Message-ID: <20260128193344.2907827-1-guinevere@redhat.com> (raw)
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
--
2.52.0
next reply other threads:[~2026-01-28 19:35 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-01-28 19:33 Guinevere Larsen [this message]
2026-01-28 19:44 ` Eli Zaretskii
2026-03-05 12:23 ` Guinevere Larsen
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=20260128193344.2907827-1-guinevere@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