forked from Imagelibrary/binutils-gdb
Compare commits
5 Commits
users/nalc
...
users/abur
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc9be6af06 | ||
|
|
215ba4f398 | ||
|
|
94a1302434 | ||
|
|
8a2fb168c9 | ||
|
|
bade3fecaf |
15
gdb/block.c
15
gdb/block.c
@@ -196,7 +196,20 @@ blockvector_for_pc_sect (CORE_ADDR pc, struct obj_section *section,
|
|||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
if (pblock)
|
if (pblock)
|
||||||
*pblock = b;
|
{
|
||||||
|
struct symtab_and_line sal = find_pc_sect_line (pc, section, 0);
|
||||||
|
if (sal.line != 0 && sal.pc == pc && sal.is_weak)
|
||||||
|
{
|
||||||
|
const struct block *b2 = find_block_in_blockvector (bl, pc - 1);
|
||||||
|
const struct block *b0 = b;
|
||||||
|
while (b0->superblock () && !b0->function ())
|
||||||
|
b0 = b0->superblock ();
|
||||||
|
if (b0->contains (b2))
|
||||||
|
b = b2;
|
||||||
|
}
|
||||||
|
*pblock = b;
|
||||||
|
}
|
||||||
|
|
||||||
return bl;
|
return bl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
106
gdb/buildsym.c
106
gdb/buildsym.c
@@ -413,6 +413,16 @@ buildsym_compunit::record_block_range (struct block *block,
|
|||||||
|| end_inclusive + 1 != block->end ())
|
|| end_inclusive + 1 != block->end ())
|
||||||
m_pending_addrmap_interesting = true;
|
m_pending_addrmap_interesting = true;
|
||||||
|
|
||||||
|
if (block->inlined_p ())
|
||||||
|
{
|
||||||
|
m_inline_end_vector.push_back (end_inclusive + 1);
|
||||||
|
if (end_inclusive + 1 == start)
|
||||||
|
{
|
||||||
|
end_inclusive = start;
|
||||||
|
m_pending_addrmap_interesting = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
m_pending_addrmap.set_empty (start, end_inclusive, block);
|
m_pending_addrmap.set_empty (start, end_inclusive, block);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -627,19 +637,16 @@ buildsym_compunit::record_line (struct subfile *subfile, int line,
|
|||||||
{
|
{
|
||||||
m_have_line_numbers = true;
|
m_have_line_numbers = true;
|
||||||
|
|
||||||
/* Normally, we treat lines as unsorted. But the end of sequence
|
/* The end of sequence marker is special. We need to delete any
|
||||||
marker is special. We sort line markers at the same PC by line
|
previous lines at the same PC, otherwise these lines may cause
|
||||||
number, so end of sequence markers (which have line == 0) appear
|
problems since they might be at the same address as the following
|
||||||
first. This is right if the marker ends the previous function,
|
function. For instance suppose a function calls abort there is no
|
||||||
and there is no padding before the next function. But it is
|
reason to emit a ret after that point (no joke).
|
||||||
wrong if the previous line was empty and we are now marking a
|
So the label may be at the same address where the following
|
||||||
switch to a different subfile. We must leave the end of sequence
|
function begins. There is also a fake end of sequence marker (-1)
|
||||||
marker at the end of this group of lines, not sort the empty line
|
that we emit internally when switching between different CUs
|
||||||
to after the marker. The easiest way to accomplish this is to
|
In this case, duplicate line table entries shall not be deleted.
|
||||||
delete any empty lines from our table, if they are followed by
|
We simply set the is_weak marker in this case. */
|
||||||
end of sequence markers. All we lose is the ability to set
|
|
||||||
breakpoints at some lines which contain no instructions
|
|
||||||
anyway. */
|
|
||||||
if (line == 0)
|
if (line == 0)
|
||||||
{
|
{
|
||||||
std::optional<int> last_line;
|
std::optional<int> last_line;
|
||||||
@@ -659,15 +666,84 @@ buildsym_compunit::record_line (struct subfile *subfile, int line,
|
|||||||
if (!last_line.has_value () || *last_line == 0)
|
if (!last_line.has_value () || *last_line == 0)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
else if (line == -1)
|
||||||
|
{
|
||||||
|
line = 0;
|
||||||
|
auto e = subfile->line_vector_entries.end ();
|
||||||
|
while (e > subfile->line_vector_entries.begin ())
|
||||||
|
{
|
||||||
|
e--;
|
||||||
|
if (e->unrelocated_pc () != pc)
|
||||||
|
break;
|
||||||
|
e->is_weak = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
linetable_entry &e = subfile->line_vector_entries.emplace_back ();
|
linetable_entry &e = subfile->line_vector_entries.emplace_back ();
|
||||||
e.line = line;
|
e.line = line;
|
||||||
e.is_stmt = (flags & LEF_IS_STMT) != 0;
|
e.is_stmt = (flags & LEF_IS_STMT) != 0;
|
||||||
|
e.is_weak = false;
|
||||||
e.set_unrelocated_pc (pc);
|
e.set_unrelocated_pc (pc);
|
||||||
e.prologue_end = (flags & LEF_PROLOGUE_END) != 0;
|
e.prologue_end = (flags & LEF_PROLOGUE_END) != 0;
|
||||||
e.epilogue_begin = (flags & LEF_EPILOGUE_BEGIN) != 0;
|
e.epilogue_begin = (flags & LEF_EPILOGUE_BEGIN) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Patch the is_stmt bits at the given inline end address.
|
||||||
|
The line table has to be already sorted. */
|
||||||
|
|
||||||
|
static void
|
||||||
|
patch_inline_end_pos (struct subfile *subfile, struct objfile *objfile,
|
||||||
|
CORE_ADDR end)
|
||||||
|
{
|
||||||
|
std::vector<linetable_entry> &items = subfile->line_vector_entries;
|
||||||
|
int a = 2, b = items.size () - 1;
|
||||||
|
|
||||||
|
/* We need at least two items with pc = end in the table.
|
||||||
|
The lowest usable items are at pos 0 and 1, the highest
|
||||||
|
usable items are at pos b - 2 and b - 1. */
|
||||||
|
if (a > b
|
||||||
|
|| end < items[1].pc (objfile)
|
||||||
|
|| end > items[b - 2].pc (objfile))
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Look for the first item with pc > end in the range [a,b].
|
||||||
|
The previous element has pc = end or there is no match.
|
||||||
|
We set a = 2, since we need at least two consecutive elements
|
||||||
|
with pc = end to do anything useful.
|
||||||
|
We set b = items.size () - 1, since we are not interested
|
||||||
|
in the last element which should be an end of sequence
|
||||||
|
marker with line = 0 and is_stmt = true. */
|
||||||
|
while (a < b)
|
||||||
|
{
|
||||||
|
int c = (a + b) / 2;
|
||||||
|
|
||||||
|
if (end < items[c].pc (objfile))
|
||||||
|
b = c;
|
||||||
|
else
|
||||||
|
a = c + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
a--;
|
||||||
|
if (items[a].pc (objfile) != end || items[a].is_stmt)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* When there is a sequence of line entries at the same address
|
||||||
|
where an inline range ends, and the last item has is_stmt = 0,
|
||||||
|
we force all previous items to have is_weak = true as well. */
|
||||||
|
do
|
||||||
|
{
|
||||||
|
/* We stop at the first line entry with a different address,
|
||||||
|
or when we see an end of sequence marker. */
|
||||||
|
a--;
|
||||||
|
if (items[a].pc (objfile) != end || items[a].line == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
items[a].is_weak = true;
|
||||||
|
}
|
||||||
|
while (a > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Subroutine of end_compunit_symtab to simplify it. Look for a subfile that
|
/* Subroutine of end_compunit_symtab to simplify it. Look for a subfile that
|
||||||
matches the main source file's basename. If there is only one, and
|
matches the main source file's basename. If there is only one, and
|
||||||
@@ -892,6 +968,10 @@ buildsym_compunit::end_compunit_symtab_with_blockvector
|
|||||||
relationships, this is why std::stable_sort is used. */
|
relationships, this is why std::stable_sort is used. */
|
||||||
std::stable_sort (subfile->line_vector_entries.begin (),
|
std::stable_sort (subfile->line_vector_entries.begin (),
|
||||||
subfile->line_vector_entries.end ());
|
subfile->line_vector_entries.end ());
|
||||||
|
|
||||||
|
for (int i = 0; i < m_inline_end_vector.size (); i++)
|
||||||
|
patch_inline_end_pos (subfile, m_objfile,
|
||||||
|
m_inline_end_vector[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Allocate a symbol table if necessary. */
|
/* Allocate a symbol table if necessary. */
|
||||||
|
|||||||
@@ -446,6 +446,9 @@ private:
|
|||||||
|
|
||||||
/* Pending symbols that are local to the lexical context. */
|
/* Pending symbols that are local to the lexical context. */
|
||||||
struct pending *m_local_symbols = nullptr;
|
struct pending *m_local_symbols = nullptr;
|
||||||
|
|
||||||
|
/* Pending inline end range addresses. */
|
||||||
|
std::vector<CORE_ADDR> m_inline_end_vector;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10688,6 +10688,17 @@ read_variable (struct die_info *die, struct dwarf2_cu *cu)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Return true if an empty range associated with an entry of type TAG in
|
||||||
|
CU should be "fixed", that is, converted to a single byte, non-empty
|
||||||
|
range. */
|
||||||
|
|
||||||
|
static bool
|
||||||
|
dwarf_fixup_empty_range (struct dwarf2_cu *cu, dwarf_tag tag)
|
||||||
|
{
|
||||||
|
return (tag == DW_TAG_inlined_subroutine
|
||||||
|
&& producer_is_gcc (cu->producer, nullptr, nullptr));
|
||||||
|
}
|
||||||
|
|
||||||
/* Call CALLBACK from DW_AT_ranges attribute value OFFSET
|
/* Call CALLBACK from DW_AT_ranges attribute value OFFSET
|
||||||
reading .debug_rnglists.
|
reading .debug_rnglists.
|
||||||
Callback's type should be:
|
Callback's type should be:
|
||||||
@@ -10850,7 +10861,12 @@ dwarf2_rnglists_process (unsigned offset, struct dwarf2_cu *cu,
|
|||||||
|
|
||||||
/* Empty range entries have no effect. */
|
/* Empty range entries have no effect. */
|
||||||
if (range_beginning == range_end)
|
if (range_beginning == range_end)
|
||||||
continue;
|
{
|
||||||
|
if (dwarf_fixup_empty_range (cu, tag))
|
||||||
|
range_end = (unrelocated_addr) ((CORE_ADDR) range_end + 1);
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
/* Only DW_RLE_offset_pair needs the base address added. */
|
/* Only DW_RLE_offset_pair needs the base address added. */
|
||||||
if (rlet == DW_RLE_offset_pair)
|
if (rlet == DW_RLE_offset_pair)
|
||||||
@@ -10972,7 +10988,12 @@ dwarf2_ranges_process (unsigned offset, struct dwarf2_cu *cu, dwarf_tag tag,
|
|||||||
|
|
||||||
/* Empty range entries have no effect. */
|
/* Empty range entries have no effect. */
|
||||||
if (range_beginning == range_end)
|
if (range_beginning == range_end)
|
||||||
continue;
|
{
|
||||||
|
if (dwarf_fixup_empty_range (cu, tag))
|
||||||
|
range_end = (unrelocated_addr) ((CORE_ADDR) range_end + 1);
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
range_beginning = (unrelocated_addr) ((CORE_ADDR) range_beginning
|
range_beginning = (unrelocated_addr) ((CORE_ADDR) range_beginning
|
||||||
+ (CORE_ADDR) *base);
|
+ (CORE_ADDR) *base);
|
||||||
@@ -11195,9 +11216,24 @@ dwarf2_get_pc_bounds (struct die_info *die, unrelocated_addr *lowpc,
|
|||||||
if (ret == PC_BOUNDS_NOT_PRESENT || ret == PC_BOUNDS_INVALID)
|
if (ret == PC_BOUNDS_NOT_PRESENT || ret == PC_BOUNDS_INVALID)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
/* partial_die_info::read has also the strict LOW < HIGH requirement. */
|
/* These LOW and HIGH values will be used to create a block. A block's
|
||||||
|
high address is the first address after the block's address range, so
|
||||||
|
if 'high <= low' then the block has no code associated with it. */
|
||||||
if (high <= low)
|
if (high <= low)
|
||||||
return PC_BOUNDS_INVALID;
|
{
|
||||||
|
/* In some cases though, when the blocks LOW / HIGH were defined with
|
||||||
|
the DW_AT_low_pc and DW_AT_high_pc, we see some compilers create
|
||||||
|
an empty block when we can provide a better debug experience by
|
||||||
|
having a non-empty block. We do this by "fixing" the block to be
|
||||||
|
a single byte in length. See dwarf_fixup_empty_range for when
|
||||||
|
this fixup is performed. */
|
||||||
|
if (high == low
|
||||||
|
&& ret == PC_BOUNDS_HIGH_LOW
|
||||||
|
&& dwarf_fixup_empty_range (cu, die->tag))
|
||||||
|
high = (unrelocated_addr) (((ULONGEST) low) + 1);
|
||||||
|
else
|
||||||
|
return PC_BOUNDS_INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
/* When using the GNU linker, .gnu.linkonce. sections are used to
|
/* When using the GNU linker, .gnu.linkonce. sections are used to
|
||||||
eliminate duplicate copies of functions and vtables and such.
|
eliminate duplicate copies of functions and vtables and such.
|
||||||
@@ -11465,7 +11501,31 @@ dwarf2_record_block_ranges (struct die_info *die, struct block *block,
|
|||||||
|
|
||||||
CORE_ADDR low = per_objfile->relocate (unrel_low);
|
CORE_ADDR low = per_objfile->relocate (unrel_low);
|
||||||
CORE_ADDR high = per_objfile->relocate (unrel_high);
|
CORE_ADDR high = per_objfile->relocate (unrel_high);
|
||||||
cu->get_builder ()->record_block_range (block, low, high - 1);
|
|
||||||
|
/* Blocks where 'high < low' should be rejected earlier in the
|
||||||
|
process, e.g. see dwarf2_get_pc_bounds. */
|
||||||
|
gdb_assert (high >= low);
|
||||||
|
|
||||||
|
/* The value of HIGH is the first address past the end, but
|
||||||
|
GDB stores ranges with the high value as last inclusive
|
||||||
|
address, so in most cases we need to decrement HIGH here.
|
||||||
|
|
||||||
|
Blocks where 'high == low' represent an empty block (i.e. a
|
||||||
|
block with no associated code).
|
||||||
|
|
||||||
|
When 'high == low' and dwarf_fixup_empty_range returns true we
|
||||||
|
"fix" the empty range into a single byte range, which we can
|
||||||
|
do by leaving HIGH untouched. Otherwise we decrement HIGH,
|
||||||
|
which might result in 'high < low'. */
|
||||||
|
if (high > low || !dwarf_fixup_empty_range (cu, die->tag))
|
||||||
|
high -= 1;
|
||||||
|
|
||||||
|
/* If the above decrement resulted in 'high < low' then this
|
||||||
|
represents an empty range. There's little point storing this
|
||||||
|
in GDB's internal structures, it's just more to search
|
||||||
|
through, and it will never match any address. */
|
||||||
|
if (high >= low)
|
||||||
|
cu->get_builder ()->record_block_range (block, low, high);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18352,21 +18412,9 @@ private:
|
|||||||
|
|
||||||
/* Additional bits of state we need to track. */
|
/* Additional bits of state we need to track. */
|
||||||
|
|
||||||
/* The last file that we called dwarf2_start_subfile for.
|
|
||||||
This is only used for TLLs. */
|
|
||||||
unsigned int m_last_file = 0;
|
|
||||||
/* The last file a line number was recorded for. */
|
/* The last file a line number was recorded for. */
|
||||||
struct subfile *m_last_subfile = NULL;
|
struct subfile *m_last_subfile = NULL;
|
||||||
|
|
||||||
/* The address of the last line entry. */
|
|
||||||
unrelocated_addr m_last_address;
|
|
||||||
|
|
||||||
/* Set to true when a previous line at the same address (using
|
|
||||||
m_last_address) had LEF_IS_STMT set in m_flags. This is reset to false
|
|
||||||
when a line entry at a new address (m_address different to
|
|
||||||
m_last_address) is processed. */
|
|
||||||
bool m_stmt_at_address = false;
|
|
||||||
|
|
||||||
/* When true, record the lines we decode. */
|
/* When true, record the lines we decode. */
|
||||||
bool m_currently_recording_lines = true;
|
bool m_currently_recording_lines = true;
|
||||||
|
|
||||||
@@ -18524,7 +18572,8 @@ dwarf_record_line_1 (struct gdbarch *gdbarch, struct subfile *subfile,
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
dwarf_finish_line (struct gdbarch *gdbarch, struct subfile *subfile,
|
dwarf_finish_line (struct gdbarch *gdbarch, struct subfile *subfile,
|
||||||
unrelocated_addr address, struct dwarf2_cu *cu)
|
unrelocated_addr address, struct dwarf2_cu *cu,
|
||||||
|
bool end_sequence)
|
||||||
{
|
{
|
||||||
if (subfile == NULL)
|
if (subfile == NULL)
|
||||||
return;
|
return;
|
||||||
@@ -18537,7 +18586,8 @@ dwarf_finish_line (struct gdbarch *gdbarch, struct subfile *subfile,
|
|||||||
paddress (gdbarch, (CORE_ADDR) address));
|
paddress (gdbarch, (CORE_ADDR) address));
|
||||||
}
|
}
|
||||||
|
|
||||||
dwarf_record_line_1 (gdbarch, subfile, 0, address, LEF_IS_STMT, cu);
|
dwarf_record_line_1 (gdbarch, subfile, end_sequence ? 0 : -1, address,
|
||||||
|
LEF_IS_STMT, cu);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -18565,38 +18615,17 @@ lnp_state_machine::record_line (bool end_sequence)
|
|||||||
/* For now we ignore lines not starting on an instruction boundary.
|
/* For now we ignore lines not starting on an instruction boundary.
|
||||||
But not when processing end_sequence for compatibility with the
|
But not when processing end_sequence for compatibility with the
|
||||||
previous version of the code. */
|
previous version of the code. */
|
||||||
else if (m_op_index == 0 || end_sequence)
|
else if ((m_op_index == 0 && m_line != 0) || end_sequence)
|
||||||
{
|
{
|
||||||
/* When we switch files we insert an end maker in the first file,
|
if (m_last_subfile != m_cu->get_builder ()->get_current_subfile ()
|
||||||
switch to the second file and add a new line entry. The
|
|| end_sequence)
|
||||||
problem is that the end marker inserted in the first file will
|
|
||||||
discard any previous line entries at the same address. If the
|
|
||||||
line entries in the first file are marked as is-stmt, while
|
|
||||||
the new line in the second file is non-stmt, then this means
|
|
||||||
the end marker will discard is-stmt lines so we can have a
|
|
||||||
non-stmt line. This means that there are less addresses at
|
|
||||||
which the user can insert a breakpoint.
|
|
||||||
|
|
||||||
To improve this we track the last address in m_last_address,
|
|
||||||
and whether we have seen an is-stmt at this address. Then
|
|
||||||
when switching files, if we have seen a stmt at the current
|
|
||||||
address, and we are switching to create a non-stmt line, then
|
|
||||||
discard the new line. */
|
|
||||||
bool file_changed
|
|
||||||
= m_last_subfile != m_cu->get_builder ()->get_current_subfile ();
|
|
||||||
bool ignore_this_line
|
|
||||||
= ((file_changed && !end_sequence && m_last_address == m_address
|
|
||||||
&& ((m_flags & LEF_IS_STMT) == 0)
|
|
||||||
&& m_stmt_at_address)
|
|
||||||
|| (!end_sequence && m_line == 0));
|
|
||||||
|
|
||||||
if ((file_changed && !ignore_this_line) || end_sequence)
|
|
||||||
{
|
{
|
||||||
dwarf_finish_line (m_gdbarch, m_last_subfile, m_address,
|
dwarf_finish_line (m_gdbarch, m_last_subfile, m_address,
|
||||||
m_currently_recording_lines ? m_cu : nullptr);
|
m_currently_recording_lines ? m_cu : nullptr,
|
||||||
|
end_sequence || (m_flags & LEF_IS_STMT) != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!end_sequence && !ignore_this_line)
|
if (!end_sequence)
|
||||||
{
|
{
|
||||||
linetable_entry_flags lte_flags = m_flags;
|
linetable_entry_flags lte_flags = m_flags;
|
||||||
if (producer_is_codewarrior (m_cu))
|
if (producer_is_codewarrior (m_cu))
|
||||||
@@ -18616,15 +18645,6 @@ lnp_state_machine::record_line (bool end_sequence)
|
|||||||
m_last_line = m_line;
|
m_last_line = m_line;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Track whether we have seen any IS_STMT true at m_address in case we
|
|
||||||
have multiple line table entries all at m_address. */
|
|
||||||
if (m_last_address != m_address)
|
|
||||||
{
|
|
||||||
m_stmt_at_address = false;
|
|
||||||
m_last_address = m_address;
|
|
||||||
}
|
|
||||||
m_stmt_at_address |= (m_flags & LEF_IS_STMT) != 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lnp_state_machine::lnp_state_machine (struct dwarf2_cu *cu, gdbarch *arch,
|
lnp_state_machine::lnp_state_machine (struct dwarf2_cu *cu, gdbarch *arch,
|
||||||
@@ -18638,8 +18658,7 @@ lnp_state_machine::lnp_state_machine (struct dwarf2_cu *cu, gdbarch *arch,
|
|||||||
This is currently used by MIPS code,
|
This is currently used by MIPS code,
|
||||||
cf. `mips_adjust_dwarf2_line'. */
|
cf. `mips_adjust_dwarf2_line'. */
|
||||||
m_address ((unrelocated_addr) gdbarch_adjust_dwarf2_line (arch, 0, 0)),
|
m_address ((unrelocated_addr) gdbarch_adjust_dwarf2_line (arch, 0, 0)),
|
||||||
m_flags (lh->default_is_stmt ? LEF_IS_STMT : (linetable_entry_flags) 0),
|
m_flags (lh->default_is_stmt ? LEF_IS_STMT : (linetable_entry_flags) 0)
|
||||||
m_last_address (m_address)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -996,7 +996,8 @@ prepare_one_step (thread_info *tp, struct step_command_fsm *sm)
|
|||||||
if (sym->aclass () == LOC_BLOCK)
|
if (sym->aclass () == LOC_BLOCK)
|
||||||
{
|
{
|
||||||
const block *block = sym->value_block ();
|
const block *block = sym->value_block ();
|
||||||
if (block->end () < tp->control.step_range_end)
|
if (block->end () < tp->control.step_range_end
|
||||||
|
&& block->end () > tp->control.step_range_start)
|
||||||
tp->control.step_range_end = block->end ();
|
tp->control.step_range_end = block->end ();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8200,6 +8200,8 @@ process_event_stop_test (struct execution_control_state *ecs)
|
|||||||
infrun_debug_printf ("stepping through inlined function");
|
infrun_debug_printf ("stepping through inlined function");
|
||||||
|
|
||||||
if (ecs->event_thread->control.step_over_calls == STEP_OVER_ALL
|
if (ecs->event_thread->control.step_over_calls == STEP_OVER_ALL
|
||||||
|
|| ecs->event_thread->stop_pc () != stop_pc_sal.pc
|
||||||
|
|| !stop_pc_sal.is_stmt
|
||||||
|| inline_frame_is_marked_for_skip (false, ecs->event_thread))
|
|| inline_frame_is_marked_for_skip (false, ecs->event_thread))
|
||||||
keep_going (ecs);
|
keep_going (ecs);
|
||||||
else
|
else
|
||||||
@@ -8248,7 +8250,8 @@ process_event_stop_test (struct execution_control_state *ecs)
|
|||||||
end_stepping_range (ecs);
|
end_stepping_range (ecs);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (*curr_frame_id == original_frame_id)
|
else if (get_stack_frame_id (frame)
|
||||||
|
== ecs->event_thread->control.step_stack_frame_id)
|
||||||
{
|
{
|
||||||
/* We are not at the start of a statement, and we have not changed
|
/* We are not at the start of a statement, and we have not changed
|
||||||
frame.
|
frame.
|
||||||
|
|||||||
@@ -497,6 +497,7 @@ jit_symtab_line_mapping_add_impl (struct gdb_symbol_callbacks *cb,
|
|||||||
(unrelocated_addr (map[i].pc));
|
(unrelocated_addr (map[i].pc));
|
||||||
stab->linetable->item[i].line = map[i].line;
|
stab->linetable->item[i].line = map[i].line;
|
||||||
stab->linetable->item[i].is_stmt = true;
|
stab->linetable->item[i].is_stmt = true;
|
||||||
|
stab->linetable->item[i].is_weak = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -265,6 +265,8 @@ dump_symtab_1 (struct symtab *symtab, struct ui_file *outfile)
|
|||||||
gdb_puts (paddress (gdbarch, l->item[i].pc (objfile)), outfile);
|
gdb_puts (paddress (gdbarch, l->item[i].pc (objfile)), outfile);
|
||||||
if (l->item[i].is_stmt)
|
if (l->item[i].is_stmt)
|
||||||
gdb_printf (outfile, "\t(stmt)");
|
gdb_printf (outfile, "\t(stmt)");
|
||||||
|
if (l->item[i].is_weak)
|
||||||
|
gdb_printf (outfile, "\t(weak)");
|
||||||
gdb_printf (outfile, "\n");
|
gdb_printf (outfile, "\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -981,12 +983,13 @@ maintenance_print_one_line_table (struct symtab *symtab, void *data)
|
|||||||
/* Leave space for 6 digits of index and line number. After that the
|
/* Leave space for 6 digits of index and line number. After that the
|
||||||
tables will just not format as well. */
|
tables will just not format as well. */
|
||||||
struct ui_out *uiout = current_uiout;
|
struct ui_out *uiout = current_uiout;
|
||||||
ui_out_emit_table table_emitter (uiout, 7, -1, "line-table");
|
ui_out_emit_table table_emitter (uiout, 8, -1, "line-table");
|
||||||
uiout->table_header (6, ui_left, "index", _("INDEX"));
|
uiout->table_header (6, ui_left, "index", _("INDEX"));
|
||||||
uiout->table_header (6, ui_left, "line", _("LINE"));
|
uiout->table_header (6, ui_left, "line", _("LINE"));
|
||||||
uiout->table_header (18, ui_left, "rel-address", _("REL-ADDRESS"));
|
uiout->table_header (18, ui_left, "rel-address", _("REL-ADDRESS"));
|
||||||
uiout->table_header (18, ui_left, "unrel-address", _("UNREL-ADDRESS"));
|
uiout->table_header (18, ui_left, "unrel-address", _("UNREL-ADDRESS"));
|
||||||
uiout->table_header (7, ui_left, "is-stmt", _("IS-STMT"));
|
uiout->table_header (7, ui_left, "is-stmt", _("IS-STMT"));
|
||||||
|
uiout->table_header (7, ui_left, "is-weak", _("IS-WEAK"));
|
||||||
uiout->table_header (12, ui_left, "prologue-end", _("PROLOGUE-END"));
|
uiout->table_header (12, ui_left, "prologue-end", _("PROLOGUE-END"));
|
||||||
uiout->table_header (14, ui_left, "epilogue-begin", _("EPILOGUE-BEGIN"));
|
uiout->table_header (14, ui_left, "epilogue-begin", _("EPILOGUE-BEGIN"));
|
||||||
uiout->table_body ();
|
uiout->table_body ();
|
||||||
@@ -1008,6 +1011,7 @@ maintenance_print_one_line_table (struct symtab *symtab, void *data)
|
|||||||
uiout->field_core_addr ("unrel-address", objfile->arch (),
|
uiout->field_core_addr ("unrel-address", objfile->arch (),
|
||||||
CORE_ADDR (item->unrelocated_pc ()));
|
CORE_ADDR (item->unrelocated_pc ()));
|
||||||
uiout->field_string ("is-stmt", item->is_stmt ? "Y" : "");
|
uiout->field_string ("is-stmt", item->is_stmt ? "Y" : "");
|
||||||
|
uiout->field_string ("is-weak", item->is_weak ? "Y" : "");
|
||||||
uiout->field_string ("prologue-end", item->prologue_end ? "Y" : "");
|
uiout->field_string ("prologue-end", item->prologue_end ? "Y" : "");
|
||||||
uiout->field_string ("epilogue-begin", item->epilogue_begin ? "Y" : "");
|
uiout->field_string ("epilogue-begin", item->epilogue_begin ? "Y" : "");
|
||||||
uiout->text ("\n");
|
uiout->text ("\n");
|
||||||
|
|||||||
42
gdb/symtab.c
42
gdb/symtab.c
@@ -3294,26 +3294,38 @@ find_pc_sect_line (CORE_ADDR pc, struct obj_section *section, int notcurrent)
|
|||||||
0) instead of a real line. */
|
0) instead of a real line. */
|
||||||
|
|
||||||
if (prev && prev->line
|
if (prev && prev->line
|
||||||
&& (!best || prev->unrelocated_pc () > best->unrelocated_pc ()))
|
&& (!best || prev->unrelocated_pc () > best->unrelocated_pc ()
|
||||||
|
|| (prev->unrelocated_pc () == best->unrelocated_pc ()
|
||||||
|
&& (best->pc (objfile) == pc
|
||||||
|
? !best->is_stmt : best->is_weak))))
|
||||||
{
|
{
|
||||||
best = prev;
|
best = prev;
|
||||||
best_symtab = iter_s;
|
best_symtab = iter_s;
|
||||||
|
|
||||||
/* If during the binary search we land on a non-statement entry,
|
/* If NOTCURRENT is false then the address we are looking for is
|
||||||
scan backward through entries at the same address to see if
|
the address the inferior is currently stopped at. In this
|
||||||
there is an entry marked as is-statement. In theory this
|
case our preference is to report a stop at a line marked as
|
||||||
duplication should have been removed from the line table
|
is_stmt. If BEST is not marked as a statement then scan
|
||||||
during construction, this is just a double check. If the line
|
backwards through entries at this address looking for one that
|
||||||
table has had the duplication removed then this should be
|
is marked as a statement; if one is found then use that.
|
||||||
pretty cheap. */
|
|
||||||
if (!best->is_stmt)
|
If NOTCURRENT is true then the address we're looking for is
|
||||||
|
not the inferior's current address, but is an address from a
|
||||||
|
previous stack frame (i.e. frames 1, 2, 3, ... etc). In this
|
||||||
|
case scanning backwards for an is_stmt line table entry is not
|
||||||
|
the desired behaviour. If an inline function terminated at
|
||||||
|
this address then the last is_stmt line will be within the
|
||||||
|
inline function, while the following non-statement line will
|
||||||
|
be for the outer function. When looking up the stack we
|
||||||
|
expect to see the outer function. */
|
||||||
|
if (!best->is_stmt && !notcurrent)
|
||||||
{
|
{
|
||||||
const linetable_entry *tmp = best;
|
const linetable_entry *tmp = best;
|
||||||
while (tmp > first
|
while (tmp > first
|
||||||
&& (tmp - 1)->unrelocated_pc () == tmp->unrelocated_pc ()
|
&& (tmp - 1)->unrelocated_pc () == tmp->unrelocated_pc ()
|
||||||
&& (tmp - 1)->line != 0 && !tmp->is_stmt)
|
&& (tmp - 1)->line != 0 && !tmp->is_stmt)
|
||||||
--tmp;
|
--tmp;
|
||||||
if (tmp->is_stmt)
|
if (tmp->is_stmt && (tmp->pc (objfile) == pc || !tmp->is_weak))
|
||||||
best = tmp;
|
best = tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3337,18 +3349,14 @@ find_pc_sect_line (CORE_ADDR pc, struct obj_section *section, int notcurrent)
|
|||||||
We used to return alt->line - 1 here, but that could be
|
We used to return alt->line - 1 here, but that could be
|
||||||
anywhere; if we don't have line number info for this PC,
|
anywhere; if we don't have line number info for this PC,
|
||||||
don't make some up. */
|
don't make some up. */
|
||||||
val.pc = pc;
|
if (notcurrent)
|
||||||
}
|
pc++;
|
||||||
else if (best->line == 0)
|
|
||||||
{
|
|
||||||
/* If our best fit is in a range of PC's for which no line
|
|
||||||
number info is available (line number is zero) then we didn't
|
|
||||||
find any valid line information. */
|
|
||||||
val.pc = pc;
|
val.pc = pc;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
val.is_stmt = best->is_stmt;
|
val.is_stmt = best->is_stmt;
|
||||||
|
val.is_weak = best->is_weak;
|
||||||
val.symtab = best_symtab;
|
val.symtab = best_symtab;
|
||||||
val.line = best->line;
|
val.line = best->line;
|
||||||
val.pc = best->pc (objfile);
|
val.pc = best->pc (objfile);
|
||||||
|
|||||||
@@ -1647,6 +1647,9 @@ struct linetable_entry
|
|||||||
/* True if this PC is a good location to place a breakpoint for LINE. */
|
/* True if this PC is a good location to place a breakpoint for LINE. */
|
||||||
bool is_stmt : 1;
|
bool is_stmt : 1;
|
||||||
|
|
||||||
|
/* True if this PC is at a subroutine range end. */
|
||||||
|
bool is_weak : 1;
|
||||||
|
|
||||||
/* True if this location is a good location to place a breakpoint after a
|
/* True if this location is a good location to place a breakpoint after a
|
||||||
function prologue. */
|
function prologue. */
|
||||||
bool prologue_end : 1;
|
bool prologue_end : 1;
|
||||||
@@ -2403,6 +2406,8 @@ struct symtab_and_line
|
|||||||
/* If the line number information is valid, then this indicates if this
|
/* If the line number information is valid, then this indicates if this
|
||||||
line table entry had the is-stmt flag set or not. */
|
line table entry had the is-stmt flag set or not. */
|
||||||
bool is_stmt = false;
|
bool is_stmt = false;
|
||||||
|
/* True if this PC is at a subroutine range end. */
|
||||||
|
bool is_weak = false;
|
||||||
|
|
||||||
/* The probe associated with this symtab_and_line. */
|
/* The probe associated with this symtab_and_line. */
|
||||||
probe *prob = NULL;
|
probe *prob = NULL;
|
||||||
|
|||||||
39
gdb/testsuite/gdb.base/empty-inline.c
Normal file
39
gdb/testsuite/gdb.base/empty-inline.c
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/* 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/>. */
|
||||||
|
|
||||||
|
static int test0 (void)
|
||||||
|
{
|
||||||
|
asm (""); /* line 20 */
|
||||||
|
return 1; /* line 21 */
|
||||||
|
}
|
||||||
|
|
||||||
|
int __attribute__((noinline, noclone))
|
||||||
|
#ifdef __CET__
|
||||||
|
__attribute__((nocf_check))
|
||||||
|
#endif
|
||||||
|
test1 (int x)
|
||||||
|
{
|
||||||
|
asm ("");
|
||||||
|
return x+1; /* line 31 */
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main()
|
||||||
|
{ test1 (test0 ()); /* line 36 */
|
||||||
|
test1 (test0 ()); /* line 37 */
|
||||||
|
return 0; /* line 38 */
|
||||||
|
}
|
||||||
51
gdb/testsuite/gdb.base/empty-inline.exp
Normal file
51
gdb/testsuite/gdb.base/empty-inline.exp
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# 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/>.
|
||||||
|
|
||||||
|
standard_testfile .c
|
||||||
|
|
||||||
|
if { ![test_compiler_info gcc*] || ![supports_statement_frontiers] } {
|
||||||
|
untested "this test needs gcc with statement frontiers"
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
global srcfile testfile
|
||||||
|
|
||||||
|
set options {debug nowarnings optimize=-O2}
|
||||||
|
lappend options additional_flags=-gstatement-frontiers
|
||||||
|
|
||||||
|
if { [prepare_for_testing "failed to prepare" $binfile \
|
||||||
|
$srcfile $options] } {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
if ![runto_main] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gdb_test "frame 0" "\\s*\\#0\\s+main.*${srcfile}:36.*" "in main"
|
||||||
|
gdb_test_multiple "step" "step into test0" {
|
||||||
|
-re ".*test0.*${srcfile}:20.*$::gdb_prompt $" {
|
||||||
|
gdb_test "step" ".*line 21.*" $gdb_test_name
|
||||||
|
}
|
||||||
|
-re ".*test0.*${srcfile}:21.*$::gdb_prompt $" {
|
||||||
|
pass $gdb_test_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gdb_test "frame 1" "\\s*\\#1\\s+main.*${srcfile}:36.*" "frame1"
|
||||||
|
gdb_test "step" ".*test1.*${srcfile}:31.*" "step into test1"
|
||||||
|
gdb_test "frame 1" "\\s*\\#1.*in main.*${srcfile}:36.*" "frame2"
|
||||||
|
gdb_test "step" ".*main.*${srcfile}:37.*" "step back to main"
|
||||||
|
gdb_test "next" ".*return 0;.*" "step over test0+1"
|
||||||
|
gdb_test "frame 0" "\\s*\\#0\\s+main.*${srcfile}:38.*" "in main again"
|
||||||
39
gdb/testsuite/gdb.base/inline-entry.c
Normal file
39
gdb/testsuite/gdb.base/inline-entry.c
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/* 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 = 0;
|
||||||
|
|
||||||
|
__attribute__((noinline, noclone)) void
|
||||||
|
foo (int arg)
|
||||||
|
{
|
||||||
|
global += arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline __attribute__((always_inline)) int
|
||||||
|
bar (int val)
|
||||||
|
{
|
||||||
|
if (__builtin_expect(global == val, 0))
|
||||||
|
return 1;
|
||||||
|
foo (1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main (void)
|
||||||
|
{
|
||||||
|
if ((__builtin_expect(global, 0) && bar (1)) || bar (2))
|
||||||
|
return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
43
gdb/testsuite/gdb.base/inline-entry.exp
Normal file
43
gdb/testsuite/gdb.base/inline-entry.exp
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# 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/>.
|
||||||
|
|
||||||
|
standard_testfile .c
|
||||||
|
|
||||||
|
if { [test_compiler_info gcc*] && ![supports_statement_frontiers] } {
|
||||||
|
untested "this test needs gcc with statement frontiers"
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
global srcfile testfile
|
||||||
|
|
||||||
|
set options {debug nowarnings optimize=-O2}
|
||||||
|
if { [supports_statement_frontiers] } {
|
||||||
|
lappend options additional_flags=-gstatement-frontiers
|
||||||
|
}
|
||||||
|
|
||||||
|
if { [prepare_for_testing "failed to prepare" $binfile \
|
||||||
|
$srcfile $options] } {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
if ![runto_main] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gdb_test "break bar" ".*Breakpoint 2 at .*" "break at bar"
|
||||||
|
gdb_test "break foo" ".*Breakpoint 3 at .*" "break at foo"
|
||||||
|
gdb_test "continue" ".*Breakpoint .* bar .*" "continue to bar"
|
||||||
|
gdb_test "continue" ".*Breakpoint .* foo .*" "continue to foo"
|
||||||
|
gdb_test "continue" ".* exited .*" "continue to exit"
|
||||||
33
gdb/testsuite/gdb.cp/empty-inline.cc
Normal file
33
gdb/testsuite/gdb.cp/empty-inline.cc
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
/* 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/>. */
|
||||||
|
|
||||||
|
/* PR 25987 */
|
||||||
|
struct MyClass;
|
||||||
|
struct ptr {
|
||||||
|
MyClass* get() { return t; } /* line 21 */
|
||||||
|
MyClass* t;
|
||||||
|
};
|
||||||
|
struct MyClass { void call(); };
|
||||||
|
void MyClass::call() {
|
||||||
|
*(volatile char*)-1 = 1; /* line 26 */
|
||||||
|
}
|
||||||
|
static void intermediate(ptr p) {
|
||||||
|
p.get()->call(); /* line 29 */
|
||||||
|
}
|
||||||
|
int main() {
|
||||||
|
intermediate(ptr{new MyClass});
|
||||||
|
}
|
||||||
51
gdb/testsuite/gdb.cp/empty-inline.exp
Normal file
51
gdb/testsuite/gdb.cp/empty-inline.exp
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# 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/>.
|
||||||
|
|
||||||
|
# PR 25987
|
||||||
|
standard_testfile .cc
|
||||||
|
|
||||||
|
if { ![test_compiler_info gcc*] || ![supports_statement_frontiers] } {
|
||||||
|
untested "this test needs gcc with statement frontiers"
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
set options {c++ debug nowarnings optimize=-Og}
|
||||||
|
lappend options additional_flags=-gstatement-frontiers
|
||||||
|
if { [prepare_for_testing "failed to prepare" $testfile \
|
||||||
|
$srcfile $options] } {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
if ![runto_main] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gdb_test "bt" "\\s*\\#0\\s+main.*" "in main"
|
||||||
|
#break at the empty inline function ptr::get
|
||||||
|
gdb_test "b get" ".*" "break at get"
|
||||||
|
gdb_test "c" ".*" "continue to get"
|
||||||
|
#call frame 1 is at line 29
|
||||||
|
gdb_test "bt" [multi_line "\\s*\\#0\\s+ptr::get\[^\r\]*${srcfile}:21" \
|
||||||
|
"\\s*\\#1\\s+intermediate\[^\r\]*${srcfile}:29" \
|
||||||
|
".*"] \
|
||||||
|
"at get"
|
||||||
|
#print a local value here
|
||||||
|
gdb_test "p t" ".*(\\\$1 = \\(MyClass \\*\\) 0x|value has been optimized out).*" "print t"
|
||||||
|
gdb_test "c" ".*SIGSEGV.*" "continue to SIGSEGV"
|
||||||
|
#call frame 1 is at line 29
|
||||||
|
gdb_test "bt" [multi_line "\\s*\\#0\\s+\[^\r\]*MyClass::call\[^\r\]*${srcfile}:26" \
|
||||||
|
"\\s*\\#1\\s+0x\[^\r\]*intermediate\[^\r\]*${srcfile}:29" \
|
||||||
|
".*"] \
|
||||||
|
"at call"
|
||||||
@@ -47,8 +47,7 @@ tree_check (tree *t, int i)
|
|||||||
|
|
||||||
int __attribute__((noinline, noclone))
|
int __attribute__((noinline, noclone))
|
||||||
get_alias_set (tree *t)
|
get_alias_set (tree *t)
|
||||||
{
|
{ if (t != NULL
|
||||||
if (t != NULL
|
|
||||||
&& TREE_TYPE (t).z != 1
|
&& TREE_TYPE (t).z != 1
|
||||||
&& TREE_TYPE (t).z != 2
|
&& TREE_TYPE (t).z != 2
|
||||||
&& TREE_TYPE (t).z != 3)
|
&& TREE_TYPE (t).z != 3)
|
||||||
@@ -60,7 +59,6 @@ tree xx;
|
|||||||
|
|
||||||
int
|
int
|
||||||
main()
|
main()
|
||||||
{
|
{ get_alias_set (&xx);
|
||||||
get_alias_set (&xx); /* Beginning of main */
|
|
||||||
return 0;
|
return 0;
|
||||||
} // main
|
} // main
|
||||||
|
|||||||
@@ -15,7 +15,8 @@
|
|||||||
|
|
||||||
standard_testfile .cc
|
standard_testfile .cc
|
||||||
|
|
||||||
if {[test_compiler_info gcc*] && ![supports_statement_frontiers] } {
|
if { ![test_compiler_info gcc*] || ![supports_statement_frontiers] } {
|
||||||
|
untested "this test needs gcc with statement frontiers"
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,17 +25,8 @@ if {[test_compiler_info gcc*] && ![supports_statement_frontiers] } {
|
|||||||
proc do_test { use_header } {
|
proc do_test { use_header } {
|
||||||
global srcfile testfile
|
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}
|
set options {c++ debug nowarnings optimize=-O2}
|
||||||
if { [supports_statement_frontiers] } {
|
lappend options additional_flags=-gstatement-frontiers
|
||||||
lappend options additional_flags=-gstatement-frontiers
|
|
||||||
}
|
|
||||||
if { $use_header } {
|
if { $use_header } {
|
||||||
lappend options additional_flags=-DUSE_NEXT_INLINE_H
|
lappend options additional_flags=-DUSE_NEXT_INLINE_H
|
||||||
set executable "$testfile-with-header"
|
set executable "$testfile-with-header"
|
||||||
@@ -53,8 +45,6 @@ proc do_test { use_header } {
|
|||||||
|
|
||||||
with_test_prefix $prefix {
|
with_test_prefix $prefix {
|
||||||
|
|
||||||
set main_location [gdb_get_line_number "Beginning of main" $srcfile]
|
|
||||||
|
|
||||||
if {![runto_main]} {
|
if {![runto_main]} {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -81,129 +71,28 @@ proc do_test { use_header } {
|
|||||||
|
|
||||||
clean_restart $executable
|
clean_restart $executable
|
||||||
|
|
||||||
if ![runto $main_location qualified] {
|
if ![runto_main] {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
gdb_test "bt" "\\s*\\#0\\s+main.*" "in main"
|
gdb_test "bt" "\\s*\\#0\\s+main.*" "in main"
|
||||||
set line1 {\t\{}
|
gdb_test "step" ".*" "step into get_alias_set"
|
||||||
set line2 {\t if \(t != NULL}
|
|
||||||
gdb_test_multiple "step" "step into get_alias_set" {
|
|
||||||
-re -wrap $line1 {
|
|
||||||
gdb_test "next" $line2 $gdb_test_name
|
|
||||||
}
|
|
||||||
-re -wrap $line2 {
|
|
||||||
pass $gdb_test_name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
|
gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
|
||||||
"not in inline 1"
|
"not in inline 1"
|
||||||
|
gdb_test "next" ".*TREE_TYPE.*" "next step 1"
|
||||||
# It's possible that this first failure (when not using a header
|
|
||||||
# file) is GCC's fault, though the remaining failures would best
|
|
||||||
# be fixed by adding location views support (though it could be
|
|
||||||
# that some easier heuristic could be figured out). Still, it is
|
|
||||||
# not certain that the first failure wouldn't also be fixed by
|
|
||||||
# having location view support, so for now it is tagged as such.
|
|
||||||
set have_kfail [expr [test_compiler_info gcc*] && !$use_header]
|
|
||||||
|
|
||||||
set ok 1
|
|
||||||
gdb_test_multiple "next" "next step 1" {
|
|
||||||
-re -wrap "if \\(t->x != i\\)" {
|
|
||||||
set ok 0
|
|
||||||
send_gdb "next\n"
|
|
||||||
exp_continue
|
|
||||||
}
|
|
||||||
-re -wrap ".*TREE_TYPE.* != 1" {
|
|
||||||
if { $ok } {
|
|
||||||
pass $gdb_test_name
|
|
||||||
} else {
|
|
||||||
if { $have_kfail } {
|
|
||||||
setup_kfail "*-*-*" symtab/25507
|
|
||||||
}
|
|
||||||
fail $gdb_test_name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
|
gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
|
||||||
"not in inline 2"
|
"not in inline 2"
|
||||||
|
gdb_test "next" ".*TREE_TYPE.*" "next step 2"
|
||||||
set ok 1
|
|
||||||
gdb_test_multiple "next" "next step 2" {
|
|
||||||
-re -wrap "return x;" {
|
|
||||||
set ok 0
|
|
||||||
send_gdb "next\n"
|
|
||||||
exp_continue
|
|
||||||
}
|
|
||||||
-re -wrap ".*TREE_TYPE.* != 2" {
|
|
||||||
if { $ok } {
|
|
||||||
pass $gdb_test_name
|
|
||||||
} else {
|
|
||||||
if { $have_kfail } {
|
|
||||||
setup_kfail "*-*-*" symtab/25507
|
|
||||||
}
|
|
||||||
fail $gdb_test_name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
|
gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
|
||||||
"not in inline 3"
|
"not in inline 3"
|
||||||
|
gdb_test "next" ".*TREE_TYPE.*" "next step 3"
|
||||||
set ok 1
|
|
||||||
gdb_test_multiple "next" "next step 3" {
|
|
||||||
-re -wrap "return x;" {
|
|
||||||
set ok 0
|
|
||||||
send_gdb "next\n"
|
|
||||||
exp_continue
|
|
||||||
}
|
|
||||||
-re -wrap ".*TREE_TYPE.* != 3\\)" {
|
|
||||||
if { $ok } {
|
|
||||||
pass $gdb_test_name
|
|
||||||
} else {
|
|
||||||
if { $have_kfail } {
|
|
||||||
setup_kfail "*-*-*" symtab/25507
|
|
||||||
}
|
|
||||||
fail $gdb_test_name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
|
gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
|
||||||
"not in inline 4"
|
"not in inline 4"
|
||||||
|
gdb_test "next" "return 0.*" "next step 4"
|
||||||
set ok 1
|
|
||||||
gdb_test_multiple "next" "next step 4" {
|
|
||||||
-re -wrap "(if \\(t != NULL|\} // get_alias_set)" {
|
|
||||||
send_gdb "next\n"
|
|
||||||
exp_continue
|
|
||||||
}
|
|
||||||
-re -wrap "return x;" {
|
|
||||||
set ok 0
|
|
||||||
send_gdb "next\n"
|
|
||||||
exp_continue
|
|
||||||
}
|
|
||||||
-re -wrap "return 0.*" {
|
|
||||||
if { $ok } {
|
|
||||||
pass $gdb_test_name
|
|
||||||
} else {
|
|
||||||
if { $have_kfail } {
|
|
||||||
setup_kfail "*-*-*" symtab/25507
|
|
||||||
}
|
|
||||||
fail $gdb_test_name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gdb_test "bt" \
|
gdb_test "bt" \
|
||||||
"\\s*\\#0\\s+(main|get_alias_set)\[^\r\]*${srcfile}:.*" \
|
"\\s*\\#0\\s+(main|get_alias_set)\[^\r\]*${srcfile}:.*" \
|
||||||
"not in inline 5"
|
"not in inline 5"
|
||||||
|
|
||||||
if {!$use_header} {
|
|
||||||
# With the debug from GCC 10.x (and earlier) GDB is currently
|
|
||||||
# unable to successfully complete the following tests when we
|
|
||||||
# are not using a header file.
|
|
||||||
kfail symtab/25507 "stepping tests"
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clean_restart ${executable}
|
clean_restart ${executable}
|
||||||
|
|
||||||
if ![runto_main] {
|
if ![runto_main] {
|
||||||
@@ -220,22 +109,84 @@ proc do_test { use_header } {
|
|||||||
gdb_test "step" ".*if \\(t->x != i\\).*" "step 2"
|
gdb_test "step" ".*if \\(t->x != i\\).*" "step 2"
|
||||||
gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \
|
gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \
|
||||||
"in inline 1 pass 2"
|
"in inline 1 pass 2"
|
||||||
gdb_test "step" ".*TREE_TYPE.*" "step 3"
|
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}:.*" \
|
gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
|
||||||
"not in inline 2 pass 2"
|
"not in inline 2 pass 2"
|
||||||
gdb_test "step" ".*if \\(t->x != i\\).*" "step 4"
|
gdb_test "step" ".*if \\(t->x != i\\).*" "step 5"
|
||||||
gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \
|
gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \
|
||||||
"in inline 2 pass 2"
|
"in inline 2 pass 2"
|
||||||
gdb_test "step" ".*TREE_TYPE.*" "step 5"
|
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}:.*" \
|
gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
|
||||||
"not in inline 3 pass 2"
|
"not in inline 3 pass 2"
|
||||||
gdb_test "step" ".*if \\(t->x != i\\).*" "step 6"
|
gdb_test "step" ".*if \\(t->x != i\\).*" "step 8"
|
||||||
gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \
|
gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \
|
||||||
"in inline 3 pass 2"
|
"in inline 3 pass 2"
|
||||||
gdb_test "step" "return 0.*" "step 7"
|
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" \
|
gdb_test "bt" \
|
||||||
"\\s*\\#0\\s+(main|get_alias_set)\[^\r\]*${srcfile}:.*" \
|
"\\s*\\#0\\s+(main|get_alias_set)\[^\r\]*${srcfile}:.*" \
|
||||||
"not in inline 4 pass 2"
|
"not in inline 4 pass 2"
|
||||||
|
|
||||||
|
clean_restart ${executable}
|
||||||
|
|
||||||
|
if ![runto_main] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gdb_test "bt" "\\s*\\#0\\s+main.*" "in main pass 3"
|
||||||
|
gdb_test "step" ".*" "step into get_alias_set pass 3"
|
||||||
|
gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
|
||||||
|
"in get_alias_set pass 3"
|
||||||
|
gdb_test "step" ".*TREE_TYPE.*" "step 1 pass 3"
|
||||||
|
gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
|
||||||
|
"not in inline 1 pass 3"
|
||||||
|
gdb_test "step" ".*if \\(t->x != i\\).*" "step 2 pass 3"
|
||||||
|
gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \
|
||||||
|
"in inline 1 pass 3"
|
||||||
|
gdb_test_multiple "p t->x = 2" "change value pass 3" {
|
||||||
|
-re ".*value has been optimized out.*$::gdb_prompt $" {
|
||||||
|
gdb_test "p xx.x = 2" ".* = 2.*" $gdb_test_name
|
||||||
|
}
|
||||||
|
-re ".* = 2.*$::gdb_prompt $" {
|
||||||
|
pass $gdb_test_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gdb_test "step" ".*abort.*" "step 3, pass 3"
|
||||||
|
gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
39
gdb/testsuite/gdb.dwarf2/dw2-empty-inline-low-high.c
Normal file
39
gdb/testsuite/gdb.dwarf2/dw2-empty-inline-low-high.c
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/* 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;
|
||||||
|
|
||||||
|
int
|
||||||
|
main (void)
|
||||||
|
{ /* main decl line */
|
||||||
|
asm ("main_label: .globl main_label");
|
||||||
|
++global_var;
|
||||||
|
|
||||||
|
asm ("main_0: .globl main_0");
|
||||||
|
++global_var;
|
||||||
|
|
||||||
|
asm ("main_1: .globl main_1");
|
||||||
|
++global_var; /* foo call line */
|
||||||
|
|
||||||
|
asm ("main_2: .globl main_2");
|
||||||
|
++global_var;
|
||||||
|
|
||||||
|
asm ("main_3: .globl main_3");
|
||||||
|
++global_var;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
128
gdb/testsuite/gdb.dwarf2/dw2-empty-inline-low-high.exp
Normal file
128
gdb/testsuite/gdb.dwarf2/dw2-empty-inline-low-high.exp
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
# 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/>.
|
||||||
|
|
||||||
|
# Define an inline function `foo` within the function `main`. The
|
||||||
|
# function `foo` uses DW_AT_low_pc and DW_AT_high_pc to define its
|
||||||
|
# range, except that DW_AT_high_pc is the constant 0.
|
||||||
|
#
|
||||||
|
# This should indicate that there is no code associated with `foo`,
|
||||||
|
# however, with gcc versions at least between 8.x and 14.x (latest at
|
||||||
|
# the time of writing this comment), it is observed that when these
|
||||||
|
# empty inline functions are created, if GDB stops at the address
|
||||||
|
# given in DW_AT_low_pc, then locals associated with the inline
|
||||||
|
# function can usually be read.
|
||||||
|
#
|
||||||
|
# At the very least, stopping at the location of the inline function
|
||||||
|
# means that the user can place a breakpoint on the inline function
|
||||||
|
# and have GDB stop in a suitable location, that alone is helpful.
|
||||||
|
#
|
||||||
|
# This test defines an inline function, places a breakpoint, and then
|
||||||
|
# runs and expects GDB to stop, and report the stop as being inside
|
||||||
|
# the inline function.
|
||||||
|
#
|
||||||
|
# We then check that the next outer frame is `main` as expected, and
|
||||||
|
# that the block for `foo` has been extended to a single byte, which
|
||||||
|
# is how GDB gives the previously empty block some range.
|
||||||
|
|
||||||
|
load_lib dwarf.exp
|
||||||
|
|
||||||
|
require dwarf2_support
|
||||||
|
|
||||||
|
standard_testfile .c .S
|
||||||
|
|
||||||
|
# Lines we reference in the generated DWARF.
|
||||||
|
set main_decl_line [gdb_get_line_number "main decl line"]
|
||||||
|
set foo_call_line [gdb_get_line_number "foo call line"]
|
||||||
|
|
||||||
|
get_func_info main
|
||||||
|
|
||||||
|
set asm_file [standard_output_file $srcfile2]
|
||||||
|
Dwarf::assemble $asm_file {
|
||||||
|
upvar entry_label entry_label
|
||||||
|
|
||||||
|
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 0 data4}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lines {version 2} lines_table {
|
||||||
|
include_dir "$::srcdir/$::subdir"
|
||||||
|
file_name "$::srcfile" 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if {[prepare_for_testing "failed to prepare" $testfile \
|
||||||
|
[list $srcfile $asm_file] {nodebug}]} {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if {![runto_main]} {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gdb_breakpoint foo
|
||||||
|
gdb_test "continue" \
|
||||||
|
"Breakpoint $decimal, $hex in foo \\(\\)" \
|
||||||
|
"continue to b/p in foo"
|
||||||
|
|
||||||
|
set foo_start [get_hexadecimal_valueof "&main_1" "*UNKNOWN*" \
|
||||||
|
"get address of foo start"]
|
||||||
|
set foo_end [get_hexadecimal_valueof "&main_1 + 1" "*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 some content"
|
||||||
|
|
||||||
|
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"
|
||||||
54
gdb/testsuite/gdb.dwarf2/dw2-empty-inline-ranges.c
Normal file
54
gdb/testsuite/gdb.dwarf2/dw2-empty-inline-ranges.c
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
/* 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;
|
||||||
|
|
||||||
|
int
|
||||||
|
main (void)
|
||||||
|
{ /* main decl line */
|
||||||
|
asm ("main_label: .globl main_label");
|
||||||
|
++global_var;
|
||||||
|
|
||||||
|
asm ("main_0: .globl main_0");
|
||||||
|
++global_var;
|
||||||
|
|
||||||
|
asm ("main_1: .globl main_1");
|
||||||
|
++global_var; /* foo call line */
|
||||||
|
|
||||||
|
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_9");
|
||||||
|
++global_var;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
260
gdb/testsuite/gdb.dwarf2/dw2-empty-inline-ranges.exp
Normal file
260
gdb/testsuite/gdb.dwarf2/dw2-empty-inline-ranges.exp
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
# 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/>.
|
||||||
|
|
||||||
|
# Define an inline function `foo` within the function `main`. The
|
||||||
|
# function `foo` uses DW_AT_ranges to define its ranges. One of the
|
||||||
|
# sub-ranges for foo will be empty.
|
||||||
|
#
|
||||||
|
# An empty sub-rnage should indicate that there is no code associated
|
||||||
|
# with `foo` at that address, however, with gcc versions at least
|
||||||
|
# between 8.x and 14.x (latest at the time of writing this comment),
|
||||||
|
# it is observed that when these empty sub-ranges are created for an
|
||||||
|
# inline function, if GDB treats the sub-range as non-empty, and stops
|
||||||
|
# at that location, then this generally gives a better debug
|
||||||
|
# experience. It is often still possible to read local variables at
|
||||||
|
# that address.
|
||||||
|
#
|
||||||
|
# This function defines an inline function, places a breakpoint on its
|
||||||
|
# entry-pc, and then runs and expects GDB to stop, and report the stop
|
||||||
|
# as being inside the inline function.
|
||||||
|
#
|
||||||
|
# We then check that the next outer frame is `main` as expected, and
|
||||||
|
# that the block for `foo` has the expected sub-ranges.
|
||||||
|
#
|
||||||
|
# We compile a variety of different configurations, broadly there are
|
||||||
|
# two variables, the location of the empty sub-range, and whether the
|
||||||
|
# entry-pc points at the empty sub-range or not.
|
||||||
|
#
|
||||||
|
# The the empty sub-range location, the empty sub-range can be the
|
||||||
|
# sub-range at the lowest address, highest address, or can be
|
||||||
|
# somewhere between a blocks low and high addresses.
|
||||||
|
|
||||||
|
load_lib dwarf.exp
|
||||||
|
|
||||||
|
require dwarf2_support
|
||||||
|
|
||||||
|
standard_testfile .c .S
|
||||||
|
|
||||||
|
# Lines we reference in the generated DWARF.
|
||||||
|
set main_decl_line [gdb_get_line_number "main decl line"]
|
||||||
|
set foo_call_line [gdb_get_line_number "foo call line"]
|
||||||
|
|
||||||
|
get_func_info main
|
||||||
|
|
||||||
|
# Compile the source file and load the executable into GDB so we can
|
||||||
|
# extract some addresses needed for creating the DWARF.
|
||||||
|
if { [prepare_for_testing "failed to prepare" ${testfile} \
|
||||||
|
[list ${srcfile}]] } {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
if {![runto_main]} {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Some addresses that we need when generating the DWARF.
|
||||||
|
for { set i 0 } { $i < 9 } { incr i } {
|
||||||
|
set main_$i [get_hexadecimal_valueof "&main_$i" "UNKNOWN" \
|
||||||
|
"get address for main_$i"]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create the DWARF assembler file into ASM_FILE. Using DWARF_VERSION
|
||||||
|
# to define which style of ranges to create. FUNC_RANGES is a list of
|
||||||
|
# 6 entries, each of which is an address, used to create the ranges
|
||||||
|
# for the inline function DIE. The ENTRY_PC is also an address and is
|
||||||
|
# used for the DW_AT_entry_pc of the inlined function.
|
||||||
|
proc write_asm_file { asm_file dwarf_version func_ranges entry_pc } {
|
||||||
|
Dwarf::assemble $asm_file {
|
||||||
|
upvar entry_label entry_label
|
||||||
|
upvar dwarf_version dwarf_version
|
||||||
|
upvar func_ranges func_ranges
|
||||||
|
upvar entry_pc entry_pc
|
||||||
|
|
||||||
|
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}
|
||||||
|
{entry_pc $entry_pc addr}
|
||||||
|
{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 [lindex $func_ranges 0] [lindex $func_ranges 1]
|
||||||
|
start_end [lindex $func_ranges 2] [lindex $func_ranges 3]
|
||||||
|
start_end [lindex $func_ranges 4] [lindex $func_ranges 5]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ranges { } {
|
||||||
|
ranges_label: sequence {
|
||||||
|
range [lindex $func_ranges 0] [lindex $func_ranges 1]
|
||||||
|
range [lindex $func_ranges 2] [lindex $func_ranges 3]
|
||||||
|
range [lindex $func_ranges 4] [lindex $func_ranges 5]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Gobal used to give each generated binary a unique name.
|
||||||
|
set test_id 0
|
||||||
|
|
||||||
|
proc run_test { dwarf_version empty_loc entry_pc_type } {
|
||||||
|
incr ::test_id
|
||||||
|
|
||||||
|
set this_testfile $::testfile-$::test_id
|
||||||
|
|
||||||
|
set asm_file [standard_output_file $this_testfile.S]
|
||||||
|
|
||||||
|
if { $empty_loc eq "start" } {
|
||||||
|
set ranges [list \
|
||||||
|
$::main_1 $::main_1 \
|
||||||
|
$::main_3 $::main_4 \
|
||||||
|
$::main_6 $::main_7]
|
||||||
|
set entry_pc_choices [list $::main_1 $::main_3]
|
||||||
|
} elseif { $empty_loc eq "middle" } {
|
||||||
|
set ranges [list \
|
||||||
|
$::main_1 $::main_2 \
|
||||||
|
$::main_4 $::main_4 \
|
||||||
|
$::main_6 $::main_7]
|
||||||
|
set entry_pc_choices [list $::main_4 $::main_1]
|
||||||
|
} elseif { $empty_loc eq "end" } {
|
||||||
|
set ranges [list \
|
||||||
|
$::main_1 $::main_2 \
|
||||||
|
$::main_4 $::main_5 \
|
||||||
|
$::main_7 $::main_7]
|
||||||
|
set entry_pc_choices [list $::main_7 $::main_1]
|
||||||
|
} else {
|
||||||
|
error "unknown location for empty range '$empty_loc'"
|
||||||
|
}
|
||||||
|
|
||||||
|
if { $entry_pc_type eq "empty" } {
|
||||||
|
set entry_pc [lindex $entry_pc_choices 0]
|
||||||
|
} elseif { $entry_pc_type eq "non_empty" } {
|
||||||
|
set entry_pc [lindex $entry_pc_choices 1]
|
||||||
|
} else {
|
||||||
|
error "unknown entry-pc type '$entry_pc_type'"
|
||||||
|
}
|
||||||
|
|
||||||
|
write_asm_file $asm_file $dwarf_version $ranges $entry_pc
|
||||||
|
|
||||||
|
if {[prepare_for_testing "failed to prepare" $this_testfile \
|
||||||
|
[list $::srcfile $asm_file] {nodebug}]} {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if {![runto_main]} {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
# Continue until we stop in 'foo'.
|
||||||
|
gdb_breakpoint foo
|
||||||
|
gdb_test "continue" \
|
||||||
|
"Breakpoint $::decimal, $::hex in foo \\(\\)" \
|
||||||
|
"continue to b/p in foo"
|
||||||
|
|
||||||
|
# Check we stopped at the entry-pc.
|
||||||
|
set pc [get_hexadecimal_valueof "\$pc" "*UNKNOWN*" \
|
||||||
|
"get \$pc at breakpoint"]
|
||||||
|
gdb_assert { $pc == $entry_pc } "stopped at entry-pc"
|
||||||
|
|
||||||
|
# The block's expected overall low/high addresses.
|
||||||
|
set block_start [lindex $ranges 0]
|
||||||
|
set block_end [lindex $ranges 5]
|
||||||
|
|
||||||
|
# Setup variables r{0,1,2}s, r{0,1,2}e, to represent ranges start
|
||||||
|
# and end addresses. These are extracted from the RANGES
|
||||||
|
# variable. However, RANGES includes the empty ranges, so spot
|
||||||
|
# the empty ranges and update the end address as GDB does.
|
||||||
|
#
|
||||||
|
# Also, if the empty range is at the end of the block, then the
|
||||||
|
# block's overall end address also needs adjusting.
|
||||||
|
for { set i 0 } { $i < 3 } { incr i } {
|
||||||
|
set start [lindex $ranges [expr $i * 2]]
|
||||||
|
set end [lindex $ranges [expr $i * 2 + 1]]
|
||||||
|
if { $start == $end } {
|
||||||
|
set end [format "0x%x" [expr $end + 1]]
|
||||||
|
}
|
||||||
|
if { $block_end == $start } {
|
||||||
|
set block_end $end
|
||||||
|
}
|
||||||
|
set r${i}s $start
|
||||||
|
set r${i}e $end
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check the block 'foo' has the expected ranges.
|
||||||
|
gdb_test "maintenance info blocks" \
|
||||||
|
[multi_line \
|
||||||
|
"\\\[\\(block \\*\\) $::hex\\\] $block_start\\.\\.$block_end" \
|
||||||
|
" entry pc: $entry_pc" \
|
||||||
|
" inline function: foo" \
|
||||||
|
" symbol count: $::decimal" \
|
||||||
|
" address ranges:" \
|
||||||
|
" $r0s\\.\\.$r0e" \
|
||||||
|
" $r1s\\.\\.$r1e" \
|
||||||
|
" $r2s\\.\\.$r2e"] \
|
||||||
|
"block for foo has some content"
|
||||||
|
|
||||||
|
# Check the outer frame is 'main' as expected.
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach_with_prefix dwarf_version { 4 5 } {
|
||||||
|
foreach_with_prefix empty_loc { start middle end } {
|
||||||
|
foreach_with_prefix entry_pc_type { empty non_empty } {
|
||||||
|
run_test $dwarf_version $empty_loc $entry_pc_type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
79
gdb/testsuite/gdb.dwarf2/dw2-inline-bt.c
Normal file
79
gdb/testsuite/gdb.dwarf2/dw2-inline-bt.c
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
/* 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/>. */
|
||||||
|
|
||||||
|
/* Used to insert labels with which we can build a fake line table. */
|
||||||
|
#define LL(N) asm ("line_label_" #N ": .globl line_label_" #N)
|
||||||
|
|
||||||
|
/* The following non-compiled code exists for the generated line table to
|
||||||
|
point at. */
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
|
||||||
|
volatile int global = 0;
|
||||||
|
|
||||||
|
__attribute__((noinline, noclone)) void
|
||||||
|
foo (int arg)
|
||||||
|
{ /* foo prologue */
|
||||||
|
asm ("");
|
||||||
|
global += arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline __attribute__((always_inline)) int
|
||||||
|
bar (void)
|
||||||
|
{
|
||||||
|
return 1; /* bar body */
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main (void)
|
||||||
|
{ /* main prologue */
|
||||||
|
foo (bar ()); /* call line */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* 0 */
|
||||||
|
|
||||||
|
volatile int var;
|
||||||
|
|
||||||
|
/* Generate some code to take up some space. */
|
||||||
|
#define FILLER do { \
|
||||||
|
var = 99; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
void
|
||||||
|
func (void)
|
||||||
|
{
|
||||||
|
asm ("func_label: .globl func_label");
|
||||||
|
FILLER;
|
||||||
|
LL (1);
|
||||||
|
FILLER;
|
||||||
|
LL (2);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main (void)
|
||||||
|
{
|
||||||
|
asm ("main_label: .globl main_label");
|
||||||
|
FILLER;
|
||||||
|
LL (4);
|
||||||
|
FILLER;
|
||||||
|
LL (5);
|
||||||
|
func ();
|
||||||
|
FILLER;
|
||||||
|
LL (6);
|
||||||
|
FILLER;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
227
gdb/testsuite/gdb.dwarf2/dw2-inline-bt.exp
Normal file
227
gdb/testsuite/gdb.dwarf2/dw2-inline-bt.exp
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
# 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/>.
|
||||||
|
|
||||||
|
# Setup a line table where:
|
||||||
|
#
|
||||||
|
# | | | | Func | Func | Func |
|
||||||
|
# | Addr | Line | Stmt | main | foo | bar |
|
||||||
|
# |------|------|------|------|------|------|
|
||||||
|
# | 1 | 28 | Y | | X | |
|
||||||
|
# | 2 | 30 | Y | | X | |
|
||||||
|
# | 3 | 31 | N | | X | |
|
||||||
|
# | 4 | 41 | Y | X | | |
|
||||||
|
# | 5 | 42 | Y | X | | |
|
||||||
|
# | 5 | 36 | Y | X | | X |
|
||||||
|
# | 5 | 42 | N | X | | |
|
||||||
|
# | 6 | 43 | Y | X | | |
|
||||||
|
# | 7 | END | Y | X | | |
|
||||||
|
# |------|------|------|------|------|------|
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# The function 'bar' is inline within 'main' while 'foo' is not
|
||||||
|
# inline. Function 'foo' is called from 'main' immediately after the
|
||||||
|
# inlined call to bar. The C code can be found within a '#if 0' block
|
||||||
|
# inside the test's .c file. The line table is similar to that
|
||||||
|
# generated by compiling the source code at optimisation level -Og.
|
||||||
|
#
|
||||||
|
# Place a breakpoint in 'foo', run to the breakpoint, and then examine
|
||||||
|
# frame #1, that is, the frame for 'main'. At one point, bugs in GDB
|
||||||
|
# meant that the user would be shown the inline line from 'bar' rather
|
||||||
|
# than the line from 'main'. In the example above the user expects to
|
||||||
|
# see line 42 from 'main', but instead would be shown line '36'.
|
||||||
|
#
|
||||||
|
# The cause of the bug is this: to find the line for frame #1 GDB
|
||||||
|
# first finds an address in frame #1 by unwinding frame #0. This
|
||||||
|
# provides the return address in frame #1. GDB subtracts 1 from this
|
||||||
|
# address and looks for a line matching this address. In this case
|
||||||
|
# that would be line 42.
|
||||||
|
#
|
||||||
|
# However, buggy GDB would then scan backward through the line table
|
||||||
|
# looking for a line table entry that is marked as is-stmt. In this
|
||||||
|
# case, the first matching entry is that for line 36, and so that is
|
||||||
|
# what is reported. This backward scan makes sense for frame #0, but
|
||||||
|
# not for outer frames.
|
||||||
|
#
|
||||||
|
# This has now been fixed to prevent the backward scan for frames
|
||||||
|
# other than frame #0.
|
||||||
|
|
||||||
|
load_lib dwarf.exp
|
||||||
|
|
||||||
|
# This test can only be run on targets which support DWARF-2 and use
|
||||||
|
# gas.
|
||||||
|
require dwarf2_support
|
||||||
|
|
||||||
|
standard_testfile .c .S
|
||||||
|
|
||||||
|
# Lines in the source code that we need to reference.
|
||||||
|
set call_line [gdb_get_line_number "call line" $srcfile]
|
||||||
|
set foo_prologue [gdb_get_line_number "foo prologue" $srcfile]
|
||||||
|
set main_prologue [gdb_get_line_number "main prologue" $srcfile]
|
||||||
|
set bar_body [gdb_get_line_number "bar body" $srcfile]
|
||||||
|
|
||||||
|
# We need the return address in 'main' after the call to 'func' so
|
||||||
|
# that we can build the line table. Compile the .c file with debug,
|
||||||
|
# and figure out the address. This works so long as the only
|
||||||
|
# difference in build flags between this compile and the later compile
|
||||||
|
# is that this is debug on, and the later compile is debug off.
|
||||||
|
if { [prepare_for_testing "failed to prepare" $testfile $srcfile] } {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if {![runto func]} {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
set func_call_line [gdb_get_line_number "func ();"]
|
||||||
|
gdb_test "up" \
|
||||||
|
[multi_line \
|
||||||
|
"#1\\s*$hex in main \\(\\) at \[^\r\n\]+" \
|
||||||
|
"$func_call_line\\s+ func \\(\\);"] \
|
||||||
|
"move up from func to main"
|
||||||
|
|
||||||
|
set return_addr_in_main [get_hexadecimal_valueof "\$pc" "*UNKNOWN*" \
|
||||||
|
"get pc after return from func"]
|
||||||
|
|
||||||
|
# Prepare and run the test. Placed into a proc in case we ever want
|
||||||
|
# to parameterise this test in the future.
|
||||||
|
|
||||||
|
proc do_test { } {
|
||||||
|
set build_options {nodebug}
|
||||||
|
|
||||||
|
set asm_file [standard_output_file $::srcfile2]
|
||||||
|
Dwarf::assemble $asm_file {
|
||||||
|
upvar build_options build_options
|
||||||
|
|
||||||
|
declare_labels lines_label foo_label bar_label
|
||||||
|
|
||||||
|
get_func_info main $build_options
|
||||||
|
get_func_info func $build_options
|
||||||
|
|
||||||
|
cu {} {
|
||||||
|
compile_unit {
|
||||||
|
{producer "gcc" }
|
||||||
|
{language @DW_LANG_C}
|
||||||
|
{name $::srcfile}
|
||||||
|
{low_pc 0 addr}
|
||||||
|
{stmt_list ${lines_label} DW_FORM_sec_offset}
|
||||||
|
} {
|
||||||
|
foo_label: subprogram {
|
||||||
|
{external 1 flag}
|
||||||
|
{name foo}
|
||||||
|
{low_pc $func_start addr}
|
||||||
|
{high_pc "$func_start + $func_len" addr}
|
||||||
|
}
|
||||||
|
bar_label: subprogram {
|
||||||
|
{external 1 flag}
|
||||||
|
{name bar}
|
||||||
|
{inline 3 data1}
|
||||||
|
}
|
||||||
|
subprogram {
|
||||||
|
{external 1 flag}
|
||||||
|
{name main}
|
||||||
|
{low_pc $main_start addr}
|
||||||
|
{high_pc "$main_start + $main_len" addr}
|
||||||
|
} {
|
||||||
|
inlined_subroutine {
|
||||||
|
{abstract_origin %$bar_label}
|
||||||
|
{low_pc line_label_4 addr}
|
||||||
|
{high_pc line_label_5 addr}
|
||||||
|
{call_file 1 data1}
|
||||||
|
{call_line $::call_line data1}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lines {version 2 default_is_stmt 1} lines_label {
|
||||||
|
include_dir "${::srcdir}/${::subdir}"
|
||||||
|
file_name "$::srcfile" 1
|
||||||
|
|
||||||
|
program {
|
||||||
|
DW_LNE_set_address func
|
||||||
|
line $::foo_prologue
|
||||||
|
DW_LNS_copy
|
||||||
|
|
||||||
|
DW_LNE_set_address line_label_1
|
||||||
|
DW_LNS_advance_line 2
|
||||||
|
DW_LNS_copy
|
||||||
|
|
||||||
|
DW_LNE_set_address line_label_2
|
||||||
|
DW_LNS_advance_line 1
|
||||||
|
DW_LNS_negate_stmt
|
||||||
|
DW_LNS_copy
|
||||||
|
|
||||||
|
DW_LNE_set_address main
|
||||||
|
DW_LNS_advance_line [expr $::main_prologue - $::foo_prologue - 3]
|
||||||
|
DW_LNS_negate_stmt
|
||||||
|
DW_LNS_copy
|
||||||
|
|
||||||
|
DW_LNE_set_address line_label_4
|
||||||
|
DW_LNS_advance_line 1
|
||||||
|
DW_LNS_copy
|
||||||
|
|
||||||
|
DW_LNE_set_address line_label_4
|
||||||
|
line $::bar_body
|
||||||
|
DW_LNS_copy
|
||||||
|
|
||||||
|
DW_LNE_set_address line_label_4
|
||||||
|
line $::call_line
|
||||||
|
DW_LNS_negate_stmt
|
||||||
|
DW_LNS_copy
|
||||||
|
|
||||||
|
# Skip line_label_5, this is used as the end of `bar`
|
||||||
|
# the inline function.
|
||||||
|
|
||||||
|
DW_LNE_set_address $::return_addr_in_main
|
||||||
|
DW_LNS_advance_line 1
|
||||||
|
DW_LNS_negate_stmt
|
||||||
|
DW_LNS_copy
|
||||||
|
|
||||||
|
DW_LNE_set_address "$main_start + $main_len"
|
||||||
|
DW_LNE_end_sequence
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if { [prepare_for_testing "failed to prepare" $::testfile \
|
||||||
|
[list $::srcfile $asm_file] $build_options] } {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ![runto foo] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
# For this backtrace we don't really care which line number in foo
|
||||||
|
# is reported. We might get different line numbers depending on
|
||||||
|
# how the architectures skip prologue function works. This test
|
||||||
|
# is all about how frame #1 is reported.
|
||||||
|
set foo_body_1 [expr $::foo_prologue + 1]
|
||||||
|
set foo_body_2 [expr $::foo_prologue + 2]
|
||||||
|
gdb_test "bt" \
|
||||||
|
[multi_line \
|
||||||
|
"^#0\\s+foo \\(\\) at \[^\r\n\]+$::srcfile:(?:$::foo_prologue|$foo_body_1|$foo_body_2)" \
|
||||||
|
"#1\\s+$::hex in main \\(\\) at \[^\r\n\]+$::srcfile:$::call_line"] \
|
||||||
|
"backtrace show correct line number in main"
|
||||||
|
|
||||||
|
gdb_test "frame 1" \
|
||||||
|
[multi_line \
|
||||||
|
"^#1\\s+$::hex in main \\(\\) at \[^\r\n\]+$::srcfile:$::call_line" \
|
||||||
|
"$::call_line\\s+foo \\(bar \\(\\)\\);\[^\r\n\]+"] \
|
||||||
|
"correct lines are shown for frame 1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run the test.
|
||||||
|
do_test
|
||||||
@@ -168,7 +168,7 @@ gdb_test_multiple "maint info line-table gdb.dwarf2/dw2-ranges-base.c" \
|
|||||||
-re ".*linetable: \\(\\(struct linetable \\*\\) 0x0\\):\r\nNo line table.\r\n" {
|
-re ".*linetable: \\(\\(struct linetable \\*\\) 0x0\\):\r\nNo line table.\r\n" {
|
||||||
exp_continue
|
exp_continue
|
||||||
}
|
}
|
||||||
-re ".*linetable: \\(\\(struct linetable \\*\\) $hex\\):\r\nINDEX\[ \t\]+LINE\[ \t\]+REL-ADDRESS\[ \t\]+UNREL-ADDRESS\[ \t\]+IS-STMT\[ \t\]PROLOGUE-END\[ \t\]EPILOGUE-BEGIN *\r\n" {
|
-re ".*linetable: \\(\\(struct linetable \\*\\) $hex\\):\r\nINDEX\[ \t\]+LINE\[ \t\]+REL-ADDRESS\[ \t\]+UNREL-ADDRESS\[ \t\]+IS-STMT\[ \t\]IS-WEAK\[ \t\]PROLOGUE-END\[ \t\]EPILOGUE-BEGIN *\r\n" {
|
||||||
exp_continue
|
exp_continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,9 @@ foreach foo {foo_1 foo_2 foo_3 foo_4 foo_5 foo_6} {
|
|||||||
"get address for $foo label"]
|
"get address for $foo label"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set foo_3_end [get_hexadecimal_valueof "&foo_3 + 1" "UNKNOWN" \
|
||||||
|
"get address for 'foo_3 + 1'"]
|
||||||
|
|
||||||
# Some line numbers needed in the generated DWARF.
|
# Some line numbers needed in the generated DWARF.
|
||||||
set foo_decl_line [gdb_get_line_number "foo decl line"]
|
set foo_decl_line [gdb_get_line_number "foo decl line"]
|
||||||
set bar_call_line [gdb_get_line_number "bar call line"]
|
set bar_call_line [gdb_get_line_number "bar call line"]
|
||||||
@@ -85,24 +88,40 @@ if [is_ilp32_target] {
|
|||||||
# generated which covers some parts of the inlined function. This
|
# generated which covers some parts of the inlined function. This
|
||||||
# makes most sense when being tested with the 'foo_6' label, as that
|
# makes most sense when being tested with the 'foo_6' label, as that
|
||||||
# label is all about handling the end of the inline function case.
|
# label is all about handling the end of the inline function case.
|
||||||
|
#
|
||||||
|
# The PRODUCER is the string used to control the DW_AT_producer string
|
||||||
|
# in the CU. When PRODUCER is 'gcc' then a string is used that
|
||||||
|
# represents the gcc compiler. When PRODUCER is 'other' then a string
|
||||||
|
# that will not be interpreted as gcc is used. The gcc compiler will
|
||||||
|
# sometimes generate empty ranges for inline functions (from at least
|
||||||
|
# gcc 8.x through to the currently latest release 14.x), and so GDB
|
||||||
|
# has code in place to convert empty ranges to non-empty. This fix is
|
||||||
|
# not applied to other compilers at this time.
|
||||||
|
|
||||||
proc run_test { entry_label dwarf_version with_line_table } {
|
proc run_test { producer entry_label dwarf_version with_line_table } {
|
||||||
set dw_testname "${::testfile}-${dwarf_version}-${entry_label}"
|
set dw_testname "${::testfile}-${producer}-${dwarf_version}-${entry_label}"
|
||||||
|
|
||||||
if { $with_line_table } {
|
if { $with_line_table } {
|
||||||
set dw_testname ${dw_testname}-lt
|
set dw_testname ${dw_testname}-lt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if { $producer eq "other" } {
|
||||||
|
set producer_str "ACME C 1.0.0"
|
||||||
|
} else {
|
||||||
|
set producer_str "GNU C 10.0.0"
|
||||||
|
}
|
||||||
|
|
||||||
set asm_file [standard_output_file "${dw_testname}.S"]
|
set asm_file [standard_output_file "${dw_testname}.S"]
|
||||||
Dwarf::assemble $asm_file {
|
Dwarf::assemble $asm_file {
|
||||||
upvar dwarf_version dwarf_version
|
upvar dwarf_version dwarf_version
|
||||||
upvar entry_label entry_label
|
upvar entry_label entry_label
|
||||||
|
upvar producer_str producer_str
|
||||||
|
|
||||||
declare_labels lines_table inline_func ranges_label
|
declare_labels lines_table inline_func ranges_label
|
||||||
|
|
||||||
cu { version $dwarf_version } {
|
cu { version $dwarf_version } {
|
||||||
compile_unit {
|
compile_unit {
|
||||||
{producer "gcc"}
|
{producer $producer_str}
|
||||||
{language @DW_LANG_C}
|
{language @DW_LANG_C}
|
||||||
{name $::srcfile}
|
{name $::srcfile}
|
||||||
{comp_dir /tmp}
|
{comp_dir /tmp}
|
||||||
@@ -157,6 +176,10 @@ proc run_test { entry_label dwarf_version with_line_table } {
|
|||||||
line 2
|
line 2
|
||||||
DW_LNS_copy
|
DW_LNS_copy
|
||||||
|
|
||||||
|
DW_LNE_set_address foo_3
|
||||||
|
line 3
|
||||||
|
DW_LNS_copy
|
||||||
|
|
||||||
DW_LNE_set_address foo_6
|
DW_LNE_set_address foo_6
|
||||||
line 10
|
line 10
|
||||||
DW_LNS_copy
|
DW_LNS_copy
|
||||||
@@ -206,6 +229,16 @@ proc run_test { entry_label dwarf_version with_line_table } {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if { $producer eq "gcc" } {
|
||||||
|
set entry_pc $::foo_3
|
||||||
|
set empty_range_re "\r\n $::foo_3\\.\\.$::foo_3_end"
|
||||||
|
set line_num 3
|
||||||
|
} else {
|
||||||
|
set entry_pc $::foo_1
|
||||||
|
set empty_range_re ""
|
||||||
|
set line_num 1
|
||||||
|
}
|
||||||
|
|
||||||
# Place a breakpoint on `bar` and run to the breakpoint. Use
|
# Place a breakpoint on `bar` and run to the breakpoint. Use
|
||||||
# gdb_test as we want full pattern matching against the stop
|
# gdb_test as we want full pattern matching against the stop
|
||||||
# location.
|
# location.
|
||||||
@@ -215,8 +248,8 @@ proc run_test { entry_label dwarf_version with_line_table } {
|
|||||||
if { $with_line_table } {
|
if { $with_line_table } {
|
||||||
set re \
|
set re \
|
||||||
[multi_line \
|
[multi_line \
|
||||||
"Breakpoint $::decimal, bar \\(\\) at \[^\r\n\]+/$::srcfile:1" \
|
"Breakpoint $::decimal, bar \\(\\) at \[^\r\n\]+/$::srcfile:$line_num" \
|
||||||
"1\\s+\[^\r\n\]+"]
|
"$line_num\\s+\[^\r\n\]+"]
|
||||||
} else {
|
} else {
|
||||||
set re "Breakpoint $::decimal, $::hex in bar \\(\\)"
|
set re "Breakpoint $::decimal, $::hex in bar \\(\\)"
|
||||||
}
|
}
|
||||||
@@ -230,21 +263,23 @@ proc run_test { entry_label dwarf_version with_line_table } {
|
|||||||
gdb_test "maint info blocks" \
|
gdb_test "maint info blocks" \
|
||||||
[multi_line \
|
[multi_line \
|
||||||
"\\\[\\(block \\*\\) $::hex\\\] $::foo_1\\.\\.$::foo_6" \
|
"\\\[\\(block \\*\\) $::hex\\\] $::foo_1\\.\\.$::foo_6" \
|
||||||
" entry pc: $::foo_1" \
|
" entry pc: $entry_pc" \
|
||||||
" inline function: bar" \
|
" inline function: bar" \
|
||||||
" symbol count: $::decimal" \
|
" symbol count: $::decimal" \
|
||||||
" address ranges:" \
|
" address ranges:$empty_range_re" \
|
||||||
" $::foo_1\\.\\.$::foo_2" \
|
" $::foo_1\\.\\.$::foo_2" \
|
||||||
" $::foo_5\\.\\.$::foo_6"]
|
" $::foo_5\\.\\.$::foo_6"]
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach_with_prefix dwarf_version { 4 5 } {
|
foreach_with_prefix producer { other gcc } {
|
||||||
# Test various labels without any line table present.
|
foreach_with_prefix dwarf_version { 4 5 } {
|
||||||
foreach_with_prefix entry_label { foo_3 foo_4 foo_2 foo_6 } {
|
# Test various labels without any line table present.
|
||||||
run_test $entry_label $dwarf_version false
|
foreach_with_prefix entry_label { foo_3 foo_4 foo_2 foo_6 } {
|
||||||
}
|
run_test $producer $entry_label $dwarf_version false
|
||||||
|
}
|
||||||
|
|
||||||
# Now test what happens if we use the end address of the block,
|
# Now test what happens if we use the end address of the block,
|
||||||
# but also supply a line table. Does GDB do anything different?
|
# but also supply a line table. Does GDB do anything different?
|
||||||
run_test foo_6 $dwarf_version true
|
run_test $producer foo_6 $dwarf_version true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
65
gdb/testsuite/gdb.opt/empty-inline-cxx.cc
Normal file
65
gdb/testsuite/gdb.opt/empty-inline-cxx.cc
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/* 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/>. */
|
||||||
|
|
||||||
|
#include "attributes.h"
|
||||||
|
|
||||||
|
/* A global to do some work on. This being volatile is important. Without
|
||||||
|
this the compiler might optimise the whole program away. */
|
||||||
|
volatile int global = 0;
|
||||||
|
|
||||||
|
__attribute__((noinline)) ATTRIBUTE_NOCLONE void
|
||||||
|
breakpt ()
|
||||||
|
{
|
||||||
|
/* Some filler work. */
|
||||||
|
global++;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MyClass;
|
||||||
|
|
||||||
|
struct ptr
|
||||||
|
{
|
||||||
|
/* The following line is a single line to aid matching in the test
|
||||||
|
script. Sometimes the DWARF will point GDB at the '{' and sometimes
|
||||||
|
at the body of the function. We don't really care for this test, so
|
||||||
|
placing everything on one line removes this variability. */
|
||||||
|
MyClass* get_myclass () { return t; }
|
||||||
|
|
||||||
|
MyClass* t;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MyClass
|
||||||
|
{
|
||||||
|
void call();
|
||||||
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
MyClass::call ()
|
||||||
|
{
|
||||||
|
breakpt (); /* Final breakpoint. */
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
intermediate (ptr p)
|
||||||
|
{
|
||||||
|
p.get_myclass ()->call ();
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main ()
|
||||||
|
{
|
||||||
|
intermediate (ptr {new MyClass});
|
||||||
|
}
|
||||||
95
gdb/testsuite/gdb.opt/empty-inline-cxx.exp
Normal file
95
gdb/testsuite/gdb.opt/empty-inline-cxx.exp
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
# 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/>.
|
||||||
|
|
||||||
|
standard_testfile .cc
|
||||||
|
|
||||||
|
require {expr ![test_compiler_info gcc* c++] \
|
||||||
|
|| [supports_statement_frontiers] }
|
||||||
|
|
||||||
|
set options {c++ debug optimize=-Og}
|
||||||
|
lappend_include_file options $srcdir/lib/attributes.h
|
||||||
|
if {[supports_statement_frontiers]} {
|
||||||
|
lappend options additional_flags=-gstatement-frontiers
|
||||||
|
}
|
||||||
|
|
||||||
|
# Some line numbers we need for the test.
|
||||||
|
set get_myclass_line [gdb_get_line_number "MyClass* get_myclass ()"]
|
||||||
|
set call_get_line [gdb_get_line_number "p.get_myclass ()"]
|
||||||
|
set final_bp_line [gdb_get_line_number "Final breakpoint"]
|
||||||
|
|
||||||
|
# Build the test executable adding "-OPT_LEVEL" to the compilation
|
||||||
|
# flags. The break on the small function which is likely to have been
|
||||||
|
# inlined, check we stop where we expect, and that the backtrace looks
|
||||||
|
# correct.
|
||||||
|
#
|
||||||
|
# Then return from the inline function and call to another function,
|
||||||
|
# check the backtrace from this second function also looks good,
|
||||||
|
# specifically, we're checking that the backtrace doesn't incorrectly
|
||||||
|
# place frame #1 on the line for the inline function.
|
||||||
|
proc run_test { opt_level } {
|
||||||
|
|
||||||
|
set opts $::options
|
||||||
|
lappend opts "additional_flags=-${opt_level}"
|
||||||
|
|
||||||
|
if { [prepare_for_testing "failed to prepare" "$::testfile-$opt_level" \
|
||||||
|
$::srcfile $opts] } {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if { ![runto_main] } {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gdb_test "bt" "#0\\s+main \\(\\) \[^\r\n\]+/$::srcfile:$::decimal" \
|
||||||
|
"backtrace in main"
|
||||||
|
|
||||||
|
# Break at the empty inline function ptr::get_myclass.
|
||||||
|
gdb_breakpoint get_myclass
|
||||||
|
gdb_continue_to_breakpoint "continue to get_myclass" \
|
||||||
|
[multi_line \
|
||||||
|
".*/$::srcfile:$::get_myclass_line" \
|
||||||
|
"$::get_myclass_line\\s+MyClass\\* get_myclass \\(\\) \[^\r\n\]+"]
|
||||||
|
|
||||||
|
# Backtrace.
|
||||||
|
gdb_test "bt" \
|
||||||
|
[multi_line \
|
||||||
|
"#0\\s+ptr::get_myclass\[^\r\n\]+/$::srcfile:$::get_myclass_line" \
|
||||||
|
"#1\\s+intermediate\[^\r\n\]+/$::srcfile:$::call_get_line" \
|
||||||
|
"#2\\s+\[^\r\n\]+main \\(\\) \[^\r\n\]+/$::srcfile:$::decimal"] \
|
||||||
|
"at get_myclass"
|
||||||
|
|
||||||
|
# Print a class member variable, this should be in scope, but is often
|
||||||
|
# reported as optimised out.
|
||||||
|
gdb_test "p t" \
|
||||||
|
"(?:\\\$1 = \\(MyClass \\*\\) $::hex|value has been optimized out)" \
|
||||||
|
"print ptr::t"
|
||||||
|
|
||||||
|
gdb_breakpoint $::srcfile:$::final_bp_line
|
||||||
|
gdb_continue_to_breakpoint "continue to final breakpoint"
|
||||||
|
|
||||||
|
# Backtrace. Check frame #1 looks right. Bug gdb/25987 would report
|
||||||
|
# frame #1 as being the correct function, but would report the line for
|
||||||
|
# ptr::get_myclass(), which is not correct.
|
||||||
|
gdb_test "bt" \
|
||||||
|
[multi_line \
|
||||||
|
"#0\\s+MyClass::call\[^\r\n\]+/$::srcfile:$::final_bp_line" \
|
||||||
|
"#1\\s+\[^\r\n\]+ intermediate\[^\r\n\]+/$::srcfile:$::call_get_line" \
|
||||||
|
"#2\\s+\[^\r\n\]+ main \\(\\) \[^\r\n\]+/$::srcfile:$::decimal"] \
|
||||||
|
"at call"
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach_with_prefix opt_level { Og O0 O1 O2 } {
|
||||||
|
run_test ${opt_level}
|
||||||
|
}
|
||||||
40
gdb/testsuite/gdb.opt/empty-inline.c
Normal file
40
gdb/testsuite/gdb.opt/empty-inline.c
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/* 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/>. */
|
||||||
|
|
||||||
|
#include "attributes.h"
|
||||||
|
|
||||||
|
static int
|
||||||
|
test0 (void)
|
||||||
|
{
|
||||||
|
asm (""); /* First line of test0. */
|
||||||
|
return 1; /* Second line of test0. */
|
||||||
|
}
|
||||||
|
|
||||||
|
int __attribute__((noinline)) ATTRIBUTE_NOCLONE
|
||||||
|
test1 (int x)
|
||||||
|
{
|
||||||
|
asm ("");
|
||||||
|
return x + 1; /* Second line of test1. */
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main (void)
|
||||||
|
{
|
||||||
|
test1 (test0 ()); /* First line of main. */
|
||||||
|
test1 (test0 ()); /* Second line of main. */
|
||||||
|
return 0; /* Third line of main. */
|
||||||
|
}
|
||||||
110
gdb/testsuite/gdb.opt/empty-inline.exp
Normal file
110
gdb/testsuite/gdb.opt/empty-inline.exp
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
# 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/>.
|
||||||
|
|
||||||
|
standard_testfile
|
||||||
|
|
||||||
|
require {expr ![test_compiler_info gcc* c++] \
|
||||||
|
|| [supports_statement_frontiers] }
|
||||||
|
|
||||||
|
set options {debug nowarnings optimize=-O2}
|
||||||
|
lappend_include_file options $srcdir/lib/attributes.h
|
||||||
|
if {[supports_statement_frontiers]} {
|
||||||
|
lappend options additional_flags=-gstatement-frontiers
|
||||||
|
}
|
||||||
|
|
||||||
|
# Some line numbers we need.
|
||||||
|
set lineno_main_1 [gdb_get_line_number "First line of main"]
|
||||||
|
set lineno_main_2 [gdb_get_line_number "Second line of main"]
|
||||||
|
set lineno_main_3 [gdb_get_line_number "Third line of main"]
|
||||||
|
set lineno_test0_1 [gdb_get_line_number "First line of test0"]
|
||||||
|
set lineno_test0_2 [gdb_get_line_number "Second line of test0"]
|
||||||
|
set lineno_test1_2 [gdb_get_line_number "Second line of test1"]
|
||||||
|
|
||||||
|
# ...
|
||||||
|
proc run_test { opt_level } {
|
||||||
|
|
||||||
|
set opts $::options
|
||||||
|
lappend opts "additional_flags=-${opt_level}"
|
||||||
|
|
||||||
|
if { [prepare_for_testing "failed to prepare" "$::testfile-$opt_level" \
|
||||||
|
$::srcfile $opts] } {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if { ![runto_main] } {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gdb_test "frame 0" \
|
||||||
|
[multi_line \
|
||||||
|
"#0\\s+main \[^\r\n\]+/$::srcfile:$::lineno_main_1" \
|
||||||
|
"$::lineno_main_1\\s+\[^\r\n\]+"] \
|
||||||
|
"frame 0 while in main"
|
||||||
|
|
||||||
|
gdb_test_multiple "step" "step into test0" {
|
||||||
|
-re -wrap ".*test0.*$::srcfile:$::lineno_test0_1.*" {
|
||||||
|
gdb_test "step" ".*line $::lineno_test0_2.*" $gdb_test_name
|
||||||
|
}
|
||||||
|
-re -wrap ".*test0.*$::srcfile:$::lineno_test0_2.*" {
|
||||||
|
pass $gdb_test_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gdb_test "frame 1" \
|
||||||
|
[multi_line \
|
||||||
|
"#1\\s+main \[^\r\n\]+/$::srcfile:$::lineno_main_1" \
|
||||||
|
"$::lineno_main_1\\s+\[^\r\n\]+"] \
|
||||||
|
"inspect frame 1, main"
|
||||||
|
|
||||||
|
# Step into test1() function.
|
||||||
|
gdb_test "step" \
|
||||||
|
[multi_line \
|
||||||
|
"test1 \\(\[^)\]+\\) at \[^\r\n\]+/$::srcfile:$::lineno_test1_2" \
|
||||||
|
"$::lineno_test1_2\\s+\[^\r\n\]+"] \
|
||||||
|
"step into test1"
|
||||||
|
|
||||||
|
# Check frame #1 looks right. Bug gdb/25987 would report frame #1 as
|
||||||
|
# being the correct function, but would report the line for a nearby
|
||||||
|
# inlined function.
|
||||||
|
gdb_test "frame 1" \
|
||||||
|
[multi_line \
|
||||||
|
"#1\\s+\[^\r\n\]*main \\(\\) \[^\r\n\]+/$::srcfile:$::lineno_main_1" \
|
||||||
|
"$::lineno_main_1\\s+\[^\r\n\]+"] \
|
||||||
|
"inspect frame 1 again, still main"
|
||||||
|
|
||||||
|
# Step from the last line of test1 back into main.
|
||||||
|
gdb_test "step" \
|
||||||
|
[multi_line \
|
||||||
|
"main \\(\\) at \[^\r\n\]+/$::srcfile:$::lineno_main_2" \
|
||||||
|
"$::lineno_main_2\\s+\[^\r\n\]+"] \
|
||||||
|
"step back to main"
|
||||||
|
|
||||||
|
# Use next to step to the last line of main. This skips over the inline
|
||||||
|
# call to test0, and the non-inline call to test1.
|
||||||
|
gdb_test "next" \
|
||||||
|
"$::lineno_main_3\\s+return 0;\\s+\[^\r\n\]+" \
|
||||||
|
"step over test0+1"
|
||||||
|
|
||||||
|
# Sanity check that we are in main like we expect.
|
||||||
|
gdb_test "frame 0" \
|
||||||
|
[multi_line \
|
||||||
|
"#0\\s+main \[^\r\n\]+/$::srcfile:$::lineno_main_3" \
|
||||||
|
"$::lineno_main_3\\s+return 0;\\s+\[^\r\n\]+"] \
|
||||||
|
"confirm expected frame in main"
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach_with_prefix opt_level { Og O0 O1 O2 } {
|
||||||
|
run_test ${opt_level}
|
||||||
|
}
|
||||||
@@ -13,6 +13,8 @@
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
#include "attributes.h"
|
||||||
|
|
||||||
/* This is only ever run if it is compiled with a new-enough GCC, but
|
/* This is only ever run if it is compiled with a new-enough GCC, but
|
||||||
we don't want the compilation to fail if compiled by some other
|
we don't want the compilation to fail if compiled by some other
|
||||||
compiler. */
|
compiler. */
|
||||||
@@ -39,6 +41,30 @@ inline ATTR int func2(void)
|
|||||||
return x * func1 (1);
|
return x * func1 (1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline ATTR int
|
||||||
|
return_one (void)
|
||||||
|
{
|
||||||
|
/* The following empty asm() statement prevents older (< 11.x) versions
|
||||||
|
of gcc from completely optimising away this function. And for newer
|
||||||
|
versions of gcc (>= 11.x) this ensures that we have two line table
|
||||||
|
entries in main for the inline call to this function, with the second
|
||||||
|
of these lines being a non-statement, which is critical for this
|
||||||
|
test. These two behaviours have been checked for versions of gcc
|
||||||
|
between 8.4.0 and 14.2.0. */
|
||||||
|
asm ("");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
volatile int global = 0;
|
||||||
|
|
||||||
|
__attribute__((noinline)) ATTRIBUTE_NOCLONE void
|
||||||
|
not_inline_func (int count)
|
||||||
|
{
|
||||||
|
global += count;
|
||||||
|
global += count; /* b/p in not_inline_func */
|
||||||
|
global += count;
|
||||||
|
}
|
||||||
|
|
||||||
int main (void)
|
int main (void)
|
||||||
{
|
{
|
||||||
int val;
|
int val;
|
||||||
@@ -53,5 +79,7 @@ int main (void)
|
|||||||
val = func2 ();
|
val = func2 ();
|
||||||
result = val;
|
result = val;
|
||||||
|
|
||||||
|
not_inline_func (return_one ()); /* bt line in main */
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,11 @@
|
|||||||
|
|
||||||
standard_testfile .c inline-markers.c
|
standard_testfile .c inline-markers.c
|
||||||
|
|
||||||
|
set opts {debug additional_flags=-Winline}
|
||||||
|
lappend_include_file opts $srcdir/lib/attributes.h
|
||||||
|
|
||||||
if {[prepare_for_testing "failed to prepare" $testfile \
|
if {[prepare_for_testing "failed to prepare" $testfile \
|
||||||
[list $srcfile $srcfile2] \
|
[list $srcfile $srcfile2] $opts]} {
|
||||||
{debug additional_flags=-Winline}]} {
|
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,40 +31,87 @@ if { [skip_inline_frame_tests] } {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
set line1 [gdb_get_line_number "set breakpoint 1 here" ${srcfile2}]
|
# Run inline function backtrace tests, compile with binary with OPT_LEVEL
|
||||||
gdb_breakpoint $srcfile2:$line1
|
# optimisation level. OPT_LEVEL should be a string like 'O0', 'O1', etc.
|
||||||
|
# No leading '-' is needed on OPT_LEVEL, that is added in this proc.
|
||||||
|
proc run_test { opt_level } {
|
||||||
|
|
||||||
gdb_test "continue" ".*set breakpoint 1 here.*" "continue to bar, 1"
|
set local_opts $::opts
|
||||||
gdb_test "backtrace" "#0 bar.*#1 .*main.*" "backtrace from bar, 1"
|
lappend local_opts "additional_flags=-$opt_level"
|
||||||
gdb_test "info frame" ".*called by frame.*" "bar not inlined"
|
|
||||||
|
|
||||||
gdb_test "continue" ".*set breakpoint 1 here.*" "continue to bar, 2"
|
if {[prepare_for_testing "failed to prepare" ${::testfile}-${opt_level} \
|
||||||
gdb_test "backtrace" "#0 bar.*#1 .*func1.*#2 .*main.*" \
|
[list $::srcfile $::srcfile2] $local_opts]} {
|
||||||
"backtrace from bar, 2"
|
return
|
||||||
gdb_test "up" "#1 .*func1.*" "up from bar, 2"
|
}
|
||||||
gdb_test "info frame" ".*inlined into frame.*" "func1 inlined, 2"
|
|
||||||
|
|
||||||
gdb_test "continue" ".*set breakpoint 1 here.*" "continue to bar, 3"
|
runto_main
|
||||||
gdb_test "backtrace" "#0 bar.*#1 .*func1.*#2 .*func2.*#3 .*main.*" \
|
|
||||||
"backtrace from bar, 3"
|
|
||||||
gdb_test "up" "#1 .*func1.*" "up from bar, 3"
|
|
||||||
gdb_test "info frame" ".*inlined into frame.*" "func1 inlined, 3"
|
|
||||||
gdb_test "up" "#2 .*func2.*" "up from func1, 3"
|
|
||||||
gdb_test "info frame" ".*inlined into frame.*" "func2 inlined, 3"
|
|
||||||
|
|
||||||
# A regression test for having a backtrace limit that forces unwinding
|
set line1 [gdb_get_line_number "set breakpoint 1 here" ${::srcfile2}]
|
||||||
# to stop after an inline frame. GDB needs to compute the frame_id of
|
gdb_breakpoint $::srcfile2:$line1
|
||||||
# the inline frame, which requires unwinding past all the inline
|
|
||||||
# frames to the real stack frame, even if that means bypassing the
|
with_test_prefix "first stop at bar" {
|
||||||
# user visible backtrace limit. See PR backtrace/15558.
|
gdb_continue_to_breakpoint "continue to bar" \
|
||||||
#
|
".*set breakpoint 1 here.*"
|
||||||
# Set a backtrace limit that forces an unwind stop after an inline
|
gdb_test "backtrace" "#0 bar.*#1 .*main.*" "backtrace from bar"
|
||||||
# function.
|
gdb_test "info frame" ".*called by frame.*" "bar not inlined"
|
||||||
gdb_test_no_output "set backtrace limit 2"
|
}
|
||||||
# Force flushing the frame cache.
|
|
||||||
gdb_test "maint flush register-cache" "Register cache flushed."
|
with_test_prefix "second stop at bar" {
|
||||||
gdb_test "up" "#1 .*func1.*" "up from bar, 4"
|
gdb_continue_to_breakpoint "continue to bar" \
|
||||||
gdb_test "info frame" ".*in func1.*" "info frame still works"
|
".*set breakpoint 1 here.*"
|
||||||
# Verify the user visible limit works as expected.
|
gdb_test "backtrace" "#0 bar.*#1 .*func1.*#2 .*main.*" \
|
||||||
gdb_test "up" "Initial frame selected; you cannot go up." "up hits limit"
|
"backtrace from bar"
|
||||||
gdb_test "backtrace" "#0 bar.*#1 .*func1.*" "backtrace hits limit"
|
gdb_test "up" "#1 .*func1.*" "up from bar"
|
||||||
|
gdb_test "info frame" ".*inlined into frame.*" "func1 inlined"
|
||||||
|
}
|
||||||
|
|
||||||
|
with_test_prefix "third stop at bar" {
|
||||||
|
gdb_continue_to_breakpoint "continue to bar" \
|
||||||
|
".*set breakpoint 1 here.*"
|
||||||
|
gdb_test "backtrace" "#0 bar.*#1 .*func1.*#2 .*func2.*#3 .*main.*" \
|
||||||
|
"backtrace from bar"
|
||||||
|
gdb_test "up" "#1 .*func1.*" "up from bar"
|
||||||
|
gdb_test "info frame" ".*inlined into frame.*" "func1 inlined"
|
||||||
|
gdb_test "up" "#2 .*func2.*" "up from func1"
|
||||||
|
gdb_test "info frame" ".*inlined into frame.*" "func2 inlined"
|
||||||
|
}
|
||||||
|
|
||||||
|
# A regression test for having a backtrace limit that forces unwinding
|
||||||
|
# to stop after an inline frame. GDB needs to compute the frame_id of
|
||||||
|
# the inline frame, which requires unwinding past all the inline
|
||||||
|
# frames to the real stack frame, even if that means bypassing the
|
||||||
|
# user visible backtrace limit. See PR backtrace/15558.
|
||||||
|
#
|
||||||
|
# Set a backtrace limit that forces an unwind stop after an inline
|
||||||
|
# function.
|
||||||
|
gdb_test_no_output "set backtrace limit 2"
|
||||||
|
# Force flushing the frame cache.
|
||||||
|
gdb_test "maint flush register-cache" "Register cache flushed."
|
||||||
|
gdb_test "up" "#1 .*func1.*" "up from bar"
|
||||||
|
gdb_test "info frame" ".*in func1.*" "info frame still works"
|
||||||
|
# Verify the user visible limit works as expected.
|
||||||
|
gdb_test "up" "Initial frame selected; you cannot go up." "up hits limit"
|
||||||
|
gdb_test "backtrace" "#0 bar.*#1 .*func1.*" "backtrace hits limit"
|
||||||
|
|
||||||
|
set line2 [gdb_get_line_number "b/p in not_inline_func" $::srcfile]
|
||||||
|
set line3 [gdb_get_line_number "bt line in main" $::srcfile]
|
||||||
|
|
||||||
|
gdb_breakpoint $::srcfile:$line2
|
||||||
|
|
||||||
|
gdb_continue_to_breakpoint "stop in not_inline_func" \
|
||||||
|
".*b/p in not_inline_func.*"
|
||||||
|
gdb_test "bt" \
|
||||||
|
[multi_line \
|
||||||
|
"^#0\\s+not_inline_func \\(\[^)\]+\\) at \[^\r\n\]+$::srcfile:$line2" \
|
||||||
|
"#1\\s+$::hex in main \\(\\) at \[^\r\n\]+$::srcfile:$line3"] \
|
||||||
|
"bt from not_inline_func to main"
|
||||||
|
gdb_test "frame 1" \
|
||||||
|
[multi_line \
|
||||||
|
"^#1\\s+$::hex in main \\(\\) at \[^\r\n\]+$::srcfile:$line3" \
|
||||||
|
"$line3\\s+not_inline_func \\(return_one \\(\\)\\);\[^\r\n\]+"] \
|
||||||
|
"select frame for main from not_inline_func"
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach_with_prefix opt_level { O0 Og O1 O2 } {
|
||||||
|
run_test $opt_level
|
||||||
|
}
|
||||||
|
|||||||
@@ -431,6 +431,7 @@ arrange_linetable (std::vector<linetable_entry> &old_linetable)
|
|||||||
linetable_entry &e = fentries.emplace_back ();
|
linetable_entry &e = fentries.emplace_back ();
|
||||||
e.line = ii;
|
e.line = ii;
|
||||||
e.is_stmt = true;
|
e.is_stmt = true;
|
||||||
|
e.is_weak = false;
|
||||||
e.set_unrelocated_pc (old_linetable[ii].unrelocated_pc ());
|
e.set_unrelocated_pc (old_linetable[ii].unrelocated_pc ());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user