mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-11-23 01:53:38 +08:00
da8730e8f9
The commit:
commit c6b486755e
Date: Thu Mar 30 19:21:22 2023 +0100
gdb: parse pending breakpoint thread/task immediately
Introduce a use bug where the value of a temporary variable was being
used after it had gone out of scope. This was picked up by the
address sanitizer and would result in this error:
(gdb) maintenance selftest create_breakpoint_parse_arg_string
Running selftest create_breakpoint_parse_arg_string.
=================================================================
==2265825==ERROR: AddressSanitizer: stack-use-after-scope on address 0x7fbb08046511 at pc 0x000001632230 bp 0x7fff7c2fb770 sp 0x7fff7c2fb768
READ of size 1 at 0x7fbb08046511 thread T0
#0 0x163222f in create_breakpoint_parse_arg_string(char const*, std::unique_ptr<char, gdb::xfree_deleter<char> >*, int*, int*, int*, std::unique_ptr<char, gdb::xfree_deleter<char> >*, bool*) ../../src/gdb/break-cond-parse.c:496
#1 0x1633026 in test ../../src/gdb/break-cond-parse.c:582
#2 0x163391b in create_breakpoint_parse_arg_string_tests ../../src/gdb/break-cond-parse.c:649
#3 0x12cfebc in void std::__invoke_impl<void, void (*&)()>(std::__invoke_other, void (*&)()) /usr/include/c++/13/bits/invoke.h:61
#4 0x12cc8ee in std::enable_if<is_invocable_r_v<void, void (*&)()>, void>::type std::__invoke_r<void, void (*&)()>(void (*&)()) /usr/include/c++/13/bits/invoke.h:111
#5 0x12c81e5 in std::_Function_handler<void (), void (*)()>::_M_invoke(std::_Any_data const&) /usr/include/c++/13/bits/std_function.h:290
#6 0x18bb51d in std::function<void ()>::operator()() const /usr/include/c++/13/bits/std_function.h:591
#7 0x4193ef9 in selftests::run_tests(gdb::array_view<char const* const>, bool) ../../src/gdbsupport/selftest.cc:100
#8 0x21c2206 in maintenance_selftest ../../src/gdb/maint.c:1172
... etc ...
The problem was caused by three lines like this one:
thread_info *thr
= parse_thread_id (std::string (t.get_value ()).c_str (), &tmptok);
After parsing the thread-id TMPTOK would be left pointing into the
temporary string which had been created on this line. When on the
next line we did this:
gdb_assert (*tmptok == '\0');
The value of *TMPTOK is undefined.
Fix this by creating the std::string earlier in the scope. Now the
contents of the string will remain valid when we check *TMPTOK. The
address sanitizer issue is now resolved.
700 lines
23 KiB
C
700 lines
23 KiB
C
/* 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)
|
|
{
|
|
std::string tok_value (t.get_value ());
|
|
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 (tok_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 (tok_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 (tok_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
|
|
}
|