forked from Imagelibrary/binutils-gdb
I noticed the following behaviour:
$ gdb -q -i=mi /tmp/hello.x
=thread-group-added,id="i1"
=cmd-param-changed,param="print pretty",value="on"
~"Reading symbols from /tmp/hello.x...\n"
(gdb)
-break-insert -p 99 main
^done,bkpt={number="1",type="breakpoint",disp="keep",enabled="y",addr="0x0000000000401198",func="main",file="/tmp/hello.c",fullname="/tmp/hello.c",line="18",thread-groups=["i1"],thread="99",times="0",original-location="main"}
(gdb)
info breakpoints
&"info breakpoints\n"
~"Num Type Disp Enb Address What\n"
~"1 breakpoint keep y 0x0000000000401198 in main at /tmp/hello.c:18\n"
&"../../src/gdb/thread.c:1434: internal-error: print_thread_id: Assertion `thr != nullptr' failed.\nA problem internal to GDB has been detected,\nfurther debugging may prove unreliable."
&"\n"
&"----- Backtrace -----\n"
&"Backtrace unavailable\n"
&"---------------------\n"
&"\nThis is a bug, please report it."
&" For instructions, see:\n"
&"<https://www.gnu.org/software/gdb/bugs/>.\n\n"
Aborted (core dumped)
What we see here is that when using the MI a user can create
thread-specific breakpoints for non-existent threads. Then if we try
to use the CLI 'info breakpoints' command GDB throws an assertion.
The assert is a result of the print_thread_id call when trying to
build the 'stop only in thread xx.yy' line; print_thread_id requires a
valid thread_info pointer, which we can't have for a non-existent
thread.
In contrast, when using the CLI we see this behaviour:
$ gdb -q /tmp/hello.x
Reading symbols from /tmp/hello.x...
(gdb) break main thread 99
Unknown thread 99.
(gdb)
The CLI doesn't allow a breakpoint to be created for a non-existent
thread. So the 'info breakpoints' command is always fine.
Interestingly, the MI -break-info command doesn't crash, this is
because the MI uses global thread-ids, and so never calls
print_thread_id. However, GDB does support using CLI and MI in
parallel, so we need to solve this problem.
One option would be to change the CLI behaviour to allow printing
breakpoints for non-existent threads. This would preserve the current
MI behaviour.
The other option is to pull the MI into line with the CLI and prevent
breakpoints being created for non-existent threads. This is good for
consistency, but is a breaking change for the MI.
In the end I figured that it was probably better to retain the
consistent CLI behaviour, and just made the MI reject requests to
place a breakpoint on a non-existent thread. The only test we had
that depended on the old behaviour was
gdb.mi/mi-thread-specific-bp.exp, which was added by me in commit:
commit 2fd9a436c8
Date: Fri Feb 17 10:48:06 2023 +0000
gdb: don't duplicate 'thread' field in MI breakpoint output
I certainly didn't intend for this test to rely on this feature of the
MI, so I propose to update this test to only create breakpoints for
threads that exist.
Actually, I've added a new test that checks the MI rejects creating a
breakpoint for a non-existent thread, and I've also extended the test
to run with the separate MI/CLI UIs, and then tested 'info
breakpoints' to ensure this command doesn't crash.
I've extended the documentation of the `-p` flag to explain the
constraints better.
I have also added a NEWS entry just in case someone runs into this
issue, at least then they'll know this change in behaviour was
intentional.
One thing that I did wonder about while writing this patch, is whether
we should treat requests like this, on both the MI and CLI, as another
form of pending breakpoint, something like:
(gdb) break foo thread 9
Thread 9 does not exist.
Make breakpoint pending on future thread creation? (y or [n]) y
Breakpoint 1 (foo thread 9) pending.
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y <PENDING> foo thread 9
Don't know if folk think that would be a useful idea or not? Either
way, I think that would be a separate patch from this one.
Reviewed-By: Eli Zaretskii <eliz@gnu.org>
589 lines
14 KiB
C
589 lines
14 KiB
C
/* MI Command Set - breakpoint and watchpoint commands.
|
|
Copyright (C) 2000-2023 Free Software Foundation, Inc.
|
|
Contributed by Cygnus Solutions (a Red Hat company).
|
|
|
|
This file is part of GDB.
|
|
|
|
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 "defs.h"
|
|
#include "arch-utils.h"
|
|
#include "mi-cmds.h"
|
|
#include "ui-out.h"
|
|
#include "mi-out.h"
|
|
#include "breakpoint.h"
|
|
#include "mi-getopt.h"
|
|
#include "observable.h"
|
|
#include "mi-main.h"
|
|
#include "mi-cmd-break.h"
|
|
#include "language.h"
|
|
#include "location.h"
|
|
#include "linespec.h"
|
|
#include "gdbsupport/gdb_obstack.h"
|
|
#include <ctype.h>
|
|
#include "tracepoint.h"
|
|
|
|
enum
|
|
{
|
|
FROM_TTY = 0
|
|
};
|
|
|
|
/* True if MI breakpoint observers have been registered. */
|
|
|
|
static int mi_breakpoint_observers_installed;
|
|
|
|
/* Control whether breakpoint_notify may act. */
|
|
|
|
static int mi_can_breakpoint_notify;
|
|
|
|
/* Output a single breakpoint, when allowed. */
|
|
|
|
static void
|
|
breakpoint_notify (struct breakpoint *b)
|
|
{
|
|
if (mi_can_breakpoint_notify)
|
|
{
|
|
try
|
|
{
|
|
print_breakpoint (b);
|
|
}
|
|
catch (const gdb_exception_error &ex)
|
|
{
|
|
exception_print (gdb_stderr, ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
enum bp_type
|
|
{
|
|
REG_BP,
|
|
HW_BP,
|
|
REGEXP_BP
|
|
};
|
|
|
|
/* Arrange for all new breakpoints and catchpoints to be reported to
|
|
CURRENT_UIOUT until the destructor of the returned scoped_restore
|
|
is run.
|
|
|
|
Note that MI output will be probably invalid if more than one
|
|
breakpoint is created inside one MI command. */
|
|
|
|
scoped_restore_tmpl<int>
|
|
setup_breakpoint_reporting (void)
|
|
{
|
|
if (! mi_breakpoint_observers_installed)
|
|
{
|
|
gdb::observers::breakpoint_created.attach (breakpoint_notify,
|
|
"mi-cmd-break");
|
|
mi_breakpoint_observers_installed = 1;
|
|
}
|
|
|
|
return make_scoped_restore (&mi_can_breakpoint_notify, 1);
|
|
}
|
|
|
|
|
|
/* Convert arguments in ARGV to the string in "format",argv,argv...
|
|
and return it. */
|
|
|
|
static std::string
|
|
mi_argv_to_format (char **argv, int argc)
|
|
{
|
|
int i;
|
|
std::string result;
|
|
|
|
/* Convert ARGV[OIND + 1] to format string and save to FORMAT. */
|
|
result += '\"';
|
|
for (i = 0; i < strlen (argv[0]); i++)
|
|
{
|
|
switch (argv[0][i])
|
|
{
|
|
case '\\':
|
|
result += "\\\\";
|
|
break;
|
|
case '\a':
|
|
result += "\\a";
|
|
break;
|
|
case '\b':
|
|
result += "\\b";
|
|
break;
|
|
case '\f':
|
|
result += "\\f";
|
|
break;
|
|
case '\n':
|
|
result += "\\n";
|
|
break;
|
|
case '\r':
|
|
result += "\\r";
|
|
break;
|
|
case '\t':
|
|
result += "\\t";
|
|
break;
|
|
case '\v':
|
|
result += "\\v";
|
|
break;
|
|
case '"':
|
|
result += "\\\"";
|
|
break;
|
|
default:
|
|
if (isprint (argv[0][i]))
|
|
result += argv[0][i];
|
|
else
|
|
{
|
|
char tmp[5];
|
|
|
|
xsnprintf (tmp, sizeof (tmp), "\\%o",
|
|
(unsigned char) argv[0][i]);
|
|
result += tmp;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
result += '\"';
|
|
|
|
/* Apply other argv to FORMAT. */
|
|
for (i = 1; i < argc; i++)
|
|
{
|
|
result += ',';
|
|
result += argv[i];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/* Insert breakpoint.
|
|
If dprintf is true, it will insert dprintf.
|
|
If not, it will insert other type breakpoint. */
|
|
|
|
static void
|
|
mi_cmd_break_insert_1 (int dprintf, const char *command, char **argv, int argc)
|
|
{
|
|
const char *address = NULL;
|
|
int hardware = 0;
|
|
int temp_p = 0;
|
|
int thread = -1;
|
|
int ignore_count = 0;
|
|
char *condition = NULL;
|
|
int pending = 0;
|
|
int enabled = 1;
|
|
int tracepoint = 0;
|
|
symbol_name_match_type match_type = symbol_name_match_type::WILD;
|
|
enum bptype type_wanted;
|
|
location_spec_up locspec;
|
|
const struct breakpoint_ops *ops;
|
|
int is_explicit = 0;
|
|
std::unique_ptr<explicit_location_spec> explicit_loc
|
|
(new explicit_location_spec ());
|
|
std::string extra_string;
|
|
bool force_condition = false;
|
|
|
|
enum opt
|
|
{
|
|
HARDWARE_OPT, TEMP_OPT, CONDITION_OPT,
|
|
IGNORE_COUNT_OPT, THREAD_OPT, PENDING_OPT, DISABLE_OPT,
|
|
TRACEPOINT_OPT,
|
|
FORCE_CONDITION_OPT,
|
|
QUALIFIED_OPT,
|
|
EXPLICIT_SOURCE_OPT, EXPLICIT_FUNC_OPT,
|
|
EXPLICIT_LABEL_OPT, EXPLICIT_LINE_OPT
|
|
};
|
|
static const struct mi_opt opts[] =
|
|
{
|
|
{"h", HARDWARE_OPT, 0},
|
|
{"t", TEMP_OPT, 0},
|
|
{"c", CONDITION_OPT, 1},
|
|
{"i", IGNORE_COUNT_OPT, 1},
|
|
{"p", THREAD_OPT, 1},
|
|
{"f", PENDING_OPT, 0},
|
|
{"d", DISABLE_OPT, 0},
|
|
{"a", TRACEPOINT_OPT, 0},
|
|
{"-force-condition", FORCE_CONDITION_OPT, 0},
|
|
{"-qualified", QUALIFIED_OPT, 0},
|
|
{"-source" , EXPLICIT_SOURCE_OPT, 1},
|
|
{"-function", EXPLICIT_FUNC_OPT, 1},
|
|
{"-label", EXPLICIT_LABEL_OPT, 1},
|
|
{"-line", EXPLICIT_LINE_OPT, 1},
|
|
{ 0, 0, 0 }
|
|
};
|
|
|
|
/* Parse arguments. It could be -r or -h or -t, <location> or ``--''
|
|
to denote the end of the option list. */
|
|
int oind = 0;
|
|
char *oarg;
|
|
|
|
while (1)
|
|
{
|
|
int opt = mi_getopt ("-break-insert", argc, argv,
|
|
opts, &oind, &oarg);
|
|
if (opt < 0)
|
|
break;
|
|
switch ((enum opt) opt)
|
|
{
|
|
case TEMP_OPT:
|
|
temp_p = 1;
|
|
break;
|
|
case HARDWARE_OPT:
|
|
hardware = 1;
|
|
break;
|
|
case CONDITION_OPT:
|
|
condition = oarg;
|
|
break;
|
|
case IGNORE_COUNT_OPT:
|
|
ignore_count = atol (oarg);
|
|
break;
|
|
case THREAD_OPT:
|
|
thread = atol (oarg);
|
|
if (!valid_global_thread_id (thread))
|
|
error (_("Unknown thread %d."), thread);
|
|
break;
|
|
case PENDING_OPT:
|
|
pending = 1;
|
|
break;
|
|
case DISABLE_OPT:
|
|
enabled = 0;
|
|
break;
|
|
case TRACEPOINT_OPT:
|
|
tracepoint = 1;
|
|
break;
|
|
case QUALIFIED_OPT:
|
|
match_type = symbol_name_match_type::FULL;
|
|
break;
|
|
case EXPLICIT_SOURCE_OPT:
|
|
is_explicit = 1;
|
|
explicit_loc->source_filename = xstrdup (oarg);
|
|
break;
|
|
case EXPLICIT_FUNC_OPT:
|
|
is_explicit = 1;
|
|
explicit_loc->function_name = xstrdup (oarg);
|
|
break;
|
|
case EXPLICIT_LABEL_OPT:
|
|
is_explicit = 1;
|
|
explicit_loc->label_name = xstrdup (oarg);
|
|
break;
|
|
case EXPLICIT_LINE_OPT:
|
|
is_explicit = 1;
|
|
explicit_loc->line_offset = linespec_parse_line_offset (oarg);
|
|
break;
|
|
case FORCE_CONDITION_OPT:
|
|
force_condition = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (oind >= argc && !is_explicit)
|
|
error (_("-%s-insert: Missing <location>"),
|
|
dprintf ? "dprintf" : "break");
|
|
if (dprintf)
|
|
{
|
|
int format_num = is_explicit ? oind : oind + 1;
|
|
|
|
if (hardware || tracepoint)
|
|
error (_("-dprintf-insert: does not support -h or -a"));
|
|
if (format_num >= argc)
|
|
error (_("-dprintf-insert: Missing <format>"));
|
|
|
|
extra_string = mi_argv_to_format (argv + format_num, argc - format_num);
|
|
address = argv[oind];
|
|
}
|
|
else
|
|
{
|
|
if (is_explicit)
|
|
{
|
|
if (oind < argc)
|
|
error (_("-break-insert: Garbage following explicit location"));
|
|
}
|
|
else
|
|
{
|
|
if (oind < argc - 1)
|
|
error (_("-break-insert: Garbage following <location>"));
|
|
address = argv[oind];
|
|
}
|
|
}
|
|
|
|
/* Now we have what we need, let's insert the breakpoint! */
|
|
scoped_restore restore_breakpoint_reporting = setup_breakpoint_reporting ();
|
|
|
|
if (tracepoint)
|
|
{
|
|
/* Note that to request a fast tracepoint, the client uses the
|
|
"hardware" flag, although there's nothing of hardware related to
|
|
fast tracepoints -- one can implement slow tracepoints with
|
|
hardware breakpoints, but fast tracepoints are always software.
|
|
"fast" is a misnomer, actually, "jump" would be more appropriate.
|
|
A simulator or an emulator could conceivably implement fast
|
|
regular non-jump based tracepoints. */
|
|
type_wanted = hardware ? bp_fast_tracepoint : bp_tracepoint;
|
|
ops = breakpoint_ops_for_location_spec (nullptr, true);
|
|
}
|
|
else if (dprintf)
|
|
{
|
|
type_wanted = bp_dprintf;
|
|
ops = &code_breakpoint_ops;
|
|
}
|
|
else
|
|
{
|
|
type_wanted = hardware ? bp_hardware_breakpoint : bp_breakpoint;
|
|
ops = &code_breakpoint_ops;
|
|
}
|
|
|
|
if (is_explicit)
|
|
{
|
|
/* Error check -- we must have one of the other
|
|
parameters specified. */
|
|
if (explicit_loc->source_filename != NULL
|
|
&& explicit_loc->function_name == NULL
|
|
&& explicit_loc->label_name == NULL
|
|
&& explicit_loc->line_offset.sign == LINE_OFFSET_UNKNOWN)
|
|
error (_("-%s-insert: --source option requires --function, --label,"
|
|
" or --line"), dprintf ? "dprintf" : "break");
|
|
|
|
explicit_loc->func_name_match_type = match_type;
|
|
|
|
locspec = std::move (explicit_loc);
|
|
}
|
|
else
|
|
{
|
|
locspec = string_to_location_spec_basic (&address, current_language,
|
|
match_type);
|
|
if (*address)
|
|
error (_("Garbage '%s' at end of location"), address);
|
|
}
|
|
|
|
create_breakpoint (get_current_arch (), locspec.get (), condition, thread,
|
|
extra_string.c_str (),
|
|
force_condition,
|
|
0 /* condition and thread are valid. */,
|
|
temp_p, type_wanted,
|
|
ignore_count,
|
|
pending ? AUTO_BOOLEAN_TRUE : AUTO_BOOLEAN_FALSE,
|
|
ops, 0, enabled, 0, 0);
|
|
}
|
|
|
|
/* Implements the -break-insert command.
|
|
See the MI manual for the list of possible options. */
|
|
|
|
void
|
|
mi_cmd_break_insert (const char *command, char **argv, int argc)
|
|
{
|
|
mi_cmd_break_insert_1 (0, command, argv, argc);
|
|
}
|
|
|
|
/* Implements the -dprintf-insert command.
|
|
See the MI manual for the list of possible options. */
|
|
|
|
void
|
|
mi_cmd_dprintf_insert (const char *command, char **argv, int argc)
|
|
{
|
|
mi_cmd_break_insert_1 (1, command, argv, argc);
|
|
}
|
|
|
|
/* Implements the -break-condition command.
|
|
See the MI manual for the list of options. */
|
|
|
|
void
|
|
mi_cmd_break_condition (const char *command, char **argv, int argc)
|
|
{
|
|
enum option
|
|
{
|
|
FORCE_CONDITION_OPT,
|
|
};
|
|
|
|
static const struct mi_opt opts[] =
|
|
{
|
|
{"-force", FORCE_CONDITION_OPT, 0},
|
|
{ 0, 0, 0 }
|
|
};
|
|
|
|
/* Parse arguments. */
|
|
int oind = 0;
|
|
char *oarg;
|
|
bool force_condition = false;
|
|
|
|
while (true)
|
|
{
|
|
int opt = mi_getopt ("-break-condition", argc, argv,
|
|
opts, &oind, &oarg);
|
|
if (opt < 0)
|
|
break;
|
|
|
|
switch (opt)
|
|
{
|
|
case FORCE_CONDITION_OPT:
|
|
force_condition = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* There must be at least one more arg: a bpnum. */
|
|
if (oind >= argc)
|
|
error (_("-break-condition: Missing the <number> argument"));
|
|
|
|
int bpnum = atoi (argv[oind]);
|
|
|
|
/* The rest form the condition expr. */
|
|
std::string expr = "";
|
|
for (int i = oind + 1; i < argc; ++i)
|
|
{
|
|
expr += argv[i];
|
|
if (i + 1 < argc)
|
|
expr += " ";
|
|
}
|
|
|
|
set_breakpoint_condition (bpnum, expr.c_str (), 0 /* from_tty */,
|
|
force_condition);
|
|
}
|
|
|
|
enum wp_type
|
|
{
|
|
REG_WP,
|
|
READ_WP,
|
|
ACCESS_WP
|
|
};
|
|
|
|
void
|
|
mi_cmd_break_passcount (const char *command, char **argv, int argc)
|
|
{
|
|
int n;
|
|
int p;
|
|
struct tracepoint *t;
|
|
|
|
if (argc != 2)
|
|
error (_("Usage: tracepoint-number passcount"));
|
|
|
|
n = atoi (argv[0]);
|
|
p = atoi (argv[1]);
|
|
t = get_tracepoint (n);
|
|
|
|
if (t)
|
|
{
|
|
t->pass_count = p;
|
|
gdb::observers::breakpoint_modified.notify (t);
|
|
}
|
|
else
|
|
{
|
|
error (_("Could not find tracepoint %d"), n);
|
|
}
|
|
}
|
|
|
|
/* Insert a watchpoint. The type of watchpoint is specified by the
|
|
first argument:
|
|
-break-watch <expr> --> insert a regular wp.
|
|
-break-watch -r <expr> --> insert a read watchpoint.
|
|
-break-watch -a <expr> --> insert an access wp. */
|
|
|
|
void
|
|
mi_cmd_break_watch (const char *command, char **argv, int argc)
|
|
{
|
|
char *expr = NULL;
|
|
enum wp_type type = REG_WP;
|
|
enum opt
|
|
{
|
|
READ_OPT, ACCESS_OPT
|
|
};
|
|
static const struct mi_opt opts[] =
|
|
{
|
|
{"r", READ_OPT, 0},
|
|
{"a", ACCESS_OPT, 0},
|
|
{ 0, 0, 0 }
|
|
};
|
|
|
|
/* Parse arguments. */
|
|
int oind = 0;
|
|
char *oarg;
|
|
|
|
while (1)
|
|
{
|
|
int opt = mi_getopt ("-break-watch", argc, argv,
|
|
opts, &oind, &oarg);
|
|
|
|
if (opt < 0)
|
|
break;
|
|
switch ((enum opt) opt)
|
|
{
|
|
case READ_OPT:
|
|
type = READ_WP;
|
|
break;
|
|
case ACCESS_OPT:
|
|
type = ACCESS_WP;
|
|
break;
|
|
}
|
|
}
|
|
if (oind >= argc)
|
|
error (_("-break-watch: Missing <expression>"));
|
|
if (oind < argc - 1)
|
|
error (_("-break-watch: Garbage following <expression>"));
|
|
expr = argv[oind];
|
|
|
|
/* Now we have what we need, let's insert the watchpoint! */
|
|
switch (type)
|
|
{
|
|
case REG_WP:
|
|
watch_command_wrapper (expr, FROM_TTY, false);
|
|
break;
|
|
case READ_WP:
|
|
rwatch_command_wrapper (expr, FROM_TTY, false);
|
|
break;
|
|
case ACCESS_WP:
|
|
awatch_command_wrapper (expr, FROM_TTY, false);
|
|
break;
|
|
default:
|
|
error (_("-break-watch: Unknown watchpoint type."));
|
|
}
|
|
}
|
|
|
|
void
|
|
mi_cmd_break_commands (const char *command, char **argv, int argc)
|
|
{
|
|
counted_command_line break_command;
|
|
char *endptr;
|
|
int bnum;
|
|
struct breakpoint *b;
|
|
|
|
if (argc < 1)
|
|
error (_("USAGE: %s <BKPT> [<COMMAND> [<COMMAND>...]]"), command);
|
|
|
|
bnum = strtol (argv[0], &endptr, 0);
|
|
if (endptr == argv[0])
|
|
error (_("breakpoint number argument \"%s\" is not a number."),
|
|
argv[0]);
|
|
else if (*endptr != '\0')
|
|
error (_("junk at the end of breakpoint number argument \"%s\"."),
|
|
argv[0]);
|
|
|
|
b = get_breakpoint (bnum);
|
|
if (b == NULL)
|
|
error (_("breakpoint %d not found."), bnum);
|
|
|
|
int count = 1;
|
|
auto reader
|
|
= [&] (std::string &buffer)
|
|
{
|
|
const char *result = nullptr;
|
|
if (count < argc)
|
|
result = argv[count++];
|
|
return result;
|
|
};
|
|
|
|
if (is_tracepoint (b))
|
|
break_command = read_command_lines_1 (reader, 1,
|
|
[=] (const char *line)
|
|
{
|
|
validate_actionline (line, b);
|
|
});
|
|
else
|
|
break_command = read_command_lines_1 (reader, 1, 0);
|
|
|
|
breakpoint_set_commands (b, std::move (break_command));
|
|
}
|
|
|