@@ -1051,6 +1051,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 \
@@ -1320,6 +1321,7 @@ HFILES_NO_SRCDIR = \
bfd-target.h \
bfin-tdep.h \
block.h \
+ break-cond-parse.h \
breakpoint.h \
bsd-kvm.h \
bsd-uthread.h \
@@ -10,6 +10,10 @@
function in Python. By default, the raw ptwrite payload is printed for
each ptwrite that is encountered.
+* 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.
+
* Python API
** Added gdb.record.clear. Clears the trace data of the current recording.
new file mode 100644
@@ -0,0 +1,702 @@
+/* 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"
+#include "exceptions.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
+};
+
+/* Find the next token in DIRECTION from *CURR. */
+
+static std::string_view
+find_next_token (const char **curr, parse_direction direction)
+{
+ const char *tok_start, *tok_end;
+
+ gdb_assert (**curr != '\0');
+
+ 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 std::string_view (tok_start, tok_end - tok_start + 1);
+}
+
+/* A class that represents a complete parsed token. Each token has a type
+ and a std::string_view into the original breakpoint condition string. */
+
+struct token
+{
+ /* The types a token might take. */
+ enum class type
+ {
+ /* These are the token types for the 'if', 'thread', 'inferior', and
+ 'task' keywords. The m_content for these token types is the value
+ passed to the keyword, not the keyword itself. */
+ CONDITION,
+ THREAD,
+ INFERIOR,
+ TASK,
+
+ /* This is the token used when we find unknown content, the m_content
+ for this token is the rest of the input string. */
+ REST,
+
+ /* This is the token for the -force-condition token, the m_content for
+ this token contains the keyword itself. */
+ FORCE
+ };
+
+ token (enum type type, std::string_view content)
+ : m_type (type),
+ m_content (std::move (content))
+ {
+ /* Nothing. */
+ }
+
+ /* Return a string representing this token. Only used for debug. */
+ std::string to_string () const
+ {
+ switch (m_type)
+ {
+ case type::CONDITION:
+ return string_printf ("{ CONDITION: \"%s\" }",
+ std::string (m_content).c_str ());
+ case type::THREAD:
+ return string_printf ("{ THREAD: \"%s\" }",
+ std::string (m_content).c_str ());
+ case type::INFERIOR:
+ return string_printf ("{ INFERIOR: \"%s\" }",
+ std::string (m_content).c_str ());
+ case type::TASK:
+ return string_printf ("{ TASK: \"%s\" }",
+ std::string (m_content).c_str ());
+ case type::REST:
+ return string_printf ("{ REST: \"%s\" }",
+ std::string (m_content).c_str ());
+ case type::FORCE:
+ return string_printf ("{ FORCE }");
+ default:
+ return "** unknown **";
+ }
+ }
+
+ /* The type of this token. */
+ const type &get_type () const
+ {
+ return m_type;
+ }
+
+ /* Return the value of this token. */
+ const std::string_view &get_value () const
+ {
+ gdb_assert (m_content.size () > 0);
+ return m_content;
+ }
+
+ /* Extend this token with the contents of OTHER. This only makes sense
+ if OTHER is the next token after this one in the original string,
+ however, enforcing that restriction is left to the caller of this
+ function.
+
+ When OTHER is a keyword/value token, e.g. 'thread 1', the m_content
+ for OTHER will only point to the '1'. However, as the m_content is a
+ std::string_view, then when we merge the m_content of OTHER into this
+ token we automatically merge in the 'thread' part too, as it
+ naturally sits between this token and OTHER. */
+
+ void
+ extend (const token &other)
+ {
+ m_content = std::string_view (this->m_content.data (),
+ (other.m_content.data ()
+ - this->m_content.data ()
+ + other.m_content.size ()));
+ }
+
+private:
+ /* The type of this token. */
+ type m_type;
+
+ /* The important content part of this token. The extend member function
+ depends on this being a std::string_view. */
+ std::string_view m_content;
+};
+
+/* Split STR, a breakpoint condition string, into a vector of tokens where
+ each token represents a component of the condition. Tokens are first
+ parsed from the front of STR until we encounter an 'if' token. At this
+ point tokens are parsed from the end of STR until we encounter an
+ unknown token, which we assume is the other end of the 'if' condition.
+ If when scanning forward we encounter an unknown token then the
+ remainder of STR is placed into a 'rest' token (the rest of the
+ string), and no backward scan is performed. */
+
+static std::vector<token>
+parse_all_tokens (const char *str)
+{
+ gdb_assert (str != nullptr);
+
+ std::vector<token> forward_results;
+ std::vector<token> backward_results;
+
+ const char *cond_start = nullptr;
+ const char *cond_end = nullptr;
+ parse_direction direction = parse_direction::forward;
+ std::vector<token> *curr_results = &forward_results;
+ while (*str != '\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. */
+ std::string_view t = find_next_token (&str, direction);
+ if (direction == parse_direction::backward && t.data () <= cond_start)
+ {
+ cond_end = &t.back ();
+ 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.
+ Additionally, we require that this flag be at least '-f', we
+ don't allow it to be abbreviated to '-'. */
+ if (t.length () > 1 && startswith ("-force-condition", t))
+ {
+ curr_results->emplace_back (token::type::FORCE, t);
+ 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 && *str == '\0')
+ {
+ str = t.data ();
+ 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. */
+ std::string_view v = find_next_token (&str, direction);
+ if (direction == parse_direction::backward && v.data () <= cond_start)
+ {
+ /* Use token T here as that must also be part of the condition
+ string. */
+ cond_end = &t.back ();
+ 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.
+
+ The order of these checks is important, at least the check for
+ 'thread' must occur before the check for 'task'. We accept
+ abbreviations of these token names, and 't' should resolve to
+ 'thread', which will only happen if we check 'thread' first. */
+ if (direction == parse_direction::forward && startswith ("if", t))
+ {
+ cond_start = v.data ();
+ str = str + strlen (str);
+ gdb_assert (*str == '\0');
+ --str;
+ direction = parse_direction::backward;
+ curr_results = &backward_results;
+ continue;
+ }
+ else if (startswith ("thread", t))
+ curr_results->emplace_back (token::type::THREAD, v);
+ else if (startswith ("inferior", t))
+ curr_results->emplace_back (token::type::INFERIOR, v);
+ else if (startswith ("task", t))
+ curr_results->emplace_back (token::type::TASK, v);
+ 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)
+ str = t.data ();
+ else
+ {
+ gdb_assert (direction == parse_direction::backward);
+ cond_end = &v.back ();
+ }
+ 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);
+
+ std::string_view v (cond_start, cond_end - cond_start + 1);
+
+ forward_results.emplace_back (token::type::CONDITION, v);
+ }
+ else if (*str != '\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);
+ gdb_assert (cond_end == nullptr);
+
+ std::string_view v (str, strlen (str));
+
+ forward_results.emplace_back (token::type::REST, v);
+ }
+
+ /* If we have tokens in the BACKWARD_RESULTS vector then this means that
+ we found an 'if' condition (which will be the last thing in the
+ FORWARD_RESULTS vector), and then we started a backward scan.
+
+ The last tokens from the input string (those after the 'if' condition)
+ will be the first tokens added to the BACKWARD_RESULTS vector, so the
+ last items in the BACKWARD_RESULTS vector are those next to the 'if'
+ condition.
+
+ Check the tokens in the BACKWARD_RESULTS vector from back to front.
+ If the tokens look invalid then we assume that they are actually part
+ of the 'if' condition, and merge the token with the 'if' condition.
+ If it turns out that this was incorrect and that instead the user just
+ messed up entering the token value, then this will show as an error
+ when parsing the 'if' condition.
+
+ Doing this allows us to handle things like:
+
+ break function if ( variable == thread )
+
+ Where 'thread' is a local variable within 'function'. When parsing
+ this we will initially see 'thread )' as a thread token with ')' as
+ the value. However, the following code will spot that ')' is not a
+ valid thread-id, and so we merge 'thread )' into the 'if' condition
+ string.
+
+ This code also handles the special treatment for '-force-condition',
+ which exists for backwards compatibility reasons. Traditionally this
+ flag, if it occurred immediately after the 'if' condition, would be
+ treated as part of the 'if' condition. When the breakpoint condition
+ parsing code was rewritten, this behaviour was retained. */
+ gdb_assert (backward_results.empty ()
+ || (forward_results.back ().get_type ()
+ == token::type::CONDITION));
+ while (!backward_results.empty ())
+ {
+ token &t = backward_results.back ();
+
+ if (t.get_type () == token::type::FORCE)
+ forward_results.back ().extend (std::move (t));
+ else if (t.get_type () == token::type::THREAD)
+ {
+ const char *end;
+ std::string v (t.get_value ());
+ if (is_thread_id (v.c_str (), &end) && *end == '\0')
+ break;
+ forward_results.back ().extend (std::move (t));
+ }
+ else if (t.get_type () == token::type::INFERIOR
+ || t.get_type () == token::type::TASK)
+ {
+ /* Place the token's value into a null-terminated string, parse
+ the string as a number and check that the entire string was
+ parsed. If this is true then this looks like a valid inferior
+ or task number, otherwise, assume an invalid id, and merge
+ this token with the 'if' token. */
+ char *end;
+ std::string v (t.get_value ());
+ (void) strtol (v.c_str (), &end, 0);
+ if (end > v.c_str () && *end == '\0')
+ break;
+ forward_results.back ().extend (std::move (t));
+ }
+ else
+ gdb_assert_not_reached ("unexpected token type");
+
+ /* If we found an actual valid token above then we will have broken
+ out of the loop. We only get here if the token was merged with
+ the 'if' condition, in which case we can discard the last token
+ and then check the token before that. */
+ backward_results.pop_back ();
+ }
+
+ /* If after the above checks we still have some tokens in the
+ BACKWARD_RESULTS vector, then these need to be appended to the
+ FORWARD_RESULTS vector. However, we first reverse the order so that
+ FORWARD_RESULTS retains the tokens in the order they appeared in the
+ input string. */
+ if (!backward_results.empty ())
+ forward_results.insert (forward_results.end (),
+ backward_results.rbegin (),
+ backward_results.rend ());
+
+ return forward_results;
+}
+
+/* Called when the global debug_breakpoint is true. Prints VEC to the
+ debug output stream. */
+
+static void
+dump_condition_tokens (const std::vector<token> &vec)
+{
+ gdb_assert (debug_breakpoint);
+
+ bool first = true;
+ std::string str = "Tokens: ";
+ for (const token &t : vec)
+ {
+ if (!first)
+ str += " ";
+ first = false;
+ str += t.to_string ();
+ }
+ breakpoint_debug_printf ("%s", str.c_str ());
+}
+
+/* See break-cond-parse.h. */
+
+void
+create_breakpoint_parse_arg_string
+ (const char *str, gdb::unique_xmalloc_ptr<char> *cond_string_ptr,
+ int *thread_ptr, int *inferior_ptr, int *task_ptr,
+ gdb::unique_xmalloc_ptr<char> *rest_ptr, bool *force_ptr)
+{
+ /* Set up the defaults. */
+ cond_string_ptr->reset ();
+ rest_ptr->reset ();
+ *thread_ptr = -1;
+ *inferior_ptr = -1;
+ *task_ptr = -1;
+ *force_ptr = false;
+
+ if (str == nullptr)
+ return;
+
+ /* Split STR into a series of tokens. */
+ std::vector<token> tokens = parse_all_tokens (str);
+ if (debug_breakpoint)
+ dump_condition_tokens (tokens);
+
+ /* Temporary variables. Initialised to the default state, then updated
+ as we parse TOKENS. If all of TOKENS is parsed successfully then the
+ state from these variables is copied into the output arguments before
+ the function returns. */
+ int thread = -1, inferior = -1, task = -1;
+ bool force = false;
+ gdb::unique_xmalloc_ptr<char> cond_string, rest;
+
+ for (const token &t : tokens)
+ {
+ switch (t.get_type ())
+ {
+ case token::type::FORCE:
+ force = true;
+ break;
+ case token::type::THREAD:
+ {
+ if (thread != -1)
+ error ("You can specify only one thread.");
+ if (task != -1 || inferior != -1)
+ error ("You can specify only one of thread, inferior, or task.");
+ const char *tmptok;
+ thread_info *thr
+ = parse_thread_id (std::string (t.get_value ()).c_str (),
+ &tmptok);
+ gdb_assert (*tmptok == '\0');
+ thread = thr->global_num;
+ }
+ break;
+ case token::type::INFERIOR:
+ {
+ if (inferior != -1)
+ error ("You can specify only one inferior.");
+ if (task != -1 || thread != -1)
+ error ("You can specify only one of thread, inferior, or task.");
+ char *tmptok;
+ long inferior_id
+ = strtol (std::string (t.get_value ()).c_str (), &tmptok, 0);
+ if (*tmptok != '\0')
+ error (_("Junk '%s' after inferior keyword."), tmptok);
+ if (inferior_id > INT_MAX)
+ error (_("No inferior number '%ld'"), inferior_id);
+ inferior = static_cast<int> (inferior_id);
+ struct inferior *inf = find_inferior_id (inferior);
+ if (inf == nullptr)
+ error (_("No inferior number '%d'"), inferior);
+ }
+ break;
+ case token::type::TASK:
+ {
+ if (task != -1)
+ error ("You can specify only one task.");
+ if (inferior != -1 || thread != -1)
+ error ("You can specify only one of thread, inferior, or task.");
+ char *tmptok;
+ long task_id
+ = strtol (std::string (t.get_value ()).c_str (), &tmptok, 0);
+ if (*tmptok != '\0')
+ error (_("Junk '%s' after task keyword."), tmptok);
+ if (task_id > INT_MAX)
+ error (_("Unknown task %ld"), task_id);
+ task = static_cast<int> (task_id);
+ if (!valid_task_id (task))
+ error (_("Unknown task %d."), task);
+ }
+ break;
+ case token::type::CONDITION:
+ cond_string.reset (savestring (t.get_value ().data (),
+ t.get_value ().size ()));
+ break;
+ case token::type::REST:
+ rest.reset (savestring (t.get_value ().data (),
+ t.get_value ().size ()));
+ break;
+ }
+ }
+
+ /* Move results into the output locations. */
+ *force_ptr = force;
+ *thread_ptr = thread;
+ *inferior_ptr = inferior;
+ *task_ptr = task;
+ rest_ptr->reset (rest.release ());
+ cond_string_ptr->reset (cond_string.release ());
+}
+
+#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 inferior = -1, int task = -1, bool force = false,
+ const char *rest = nullptr, const char *error_msg = nullptr)
+{
+ gdb::unique_xmalloc_ptr<char> extracted_condition;
+ gdb::unique_xmalloc_ptr<char> extracted_rest;
+ int extracted_thread, extracted_inferior, extracted_task;
+ bool extracted_force_condition;
+ std::string exception_msg, error_str;
+
+ if (error_msg != nullptr)
+ error_str = std::string (error_msg) + "\n";
+
+ try
+ {
+ create_breakpoint_parse_arg_string (input, &extracted_condition,
+ &extracted_thread,
+ &extracted_inferior,
+ &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
+ || inferior != extracted_inferior
+ || task != extracted_task
+ || force != extracted_force_condition
+ || exception_msg != error_str)
+ {
+ 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 ("inferior: %d\n", extracted_inferior);
+ 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);
+ }
+}
+
+/* Wrapper for test function. Pass through the default values for all
+ parameters, except the last parameter, which indicates that we expect
+ INPUT to trigger an error. */
+
+static void
+test_error (const char *input, const char *error_msg)
+{
+ test (input, nullptr, -1, -1, -1, false, nullptr, error_msg);
+}
+
+/* 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 ()
+{
+ gdbarch *arch = current_inferior ()->arch ();
+ 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 parsing valid breakpoint condition strings. */
+ test (" if blah ", "blah");
+ test (" if blah thread 1", "blah", global_thread_num);
+ test (" if blah inferior 1", "blah", -1, 1);
+ test (" if blah thread 1 ", "blah", global_thread_num);
+ test ("thread 1 woof", nullptr, global_thread_num, -1, -1, false, "woof");
+ test ("thread 1 X", nullptr, global_thread_num, -1, -1, false, "X");
+ test (" if blah thread 1 -force-condition", "blah", global_thread_num,
+ -1, -1, true);
+ test (" -force-condition if blah thread 1", "blah", global_thread_num,
+ -1, -1, true);
+ test (" -force-condition if blah thread 1 ", "blah", global_thread_num,
+ -1, -1, true);
+ test ("thread 1 -force-condition if blah", "blah", global_thread_num,
+ -1, -1, true);
+ test ("if (A::outer::func ())", "(A::outer::func ())");
+ test ("if ( foo == thread )", "( foo == thread )");
+ test ("if ( foo == thread ) inferior 1", "( foo == thread )", -1, 1);
+ test ("if ( foo == thread ) thread 1", "( foo == thread )",
+ global_thread_num);
+ test ("if foo == thread", "foo == thread");
+ test ("if foo == thread 1", "foo ==", global_thread_num);
+
+ /* Test parsing some invalid breakpoint condition strings. */
+ test_error ("thread 1 if foo == 123 thread 1",
+ "You can specify only one thread.");
+ test_error ("thread 1 if foo == 123 inferior 1",
+ "You can specify only one of thread, inferior, or task.");
+ test_error ("thread 1 if foo == 123 task 1",
+ "You can specify only one of thread, inferior, or task.");
+ test_error ("inferior 1 if foo == 123 inferior 1",
+ "You can specify only one inferior.");
+ test_error ("inferior 1 if foo == 123 thread 1",
+ "You can specify only one of thread, inferior, or task.");
+ test_error ("inferior 1 if foo == 123 task 1",
+ "You can specify only one of thread, inferior, or task.");
+ test_error ("thread 1.2.3", "Invalid thread ID: 1.2.3");
+ test_error ("thread 1/2", "Invalid thread ID: 1/2");
+ test_error ("thread 1xxx", "Invalid thread ID: 1xxx");
+ test_error ("inferior 1xxx", "Junk 'xxx' after inferior keyword.");
+ test_error ("task 1xxx", "Junk 'xxx' after task keyword.");
+}
+
+} // 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,52 @@
+/* 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, inferior,
+ task and force-condition flag, as accepted by the 'break' command,
+ extract the condition string, thread, inferior, task number, and the
+ force_condition flag, then set *COND_STRING, *THREAD, *INFERIOR, *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 inferior is found,
+ *INFERIOR 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.
+
+ If TOK is nullptr, or TOK is the empty string, then the output variables
+ are all given their default values. */
+
+extern void create_breakpoint_parse_arg_string
+ (const char *tok, gdb::unique_xmalloc_ptr<char> *cond_string,
+ int *thread, int *inferior, int *task,
+ gdb::unique_xmalloc_ptr<char> *rest, bool *force);
+
+#endif
@@ -70,6 +70,7 @@
#include "cli/cli-style.h"
#include "cli/cli-decode.h"
#include <unordered_set>
+#include "break-cond-parse.h"
/* readline include files */
#include "readline/tilde.h"
@@ -6321,20 +6322,7 @@ print_breakpoint_location (const breakpoint *b, const bp_location *loc)
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
@@ -8749,8 +8737,8 @@ code_breakpoint::code_breakpoint (struct gdbarch *gdbarch_,
command line, otherwise it's an error. */
if (type == bp_dprintf)
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. */
@@ -8978,197 +8966,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 into *COND_STRING.
- If no condition string is found then *COND_STRING is set to nullptr.
-
- If the breakpoint specification has an associated thread, task, or
- inferior, these are extracted into *THREAD, *TASK, and *INFERIOR
- respectively, otherwise these arguments are set to -1 (for THREAD and
- INFERIOR) or 0 (for TASK).
-
- PC identifies the context at which the condition should be parsed. */
-
-static void
-find_condition_and_thread (const char *tok, CORE_ADDR pc,
- gdb::unique_xmalloc_ptr<char> *cond_string,
- int *thread, int *inferior, int *task,
- gdb::unique_xmalloc_ptr<char> *rest)
-{
- cond_string->reset ();
- *thread = -1;
- *inferior = -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)));
- break;
- }
-
- 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."));
-
- if (*inferior != -1)
- error (_("You can specify only one of inferior or thread."));
-
- 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, "inferior", toklen) == 0)
- {
- if (*inferior != -1)
- error(_("You can specify only one inferior."));
-
- if (*task != -1)
- error (_("You can specify only one of inferior or task."));
-
- if (*thread != -1)
- error (_("You can specify only one of inferior or thread."));
-
- char *tmptok;
- tok = end_tok + 1;
- *inferior = strtol (tok, &tmptok, 0);
- if (tok == tmptok)
- error (_("Junk after inferior keyword."));
- if (!valid_global_inferior_id (*inferior))
- error (_("Unknown inferior number %d."), *inferior);
- 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."));
-
- if (*inferior != -1)
- error (_("You can specify only one of inferior 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)));
- break;
- }
- 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 *inferior, 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 inferior_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,
- &inferior_id, &task_id, &remaining);
- *cond_string = std::move (cond);
- /* A value of -1 indicates that these fields are unset. At most
- one of these fields should be set (to a value other than -1)
- at this point. */
- gdb_assert (((thread_id == -1 ? 1 : 0)
- + (task_id == -1 ? 1 : 0)
- + (inferior_id == -1 ? 1 : 0)) >= 2);
- *thread = thread_id;
- *inferior = inferior_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>
@@ -9285,6 +9082,46 @@ create_breakpoint (struct gdbarch *gdbarch,
? (extra_string != nullptr && !parse_extra)
: (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, &inferior, &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);
+ gdb_assert (inferior == -1 || inferior > 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);
@@ -9320,6 +9157,13 @@ create_breakpoint (struct gdbarch *gdbarch,
throw;
}
+ /* Only bp_dprintf breakpoints 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;
@@ -9343,63 +9187,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, &inferior,
- &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,
@@ -9416,21 +9228,16 @@ 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 the condition string. */
+ b->cond_string = std::move (cond_string_copy);
+
+ b->thread = thread;
+ b->task = task;
+ b->inferior = inferior;
/* 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;
@@ -9439,9 +9246,12 @@ create_breakpoint (struct gdbarch *gdbarch,
&& type_wanted != bp_hardware_breakpoint) || thread != -1)
b->pspace = current_program_space;
+ if (b->type == 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 "
@@ -13180,24 +12990,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, local_inferior;
-
- find_condition_and_thread_for_sals (sals, extra_string.get (),
- &local_cond, &local_thread,
- &local_inferior,
- &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)
{
@@ -55,11 +55,11 @@ gdb_test "watch j task 1 task 3" "You can specify only one task\\."
# Check that attempting to combine 'task' and 'thread' gives an error.
gdb_test "break break_me task 1 thread 1" \
- "You can specify only one of thread or task\\."
+ "You can specify only one of thread, inferior, or task\\."
gdb_test "break break_me thread 1 task 1" \
- "You can specify only one of thread or task\\."
+ "You can specify only one of thread, inferior, or task\\."
gdb_test "break break_me inferior 1 task 1" \
- "You can specify only one of inferior or task\\."
+ "You can specify only one of thread, inferior, or task\\."
gdb_test "watch j task 1 thread 1" \
"You can specify only one of thread or task\\."
gdb_test "watch j thread 1 task 1" \
@@ -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,71 @@ 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", "task" and
+# "-force-condition", when these keywords appear after
+# the breakpoint 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\\."
+
+# Check the use of abbreviations before the condition. This works
+# because, when the location spec starts with '*' GDB is able to
+# figure out that the complete location is '*main'.
+gdb_test "break *main t 999 if (1==1)" \
+ "Unknown thread 999\\."
+gdb_test "break *main th 999 if (1==1)" \
+ "Unknown thread 999\\."
+gdb_test "break *main ta 999 if (1==1)" \
+ "Unknown task 999\\."
+gdb_test "break *main t 999 -force if (1==1)" \
+ "Unknown thread 999\\."
+gdb_test "break *main th 999 -force if (1==1)" \
+ "Unknown thread 999\\."
+gdb_test "break *main ta 999 -force if (1==1)" \
+ "Unknown task 999\\."
+
+# However, when the location spec doesn't start with '*' GDB relies on
+# the linespec parser to spot the keyword which marks the end of the
+# linespec, and this parser doesn't check for abbreviations.
+gdb_test "with breakpoint pending off -- break main t 999 if (1==1)" \
+ "Function \"main t 999\" not defined\\."
+gdb_test "with breakpoint pending off -- break main th 999 if (1==1)" \
+ "Function \"main th 999\" not defined\\."
+gdb_test "with breakpoint pending off -- break main ta 999 if (1==1)" \
+ "Function \"main ta 999\" not defined\\."
+
+# GDB does not treat a "-force-condition" flag that appears
+# immediately after the condition as the flag, but instead treats it
+# as " - force - condition", that is, subtraction of the symbol
+# "force" followed by subtraction of symbol "context". This is really
+# just a quirk of how this used to be implemented, and should maybe be
+# changed in the future. However, for now GDB retains this behaviour.
+gdb_test "break *main if (1==1) -force-condition" \
+ "No symbol \"force\" in current context\\."
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,24 @@ 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"]
+
+if {[gdb_protocol_is_remote]} {
+ set evals_re "(?: \\(\[^) \]+ evals\\))?"
+} else {
+ set evals_re ""
+}
+
+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\\)${evals_re}" \
+ "\\s+stop only in thread 1"] \
+ "check pending breakpoint on foo"
@@ -575,22 +575,30 @@ 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
gdb_start
+ if {[target_info gdb_protocol] == "extended-remote"} {
+ set evals_re "(?: \\(\[^) \]+ evals\\))?"
+ } else {
+ set evals_re ""
+ }
+
set tst "pending valid conditional explicit breakpoint"
if {![gdb_breakpoint "-func myfunction if arg == 0" \
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${evals_re}" \
+ $tst
gdb_load [standard_output_file $exefile]
gdb_test "info break" \
- ".*in myfunction at .*$srcfile:.*stop only if arg == 0.*" \
+ ".*in myfunction at .*$srcfile:.*stop only if arg == 0${evals_re}" \
"$tst resolved"
}
@@ -55,7 +55,7 @@ with_test_prefix "trailing whitespace" {
gdb_test "break thread 123" "Unknown thread 123\\."
gdb_test "break thread foo" "Invalid thread ID: foo"
gdb_test "break task 123" "Unknown task 123\\."
-gdb_test "break task foo" "Junk after task keyword\\."
+gdb_test "break task foo" "Junk 'foo' after task keyword\\."
gdb_breakpoint "thread if 0" "message"
# These are also NULL locations, but using a subsequent keyword
@@ -63,9 +63,9 @@ gdb_breakpoint "thread if 0" "message"
gdb_test "break thread thread" "Invalid thread ID: thread"
gdb_test "break thread task" "Invalid thread ID: task"
gdb_test "break thread if" "Invalid thread ID: if"
-gdb_test "break task task" "Junk after task keyword\\."
-gdb_test "break task thread" "Junk after task keyword\\."
-gdb_test "break task if" "Junk after task keyword\\."
+gdb_test "break task task" "Junk 'task' after task keyword\\."
+gdb_test "break task thread" "Junk 'thread' after task keyword\\."
+gdb_test "break task if" "Junk 'if' after task keyword\\."
# Test locations containing keyword followed by keyword.
gdb_test "break thread thread 123" "Unknown thread 123\\."
@@ -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"
@@ -51,9 +51,9 @@ if {![runto_main]} {
# this should fail. Try with the keywords in both orders just in case the
# parser has a bug.
gdb_test "break foo thread 1.1 inferior 1" \
- "You can specify only one of inferior or thread\\."
+ "You can specify only one of thread, inferior, or task\\."
gdb_test "break foo inferior 1 thread 1.1" \
- "You can specify only one of inferior or thread\\."
+ "You can specify only one of thread, inferior, or task\\."
# Try to create a breakpoint using the 'inferior' keyword multiple times.
gdb_test "break foo inferior 1 inferior 2" \
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 (void)
+{
+ /* 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,98 @@
+# 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.
+gdb_test "continue" \
+ [multi_line \
+ "Continuing\\." \
+ "\\\[Thread \[^\r\n\]+ exited\\\]" \
+ "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\]+"] \
+ "second breakpt call"
+
+# Confirm breakpoint has been deleted.
+gdb_test "info breakpoints $bpnum" \
+ "No breakpoint, watchpoint, tracepoint, or catchpoint 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\\.\\.\\.\\)"]