mirror of
https://github.com/bminor/binutils-gdb.git
synced 2025-11-16 12:34:43 +00:00
PR ada/33217 points out that gdb incorrectly calls the <ctype.h>
functions. In particular, gdb feels free to pass a 'char' like:
char *str = ...;
... isdigit (*str)
This is incorrect as isdigit only accepts EOF and values that can be
represented as 'unsigned char' -- that is, a cast is needed here to
avoid undefined behavior when 'char' is signed and a character in the
string might be sign-extended. (As an aside, I think this API seems
obviously bad, but unfortunately this is what the standard says, and
some systems check this.)
Rather than adding casts everywhere, this changes all the code in gdb
that uses any <ctype.h> API to instead call the corresponding c-ctype
function.
Now, c-ctype has some limitations compared to <ctype.h>. It works as
if the C locale is in effect, so in theory some non-ASCII characters
may be misclassified. This would only affect a subset of character
sets, though, and in most places I think ASCII is sufficient -- for
example the many places in gdb that check for whitespace.
Furthermore, in practice most users are using UTF-8-based locales,
where these functions aren't really informative for non-ASCII
characters anyway; see the existing workarounds in gdb/c-support.h.
Note that safe-ctype.h cannot be used because it causes conflicts with
readline.h. And, we canot poison the <ctype.h> identifiers as this
provokes errors from some libstdc++ headers.
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=33217
Approved-By: Simon Marchi <simon.marchi@efficios.com>
380 lines
8.7 KiB
C
380 lines
8.7 KiB
C
/* TID parsing for GDB, the GNU debugger.
|
|
|
|
Copyright (C) 2015-2025 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 "tid-parse.h"
|
|
#include "inferior.h"
|
|
#include "gdbthread.h"
|
|
|
|
/* See tid-parse.h. */
|
|
|
|
[[noreturn]] void
|
|
invalid_thread_id_error (const char *string)
|
|
{
|
|
error (_("Invalid thread ID: %s"), string);
|
|
}
|
|
|
|
/* Wrapper for get_number_trailer that throws an error if we get back
|
|
a negative number. We'll see a negative value if the number is
|
|
stored in a negative convenience variable (e.g., $minus_one = -1).
|
|
STRING is the parser string to be used in the error message if we
|
|
do get back a negative number. */
|
|
|
|
static int
|
|
get_positive_number_trailer (const char **pp, int trailer, const char *string)
|
|
{
|
|
int num;
|
|
|
|
num = get_number_trailer (pp, trailer);
|
|
if (num < 0)
|
|
error (_("negative value: %s"), string);
|
|
return num;
|
|
}
|
|
|
|
/* Parse TIDSTR as a per-inferior thread ID, in either INF_NUM.THR_NUM
|
|
or THR_NUM form, and return a pair, the first item of the pair is
|
|
INF_NUM and the second item is THR_NUM.
|
|
|
|
If TIDSTR does not include an INF_NUM component, then the first item in
|
|
the pair will be 0 (which is an invalid inferior number), this indicates
|
|
that TIDSTR references the current inferior.
|
|
|
|
This function does not validate the INF_NUM and THR_NUM are actually
|
|
valid numbers, that is, they might reference inferiors or threads that
|
|
don't actually exist; this function just splits the string into its
|
|
component parts.
|
|
|
|
If there is an error parsing TIDSTR then this function will raise an
|
|
exception. */
|
|
|
|
static std::pair<int, int>
|
|
parse_thread_id_1 (const char *tidstr, const char **end)
|
|
{
|
|
const char *number = tidstr;
|
|
const char *dot, *p1;
|
|
int thr_num, inf_num;
|
|
|
|
dot = strchr (number, '.');
|
|
|
|
if (dot != NULL)
|
|
{
|
|
/* Parse number to the left of the dot. */
|
|
p1 = number;
|
|
inf_num = get_positive_number_trailer (&p1, '.', number);
|
|
if (inf_num == 0)
|
|
invalid_thread_id_error (number);
|
|
p1 = dot + 1;
|
|
}
|
|
else
|
|
{
|
|
inf_num = 0;
|
|
p1 = number;
|
|
}
|
|
|
|
thr_num = get_positive_number_trailer (&p1, 0, number);
|
|
if (thr_num == 0)
|
|
invalid_thread_id_error (number);
|
|
|
|
if (end != nullptr)
|
|
*end = p1;
|
|
|
|
return { inf_num, thr_num };
|
|
}
|
|
|
|
/* See tid-parse.h. */
|
|
|
|
struct thread_info *
|
|
parse_thread_id (const char *tidstr, const char **end)
|
|
{
|
|
const auto [inf_num, thr_num] = parse_thread_id_1 (tidstr, end);
|
|
|
|
inferior *inf;
|
|
bool explicit_inf_id = false;
|
|
|
|
if (inf_num != 0)
|
|
{
|
|
inf = find_inferior_id (inf_num);
|
|
if (inf == nullptr)
|
|
error (_("No inferior number '%d'"), inf_num);
|
|
explicit_inf_id = true;
|
|
}
|
|
else
|
|
inf = current_inferior ();
|
|
|
|
thread_info *tp = nullptr;
|
|
for (thread_info *it : inf->threads ())
|
|
if (it->per_inf_num == thr_num)
|
|
{
|
|
tp = it;
|
|
break;
|
|
}
|
|
|
|
if (tp == nullptr)
|
|
{
|
|
if (show_inferior_qualified_tids () || explicit_inf_id)
|
|
error (_("Unknown thread %d.%d."), inf->num, thr_num);
|
|
else
|
|
error (_("Unknown thread %d."), thr_num);
|
|
}
|
|
|
|
return tp;
|
|
}
|
|
|
|
/* See tid-parse.h. */
|
|
|
|
bool
|
|
is_thread_id (const char *tidstr, const char **end)
|
|
{
|
|
try
|
|
{
|
|
(void) parse_thread_id_1 (tidstr, end);
|
|
return true;
|
|
}
|
|
catch (const gdb_exception_error &)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* See tid-parse.h. */
|
|
|
|
tid_range_parser::tid_range_parser (const char *tidlist,
|
|
int default_inferior)
|
|
{
|
|
init (tidlist, default_inferior);
|
|
}
|
|
|
|
/* See tid-parse.h. */
|
|
|
|
void
|
|
tid_range_parser::init (const char *tidlist, int default_inferior)
|
|
{
|
|
m_state = STATE_INFERIOR;
|
|
m_cur_tok = tidlist;
|
|
m_inf_num = 0;
|
|
m_qualified = false;
|
|
m_default_inferior = default_inferior;
|
|
}
|
|
|
|
/* See tid-parse.h. */
|
|
|
|
bool
|
|
tid_range_parser::finished () const
|
|
{
|
|
switch (m_state)
|
|
{
|
|
case STATE_INFERIOR:
|
|
/* Parsing is finished when at end of string or null string,
|
|
or we are not in a range and not in front of an integer, negative
|
|
integer, convenience var or negative convenience var. */
|
|
return (*m_cur_tok == '\0'
|
|
|| !(c_isdigit (*m_cur_tok)
|
|
|| *m_cur_tok == '$'
|
|
|| *m_cur_tok == '*'));
|
|
case STATE_THREAD_RANGE:
|
|
case STATE_STAR_RANGE:
|
|
return m_range_parser.finished ();
|
|
}
|
|
|
|
gdb_assert_not_reached ("unhandled state");
|
|
}
|
|
|
|
/* See tid-parse.h. */
|
|
|
|
const char *
|
|
tid_range_parser::cur_tok () const
|
|
{
|
|
switch (m_state)
|
|
{
|
|
case STATE_INFERIOR:
|
|
return m_cur_tok;
|
|
case STATE_THREAD_RANGE:
|
|
case STATE_STAR_RANGE:
|
|
return m_range_parser.cur_tok ();
|
|
}
|
|
|
|
gdb_assert_not_reached ("unhandled state");
|
|
}
|
|
|
|
void
|
|
tid_range_parser::skip_range ()
|
|
{
|
|
gdb_assert (m_state == STATE_THREAD_RANGE
|
|
|| m_state == STATE_STAR_RANGE);
|
|
|
|
m_range_parser.skip_range ();
|
|
init (m_range_parser.cur_tok (), m_default_inferior);
|
|
}
|
|
|
|
/* See tid-parse.h. */
|
|
|
|
bool
|
|
tid_range_parser::tid_is_qualified () const
|
|
{
|
|
return m_qualified;
|
|
}
|
|
|
|
/* Helper for tid_range_parser::get_tid and
|
|
tid_range_parser::get_tid_range. Return the next range if THR_END
|
|
is non-NULL, return a single thread ID otherwise. */
|
|
|
|
bool
|
|
tid_range_parser::get_tid_or_range (int *inf_num,
|
|
int *thr_start, int *thr_end)
|
|
{
|
|
if (m_state == STATE_INFERIOR)
|
|
{
|
|
const char *p;
|
|
const char *space;
|
|
|
|
space = skip_to_space (m_cur_tok);
|
|
|
|
p = m_cur_tok;
|
|
while (p < space && *p != '.')
|
|
p++;
|
|
if (p < space)
|
|
{
|
|
const char *dot = p;
|
|
|
|
/* Parse number to the left of the dot. */
|
|
p = m_cur_tok;
|
|
m_inf_num = get_positive_number_trailer (&p, '.', m_cur_tok);
|
|
if (m_inf_num == 0)
|
|
return 0;
|
|
|
|
m_qualified = true;
|
|
p = dot + 1;
|
|
|
|
if (c_isspace (*p))
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
m_inf_num = m_default_inferior;
|
|
m_qualified = false;
|
|
p = m_cur_tok;
|
|
}
|
|
|
|
m_range_parser.init (p);
|
|
if (p[0] == '*' && (p[1] == '\0' || c_isspace (p[1])))
|
|
{
|
|
/* Setup the number range parser to return numbers in the
|
|
whole [1,INT_MAX] range. */
|
|
m_range_parser.setup_range (1, INT_MAX, skip_spaces (p + 1));
|
|
m_state = STATE_STAR_RANGE;
|
|
}
|
|
else
|
|
m_state = STATE_THREAD_RANGE;
|
|
}
|
|
|
|
*inf_num = m_inf_num;
|
|
*thr_start = m_range_parser.get_number ();
|
|
if (*thr_start < 0)
|
|
error (_("negative value: %s"), m_cur_tok);
|
|
if (*thr_start == 0)
|
|
{
|
|
m_state = STATE_INFERIOR;
|
|
return false;
|
|
}
|
|
|
|
/* If we successfully parsed a thread number or finished parsing a
|
|
thread range, switch back to assuming the next TID is
|
|
inferior-qualified. */
|
|
if (!m_range_parser.in_range ())
|
|
{
|
|
m_state = STATE_INFERIOR;
|
|
m_cur_tok = m_range_parser.cur_tok ();
|
|
|
|
if (thr_end != NULL)
|
|
*thr_end = *thr_start;
|
|
}
|
|
|
|
/* If we're midway through a range, and the caller wants the end
|
|
value, return it and skip to the end of the range. */
|
|
if (thr_end != NULL
|
|
&& (m_state == STATE_THREAD_RANGE
|
|
|| m_state == STATE_STAR_RANGE))
|
|
{
|
|
*thr_end = m_range_parser.end_value ();
|
|
|
|
skip_range ();
|
|
}
|
|
|
|
return (*inf_num != 0 && *thr_start != 0);
|
|
}
|
|
|
|
/* See tid-parse.h. */
|
|
|
|
bool
|
|
tid_range_parser::get_tid_range (int *inf_num,
|
|
int *thr_start, int *thr_end)
|
|
{
|
|
gdb_assert (inf_num != NULL && thr_start != NULL && thr_end != NULL);
|
|
|
|
return get_tid_or_range (inf_num, thr_start, thr_end);
|
|
}
|
|
|
|
/* See tid-parse.h. */
|
|
|
|
bool
|
|
tid_range_parser::get_tid (int *inf_num, int *thr_num)
|
|
{
|
|
gdb_assert (inf_num != NULL && thr_num != NULL);
|
|
|
|
return get_tid_or_range (inf_num, thr_num, NULL);
|
|
}
|
|
|
|
/* See tid-parse.h. */
|
|
|
|
bool
|
|
tid_range_parser::in_star_range () const
|
|
{
|
|
return m_state == STATE_STAR_RANGE;
|
|
}
|
|
|
|
bool
|
|
tid_range_parser::in_thread_range () const
|
|
{
|
|
return m_state == STATE_THREAD_RANGE;
|
|
}
|
|
|
|
/* See tid-parse.h. */
|
|
|
|
int
|
|
tid_is_in_list (const char *list, int default_inferior,
|
|
int inf_num, int thr_num)
|
|
{
|
|
if (list == NULL || *list == '\0')
|
|
return 1;
|
|
|
|
tid_range_parser parser (list, default_inferior);
|
|
if (parser.finished ())
|
|
invalid_thread_id_error (parser.cur_tok ());
|
|
while (!parser.finished ())
|
|
{
|
|
int tmp_inf, tmp_thr_start, tmp_thr_end;
|
|
|
|
if (!parser.get_tid_range (&tmp_inf, &tmp_thr_start, &tmp_thr_end))
|
|
invalid_thread_id_error (parser.cur_tok ());
|
|
if (tmp_inf == inf_num
|
|
&& tmp_thr_start <= thr_num && thr_num <= tmp_thr_end)
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|