From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id uB3NB1PEfGhHdSoAWB0awg (envelope-from ) for ; Sun, 20 Jul 2025 06:26:27 -0400 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=hFbyRYRY; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id 1679B1E11C; Sun, 20 Jul 2025 06:26:27 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-6.8 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_SBL_CSS,RCVD_IN_VALIDITY_CERTIFIED, RCVD_IN_VALIDITY_RPBL,RCVD_IN_VALIDITY_SAFE,WEIRD_PORT autolearn=ham autolearn_force=no version=4.0.1 Received: from server2.sourceware.org (server2.sourceware.org [8.43.85.97]) (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 E8CC81E0C2 for ; Sun, 20 Jul 2025 06:26:17 -0400 (EDT) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 93B883857C58 for ; Sun, 20 Jul 2025 10:26:17 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 93B883857C58 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=hFbyRYRY 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 E13F13858405 for ; Sun, 20 Jul 2025 10:21:12 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org E13F13858405 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 E13F13858405 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=1753006873; cv=none; b=QW3p3kMVTewuHhAxbZ0J9JVFg4DC2YOmp7kLcLqriRSUpyvzO4xu+S+O2TwS7kVnja0WMr2Eg6B+4XSpkJbzw+r7CmuaPnJ6eOMPib+tPK+hIcIt+CHq5VlQ3VCgeuR9jUkNrI7vf5dpGjDgaDQMQAu1KHL60TSNx0MhizzxSYI= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1753006873; c=relaxed/simple; bh=QJxd5xcTnon1y5qKrMJBYMP+fyBNQYONtpFuPs2gNdc=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=QmOqL0xcACXODeRbpEcH2G6CUJytD1y/wXiT3LKqY2mJ9VCOTTjqRoSF1snQYrbsSsfcctbNUFjR2d0X/mpMsZtTX+qyTsN50vpA8+PVXzQm1KPIAdyRhpZclTY/GvdXKUWIoGRvs4R9j4K/MjDSomdsXxYtRPHka66awbTU8BU= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org E13F13858405 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1753006872; 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=7HRJq4sWO7SyyKLbY+i2J8tk2uSn1MpqtTxN8aQOCvU=; b=hFbyRYRYTh2cwPhG62aS6kVlDa1GFcea1T0mKW+3AF4Y7XMjPa+pZcJKB8n88jnIUmJtM7 9pVVYtYPXsnZWZdCf5jXw1jHdNb7QJiyn429oRwuGofMFs7PPwXKokThyzcggMaDh8U23R 4oo1GB3Wzm7mH5+K2tB7+8mT+E9lHF8= Received: from mail-wr1-f70.google.com (mail-wr1-f70.google.com [209.85.221.70]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-677-MbwjS0PcO5-2WUQmMa_Q7g-1; Sun, 20 Jul 2025 06:21:11 -0400 X-MC-Unique: MbwjS0PcO5-2WUQmMa_Q7g-1 X-Mimecast-MFC-AGG-ID: MbwjS0PcO5-2WUQmMa_Q7g_1753006870 Received: by mail-wr1-f70.google.com with SMTP id ffacd0b85a97d-3a4f6ba526eso2352471f8f.1 for ; Sun, 20 Jul 2025 03:21:10 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1753006869; x=1753611669; 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=7HRJq4sWO7SyyKLbY+i2J8tk2uSn1MpqtTxN8aQOCvU=; b=YG8jKRS46wktge53arxSdJcN3u/ipbJ4jRfFfzqnRZOtvmLknFKuUTtUglG1EDqsWv 5j56NjPLekq4gT9wZ8RYVoe0e+i2X1/z6KagyG3yIatMglHFHZrHT5T1dxqAVuhh60Nj /uSTDiT9ek+ayh7eXKHT5wRhXgimKSc0Dl528i4yj8BnAtVu1wr1RbBS+ZxqtXuAEgeU ECKuPBgHQbg4i2WM5EC9QDjnsII0MH2uEDuu1nXe4cda5okKU4oaVc8fbwdpIliLglVJ FleJCiuzWQZEJ0LXqAe6JDR+/ZjnEwDZczgc5FyPaY5FjY+OtMI0dmh98vsr4lI3SELl +3mg== X-Gm-Message-State: AOJu0YylquKrazLm0GEyvL1KII8vhjORjS+vYW5X9FRZWjy1I3fUcJZo utcBaoqnANc8/TixnYQf2aSeHPKpF0hSX5x22YA3QCL/+Mc08o8uvw02UKiovaJOzLA0fkNYGh6 vd3pAbI4tiY0o6/ie1UDewSkT6Hg6004FeAH68a4IGSUkmuJc/dSQWQ5yNYCTtBeEIY72t2Foit 9qMwLQXTNZnNRIU+kA0fRe9WKLninWq0iMWHXhMXlSIqEuLz0= X-Gm-Gg: ASbGncuk4iMQavL1gD24FmMrvES2RphvY5LGBE4jxiulZXqr1pbKiKzhELCeHbmqBtO wAs1nm2dwEeyvdknBZ3qyN1flTmL6wM8kq/AfZxjS3Vc7jzrHmlTvbwnnPFA+uUcXwHb1iBBSvb 85skTxs9Oo+X1ifWMUba97nxvYlVs6jvSY/ExsEvTYp5fCnlZaH/hPqKigWPK35PFr7v4cjbp5N bMUzzKNr3jJ7vkmhT1i+tJ+ZV3bSriZ1xqIPUqGPGwJ482Vt88yiPlpJtDhnm8Q3csEE0yKmoeR AH/yp7YTt1ChuzK+2LdxrFUSo9J+M/bfAa3NnvPfaJgZhPnCa4Qd7m5zmsTsKg== X-Received: by 2002:a05:6000:41cb:b0:3b6:46d:fb70 with SMTP id ffacd0b85a97d-3b60e4d2a8bmr12131758f8f.25.1753006868455; Sun, 20 Jul 2025 03:21:08 -0700 (PDT) X-Google-Smtp-Source: AGHT+IECUwHVdpc1WVfmI5zT9Ly0qbkjUYs2sEm9q+IJeo7zSqXSCburZHHta2ynqVD5NHg+2BUqxw== X-Received: by 2002:a05:6000:41cb:b0:3b6:46d:fb70 with SMTP id ffacd0b85a97d-3b60e4d2a8bmr12131722f8f.25.1753006867219; Sun, 20 Jul 2025 03:21:07 -0700 (PDT) Received: from localhost (92.40.185.56.threembb.co.uk. [92.40.185.56]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-3b61ca5d266sm7112760f8f.91.2025.07.20.03.21.06 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 20 Jul 2025 03:21:06 -0700 (PDT) From: Andrew Burgess To: gdb-patches@sourceware.org Cc: Andrew Burgess , Bernd Edlinger Subject: [PATCH 7/7] gdb: fix-up truncated inline function block ranges Date: Sun, 20 Jul 2025 11:20:46 +0100 Message-ID: X-Mailer: git-send-email 2.47.1 In-Reply-To: References: MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: 4i6yORSoTXzEEwVFqMdh6bj6AQN8ccpvhjw5IehsvCk_1753006870 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: 8bit content-type: text/plain; charset="US-ASCII"; x-default=true X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gdb-patches-bounces~public-inbox=simark.ca@sourceware.org This commit aims to improve GDB's handling of inline functions. There are two mechanisms which can tell GDB, or the user of GDB, that the inferior is within an inline function, these are the block range associated with an inline instance of a function, and also the line table, which associates addresses with source lines in the program. Currently, gcc truncates the address range for, at least some, inline function blocks, such that a given address is considered outside the inline function. However, the line table maps that same address to a line within the inline function. A consequence of this, is that, when using 'next' to move the inferior forward, GDB will often stop the inferior believing that the inferior has left an inline function, and indeed, GDB will claim that the inferior is in the outer, non-inline function, but GDB will then display a source line from the inline function as the current location. An example of this problem can be seen with the test gdb.cp/step-and-next-inline.exp. Using the step-and-next-inline-no-header binary that is built as part of the test: (gdb) file ./gdb/testsuite/outputs/gdb.cp/step-and-next-inline/step-and-next-inline-no-header Reading symbols from ./gdb/testsuite/outputs/gdb.cp/step-and-next-inline/step-and-next-inline-no-header... (gdb) break get_alias_set Breakpoint 1 at 0x401160: file /tmp/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.cp/step-and-next-inline.cc, line 51. (gdb) run Starting program: /tmp/build/gdb/testsuite/outputs/gdb.cp/step-and-next-inline/step-and-next-inline-no-header Breakpoint 1, get_alias_set (t=t@entry=0x404038 ) at /tmp/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.cp/step-and-next-inline.cc:51 51 if (t != NULL (gdb) next 52 && TREE_TYPE (t).z != 1 (gdb) next 43 return x; <------------- Problem line. (gdb) bt #0 get_alias_set (t=t@entry=0x404038 ) at /tmp/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.cp/step-and-next-inline.cc:43 #1 0x000000000040105e in main () at /tmp/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.cp/step-and-next-inline.cc:64 (gdb) I've labelled the issue as 'Problem line'. After the second 'next' GDB stopped thinking it was in get_alias_set, but printed a line from the inline function behind the TREE_TYPE macro. The 'Problem line' should have been line 53, not line 43. The $pc at which GDB stopped is 0x40116f. If we then use 'objdump --dwarf=decodedline' to view the line table for the executable, this is what we see: File name Line number Starting address View Stmt ... step-and-next-inline.cc 38 0x401165 x step-and-next-inline.cc 40 0x401165 1 x step-and-next-inline.cc 40 0x401165 2 step-and-next-inline.cc 40 0x401167 step-and-next-inline.cc 42 0x40116f x step-and-next-inline.cc 43 0x40116f 1 x step-and-next-inline.cc 43 0x40116f 2 step-and-next-inline.cc 52 0x40116f 3 step-and-next-inline.cc 52 0x401172 step-and-next-inline.cc 38 0x401177 x step-and-next-inline.cc 40 0x401177 1 x step-and-next-inline.cc 40 0x401177 2 ... NOTE: I use objdump to view the line table, not 'maintenance info line-table' as GDB drops some line table entries that it sees as irrelevant. Using objdump give a complete view of the line table. We can see that address 0x40116f is associated with three line numbers, 42, and 43 are both within the inline function, and 52 is the line from which the inline function was called. Notice too that 52 is a non-statement line. If we now look at the block structure for the previous $pc value 0x40116e (i.e. $pc - 1), then we see this: (gdb) maintenance info blocks 0x40116e ... [(block *) 0x3c62900] 0x401040..0x4011a6 entry pc: 0x401160 function: get_alias_set(tree*) symbol count: 1 address ranges: 0x401160..0x4011a6 0x401040..0x401046 [(block *) 0x3c61260] 0x401041..0x40116f entry pc: 0x401165 inline function: tree_check(tree*, int) symbol count: 4 address ranges: 0x401165..0x40116f 0x401041..0x401046 (gdb) Here we see 'tree_check', the inline function that backs the TREE_TYPE macro, this is the inline function we have just stepped out of. This makes sense as the end-address for the tree_check block is 0x40116f, and as the block's end address is not inclusive, that means that 0x40116f is the first address outside the block, which, is the current $pc value. And so, we can see what's going on. When the 'next' starts GDB is in get_alias_set, GDB steps forward, entering tree_check. GDB then uses the extent of the block to figure out where the inline function ends, and steps forward to that address (0x40116f). At this point, GDB looks up the current line in the line table (43), and reports a stop at this line. In this commit, the fix I propose is to look for the line table pattern seen above, a sequence of line table entries, that end with a non-statement entry for the calling line of an inline function, located at the exact end address of an inline function block. When such a pattern is found, then we can extend the inline function's address range to the next line table address, so long as doing so does not extend the inline function beyond the extent of the containing, non-inline, function. In the above example, the block for the tree_check function would be extended to end at 0x401172. With this fix in place, and with the same test binary, GDB now behaves like this: (gdb) break get_alias_set Breakpoint 1 at 0x401160: file /tmp/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.cp/step-and-next-inline.cc, line 51. (gdb) run Starting program: /tmp/build/gdb/testsuite/outputs/gdb.cp/step-and-next-inline/step-and-next-inline-no-header Breakpoint 1, get_alias_set (t=t@entry=0x404038 ) at /tmp/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.cp/step-and-next-inline.cc:51 51 if (t != NULL (gdb) next 52 && TREE_TYPE (t).z != 1 (gdb) next 53 && TREE_TYPE (t).z != 2 (gdb) The block for the inline function has been updated, like this: (gdb) maintenance info blocks 0x40116e ... [(block *) 0x4965530] 0x401040..0x4011a6 entry pc: 0x401160 function: get_alias_set(tree*) symbol count: 1 address ranges: 0x401160..0x4011a6 0x401040..0x401046 [(block *) 0x49640b0] 0x401040..0x401172 entry pc: 0x401165 inline function: tree_check(tree*, int) symbol count: 4 address ranges: 0x401165..0x401172 0x401040..0x401041 0x401041..0x401046 This is my alternative to the patch presented here: https://inbox.sourceware.org/gdb-patches/AS1PR01MB946510286FBF2497A6F03E83E4922@AS1PR01MB9465.eurprd01.prod.exchangelabs.com/ This original patch series from Bernd Edlinger contains a number of different fixes, some have already been split out and merged into GDB, but the core idea for how to improve inline function handling by extending the inline block range is the same, however, the mechanism Bernd uses is significantly different. In the above series, the approach taken is to mark the line table at the end address of an inline function, and a few addresses beyond that (see the is-weak flag in the above series). Then, when looking up the block for a given address, if the address is within this marked region, then we actually return the previous (inline function) block. I believe that the above is a fair high-level summary of how the above patch solves the inline function range problem. Any differences are down to my misunderstanding the above patch, for which I apologise. My problem with the above patch is that it breaks what I think should be an invariant of GDB, that when looking up a block for a given address, the block returned must contain the address within its ranges. I feel that, if we start to break this invariant, then we risk introducing bugs within, e.g. the stepping control code. In contrast, my approach solves this problem during the DWARF parsing, where problem cases are identified, and the DWARF "fixed" by extending the block ranges. After this, no additional changes are needed in GDB, the address to block mapping can work as normal, and the stepping logic can continue to work just as it always has. The test changes for gdb.cp/step-and-next-inline.exp have been taken from Bernd's original patch series, and I've added a Co-Author tag for Bernd to reflect this, as well as for the inspiration that I took from his original series when creating this alternative proposal. If/when this patch is merged, I plan to follow up with some cleanup to the test case gdb.cp/step-and-next-inline.exp. I think this test should really be moved to gdb.opt/, it's really testing optimisation debug, not C++ features, but also the structure of the test file is a bit of a mess. I think with some restructuring we could make the test more readable, and also, maybe, test some additional compiler flags (e.g. more optimisation levels). I've not done the refactoring in this patch in order to make it clearer what new tests I've added, and also, I want to leave the test similar to what's in Bernd's original series, to make comparison easier. The gdb.cp/step-and-next-inline.exp test was originally added by me back in 2019, so the problems with it are of my own making. For testing I've obviously run the entire test suite, but of particular interest are these tests: gdb.cp/step-and-next-inline.exp gdb.dwarf2/dw2-inline-bt.exp gdb.opt/empty-inline-cxx.exp gdb.opt/empty-inline.exp gdb.opt/inline-bt.exp I've run these tests with a range of different gcc versions: 9.5.0, 10.5.0, 11.5.0, 12.2.0, 13.3.0, 14.2.0, 15.1.0. These tests all relate to optimised debug of inline functions, and all passed with all compiler versions listed here. Co-Authored-By: Bernd Edlinger --- gdb/dwarf2/read.c | 80 +++ gdb/testsuite/gdb.cp/step-and-next-inline.exp | 38 +- .../gdb.dwarf2/dw2-extend-inline-block.c | 78 +++ .../gdb.dwarf2/dw2-extend-inline-block.exp | 574 ++++++++++++++++++ 4 files changed, 763 insertions(+), 7 deletions(-) create mode 100644 gdb/testsuite/gdb.dwarf2/dw2-extend-inline-block.c create mode 100644 gdb/testsuite/gdb.dwarf2/dw2-extend-inline-block.exp diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c index 992cf008da0..89e3efe5013 100644 --- a/gdb/dwarf2/read.c +++ b/gdb/dwarf2/read.c @@ -16237,6 +16237,79 @@ dwarf_finish_line (struct gdbarch *gdbarch, struct subfile *subfile, dwarf_record_line_1 (gdbarch, subfile, 0, address, LEF_IS_STMT, cu); } +/* Look for an inline block that finishes at ORIGINAL_ADDRESS. If a block + is found, then search up the block hierarchy looking for a suitable + inline block to extend, a suitable block will be called from LINE. If a + block is found then update its end address to EXTENDED_ADDRESS. */ + +static void +dwarf_find_and_extend_inline_block_range (dwarf2_cu *cu, + unrelocated_addr original_address, + unrelocated_addr extended_address, + unsigned int line) +{ + /* Is there an inline block that ends at ORIGINAL_ADDRESS? */ + auto it = cu->inline_block_ends.find (original_address); + if (it == cu->inline_block_ends.end ()) + return; + + /* Walk back up the block structure until we find the first inline block + that occurs after a non-inline block. This is our candidate for + extending. */ + struct block *block = nullptr; + for (const struct block *b = it->second; + b != nullptr; + b = b->superblock ()) + { + if (b->function () != nullptr && b->inlined_p ()) + { + if (b->superblock () != nullptr + && b->superblock ()->function () != nullptr + && !b->superblock ()->inlined_p ()) + { + block = const_cast (b); + break; + } + } + } + + /* If we didn't find a block, of the line table doesn't indicate that the + block should be extended, then we're done. Maybe we should try harder + to look for the block that matches LINE, but this would require us to + possibly extended more blocks, adding more complexity. Currently, + this works enough for simple cases, we can possibly improve the logic + here later on. */ + if (block == nullptr || block->function ()->line () != line) + return; + + /* Sanity check. We should have an inline block, which should have a + valid super block. */ + gdb_assert (block->inlined_p ()); + gdb_assert (block->superblock () != nullptr); + + CORE_ADDR extended_end = cu->per_objfile->relocate (extended_address); + + /* The proposed new end of BLOCK is outside of the ranges of BLOCK's + superblock. If we tried to extend BLOCK then this would create an + invalid block structure; BLOCK would no longer be fully nested within + its superblock. Don't do that. */ + if (extended_end > block->superblock ()->end ()) + return; + + CORE_ADDR original_end = cu->per_objfile->relocate (original_address); + + /* Now find the part of BLOCK that ends at ORIGINAL_END, and extend it + out to EXTENDED_END. */ + for (blockrange &br : block->ranges ()) + { + if (br.end () == original_end) + br.set_end (extended_end); + } + + if (block->end () == original_end) + block->set_end (extended_end); +} + void lnp_state_machine::record_line (bool end_sequence) { @@ -16255,6 +16328,13 @@ lnp_state_machine::record_line (bool end_sequence) (end_sequence ? "\t(end sequence)" : "")); } + if (m_address != m_last_address + && m_stmt_at_address + && m_cu->producer_is_gcc () + && (m_flags & LEF_IS_STMT) == 0) + dwarf_find_and_extend_inline_block_range (m_cu, m_last_address, + m_address, m_line); + file_entry *fe = current_file (); if (fe == NULL) diff --git a/gdb/testsuite/gdb.cp/step-and-next-inline.exp b/gdb/testsuite/gdb.cp/step-and-next-inline.exp index effff2721fa..4ed82deda32 100644 --- a/gdb/testsuite/gdb.cp/step-and-next-inline.exp +++ b/gdb/testsuite/gdb.cp/step-and-next-inline.exp @@ -24,13 +24,6 @@ if {[test_compiler_info gcc*] && ![supports_statement_frontiers] } { proc do_test { use_header } { global srcfile testfile - if { $use_header } { - # This test will not pass due to poor debug information - # generated by GCC (at least up to 10.x). See - # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94474 - return - } - set options {c++ debug nowarnings optimize=-O2} if { [supports_statement_frontiers] } { lappend options additional_flags=-gstatement-frontiers @@ -198,6 +191,8 @@ proc do_test { use_header } { gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \ "in inline 1 pass 2" gdb_test "step" ".*return x.*" "step 3" + gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \ + "return from inline 1 pass 2" gdb_test "step" ".*TREE_TYPE.*" "step 4" gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \ "not in inline 2 pass 2" @@ -205,6 +200,8 @@ proc do_test { use_header } { gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \ "in inline 2 pass 2" gdb_test "step" ".*return x.*" "step 6" + gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \ + "return from inline 2 pass 2" gdb_test "step" ".*TREE_TYPE.*" "step 7" gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \ "not in inline 3 pass 2" @@ -212,6 +209,8 @@ proc do_test { use_header } { gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \ "in inline 3 pass 2" gdb_test "step" "return x.*" "step 9" + gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \ + "return from inline 3 pass 2" gdb_test "step" "return 0.*" "step 10" gdb_test "bt" \ "\\s*\\#0\\s+(main|get_alias_set)\[^\r\]*${srcfile}:.*" \ @@ -255,6 +254,31 @@ proc do_test { use_header } { gdb_test "bt" "#0\\s+\[^\r\n\]*tree_check\[^\r\n\]*${hdrfile}:.*" \ "abort from inline 1 pass 3" } + + clean_restart ${executable} + + if ![runto_main] { + return + } + + gdb_test "bt" "\\s*\\#0\\s+main.*" "in main pass 4" + gdb_test "skip tree_check" ".*" "skip tree_check pass 4" + gdb_test "step" ".*" "step into get_alias_set pass 4" + gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \ + "in get_alias_set pass 4" + gdb_test "step" ".*TREE_TYPE.*" "step 1 pass 4" + gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \ + "not in inline 1 pass 4" + gdb_test "step" ".*TREE_TYPE.*" "step 2 pass 4" + gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \ + "not in inline 2 pass 4" + gdb_test "step" ".*TREE_TYPE.*" "step 3 pass 4" + gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \ + "not in inline 3 pass 4" + gdb_test "step" "return 0.*" "step 4 pass 4" + gdb_test "bt" \ + "\\s*\\#0\\s+(main|get_alias_set)\[^\r\]*${srcfile}:.*" \ + "not in inline 4 pass 4" } } diff --git a/gdb/testsuite/gdb.dwarf2/dw2-extend-inline-block.c b/gdb/testsuite/gdb.dwarf2/dw2-extend-inline-block.c new file mode 100644 index 00000000000..d6becf5d66b --- /dev/null +++ b/gdb/testsuite/gdb.dwarf2/dw2-extend-inline-block.c @@ -0,0 +1,78 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2025 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +volatile int global_var = 0; + +/* The follow code exists only to be referenced from the generated line + table. */ +#if 0 +static inline void +foo (void) +{ + /* foo:1 */ + /* foo:2 */ + /* foo:3 */ +} + +int +main (void) +{ /* main decl line */ + /* main:1 */ + /* main:2 */ + /* main:3 */ foo (); /* foo call line */ + /* main:4 */ + /* main:5 */ + /* main:6 */ +} +#endif + + +int +main (void) +{ + asm ("main_label: .globl main_label"); + ++global_var; + + asm ("main_0: .globl main_0"); + ++global_var; + + asm ("main_1: .globl main_1"); + ++global_var; + + asm ("main_2: .globl main_2"); + ++global_var; + + asm ("main_3: .globl main_3"); + ++global_var; + + asm ("main_4: .globl main_4"); + ++global_var; + + asm ("main_5: .globl main_5"); + ++global_var; + + asm ("main_6: .globl main_6"); + ++global_var; + + asm ("main_7: .globl main_7"); + ++global_var; + + asm ("main_8: .globl main_8"); + ++global_var; + + return 0; +} diff --git a/gdb/testsuite/gdb.dwarf2/dw2-extend-inline-block.exp b/gdb/testsuite/gdb.dwarf2/dw2-extend-inline-block.exp new file mode 100644 index 00000000000..963410ce057 --- /dev/null +++ b/gdb/testsuite/gdb.dwarf2/dw2-extend-inline-block.exp @@ -0,0 +1,574 @@ +# Copyright 2025 Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# When compiling optimised code, GCC will sometimes truncate the address +# range of an inline function, usually by a single instruction. +# +# It is possible to detect when this has happened by looking at the line +# table, GCC will create two non-statement line table entries associated +# with the call-line of the inline function, but the end address of the +# inline function will be set to be the address of the first of these line +# table entries. +# +# The problem here is that block end addresses are not inclusive, which +# means the block ends before either of these line table entries. +# +# What we find is that we get a better debug experience if we extend the +# inline function to actually end at the second line table entry, that is +# the first line table entry becomes part of the inline function, while the +# second entry remains outside the inline function. +# +# This test tries to create this situation using the DWARF assembler, and +# then checks that GDB correctly extends the inline function to include the +# first line table entry. + +load_lib dwarf.exp + +require dwarf2_support + +standard_testfile .c + +# Lines numbers we reference in the generated DWARF. +set main_decl_line [gdb_get_line_number "main decl line"] +set main_line_1 [gdb_get_line_number "main:1"] +set main_line_4 [gdb_get_line_number "main:4"] +set foo_call_line [gdb_get_line_number "foo call line"] +set foo_line_1 [gdb_get_line_number "foo:1"] + +get_func_info main + +# Create DWARF for the test. In this case, inline function 'foo' is created +# with a contiguous address range that needs extending. + +proc build_dwarf_for_contiguous_block { asm_file } { + Dwarf::assemble $asm_file { + declare_labels lines_table inline_func + + cu { } { + compile_unit { + {producer "GNU C 14.1.0"} + {language @DW_LANG_C} + {name $::srcfile} + {comp_dir /tmp} + {low_pc 0 addr} + {DW_AT_stmt_list $lines_table DW_FORM_sec_offset} + } { + inline_func: subprogram { + {name foo} + {inline @DW_INL_declared_inlined} + } + subprogram { + {name main} + {decl_file 1 data1} + {decl_line $::main_decl_line data1} + {decl_column 1 data1} + {low_pc $::main_start addr} + {high_pc $::main_len data4} + {external 1 flag} + } { + inlined_subroutine { + {abstract_origin %$inline_func} + {call_file 1 data1} + {call_line $::foo_call_line data1} + {low_pc main_1 addr} + {high_pc main_3 addr} + } + } + } + } + + lines {version 2 default_is_stmt 1} lines_table { + include_dir "$::srcdir/$::subdir" + file_name "$::srcfile" 1 + + program { + DW_LNE_set_address main + line $::main_line_1 + DW_LNS_copy + + DW_LNE_set_address main_0 + DW_LNS_advance_line 1 + DW_LNS_copy + + DW_LNE_set_address main_1 + line $::foo_line_1 + DW_LNS_copy + + DW_LNE_set_address main_2 + DW_LNS_advance_line 1 + DW_LNS_copy + + DW_LNE_set_address main_3 + DW_LNS_advance_line 1 + DW_LNS_copy + + DW_LNE_set_address main_3 + line $::foo_call_line + DW_LNS_negate_stmt + DW_LNS_copy + + DW_LNE_set_address main_4 + DW_LNS_copy + + DW_LNE_set_address main_5 + DW_LNS_advance_line 1 + DW_LNS_negate_stmt + DW_LNS_copy + + DW_LNE_set_address main_6 + DW_LNS_advance_line 1 + DW_LNS_copy + + DW_LNE_set_address main_7 + DW_LNS_advance_line 1 + DW_LNS_copy + + DW_LNE_set_address main_8 + DW_LNS_advance_line 1 + DW_LNS_copy + + DW_LNE_set_address "$::main_start + $::main_len" + DW_LNE_end_sequence + } + } + } +} + +# Assuming GDB is stopped at the entry $pc for 'foo', use 'maint info +# blocks' to check the block for 'foo' is correct. This function checks +# 'foo' created by 'build_dwarf_for_contiguous_block'. + +proc check_contiguous_block {} { + set foo_start [get_hexadecimal_valueof "&main_1" "*UNKNOWN*" \ + "get address of foo start"] + set foo_end [get_hexadecimal_valueof "&main_4" "*UNKNOWN*" \ + "get address of foo end"] + + gdb_test "maintenance info blocks" \ + [multi_line \ + "\\\[\\(block \\*\\) $::hex\\\] $foo_start\\.\\.$foo_end" \ + " entry pc: $foo_start" \ + " inline function: foo" \ + " symbol count: $::decimal" \ + " is contiguous"] \ + "block for foo has expected content" +} + +# Create DWARF for the test. In this case, inline function 'foo' is created +# with two ranges, and it is the first range that needs extending. + +proc build_dwarf_for_first_block_range { asm_file dwarf_version } { + Dwarf::assemble $asm_file { + upvar dwarf_version dwarf_version + declare_labels lines_table inline_func ranges_label + + cu { version $dwarf_version } { + compile_unit { + {producer "GNU C 14.1.0"} + {language @DW_LANG_C} + {name $::srcfile} + {comp_dir /tmp} + {low_pc 0 addr} + {DW_AT_stmt_list $lines_table DW_FORM_sec_offset} + } { + inline_func: subprogram { + {name foo} + {inline @DW_INL_declared_inlined} + } + subprogram { + {name main} + {decl_file 1 data1} + {decl_line $::main_decl_line data1} + {decl_column 1 data1} + {low_pc $::main_start addr} + {high_pc $::main_len data4} + {external 1 flag} + } { + inlined_subroutine { + {abstract_origin %$inline_func} + {call_file 1 data1} + {call_line $::foo_call_line data1} + {ranges $ranges_label DW_FORM_sec_offset} + } + } + } + } + + lines {version 2 default_is_stmt 1} lines_table { + include_dir "$::srcdir/$::subdir" + file_name "$::srcfile" 1 + + program { + DW_LNE_set_address main + line $::main_line_1 + DW_LNS_copy + + DW_LNE_set_address main_0 + DW_LNS_advance_line 1 + DW_LNS_copy + + DW_LNE_set_address main_1 + line $::foo_line_1 + DW_LNS_copy + + DW_LNE_set_address main_2 + DW_LNS_advance_line 1 + DW_LNS_copy + + DW_LNE_set_address main_3 + DW_LNS_advance_line 1 + DW_LNS_copy + + DW_LNE_set_address main_3 + line $::foo_call_line + DW_LNS_negate_stmt + DW_LNS_copy + + DW_LNE_set_address main_4 + DW_LNS_copy + + DW_LNE_set_address main_5 + DW_LNS_advance_line 1 + DW_LNS_negate_stmt + DW_LNS_copy + + DW_LNE_set_address main_6 + DW_LNS_advance_line 1 + DW_LNS_copy + + DW_LNE_set_address main_7 + DW_LNS_advance_line 1 + DW_LNS_copy + + DW_LNE_set_address main_8 + DW_LNS_advance_line 1 + DW_LNS_copy + + DW_LNE_set_address "$::main_start + $::main_len" + DW_LNE_end_sequence + } + } + + if { $dwarf_version == 5 } { + rnglists {} { + table {} { + ranges_label: list_ { + start_end main_1 main_3 + start_end main_7 main_8 + } + } + } + } else { + ranges { } { + ranges_label: sequence { + range main_1 main_3 + range main_7 main_8 + } + } + } + } +} + +# Wrapper around 'build_dwarf_for_first_block_range', creates DWARF 4 range +# information. + +proc build_dwarf_for_first_block_range_4 { asm_file } { + build_dwarf_for_first_block_range $asm_file 4 +} + +# Wrapper around 'build_dwarf_for_first_block_range', creates DWARF 5 range +# information. + +proc build_dwarf_for_first_block_range_5 { asm_file } { + build_dwarf_for_first_block_range $asm_file 5 +} + +# Assuming GDB is stopped at the entry $pc for 'foo', use 'maint info +# blocks' to check the block for 'foo' is correct. This function checks +# 'foo' created by 'build_dwarf_for_first_block_range'. + +proc check_for_block_ranges_1 {} { + + set foo_start [get_hexadecimal_valueof "&main_1" "*UNKNOWN*" \ + "get address of foo start"] + set foo_end [get_hexadecimal_valueof "&main_8" "*UNKNOWN*" \ + "get address of foo end"] + + set main_4 [get_hexadecimal_valueof "&main_4" "*UNKNOWN*" \ + "get address of main_4 label"] + set main_7 [get_hexadecimal_valueof "&main_7" "*UNKNOWN*" \ + "get address of main_7 label"] + + gdb_test "maintenance info blocks" \ + [multi_line \ + "\\\[\\(block \\*\\) $::hex\\\] $foo_start\\.\\.$foo_end" \ + " entry pc: $foo_start" \ + " inline function: foo" \ + " symbol count: $::decimal" \ + " address ranges:" \ + " $foo_start\\.\\.$main_4" \ + " $main_7\\.\\.$foo_end"] \ + "block for foo has expected content" +} + +# Create DWARF for the test. In this case, inline function 'foo' is created +# with two ranges, and it is the second range that needs extending. + +proc build_dwarf_for_last_block_range { asm_file dwarf_version } { + Dwarf::assemble $asm_file { + upvar dwarf_version dwarf_version + declare_labels lines_table inline_func ranges_label + + cu { version $dwarf_version } { + compile_unit { + {producer "GNU C 14.1.0"} + {language @DW_LANG_C} + {name $::srcfile} + {comp_dir /tmp} + {low_pc 0 addr} + {DW_AT_stmt_list $lines_table DW_FORM_sec_offset} + } { + inline_func: subprogram { + {name foo} + {inline @DW_INL_declared_inlined} + } + subprogram { + {name main} + {decl_file 1 data1} + {decl_line $::main_decl_line data1} + {decl_column 1 data1} + {low_pc $::main_start addr} + {high_pc $::main_len data4} + {external 1 flag} + } { + inlined_subroutine { + {abstract_origin %$inline_func} + {call_file 1 data1} + {call_line $::foo_call_line data1} + {ranges $ranges_label DW_FORM_sec_offset} + {entry_pc main_1 addr} + } + } + } + } + + lines {version 2 default_is_stmt 1} lines_table { + include_dir "$::srcdir/$::subdir" + file_name "$::srcfile" 1 + + program { + DW_LNE_set_address main + line $::main_line_1 + DW_LNS_copy + + DW_LNE_set_address main_0 + DW_LNS_advance_line 1 + DW_LNS_copy + + DW_LNE_set_address main_1 + line $::foo_line_1 + DW_LNS_copy + + DW_LNE_set_address main_2 + DW_LNS_advance_line 1 + DW_LNS_copy + + DW_LNE_set_address main_3 + DW_LNS_advance_line 1 + DW_LNS_copy + + DW_LNE_set_address main_3 + line $::foo_call_line + DW_LNS_negate_stmt + DW_LNS_copy + + DW_LNE_set_address main_4 + DW_LNS_copy + + DW_LNE_set_address main_5 + DW_LNS_advance_line 1 + DW_LNS_negate_stmt + DW_LNS_copy + + DW_LNE_set_address main_6 + DW_LNS_advance_line 1 + DW_LNS_copy + + DW_LNE_set_address main_7 + DW_LNS_advance_line 1 + DW_LNS_copy + + DW_LNE_set_address main_8 + DW_LNS_advance_line 1 + DW_LNS_copy + + DW_LNE_set_address "$::main_start + $::main_len" + DW_LNE_end_sequence + } + } + + if { $dwarf_version == 5 } { + rnglists {} { + table {} { + ranges_label: list_ { + start_end main_7 main_8 + start_end main_1 main_3 + } + } + } + } else { + ranges { } { + ranges_label: sequence { + range main_7 main_8 + range main_1 main_3 + } + } + } + } +} + +# Wrapper around 'build_dwarf_for_last_block_range', creates DWARF 4 range +# information. + +proc build_dwarf_for_last_block_range_4 { asm_file } { + build_dwarf_for_last_block_range $asm_file 4 +} + +# Wrapper around 'build_dwarf_for_last_block_range', creates DWARF 5 range +# information. + +proc build_dwarf_for_last_block_range_5 { asm_file } { + build_dwarf_for_last_block_range $asm_file 5 +} + +# Assuming GDB is stopped at the entry $pc for 'foo', use 'maint info +# blocks' to check the block for 'foo' is correct. This function checks +# 'foo' created by 'build_dwarf_for_last_block_range'. + +proc check_for_block_ranges_2 {} { + + set foo_start [get_hexadecimal_valueof "&main_1" "*UNKNOWN*" \ + "get address of foo start"] + set foo_end [get_hexadecimal_valueof "&main_8" "*UNKNOWN*" \ + "get address of foo end"] + + set main_4 [get_hexadecimal_valueof "&main_4" "*UNKNOWN*" \ + "get address of main_4 label"] + set main_7 [get_hexadecimal_valueof "&main_7" "*UNKNOWN*" \ + "get address of main_7 label"] + + gdb_test "maintenance info blocks" \ + [multi_line \ + "\\\[\\(block \\*\\) $::hex\\\] $foo_start\\.\\.$foo_end" \ + " entry pc: $foo_start" \ + " inline function: foo" \ + " symbol count: $::decimal" \ + " address ranges:" \ + " $main_7\\.\\.$foo_end" \ + " $foo_start\\.\\.$main_4"] \ + "block for foo has expected content" +} + +# Buidl ASM_FILE, along with the global SRCFILE into an executable called +# TESTFILE. Place a breakpoint in 'foo', run to the breakpoint, and use +# BLOCK_CHECK_FUNC to ensure the block for 'foo' is correct. +# +# Then step through 'foo' and back into 'main'. + +proc run_test { asm_file testfile block_check_func } { + if {[prepare_for_testing "failed to prepare" $testfile \ + [list $::srcfile $asm_file] {nodebug}]} { + return + } + + if {![runto_main]} { + return + } + + gdb_breakpoint foo + gdb_test "continue" \ + [multi_line \ + "Breakpoint $::decimal, foo \\(\\) \[^\r\n\]+:$::foo_line_1" \ + "$::foo_line_1\\s+/\\* foo:1 \\*/"] \ + "continue to b/p in foo" + + # Check that the block for `foo` has been extended. + $block_check_func + + gdb_test "frame 1" \ + [multi_line \ + "#1 main \\(\\) at \[^\r\n\]+/$::srcfile:$::foo_call_line" \ + "$::foo_call_line\\s+\[^\r\n\]+/\\* foo call line \\*/"] \ + "frame 1 is for main" + + gdb_test "step" \ + "^[expr $::foo_line_1 + 1]\\s+/\\* foo:2 \\*/" \ + "step to second line of foo" + + gdb_test "step" \ + "^[expr $::foo_line_1 + 2]\\s+/\\* foo:3 \\*/" \ + "step to third line of foo" + + gdb_test "step" \ + [multi_line \ + "^main \\(\\) at \[^\r\n\]+:$::main_line_4" \ + "$::main_line_4\\s+/\\* main:4 \\*/"] \ + "set back to main" + + gdb_test "step" \ + "^[expr $::main_line_4 + 1]\\s+/\\* main:5 \\*/" \ + "step again in main" +} + +# Test specifications, items are: +# 1. Prefix string used to describe the test. +# 2. Proc to call that builds the DWARF. +# 3. Proc to call that runs 'maint info blocks' when stopped at the entry +# $pc for 'foo' (the inline function), and checks that the block details +# for 'foo' are correct. +set test_list \ + [list \ + [list "block with ranges, extend first range, dwarf 4" \ + build_dwarf_for_first_block_range_4 \ + check_for_block_ranges_1] \ + [list "block with ranges, extend first range, dwarf 5" \ + build_dwarf_for_first_block_range_5 \ + check_for_block_ranges_1] \ + [list "block with ranges, extend last range, dwarf 4" \ + build_dwarf_for_last_block_range_4 \ + check_for_block_ranges_2] \ + [list "block with ranges, extend last range, dwarf 5" \ + build_dwarf_for_last_block_range_4 \ + check_for_block_ranges_2] \ + [list "contiguous block" \ + build_dwarf_for_contiguous_block \ + check_contiguous_block] \ + ] + +# Run all the tests. +set suffix 0 +foreach test_spec $test_list { + incr suffix + + set prefix [lindex $test_spec 0] + set build_dwarf_func [lindex $test_spec 1] + set check_block_func [lindex $test_spec 2] + + with_test_prefix $prefix { + set asm_file [standard_output_file ${testfile}-${suffix}.S] + $build_dwarf_func $asm_file + run_test $asm_file ${testfile}-${suffix} $check_block_func + } +} -- 2.47.1