From patchwork Mon Jul 22 14:42:08 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrew Burgess X-Patchwork-Id: 94322 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id A5F9A385E833 for ; Mon, 22 Jul 2024 14:43:40 +0000 (GMT) X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org 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 F1AF2385B50B for ; Mon, 22 Jul 2024 14:42:17 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org F1AF2385B50B Authentication-Results: sourceware.org; dmarc=pass (p=none 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 F1AF2385B50B 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=1721659342; cv=none; b=UqXOVEjw95hEvwKGY1OvJm52qwkIHX3u5jIJXKuudFuHcI7VF/6h7Xr4d75yXJY0bWmNDBB92QfzNXfb2jdLoG3kPR9ZtbSRpHMTguuHQeSw9upNMloHuVOAxNVwNodneaIi01GfUbhXaCyzIUQXBV5pwNSStHtcQdqjpzxX/AY= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1721659342; c=relaxed/simple; bh=Z/ZTYE1VMzbr4voFUibK4tlFyutz626qsZdLko7fZUQ=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=caxHkDDjbSy5jQXv6Y2BThWphZlnIyZvxFSl3z/JyoxKRMtBkGJ1bgxj9IMrkywM2F6runutKtNjvfFEzNx34poGduFZn6oWCmN8URq5zw9mlUp58NehnOP+5nghf3XdzeLpG749pxDAgDni5QcT31SyIEjvWckRrmfTsnHgEbs= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1721659337; 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=7/VnA6UvGkP1swwrfKBEuj1Ayp/UL6XirTj4AQHggjE=; b=O+DOmsnwfnIz0OqA+J7HqQxXPp3UmmA8ZGlrGyVkCMqDK/w+ehx9LQdTd1ExOkdWy4mnCG zSdv2j48/vGCh/hkIe6SchqavocZy6O7DhO7kc2NAy6O2zlmaosH/8Y750HP39L3H8o7iB a+Hc15blo5CGGmdUHfpuS992QjzFubg= Received: from mail-wm1-f72.google.com (mail-wm1-f72.google.com [209.85.128.72]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-108-yFDLmJEPPLu4nHwwvHz-Mw-1; Mon, 22 Jul 2024 10:42:16 -0400 X-MC-Unique: yFDLmJEPPLu4nHwwvHz-Mw-1 Received: by mail-wm1-f72.google.com with SMTP id 5b1f17b1804b1-427e9c2f82cso8106205e9.1 for ; Mon, 22 Jul 2024 07:42:16 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1721659335; x=1722264135; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=Cn/nu8ZuZN66y7tsPAiFU1EMEzb6J4rYTkFNLgQqH/g=; b=rO/hmVbvEGABw9vpCzvzWKBY44fERscvW7UuicJjs2OyRKOsZ9BHGdeV6lJDnyH5Oc ixoLFyyGkkROKcJ/ZGlcmbqN/IkMvzTrBrR7m7Be/Pxi+3VmNnwNBD33mUSOFbgiUJ0M ESPT6KSfwcLcmA34JIDwYQNt6quREtHOgzUlYx3+15xDFKoWGPIFEWJSvDClSBHK5o0z /FHRib7a061LxbQrgdNDJi+gUN3PHPHKCMtKseXLlDKmSxyTDcpV3A/0sTmrRR0BH3CQ 0LqDPZDJxj24hzSuP++tYLmzrwOy5fS3rkKS5pMQmh69kQGrHUnCIo1gLoBWOHqXXqbV KCvQ== X-Gm-Message-State: AOJu0YyPgX2542uWDHso3vgAR/4xETXB0bUtfdKYMGDlx6e4UGHwFmbJ tF0jdnBcFVzl1gw8KAdTfDvzIVPDljfsRUgIeLtS/iwYqw9/aQs2VIfQDzwVDrbKqHI9HPheYeK NDxsTyvX+hf+JeudNpVeiyD9ViExEOIDbmaIB8Rbkmyp2CMY+yLFlDSidjP/xwg6BzRY6ORq0a0 BtB9zx6k5ETjM970rQ9QYa3plUm7R9n7ToFaAoWEXAM0s= X-Received: by 2002:a05:600c:1994:b0:426:5b21:9801 with SMTP id 5b1f17b1804b1-427daa61ce8mr52431585e9.27.1721659334533; Mon, 22 Jul 2024 07:42:14 -0700 (PDT) X-Google-Smtp-Source: AGHT+IEKQhUdvu67CAntxTzxrTluECqjREfrbW/XnSHkxstDMO8dKQTvXZMOGQaMTjc/Txui+1nrwQ== X-Received: by 2002:a05:600c:1994:b0:426:5b21:9801 with SMTP id 5b1f17b1804b1-427daa61ce8mr52431355e9.27.1721659333737; Mon, 22 Jul 2024 07:42:13 -0700 (PDT) Received: from localhost ([31.111.84.186]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-427d2a8d8b5sm155271935e9.38.2024.07.22.07.42.12 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 22 Jul 2024 07:42:13 -0700 (PDT) From: Andrew Burgess To: gdb-patches@sourceware.org Cc: Andrew Burgess Subject: [PATCH 1/2] gdb: Add 'maint info inline-frames' command Date: Mon, 22 Jul 2024 15:42:08 +0100 Message-Id: X-Mailer: git-send-email 2.25.4 In-Reply-To: References: MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-11.5 required=5.0 tests=BAYES_00, DKIM_INVALID, DKIM_SIGNED, GIT_PATCH_0, KAM_DMARC_NONE, KAM_DMARC_STATUS, KAM_SHORT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H4, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org 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~patchwork=sourceware.org@sourceware.org While reviewing a patch I wanted to view GDB's inline frame state. I don't believe there's currently a maintenance command to view this information, so in this commit I've added one. The new command is: maintenance info inline-frames maintenance info inline-frames ADDRESS The command lists the inline frames that start at ADDRESS, or at the current $pc if no ADDRESS is given. The command also displays the "outer" function in which the inline functions are present. An example of the command output: (gdb) maintenance info inline-frames program counter = 0x401137 skipped frames = 1 bar > foo main (gdb) This tells us that function 'main' called 'foo' which called 'bar'. The functions 'foo' and 'bar' are both inline and both start at the address 0x401137. Currently GDB considers the inferior to be stopped in frame 'foo' (note the '>' marker), this means that there is 1 skipped frame (function 'bar'). The function 'main' is the outer function. The outer function might not start at 0x401137, it is simply the function that contains the inline functions. If the user does a 'step' then GDB will not actually move the inferior forward, but will instead simply tell the user that the inferior entered 'bar'. The output of 'maint info inline-frames' will change like this: (gdb) step bar () at inline.c:6 6 ++global_counter; (gdb) maintenance info inline-frames program counter = 0x401137 skipped frames = 0 > bar foo main (gdb) Now GDB is in function 'bar' and there are no skipped frames. I've added a basic test for the new command. Please excuse the file name for the new test, in the next commit I'll be adding additional tests and at that point the file name will make sense. Reviewed-By: Eli Zaretskii --- gdb/NEWS | 7 + gdb/doc/gdb.texinfo | 97 +++++++++++ gdb/inline-frame.c | 163 ++++++++++++++++-- .../maint-info-inline-frames-and-blocks.c | 57 ++++++ .../maint-info-inline-frames-and-blocks.exp | 147 ++++++++++++++++ 5 files changed, 456 insertions(+), 15 deletions(-) create mode 100644 gdb/testsuite/gdb.base/maint-info-inline-frames-and-blocks.c create mode 100644 gdb/testsuite/gdb.base/maint-info-inline-frames-and-blocks.exp diff --git a/gdb/NEWS b/gdb/NEWS index b56ba9b36ce..1cbc05c5a18 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -13,6 +13,13 @@ This may cause breakage when using an incompatible libc, like uclibc or newlib, or an older glibc. +* New commands + +maintenance info inline-frames [ADDRESS] + New command which displays GDB's inline-frame information for the + current address, or for ADDRESS if specified. The output identifies + inlined frames which start at the specified address. + *** Changes in GDB 15 * The MPX commands "show/set mpx bound" have been deprecated, as Intel diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index c55913c7c79..0f710cf9289 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -41850,6 +41850,103 @@ frame-id for frame #2: @{stack=0x7fffffffac90,code=0x000000000040111c,!special@} @end smallexample +@kindex maint info inline-frames +@item maint info inline-frames +@itemx maint info inline-frames @var{address} +Print information about inlined frames which start at the current +address, or @var{address} if specified. + +In order to allow a user to correctly step into inlined function, +@value{GDBN} needs to identify which inlined functions start at a +particular address, and @value{GDBN} also needs to track which of +these functions was last displayed to the user as the current frame. + +Imagine a situation where function @code{main} calls @code{foo}, which +then calls @code{bar}, something like this: + +@smallexample +int +main () +@{ + /* Some interesting code here... */ + + foo (); + + /* More interesting code here... */ +@} + +void +foo () +@{ + bar (); +@} + +void +bar () +@{ + /* Some interesting code here... */ +@} +@end smallexample + +As both @code{foo} and @code{bar} are inlined within @code{main} then +there will be one address within @code{main} which is also the start +of @code{foo} and also the start of @code{bar}. When the user stops +at this address then will initially be told they are in @code{main}, +if the user does a @command{step} then @value{GDBN} doesn't actually +step the inferior, instead the user is now told they have entered +@code{foo}. After the next @command{step} the user is told they have +entered @code{bar}. The @command{maint info inline-frames} command +can be used to view this internal @value{GDBN} state, like this: + +@smallexample +(@value{GDBP}) step +24 foo (); +(@value{GDBP}) maintenance info inline-frames +program counter = 0x401137 +skipped frames = 2 + bar + foo +> main +@end smallexample + +Here the user is stopped in @code{main} at the call to @code{foo}. The +inline-frames information shows that at this address @value{GDBN} has +found the start of inlined functions @code{bar} and @code{foo}, but +currently @value{GDBN} has skipped 2 frames and considers @code{main} +to be the current frame, this is indicated with the @samp{>}. + +If the user performs a @command{step} to enter @code{foo} then the +situation is updated: + +@smallexample +(@value{GDBP}) step +foo () at inline.c:14 +14 bar (); +(@value{GDBP}) maintenance info inline-frames +program counter = 0x401137 +skipped frames = 1 + bar +> foo + main +@end smallexample + +Now @value{GDBN} considers @code{foo} to be the current frame, and it +is marked as such with the @samp{>}. + +Finally, the user performs another @command{step} to enter @code{bar}: + +@smallexample +(@value{GDBP}) step +bar () at inline.c:6 +6 ++global_counter; +(@value{GDBP}) maintenance info inline-frames +program counter = 0x401137 +skipped frames = 0 +> bar + foo + main +@end smallexample + @kindex maint print registers @kindex maint print raw-registers @kindex maint print cooked-registers diff --git a/gdb/inline-frame.c b/gdb/inline-frame.c index f65f39be40d..96cea9eeb1e 100644 --- a/gdb/inline-frame.c +++ b/gdb/inline-frame.c @@ -27,6 +27,7 @@ #include "regcache.h" #include "symtab.h" #include "frame.h" +#include "cli/cli-cmds.h" #include /* We need to save a few variables for every thread stopped at the @@ -336,25 +337,28 @@ stopped_by_user_bp_inline_frame (const block *frame_block, bpstat *stop_chain) return false; } -/* See inline-frame.h. */ +/* Gather information about inlined frames that start at THIS_PC. + STOP_CHAIN indicates where GDB has just stopped. *SKIPPED_SYMS will + have the symbols for all skipped frames at THIS_PC appended too it. If + OUTER_SYMBOL is not nullptr then *OUTER_SYMBOL is set to the outer + function symbol, this is the function that contains all of the skipped + functions. If the outer function can't be established then + *OUTER_SYMBOL will be set to nullptr. */ -void -skip_inline_frames (thread_info *thread, bpstat *stop_chain) +static int +gather_inline_frame_info (CORE_ADDR this_pc, bpstat *stop_chain, + std::vector *skipped_syms, + const struct symbol **outer_symbol) { - const struct block *frame_block, *cur_block; - std::vector skipped_syms; int skip_count = 0; - /* This function is called right after reinitializing the frame - cache. We try not to do more unwinding than absolutely - necessary, for performance. */ - CORE_ADDR this_pc = get_frame_pc (get_current_frame ()); - frame_block = block_for_pc (this_pc); + if (outer_symbol != nullptr) + *outer_symbol = nullptr; - if (frame_block != NULL) + const struct block *cur_block = block_for_pc (this_pc); + if (cur_block != NULL) { - cur_block = frame_block; - while (cur_block->superblock ()) + while (cur_block->superblock () != nullptr) { if (cur_block->inlined_p ()) { @@ -370,18 +374,38 @@ skip_inline_frames (thread_info *thread, bpstat *stop_chain) break; skip_count++; - skipped_syms.push_back (cur_block->function ()); + skipped_syms->push_back (cur_block->function ()); } else break; } - else if (cur_block->function () != NULL) + else if (cur_block->function () != nullptr) break; cur_block = cur_block->superblock (); } } + if (outer_symbol != nullptr && cur_block != nullptr) + *outer_symbol = cur_block->function (); + + return skip_count; +} + +/* See inline-frame.h. */ + +void +skip_inline_frames (thread_info *thread, bpstat *stop_chain) +{ + std::vector skipped_syms; + + /* This function is called right after reinitializing the frame + cache. We try not to do more unwinding than absolutely + necessary, for performance. */ + CORE_ADDR this_pc = get_frame_pc (get_current_frame ()); + int skip_count = gather_inline_frame_info (this_pc, stop_chain, + &skipped_syms, nullptr); + gdb_assert (find_inline_frame_state (thread) == NULL); inline_states.emplace_back (thread, skip_count, this_pc, std::move (skipped_syms)); @@ -460,3 +484,112 @@ frame_inlined_callees (const frame_info_ptr &this_frame) return inline_count; } + +/* The 'maint info inline-frames' command. Takes an optional address + expression. If an address is passed then list inline frames that start + at the given address. If no address is given then list the current + thread's current inline frame state. */ + +static void +maintenance_info_inline_frames (const char *arg, int from_tty) +{ + int skipped_frame_count; + std::vector *skipped_sym_vec; + CORE_ADDR addr; + + /* With no argument then the user wants to know about the current inline + frame information. This information is cached per-thread and can be + updated as the user steps between inline functions at the current + address. + + If there is an argument then parse it as an address, the user is + asking about inline functions that start at that address. */ + if (arg == nullptr) + { + if (inferior_ptid == null_ptid) + error (_("no inferior thread")); + + thread_info *thread = inferior_thread (); + + auto it = std::find_if (inline_states.begin (), inline_states.end (), + [thread] (const inline_state &istate) + { + return thread == istate.thread; + }); + + if (it == inline_states.end ()) + { + gdb_printf (_("No inline frame info for current thread.\n")); + return; + } + + skipped_frame_count = it->skipped_frames; + skipped_sym_vec = &it->skipped_symbols; + addr = it->saved_pc; + } + else + addr = parse_and_eval_address (arg); + + /* The cached inline frame information only tracks skipped frames. In + order to display the outer function (that contains the inline frames) + we always gather the complete information here, even if we are using + some details from the previous cached information. */ + bpstat stat; + std::vector skipped_syms; + const struct symbol *outer_symbol; + int skipped_frames = gather_inline_frame_info (addr, &stat, &skipped_syms, + &outer_symbol); + if (arg != nullptr) + { + skipped_frame_count = skipped_frames; + skipped_sym_vec = &skipped_syms; + } + + gdb_printf (_("program counter = %s\n"), core_addr_to_string_nz (addr)); + gdb_printf (_("skipped frames = %d\n"), skipped_frame_count); + + int i; + for (i = 0; i < skipped_syms.size (); ++i) + { + std::string tail; + + if (i < skipped_sym_vec->size () + && (*skipped_sym_vec)[i] != skipped_syms[i]) + tail = string_printf (_("\t(expected %s)"), + (*skipped_sym_vec)[i]->print_name ()); + + gdb_printf (_("%c %s%s\n"), (i == skipped_frame_count ? '>' : ' '), + skipped_syms[i]->print_name (), + tail.c_str ()); + } + + if (outer_symbol != nullptr) + gdb_printf (_("%c %s\n"), + (i == skipped_frame_count ? '>' : ' '), + outer_symbol->print_name ()); + else + gdb_printf (_(" Failed to find an outer function\n")); +} + + + +void _initialize_inline_frame (); +void +_initialize_inline_frame () +{ + add_cmd ("inline-frames", class_maintenance, maintenance_info_inline_frames, + _("\ +Display inline frame information for current thread.\n\ +\n\ +Usage:\n\ +\n\ + maintenance info inline-frames [ADDRESS]\n\ +\n\ +With no ADDRESS show all inline frames starting at the current program\n\ +counter address. When ADDRESS is given, list all inline frames starting\n\ +at ADDRESS.\n\ +\n\ +The last frame listed might not start at ADDRESS, this is the frame that\n\ +contains the other inline frames."), + &maintenanceinfolist); +} diff --git a/gdb/testsuite/gdb.base/maint-info-inline-frames-and-blocks.c b/gdb/testsuite/gdb.base/maint-info-inline-frames-and-blocks.c new file mode 100644 index 00000000000..35b20648cd5 --- /dev/null +++ b/gdb/testsuite/gdb.base/maint-info-inline-frames-and-blocks.c @@ -0,0 +1,57 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2024 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 . */ + +static void inline_func_a (void); +static void inline_func_b (void); +static void normal_func (void); + +volatile int global_var = 0; + +static void __attribute__((noinline)) +normal_func (void) +{ + /* Do some work. */ + ++global_var; + ++global_var; + + /* Now the inline function. */ + inline_func_a (); + + /* Do some work. */ + ++global_var; /* After inline function. */ + ++global_var; +} + +static inline void __attribute__((__always_inline__)) +inline_func_a (void) +{ + inline_func_b (); +} + +static inline void __attribute__((__always_inline__)) +inline_func_b (void) +{ + ++global_var; + ++global_var; +} + +int +main () +{ + normal_func (); + return 0; +} diff --git a/gdb/testsuite/gdb.base/maint-info-inline-frames-and-blocks.exp b/gdb/testsuite/gdb.base/maint-info-inline-frames-and-blocks.exp new file mode 100644 index 00000000000..f5c5fd2623e --- /dev/null +++ b/gdb/testsuite/gdb.base/maint-info-inline-frames-and-blocks.exp @@ -0,0 +1,147 @@ +# Copyright (C) 2024 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 . + +# Check the 'maint info inline-frames' command. + +standard_testfile + +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} \ + {debug nopie}]} { + return -1 +} + +if {![runto normal_func]} { + return 0 +} + +# Next forward until we find the call to inline_func_a(). The hope is +# that when we see the 'inline_func_a()' line this will be the start of +# the inlined function. This might not be the case on all +# architectures if the compiler needs to perform some preamble. +gdb_test_multiple "next" "next forward to inline_func_a" { + -re "^$decimal\\s+inline_func_a \\(\\);\r\n" { + # Consume the next prompt. + gdb_expect { + -re "^$gdb_prompt $" {} + } + pass $gdb_test_name + } + + -re "^$decimal\\s+\[^\r\n\]+After inline function\[^\r\n\]+\r\n" { + # We've gone too far! + fail $gdb_test_name + } + + -re "^$decimal\\s+\[^\r\n\]+\r\n" { + send_gdb "next\n" + exp_continue + } + + -re "^\[^\r\n\]+\r\n" { + exp_continue + } +} + +# View the inline frame information. This should display that we are +# at the start of inline_func_a() within normal_func(). +gdb_test "maint info inline-frames" \ + [multi_line \ + "^program counter = $hex" \ + "skipped frames = 2" \ + " inline_func_b" \ + " inline_func_a" \ + "> normal_func"] \ + "check inline-frames state when in normal_func" + +# Step, we should now enter the inlined function. +gdb_test "step" ".*" \ + "step to enter inline_func" + +# And the inline-frames information should update. +gdb_test "maint info inline-frames" \ + [multi_line \ + "^program counter = $hex" \ + "skipped frames = 1" \ + " inline_func_b" \ + "> inline_func_a" \ + " normal_func"] \ + "check inline-frames state when just entered inline_func_a" + +# Record the current program counter. +set pc [get_hexadecimal_valueof "\$pc" "UNKNOWN"] + +# Use the recorded $pc value to check inline frames. +gdb_test "maint info inline-frames $pc" \ + [multi_line \ + "^program counter = $hex" \ + "skipped frames = 2" \ + " inline_func_b" \ + " inline_func_a" \ + "> normal_func"] \ + "check inline-frames state at recorded \$pc while at the \$pc" + +# Step again, we should now enter inlined_func_b(). +gdb_test "step" ".*" \ + "step into inline_func_b" + +gdb_test "maint info inline-frames" \ + [multi_line \ + "^program counter = $hex" \ + "skipped frames = 0" \ + "> inline_func_b" \ + " inline_func_a" \ + " normal_func"] \ + "check inline-frames state when just entered inline_func_b" + +gdb_test "step" ".*" \ + "step into the body of inline_func_b" + +# Now we are no longer at the start of the inlined function we should +# no longer see normal_func() in the inline-frames information. +gdb_test "maint info inline-frames" \ + [multi_line \ + "^program counter = $hex" \ + "skipped frames = 0" \ + "> inline_func_b"] \ + "check inline-frames state when within inline_func_b" + +# Use the recorded $pc value to check inline frames. +gdb_test "maint info inline-frames $pc" \ + [multi_line \ + "^program counter = $hex" \ + "skipped frames = 2" \ + " inline_func_b" \ + " inline_func_a" \ + "> normal_func"] \ + "check inline-frames state at recorded \$pc" + +clean_restart $binfile + +# Use the recorded $pc value to check inline frames when the inferior +# is not executing. +gdb_test "maint info inline-frames $pc" \ + [multi_line \ + "^program counter = $hex" \ + "skipped frames = 2" \ + " inline_func_b" \ + " inline_func_a" \ + "> normal_func"] \ + "check inline-frames state at recorded \$pc before execution starts" + +# Trying to read the $pc from the current thread should fail if the +# inferior is not yet running. +gdb_test "maint info inline-frames" \ + "^no inferior thread" \ + "check inline-frames state of current thread before execution starts" From patchwork Mon Jul 22 14:42:09 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrew Burgess X-Patchwork-Id: 94320 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id AB84E3858416 for ; Mon, 22 Jul 2024 14:43:10 +0000 (GMT) X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org 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 BC30A385DDD6 for ; Mon, 22 Jul 2024 14:42:18 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org BC30A385DDD6 Authentication-Results: sourceware.org; dmarc=pass (p=none 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 BC30A385DDD6 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=1721659342; cv=none; b=EZtIXUM+tCNDiu8S3BoFglOogLfE1YGAL4ANE2OqU5YcGefFaCKK4GCr1HKMVNNaXpGu7715e1yXBOJzMkINIE9dLo8EuRTvWLa265itqSenimgXiVk8Vxq0UfUIn3iLfPeJwfX2+VrLc7kfQ53Ae1rbGL5zS1l45yNj6Cl083U= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1721659342; c=relaxed/simple; bh=efT0LohB0uH/um3X1A5KrpphADNJWM+ie1ijonq8XE0=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=MCQ0jZrjunPpilgdiIQL/L9pJdrAG4NUPzMRJjV5NJN4E9btZfLlzjto2YDLYIWAxaBvjqPhzOjssFOC9sDybEgDsa8ROqri8CSLlvHCwGfo44QGICaG1jfU99yQ5y7ikeeXslwIv22qBjzwB6ZWkvaZ69xtM69p4YmiuNNVvxQ= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1721659338; 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=ABwMgm7Rw5wP+jFp2Vpa2sdcqZTndqR4AsGXaZ5tvkw=; b=aPmyFCcQSs51Erb9nrTWqmZUH5onSozXqyYJRccQ5pbGsIo1hHYmtwkJCiBH1NiE3DUjIQ sLMK8jMBwTUVCKK+RqPkI7AQ7qdqIHu+q6KFG4ao04C6iIkg2GRd87iAOqRb1tbDxz3a+d hwxgf/Nw/N2+i9h9h0SO+GSMyEiyD9U= Received: from mail-wm1-f72.google.com (mail-wm1-f72.google.com [209.85.128.72]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-447-jZpEyuXYM0CKOTUjG5uzZQ-1; Mon, 22 Jul 2024 10:42:17 -0400 X-MC-Unique: jZpEyuXYM0CKOTUjG5uzZQ-1 Received: by mail-wm1-f72.google.com with SMTP id 5b1f17b1804b1-42668699453so45217845e9.3 for ; Mon, 22 Jul 2024 07:42:17 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1721659335; x=1722264135; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=Zlu/BIh0M3hOTB4SZW55d3ZZ9JOrRUbXyQL2Xog6Dxc=; b=NCM6o1gNDJVY6SYwFW+/WMM7Q4BKMCR2Rlx2L/XDkWgCTiuy+NWyt+3m5U9/Gm2HcW uwufA5oDQLZsqntJ+1nft3ibFAxPjMSyY6deyL0tBnd+dFR2kx+s2MTOhzXOyPjYtETX W0yryoWSXUCOBLJIZ8wVoKWDxo0ur7/YE/Sb2EWbg6dAhk9ozCz8HFfRJa8ojj6XnR06 /fAWaj2X8/+hzqg7oCOvIdPVYhLUcpmf9j1g7u+b/NIcGEc/X0gwZH0Y1OmHFNNupmzY sXAn536fdq2XtjEL3srdTehTm170PzOcv1LUtvs1v76yIR2BVGl+R56Tw3r2DOCmmRdp alFA== X-Gm-Message-State: AOJu0Yw+HWlEfQAGJMuihQOL+iZXwVv1rdFqyIXq3GkMiQkYJ6PNL5A+ dMoMbtqQrmJB8o+t9yeNA/I9zzdP//ERgLoQB3M5nSjDFXWYSLyF3WCysR6hxzq4CbkV/VwuH2N DWUiL7ej8D4SDT1D4WB3KlAcCyh+NZtGEmEy7Fyjgh6e1WzAFVIsr5qWG0GGfz39Y7T1fxWXPVN YrOqh4IlOJ24tnKflce6JIiZiq9bjPuAhdmDpk9XucbpA= X-Received: by 2002:a05:600c:1f07:b0:426:545b:ec00 with SMTP id 5b1f17b1804b1-427dc5282eemr57092365e9.19.1721659335199; Mon, 22 Jul 2024 07:42:15 -0700 (PDT) X-Google-Smtp-Source: AGHT+IFhVM3boH8mb3RJcH04XzR1qU5SeeQcUOstWgo4wXgc5yn2+fSAwCKgHSIwYCdDubzZAsaUTQ== X-Received: by 2002:a05:600c:1f07:b0:426:545b:ec00 with SMTP id 5b1f17b1804b1-427dc5282eemr57092145e9.19.1721659334673; Mon, 22 Jul 2024 07:42:14 -0700 (PDT) Received: from localhost ([31.111.84.186]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-427d7263687sm128315805e9.4.2024.07.22.07.42.14 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 22 Jul 2024 07:42:14 -0700 (PDT) From: Andrew Burgess To: gdb-patches@sourceware.org Cc: Andrew Burgess Subject: [PATCH 2/2] gdb: add 'maint info blocks' command Date: Mon, 22 Jul 2024 15:42:09 +0100 Message-Id: X-Mailer: git-send-email 2.25.4 In-Reply-To: References: MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-11.5 required=5.0 tests=BAYES_00, DKIM_INVALID, DKIM_SIGNED, GIT_PATCH_0, KAM_DMARC_NONE, KAM_DMARC_STATUS, KAM_SHORT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org 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~patchwork=sourceware.org@sourceware.org While reviewing a patch I wanted to understand which blocks existed at a given address. The 'maint print symbols' command does provide some of this information, but that command displays all blocks within a given symtab. If I want to know which blocks are at a given address I have to figure that out for myself based on the output of 'maint print symbols' ... and I'm too lazy for that! So this command lists just those blocks at a given address, along with information about the blocks type. This new command doesn't list the symbols within each block, for that my expectation is that you'd cross reference the output with that of 'maint print symbols'. The new command format is: maintenance info blocks maintenance info blocks ADDRESS This lists the blocks at ADDRESS, or at the current $pc if ADDRESS is not given. Blocks are listed starting at the global block, then the static block, and then the progressively narrower scoped blocks. For each block we list the internal block pointer (which allows easy cross referencing with 'maint print symbols'), the inferior address range, along with other useful information. Reviewed-By: Eli Zaretskii --- gdb/NEWS | 6 + gdb/block.c | 132 ++++++++++++++++++ gdb/doc/gdb.texinfo | 50 +++++++ .../maint-info-inline-frames-and-blocks.exp | 65 ++++++++- 4 files changed, 252 insertions(+), 1 deletion(-) diff --git a/gdb/NEWS b/gdb/NEWS index 1cbc05c5a18..1673671744d 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -20,6 +20,12 @@ maintenance info inline-frames [ADDRESS] current address, or for ADDRESS if specified. The output identifies inlined frames which start at the specified address. +maintenance info blocks [ADDRESS] + New command which displays information about all of the blocks at + ADDRESS, or at the current address if ADDRESS is not given. Blocks + are listed starting at the inner global block out to the most inner + block. + *** Changes in GDB 15 * The MPX commands "show/set mpx bound" have been deprecated, as Intel diff --git a/gdb/block.c b/gdb/block.c index 511689c9738..3cb6c28dc1e 100644 --- a/gdb/block.c +++ b/gdb/block.c @@ -25,6 +25,8 @@ #include "addrmap.h" #include "gdbtypes.h" #include "objfiles.h" +#include "cli/cli-cmds.h" +#include "inferior.h" /* This is used by struct block to store namespace-related info for C++ files, namely using declarations and the current namespace in @@ -834,3 +836,133 @@ make_blockranges (struct objfile *objfile, return blr; } +/* Implement 'maint info blocks' command. If passed an argument then + print a list of all blocks at the given address. With no arguments + then list all blocks at the current address of the current inferior. */ + +static void +maintenance_info_blocks (const char *arg, int from_tty) +{ + CORE_ADDR address; + + /* With no argument use the program counter of the current thread. If + there is an argument then use this as the address to examine. */ + if (arg == nullptr) + { + if (inferior_ptid == null_ptid) + error (_("no inferior thread")); + + struct regcache *regcache = get_thread_regcache (inferior_thread ()); + address = regcache_read_pc (regcache); + } + else + address = parse_and_eval_address (arg); + + /* Find the inner most block for ADDRESS. */ + const struct block *cur_block = block_for_pc (address); + if (cur_block == nullptr) + { + gdb_printf (_("No blocks at %s\n"), core_addr_to_string_nz (address)); + return; + } + + gdb_printf (_("Blocks at %s:\n"), core_addr_to_string_nz (address)); + + const struct objfile *toplevel_objfile = cur_block->objfile (); + if (toplevel_objfile != nullptr) + gdb_printf (_(" from objfile: [(objfile *) %s] %s\n"), + host_address_to_string (toplevel_objfile), + objfile_name (toplevel_objfile)); + + gdb_printf ("\n"); + + /* List the blocks backwards; global block (widest scope) first, down to + the smallest scoped block last. To do this we need to build the list + of blocks starting from the inner block, then print that list + backwards. */ + std::vector blocks; + while (cur_block != nullptr) + { + blocks.emplace_back (cur_block); + cur_block = cur_block->superblock (); + } + + for (auto it = blocks.rbegin (); it != blocks.rend (); ++it) + { + cur_block = *it; + + gdb_printf (_("[(block *) %s] %s..%s\n"), + host_address_to_string (cur_block), + core_addr_to_string_nz (cur_block->start ()), + core_addr_to_string_nz (cur_block->end ())); + gdb_printf (_(" entry pc: %s\n"), + core_addr_to_string_nz (cur_block->entry_pc ())); + if (cur_block->is_static_block ()) + gdb_printf (_(" is static block\n")); + if (cur_block->is_global_block ()) + gdb_printf (_(" is global block\n")); + if (cur_block->function () != nullptr) + { + if (cur_block->inlined_p ()) + gdb_printf (_(" inline function: %s\n"), + cur_block->function ()->print_name ()); + else + gdb_printf (_(" function: %s\n"), + cur_block->function ()->print_name ()); + } + else if (cur_block->inlined_p ()) + gdb_printf (_(" is inline block, not has no function symbol\n")); + if (cur_block->scope () != nullptr + && *cur_block->scope () != '\0') + gdb_printf (_(" scope: %s\n"), cur_block->scope ()); + int symbol_count = mdict_size (cur_block->multidict ()); + if (symbol_count > 0) + gdb_printf (_(" symbol count: %d\n"), symbol_count); + + /* Check out block::objfile() to see why it might be the case that a + nested block could have a different objfile than its containing + block. */ + if (cur_block->objfile () != toplevel_objfile) + { + if (cur_block->objfile () == nullptr) + gdb_printf (_(" from objfile: [(objfile *) %s]\n"), + host_address_to_string (cur_block->objfile ())); + else + gdb_printf (_(" from objfile: [(objfile *) %s] %s\n"), + host_address_to_string (cur_block->objfile ()), + objfile_name (cur_block->objfile ())); + } + + if (cur_block->is_contiguous ()) + gdb_printf (_(" is contiguous\n")); + else + { + gdb_printf (_(" address ranges:\n")); + for (const blockrange &rng : cur_block->ranges ()) + gdb_printf (_(" %s..%s\n"), + core_addr_to_string_nz (rng.start ()), + core_addr_to_string_nz (rng.end ())); + } + } +} + + + +void _initialize_block (); +void +_initialize_block () +{ + add_cmd ("blocks", class_maintenance, maintenance_info_blocks, + _("\ +Display block information for current thread.\n\ +\n\ +Usage:\n\ +\n\ + maintenance info blocks [ADDRESS]\n\ +\n\ +With no ADDRESS show all blocks at the current address, starting with the\n\ +global block and working down to the inner most block.\n\ +\n\ +When ADDRESS is given, list the blocks at ADDRESS."), + &maintenanceinfolist); +} diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index 0f710cf9289..aeeb9f94dea 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -20412,6 +20412,7 @@ Show whether messages will be printed when a @value{GDBN} command entered from the keyboard causes symbol information to be loaded. +@anchor{maint print symbols} @kindex maint print symbols @cindex symbol dump @kindex maint print psymbols @@ -41947,6 +41948,55 @@ main @end smallexample +@kindex maint info blocks +@item maint info blocks +@itemx maint info blocks @var{address} +Print information about all blocks at @var{address}, or at the current +@code{$pc} if @var{address} is not given. + +For information about what blocks are in @value{GDBN} see @ref{Blocks +In Python}. + +Blocks are listed starting from the global block, then the static +block, and then proceeding through progressively narrower scopes. + +Here is an example of the command's output: +@smallexample +(@value{GDBP}) maintenance info blocks +Blocks at 0x401137: + from objfile: [(objfile *) 0x50507d0] /tmp/inline_func_demo + +[(block *) 0x504da90] 0x401106..0x40119a + entry pc: 0x401106 + is global block + symbol count: 2 + is contiguous +[(block *) 0x504d9f0] 0x401106..0x40119a + entry pc: 0x401106 + is static block + symbol count: 1 + is contiguous +[(block *) 0x504d9a0] 0x401106..0x40119a + entry pc: 0x401106 + function: main + is contiguous +[(block *) 0x504d900] 0x401137..0x401166 + entry pc: 0x401137 + inline function: foo + symbol count: 1 + is contiguous +[(block *) 0x504d860] 0x401137..0x401165 + entry pc: 0x401137 + inline function: bar + symbol count: 1 + is contiguous +@end smallexample + +The @command{maint info blocks} command lists the symbol count for +each block but doesn't print the symbols themselves. The symbol names +can be found using @command{maint print symbols} (@pxref{maint print +symbols}). + @kindex maint print registers @kindex maint print raw-registers @kindex maint print cooked-registers diff --git a/gdb/testsuite/gdb.base/maint-info-inline-frames-and-blocks.exp b/gdb/testsuite/gdb.base/maint-info-inline-frames-and-blocks.exp index f5c5fd2623e..4be8167895a 100644 --- a/gdb/testsuite/gdb.base/maint-info-inline-frames-and-blocks.exp +++ b/gdb/testsuite/gdb.base/maint-info-inline-frames-and-blocks.exp @@ -13,7 +13,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -# Check the 'maint info inline-frames' command. +# Check the 'maint info inline-frames' and 'maint info blocks' +# commands. standard_testfile @@ -26,6 +27,45 @@ if {![runto normal_func]} { return 0 } +# Make a pattern to match 'maint info blocks' output. ARGS is the +# list of function names we expect to see. If the function name +# starts with 'inline_func' then we expect to see an inline block, +# otherwise blocks are not expected to be inline. +proc make_blocks_result { args } { + set result \ + [list \ + "Blocks at $::hex:" \ + " from objfile: \\\[\\(objfile \\*\\) $::hex\\\] [string_to_regexp $::binfile]" \ + ""\ + "\\\[\\(block \\*\\) $::hex\\\] $::hex\\.\\.$::hex" \ + " entry pc: $::hex" \ + " is global block" \ + ".*" \ + "\\\[\\(block \\*\\) $::hex\\\] $::hex\\.\\.$::hex" \ + " entry pc: $::hex" \ + " is static block" \ + ".*" ] + + foreach func $args { + lappend result \ + "\\\[\\(block \\*\\) $::hex\\\] $::hex\\.\\.$::hex" \ + " entry pc: $::hex" + + if { [string range $func 0 10] eq "inline_func" } { + lappend result" inline function: $func" + } else { + lappend result" function: $func" + } + + lappend result ".*" + } + + return [multi_line {*}$result] +} + +gdb_test "maint info blocks" [make_blocks_result normal_func] \ + "maint info blocks in normal_func only" + # Next forward until we find the call to inline_func_a(). The hope is # that when we see the 'inline_func_a()' line this will be the start of # the inlined function. This might not be the case on all @@ -54,6 +94,10 @@ gdb_test_multiple "next" "next forward to inline_func_a" { } } +gdb_test "maint info blocks" [make_blocks_result normal_func \ + inline_func_a inline_func_b] \ + "maint info blocks when all blocks visible" + # View the inline frame information. This should display that we are # at the start of inline_func_a() within normal_func(). gdb_test "maint info inline-frames" \ @@ -105,6 +149,10 @@ gdb_test "maint info inline-frames" \ " normal_func"] \ "check inline-frames state when just entered inline_func_b" +gdb_test "maint info blocks" [make_blocks_result normal_func \ + inline_func_a inline_func_b] \ + "maint info blocks when all blocks still visible" + gdb_test "step" ".*" \ "step into the body of inline_func_b" @@ -117,6 +165,10 @@ gdb_test "maint info inline-frames" \ "> inline_func_b"] \ "check inline-frames state when within inline_func_b" +gdb_test "maint info blocks" [make_blocks_result normal_func \ + inline_func_a inline_func_b] \ + "maint info blocks within inline function, all blocks still visible" + # Use the recorded $pc value to check inline frames. gdb_test "maint info inline-frames $pc" \ [multi_line \ @@ -127,6 +179,10 @@ gdb_test "maint info inline-frames $pc" \ "> normal_func"] \ "check inline-frames state at recorded \$pc" +gdb_test "maint info blocks" [make_blocks_result normal_func \ + inline_func_a inline_func_b] \ + "maint info blocks using stored \$pc, inferior still running" + clean_restart $binfile # Use the recorded $pc value to check inline frames when the inferior @@ -140,8 +196,15 @@ gdb_test "maint info inline-frames $pc" \ "> normal_func"] \ "check inline-frames state at recorded \$pc before execution starts" +gdb_test "maint info blocks $pc" [make_blocks_result normal_func \ + inline_func_a inline_func_b] \ + "maint info blocks using stored \$pc, inferior not running" + # Trying to read the $pc from the current thread should fail if the # inferior is not yet running. gdb_test "maint info inline-frames" \ "^no inferior thread" \ "check inline-frames state of current thread before execution starts" + +gdb_test "maint info blocks" "^no inferior thread" \ + "maint info blocks with no \$pc and inferior not running"