From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id UaoHA2Mx1WoDYCwAWB0awg (envelope-from ) for ; Sun, 18 Oct 2026 16:51:47 -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=dScTCvVC; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id 070321E04E; Sun, 18 Oct 2026 16:51:47 -0400 (EDT) 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 640AF1E04E for ; Sun, 18 Oct 2026 16:51:44 -0400 (EDT) Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id EFEE94BA2E23 for ; Sun, 18 Oct 2026 20:51:43 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org EFEE94BA2E23 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=dScTCvVC 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 EE3674BA23CD for ; Sun, 18 Oct 2026 20:50:46 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org EE3674BA23CD 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 EE3674BA23CD Authentication-Results: sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1792356647; cv=none; b=SArHcRWCLlxFj8CGPUQPttXXZM4LrbbBaqd7gO+kVRtga1wlO1Rh9cF50Oz18Y8FZadi8uaTKzQAbYaF0r9Lw/xY5bMrTiFXmNciFfkegVHPJSNnzPi4+tCplgAvc45T66sGRgdiIyKEfpAlhAX0pFuXhys6Bd7hTl2mW6x0g1I= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1792356647; c=relaxed/simple; bh=DpJg9BtHUnXjbfU/bBAB3N6/zERfwcT+Nz2y4VRAxnw=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=gJs4tB3/mlBypmvP90MXhZ3olt5wSzBWX4fnQ2qZlL+CWg/7qMtF/zFd/XE0QWklW6R89edHtnOYpwzWeKEUap4FHIGQuuDxZIJZAibsZ6MZpfusIAvd341ajdJjFIRH8tZVKPMHsLYx0EMxlOxJgnYzlaO6OYb61tSVcfHqNjA= ARC-Authentication-Results: i=1; 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=dScTCvVC DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org EE3674BA23CD DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1792356646; 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=e7y56BZhuStuaH8MwtpSnoF+eXPGjvJM8jMwzJz88cU=; b=dScTCvVCRqJQmQGJTqDj/ogNQTIj9ssPiDFakm3LtMVCHbiTmx/XbYaoTNqv12NZ+pBAym 1HlCWeSuy/n0WaaED5U+HZ6fFKcUI0hbmcPLy0A6oM3M75x6mFbyb6/6+cloRnw3SQcBak 1DwZLlKY+SxXugLZlN/moO1I2hsUGZI= 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-140-XGhvMLP8P4a4MjgdJVJmKQ-1; Sun, 18 Oct 2026 20:50:44 +0000 X-MC-Unique: XGhvMLP8P4a4MjgdJVJmKQ-1 X-Mimecast-MFC-AGG-ID: XGhvMLP8P4a4MjgdJVJmKQ_1792356643 Received: by mail-wr1-f70.google.com with SMTP id ffacd0b85a97d-48df02b91f9so1495643f8f.1 for ; Sun, 18 Oct 2026 13:50:44 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1792356643; x=1792961443; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=e7y56BZhuStuaH8MwtpSnoF+eXPGjvJM8jMwzJz88cU=; b=gi5Z5Scut1XA3QpQpGEqFaLGdWiOi37YIO/fsr+vhOEhMIKawA3K3ep3daW0pi7Aiw rNAcgHV4oh4AOKbUGuXvnwrMbn4pMrMoksg4kCzP41yXnyDEHkUi5MiFyUIADKxnDDWu NkNfmgWWAk6ovw0F54sEsyh00rc7eUPTDKZ4knlQ+yW21gphIbu/M+KpMzKRCAkz0jO5 XD87Jih1yA7WoDU5eZQLO85N+issUV42aMypRBhs9obrNQ20ovkVvY0os/fLXwVJwYLf GYOEu/2rZtGTMjucKdrFHko9QCAj58DPH6mzEBwVgSC7N4lt4ld1vjAH+tuT2AeDtAuN JE7w== X-Gm-Message-State: AFq9FYLULVRfZj2w94zQ6j7ZcWKopjUOKLSPfxTKLNsZ+DhWbVwwv1+n YiJ+GhzelhySeX+vEKCHTXezKCVz60aspdb9eYll42KG0ti86tpHJijEco8QUQw6Ni4yBSDFRfX SX5k/sJzELICARSoZ5Gs+1MzYEhFOFyG0fkGVufzVOKtyfRb5Gn/RxBc75yl0EhH9stevSw6vND H5xxtIl19i8JmMyjt8OQMJeZ9BFbfy3K1uZyJuTdwlQmmkDLs= X-Gm-Gg: AYBFou0PQxXYRbsUPyev3/3ja0HHADkH5vesc242/gLlkd+8JR3cLPbaXUgASuVIKDX 6+6T34nHucna+MYFp96qV2eUbAUQ4lZeRpwTBwuI0jRO7iev/0K9cOM7rUY0SxFyBJa+v1QXVpT c1RJVwRXg98coX1MUleTfXL6AJNt1lbRzBIvnMPQeaAtWasFxB/2XoK8L6QgPGbFp7xtdIRRSJ6 y0ceTYnAYONmOzic9s9B8df+9FeDByMuS/QFWofsqzR+gZyafPoE2f+JDhs44wo3fGb1M5ijVpx /Rubrcrsbokdiwqnq/iqxA6kPzy4slFWBUoMQayZTmpNELwt8CLtStgfF5I+PLN+IyrE X-Received: by 2002:adf:f0cc:0:b0:48c:58b9:f17c with SMTP id ffacd0b85a97d-48dfae645admr14736850f8f.12.1792356642395; Sun, 18 Oct 2026 13:50:42 -0700 (PDT) X-Received: by 2002:adf:f0cc:0:b0:48c:58b9:f17c with SMTP id ffacd0b85a97d-48dfae645admr14736827f8f.12.1792356641291; Sun, 18 Oct 2026 13:50:41 -0700 (PDT) Received: from localhost ([213.31.44.29]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-48dfd4c5813sm26333545f8f.17.2026.10.18.13.50.39 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 18 Oct 2026 13:50:40 -0700 (PDT) From: Andrew Burgess To: gdb-patches@sourceware.org Cc: Andrew Burgess Subject: [PATCH 1/6] gdb: a number of fixes for .gdb_index parsing Date: Sun, 18 Oct 2026 21:50:30 +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-MFC-PROC-ID: sM9Qi6m73RX8NmOWx3D3YypxCQI14xpAZNkOa1h_kb0_1792356643 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 I recently posted a patch which Simon pointed out during review caused GDB to crash when running with cc-with-gdb-index board. The problem was that I was now generating an invalid .gdb_index section. This got me thinking, rather than crashing, GDB should really be rejecting the index as invalid. GDB should not treat the .gdb_index as trusted input. Even if we take the position that GDB will always generate valid .gdb_index sections, there's nothing to say that some other tool might try to generate .gdb_index sections, and do it wrong. We also write the .gdb_index into the index cache, disk corruption could result in an invalid .gdb_index being read back in later. This commit includes a number of fixes which I detail below, but the most important thing here is the addition of the 'gdb_index' proc to testsuite/lib/dwarf.exp. This proc started as a copy of 'proc debug_names' but updated to emit .gdb_index sections. This proc has been further modified to allow invalid indexes to be generated. I believe all of the features I've added so far are documented in the comments, so I'll not repeat it all here, but the general idea is to allow every offset to be overridden to allow for invalid offsets to be encoded into the .gdb_index. There are also options to allow parts of the index to be elided completely. Not every override feature of 'proc gdb_index' has a matching test right now. If I'd done that then I was worried that the GDB side fixes would be too large for a single patch. Further patches in this series will make use of the currently unused features. Currently, in dwarf2/read-gdb-index.c, I have the following fixes: In mapped_gdb_index::build_name_components: (1) If the name index in the .gdb_index's symbol table is out of bounds of the constant pool then ignore this symbol table entry. (2) Add an assert that the components list will contain at least one entry, even an empty string will result in a single component, the empty string. (3) When processing the CU vector, if the declared CU vector length is larger than the constant pool then ignore this CU vector. (4) If we end up with an empty component string after splitting a name from the index, then don't try to call .back() on the empty string. In read_gdb_index_from_buffer: (5) If the symbol table is not empty, but the constant pool doesn't end with a null character then reject this index. A non-empty symbol table means that there must be strings in the constant pool's string table. The strings in this table are null-terminated. And the string table is at the end of the constant pool. Therefore, the last byte in the constant pool must be a null character. If this is not the case then reading strings from the constant pool might not be null-terminated, and might read outside the constant pool. Placing this check here means that we can later on assume that any string read from the constant pool is null-terminated without reading outside the constant pool. In mapped_gdb_index::set_main_name: (6) If the main function name is outside the bounds of the constant pool then warn, and ignore the main function name. (7) Assert that the constant pool is null-terminated, see (5) above. --- gdb/dwarf2/read-gdb-index.c | 85 ++- .../gdb.dwarf2/gdb-index-corrupted.c | 47 ++ .../gdb.dwarf2/gdb-index-corrupted.exp | 367 ++++++++++++ gdb/testsuite/lib/dwarf.exp | 531 ++++++++++++++++++ 4 files changed, 1022 insertions(+), 8 deletions(-) create mode 100644 gdb/testsuite/gdb.dwarf2/gdb-index-corrupted.c create mode 100644 gdb/testsuite/gdb.dwarf2/gdb-index-corrupted.exp diff --git a/gdb/dwarf2/read-gdb-index.c b/gdb/dwarf2/read-gdb-index.c index 324984c0de2..86512c4ab9c 100644 --- a/gdb/dwarf2/read-gdb-index.c +++ b/gdb/dwarf2/read-gdb-index.c @@ -187,8 +187,9 @@ struct mapped_gdb_index const char *symbol_name_at (offset_type idx, dwarf2_per_objfile *per_objfile) const { - return (const char *) (this->constant_pool.data () - + symbol_name_index (idx)); + offset_type name_index = symbol_name_index (idx); + gdb_assert (name_index < this->constant_pool.size ()); + return (const char *) (this->constant_pool.data () + name_index); } size_t symbol_name_count () const @@ -218,6 +219,17 @@ mapped_gdb_index::build_name_components (dwarf2_per_objfile *per_objfile) if (this->symbol_name_slot_invalid (idx)) continue; + /* If the name offset is out of range for the constant pool then + we cannot get a valid string. We just skip this symbol. */ + if (this->symbol_name_index (idx) >= this->constant_pool.size ()) + { + complaint (_(".gdb_index entry %s has bad name offset %s [in module %s]"), + pulongest (idx), + pulongest (this->symbol_name_index (idx)), + objfile_name (per_objfile->objfile)); + continue; + } + const char *name = this->symbol_name_at (idx, per_objfile); /* This code only knows how to break apart components of C++ @@ -256,9 +268,25 @@ mapped_gdb_index::build_name_components (dwarf2_per_objfile *per_objfile) lang = (components.size () == 1) ? language_unknown : language_go; } + /* Even if NAME was the empty string then COMPONENTS will contain one + entry, the empty string. */ + gdb_assert (components.size () > 0); + std::vector these_entries; offset_view vec (constant_pool.slice (symbol_vec_index (idx))); offset_type vec_len = vec[0]; + + /* Bounds check VEC_LEN against the CONSTANT_POOL. */ + if (vec_len >= vec.size ()) + { + /* The vector count would exceed the available space in the + constant pool. This is a corrupt .gdb_index. We could + treat everything up to the end of the constant pool as a + valid vector, but we have no reason to think that's not + also wrong. Instead just pretend this vector is empty. */ + vec_len = 0; + } + for (offset_type vec_idx = 0; vec_idx < vec_len; ++vec_idx) { offset_type cu_index_and_attrs = vec[vec_idx + 1]; @@ -347,9 +375,15 @@ mapped_gdb_index::build_name_components (dwarf2_per_objfile *per_objfile) if (components.size () > 1) { std::string_view penultimate = components[components.size () - 2]; - std::string_view prefix (name, &penultimate.back () + 1 - name); - - need_parents.emplace_back (prefix, std::move (these_entries)); + /* When parsing the DIE, GDB strips out entries with an empty name, + so in a well formed index PENULTIMATE should never be empty. + But the index is external data, so we cannot assume it is well + formed, ignore any empty prefixes. */ + if (penultimate.size () > 0) + { + std::string_view prefix (name, &penultimate.back () + 1 - name); + need_parents.emplace_back (prefix, std::move (these_entries)); + } } } @@ -502,6 +536,25 @@ read_gdb_index_from_buffer (const char *filename, symbol_table)); } + if (!map->symbol_table.empty ()) + { + gdb_assert (!map->constant_pool.empty ()); + + if (map->constant_pool[map->constant_pool.size () - 1] != '\0') + { + static bool warning_printed = false; + if (!warning_printed) + { + warning (_("skipping .gdb_index in %ps, null byte at end of " + "constant pool is missing"), + styled_string (file_name_style.style (), + filename)); + warning_printed = true; + } + return false; + } + } + return true; } @@ -707,10 +760,26 @@ mapped_gdb_index::set_main_name (dwarf2_per_objfile *per_objfile) } ptr += 4; + const ULONGEST name_offset + = extract_unsigned_integer (ptr, sizeof (offset_type), BFD_ENDIAN_LITTLE); + + if (name_offset >= this->constant_pool.size ()) + { + complaint (_(".gdb_index shortcut table has invalid main name offset %s"), + pulongest (name_offset)); + return; + } + + /* If the symbol table is empty then we ignore the .gdb_index in + dwarf2_read_gdb_index. If the symbol table is not empty, and doesn't + end in a '\0' byte then we also ignore the .gdb_index in + read_gdb_index_from_buffer. What this means is that, by the time we + get here, we know that the constant pool ends with '\0' and so the + MAIN_NAME will be a correctly terminated C style string. */ + gdb_assert ((this->constant_pool)[(this->constant_pool).size () - 1] + == '\0'); + main_lang = dwarf_lang_to_enum_language (dw_lang); - const auto name_offset = extract_unsigned_integer (ptr, - sizeof (offset_type), - BFD_ENDIAN_LITTLE); main_name = (const char *) (this->constant_pool.data () + name_offset); } diff --git a/gdb/testsuite/gdb.dwarf2/gdb-index-corrupted.c b/gdb/testsuite/gdb.dwarf2/gdb-index-corrupted.c new file mode 100644 index 00000000000..520df0ce8c4 --- /dev/null +++ b/gdb/testsuite/gdb.dwarf2/gdb-index-corrupted.c @@ -0,0 +1,47 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 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 . */ + +/* Dummy _start function, to be used with -nostartfile. */ + +#include + +volatile int global_var = 0; + +void +_start (void) +{ + asm ("_start_label: .globl _start_label"); + + /* Some work so that the body of the function is not too small. */ + global_var = global_var + 1; + global_var = global_var + 1; + global_var = global_var + 1; + global_var = global_var + 1; + global_var = global_var + 1; + global_var = global_var + 1; + global_var = global_var + 1; + global_var = global_var + 1; + global_var = global_var + 1; + global_var = global_var + 1; + global_var = global_var + 1; + global_var = global_var + 1; + global_var = global_var + 1; + global_var = global_var + 1; + global_var = global_var + 1; + + exit (0); +} diff --git a/gdb/testsuite/gdb.dwarf2/gdb-index-corrupted.exp b/gdb/testsuite/gdb.dwarf2/gdb-index-corrupted.exp new file mode 100644 index 00000000000..a57b3c26341 --- /dev/null +++ b/gdb/testsuite/gdb.dwarf2/gdb-index-corrupted.exp @@ -0,0 +1,367 @@ +# Copyright 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 . + +# Test that GDB handles a corrupt .gdb_index gracefully. This test +# checks a number of different scenarios, check the 'gdb_index' calls +# within the 'run_test' proc for details. + +load_lib dwarf.exp + +require dwarf2_support + +# This test installs its own hand-crafted .gdb_index into the +# executable. The 'readnow' board forces GDB to ignore the index and +# read the full debug info, making this test irrelevant. +require !readnow + +standard_testfile + +if {[prepare_for_testing "failed to prepare" "${testfile}" \ + [list ${srcfile}] {debug ldflags=-nostartfiles}]} { + return +} + +set index [have_index $binfile] +if { ![string eq $index ""] } { + unsupported "cache created at compile time" + return +} + +set func_info_vars \ + [get_func_info _start [list debug ldflags=-nostartfiles]] + +# TESTNAME is a string that is used to identify the test to run. This +# string is used to control how the gdb_index is created, and then +# what checks are performed on the executable after loading it. +proc run_test { testname } { + set asm_file [standard_output_file ${testname}.S] + Dwarf::assemble { + filename $asm_file + add_dummy_cus 0 + } { + upvar testname testname + global func_info_vars + foreach var $func_info_vars { + global $var + } + + cu { label cu_label } { + compile_unit { + DW_AT_language @DW_LANG_C_plus_plus + } { + subprogram { + DW_AT_name _start + DW_AT_low_pc $_start_start DW_FORM_addr + DW_AT_high_pc $_start_end DW_FORM_addr + DW_AT_external 1 DW_FORM_flag + } + } + } + + aranges {} cu_label { + arange {} $_start_start $_start_len + } + + if { $testname eq "empty-component" } { + # GDB tries to split entries within the index based on + # '::' by default. Create an index entry with an empty + # component and ensure that this doesn't trigger undefined + # behaviour in GDB. + gdb_index {} { + cu cu_label + name "::method" {{cu_label function global}} + name _start {{cu_label function global}} + } + } elseif { $testname eq "main-name-offset-1" } { + # Add main function details to a basic index, but encode + # an out of range name offset using the special string + # 'end'. Ensure that GDB doesn't crash and instead just + # ignores the main function details. + # + # Using 'end' here means that the constant pool index used + # for the main entry will point at the first byte after + # the constant pool. This is the off-by-one boundary + # case. + gdb_index {} { + cu cu_label + name _start {{cu_label function global}} + main DW_LANG_C99 end + } + } elseif { $testname eq "main-name-offset-2" } { + # Add main function details to a basic index, but encode + # an out of range name offset. Ensure that GDB doesn't + # crash and instead just ignores the main function details. + # + # This is very similar to the 'main-name-offset-1' test, + # but here the name offset is huge, far past the end of + # the constant pool. This is far more likely to cause GDB + # to crash. + gdb_index {} { + cu cu_label + name _start {{cu_label function global}} + main DW_LANG_C99 0xffffff00 + } + } elseif { $testname eq "main-language" } { + # Add main function details to a basic index, but encode + # an invalid language number. Ensure that GDB ignores the + # main function details. + gdb_index {} { + cu cu_label + name _start {{cu_label function global}} + main 0xfffff0 _start + } + } elseif { $testname eq "no-null" } { + # Create a .gdb_index without a null character at the end + # of the string table. This is invalid and GDB should just + # ignore this .gdb_index. + gdb_index { + no_null true + } { + cu cu_label + name _start {{cu_label function global}} + } + } elseif { $testname eq "skip-constant-pool" } { + # Create a .gdb_index with a non-empty symbol table but an + # empty constant pool. GDB should ignore this .gdb_index. + gdb_index { + skip_index_part { constant_pool } + } { + cu cu_label + name _start {{cu_label function global}} + } + } elseif { $testname eq "long-cu-vector" } { + # Create a .gdb_index containing a CU vector in the + # constant pool where the vector length is invalid. + gdb_index {} { + cu cu_label + name _start {{cu_label function global}} + name foo {99999 {cu_label function global}} + } + } elseif { $testname eq "name-offset-1" } { + # Add a symbol to the index whose name offset in the symbol + # table is out of range. The slot still has a valid CU + # vector, so GDB won't treat it as an empty slot and will + # try to read the name. Ensure GDB doesn't crash and + # instead just ignores this one symbol. + # + # The 'end' offset is exactly the size of the constant pool, + # that is, it points at the first byte after the pool. This + # is the off-by-one boundary case. + gdb_index {} { + cu cu_label + name _start {{cu_label function global}} + name foo {{cu_label function global}} end + } + } elseif { $testname eq "name-offset-2" } { + # Just like 'name-offset-1', but here the name offset is + # huge, far past the end of the constant pool. This is far + # more likely to cause GDB to crash. + gdb_index {} { + cu cu_label + name _start {{cu_label function global}} + name foo {{cu_label function global}} 0xffffff00 + } + } elseif { $testname eq "valid-index" } { + # Generate a well-formed index that includes a valid main + # function (valid language, with the name pointing at the + # '_start' symbol). This is a sanity check that index + # generation works, at one point this got broken and this + # wasn't spotted straight away as every index was broken + # in some subtle way. + # + # The C language is used, rather than C++, for the main + # function as for C++/Ada GDB only honours an IS_MAIN entry + # whose name is literally "main". + gdb_index {} { + cu cu_label + name _start {{cu_label function global}} + name foo {{cu_label function global}} + main DW_LANG_C99 _start + } + } else { + perror "invalid testname '$testname' during .gdb_index creation" + } + } + + set testfile ${::testfile}-${testname} + set binfile [standard_output_file $testfile] + if {[build_executable "build executable" \ + $testfile "$asm_file $::srcfile" \ + [list ldflags=-nostartfiles]] == -1} { + return + } + + clean_restart + + gdb_test_no_output "maint set dwarf synchronous on" + gdb_test_no_output "set complaints 100" + + # Loading the binary should not crash despite whatever weird + # things are in the .gdb_index. + gdb_load $binfile + + # If we're using the cc-with-gnu-debuglink board then the debug + # information will have been moved to a separate file. + if { [section_get $binfile ".gnu_debuglink"] eq "" } { + set suffix "" + } else { + # If we are using a debuglink then assume the debug is moved + # into a file with a .debug suffix. + set suffix ".debug" + } + + # Setup the expected results based on the testname. + if { $testname eq "empty-component" } { + set expect_gdb_index_ignored false + set expect_start true + set expect_method true + set expect_main false + set expect_foo false + set expect_name_count 2 + set expect_warning_re "" + } elseif { $testname eq "main-name-offset-1" \ + || $testname eq "main-name-offset-2" \ + || $testname eq "main-language" \ + || $testname eq "long-cu-vector" \ + || $testname eq "name-offset-1" \ + || $testname eq "name-offset-2" } { + set expect_gdb_index_ignored false + set expect_start true + set expect_method false + set expect_main false + set expect_foo false + set expect_name_count 1 + if { $testname eq "main-language" } { + set expect_warning_re "\r\nDuring symbol reading: \\.gdb_index shortcut table has invalid main language $::decimal\r\n" + } elseif { $testname eq "main-name-offset-1" \ + || $testname eq "main-name-offset-2" } { + set expect_warning_re "\r\nDuring symbol reading: \\.gdb_index shortcut table has invalid main name offset $::decimal\r\n" + } elseif { $testname eq "name-offset-1" \ + || $testname eq "name-offset-2" } { + set expect_warning_re "\r\nDuring symbol reading: \\.gdb_index entry $::decimal has bad name offset $::decimal \\\[in module \[^\r\n\]+\\\]\r\n" + } else { + set expect_warning_re "" + } + } elseif { $testname eq "valid-index" } { + # This is a well-formed index, so GDB should see _start and + # should recognise it as the main function. + set expect_gdb_index_ignored false + set expect_start true + set expect_method false + set expect_main true + set expect_foo true + set expect_name_count 2 + set expect_warning_re "" + } elseif { $testname eq "no-null" + || $testname eq "skip-constant-pool" } { + set expect_gdb_index_ignored true + if { $testname eq "no-null" } { + set expect_warning_re "\r\nwarning: skipping .gdb_index in \[^\r\n\]+, null byte at end of constant pool is missing\r\n" + } else { + set expect_warning_re "" + } + + # No need to set any of the other EXPECTED_* variables as they + # will never be used. + } else { + perror "invalid testname '$testname' during index check" + } + + # Check the expected warning was emitted while loading the + # executable. The GDB_FILE_CMD_MSG global was set by the call to + # gdb_load above. + if { $expect_warning_re ne "" } { + gdb_assert { [regexp $expect_warning_re $::gdb_file_cmd_msg] } \ + "index rejected with expected warning" + } else { + gdb_assert { ![regexp "\r\nDuring symbol reading: " \ + $::gdb_file_cmd_msg] \ + && ![regexp "\r\nwarning: " \ + $::gdb_file_cmd_msg] } \ + "index loaded without any warnings or complaints" + } + + # Check if GDB used a .gdb_index section, or ignored it. + set index [get_index_type [file tail $binfile]$suffix] + if { $expect_gdb_index_ignored } { + gdb_assert { [string equal $index "cooked"] } ".gdb_index ignored" + + # Ensure that an attempt to expand all symtabs doesn't crash GDB, + # even when the index is corrupted as it is in this test. + gdb_test_no_output "maint expand-symtabs" "expand all symtabs" + + return + } else { + gdb_assert { [string equal $index "gdb"] } ".gdb_index used" + } + + # Take a look at the details of the loaded .gdb_index and check + # that they match the expected results. + set saw_start false + set saw_method false + set saw_foo false + set saw_main "" + set name_count 0 + gdb_test_multiple "maint print objfiles" "" -lbl { + -re "^\r\n\\s+name:\\s+(\[^\r\n\]+)(?=\r\n)" { + incr name_count + set name $expect_out(1,string) + if { $name eq "_start" } { + set saw_start true + } elseif { $name eq "method" } { + set saw_method true + } elseif { $name eq "foo" } { + set saw_foo true + } + exp_continue + } + -re "^\r\n\\s+main: \\(\\(cooked_index_entry \\*\\) ($::decimal|$::hex)\\)\[^\r\n\]*(?=\r\n)" { + set offset $expect_out(1,string) + if { $offset eq "0" } { + set saw_main false + } else { + set saw_main true + } + exp_continue + } + -re "^\r\n$::gdb_prompt $" { + gdb_assert { $saw_start == $expect_start \ + && $saw_method == $expect_method \ + && $saw_main == $expect_main \ + && $name_count == $expect_name_count \ + && $saw_foo == $expect_foo } \ + $gdb_test_name + } + } + + # Ensure that an attempt to expand all symtabs doesn't crash GDB, + # even when the index is corrupted as it is in this test. + gdb_test_no_output "maint expand-symtabs" "expand all symtabs" +} + +set tests { empty-component \ + main-name-offset-1 \ + main-name-offset-2 \ + main-language \ + no-null \ + skip-constant-pool \ + long-cu-vector \ + name-offset-1 \ + name-offset-2 \ + valid-index } + +foreach_with_prefix test $tests { + run_test $test +} diff --git a/gdb/testsuite/lib/dwarf.exp b/gdb/testsuite/lib/dwarf.exp index b3c77864719..7a82f3915d3 100644 --- a/gdb/testsuite/lib/dwarf.exp +++ b/gdb/testsuite/lib/dwarf.exp @@ -585,6 +585,11 @@ namespace eval Dwarf { # otherwise false. variable _cu_is_fission + # The total lengths of each CU/TU unit as an associative array, + # keyed by the unit's label. This is used by the .gdb_index + # generator (see 'gdb_index') to emit a correct CU length. + variable _cu_lengths + # When assembling, this is used to stash the level in which any # code should be evaluated. variable _level @@ -1796,6 +1801,7 @@ namespace eval Dwarf { variable _cu_addr_size variable _cu_offset_size variable _cu_is_fission + variable _cu_lengths variable _level # Establish the defaults. @@ -1858,6 +1864,9 @@ namespace eval Dwarf { set start_label [_compute_label "cu${cu_num}_start"] set end_label [_compute_label "cu${cu_num}_end"] + # Record the length of this CU. + set _cu_lengths($_cu_label) "$end_label - $_cu_label" + if { $label != "" } { upvar $label my_label set my_label $_cu_label @@ -1955,6 +1964,7 @@ namespace eval Dwarf { variable _cu_addr_size variable _cu_offset_size variable _cu_is_fission + variable _cu_lengths variable _level # Establish the defaults. @@ -2018,6 +2028,9 @@ namespace eval Dwarf { set start_label [_compute_label "cu${cu_num}_start"] set end_label [_compute_label "cu${cu_num}_end"] + # Record the length of this TU. + set _cu_lengths($_cu_label) "$end_label - $_cu_label" + if { $label != "" } { upvar $label my_label set my_label $_cu_label @@ -3757,6 +3770,524 @@ namespace eval Dwarf { debug_names_end: } + # Compute the .gdb_index hash for NAME. This must match + # mapped_index_string_hash (index version >= 5) from + # dwarf2/index-common.c. + proc _gdb_index_hash { name } { + set r 0 + foreach c [split $name ""] { + scan $c %c code + if {$code >= 65 && $code <= 90} { + set code [expr {$code + 32}] + } + set r [expr {($r * 67 + $code - 113) & 0xFFFFFFFF}] + } + return $r + } + + # Pack a CU vector entry for .gdb_index. Returns a uint32 with + # the CU index in bits 0-23, symbol kind in bits 28-30, and the + # static/global flag in bit 31. + proc _gdb_index_pack_entry { cu_index kind_str scope_str } { + set kinds {none 0 type 1 variable 2 function 3 other 4} + set kind_val [dict get $kinds $kind_str] + set static_bit [expr {$scope_str eq "static" ? 1 : 0}] + return [expr {($static_bit << 31) | ($kind_val << 28) + | ($cu_index & 0xFFFFFF)}] + } + + # Emit a .gdb_index section. + # + # OPTIONS is a list with an even number of elements containing + # option-name and option-value pairs. + # Current options are: + # version N - the .gdb_index version number + # (default 9) + # no_null true|false + # - when true, omit inserting '\0' bytes after each string + # in the string table. Failing to do this results in an + # invalid index. This exists only for testing GDB's error + # handling. + # (default false) + # skip_index_part + # - skip generating some parts of the .gdb_index. + # is a list of part names, each name identifies part of + # the .gdb_index to not generate. When a part is skipped + # its content is not emitted into the index, but the + # offset to where the content would have been is still + # placed into the index header, the next header offset + # will be at the same location though as the skipped part + # has zero length. This option is only useful for + # generating invalid .gdb_index sections for testing GDB's + # error handling. The supported part names are: + # 'constant_pool'. + # (default {} -- no .gdb_index parts are skipped) + # + # BODY is Tcl code that emits the parts which make up the body of + # the .gdb_index section. It is evaluated in the caller's + # context. The following commands are available for the BODY + # section: + # + # cu [] + # -- add a CU by its label. The optional overrides the + # CU length written into the CU list. Normally this length is + # computed automatically to match the actual size of the CU in + # .debug_info; providing a (bogus) override allows GDB's + # handling of an inconsistent CU length to be tested. + # + # name [] + # -- add symbol with its CU vector to the constant pool: + # + # The is a list of { } tuples + # where: + # + # is a CU label as passed to the 'cu' command; + # this is used to calculate the index into the CU list + # within the index. + # + # Or, this can be a string 'CU-' where + # is a decimal value which is used as the index. + # This exists for testing, it allows invalid indexes + # to be generated. + # + # is 'none', 'type', 'variable', 'function', or 'other'. + # + # is 'global' or 'static' + # + # The optional overrides the name offset that is + # written into this symbol's slot in the symbol table. Normally + # this offset is computed automatically so that it points at the + # string in the constant pool. Providing an override + # value allows an invalid (e.g. out of range) offset to be + # encoded for testing GDB's error handling. The value can be a + # raw number, or the keyword 'end' which resolves to the exact + # size of the constant pool (i.e. one byte past its end). The + # string is still emitted into the constant pool. + # + # main + # -- add symbol of to the index as the entry point + # symbol. + # + # For a well formed index the should be something + # declared with the 'name' command above, and is used to + # compute the offset into the constant pool's string table. + # + # For testing purposes the can also be a hex or + # decimal value, in which case this is used as the offset, + # without being checked. This allows for offsets that + # point outside the constant pool. + # + # If is the string 'end' then this also generates an + # invalid offset, this offset points exactly to the first + # byte outside the constant pool and is used to test for + # off-by-one errors in GDB. This does mean that we cannot + # generate a fake index including a function called 'end', + # but this felt like an acceptable tradeoff. + # + # The can be any valid DW_LANG_* constant, or for + # testing purposes, any integer, even if this is not a valid + # language. + # + # Example: + # gdb_index {} { + # cu cu_label + # name "object" {{cu_label type global}} + # name "object::method" {{cu_label function global}} + # name "some_function" {{cu_label function global}} + # main DW_LANG_C99 some_function + # } + proc gdb_index { options body } { + global decimal + variable _cu_lengths + + parse_options { + { version 9 } + { no_null false } + { skip_index_part {} } + } + + # Validate the names passed to skip_index_part to catch typos in + # tests. + foreach part $skip_index_part { + if { [lsearch -exact { constant_pool } $part] == -1 } { + error "gdb_index: unknown skip_index_part '$part'" + } + } + + # A list of two entry lists describing CUs. Each sub-list has the + # form {label length}. The