From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id HqonA6cUeWkvdBgAWB0awg (envelope-from ) for ; Tue, 27 Jan 2026 14:40:23 -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=K7rBhcUp; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id F04401E08D; Tue, 27 Jan 2026 14:40:22 -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 4C9581E08D for ; Tue, 27 Jan 2026 14:40:21 -0500 (EST) Received: from vm01.sourceware.org (localhost [127.0.0.1]) by sourceware.org (Postfix) with ESMTP id B57594BA23DA for ; Tue, 27 Jan 2026 19:40:20 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org B57594BA23DA 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=K7rBhcUp 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 8FF5E4BA2E26 for ; Tue, 27 Jan 2026 19:39:48 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 8FF5E4BA2E26 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 8FF5E4BA2E26 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=1769542788; cv=none; b=JgrxhBy3BrHy4pyG+h+ud5PieP6WEWOp75TlvSun+CI/rBNGKXUPYXOIGjhLC6Bc1DGJI113gHrE0V/Dw/QAqB6vOfUhxlmtL4ckMOC1GvqSTDPnSO98LRYMHGOuNNGMG5F1KAuxUUzW/xnz9Vu1IvjGglSakXpKEAFGLbseMCo= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1769542788; c=relaxed/simple; bh=fe9ppURjJRqyzq4EYmLboo3ySY1NoCGEyPBqPb7UnVE=; h=DKIM-Signature:Message-ID:Date:MIME-Version:Subject:To:From; b=EGWoRuEH3O84iB/0rFPTMD072XdVWQpZXM5b6Awr/NcMLAiCiJI11fSsrCvDF+OxVxweXJupezSdbnzBf334vZc50UNjg8GeeFFwVCjue0gEl+HPnYaz5Zh/JwM2Q6+/pam6+jQ7yIi0zqWjDU1cfgMpE168zcowEfUd98qrFk0= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 8FF5E4BA2E26 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1769542788; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=aGb853ezTJZS0MZqdAxeEvR2jm/EHRs6NmaGQW0jeck=; b=K7rBhcUpd9JIzJD7zBW+6iW4/2b+lxWB2bHPGpoMQ7fGfSlvZYx0LWpFrwh7u1uOcHpCYG pNgZ4a1tXeLmivIUxvkWL7+k39WaOQpQCvwZQzj7q521lbz34gCvBxPB4c8RI7llAT7nUO +R0yBPaROHQPI3raSev+TRFSchV9q9Q= Received: from mail-dl1-f71.google.com (mail-dl1-f71.google.com [74.125.82.71]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-622-sHMlSHzbOoSOhRceKvG3Ew-1; Tue, 27 Jan 2026 14:39:46 -0500 X-MC-Unique: sHMlSHzbOoSOhRceKvG3Ew-1 X-Mimecast-MFC-AGG-ID: sHMlSHzbOoSOhRceKvG3Ew_1769542785 Received: by mail-dl1-f71.google.com with SMTP id a92af1059eb24-12498fcee1eso7564595c88.1 for ; Tue, 27 Jan 2026 11:39:46 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769542785; x=1770147585; h=content-transfer-encoding:in-reply-to:from:content-language :references: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=aGb853ezTJZS0MZqdAxeEvR2jm/EHRs6NmaGQW0jeck=; b=a2G/53/13L2UuEhH0djhopz5jk9Xp5VXwABu6G3RZxGVFsQOlhbOIENiNKM4wBKbkd WGkBtPw0ly1s/y47H6cPnOvRC5IcvsfzP1jNTxx/4Wy4B0ncIfJyRJl7tJHn506IrulC 4FwDvEQ/pGMvTagUOYvwLdqnxoBRkN1lccZeZlYeD6oQLSQ1Go8G3qZOF0SMNYMKVx47 TMNu/q56YyGLqmJ6EnlZmAg4x82pGWcUTWf/3r6eG/cbm0eW7npTEk1G5Re3FOVCVxHN vEA74ySlo23wWiDcCLja427dAo+1WjnQ/WPYsVRqCQDid2NYfM36+R5VmGZbuOC90tlN FnLw== X-Forwarded-Encrypted: i=1; AJvYcCXgUK8UyNr+o5rF1uC5CuGhHSmenVgi5l4CWCWulNFUeZAxUR8LiWWz3hxScm1fDPizUXuoIMwrCKa7XQ==@sourceware.org X-Gm-Message-State: AOJu0YzyKj2qWm5tgMHK6FmpGZXmj94eJ/syorMes1pTcOf1SNwDLUre OhkDanvpHTNKU3dhtoqS8ZWacgnPXdmaHH+3H7sxIVYw8LDC3y32ZE1C6YcRognot4ltW7++qW6 wCXhgHIjdmMANkGN1DKcdCPHn4ykPxfzc8LTUM4U9ZYJEJ6+fDdhy7kA2iqYB0qQ= X-Gm-Gg: AZuq6aIoOdgaYEy5dBTEwf5MMit/kkocA/K9gLlSBKb9h2gFusHw3Y/e/itSxyuwHls PHys86825YnwIoHzXX3Agv1LVBdv04mej+LQ2DaCvPxwyhnzZQZKvPHhMsfjn5u2g8UHRqIA9OK 8dvJnP9KqLhFOnUUH47Dn3Ukc1+4qUL1EKxvVDTDY38mtyiI14NZTlDOdHHkXzaqX4wCQJjAqsw us4V/AY0/KH8POTpmxjn+n/foIi4cQskKi1gIb0GZbxgfxdqv2dA52FOge+FlMEqVcsF4XZgwWx 7Eg2Cpqxor/ZQRvcuMrhW0SP2hCFTmOAEOwe8Lz8Krd809bqS6QXBaxBtExtUfZcE11ns/SVzkJ nhTBnIV7RImhVcoCJPYpJdQ== X-Received: by 2002:a05:7022:6982:b0:11b:9b9f:426b with SMTP id a92af1059eb24-124a006aa70mr1579831c88.20.1769542784881; Tue, 27 Jan 2026 11:39:44 -0800 (PST) X-Received: by 2002:a05:7022:6982:b0:11b:9b9f:426b with SMTP id a92af1059eb24-124a006aa70mr1579813c88.20.1769542783812; Tue, 27 Jan 2026 11:39:43 -0800 (PST) Received: from ?IPV6:2804:14d:8084:a5b1::1002? ([2804:14d:8084:a5b1::1002]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-124a7c62819sm826637c88.12.2026.01.27.11.39.41 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Tue, 27 Jan 2026 11:39:43 -0800 (PST) Message-ID: <49cb0018-6d33-4b7f-8246-5b6698b1767a@redhat.com> Date: Tue, 27 Jan 2026 16:39:40 -0300 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: [PATCH v2] gdb: add tutorial command To: Andrew Burgess , gdb-patches@sourceware.org References: <20260122202834.393095-1-guinevere@redhat.com> <87wm14fj3i.fsf@redhat.com> From: Guinevere Larsen In-Reply-To: <87wm14fj3i.fsf@redhat.com> X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: UiuqDWBXLVs36sw_yFL60tYC5hMTgEcaDxoo6RDf6ss_1769542785 X-Mimecast-Originator: redhat.com Content-Language: en-US Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 8bit 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 On 1/26/26 1:13 PM, Andrew Burgess wrote: > Guinevere Larsen writes: > >> 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 ' 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 . >> + >> +"""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. > I'm not sure I'd agree with this. The inferior is the whole thing being > debugged, the executable, shared libraries, process memory. The > executable, which the 'file' command loads, is just one part of an > inferior. > > I get we don't want to bike shed each and every word of the tutorial, > and this stuff can be polished later, so I guess, feel free to take my > nit-picking as much as you'd like. I think this is valuable feedback. I probably didn't really know the difference between them. I'll try to figure out some better wording for v3. > >> +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 > There is a reasonably new gdb.Style API, which would allow you to: > > command_style = gdb.Style('command') > print(command_style.apply("start")) > > this will print 'start' using the 'command' style. > > Here you could do something like: > > ...is by using the command '""" + command_style.apply('start') + """', > which runs the inferior... > > And you'll now have the command name styled. > > Of course, there's probably better ways to do this. I think I'd be > tempted to support some kind of basic markup scheme and then filter the > strings before printing. Good idea, I'll try to figure something like that out. Shouldn't be too hard to do. > Also, in the original, "Welcome to GDB! This quick tutorial..." text you > wrap commands in single quotes, but here you switch to double quotes. I > would be nice to make this consistent. I changed it to single quotes in > my example above, but only because I didn't know if 4 double quotes in a > row would do the right thing. Yeah, I'll standardize. I have this bad habit of doing things and forgetting what I chose the day before and just going ahead anyway. > >> +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. >> +""", > In stage 0 the tutorial text ends with a very definitive instruction for > what to do next: > > Use the following command to load the inferior: > file a.out > > In comparison the text here is more suggestive: > > ... One way to do that is by using the command "start", ... > > While it's not rocket science, I do think I prefer the approach from > stage 0, even if it means duplicating some text. I think each stage > should end with a definitive list of what to do next: > > Use the following two commands to run the inferior to completion: > start > continue I disagree. The reason is that information is much more likely to stick in someone's brain if they had to work to get the answer. It's why a class giving exposition on solving problems will be forgotten much faster than practical exercises with proper guidance. This is something I commonly hear in video game design critique, but holds true in most software design I think... Think about how many "website tours" you've taken that were immediately forgotten, but once you had to figure out how to do it without the tour you remember. The reason why the first step isn't vague is because it is just useful for the tutorial, as I expect most users to be familiar with making a `gdb ` call, so they don't need to remember that step for the long term. The rest of the tutorial wants to teach the user to reason with the commands explained, so that they can solve new problems, rather than teaching them the "correct" way to do something (especially since many of these have no correct way to be done). > >> + """ >> +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". 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 " to get more information on those > Elsewhere in the text you use 'help ' which I think is better. > But either way you should probably pick one. Agreed. Will update to be more verbose > >> +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 ' 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: ") > I tried running the tutorial in a 25 line terminal, and not long after > this input prompt you'll get a pagination request. The problem is that > GDB's pager doesn't understand that we've blocked at an input prompt, > giving users a chance to read what's on the screen. > > I wonder if we need a new API, like: 'gdb.reset_pager()' which calls > 'reinitialize_more_filter ()' (from utils.c). You'd then call this > immediately after each call to input() as you know at that point the > user has had a chance to read any previous text. > > Or maybe we should provide a gdb.input() function which is like input, > but includes the clear for us? > > Or maybe this doesn't need fixing at all for this commit, but is just > something to think about for the future. I think providing gdb.input is the best option, but if you wouldn't mind, I'd leave this as a future improvement. > >> + >> + 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 > Missing period at the end. oops, this was a note for myself, deleted everything past the return > >> + try: >> + self.teachMainCommands() >> + except Exception: >> + print("leaving tutorial") >> + >> + 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; > Shouldn't this code also adopt GNU coding standard? yeah fair point.  Updated > >> +}""" >> + 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")); > I think there's two possibilities here. > > This additional line of text could be made conditional on HAVE_PYTHON, > so we only advertise the command when it's available. > > Or, you can provide a default C++ implementation that prints a message > about the tutorial command requiring Python support. A Python command > will override a builtin C++ command, so if Python support is available, > that's what the user will see. Oh, I didn't know that this is how it worked. That makes it a lot easier to make the fallback command! I think I'll go that route > > Thanks, > Andrew > >> >> /* 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 -- Cheers, Guinevere Larsen It/she