/* TID parsing for GDB, the GNU debugger. Copyright (C) 2015-2024 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 "tid-parse.h" #include "inferior.h" #include "gdbthread.h" #include /* 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 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' || !(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 (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' || 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; }