diff --git a/gdb/Makefile.in b/gdb/Makefile.in index 13f512f5cad..bcf1ee45a70 100644 --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -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 \ diff --git a/gdb/NEWS b/gdb/NEWS index 19e05af625a..3ee6a61b210 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -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. diff --git a/gdb/break-cond-parse.c b/gdb/break-cond-parse.c new file mode 100644 index 00000000000..f5fe308a923 --- /dev/null +++ b/gdb/break-cond-parse.c @@ -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 . */ + +#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 +parse_all_tokens (const char *str) +{ + gdb_assert (str != nullptr); + + std::vector forward_results; + std::vector backward_results; + + const char *cond_start = nullptr; + const char *cond_end = nullptr; + parse_direction direction = parse_direction::forward; + std::vector *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 &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 *cond_string_ptr, + int *thread_ptr, int *inferior_ptr, int *task_ptr, + gdb::unique_xmalloc_ptr *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 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 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 (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 (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 extracted_condition; + gdb::unique_xmalloc_ptr 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 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 +} diff --git a/gdb/break-cond-parse.h b/gdb/break-cond-parse.h new file mode 100644 index 00000000000..cbee70f4e9e --- /dev/null +++ b/gdb/break-cond-parse.h @@ -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 . */ + +#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 *cond_string, + int *thread, int *inferior, int *task, + gdb::unique_xmalloc_ptr *rest, bool *force); + +#endif diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c index eab3b937c13..9e30dbc1a93 100644 --- a/gdb/breakpoint.c +++ b/gdb/breakpoint.c @@ -70,6 +70,7 @@ #include "cli/cli-style.h" #include "cli/cli-decode.h" #include +#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 *cond_string, - int *thread, int *inferior, int *task, - gdb::unique_xmalloc_ptr *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 &sals, - const char *input, - gdb::unique_xmalloc_ptr *cond_string, - int *thread, int *inferior, int *task, - gdb::unique_xmalloc_ptr *rest) -{ - int num_failures = 0; - for (auto &sal : sals) - { - gdb::unique_xmalloc_ptr cond; - int thread_id = -1; - int inferior_id = -1; - int task_id = -1; - gdb::unique_xmalloc_ptr 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 @@ -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 cond_string_copy; + gdb::unique_xmalloc_ptr 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 cond_string_copy; - gdb::unique_xmalloc_ptr 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 rest; - gdb::unique_xmalloc_ptr 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 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) { diff --git a/gdb/testsuite/gdb.ada/tasks.exp b/gdb/testsuite/gdb.ada/tasks.exp index 6e588408c9d..ecbfbd5522f 100644 --- a/gdb/testsuite/gdb.ada/tasks.exp +++ b/gdb/testsuite/gdb.ada/tasks.exp @@ -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" \ diff --git a/gdb/testsuite/gdb.base/condbreak.exp b/gdb/testsuite/gdb.base/condbreak.exp index 65d19b3e7a9..3b619e47bfc 100644 --- a/gdb/testsuite/gdb.base/condbreak.exp +++ b/gdb/testsuite/gdb.base/condbreak.exp @@ -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 { diff --git a/gdb/testsuite/gdb.base/pending.exp b/gdb/testsuite/gdb.base/pending.exp index 5ee31d32389..833e084db82 100644 --- a/gdb/testsuite/gdb.base/pending.exp +++ b/gdb/testsuite/gdb.base/pending.exp @@ -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+\\s+foo" \ + "\\s+stop only if \\(unknown_var && another_unknown_var\\)${evals_re}" \ + "\\s+stop only in thread 1"] \ + "check pending breakpoint on foo" diff --git a/gdb/testsuite/gdb.linespec/explicit.exp b/gdb/testsuite/gdb.linespec/explicit.exp index 60183e98e1e..e8ae10a27df 100644 --- a/gdb/testsuite/gdb.linespec/explicit.exp +++ b/gdb/testsuite/gdb.linespec/explicit.exp @@ -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" } diff --git a/gdb/testsuite/gdb.linespec/keywords.exp b/gdb/testsuite/gdb.linespec/keywords.exp index 36a919c8be2..d2596d2b357 100644 --- a/gdb/testsuite/gdb.linespec/keywords.exp +++ b/gdb/testsuite/gdb.linespec/keywords.exp @@ -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\\." diff --git a/gdb/testsuite/gdb.mi/mi-dprintf-pending.exp b/gdb/testsuite/gdb.mi/mi-dprintf-pending.exp index fd5684bd2b1..4cf6dec4b41 100644 --- a/gdb/testsuite/gdb.mi/mi-dprintf-pending.exp +++ b/gdb/testsuite/gdb.mi/mi-dprintf-pending.exp @@ -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" diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp.exp b/gdb/testsuite/gdb.multi/inferior-specific-bp.exp index 5cc451b0ecc..46efe6f54bc 100644 --- a/gdb/testsuite/gdb.multi/inferior-specific-bp.exp +++ b/gdb/testsuite/gdb.multi/inferior-specific-bp.exp @@ -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" \ diff --git a/gdb/testsuite/gdb.threads/del-pending-thread-bp-lib.c b/gdb/testsuite/gdb.threads/del-pending-thread-bp-lib.c new file mode 100644 index 00000000000..15d1b9833dd --- /dev/null +++ b/gdb/testsuite/gdb.threads/del-pending-thread-bp-lib.c @@ -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 . */ + +int global_var = 0; + +void +foo (int arg) +{ + global_var = arg; +} diff --git a/gdb/testsuite/gdb.threads/del-pending-thread-bp.c b/gdb/testsuite/gdb.threads/del-pending-thread-bp.c new file mode 100644 index 00000000000..6fc76dbf08c --- /dev/null +++ b/gdb/testsuite/gdb.threads/del-pending-thread-bp.c @@ -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 . */ + +#include +#include +#include + +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; +} + diff --git a/gdb/testsuite/gdb.threads/del-pending-thread-bp.exp b/gdb/testsuite/gdb.threads/del-pending-thread-bp.exp new file mode 100644 index 00000000000..14a91a40d76 --- /dev/null +++ b/gdb/testsuite/gdb.threads/del-pending-thread-bp.exp @@ -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 . + +# 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\\.\\.\\.\\)"]