bool is_contiguous () const
{ return this->ranges ().size () <= 1; }
- /* Return the "entry PC" of this block.
+ /* Return the entry-pc of this block.
- The entry PC is the lowest (start) address for the block when all addresses
- within the block are contiguous. If non-contiguous, then use the start
- address for the first range in the block.
+ If the entry PC has been set to a specific value then this is
+ returned. Otherwise, the default_entry_pc() address is returned. */
- At the moment, this almost matches what DWARF specifies as the entry
- pc. (The missing bit is support for DW_AT_entry_pc which should be
- preferred over range data and the low_pc.)
+ CORE_ADDR entry_pc () const
+ {
+ return default_entry_pc () + m_entry_pc_offset;
+ }
- Once support for DW_AT_entry_pc is added, I expect that an entry_pc
- field will be added to one of these data structures. Once that's done,
- the entry_pc field can be set from the dwarf reader (and other readers
- too). ENTRY_PC can then be redefined to be less DWARF-centric. */
+ /* Set this block's entry-pc to ADDR, which must lie between start() and
+ end(). The entry-pc is stored as the signed offset from the
+ default_entry_pc() address.
- CORE_ADDR entry_pc () const
+ Note that block sub-ranges can be out of order, as such the offset of
+ the entry-pc might be negative. */
+
+ void set_entry_pc (CORE_ADDR addr)
{
- if (this->is_contiguous ())
- return this->start ();
- else
- return this->ranges ()[0].start ();
+ CORE_ADDR start = default_entry_pc ();
+
+ gdb_assert (addr >= this->start () && addr < this->end ());
+ gdb_assert (start >= this->start () && start < this->end ());
+
+ m_entry_pc_offset = addr - start;
}
/* Return the objfile of this block. */
private:
+ /* Return the default entry-pc of this block. The default is the address
+ we use if the debug information hasn't specifically set a different
+ entry-pc value. This is the lowest address for the block when all
+ addresses within the block are contiguous. If non-contiguous, then
+ use the start address for the first range in the block.
+
+ This almost matches what DWARF specifies as the entry pc, except that
+ the final case, using the first address of the first range, is a GDB
+ extension. However, the DWARF reader sets the specific entry-pc
+ wherever possible, so this non-standard fallback case is only used as
+ a last resort. */
+
+ CORE_ADDR default_entry_pc () const
+ {
+ if (this->is_contiguous ())
+ return this->start ();
+ else
+ return this->ranges ()[0].start ();
+ }
+
/* If the namespace_info is NULL, allocate it via OBSTACK and
initialize its members to zero. */
void initialize_namespace (struct obstack *obstack);
startaddr and endaddr above. */
struct blockranges *m_ranges = nullptr;
+
+ /* The offset of the actual entry-pc value from the default entry-pc
+ value. If space was no object then we'd store an actual address along
+ with a flag to indicate if the address has been set or not. But we'd
+ like to keep the size of block low, so we'd like to use a single
+ member variable.
+
+ We would also like to avoid using 0 as a special address; some targets
+ do allow for accesses to address 0.
+
+ So instead we store the offset of the defined entry-pc from the
+ default entry-pc. See default_entry_pc() for the definition of the
+ default entry-pc. See entry_pc() for how this offset is used. */
+
+ LONGEST m_entry_pc_offset = 0;
};
/* The global block is singled out so that we can provide a back-link
*highpc = best_high;
}
+/* Return the base address for DIE (which is represented by BLOCK) within
+ CU. The base address is the DW_AT_low_pc, or if that is not present,
+ the first address in the first range defined by DW_AT_ranges.
+
+ The DWARF standard actually says that if DIE has neither DW_AT_low_pc or
+ DW_AT_ranges then we should search in the parent of DIE for those
+ properties, and so on up the hierarchy, until we find a die with one of
+ those attributes, and use that as the base address. We don't implement
+ that yet simply because we've never encountered a need for it. */
+
+static std::optional<CORE_ADDR>
+dwarf2_die_base_address (struct die_info *die, struct block *block,
+ struct dwarf2_cu *cu)
+{
+ dwarf2_per_objfile *per_objfile = cu->per_objfile;
+
+ struct attribute *attr = dwarf2_attr (die, DW_AT_low_pc, cu);
+ if (attr != nullptr)
+ return per_objfile->relocate (attr->as_address ());
+ else if (block->ranges ().size () > 0)
+ return block->ranges ()[0].start ();
+
+ return {};
+}
+
+/* Set the entry PC for BLOCK which represents DIE from CU. Relies on the
+ range information (if present) already having been read from DIE and
+ stored into BLOCK. */
+
+static void
+dwarf2_record_block_entry_pc (struct die_info *die, struct block *block,
+ struct dwarf2_cu *cu)
+{
+ dwarf2_per_objfile *per_objfile = cu->per_objfile;
+
+ /* Filled with the entry-pc if we can find it. */
+ std::optional<CORE_ADDR> entry;
+
+ /* Set the block's entry PC where possible. */
+ struct attribute *attr = dwarf2_attr (die, DW_AT_entry_pc, cu);
+ if (attr != nullptr)
+ {
+ /* DWARF-5 allows for the DW_AT_entry_pc to be an unsigned constant
+ offset from the containing DIE's base address. We don't limit the
+ constant handling to DWARF-5 though. If a broken compiler emits
+ this for DWARF-4 then we handle it just as we would for DWARF-5. */
+ if (attr->form_is_constant ())
+ {
+ if (attr->form_is_unsigned ())
+ {
+ CORE_ADDR offset = attr->as_unsigned ();
+
+ std::optional<CORE_ADDR> base
+ = dwarf2_die_base_address (die, block, cu);
+
+ if (base.has_value ())
+ entry.emplace (base.value () + offset);
+ }
+ else
+ {
+ /* We could possibly handle signed constants, but this is out
+ of spec, so for now, just complain and ignore it. */
+ complaint (_("Unhandled constant for DW_AT_entry_pc, value (%s)"),
+ plongest (attr->as_nonnegative ()));
+ }
+ }
+ else
+ entry.emplace (per_objfile->relocate (attr->as_address ()));
+ }
+ else
+ entry = dwarf2_die_base_address (die, block, cu);
+
+ if (entry.has_value ())
+ {
+ CORE_ADDR entry_pc = entry.value ();
+
+ /* Some compilers (e.g. GCC) will have the DW_AT_entry_pc point at an
+ empty sub-range, which by a strict reading of the DWARF means that
+ the entry-pc is outside the blocks code range. If we continue
+ using this address then GDB will confuse itself, breakpoints will
+ be placed at the entry-pc, but once stopped there, GDB will not
+ recognise that it is inside this block.
+
+ To avoid this, ignore entry-pc values that are outside the block's
+ range, GDB will then select a suitable default entry-pc. */
+ if (entry_pc >= block->start () && entry_pc < block->end ())
+ block->set_entry_pc (entry_pc);
+ else
+ complaint (_("in %s, DIE %s, DW_AT_entry_pc (%s) outside "
+ "block range (%s -> %s)"),
+ objfile_name (per_objfile->objfile),
+ sect_offset_str (die->sect_off),
+ paddress (per_objfile->objfile->arch (), entry_pc),
+ paddress (per_objfile->objfile->arch (), block->start ()),
+ paddress (per_objfile->objfile->arch (), block->end ()));
+ }
+}
+
/* Record the address ranges for BLOCK, offset by BASEADDR, as given
- in DIE. */
+ in DIE. Also set the entry PC for BLOCK. */
static void
dwarf2_record_block_ranges (struct die_info *die, struct block *block,
block->set_ranges (make_blockranges (objfile, blockvec));
}
+
+ dwarf2_record_block_entry_pc (die, block, cu);
}
/* Check whether the producer field indicates either of GCC < 4.6, or the
--- /dev/null
+/* 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 <http://www.gnu.org/licenses/>. */
+
+volatile int global_var = 0;
+
+void
+foo (void) /* foo decl line */
+{
+ /* This label is used to find the start of 'foo' when generating the
+ debug information. */
+ asm ("foo_label: .globl foo_label");
+
+ /* These labels define a range within foo. */
+ asm ("foo_r1_s: .globl foo_r1_s");
+ ++global_var;
+ asm ("foo_r1_e: .globl foo_r1_e");
+
+ ++global_var;
+
+ asm ("foo_r2_s: .globl foo_r2_s");
+ ++global_var;
+ asm ("foo_middle: .globl foo_middle");
+ ++global_var;
+ asm ("foo_r2_e: .globl foo_r2_e");
+
+ ++global_var;
+
+ asm ("foo_r3_s: .globl foo_r3_s");
+ ++global_var;
+ asm ("foo_r3_e: .globl foo_r3_e");
+}
+
+int
+main (void)
+{
+ asm ("main_label: .globl main_label");
+}
--- /dev/null
+# 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 <http://www.gnu.org/licenses/>.
+
+# Test different ways in which DW_AT_entry_pc can be expressed in the
+# DWARF. Also test with DWARF-4 and DWARF-5. See the individule test
+# procs below precise details of what DW_AT_entry_pc forms are tested.
+
+load_lib dwarf.exp
+
+require dwarf2_support
+
+standard_testfile
+
+# This compiles the source file and starts and stops GDB, so run it
+# before calling prepare_for_testing otherwise GDB will have exited.
+get_func_info foo
+
+if { [prepare_for_testing "failed to prepare" ${testfile} \
+ [list ${srcfile}]] } {
+ return -1
+}
+
+if ![runto_main] {
+ return -1
+}
+
+# Address for the middle of foo. This is used as our entry point when
+# the entry_pc is defined as an address.
+set foo_middle_addr [get_hexadecimal_valueof "&foo_middle" "UNKNOWN" \
+ "get address for middle of foo"]
+
+# The FOO_START and FOO_END we get from get_func_info is an expression
+# involving symbols and offsets. To check the 'maint info blocks'
+# output we need these converted into actual addresses.
+set foo_start_addr [get_hexadecimal_valueof "$foo_start" "UNKNOWN" \
+ "get address for start of foo"]
+set foo_end_addr [get_hexadecimal_valueof "$foo_end" "UNKNOWN" \
+ "get address for end of foo"]
+
+# The ranges within foo. Used when foo is defined using ranges rather
+# than a low pc and high pc pair. The entry point is in the middle of
+# the second range.
+foreach var { r1_s r1_e r2_s r2_e r3_s r3_e } {
+ set $var [get_hexadecimal_valueof "&foo_$var" "UNKNOWN" \
+ "get address for foo_$var"]
+}
+
+# Line on which 'foo' is declared. Used in generated debug.
+set foo_decl_line [gdb_get_line_number "foo decl line"]
+
+if [is_ilp32_target] {
+ set ptr_type "data4"
+} else {
+ set ptr_type "data8"
+}
+
+# Generate a suffix number. Called from each of the test procs below
+# to acquire a unique suffix for naming asm files and executables.
+
+set global_test_suffix 0
+proc get_next_suffix {} {
+ global global_test_suffix
+ incr global_test_suffix
+
+ return $global_test_suffix
+}
+
+# Helper for the two build_and_test_* procs below. Combine ASM_FILE
+# with the global SRCFILE and build an executable. Use SUFFIX to give
+# the executable a unique name.
+
+proc build_and_runto_main { suffix asm_file } {
+ if {[prepare_for_testing "failed to prepare" "${::testfile}-${suffix}" \
+ [list $::srcfile $asm_file] {nodebug}]} {
+ return false
+ }
+
+ if ![runto_main] {
+ return false
+ }
+
+ # Ensure the CU containing 'foo' is expanded, so the blocks are
+ # visible.
+ gdb_test "info function foo" \
+ "File \[^\r\n\]+/$::srcfile:\r\n$::foo_decl_line:\\s+void foo\\(\\);.*"
+
+ return true
+}
+
+
+# Combine ASM_FILE with the global SRCFILE and build an executable,
+# use SUFFIX to make the executable name unique.
+#
+# Then check the blocks at the symbol `foo_middle'. The inner most
+# block should be a block for 'foo' with a continuous address range
+# and an entry address of ENTRY_PC.
+
+proc build_and_test_continuous { suffix asm_file entry_pc } {
+ if { ![build_and_runto_main $suffix $asm_file] } {
+ return false
+ }
+
+ gdb_test "maint info blocks foo_middle" \
+ [multi_line \
+ "\\\[\[^\]\]+\\\] $::foo_start_addr\.\.$::foo_end_addr" \
+ " entry pc: $entry_pc" \
+ " function: foo" \
+ " is contiguous"]
+}
+
+# Combine ASM_FILE with the global SRCFILE and build an executable,
+# use SUFFIX to make the executable name unique.
+#
+# Then check the blocks at the symbol `foo_middle'. The inner most
+# block should be a block for 'foo' which has 3 address ranges and an
+# entry address of ENTRY_PC.
+
+proc build_and_test_ranged { suffix asm_file entry_pc } {
+ if { ![build_and_runto_main $suffix $asm_file] } {
+ return false
+ }
+
+ gdb_test "maint info blocks foo_middle" \
+ [multi_line \
+ "\\\[\[^\]\]+\\\] $::r1_s\.\.$::r3_e" \
+ " entry pc: $entry_pc" \
+ " function: foo" \
+ " address ranges:" \
+ " $::r1_s\.\.$::r1_e" \
+ " $::r2_s\.\.$::r2_e" \
+ " $::r3_s\.\.$::r3_e" ]
+}
+
+# The function's address range is defined using low/high bounds and
+# the entry_pc attribute is not given. The function's entry PC will
+# default to the low address.
+
+proc_with_prefix use_low_high_bounds_without_entry_pc { dwarf_vesion } {
+ set suffix [get_next_suffix]
+
+ # Make some DWARF for the test.
+ set asm_file [standard_output_file "$::testfile-dw-$suffix.S"]
+ Dwarf::assemble $asm_file {
+ global srcfile
+
+ declare_labels lines_table
+
+ cu { version $::dwarf_version } {
+ compile_unit {
+ {producer "gcc"}
+ {language @DW_LANG_C}
+ {name ${srcfile}}
+ {comp_dir /tmp}
+ {stmt_list $lines_table DW_FORM_sec_offset}
+ } {
+ subprogram {
+ {name foo}
+ {decl_file 1 data1}
+ {decl_line $::foo_decl_line data1}
+ {decl_column 1 data1}
+ {low_pc $::foo_start addr}
+ {high_pc $::foo_len $::ptr_type}
+ {external 1 flag}
+ }
+ }
+ }
+
+ lines {version 2} lines_table {
+ include_dir "$::srcdir/$::subdir"
+ file_name "$srcfile" 1
+ }
+ }
+
+ build_and_test_continuous $suffix $asm_file $::foo_start_addr
+}
+
+# The function's address range is defined using low/high bounds and an
+# entry_pc attribute is given (which contains an address), which will
+# be used as the function's entry address.
+
+proc_with_prefix use_low_high_bounds_with_entry_pc { dwarf_version } {
+ set suffix [get_next_suffix]
+
+ # Make some DWARF for the test.
+ set asm_file [standard_output_file "$::testfile-dw-$suffix.S"]
+ Dwarf::assemble $asm_file {
+ global srcfile
+
+ declare_labels lines_table
+
+ cu { version $::dwarf_version } {
+ compile_unit {
+ {producer "gcc"}
+ {language @DW_LANG_C}
+ {name ${srcfile}}
+ {comp_dir /tmp}
+ {stmt_list $lines_table DW_FORM_sec_offset}
+ } {
+ subprogram {
+ {name foo}
+ {decl_file 1 data1}
+ {decl_line $::foo_decl_line data1}
+ {decl_column 1 data1}
+ {low_pc $::foo_start addr}
+ {high_pc $::foo_len $::ptr_type}
+ {external 1 flag}
+ {entry_pc foo_middle addr}
+ }
+ }
+ }
+
+ lines {version 2} lines_table {
+ include_dir "$::srcdir/$::subdir"
+ file_name "$srcfile" 1
+ }
+ }
+
+ build_and_test_continuous $suffix $asm_file $::foo_middle_addr
+}
+
+# The function's address range is defined using low/high bounds and an
+# entry_pc attribute is given (which contains an offset from the base
+# address), which will be used to compute the function's entry address.
+
+proc_with_prefix use_low_high_bounds_with_entry_offset { dwarf_version } {
+ set suffix [get_next_suffix]
+
+ # Make some DWARF for the test.
+ set asm_file [standard_output_file "$::testfile-dw-$suffix.S"]
+ Dwarf::assemble $asm_file {
+ global srcfile
+
+ declare_labels lines_table
+
+ set foo_offset [expr $::foo_middle_addr - $::foo_start_addr]
+
+ cu { version $::dwarf_version } {
+ compile_unit {
+ {producer "gcc"}
+ {language @DW_LANG_C}
+ {name ${srcfile}}
+ {comp_dir /tmp}
+ {stmt_list $lines_table DW_FORM_sec_offset}
+ } {
+ subprogram {
+ {name foo}
+ {decl_file 1 data1}
+ {decl_line $::foo_decl_line data1}
+ {decl_column 1 data1}
+ {low_pc $::foo_start addr}
+ {high_pc $::foo_len $::ptr_type}
+ {external 1 flag}
+ {entry_pc $foo_offset data4}
+ }
+ }
+ }
+
+ lines {version 2} lines_table {
+ include_dir "$::srcdir/$::subdir"
+ file_name "$srcfile" 1
+ }
+ }
+
+ build_and_test_continuous $suffix $asm_file $::foo_middle_addr
+}
+
+# The function's address range is defined using range information. No
+# entry_pc attribute is used. The entry PC for the function will
+# default to the first address of the first range.
+
+proc_with_prefix use_ranges_without_entry_pc { dwarf_version } {
+ set suffix [get_next_suffix]
+
+ # Make some DWARF for the test.
+ set asm_file [standard_output_file "$::testfile-dw-$suffix.S"]
+ Dwarf::assemble $asm_file {
+ upvar dwarf_version dwarf_version
+ global srcfile
+
+ declare_labels lines_table ranges_label
+
+ cu { version $::dwarf_version } {
+ compile_unit {
+ {producer "gcc"}
+ {language @DW_LANG_C}
+ {name ${srcfile}}
+ {comp_dir /tmp}
+ {stmt_list $lines_table DW_FORM_sec_offset}
+ {low_pc 0 addr}
+ } {
+ subprogram {
+ {name foo}
+ {decl_file 1 data1}
+ {decl_line $::foo_decl_line data1}
+ {decl_column 1 data1}
+ {external 1 flag}
+ {ranges ${ranges_label} DW_FORM_sec_offset}
+ }
+ }
+ }
+
+ lines {version 2} lines_table {
+ include_dir "$::srcdir/$::subdir"
+ file_name "$srcfile" 1
+ }
+
+ if { $dwarf_version == 5 } {
+ rnglists {} {
+ table {} {
+ ranges_label: list_ {
+ start_end foo_r1_s foo_r1_e
+ start_end foo_r2_s foo_r2_e
+ start_end foo_r3_s foo_r3_e
+ }
+ }
+ }
+ } else {
+ ranges { } {
+ ranges_label: sequence {
+ range foo_r1_s foo_r1_e
+ range foo_r2_s foo_r2_e
+ range foo_r3_s foo_r3_e
+ }
+ }
+ }
+ }
+
+ build_and_test_ranged $suffix $asm_file $::r1_s
+}
+
+# The function's address range is defined using range information and
+# an entry_pc attribute (which is an address) is used, this will be
+# the entry PC for the function.
+
+proc_with_prefix use_ranges_with_entry_pc { dwarf_version } {
+ set suffix [get_next_suffix]
+
+ # Make some DWARF for the test.
+ set asm_file [standard_output_file "$::testfile-dw-$suffix.S"]
+ Dwarf::assemble $asm_file {
+ upvar dwarf_version dwarf_version
+ global srcfile
+
+ declare_labels lines_table ranges_label
+
+ cu { version $::dwarf_version } {
+ compile_unit {
+ {producer "gcc"}
+ {language @DW_LANG_C}
+ {name ${srcfile}}
+ {comp_dir /tmp}
+ {stmt_list $lines_table DW_FORM_sec_offset}
+ {low_pc 0 addr}
+ } {
+ subprogram {
+ {name foo}
+ {decl_file 1 data1}
+ {decl_line $::foo_decl_line data1}
+ {decl_column 1 data1}
+ {external 1 flag}
+ {ranges ${ranges_label} DW_FORM_sec_offset}
+ {entry_pc foo_middle addr}
+ }
+ }
+ }
+
+ lines {version 2} lines_table {
+ include_dir "$::srcdir/$::subdir"
+ file_name "$srcfile" 1
+ }
+
+ if { $dwarf_version == 5 } {
+ rnglists {} {
+ table {} {
+ ranges_label: list_ {
+ start_end foo_r1_s foo_r1_e
+ start_end foo_r2_s foo_r2_e
+ start_end foo_r3_s foo_r3_e
+ }
+ }
+ }
+ } else {
+ ranges { } {
+ ranges_label: sequence {
+ range foo_r1_s foo_r1_e
+ range foo_r2_s foo_r2_e
+ range foo_r3_s foo_r3_e
+ }
+ }
+ }
+ }
+
+ build_and_test_ranged $suffix $asm_file $::foo_middle_addr
+}
+
+# The function's address range is defined using range information and
+# an entry_pc attribute (which is an offset) is used, this will be
+# used to calculate the entry PC for the function.
+
+proc_with_prefix use_ranges_with_entry_offset { dwarf_version } {
+ set suffix [get_next_suffix]
+
+ # Make some DWARF for the test.
+ set asm_file [standard_output_file "$::testfile-dw-$suffix.S"]
+ Dwarf::assemble $asm_file {
+ upvar dwarf_version dwarf_version
+ global srcfile
+
+ declare_labels lines_table ranges_label
+
+ set foo_offset [expr $::foo_middle_addr - $::r1_s]
+
+ cu { version $::dwarf_version } {
+ compile_unit {
+ {producer "gcc"}
+ {language @DW_LANG_C}
+ {name ${srcfile}}
+ {comp_dir /tmp}
+ {stmt_list $lines_table DW_FORM_sec_offset}
+ {low_pc 0 addr}
+ } {
+ subprogram {
+ {name foo}
+ {decl_file 1 data1}
+ {decl_line $::foo_decl_line data1}
+ {decl_column 1 data1}
+ {external 1 flag}
+ {ranges ${ranges_label} DW_FORM_sec_offset}
+ {entry_pc $foo_offset data4}
+ }
+ }
+ }
+
+ lines {version 2} lines_table {
+ include_dir "$::srcdir/$::subdir"
+ file_name "$srcfile" 1
+ }
+
+ if { $dwarf_version == 5 } {
+ rnglists {} {
+ table {} {
+ ranges_label: list_ {
+ start_end foo_r1_s foo_r1_e
+ start_end foo_r2_s foo_r2_e
+ start_end foo_r3_s foo_r3_e
+ }
+ }
+ }
+ } else {
+ ranges { } {
+ ranges_label: sequence {
+ range foo_r1_s foo_r1_e
+ range foo_r2_s foo_r2_e
+ range foo_r3_s foo_r3_e
+ }
+ }
+ }
+ }
+
+ build_and_test_ranged $suffix $asm_file $::foo_middle_addr
+}
+
+# Run the tests.
+foreach_with_prefix dwarf_version { 4 5 } {
+ use_low_high_bounds_without_entry_pc $dwarf_version
+ use_low_high_bounds_with_entry_offset $dwarf_version
+ use_low_high_bounds_with_entry_pc $dwarf_version
+ use_ranges_without_entry_pc $dwarf_version
+ use_ranges_with_entry_pc $dwarf_version
+ use_ranges_with_entry_offset $dwarf_version
+}
--- /dev/null
+/* 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 <http://www.gnu.org/licenses/>. */
+
+#include "attributes.h"
+
+volatile int global = 0;
+
+__attribute__((noinline)) ATTRIBUTE_NOCLONE void
+foo (int arg)
+{
+ global += arg;
+}
+
+inline __attribute__((always_inline)) int
+bar (int val)
+{
+ if (global == val)
+ return 1;
+ foo (1);
+ return 1;
+}
+
+int
+main (void)
+{
+ if ((global && bar (1)) || bar (2))
+ return 0;
+ return 1;
+}
--- /dev/null
+# 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 <http://www.gnu.org/licenses/>.
+
+# Test some code which relies on GDB interpreting the DW_AT_entry_pc
+# correctly in order to place the breakpoints. This was tested with
+# versions of GCC between 8.4 and 14.2 and in all cases the entry_pc
+# was required.
+#
+# Testing with Clang 9.0.1 and 15.0.2 seemed to indicate that the
+# Clang generated code didn't depend on the entry_pc being parsed.
+
+standard_testfile
+
+set options {debug optimize=-O2}
+lappend_include_file options $srcdir/lib/attributes.h
+
+if { [prepare_for_testing "failed to prepare" $binfile $srcfile $options] } {
+ return
+}
+
+if ![runto_main] {
+ return
+}
+
+# This test makes use of inline functions.
+get_debug_format
+if { [skip_inline_frame_tests] } {
+ untested "skipping inline frame tests"
+ return
+}
+
+gdb_breakpoint "bar"
+set bp_bar_num [get_integer_valueof "\$bpnum" "*UNKNOWN*" \
+ "get number of bar breakpoint"]
+
+gdb_breakpoint "foo"
+set bp_foo_num [get_integer_valueof "\$bpnum" "*UNKNOWN*" \
+ "get number of foo breakpoint"]
+
+gdb_test "continue" \
+ "Breakpoint ${bp_bar_num}(?:\\.$decimal)?, bar .*" "continue to bar"
+
+gdb_test "continue" \
+ "Breakpoint ${bp_foo_num}(?:\\.$decimal)?, foo .*" "continue to foo"
+
+gdb_continue_to_end