mirror of
https://github.com/bminor/binutils-gdb.git
synced 2025-11-16 12:34:43 +00:00
Compare commits
5 Commits
binutils-2
...
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
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 ())
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -627,19 +637,16 @@ buildsym_compunit::record_line (struct subfile *subfile, int line,
|
||||
{
|
||||
m_have_line_numbers = true;
|
||||
|
||||
/* Normally, we treat lines as unsorted. But the end of sequence
|
||||
marker is special. We sort line markers at the same PC by line
|
||||
number, so end of sequence markers (which have line == 0) appear
|
||||
first. This is right if the marker ends the previous function,
|
||||
and there is no padding before the next function. But it is
|
||||
wrong if the previous line was empty and we are now marking a
|
||||
switch to a different subfile. We must leave the end of sequence
|
||||
marker at the end of this group of lines, not sort the empty line
|
||||
to after the marker. The easiest way to accomplish this is to
|
||||
delete any empty lines from our table, if they are followed by
|
||||
end of sequence markers. All we lose is the ability to set
|
||||
breakpoints at some lines which contain no instructions
|
||||
anyway. */
|
||||
/* The end of sequence marker is special. We need to delete any
|
||||
previous lines at the same PC, otherwise these lines may cause
|
||||
problems since they might be at the same address as the following
|
||||
function. For instance suppose a function calls abort there is no
|
||||
reason to emit a ret after that point (no joke).
|
||||
So the label may be at the same address where the following
|
||||
function begins. There is also a fake end of sequence marker (-1)
|
||||
that we emit internally when switching between different CUs
|
||||
In this case, duplicate line table entries shall not be deleted.
|
||||
We simply set the is_weak marker in this case. */
|
||||
if (line == 0)
|
||||
{
|
||||
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)
|
||||
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 ();
|
||||
e.line = line;
|
||||
e.is_stmt = (flags & LEF_IS_STMT) != 0;
|
||||
e.is_weak = false;
|
||||
e.set_unrelocated_pc (pc);
|
||||
e.prologue_end = (flags & LEF_PROLOGUE_END) != 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
|
||||
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. */
|
||||
std::stable_sort (subfile->line_vector_entries.begin (),
|
||||
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. */
|
||||
|
||||
@@ -446,6 +446,9 @@ private:
|
||||
|
||||
/* Pending symbols that are local to the lexical context. */
|
||||
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
|
||||
reading .debug_rnglists.
|
||||
Callback's type should be:
|
||||
@@ -10850,7 +10861,12 @@ dwarf2_rnglists_process (unsigned offset, struct dwarf2_cu *cu,
|
||||
|
||||
/* Empty range entries have no effect. */
|
||||
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. */
|
||||
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. */
|
||||
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
|
||||
+ (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)
|
||||
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)
|
||||
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
|
||||
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 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. */
|
||||
|
||||
/* 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. */
|
||||
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. */
|
||||
bool m_currently_recording_lines = true;
|
||||
|
||||
@@ -18524,7 +18572,8 @@ dwarf_record_line_1 (struct gdbarch *gdbarch, struct subfile *subfile,
|
||||
|
||||
static void
|
||||
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)
|
||||
return;
|
||||
@@ -18537,7 +18586,8 @@ dwarf_finish_line (struct gdbarch *gdbarch, struct subfile *subfile,
|
||||
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
|
||||
@@ -18565,38 +18615,17 @@ lnp_state_machine::record_line (bool end_sequence)
|
||||
/* For now we ignore lines not starting on an instruction boundary.
|
||||
But not when processing end_sequence for compatibility with the
|
||||
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,
|
||||
switch to the second file and add a new line entry. The
|
||||
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)
|
||||
if (m_last_subfile != m_cu->get_builder ()->get_current_subfile ()
|
||||
|| end_sequence)
|
||||
{
|
||||
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;
|
||||
if (producer_is_codewarrior (m_cu))
|
||||
@@ -18616,15 +18645,6 @@ lnp_state_machine::record_line (bool end_sequence)
|
||||
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,
|
||||
@@ -18638,8 +18658,7 @@ lnp_state_machine::lnp_state_machine (struct dwarf2_cu *cu, gdbarch *arch,
|
||||
This is currently used by MIPS code,
|
||||
cf. `mips_adjust_dwarf2_line'. */
|
||||
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_last_address (m_address)
|
||||
m_flags (lh->default_is_stmt ? LEF_IS_STMT : (linetable_entry_flags) 0)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -996,7 +996,8 @@ prepare_one_step (thread_info *tp, struct step_command_fsm *sm)
|
||||
if (sym->aclass () == LOC_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 ();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8200,6 +8200,8 @@ process_event_stop_test (struct execution_control_state *ecs)
|
||||
infrun_debug_printf ("stepping through inlined function");
|
||||
|
||||
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))
|
||||
keep_going (ecs);
|
||||
else
|
||||
@@ -8248,7 +8250,8 @@ process_event_stop_test (struct execution_control_state *ecs)
|
||||
end_stepping_range (ecs);
|
||||
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
|
||||
frame.
|
||||
|
||||
@@ -497,6 +497,7 @@ jit_symtab_line_mapping_add_impl (struct gdb_symbol_callbacks *cb,
|
||||
(unrelocated_addr (map[i].pc));
|
||||
stab->linetable->item[i].line = map[i].line;
|
||||
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);
|
||||
if (l->item[i].is_stmt)
|
||||
gdb_printf (outfile, "\t(stmt)");
|
||||
if (l->item[i].is_weak)
|
||||
gdb_printf (outfile, "\t(weak)");
|
||||
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
|
||||
tables will just not format as well. */
|
||||
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, "line", _("LINE"));
|
||||
uiout->table_header (18, ui_left, "rel-address", _("REL-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-weak", _("IS-WEAK"));
|
||||
uiout->table_header (12, ui_left, "prologue-end", _("PROLOGUE-END"));
|
||||
uiout->table_header (14, ui_left, "epilogue-begin", _("EPILOGUE-BEGIN"));
|
||||
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 (),
|
||||
CORE_ADDR (item->unrelocated_pc ()));
|
||||
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 ("epilogue-begin", item->epilogue_begin ? "Y" : "");
|
||||
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. */
|
||||
|
||||
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_symtab = iter_s;
|
||||
|
||||
/* If during the binary search we land on a non-statement entry,
|
||||
scan backward through entries at the same address to see if
|
||||
there is an entry marked as is-statement. In theory this
|
||||
duplication should have been removed from the line table
|
||||
during construction, this is just a double check. If the line
|
||||
table has had the duplication removed then this should be
|
||||
pretty cheap. */
|
||||
if (!best->is_stmt)
|
||||
/* If NOTCURRENT is false then the address we are looking for is
|
||||
the address the inferior is currently stopped at. In this
|
||||
case our preference is to report a stop at a line marked as
|
||||
is_stmt. If BEST is not marked as a statement then scan
|
||||
backwards through entries at this address looking for one that
|
||||
is marked as a statement; if one is found then use that.
|
||||
|
||||
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;
|
||||
while (tmp > first
|
||||
&& (tmp - 1)->unrelocated_pc () == tmp->unrelocated_pc ()
|
||||
&& (tmp - 1)->line != 0 && !tmp->is_stmt)
|
||||
--tmp;
|
||||
if (tmp->is_stmt)
|
||||
if (tmp->is_stmt && (tmp->pc (objfile) == pc || !tmp->is_weak))
|
||||
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
|
||||
anywhere; if we don't have line number info for this PC,
|
||||
don't make some up. */
|
||||
val.pc = 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. */
|
||||
if (notcurrent)
|
||||
pc++;
|
||||
val.pc = pc;
|
||||
}
|
||||
else
|
||||
{
|
||||
val.is_stmt = best->is_stmt;
|
||||
val.is_weak = best->is_weak;
|
||||
val.symtab = best_symtab;
|
||||
val.line = best->line;
|
||||
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. */
|
||||
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
|
||||
function prologue. */
|
||||
bool prologue_end : 1;
|
||||
@@ -2403,6 +2406,8 @@ struct symtab_and_line
|
||||
/* If the line number information is valid, then this indicates if this
|
||||
line table entry had the is-stmt flag set or not. */
|
||||
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. */
|
||||
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))
|
||||
get_alias_set (tree *t)
|
||||
{
|
||||
if (t != NULL
|
||||
{ if (t != NULL
|
||||
&& TREE_TYPE (t).z != 1
|
||||
&& TREE_TYPE (t).z != 2
|
||||
&& TREE_TYPE (t).z != 3)
|
||||
@@ -60,7 +59,6 @@ tree xx;
|
||||
|
||||
int
|
||||
main()
|
||||
{
|
||||
get_alias_set (&xx); /* Beginning of main */
|
||||
{ get_alias_set (&xx);
|
||||
return 0;
|
||||
} // main
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -24,17 +25,8 @@ if {[test_compiler_info gcc*] && ![supports_statement_frontiers] } {
|
||||
proc do_test { use_header } {
|
||||
global srcfile testfile
|
||||
|
||||
if { $use_header } {
|
||||
# This test will not pass due to poor debug information
|
||||
# generated by GCC (at least up to 10.x). See
|
||||
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94474
|
||||
return
|
||||
}
|
||||
|
||||
set options {c++ debug nowarnings optimize=-O2}
|
||||
if { [supports_statement_frontiers] } {
|
||||
lappend options additional_flags=-gstatement-frontiers
|
||||
}
|
||||
lappend options additional_flags=-gstatement-frontiers
|
||||
if { $use_header } {
|
||||
lappend options additional_flags=-DUSE_NEXT_INLINE_H
|
||||
set executable "$testfile-with-header"
|
||||
@@ -53,8 +45,6 @@ proc do_test { use_header } {
|
||||
|
||||
with_test_prefix $prefix {
|
||||
|
||||
set main_location [gdb_get_line_number "Beginning of main" $srcfile]
|
||||
|
||||
if {![runto_main]} {
|
||||
return
|
||||
}
|
||||
@@ -81,129 +71,28 @@ proc do_test { use_header } {
|
||||
|
||||
clean_restart $executable
|
||||
|
||||
if ![runto $main_location qualified] {
|
||||
if ![runto_main] {
|
||||
return
|
||||
}
|
||||
|
||||
gdb_test "bt" "\\s*\\#0\\s+main.*" "in main"
|
||||
set line1 {\t\{}
|
||||
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 "step" ".*" "step into get_alias_set"
|
||||
gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
|
||||
"not in inline 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 "next" ".*TREE_TYPE.*" "next step 1"
|
||||
gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
|
||||
"not in inline 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 "next" ".*TREE_TYPE.*" "next step 2"
|
||||
gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
|
||||
"not in inline 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 "next" ".*TREE_TYPE.*" "next step 3"
|
||||
gdb_test "bt" "\\s*\\#0\\s+get_alias_set\[^\r\]*${srcfile}:.*" \
|
||||
"not in inline 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 "next" "return 0.*" "next step 4"
|
||||
gdb_test "bt" \
|
||||
"\\s*\\#0\\s+(main|get_alias_set)\[^\r\]*${srcfile}:.*" \
|
||||
"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}
|
||||
|
||||
if ![runto_main] {
|
||||
@@ -220,22 +109,84 @@ proc do_test { use_header } {
|
||||
gdb_test "step" ".*if \\(t->x != i\\).*" "step 2"
|
||||
gdb_test "bt" "\\s*\\#0\\s+\[^\r\]*tree_check\[^\r\]*${hdrfile}:.*" \
|
||||
"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}:.*" \
|
||||
"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}:.*" \
|
||||
"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}:.*" \
|
||||
"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}:.*" \
|
||||
"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" \
|
||||
"\\s*\\#0\\s+(main|get_alias_set)\[^\r\]*${srcfile}:.*" \
|
||||
"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" {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,9 @@ foreach foo {foo_1 foo_2 foo_3 foo_4 foo_5 foo_6} {
|
||||
"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.
|
||||
set foo_decl_line [gdb_get_line_number "foo decl 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
|
||||
# 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.
|
||||
#
|
||||
# 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 } {
|
||||
set dw_testname "${::testfile}-${dwarf_version}-${entry_label}"
|
||||
proc run_test { producer entry_label dwarf_version with_line_table } {
|
||||
set dw_testname "${::testfile}-${producer}-${dwarf_version}-${entry_label}"
|
||||
|
||||
if { $with_line_table } {
|
||||
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"]
|
||||
Dwarf::assemble $asm_file {
|
||||
upvar dwarf_version dwarf_version
|
||||
upvar entry_label entry_label
|
||||
upvar producer_str producer_str
|
||||
|
||||
declare_labels lines_table inline_func ranges_label
|
||||
|
||||
cu { version $dwarf_version } {
|
||||
compile_unit {
|
||||
{producer "gcc"}
|
||||
{producer $producer_str}
|
||||
{language @DW_LANG_C}
|
||||
{name $::srcfile}
|
||||
{comp_dir /tmp}
|
||||
@@ -157,6 +176,10 @@ proc run_test { entry_label dwarf_version with_line_table } {
|
||||
line 2
|
||||
DW_LNS_copy
|
||||
|
||||
DW_LNE_set_address foo_3
|
||||
line 3
|
||||
DW_LNS_copy
|
||||
|
||||
DW_LNE_set_address foo_6
|
||||
line 10
|
||||
DW_LNS_copy
|
||||
@@ -206,6 +229,16 @@ proc run_test { entry_label dwarf_version with_line_table } {
|
||||
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
|
||||
# gdb_test as we want full pattern matching against the stop
|
||||
# location.
|
||||
@@ -215,8 +248,8 @@ proc run_test { entry_label dwarf_version with_line_table } {
|
||||
if { $with_line_table } {
|
||||
set re \
|
||||
[multi_line \
|
||||
"Breakpoint $::decimal, bar \\(\\) at \[^\r\n\]+/$::srcfile:1" \
|
||||
"1\\s+\[^\r\n\]+"]
|
||||
"Breakpoint $::decimal, bar \\(\\) at \[^\r\n\]+/$::srcfile:$line_num" \
|
||||
"$line_num\\s+\[^\r\n\]+"]
|
||||
} else {
|
||||
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" \
|
||||
[multi_line \
|
||||
"\\\[\\(block \\*\\) $::hex\\\] $::foo_1\\.\\.$::foo_6" \
|
||||
" entry pc: $::foo_1" \
|
||||
" entry pc: $entry_pc" \
|
||||
" inline function: bar" \
|
||||
" symbol count: $::decimal" \
|
||||
" address ranges:" \
|
||||
" address ranges:$empty_range_re" \
|
||||
" $::foo_1\\.\\.$::foo_2" \
|
||||
" $::foo_5\\.\\.$::foo_6"]
|
||||
}
|
||||
|
||||
foreach_with_prefix dwarf_version { 4 5 } {
|
||||
# Test various labels without any line table present.
|
||||
foreach_with_prefix entry_label { foo_3 foo_4 foo_2 foo_6 } {
|
||||
run_test $entry_label $dwarf_version false
|
||||
}
|
||||
foreach_with_prefix producer { other gcc } {
|
||||
foreach_with_prefix dwarf_version { 4 5 } {
|
||||
# Test various labels without any line table present.
|
||||
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,
|
||||
# but also supply a line table. Does GDB do anything different?
|
||||
run_test foo_6 $dwarf_version true
|
||||
# Now test what happens if we use the end address of the block,
|
||||
# but also supply a line table. Does GDB do anything different?
|
||||
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
|
||||
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
|
||||
we don't want the compilation to fail if compiled by some other
|
||||
compiler. */
|
||||
@@ -39,6 +41,30 @@ inline ATTR int func2(void)
|
||||
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 val;
|
||||
@@ -53,5 +79,7 @@ int main (void)
|
||||
val = func2 ();
|
||||
result = val;
|
||||
|
||||
not_inline_func (return_one ()); /* bt line in main */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -15,9 +15,11 @@
|
||||
|
||||
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 \
|
||||
[list $srcfile $srcfile2] \
|
||||
{debug additional_flags=-Winline}]} {
|
||||
[list $srcfile $srcfile2] $opts]} {
|
||||
return -1
|
||||
}
|
||||
|
||||
@@ -29,40 +31,87 @@ if { [skip_inline_frame_tests] } {
|
||||
return
|
||||
}
|
||||
|
||||
set line1 [gdb_get_line_number "set breakpoint 1 here" ${srcfile2}]
|
||||
gdb_breakpoint $srcfile2:$line1
|
||||
# Run inline function backtrace tests, compile with binary with OPT_LEVEL
|
||||
# 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"
|
||||
gdb_test "backtrace" "#0 bar.*#1 .*main.*" "backtrace from bar, 1"
|
||||
gdb_test "info frame" ".*called by frame.*" "bar not inlined"
|
||||
set local_opts $::opts
|
||||
lappend local_opts "additional_flags=-$opt_level"
|
||||
|
||||
gdb_test "continue" ".*set breakpoint 1 here.*" "continue to bar, 2"
|
||||
gdb_test "backtrace" "#0 bar.*#1 .*func1.*#2 .*main.*" \
|
||||
"backtrace from bar, 2"
|
||||
gdb_test "up" "#1 .*func1.*" "up from bar, 2"
|
||||
gdb_test "info frame" ".*inlined into frame.*" "func1 inlined, 2"
|
||||
if {[prepare_for_testing "failed to prepare" ${::testfile}-${opt_level} \
|
||||
[list $::srcfile $::srcfile2] $local_opts]} {
|
||||
return
|
||||
}
|
||||
|
||||
gdb_test "continue" ".*set breakpoint 1 here.*" "continue to bar, 3"
|
||||
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"
|
||||
runto_main
|
||||
|
||||
# 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, 4"
|
||||
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 line1 [gdb_get_line_number "set breakpoint 1 here" ${::srcfile2}]
|
||||
gdb_breakpoint $::srcfile2:$line1
|
||||
|
||||
with_test_prefix "first stop at bar" {
|
||||
gdb_continue_to_breakpoint "continue to bar" \
|
||||
".*set breakpoint 1 here.*"
|
||||
gdb_test "backtrace" "#0 bar.*#1 .*main.*" "backtrace from bar"
|
||||
gdb_test "info frame" ".*called by frame.*" "bar not inlined"
|
||||
}
|
||||
|
||||
with_test_prefix "second stop at bar" {
|
||||
gdb_continue_to_breakpoint "continue to bar" \
|
||||
".*set breakpoint 1 here.*"
|
||||
gdb_test "backtrace" "#0 bar.*#1 .*func1.*#2 .*main.*" \
|
||||
"backtrace from bar"
|
||||
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 ();
|
||||
e.line = ii;
|
||||
e.is_stmt = true;
|
||||
e.is_weak = false;
|
||||
e.set_unrelocated_pc (old_linetable[ii].unrelocated_pc ());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user