From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id /9wPH/B1qWmqgBYAWB0awg (envelope-from ) for ; Thu, 05 Mar 2026 07:24:16 -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=N+S/hjmb; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id 6B69D1E089; Thu, 05 Mar 2026 07:24:16 -0500 (EST) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-3.4 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_DNSWL_MED,RCVD_IN_VALIDITY_CERTIFIED_BLOCKED, RCVD_IN_VALIDITY_RPBL_BLOCKED,RCVD_IN_VALIDITY_SAFE_BLOCKED autolearn=ham autolearn_force=no version=4.0.1 Received: from vm01.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 36B271E089 for ; Thu, 05 Mar 2026 07:24:14 -0500 (EST) Received: from vm01.sourceware.org (localhost [127.0.0.1]) by sourceware.org (Postfix) with ESMTP id DAB434BA23CF for ; Thu, 5 Mar 2026 12:24:12 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org DAB434BA23CF 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=N+S/hjmb Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTP id F25DC4BA23C0 for ; Thu, 5 Mar 2026 12:23:10 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org F25DC4BA23C0 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 F25DC4BA23C0 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1772713391; cv=none; b=oTAYlHJrF4bv2Ua9xSnoGCkz2sDeL2M8zLYIpl7MpS0usRWtDXQOQanOs42sX+140ERUiTB8Sxfu39V1MNnCqj/VEp70ppdfFBaRX3/hwrAlPAx05OCC5S/vbJxJY1UhvfvB849V4u45thk9G5y/Wn0gQ2eJvsiU/raMWTR5G2A= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1772713391; c=relaxed/simple; bh=ol7FYuejZZ3+QOzzyY+QLwhw4HDskJbYtGlAlsQiuIU=; h=DKIM-Signature:Message-ID:Date:MIME-Version:Subject:To:From; b=Hfq6RcImuO/E8xCrxLUoQEvyOV4BzKcG4vPnNg10OfeA11kRqZEn+9FvWkuyYdtsyQbZ0tkSpL/rd5bd9pg3YWo+bNxsIVgEoBxNDusXdEmUAhruKoZ7bl3swNUe9xRWabDVLpkOlVt0Fw/D3VKP2hTx3Lkb4fMce08TQCclW5s= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org F25DC4BA23C0 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1772713390; 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: in-reply-to:in-reply-to:references:references; bh=zsbngclh0OcVItmi7sgpIEZwFZ/6pQAZE/jln2CqeTU=; b=N+S/hjmb6CwM935ETid7HKLLs8VLRNHDLgT8yzsC2aqafJXA8dcSynse9s8i90uMArEEk9 ubQd0VVYXaP/7Odj2laNa2fjOg41vHh5dd++ed86L7qHgclulTHxeNX94aPK1qvgbpobID tQUIyjNfZEMtrSfcM/yh1EFnH22s2oM= Received: from mail-vk1-f198.google.com (mail-vk1-f198.google.com [209.85.221.198]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-160-EhLvQdViPSqEAkwd5mbo2w-1; Thu, 05 Mar 2026 07:23:09 -0500 X-MC-Unique: EhLvQdViPSqEAkwd5mbo2w-1 X-Mimecast-MFC-AGG-ID: EhLvQdViPSqEAkwd5mbo2w_1772713389 Received: by mail-vk1-f198.google.com with SMTP id 71dfb90a1353d-56a844fc7deso9238119e0c.2 for ; Thu, 05 Mar 2026 04:23:09 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1772713388; x=1773318188; h=content-transfer-encoding:in-reply-to:from:content-language :references:cc:to:subject:user-agent:mime-version:date:message-id :x-gm-gg:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=zsbngclh0OcVItmi7sgpIEZwFZ/6pQAZE/jln2CqeTU=; b=lhQaXSAksw7aA+C4y/z3FHNAlZG4PpsX4EhvgKUoOsrbmde4y8kzaWmJm4ToFVVfzz SkJ9sP7ZQUXujiGejFAyp2NlcsYJkhx1l5fHxt8CMa06WJTdBlLd81tIstxPw+Wb6LJH aQ3wjFS/Pq6hYVfNlFfPhpkZisZ3q9gmoUf3U9dbs/4jVzymditWlSWJtVnjCoCjQb4r httYiDX883v61P1Dg5umOSVJX3pGcEzpwJbTIlY5WVR3eztI7wn6ZRRdY5jkrN7FikbL 3ITrpynHWA8i57WrS0v0cm6fl9cdZ+iCcdNsItSu5rUfm+TTRP8XwqqOtITAJ7Z2oSdh 0Trg== X-Gm-Message-State: AOJu0YwgunQI2111IPDoRRye4Ezvq0uEHSzyqasAxnW/YhAYo1K2lpvY 5eCz5iKbtIa42lbaIvNP/W3UEeaCPnN1Z/gNh6M2FFkUZ3xYqYsXsJEpOvFkAeiXaWIohNukgFl jVAe3l6OEK2XMZ40H1bgnhM0/pm5nYXO9HpNnuCzm3SWcNwWe9/KoH1fqj46gQdZAiGIcsoaI/C cNpSo6XWYEILQb7PLX/+UGE7MTEKm7P0rz6lisMdmD3UDAD5G6 X-Gm-Gg: ATEYQzyL7Psj8nWP36ZzQ+FKINwL4iF2FhnBpbbysfcCyEJDY3DtzPFglBDR1sLajKv Q9LYvbkymvm3k7BoodeoX9sHgcc70xxuaHHvC+g5JeREXraN7nx1Qib29Po/FkIGOwXsMm/9uG7 ujMkrTW6AVcO/bCKIrVjQHWWdZHj9bYzyUNCT0dYzyWl8BBMpJ7ssINRD1pb42RqVHUx37DLgkE N5S60lSntdgTvA00cKeCvQWUAOXTEk+IVC6ASGUCjJaI/w9Z1VAFBz213Akkg1HtrbHhzX1p89b iZtJl7IhBIOcPpNCqm6pMFuOJOSbnnEKU8msfA8Nqa/K0cpj/zd+nB7BOAG9bq5ltKPSB+woZcw J/wT/IeXOQ4V7mlpIQIOORV2VYwPKVmk= X-Received: by 2002:a05:6122:d8b:b0:567:44ba:bd87 with SMTP id 71dfb90a1353d-56ae74d41f7mr2345288e0c.1.1772713388064; Thu, 05 Mar 2026 04:23:08 -0800 (PST) X-Received: by 2002:a05:6122:d8b:b0:567:44ba:bd87 with SMTP id 71dfb90a1353d-56ae74d41f7mr2345274e0c.1.1772713387306; Thu, 05 Mar 2026 04:23:07 -0800 (PST) Received: from ?IPV6:2804:14d:8084:993e::75d? ([2804:14d:8084:993e::75d]) by smtp.gmail.com with ESMTPSA id 71dfb90a1353d-56a92049d05sm27503012e0c.12.2026.03.05.04.23.05 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Thu, 05 Mar 2026 04:23:06 -0800 (PST) Message-ID: <6aff4ca8-6a78-4a6b-8ed8-83a43bf3acda@redhat.com> Date: Thu, 5 Mar 2026 09:23:03 -0300 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: [PATCH v3] gdb: add tutorial command To: gdb-patches@sourceware.org, Guinevere Larsen Cc: Eli Zaretskii References: <20260128193344.2907827-1-guinevere@redhat.com> From: Guinevere Larsen In-Reply-To: <20260128193344.2907827-1-guinevere@redhat.com> X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: EodHCGI1DkyJTZZUr45M1wPPKPJ0ai2i8m_k0nIO-fw_1772713389 X-Mimecast-Originator: redhat.com Content-Language: en-US Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 7bit 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 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 > --- > 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 ' 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 . > + > +"""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 **' > +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**'. 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 **' 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 **' 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 > + > +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 styled_msg { > + std::array 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