From: Guinevere Larsen <guinevere@redhat.com>
To: gdb-patches@sourceware.org
Cc: Guinevere Larsen <guinevere@redhat.com>
Subject: [PATCH v2] gdb: add tutorial command
Date: Thu, 22 Jan 2026 17:28:34 -0300 [thread overview]
Message-ID: <20260122202834.393095-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.
---
gdb/NEWS | 4 +
gdb/data-directory/Makefile.in | 1 +
gdb/doc/gdb.texinfo | 34 +++
gdb/python/lib/gdb/command/tutorial.py | 404 +++++++++++++++++++++++++
gdb/top.c | 5 +-
5 files changed, 447 insertions(+), 1 deletion(-)
create mode 100644 gdb/python/lib/gdb/command/tutorial.py
diff --git a/gdb/NEWS b/gdb/NEWS
index 74fc353d7e9..5c8c06ca301 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -80,6 +80,10 @@ 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
+ Guided example session with a generated C file, to teach an entirely
+ new user how to use GDB for basic debugging.
+
* Changed commands
maintenance info program-spaces
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 7059f73935c..99359f27822 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -2577,6 +2577,40 @@ 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.
+Warning: If you have an ongoing debug session, this command will kill that
+session and you'll have to start from scratch.
+
+@smallexample
+(@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 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..7e0bd0331f0
--- /dev/null
+++ b/gdb/python/lib/gdb/command/tutorial.py
@@ -0,0 +1,404 @@
+# 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 file, which GDB calls the
+"inferior", needs to be loaded. 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". That command will evaluate any arbitrary
+expression valid for the current language, with all side effects
+that the expression would have.
+This means you can use it to prototype a solution without
+needing to recompile the inferior. Try it by setting 'vec'
+to a sorted array, like by using:
+ print vec = {0, 1, 2, 3, 4, 5}
+and see how the program would end, with the "continue"
+command again.
+""",
+ """
+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 <cmd>" 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 print 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 then get an input from the user
+# The input must be one of: y, Y, n, N
+# Return True if the user accepted (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 == 3:
+ return False
+ elif tutorial_state == 2:
+ return inferior_in_location(25)
+ elif tutorial_state == 4:
+ return inferior_in_location(13, 20)
+ else:
+ return False
+
+
+def cleanup():
+ 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
+ * shell
+ * file
+ * start
+ * continue
+ * list
+ * break
+ * print
+ * 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):
+ if tutorial_state == 1:
+ increment_state()
+ elif tutorial_state == 3:
+ if event.exit_code == 1:
+ increment_state()
+ else:
+ print("The solution didn't work. Try again!")
+ elif tutorial_state == (len(state_string) - 1):
+ print(
+ "The tutorial is complete. Exiting tutorial and cleaning up the artifacts"
+ )
+ cleanup()
+ else:
+ opt = get_yn_opt(
+ "The tutorial program exited unexpectedly, "
+ + "would you like to quit the tutorial?"
+ )
+ if opt:
+ cleanup()
+
+
+# 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:
+ print("Reminder:", tutorial_reminders[tutorial_state])
+ display_counter = 1
+
+ if tutorial_state >= len(state_string):
+ cleanup()
+ gdb.events.before_prompt.disconnect(tutorial_hook)
+ elif display_counter == 0:
+ 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
+ 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. 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.
+"""
+ )
+ 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
+
+ # Keeping for historical context
+ try:
+ self.teachMainCommands()
+ except Exception:
+ print("leaving tutorial")
+
+ 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: 483c5cc7764832b2944f99e5548103a7c1f89ea7
--
2.52.0
next reply other threads:[~2026-01-22 20:29 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-01-22 20:28 Guinevere Larsen [this message]
2026-01-23 7:11 ` Eli Zaretskii
2026-01-23 14:14 ` Guinevere Larsen
2026-01-23 14:23 ` Arsen Arsenović
2026-01-23 14:28 ` Eli Zaretskii
2026-01-26 14:54 ` Andrew Burgess
2026-01-26 18:50 ` Guinevere Larsen
2026-01-24 11:41 ` Hannes Domani
2026-01-26 18:08 ` Guinevere Larsen
2026-01-26 16:13 ` Andrew Burgess
2026-01-27 19:39 ` 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=20260122202834.393095-1-guinevere@redhat.com \
--to=guinevere@redhat.com \
--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