/* 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.
-
- 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.)
+ If the entry PC has been set to a specific value then this is
+ returned. Otherwise, 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.
- 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. */
+ 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 entry_pc () const
{
- if (this->is_contiguous ())
+ if (m_entry_pc != 0)
+ return m_entry_pc;
+ else if (this->is_contiguous ())
return this->start ();
else
return this->ranges ()[0].start ();
}
+ /* Set this block's entry PC. */
+
+ void set_entry_pc (CORE_ADDR addr)
+ {
+ m_entry_pc = addr;
+ }
+
/* Return the objfile of this block. */
struct objfile *objfile () const;
startaddr and endaddr above. */
struct blockranges *m_ranges = nullptr;
+
+ /* The entry address for this block. The value 0 is special and
+ indicates that the entry address has not been set.
+
+ Using 0 as a special value is not ideal, targets for which 0 is a
+ valid code address might run into problems if they want to use 0 as a
+ block's entry address, but the alternative is to carry a flag
+ indicating if m_entry_pc is valid or not, but that would make 'struct
+ block' even bigger, and we want to keep 'struct block' as small as
+ possible (we might have a lot of blocks). */
+
+ CORE_ADDR m_entry_pc = 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;
+
+ /* 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 ())
+ block->set_entry_pc (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
+ {
+ CORE_ADDR entry_pc = per_objfile->relocate (attr->as_address ());
+ block->set_entry_pc (entry_pc);
+ }
+ }
+ else
+ {
+ std::optional<CORE_ADDR> entry
+ = dwarf2_die_base_address (die, block, cu);
+
+ if (entry.has_value ())
+ block->set_entry_pc (entry.value ());
+ }
+}
+
/* 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
+# 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"]
+}
+
+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
+ }
+
+ 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
+
+ set foo_decl_line [gdb_get_line_number "foo decl line"]
+
+ 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
+
+ set foo_decl_line [gdb_get_line_number "foo decl line"]
+
+ 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_decl_line [gdb_get_line_number "foo decl line"]
+
+ 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
+
+ set foo_decl_line [gdb_get_line_number "foo decl line"]
+
+ 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
+
+ set foo_decl_line [gdb_get_line_number "foo decl line"]
+
+ 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_decl_line [gdb_get_line_number "foo decl line"]
+
+ 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
+}