From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id dSejHK7LLWldYAwAWB0awg (envelope-from ) for ; Mon, 01 Dec 2025 12:09:02 -0500 Authentication-Results: simark.ca; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=NUo9Md3L; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id 644671E0B6; Mon, 01 Dec 2025 12:09:02 -0500 (EST) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-1.1 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_00, DKIMWL_WL_HIGH,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI, RCVD_IN_VALIDITY_CERTIFIED_BLOCKED,RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED autolearn=no autolearn_force=no version=4.0.1 Received: from sourceware.org (vm01.sourceware.org [38.145.34.32]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by simark.ca (Postfix) with ESMTPS id BE6AE1E08D for ; Mon, 01 Dec 2025 12:09:00 -0500 (EST) Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 20E0F48FD86E for ; Mon, 1 Dec 2025 17:09:00 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 20E0F48FD86E Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=NUo9Md3L Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTP id 8458C4BA540C for ; Mon, 1 Dec 2025 17:08:30 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 8458C4BA540C Authentication-Results: sourceware.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 8458C4BA540C Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1764608910; cv=none; b=u6+SwaTA6kemHqfxW1ADHmkFeu73GXS6odaB5BvW6OTgSutyLUu8fQFJ+nGWEE26NdPxDM28w0Qiyo5/3SsVhow3IIegey+wc/Z07jv2tE3tOyasN+hbu11jbPXKchsYV/Y3WxO1Qa/BB94FzOVwTpfM1WnX8Sm4rz5GkUQETCA= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1764608910; c=relaxed/simple; bh=UGierZlrBT1nG7+Kopdal1E1/0Q96JUjG86iK264V7Q=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=Se5hHgzceoAWvF+mKT2HxH1CGIzhLKyOZACOxV+Y3OU/k1PCD+gejH3TMEzEHc8XRaHNk3bJ8UJWbf1pndzNz+Zf23Vj7utUsbCjyqA82gntKKO//DJf+QxCWt9dReRwx802YtURuZhsXNwFB170oAq/GwDo//LE2I3Tel48e08= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 8458C4BA540C DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1764608910; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=w8IlSXT3/9sGnTTORBFj7D21PP6QP1DW3pHj0F9+rK4=; b=NUo9Md3LbIkR6kSXycjaUa9K0fh6Vt9FrZpI/Y5Z2v4KvmgyC2JBc7N9nYdlYo0os/rBQ/ 2p2BcKQMRf4LwruHKhzxDcjCTSDjF7puVEkYdVAWN6/LE1h5nAyz6o0nlsXcXkawWF/cLV EOo3TfSfzY2eSmrnj5TauygFIQFVFTM= Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-633-yNM3z9xfOQ-6DSo5nCyDag-1; Mon, 01 Dec 2025 12:08:28 -0500 X-MC-Unique: yNM3z9xfOQ-6DSo5nCyDag-1 X-Mimecast-MFC-AGG-ID: yNM3z9xfOQ-6DSo5nCyDag_1764608907 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 8FD971955EBC for ; Mon, 1 Dec 2025 17:08:27 +0000 (UTC) Received: from pi.hole.com (unknown [10.96.134.142]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 1D41430001A4; Mon, 1 Dec 2025 17:08:25 +0000 (UTC) From: Guinevere Larsen To: gdb-patches@sourceware.org Cc: Guinevere Larsen Subject: [PATCH] gdb: add tutorial command Date: Mon, 1 Dec 2025 14:08:19 -0300 Message-ID: <20251201170819.1573624-1-guinevere@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: q8f9lWzivz91zdqZDxuM3BTTONyiyz4BdPMG8x6ilzQ_1764608907 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: 8bit content-type: text/plain; charset="US-ASCII"; x-default=true X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gdb-patches-bounces~public-inbox=simark.ca@sourceware.org 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. --- gdb/NEWS | 4 + gdb/data-directory/Makefile.in | 1 + gdb/python/lib/gdb/command/tutorial.py | 358 +++++++++++++++++++++++++ 3 files changed, 363 insertions(+) create mode 100644 gdb/python/lib/gdb/command/tutorial.py diff --git a/gdb/NEWS b/gdb/NEWS index 01c998f4ea0..4543404d28a 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -59,6 +59,10 @@ maintenance test-remote-args ARGS Test splitting and joining of inferior arguments ARGS as they would be split and joined when being passed to a remote target. +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 d7f4c988fb6..d80ff760ee6 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/python/lib/gdb/command/tutorial.py b/gdb/python/lib/gdb/command/tutorial.py new file mode 100644 index 00000000000..2ca4565793e --- /dev/null +++ b/gdb/python/lib/gdb/command/tutorial.py @@ -0,0 +1,358 @@ +# GDB 'tutorial' command. +# Copyright (C) 2025 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 . + +"""Implementation of the GDB 'tutorial' command using the GDB Python API.""" + +import os + +import gdb + +state_string = [ + """ +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 ' 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. +""", + """ +Please enter the desired binary name for the example program. +""", + """ +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:""", + """ +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. +""", + """ +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 " +to break right before executing line ''. 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 tutorial. Feel free to debug to your +preference. The following commands may be helpful: + "next" - execute one line, skips functions + "display" - like print, but every time the inferior stops + "watch" - like breakpoint, but when a variable changes +Feel free to use "help " 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 satisfied with exploring this example +program, use "quit" to leave the tutorial. +""", +] + + +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 + ) + # If left empty, the files won't have been created yet. + self.example_file = "" + self.output_file = "" + self.prompt = "(tutorial-gdb) " + # Whether we should print a recap of the tutorial when + # leaving. + self.should_print_recap = False + # Get last word of the input, this is either "on." or "off.". + pagination = gdb.execute("show pagination", to_string=True).split()[-1] + # Remove period at the end. + self.pagination = pagination.strip(".") + + def invoke(self, arg_str, from_tty): + self._setup() + print(state_string[0]) + self.example_file = self._get_user_input( + "Leave empty for default, example_code.c: " + ) + + if self.example_file == "": + self.example_file = "example_code.c" + + try: + self._generateFile() + self.teachMainCommands() + except Exception: + print("leaving tutorial") + finally: + if self.should_print_recap: + self._print_recap() + self._cleanup() + + def _setup(self): + # Avoid pagination as the command will take several screens to be + # fully executed. + gdb.execute("set pagination off") + # $_exitcode is used to recognize when the user has executed the + # inferior at least once, to advance the state. Therefore we + # need it to start with the known value of "no execution". + gdb.set_convenience_variable("_exitcode", None) + + def _cleanup(self): + # Clean up example code. + try: + f = open(self.example_file) + f.close() + print("removing", self.example_file) + os.remove(self.example_file) + except FileNotFoundError: + # File doesn't exist, nothing to do. + pass + try: + f = open(self.output_file) + f.close() + print("removing", self.output_file) + os.remove(self.output_file) + except FileNotFoundError: + # File doesn't exist, nothing to do. + pass + # Undo setup. Pagination should be the last change. + gdb.execute("set pagination " + self.pagination) + + def _get_user_input(self, msg): + inp = input(msg) + while inp.startswith("help "): + try: + gdb.execute(inp) + except gdb.error as e: + print(e) + inp = input(msg) + if inp == "quit": + raise Exception("user requested to quit") + return inp + + def _run_user_input(self, msg): + inp = self._get_user_input(msg) + try: + gdb.execute(inp) + except gdb.error as e: + print(e) + + def _print_recap(self): + 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 + """ + ) + + def _get_inferior_binary(self): + inf = gdb.execute("inferior", to_string=True) + inf = inf[inf.find("(") + 1 : inf.rfind(")")] + return inf + + def teachMainCommands(self): + print(state_string[3]) + print(" file", self.output_file) + curr_bin = "" + while not curr_bin.endswith(self.output_file): + self._run_user_input(self.prompt) + curr_bin = self._get_inferior_binary() + + print(state_string[4]) + while gdb.convenience_variable("_exitcode") is None: + self._run_user_input(self.prompt) + + print(state_string[5]) + while not self.inferior_in_location(25): + self._run_user_input(self.prompt) + + print(state_string[6]) + while gdb.convenience_variable("_exitcode") != 1: + self._run_user_input(self.prompt) + + print(state_string[7]) + while not self.inferior_in_location(13, 20): + self._run_user_input(self.prompt) + + self.should_print_recap = True + print(state_string[8]) + while True: + self._run_user_input(self.prompt) + + def inferior_in_location(self, 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: + pass + return False + + def _generateFile(self): + code = """ +#include + +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(self.example_file, "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'"{self.example_file}"') + print(code) + + print(state_string[1]) + self.output_file = self._get_user_input( + "Leave empty for the default name, a.out: " + ) + if self.output_file == "": + self.output_file = "a.out" + + print(state_string[2]) + print(f" gcc {self.example_file} -g -o {self.output_file}\n") + inp = self._get_user_input("Enter the compilation command: ") + if inp == "": + inp = "gcc " + self.example_file + " -g -o " + self.output_file + 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() base-commit: 25902bd0baaf58ae86ac46f7d1d71f3f9ee62c1c -- 2.52.0