gdb: reduce breakpoint-modified events for dprintf b/p

Consider this backtrace within GDB:

  #0  notify_breakpoint_modified (b=0x57d31d0) at ../../src/gdb/breakpoint.c:1083
  #1  0x00000000005b6406 in breakpoint_set_commands (b=0x57d31d0, commands=...) at ../../src/gdb/breakpoint.c:1523
  #2  0x00000000005c8c63 in update_dprintf_command_list (b=0x57d31d0) at ../../src/gdb/breakpoint.c:8641
  #3  0x00000000005d3c4e in dprintf_breakpoint::re_set (this=0x57d31d0) at ../../src/gdb/breakpoint.c:12476
  #4  0x00000000005d6347 in breakpoint_re_set () at ../../src/gdb/breakpoint.c:13298

Whenever breakpoint_re_set is called we re-build the commands that the
dprintf b/p will execute and store these into the breakpoint.  The
commands are re-built in update_dprintf_command_list and stored into
the breakpoint object in breakpoint_set_commands.

Now sometimes these commands can change, dprintf_breakpoint::re_set
explains one case where this can occur, and I'm sure there must be
others.  But in most cases the commands we recalculate will not
change.  This means that the breakpoint modified event which is
emitted from breakpoint_set_commands is redundant.

This commit aims to eliminate the redundant breakpoint modified events
for dprintf breakpoints.  This is done by adding a commands_equal call
to the start of breakpoint_set_commands.

The commands_equal function is a new function which compares two
command_line objects and returns true if they are identical.  Using
this function we can check if the new commands passed to
breakpoint_set_commands are identical to the breakpoint's existing
commands.  If the new commands are equal then we don't need to change
anything on the new breakpoint, and the breakpoint modified event can
be skipped.

The test for this commit stops at a dlopen() call in the inferior,
sets up a dprintf breakpoint, then uses 'next' to step over the
dlopen() call.  When the library loads GDB call breakpoint_re_set,
which calls dprintf_breakpoint::re_set.  But in this case we don't
expect the calculated command string to change, so we don't expect to
see the breakpoint modified event.
This commit is contained in:
Andrew Burgess
2024-09-07 13:45:40 +01:00
parent 43ac3df614
commit e83f612167
6 changed files with 270 additions and 0 deletions

View File

@@ -1625,6 +1625,65 @@ define_prefix_command (const char *comname, int from_tty)
c->allow_unknown = c->user_commands.get () != nullptr;
}
/* See cli/cli-script.h. */
bool
commands_equal (const command_line *a, const command_line *b)
{
if ((a == nullptr) != (b == nullptr))
return false;
while (a != nullptr)
{
/* We are either at the end of both command lists, or there's
another command in both lists. */
if ((a->next == nullptr) != (b->next == nullptr))
return false;
/* There's a command line for both, or neither. */
if ((a->line == nullptr) != (b->line == nullptr))
return false;
/* Check control_type matches. */
if (a->control_type != b->control_type)
return false;
if (a->control_type == compile_control)
{
if (a->control_u.compile.scope != b->control_u.compile.scope)
return false;
/* This is where we "fail safe". The scope_data is a 'void *'
pointer which changes in meaning based on the value of
'scope'. It is possible that two different 'void *' pointers
could point to the equal scope data, however, we just assume
that if the pointers are different, then the scope_data is
different. This could be improved in the future. */
if (a->control_u.compile.scope_data
!= b->control_u.compile.scope_data)
return false;
}
/* Check lines are identical. */
if (a->line != nullptr && strcmp (a->line, b->line) != 0)
return false;
/* Check body_list_0. */
if (!commands_equal (a->body_list_0.get (), b->body_list_0.get ()))
return false;
/* Check body_list_1. */
if (!commands_equal (a->body_list_1.get (), b->body_list_1.get ()))
return false;
/* Move to the next element in each chain. */
a = a->next;
b = b->next;
}
return true;
}
/* Used to implement source_command. */