@@ -1025,6 +1025,7 @@ COMMON_SFILES = \
break-catch-sig.c \
break-catch-syscall.c \
break-catch-throw.c \
+ break-cond-parse.c \
breakpoint.c \
bt-utils.c \
btrace.c \
@@ -1290,6 +1291,7 @@ HFILES_NO_SRCDIR = \
bfd-target.h \
bfin-tdep.h \
block.h \
+ break-cond-parse.h \
breakpoint.h \
bsd-kvm.h \
bsd-uthread.h \
@@ -67,6 +67,10 @@
break foo thread 1 task 1
watch var thread 2 task 3
+* For breakpoints that are created in the 'pending' state, any
+ 'thread' or 'task' keywords are parsed at the time the breakpoint is
+ created, rather than at the time the breakpoint becomes non-pending.
+
* New commands
maintenance print record-instruction [ N ]
new file mode 100644
@@ -0,0 +1,403 @@
+/* Copyright (C) 2023 Free Software Foundation, Inc.
+
+ 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 "gdbsupport/gdb_assert.h"
+#include "gdbsupport/selftest.h"
+#include "test-target.h"
+#include "scoped-mock-context.h"
+#include "break-cond-parse.h"
+#include "tid-parse.h"
+#include "ada-lang.h"
+
+/* When parsing tokens from a string, which direction are we parsing?
+
+ Given the following string and pointer 'ptr':
+
+ ABC DEF GHI JKL
+ ^
+ ptr
+
+ Parsing 'forward' will return the token 'GHI' and update 'ptr' to point
+ between GHI and JKL. Parsing 'backward' will return the token 'DEF' and
+ update 'ptr' to point between ABC and DEF.
+*/
+
+enum class parse_direction
+{
+ /* Parse the next token forwards. */
+ forward,
+
+ /* Parse the previous token backwards. */
+ backward
+};
+
+struct token
+{
+ /* Create a new token. START points to the first character of the new
+ token, while END points at the last character of the new token.
+
+ Neither START or END can be nullptr, and both START and END must point
+ into the same C style string (i.e. there should be no null character
+ between START and END). END must be equal to, or greater than START,
+ that is, it is not possible to create a zero length token. */
+
+ token (const char *start, const char *end)
+ : m_start (start),
+ m_end (end)
+ {
+ gdb_assert (m_end >= m_start);
+ gdb_assert (m_start + strlen (m_start) > m_end);
+ }
+
+ /* Return a pointer to the first character of this token. */
+ const char *start () const
+ {
+ return m_start;
+ }
+
+ /* Return a pointer to the last character of this token. */
+ const char *end () const
+ {
+ return m_end;
+ }
+
+ /* Return the length of the token. */
+ size_t length () const
+ {
+ return m_end - m_start + 1;
+ }
+
+ /* Return true if this token matches STR. If this token is shorter than
+ STR then only a partial match is performed and true will be returned
+ if the token length sub-string matches. Otherwise, return false. */
+ bool matches (const char *str) const
+ {
+ return strncmp (m_start, str, length ()) == 0;
+ }
+
+private:
+ /* The first character of this token. */
+ const char *m_start;
+
+ /* The last character of this token. */
+ const char *m_end;
+};
+
+/* Find the next token in DIRECTION from *CURR. */
+
+static token
+find_next_token (const char **curr, parse_direction direction)
+{
+ const char *tok_start, *tok_end;
+
+ if (direction == parse_direction::forward)
+ {
+ *curr = skip_spaces (*curr);
+ tok_start = *curr;
+ *curr = skip_to_space (*curr);
+ tok_end = *curr - 1;
+ }
+ else
+ {
+ gdb_assert (direction == parse_direction::backward);
+
+ while (isspace (**curr))
+ --(*curr);
+
+ tok_end = *curr;
+
+ while (!isspace (**curr))
+ --(*curr);
+
+ tok_start = (*curr) + 1;
+ }
+
+ return token (tok_start, tok_end);
+}
+
+/* See break-cond-parse.h. */
+
+void
+create_breakpoint_parse_arg_string
+ (const char *tok, gdb::unique_xmalloc_ptr<char> *cond_string,
+ int *thread, int *task, gdb::unique_xmalloc_ptr<char> *rest, bool *force)
+{
+ /* Set up the defaults. */
+ cond_string->reset ();
+ rest->reset ();
+ *thread = -1;
+ *task = -1;
+ *force = false;
+
+ if (tok == nullptr)
+ return;
+
+ const char *cond_start = nullptr;
+ const char *cond_end = nullptr;
+ parse_direction direction = parse_direction::forward;
+ while (*tok != '\0')
+ {
+ /* Find the next token. If moving backward and this token starts at
+ the same location as the condition then we must have found the
+ other end of the condition string -- we're done. */
+ token t = find_next_token (&tok, direction);
+ if (direction == parse_direction::backward && t.start () <= cond_start)
+ {
+ cond_end = t.end () + 1;
+ break;
+ }
+
+ /* We only have a single flag option to check for. All the other
+ options take a value so require an additional token to be found. */
+ if (t.length () > 0 && t.matches ("-force-condition"))
+ {
+ *force = true;
+ continue;
+ }
+
+ /* Maybe the first token was the last token in the string. If this
+ is the case then we definitely can't try to extract a value
+ token. This also means that the token T is meaningless. Reset
+ TOK to point at the start of the unknown content and break out of
+ the loop. We'll record the unknown part of the string outside of
+ the scanning loop (below). */
+ if (direction == parse_direction::forward && *tok == '\0')
+ {
+ tok = t.start ();
+ break;
+ }
+
+ /* As before, find the next token and, if we are scanning backwards,
+ check that we have not reached the start of the condition string. */
+ token v = find_next_token (&tok, direction);
+ if (direction == parse_direction::backward && v.start () <= cond_start)
+ {
+ /* Use token T here as that must also be part of the condition
+ string. */
+ cond_end = t.end () + 1;
+ break;
+ }
+
+ /* When moving backward we will first parse the value token then the
+ keyword token, so swap them now. */
+ if (direction == parse_direction::backward)
+ std::swap (t, v);
+
+ /* Check for valid option in token T. If we find a valid option then
+ parse the value from the token V. Except for 'if', that's handled
+ differently.
+
+ For the 'if' token we need to capture the entire condition
+ string, so record the start of the condition string and then
+ start scanning backwards looking for the end of the condition
+ string. */
+ if (direction == parse_direction::forward && t.length () > 0
+ && t.matches ("if"))
+ {
+ cond_start = v.start ();
+ tok = tok + strlen (tok);
+ gdb_assert (*tok == '\0');
+ --tok;
+ direction = parse_direction::backward;
+ continue;
+ }
+ else if (t.length () > 0 && t.matches ("thread"))
+ {
+ const char *tmptok;
+ struct thread_info *thr;
+
+ if (*thread != -1)
+ error(_("You can specify only one thread."));
+
+ if (*task != -1)
+ error (_("You can specify only one of thread or task."));
+
+ thr = parse_thread_id (v.start (), &tmptok);
+ const char *expected_end = skip_spaces (v.end () + 1);
+ if (tmptok != expected_end)
+ error (_("Junk after thread keyword."));
+ *thread = thr->global_num;
+ }
+ else if (t.length () > 0 && t.matches ("task"))
+ {
+ char *tmptok;
+
+ if (*task != -1)
+ error(_("You can specify only one task."));
+
+ if (*thread != -1)
+ error (_("You can specify only one of thread or task."));
+
+ *task = strtol (v.start (), &tmptok, 0);
+ const char *expected_end = v.end () + 1;
+ if (tmptok != expected_end)
+ error (_("Junk after task keyword."));
+ if (!valid_task_id (*task))
+ error (_("Unknown task %d."), *task);
+ }
+ else
+ {
+ /* An unknown token. If we are scanning forward then reset TOK
+ to point at the start of the unknown content, we record this
+ outside of the scanning loop (below).
+
+ If we are scanning backward then unknown content is assumed to
+ be the other end of the condition string, obviously, this is
+ just a heuristic, we could be looking at a mistyped command
+ line, but this will be spotted when the condition is
+ eventually evaluated.
+
+ Either way, no more scanning is required after this. */
+ if (direction == parse_direction::forward)
+ tok = t.start ();
+ else
+ {
+ gdb_assert (direction == parse_direction::backward);
+ cond_end = v.end () + 1;
+ }
+ break;
+ }
+ }
+
+ if (cond_start != nullptr)
+ {
+ /* If we found the start of a condition string then we should have
+ switched to backward scan mode, and found the end of the condition
+ string. Capture the whole condition string into COND_STRING now. */
+ gdb_assert (direction == parse_direction::backward);
+ gdb_assert (cond_end != nullptr);
+ cond_string->reset (savestring (cond_start, cond_end - cond_start));
+ }
+ else if (*tok != '\0')
+ {
+ /* If we didn't have a condition start pointer then we should still
+ be in forward scanning mode. If we didn't reach the end of the
+ input string (TOK is not at the null character) then the rest of
+ the input string is garbage that we didn't understand.
+
+ Record the unknown content into REST. The caller of this function
+ will report this as an error later on. We could report the error
+ here, but we prefer to allow the caller to run other checks, and
+ prioritise other errors before reporting this problem. */
+ gdb_assert (direction == parse_direction::forward);
+ rest->reset (savestring (tok, strlen (tok)));
+ }
+}
+
+#if GDB_SELF_TEST
+
+namespace selftests {
+
+/* Run a single test of the create_breakpoint_parse_arg_string function.
+ INPUT is passed to create_breakpoint_parse_arg_string while all other
+ arguments are the expected output from
+ create_breakpoint_parse_arg_string. */
+
+static void
+test (const char *input, const char *condition, int thread = -1,
+ int task = -1, bool force = false, const char *rest = nullptr)
+{
+ gdb::unique_xmalloc_ptr<char> extracted_condition;
+ gdb::unique_xmalloc_ptr<char> extracted_rest;
+ int extracted_thread, extracted_task;
+ bool extracted_force_condition;
+ std::string exception_msg;
+
+ try
+ {
+ create_breakpoint_parse_arg_string (input, &extracted_condition,
+ &extracted_thread,
+ &extracted_task, &extracted_rest,
+ &extracted_force_condition);
+ }
+ catch (const gdb_exception_error &ex)
+ {
+ string_file buf;
+
+ exception_print (&buf, ex);
+ exception_msg = buf.release ();
+ }
+
+ if ((condition == nullptr) != (extracted_condition.get () == nullptr)
+ || (condition != nullptr
+ && strcmp (condition, extracted_condition.get ()) != 0)
+ || (rest == nullptr) != (extracted_rest.get () == nullptr)
+ || (rest != nullptr && strcmp (rest, extracted_rest.get ()) != 0)
+ || thread != extracted_thread
+ || task != extracted_task
+ || force != extracted_force_condition
+ || !exception_msg.empty ())
+ {
+ if (run_verbose ())
+ {
+ debug_printf ("input: '%s'\n", input);
+ debug_printf ("condition: '%s'\n", extracted_condition.get ());
+ debug_printf ("rest: '%s'\n", extracted_rest.get ());
+ debug_printf ("thread: %d\n", extracted_thread);
+ debug_printf ("task: %d\n", extracted_task);
+ debug_printf ("forced: %s\n",
+ extracted_force_condition ? "true" : "false");
+ debug_printf ("exception: '%s'\n", exception_msg.c_str ());
+ }
+
+ /* Report the failure. */
+ SELF_CHECK (false);
+ }
+}
+
+/* Test the create_breakpoint_parse_arg_string function. Just wraps
+ multiple calls to the test function above. */
+
+static void
+create_breakpoint_parse_arg_string_tests (void)
+{
+ gdbarch *arch = current_inferior ()->gdbarch;
+ scoped_restore_current_pspace_and_thread restore;
+ scoped_mock_context<test_target_ops> mock_target (arch);
+
+ int global_thread_num = mock_target.mock_thread.global_num;
+
+ test (" if blah ", "blah");
+ test (" if blah thread 1", "blah", global_thread_num);
+ test (" if blah thread 1 ", "blah", global_thread_num);
+ test ("thread 1 woof", nullptr, global_thread_num, -1, false, "woof");
+ test ("thread 1 X", nullptr, global_thread_num, -1, false, "X");
+ test (" if blah thread 1 -force-condition", "blah", global_thread_num,
+ -1, true);
+ test (" -force-condition if blah thread 1", "blah", global_thread_num,
+ -1, true);
+ test (" -force-condition if blah thread 1 ", "blah", global_thread_num,
+ -1, true);
+ test ("thread 1 -force-condition if blah", "blah", global_thread_num,
+ -1, true);
+ test ("if (A::outer::func ())", "(A::outer::func ())");
+}
+
+} // namespace selftests
+#endif /* GDB_SELF_TEST */
+
+void _initialize_break_cond_parse ();
+void
+_initialize_break_cond_parse ()
+{
+#if GDB_SELF_TEST
+ selftests::register_test
+ ("create_breakpoint_parse_arg_string",
+ selftests::create_breakpoint_parse_arg_string_tests);
+#endif
+}
new file mode 100644
@@ -0,0 +1,47 @@
+/* Copyright (C) 2023 Free Software Foundation, Inc.
+
+ 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/>. */
+
+#if !defined (BREAK_COND_PARSE_H)
+#define BREAK_COND_PARSE_H 1
+
+/* Given TOK, a string possibly containing a condition, thread, task and
+ force-condition flag, as accepted by the 'break' command, extract the
+ condition string, thread and task numbers, and the force_condition flag,
+ then set *COND_STRING, *THREAD, *TASK, and *FORCE.
+
+ As TOK is parsed, if an unknown keyword is encountered before the 'if'
+ keyword then everything starting from the unknown keyword is placed into
+ *REST.
+
+ Both *COND and *REST are initialized to nullptr. If no 'if' keyword is
+ found then *COND will be returned as nullptr. If no unknown content is
+ found then *REST is returned as nullptr.
+
+ If no thread is found, *THREAD is set to -1. If no task is found, *TASK
+ is set to -1. If the -force-condition flag is not found then *FORCE is
+ set to false.
+
+ Due to the free-form nature that the string TOK might take (a 'thread'
+ keyword can appear before or after an 'if' condition) then we end up
+ having to check for keywords from both the start of TOK and the end of
+ TOK. */
+
+extern void create_breakpoint_parse_arg_string
+ (const char *tok, gdb::unique_xmalloc_ptr<char> *cond_string,
+ int *thread, int *task, gdb::unique_xmalloc_ptr<char> *rest, bool *force);
+
+#endif
@@ -68,6 +68,7 @@
#include "tid-parse.h"
#include "cli/cli-style.h"
#include "cli/cli-decode.h"
+#include "break-cond-parse.h"
/* readline include files */
#include "readline/tilde.h"
@@ -6212,20 +6213,7 @@ print_breakpoint_location (const breakpoint *b,
uiout->field_stream ("at", stb);
}
else
- {
- uiout->field_string ("pending", b->locspec->to_string ());
- /* If extra_string is available, it could be holding a condition
- or dprintf arguments. In either case, make sure it is printed,
- too, but only for non-MI streams. */
- if (!uiout->is_mi_like_p () && b->extra_string != NULL)
- {
- if (b->type == bp_dprintf)
- uiout->text (",");
- else
- uiout->text (" ");
- uiout->text (b->extra_string.get ());
- }
- }
+ uiout->field_string ("pending", b->locspec->to_string ());
if (loc && is_breakpoint (b)
&& breakpoint_condition_evaluation_mode () == condition_evaluation_target
@@ -8552,8 +8540,8 @@ code_breakpoint::code_breakpoint (struct gdbarch *gdbarch_,
gdb_assert (extra_string != nullptr);
update_dprintf_command_list (this);
}
- else if (extra_string != nullptr)
- error (_("Garbage '%s' at end of command"), extra_string.get ());
+ else
+ gdb_assert (extra_string == nullptr);
/* The order of the locations is now stable. Set the location
condition using the location's number. */
@@ -8780,160 +8768,6 @@ check_fast_tracepoint_sals (struct gdbarch *gdbarch,
}
}
-/* Given TOK, a string specification of condition and thread, as
- accepted by the 'break' command, extract the condition
- string and thread number and set *COND_STRING and *THREAD.
- PC identifies the context at which the condition should be parsed.
- If no condition is found, *COND_STRING is set to NULL.
- If no thread is found, *THREAD is set to -1. */
-
-static void
-find_condition_and_thread (const char *tok, CORE_ADDR pc,
- gdb::unique_xmalloc_ptr<char> *cond_string,
- int *thread, int *task,
- gdb::unique_xmalloc_ptr<char> *rest)
-{
- cond_string->reset ();
- *thread = -1;
- *task = -1;
- rest->reset ();
- bool force = false;
-
- while (tok && *tok)
- {
- const char *end_tok;
- int toklen;
- const char *cond_start = NULL;
- const char *cond_end = NULL;
-
- tok = skip_spaces (tok);
-
- if ((*tok == '"' || *tok == ',') && rest)
- {
- rest->reset (savestring (tok, strlen (tok)));
- return;
- }
-
- end_tok = skip_to_space (tok);
-
- toklen = end_tok - tok;
-
- if (toklen >= 1 && strncmp (tok, "if", toklen) == 0)
- {
- tok = cond_start = end_tok + 1;
- try
- {
- parse_exp_1 (&tok, pc, block_for_pc (pc), 0);
- }
- catch (const gdb_exception_error &)
- {
- if (!force)
- throw;
- else
- tok = tok + strlen (tok);
- }
- cond_end = tok;
- cond_string->reset (savestring (cond_start, cond_end - cond_start));
- }
- else if (toklen >= 1 && strncmp (tok, "-force-condition", toklen) == 0)
- {
- tok = tok + toklen;
- force = true;
- }
- else if (toklen >= 1 && strncmp (tok, "thread", toklen) == 0)
- {
- const char *tmptok;
- struct thread_info *thr;
-
- if (*thread != -1)
- error(_("You can specify only one thread."));
-
- if (*task != -1)
- error (_("You can specify only one of thread or task."));
-
- tok = end_tok + 1;
- thr = parse_thread_id (tok, &tmptok);
- if (tok == tmptok)
- error (_("Junk after thread keyword."));
- *thread = thr->global_num;
- tok = tmptok;
- }
- else if (toklen >= 1 && strncmp (tok, "task", toklen) == 0)
- {
- char *tmptok;
-
- if (*task != -1)
- error(_("You can specify only one task."));
-
- if (*thread != -1)
- error (_("You can specify only one of thread or task."));
-
- tok = end_tok + 1;
- *task = strtol (tok, &tmptok, 0);
- if (tok == tmptok)
- error (_("Junk after task keyword."));
- if (!valid_task_id (*task))
- error (_("Unknown task %d."), *task);
- tok = tmptok;
- }
- else if (rest)
- {
- rest->reset (savestring (tok, strlen (tok)));
- return;
- }
- else
- error (_("Junk at end of arguments."));
- }
-}
-
-/* Call 'find_condition_and_thread' for each sal in SALS until a parse
- succeeds. The parsed values are written to COND_STRING, THREAD,
- TASK, and REST. See the comment of 'find_condition_and_thread'
- for the description of these parameters and INPUT. */
-
-static void
-find_condition_and_thread_for_sals (const std::vector<symtab_and_line> &sals,
- const char *input,
- gdb::unique_xmalloc_ptr<char> *cond_string,
- int *thread, int *task,
- gdb::unique_xmalloc_ptr<char> *rest)
-{
- int num_failures = 0;
- for (auto &sal : sals)
- {
- gdb::unique_xmalloc_ptr<char> cond;
- int thread_id = -1;
- int task_id = -1;
- gdb::unique_xmalloc_ptr<char> remaining;
-
- /* Here we want to parse 'arg' to separate condition from thread
- number. But because parsing happens in a context and the
- contexts of sals might be different, try each until there is
- success. Finding one successful parse is sufficient for our
- goal. When setting the breakpoint we'll re-parse the
- condition in the context of each sal. */
- try
- {
- find_condition_and_thread (input, sal.pc, &cond, &thread_id,
- &task_id, &remaining);
- *cond_string = std::move (cond);
- /* At most one of thread or task can be set. */
- gdb_assert (thread_id == -1 || task_id == -1);
- *thread = thread_id;
- *task = task_id;
- *rest = std::move (remaining);
- break;
- }
- catch (const gdb_exception_error &e)
- {
- num_failures++;
- /* If no sal remains, do not continue. */
- if (num_failures == sals.size ())
- throw;
- }
- }
-}
-
/* Decode a static tracepoint marker spec. */
static std::vector<symtab_and_line>
@@ -9044,6 +8878,44 @@ create_breakpoint (struct gdbarch *gdbarch,
|| (type_wanted != bp_dprintf
&& (extra_string == nullptr || parse_extra)));
+ /* Will hold either copies of the similarly named function argument, or
+ will hold a modified version of the function argument, depending on
+ the value of PARSE_EXTRA. */
+ gdb::unique_xmalloc_ptr<char> cond_string_copy;
+ gdb::unique_xmalloc_ptr<char> extra_string_copy;
+
+ if (parse_extra)
+ {
+ /* Parse EXTRA_STRING splitting the parts out. */
+ create_breakpoint_parse_arg_string (extra_string, &cond_string_copy,
+ &thread, &task, &extra_string_copy,
+ &force_condition);
+
+ /* We could check that EXTRA_STRING_COPY is empty at this point -- it
+ should be, as we only get here for things that are not bp_dprintf,
+ however, we prefer to give the location spec parser a chance to
+ run first, this means the user will get errors about invalid
+ location spec instead of an error about garbage at the end of the
+ command line.
+
+ We still do the EXTRA_STRING_COPY is empty check, just later in
+ this function. */
+
+ gdb_assert (thread == -1 || thread > 0);
+ gdb_assert (task == -1 || task > 0);
+ }
+ else
+ {
+ if (cond_string != nullptr)
+ cond_string_copy.reset (xstrdup (cond_string));
+ if (extra_string != nullptr)
+ extra_string_copy.reset (xstrdup (extra_string));
+ }
+
+ /* Clear these. Updated values are now held in the *_copy locals. */
+ cond_string = nullptr;
+ extra_string = nullptr;
+
try
{
ops->create_sals_from_location_spec (locspec, &canonical);
@@ -9079,6 +8951,13 @@ create_breakpoint (struct gdbarch *gdbarch,
throw;
}
+ /* The only bp_dprintf should have anything in EXTRA_STRING_COPY by this
+ point. For all other breakpoints this indicates an error. We could
+ place this check earlier in the function, but we prefer to see errors
+ from the location spec parser before we see this error message. */
+ if (type_wanted != bp_dprintf && extra_string_copy.get () != nullptr)
+ error (_("Garbage '%s' at end of command"), extra_string_copy.get ());
+
if (!pending && canonical.lsals.empty ())
return 0;
@@ -9102,62 +8981,31 @@ create_breakpoint (struct gdbarch *gdbarch,
breakpoint. */
if (!pending)
{
- gdb::unique_xmalloc_ptr<char> cond_string_copy;
- gdb::unique_xmalloc_ptr<char> extra_string_copy;
-
- if (parse_extra)
+ /* Check the validity of the condition. We should error out if the
+ condition is invalid at all of the locations and if it is not
+ forced. In the PARSE_EXTRA case above, this check is done when
+ parsing the EXTRA_STRING. */
+ if (cond_string_copy.get () != nullptr && !force_condition)
{
- gdb_assert (type_wanted != bp_dprintf);
-
- gdb::unique_xmalloc_ptr<char> rest;
- gdb::unique_xmalloc_ptr<char> cond;
-
+ int num_failures = 0;
const linespec_sals &lsal = canonical.lsals[0];
-
- find_condition_and_thread_for_sals (lsal.sals, extra_string,
- &cond, &thread, &task, &rest);
-
- if (rest.get () != nullptr && *(rest.get ()) != '\0')
- error (_("Garbage '%s' at end of command"), rest.get ());
-
- cond_string_copy = std::move (cond);
- extra_string_copy = std::move (rest);
- }
- else
- {
- /* Check the validity of the condition. We should error out
- if the condition is invalid at all of the locations and
- if it is not forced. In the PARSE_EXTRA case above, this
- check is done when parsing the EXTRA_STRING. */
- if (cond_string != nullptr && !force_condition)
+ for (const auto &sal : lsal.sals)
{
- int num_failures = 0;
- const linespec_sals &lsal = canonical.lsals[0];
- for (const auto &sal : lsal.sals)
+ const char *cond = cond_string_copy.get ();
+ try
{
- const char *cond = cond_string;
- try
- {
- parse_exp_1 (&cond, sal.pc, block_for_pc (sal.pc), 0);
- /* One success is sufficient to keep going. */
- break;
- }
- catch (const gdb_exception_error &)
- {
- num_failures++;
- /* If this is the last sal, error out. */
- if (num_failures == lsal.sals.size ())
- throw;
- }
+ parse_exp_1 (&cond, sal.pc, block_for_pc (sal.pc), 0);
+ /* One success is sufficient to keep going. */
+ break;
+ }
+ catch (const gdb_exception_error &)
+ {
+ num_failures++;
+ /* If this is the last sal, error out. */
+ if (num_failures == lsal.sals.size ())
+ throw;
}
}
-
- /* Create a private copy of condition string. */
- if (cond_string)
- cond_string_copy.reset (xstrdup (cond_string));
- /* Create a private copy of any extra string. */
- if (extra_string)
- extra_string_copy.reset (xstrdup (extra_string));
}
ops->create_breakpoints_sal (gdbarch, &canonical,
@@ -9174,21 +9022,15 @@ create_breakpoint (struct gdbarch *gdbarch,
type_wanted);
b->locspec = locspec->clone ();
- if (parse_extra)
- b->cond_string = NULL;
- else
- {
- /* Create a private copy of condition string. */
- b->cond_string.reset (cond_string != NULL
- ? xstrdup (cond_string)
- : NULL);
- b->thread = thread;
- }
+ /* Create a private copy of condition string. */
+ b->cond_string = std::move (cond_string_copy);
+
+ b->thread = thread;
+ b->task = task;
/* Create a private copy of any extra string. */
- b->extra_string.reset (extra_string != NULL
- ? xstrdup (extra_string)
- : NULL);
+ b->extra_string = std::move (extra_string_copy);
+
b->ignore_count = ignore_count;
b->disposition = tempflag ? disp_del : disp_donttouch;
b->condition_not_parsed = 1;
@@ -9197,9 +9039,12 @@ create_breakpoint (struct gdbarch *gdbarch,
&& type_wanted != bp_hardware_breakpoint) || thread != -1)
b->pspace = current_program_space;
+ if (type_wanted == bp_dprintf)
+ update_dprintf_command_list (b.get ());
+
install_breakpoint (internal, std::move (b), 0);
}
-
+
if (canonical.lsals.size () > 1)
{
warning (_("Multiple breakpoints were set.\nUse the "
@@ -12915,23 +12760,6 @@ code_breakpoint::location_spec_to_sals (location_spec *locspec,
{
for (auto &sal : sals)
resolve_sal_pc (&sal);
- if (condition_not_parsed && extra_string != NULL)
- {
- gdb::unique_xmalloc_ptr<char> local_cond, local_extra;
- int local_thread, local_task;
-
- find_condition_and_thread_for_sals (sals, extra_string.get (),
- &local_cond, &local_thread,
- &local_task, &local_extra);
- gdb_assert (cond_string == nullptr);
- if (local_cond != nullptr)
- cond_string = std::move (local_cond);
- thread = local_thread;
- task = local_task;
- if (local_extra != nullptr)
- extra_string = std::move (local_extra);
- condition_not_parsed = 0;
- }
if (type == bp_static_tracepoint)
sals[0] = update_static_tracepoint (this, sals[0]);
@@ -179,6 +179,10 @@ gdb_test "break -q main if (1==1) thread 999" \
"Unknown thread 999\\."
gdb_test "break -q main thread 999 if (1==1)" \
"Unknown thread 999\\."
+gdb_test "break -q main if (1==1) thread 999 -force-condition" \
+ "Unknown thread 999\\."
+gdb_test "break -q main thread 999 if (1==1) -force-condition" \
+ "Unknown thread 999\\."
# Verify that both if and thread can be distinguished from a breakpoint
# address expression.
@@ -186,20 +190,34 @@ gdb_test "break *main if (1==1) thread 999" \
"Unknown thread 999\\."
gdb_test "break *main thread 999 if (1==1)" \
"Unknown thread 999\\."
+gdb_test "break *main if (1==1) thread 999 -force-condition" \
+ "Unknown thread 999\\."
+gdb_test "break *main thread 999 if (1==1) -force-condition" \
+ "Unknown thread 999\\."
# Similarly for task.
gdb_test "break *main if (1==1) task 999" \
"Unknown task 999\\."
gdb_test "break *main task 999 if (1==1)" \
"Unknown task 999\\."
+gdb_test "break *main if (1==1) task 999 -force-condition" \
+ "Unknown task 999\\."
+gdb_test "break *main task 999 if (1==1) -force-condition" \
+ "Unknown task 999\\."
-# GDB accepts abbreviations for "thread" and "task".
+# GDB accepts abbreviations for "thread" and "task" and "-force-condition"
gdb_test "break *main if (1==1) t 999" \
"Unknown thread 999\\."
gdb_test "break *main if (1==1) th 999" \
"Unknown thread 999\\."
gdb_test "break *main if (1==1) ta 999" \
"Unknown task 999\\."
+gdb_test "break *main if (1==1) t 999 -force" \
+ "Unknown thread 999\\."
+gdb_test "break *main if (1==1) th 999 -force" \
+ "Unknown thread 999\\."
+gdb_test "break *main if (1==1) ta 999 -force" \
+ "Unknown task 999\\."
set test "run until breakpoint at marker3"
gdb_test_multiple "continue" $test {
@@ -170,7 +170,8 @@ gdb_test "info break" \
\[\t \]+stop only if k == 1.*
\[\t \]+print k.*
\[0-9\]+\[\t \]+breakpoint keep y.* in main at .*$srcfile:$mainline.*
-\[0-9\]+\[\t \]+breakpoint keep y.*PENDING.*pendshr.c:$bp2_loc if x > 3.*" \
+\[0-9\]+\[\t \]+breakpoint keep y.*PENDING.*pendshr.c:$bp2_loc.*
+\\s+stop only if x > 3.*" \
"multiple pending breakpoints"
@@ -195,8 +196,10 @@ gdb_test "info break" \
\[\t \]+stop only if k == 1.*
\[\t \]+print k.*
\[0-9\]+\[\t \]+breakpoint keep y.* in main at .*$srcfile:$mainline.*
-\[0-9\]+\[\t \]+breakpoint keep y.*PENDING.*pendshr.c:$bp2_loc if x > 3.*
-\[0-9\]+\[\t \]+breakpoint keep y.*PENDING.*pendshr.c:$bp3_loc.*ignore next 2 hits.*" \
+\[0-9\]+\[\t \]+breakpoint keep y.*PENDING.*pendshr.c:$bp2_loc.*
+\\s+stop only if x > 3.*
+\[0-9\]+\[\t \]+breakpoint keep y.*PENDING.*pendshr.c:$bp3_loc.*
+\\s+ignore next 2 hits.*" \
"multiple pending breakpoints 2"
#
@@ -267,3 +270,17 @@ gdb_test "info break" \
\[0-9\]+\[\t \]+breakpoint keep y.* in main at .*$srcfile:$mainline.*
\[0-9\]+\[\t \]+breakpoint keep y.*PENDING.*imaginary.*" \
"verify pending breakpoint after restart"
+
+# Test GDB's parsing of pending breakpoint thread and condition.
+
+gdb_test_no_output "set breakpoint pending on"
+gdb_test "break foo if (unknown_var && another_unknown_var) thread 1" \
+ "Breakpoint $decimal \\(foo\\) pending\\."
+set bpnum [get_integer_valueof "\$bpnum" "*INVALID" \
+ "get number for foo breakpoint"]
+gdb_test "info breakpoints $bpnum" \
+ [multi_line \
+ "$bpnum\\s+breakpoint\\s+keep\\s+y\\s+<PENDING>\\s+foo" \
+ "\\s+stop only if \\(unknown_var && another_unknown_var\\)" \
+ "\\s+stop only in thread 1"] \
+ "check pending breakpoint on foo"
@@ -574,7 +574,7 @@ namespace eval $testfile {
allow-pending]} {
fail "set $tst"
} else {
- gdb_test "info break" ".*PENDING.*myfunction if foofoofoo == 1.*" $tst
+ gdb_test "info break" ".*PENDING.*myfunction\r\n\\s+stop only if foofoofoo == 1.*" $tst
}
gdb_exit
@@ -585,7 +585,7 @@ namespace eval $testfile {
allow-pending]} {
fail "set $tst"
} else {
- gdb_test "info break" ".*PENDING.*myfunction if arg == 0" $tst
+ gdb_test "info break" ".*PENDING.*myfunction\r\n\\s+stop only if arg == 0" $tst
gdb_load [standard_output_file $exefile]
gdb_test "info break" \
@@ -50,7 +50,8 @@ set bp_location1 [gdb_get_line_number "set breakpoint 1 here"]
# Set pending dprintf via MI.
set bp [mi_make_breakpoint_pending -number "1" -type "dprintf" \
-disp "keep" -enabled "y" -pending "pendfunc1" \
- -original-location "pendfunc1"]
+ -original-location "pendfunc1" \
+ -script {\["printf \\\"hello\\\""\]}]
mi_gdb_test "-dprintf-insert -f pendfunc1 \"hello\"" \
".*\\^done,$bp" "mi set dprintf"
new file mode 100644
@@ -0,0 +1,22 @@
+/* Copyright 2023 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/>. */
+
+int global_var = 0;
+
+void
+foo (int arg)
+{
+ global_var = arg;
+}
new file mode 100644
@@ -0,0 +1,85 @@
+/* Copyright 2023 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 <dlfcn.h>
+#include <pthread.h>
+#include <stdlib.h>
+
+pthread_barrier_t barrier;
+
+static void
+barrier_wait (pthread_barrier_t *b)
+{
+ int res = pthread_barrier_wait (b);
+ if (res != 0 && res != PTHREAD_BARRIER_SERIAL_THREAD)
+ abort ();
+}
+
+static void *
+thread_worker (void *arg)
+{
+ barrier_wait (&barrier);
+ return NULL;
+}
+
+void
+breakpt ()
+{
+ /* Nothing. */
+}
+
+int
+main (void)
+{
+ void *handle;
+ void (*func)(int);
+ pthread_t thread;
+
+ if (pthread_barrier_init (&barrier, NULL, 2) != 0)
+ abort ();
+
+ if (pthread_create (&thread, NULL, thread_worker, NULL) != 0)
+ abort ();
+
+ breakpt ();
+
+ /* Allow the worker thread to complete. */
+ barrier_wait (&barrier);
+
+ if (pthread_join (thread, NULL) != 0)
+ abort ();
+
+ breakpt ();
+
+ /* Now load the shared library. */
+ handle = dlopen (SHLIB_NAME, RTLD_LAZY);
+ if (handle == NULL)
+ abort ();
+
+ /* Find the function symbol. */
+ func = (void (*)(int)) dlsym (handle, "foo");
+
+ /* Call the library function. */
+ func (1);
+
+ /* Unload the shared library. */
+ if (dlclose (handle) != 0)
+ abort ();
+
+ breakpt ();
+
+ return 0;
+}
+
new file mode 100644
@@ -0,0 +1,108 @@
+# Copyright 2023 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/>.
+
+# This test checks that pending thread-specific breakpoints are
+# correctly deleted when the thread the breakpoint is for goes out of
+# scope.
+#
+# We also check that we can't create a pending thread-specific
+# breakpoint for a non-existent thread.
+
+require allow_shlib_tests
+
+standard_testfile
+
+set libname $testfile-lib
+set srcfile_lib $srcdir/$subdir/$libname.c
+set binfile_lib [standard_output_file $libname.so]
+
+if { [gdb_compile_shlib $srcfile_lib $binfile_lib {}] != "" } {
+ untested "failed to compile shared library 1"
+ return -1
+}
+
+set binfile_lib_target [gdb_download_shlib $binfile_lib]
+
+if { [prepare_for_testing "failed to prepare" $testfile $srcfile \
+ [list debug \
+ additional_flags=-DSHLIB_NAME=\"$binfile_lib_target\" \
+ shlib_load pthreads]] } {
+ return -1
+}
+
+gdb_locate_shlib $binfile_lib
+
+if ![runto_main] {
+ return 0
+}
+
+# Run until we have two threads.
+gdb_breakpoint "breakpt"
+gdb_continue_to_breakpoint "first breakpt call"
+
+# Confirm that we have a thread '2'.
+gdb_test "info threads" "\r\n\\s+2\\s+\[^\r\n\]+"
+
+# Create a pending, thread-specific, breakpoint on 'foo'.
+gdb_breakpoint "foo thread 2" allow-pending
+set bpnum [get_integer_valueof "\$bpnum" "*INVALID*" \
+ "get breakpoint number"]
+
+# Check we can't create a pending thread-specific breakpoint for a
+# non-existent thread.
+gdb_test "with breakpoint pending on -- break foo thread 99" \
+ "Unknown thread 99\\."
+
+# Continue to 'breakpt' again. Don't use gdb_continue_to_breakpoint
+# as we are looking for the thread exited and breakpoint deleted
+# messages.
+set output [list "Continuing\\."]
+
+if {!([target_info exists gdb_protocol]
+ && ([target_info gdb_protocol] == "remote"
+ || [target_info gdb_protocol] == "extended-remote"))} {
+ # Due to bug PR gdb/30129 we don't see the thread exited messages
+ # for remote targets. When this bit of this test can be cleaned
+ # up.
+ lappend output "\\\[Thread \[^\r\n\]+ exited\\\]"
+}
+
+lappend output "Thread-specific breakpoint $bpnum deleted - thread 2 no longer in the thread list\\." \
+ "" \
+ "Thread 1 \[^\r\n\]+, breakpt \\(\\) at \[^\r\n\]+" \
+ "$decimal\\s+\[^\r\n\]+"
+
+gdb_test "continue" \
+ [multi_line {*}$output] \
+ "second breakpt call"
+
+# Confirm breakpoint has been deleted.
+gdb_test "info breakpoints $bpnum" \
+ "No breakpoint or watchpoint matching '$bpnum'\\."
+
+# Continue again. This will pass through 'foo'. We should not stop
+# in 'foo', the breakpoint has been deleted. We should next stop in
+# breakpt again.
+gdb_test "continue" \
+ [multi_line \
+ "Continuing\\." \
+ "" \
+ "Thread 1 \[^\r\n\]+ hit Breakpoint $decimal, breakpt \\(\\) at \[^\r\n\]+" \
+ "$decimal\\s+\[^\r\n\]+"] \
+ "third breakpt call"
+gdb_test "bt 1" \
+ [multi_line \
+ "#0\\s+breakpt \\(\\) at \[^\r\n\]+" \
+ "\\(More stack frames follow\\.\\.\\.\\)"]