diff options
Diffstat (limited to 'gdbsupport')
31 files changed, 904 insertions, 521 deletions
diff --git a/gdbsupport/Makefile.am b/gdbsupport/Makefile.am index 79aef95..20122e4 100644 --- a/gdbsupport/Makefile.am +++ b/gdbsupport/Makefile.am @@ -80,6 +80,7 @@ libgdbsupport_a_SOURCES = \ pathstuff.cc \ print-utils.cc \ ptid.cc \ + remote-args.cc \ rsp-low.cc \ run-time-clock.cc \ safe-strerror.cc \ diff --git a/gdbsupport/Makefile.in b/gdbsupport/Makefile.in index c2feacc..66b8891 100644 --- a/gdbsupport/Makefile.in +++ b/gdbsupport/Makefile.in @@ -163,12 +163,12 @@ am_libgdbsupport_a_OBJECTS = agent.$(OBJEXT) btrace-common.$(OBJEXT) \ gdb_tilde_expand.$(OBJEXT) gdb_wait.$(OBJEXT) \ gdb_vecs.$(OBJEXT) job-control.$(OBJEXT) netstuff.$(OBJEXT) \ new-op.$(OBJEXT) osabi.$(OBJEXT) pathstuff.$(OBJEXT) \ - print-utils.$(OBJEXT) ptid.$(OBJEXT) rsp-low.$(OBJEXT) \ - run-time-clock.$(OBJEXT) safe-strerror.$(OBJEXT) \ - scoped_mmap.$(OBJEXT) search.$(OBJEXT) signals.$(OBJEXT) \ - signals-state-save-restore.$(OBJEXT) task-group.$(OBJEXT) \ - tdesc.$(OBJEXT) thread-pool.$(OBJEXT) xml-utils.$(OBJEXT) \ - $(am__objects_1) $(am__objects_2) + print-utils.$(OBJEXT) ptid.$(OBJEXT) remote-args.$(OBJEXT) \ + rsp-low.$(OBJEXT) run-time-clock.$(OBJEXT) \ + safe-strerror.$(OBJEXT) scoped_mmap.$(OBJEXT) search.$(OBJEXT) \ + signals.$(OBJEXT) signals-state-save-restore.$(OBJEXT) \ + task-group.$(OBJEXT) tdesc.$(OBJEXT) thread-pool.$(OBJEXT) \ + xml-utils.$(OBJEXT) $(am__objects_1) $(am__objects_2) libgdbsupport_a_OBJECTS = $(am_libgdbsupport_a_OBJECTS) AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) @@ -438,6 +438,7 @@ libgdbsupport_a_SOURCES = \ pathstuff.cc \ print-utils.cc \ ptid.cc \ + remote-args.cc \ rsp-low.cc \ run-time-clock.cc \ safe-strerror.cc \ @@ -548,6 +549,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pathstuff.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/print-utils.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ptid.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/remote-args.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rsp-low.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run-time-clock.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/safe-strerror.Po@am__quote@ diff --git a/gdbsupport/common-defs.h b/gdbsupport/common-defs.h index 07caf3b..6f58914 100644 --- a/gdbsupport/common-defs.h +++ b/gdbsupport/common-defs.h @@ -27,6 +27,14 @@ #pragma GCC optimize("-fno-hoist-adjacent-loads") #endif +#if defined (__GNUC__) && !defined (__clang__) \ + && ((__GNUC__ >= 12 && __GNUC__ <= 15) \ + || (__GNUC__ == 16 && __GNUC_MINOR__ < 1)) +/* Work around PR gcc/120987 starting gcc 12, and assume it will be fixed in + the gcc 16.1 release. */ +#pragma GCC optimize("-fno-ipa-modref") +#endif + #include <gdbsupport/config.h> #undef PACKAGE_NAME @@ -208,13 +216,18 @@ #include "errors.h" #include "print-utils.h" #include "common-debug.h" -#include "cleanups.h" #include "common-exceptions.h" #include "gdbsupport/poison.h" /* Pull in gdb::unique_xmalloc_ptr. */ #include "gdbsupport/gdb_unique_ptr.h" +/* Note that there's no simple way to enforce the use of the c-ctype + functions. We can't poison the <ctype.h> functions (see + safe-ctype.h) because that will provoke errors from libstdc++ + headers. */ +#include "c-ctype.h" + /* sbrk on macOS is not useful for our purposes, since sbrk(0) always returns the same value. brk/sbrk on macOS is just an emulation that always returns a pointer to a 4MB section reserved for diff --git a/gdbsupport/common-types.h b/gdbsupport/common-types.h index 210e09b..d849ea8 100644 --- a/gdbsupport/common-types.h +++ b/gdbsupport/common-types.h @@ -21,6 +21,7 @@ #define GDBSUPPORT_COMMON_TYPES_H #include <inttypes.h> +#include "gdbsupport/offset-type.h" /* * A byte from the program being debugged. */ typedef unsigned char gdb_byte; @@ -29,10 +30,8 @@ typedef unsigned char gdb_byte; typedef uint64_t CORE_ADDR; /* Like a CORE_ADDR, but not directly convertible. This is used to - represent an unrelocated CORE_ADDR. DEFINE_OFFSET_TYPE is not used - here because there's no need to add or subtract values of this - type. */ -enum class unrelocated_addr : CORE_ADDR { }; + represent an unrelocated CORE_ADDR. */ +DEFINE_OFFSET_TYPE (unrelocated_addr, CORE_ADDR); /* LONGEST must be at least as big as CORE_ADDR. */ diff --git a/gdbsupport/common-utils.cc b/gdbsupport/common-utils.cc index 266d836..5c7ba31 100644 --- a/gdbsupport/common-utils.cc +++ b/gdbsupport/common-utils.cc @@ -19,7 +19,6 @@ #include "common-utils.h" #include "host-defs.h" -#include "gdbsupport/gdb-safe-ctype.h" #include "gdbsupport/gdb-xfree.h" void * @@ -180,7 +179,7 @@ extract_string_maybe_quoted (const char **arg) /* Parse p similarly to gdb_argv buildargv function. */ while (*p != '\0') { - if (ISSPACE (*p) && !squote && !dquote && !bsquote) + if (c_isspace (*p) && !squote && !dquote && !bsquote) break; else { @@ -254,21 +253,21 @@ make_quoted_string (const char *str) static int is_digit_in_base (unsigned char digit, int base) { - if (!ISALNUM (digit)) + if (!c_isalnum (digit)) return 0; if (base <= 10) - return (ISDIGIT (digit) && digit < base + '0'); + return (c_isdigit (digit) && digit < base + '0'); else - return (ISDIGIT (digit) || TOLOWER (digit) < base - 10 + 'a'); + return (c_isdigit (digit) || c_tolower (digit) < base - 10 + 'a'); } static int digit_to_int (unsigned char c) { - if (ISDIGIT (c)) + if (c_isdigit (c)) return c - '0'; else - return TOLOWER (c) - 'a' + 10; + return c_tolower (c) - 'a' + 10; } /* As for strtoul, but for ULONGEST results. */ @@ -282,7 +281,7 @@ strtoulst (const char *num, const char **trailer, int base) int i = 0; /* Skip leading whitespace. */ - while (ISSPACE (num[i])) + while (c_isspace (num[i])) i++; /* Handle prefixes. */ @@ -349,7 +348,7 @@ skip_spaces (char *chp) { if (chp == NULL) return NULL; - while (*chp && ISSPACE (*chp)) + while (*chp && c_isspace (*chp)) chp++; return chp; } @@ -361,7 +360,7 @@ skip_spaces (const char *chp) { if (chp == NULL) return NULL; - while (*chp && ISSPACE (*chp)) + while (*chp && c_isspace (*chp)) chp++; return chp; } @@ -373,7 +372,7 @@ skip_to_space (const char *chp) { if (chp == NULL) return NULL; - while (*chp && !ISSPACE (*chp)) + while (*chp && !c_isspace (*chp)) chp++; return chp; } diff --git a/gdbsupport/common-utils.h b/gdbsupport/common-utils.h index a168458..10bf9f4 100644 --- a/gdbsupport/common-utils.h +++ b/gdbsupport/common-utils.h @@ -196,6 +196,16 @@ in_inclusive_range (T value, T low, T high) extern ULONGEST align_up (ULONGEST v, int n); extern ULONGEST align_down (ULONGEST v, int n); +/* Sign-extend the value V, using N as the number of valid bits. That + is, bit N-1 is the sign bit. The higher-order bits (those outside + 0..N-1) must be zero. */ +static inline ULONGEST +sign_extend (ULONGEST v, int n) +{ + ULONGEST mask = (ULONGEST) 1 << (n - 1); + return (v ^ mask) - mask; +} + /* Convert hex digit A to a number, or throw an exception. */ extern int fromhex (int a); diff --git a/gdbsupport/common.m4 b/gdbsupport/common.m4 index f265af9..cde8bd6 100644 --- a/gdbsupport/common.m4 +++ b/gdbsupport/common.m4 @@ -21,7 +21,6 @@ AC_DEFUN([GDB_AC_COMMON], [ # Set the 'development' global. . $srcdir/../bfd/development.sh - AC_HEADER_STDC AC_FUNC_ALLOCA WIN32APILIBS= diff --git a/gdbsupport/configure b/gdbsupport/configure index bcfae34..133ddfa 100755 --- a/gdbsupport/configure +++ b/gdbsupport/configure @@ -10704,118 +10704,6 @@ $as_echo "$ac_cv_path_SED" >&6; } # Set the 'development' global. . $srcdir/../bfd/development.sh - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5 -$as_echo_n "checking for ANSI C header files... " >&6; } -if ${ac_cv_header_stdc+:} false; then : - $as_echo_n "(cached) " >&6 -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include <stdlib.h> -#include <stdarg.h> -#include <string.h> -#include <float.h> - -int -main () -{ - - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - ac_cv_header_stdc=yes -else - ac_cv_header_stdc=no -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext - -if test $ac_cv_header_stdc = yes; then - # SunOS 4.x string.h does not declare mem*, contrary to ANSI. - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include <string.h> - -_ACEOF -if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | - $EGREP "memchr" >/dev/null 2>&1; then : - -else - ac_cv_header_stdc=no -fi -rm -f conftest* - -fi - -if test $ac_cv_header_stdc = yes; then - # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include <stdlib.h> - -_ACEOF -if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | - $EGREP "free" >/dev/null 2>&1; then : - -else - ac_cv_header_stdc=no -fi -rm -f conftest* - -fi - -if test $ac_cv_header_stdc = yes; then - # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. - if test "$cross_compiling" = yes; then : - : -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include <ctype.h> -#include <stdlib.h> -#if ((' ' & 0x0FF) == 0x020) -# define ISLOWER(c) ('a' <= (c) && (c) <= 'z') -# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) -#else -# define ISLOWER(c) \ - (('a' <= (c) && (c) <= 'i') \ - || ('j' <= (c) && (c) <= 'r') \ - || ('s' <= (c) && (c) <= 'z')) -# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c)) -#endif - -#define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) -int -main () -{ - int i; - for (i = 0; i < 256; i++) - if (XOR (islower (i), ISLOWER (i)) - || toupper (i) != TOUPPER (i)) - return 2; - return 0; -} -_ACEOF -if ac_fn_c_try_run "$LINENO"; then : - -else - ac_cv_header_stdc=no -fi -rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ - conftest.$ac_objext conftest.beam conftest.$ac_ext -fi - -fi -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5 -$as_echo "$ac_cv_header_stdc" >&6; } -if test $ac_cv_header_stdc = yes; then - -$as_echo "#define STDC_HEADERS 1" >>confdefs.h - -fi - # The Ultrix 4.2 mips builtin alloca declared by alloca.h only works # for constant arguments. Useless! { $as_echo "$as_me:${as_lineno-$LINENO}: checking for working alloca.h" >&5 diff --git a/gdbsupport/cxx-thread.h b/gdbsupport/cxx-thread.h new file mode 100644 index 0000000..e4061eb --- /dev/null +++ b/gdbsupport/cxx-thread.h @@ -0,0 +1,243 @@ +/* Wrappers for C++ threading + + Copyright (C) 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/>. */ + +#ifndef GDBSUPPORT_CXX_THREAD_H +#define GDBSUPPORT_CXX_THREAD_H + +/* This header implements shims for the parts of the C++ threading + library that are needed by gdb. + + The reason this exists is that some versions of libstdc++ do not + supply a working C++ thread implementation. In particular this was + true for several versions of the Windows compiler. See + https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93687. + + For systems where this works, this header just supplies aliases of + the standard functionality, in the "gdb" namespace. For example, + "gdb::mutex" is an alias for "std::mutex". + + For non-working ports, shims are provided. These are just the + subset needed by gdb, and they generally do nothing, or as little + as possible. In particular they all simply assume single-threaded + operation. */ + +#if CXX_STD_THREAD + +#include <thread> +#include <mutex> +#include <condition_variable> +#include <future> + +namespace gdb +{ + +using condition_variable = std::condition_variable; +using cv_status = std::cv_status; +using future_status = std::future_status; +using mutex = std::mutex; +using recursive_mutex = std::recursive_mutex; +using thread = std::thread; + +namespace this_thread = std::this_thread; + +template<typename T> +using lock_guard = std::lock_guard<T>; + +template<typename T> +using unique_lock = std::unique_lock<T>; + +template<typename T> +using future = std::future<T>; + +} /* namespace gdb*/ + +#else + +#include <chrono> + +namespace gdb +{ + +/* A do-nothing replacement for std::mutex. */ +struct mutex +{ + mutex () = default; + + DISABLE_COPY_AND_ASSIGN (mutex); + + void lock () + { + } + + void unlock () + { + } +}; + +/* A do-nothing replacement for std::recursive_mutex. */ +struct recursive_mutex +{ + recursive_mutex () = default; + + DISABLE_COPY_AND_ASSIGN (recursive_mutex); + + void lock () + { + } + + void unlock () + { + } +}; + +/* A do-nothing replacement for std::lock_guard. */ +template<typename T> +struct lock_guard +{ + explicit lock_guard (T &m) + { + } + + DISABLE_COPY_AND_ASSIGN (lock_guard); +}; + +/* A do-nothing replacement for std::unique_lock. */ +template<typename T> +struct unique_lock +{ + explicit unique_lock (T &m) + { + } + + DISABLE_COPY_AND_ASSIGN (unique_lock); +}; + +/* A compatibility enum for std::cv_status. */ +enum class cv_status +{ + no_timeout, + timeout, +}; + +/* A do-nothing replacement for std::condition_variable. */ +struct condition_variable +{ + condition_variable () = default; + + DISABLE_COPY_AND_ASSIGN (condition_variable); + + void notify_one () noexcept + { + } + + void wait (unique_lock<mutex> &lock) + { + } + + template<class Rep, class Period> + cv_status wait_for (unique_lock<mutex> &lock, + const std::chrono::duration<Rep, Period> &rel_time) + { + return cv_status::no_timeout; + } +}; + +/* A compatibility enum for std::future_status. This is just the + subset needed by gdb. */ +enum class future_status +{ + ready, + timeout, +}; + +/* A compatibility implementation of std::future. */ +template<typename T> +class future +{ +public: + + explicit future (T value) + : m_value (std::move (value)) + { + } + + future () = default; + future (future &&other) = default; + future (const future &other) = delete; + future &operator= (future &&other) = default; + future &operator= (const future &other) = delete; + + void wait () const { } + + template<class Rep, class Period> + future_status wait_for (const std::chrono::duration<Rep,Period> &duration) + const + { + return future_status::ready; + } + + T get () { return std::move (m_value); } + +private: + + T m_value; +}; + +/* A specialization for void. */ + +template<> +class future<void> +{ +public: + void wait () const { } + + template<class Rep, class Period> + future_status wait_for (const std::chrono::duration<Rep,Period> &duration) + const + { + return future_status::ready; + } + + void get () { } +}; + +/* Rather than try to write a gdb::thread class, we just use a + namespace since only the 'id' type is needed. Code manipulating + actual std::thread objects has to be wrapped in a check anyway. */ +namespace thread +{ +/* Replacement for std::thread::id. */ +using id = int; +} + +/* Replacement for std::this_thread. */ +namespace this_thread +{ +static inline thread::id +get_id () +{ + return 0; +} +} + +} /* namespace gdb */ + +#endif /* CXX_STD_THREAD */ + +#endif /* GDBSUPPORT_CXX_THREAD_H */ diff --git a/gdbsupport/event-loop.cc b/gdbsupport/event-loop.cc index c080490..e7b21e7 100644 --- a/gdbsupport/event-loop.cc +++ b/gdbsupport/event-loop.cc @@ -827,7 +827,8 @@ update_wait_timeout (void) /* Update the timeout for select/ poll. */ #ifdef HAVE_POLL if (use_poll) - gdb_notifier.poll_timeout = timeout.tv_sec * 1000; + gdb_notifier.poll_timeout = (timeout.tv_sec * 1000 + + (timeout.tv_usec + 1000 - 1) / 1000); else #endif /* HAVE_POLL */ { diff --git a/gdbsupport/filestuff.cc b/gdbsupport/filestuff.cc index 5c1817e..817663b 100644 --- a/gdbsupport/filestuff.cc +++ b/gdbsupport/filestuff.cc @@ -333,14 +333,10 @@ gdb_fopen_cloexec (const char *filename, const char *opentype) if (!fopen_e_ever_failed_einval) { - char *copy; - - copy = (char *) alloca (strlen (opentype) + 2); - strcpy (copy, opentype); /* This is a glibc extension but we try it unconditionally on this path. */ - strcat (copy, "e"); - result = fopen (filename, copy); + auto opentype_e = std::string (opentype) + 'e'; + result = fopen (filename, opentype_e.c_str ()); if (result == NULL && errno == EINVAL) { diff --git a/gdbsupport/filtered-iterator.h b/gdbsupport/filtered-iterator.h index e824d61..872bdeb 100644 --- a/gdbsupport/filtered-iterator.h +++ b/gdbsupport/filtered-iterator.h @@ -19,8 +19,6 @@ #ifndef GDBSUPPORT_FILTERED_ITERATOR_H #define GDBSUPPORT_FILTERED_ITERATOR_H -#include <type_traits> - /* A filtered iterator. This wraps BaseIterator and automatically skips elements that FilterFunc filters out. Requires that default-constructing a BaseIterator creates a valid one-past-end @@ -30,21 +28,28 @@ template<typename BaseIterator, typename FilterFunc> class filtered_iterator { public: - typedef filtered_iterator self_type; - typedef typename BaseIterator::value_type value_type; - typedef typename BaseIterator::reference reference; - typedef typename BaseIterator::pointer pointer; - typedef typename BaseIterator::iterator_category iterator_category; - typedef typename BaseIterator::difference_type difference_type; - - /* Construct by forwarding all arguments to the underlying - iterator. */ - template<typename... Args> - explicit filtered_iterator (Args &&...args) - : m_it (std::forward<Args> (args)...) + using self_type = filtered_iterator; + using value_type = typename std::iterator_traits<BaseIterator>::value_type; + using reference = typename std::iterator_traits<BaseIterator>::reference; + using pointer = typename std::iterator_traits<BaseIterator>::pointer; + using iterator_category + = typename std::iterator_traits<BaseIterator>::iterator_category; + using difference_type + = typename std::iterator_traits<BaseIterator>::difference_type; + + /* Construct by providing the begin underlying iterators. The end iterator + is default-constructed. */ + filtered_iterator (BaseIterator begin) + : filtered_iterator (std::move (begin), BaseIterator {}) + {} + + /* Construct by providing begin and end underlying iterators. */ + filtered_iterator (BaseIterator begin, BaseIterator end) + : m_it (std::move (begin)), m_end (std::move (end)) { skip_filtered (); } - /* Create a one-past-end iterator. */ + /* Create a one-past-end iterator. The underlying end iterator is obtained + by default-constructing. */ filtered_iterator () = default; /* Need these as the variadic constructor would be a better match @@ -56,9 +61,7 @@ public: : filtered_iterator (static_cast<const filtered_iterator &> (other)) {} - typename std::invoke_result<decltype(&BaseIterator::operator*), - BaseIterator>::type - operator* () const + decltype(auto) operator* () const { return *m_it; } self_type &operator++ () diff --git a/gdbsupport/format.cc b/gdbsupport/format.cc index be3d821..145c876 100644 --- a/gdbsupport/format.cc +++ b/gdbsupport/format.cc @@ -22,14 +22,11 @@ format_pieces::format_pieces (const char **arg, bool gdb_extensions, bool value_extension) { - const char *s; + const char *s = *arg; const char *string; - const char *prev_start; - const char *percent_loc; - char *sub_start, *current_substring; - enum argclass this_argclass; - s = *arg; + /* Buffer to hold the escaped-processed version of the string. */ + std::string de_escaped; if (gdb_extensions) { @@ -40,10 +37,6 @@ format_pieces::format_pieces (const char **arg, bool gdb_extensions, { /* Parse the format-control string and copy it into the string STRING, processing some kinds of escape sequence. */ - - char *f = (char *) alloca (strlen (s) + 1); - string = f; - while (*s != '"' && *s != '\0') { int c = *s++; @@ -56,34 +49,34 @@ format_pieces::format_pieces (const char **arg, bool gdb_extensions, switch (c = *s++) { case '\\': - *f++ = '\\'; + de_escaped += '\\'; break; case 'a': - *f++ = '\a'; + de_escaped += '\a'; break; case 'b': - *f++ = '\b'; + de_escaped += '\b'; break; case 'e': - *f++ = '\e'; + de_escaped += '\e'; break; case 'f': - *f++ = '\f'; + de_escaped += '\f'; break; case 'n': - *f++ = '\n'; + de_escaped += '\n'; break; case 'r': - *f++ = '\r'; + de_escaped += '\r'; break; case 't': - *f++ = '\t'; + de_escaped += '\t'; break; case 'v': - *f++ = '\v'; + de_escaped += '\v'; break; case '"': - *f++ = '"'; + de_escaped += '"'; break; default: /* ??? TODO: handle other escape sequences. */ @@ -93,29 +86,23 @@ format_pieces::format_pieces (const char **arg, bool gdb_extensions, break; default: - *f++ = c; + de_escaped += c; } } - /* Terminate our escape-processed copy. */ - *f++ = '\0'; + string = de_escaped.c_str (); /* Whether the format string ended with double-quote or zero, we're done with it; it's up to callers to complain about syntax. */ *arg = s; } - /* Need extra space for the '\0's. Doubling the size is sufficient. */ - - current_substring = (char *) xmalloc (strlen (string) * 2 + 1000); - m_storage.reset (current_substring); - /* Now scan the string for %-specs and see what kinds of args they want. argclass classifies the %-specs so we can give printf-type functions something of the right size. */ - const char *f = string; - prev_start = string; + const char *prev_start = string; + while (*f) if (*f++ == '%') { @@ -135,16 +122,15 @@ format_pieces::format_pieces (const char **arg, bool gdb_extensions, continue; } - sub_start = current_substring; + std::string::size_type sub_start = m_storage.size (); - strncpy (current_substring, prev_start, f - 1 - prev_start); - current_substring += f - 1 - prev_start; - *current_substring++ = '\0'; + m_storage.append (prev_start, f - 1 - prev_start); + m_storage += '\0'; - if (*sub_start != '\0') + if (m_storage[sub_start] != '\0') m_pieces.emplace_back (sub_start, literal_piece, 0); - percent_loc = f - 1; + const char *percent_loc = f - 1; /* Check the validity of the format specifier, and work out what argument it expects. We only accept C89 @@ -251,6 +237,8 @@ format_pieces::format_pieces (const char **arg, bool gdb_extensions, break; } + argclass this_argclass; + switch (*f) { case 'u': @@ -381,7 +369,7 @@ format_pieces::format_pieces (const char **arg, bool gdb_extensions, f++; - sub_start = current_substring; + sub_start = m_storage.size (); if (lcount > 1 && !seen_i64 && USE_PRINTF_I64) { @@ -389,11 +377,9 @@ format_pieces::format_pieces (const char **arg, bool gdb_extensions, Convert %lld to %I64d. */ int length_before_ll = f - percent_loc - 1 - lcount; - strncpy (current_substring, percent_loc, length_before_ll); - strcpy (current_substring + length_before_ll, "I64"); - current_substring[length_before_ll + 3] = - percent_loc[length_before_ll + lcount]; - current_substring += length_before_ll + 4; + m_storage.append (percent_loc, length_before_ll); + m_storage += "I64"; + m_storage += percent_loc[length_before_ll + lcount]; } else if (this_argclass == wide_string_arg || this_argclass == wide_char_arg) @@ -401,18 +387,13 @@ format_pieces::format_pieces (const char **arg, bool gdb_extensions, /* Convert %ls or %lc to %s. */ int length_before_ls = f - percent_loc - 2; - strncpy (current_substring, percent_loc, length_before_ls); - strcpy (current_substring + length_before_ls, "s"); - current_substring += length_before_ls + 2; + m_storage.append (percent_loc, length_before_ls); + m_storage += "s"; } else - { - strncpy (current_substring, percent_loc, f - percent_loc); - current_substring += f - percent_loc; - } - - *current_substring++ = '\0'; + m_storage.append (percent_loc, f - percent_loc); + m_storage += '\0'; prev_start = f; m_pieces.emplace_back (sub_start, this_argclass, n_int_args); @@ -422,11 +403,9 @@ format_pieces::format_pieces (const char **arg, bool gdb_extensions, if (f > prev_start) { - sub_start = current_substring; - - strncpy (current_substring, prev_start, f - prev_start); - current_substring += f - prev_start; - *current_substring++ = '\0'; + std::string::size_type sub_start = m_storage.size (); + m_storage.append (prev_start, f - prev_start); + /* No need for a final '\0', std::string already has one. */ m_pieces.emplace_back (sub_start, literal_piece, 0); } diff --git a/gdbsupport/format.h b/gdbsupport/format.h index 118b947..46dae22 100644 --- a/gdbsupport/format.h +++ b/gdbsupport/format.h @@ -20,8 +20,6 @@ #ifndef GDBSUPPORT_FORMAT_H #define GDBSUPPORT_FORMAT_H -#include <string_view> - #if defined(__MINGW32__) && !defined(PRINTF_HAS_LONG_LONG) # define USE_PRINTF_I64 1 # define PRINTF_HAS_LONG_LONG @@ -51,21 +49,15 @@ enum argclass struct format_piece { - format_piece (const char *str, enum argclass argc, int n) - : string (str), + format_piece (std::string::size_type start, enum argclass argc, int n) + : start (start), argclass (argc), n_int_args (n) - { - gdb_assert (str != nullptr); - } + {} - bool operator== (const format_piece &other) const - { - return (this->argclass == other.argclass - && std::string_view (this->string) == other.string); - } + /* Where this piece starts, within FORMAT_PIECES::M_STORAGE. */ + std::string::size_type start; - const char *string; enum argclass argclass; /* Count the number of preceding 'int' arguments that must be passed along. This is used for a width or precision of '*'. Note that @@ -95,10 +87,17 @@ public: return m_pieces.end (); } + /* Return the string associated to PIECE. */ + const char *piece_str (const format_piece &piece) + { return &m_storage[piece.start]; } + private: std::vector<format_piece> m_pieces; - gdb::unique_xmalloc_ptr<char> m_storage; + + /* This is used as a buffer of concatenated null-terminated strings. The + individual strings are referenced by FORMAT_PIECE::START. */ + std::string m_storage; }; #endif /* GDBSUPPORT_FORMAT_H */ diff --git a/gdbsupport/gdb-safe-ctype.h b/gdbsupport/gdb-safe-ctype.h deleted file mode 100644 index 36b78f5..0000000 --- a/gdbsupport/gdb-safe-ctype.h +++ /dev/null @@ -1,49 +0,0 @@ -/* Wrapper around libiberty's safe-ctype.h for GDB, the GNU debugger. - - Copyright (C) 2019-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/>. */ - -#ifndef GDBSUPPORT_GDB_SAFE_CTYPE_H -#define GDBSUPPORT_GDB_SAFE_CTYPE_H - -/* After safe-ctype.h is included, we can no longer use the host's - ctype routines. Trying to do so results in compile errors. Code - that uses safe-ctype.h that wants to refer to the locale-dependent - ctype functions must call these wrapper versions instead. - When compiling in C++ mode, also include <locale> before "safe-ctype.h" - which also defines is* functions. */ - -static inline int -gdb_isprint (int ch) -{ - return isprint (ch); -} - -/* readline.h defines these symbols too, but we want libiberty's - versions. */ -#undef ISALPHA -#undef ISALNUM -#undef ISDIGIT -#undef ISLOWER -#undef ISPRINT -#undef ISUPPER -#undef ISXDIGIT - -#include <locale> -#include "safe-ctype.h" - -#endif /* GDBSUPPORT_GDB_SAFE_CTYPE_H */ diff --git a/gdbsupport/gdb_argv_vec.h b/gdbsupport/gdb_argv_vec.h index 1f3b6db..43571ae 100644 --- a/gdbsupport/gdb_argv_vec.h +++ b/gdbsupport/gdb_argv_vec.h @@ -90,6 +90,15 @@ public: m_args.push_back (value); } + /* Like calling emplace_back on the underlying vector. This class takes + ownership of the value added to the vector, and will release the value + by calling xfree() on it when this object is destroyed. */ + template<typename... Args> + reference emplace_back (Args &&...args) + { + return m_args.emplace_back (std::forward<Args> (args)...); + } + /* Non constant iterator to start of m_args. */ iterator begin () { @@ -133,6 +142,12 @@ public: { return m_args.empty (); } + + /* Clear the argument vector. */ + void clear () + { + free_vector_argv (m_args); + } }; } /* namespac gdb */ diff --git a/gdbsupport/iterator-range.h b/gdbsupport/iterator-range.h index a8cacfb..d18939c 100644 --- a/gdbsupport/iterator-range.h +++ b/gdbsupport/iterator-range.h @@ -30,13 +30,11 @@ struct iterator_range /* Create an iterator_range using BEGIN as the begin iterator. Assume that the end iterator can be default-constructed. */ - template <typename... Args> - iterator_range (Args &&...args) - : m_begin (std::forward<Args> (args)...) + explicit iterator_range (IteratorType begin) + : iterator_range (std::move (begin), IteratorType {}) {} /* Create an iterator range using explicit BEGIN and END iterators. */ - template <typename... Args> iterator_range (IteratorType begin, IteratorType end) : m_begin (std::move (begin)), m_end (std::move (end)) {} @@ -57,6 +55,10 @@ struct iterator_range std::size_t size () const { return std::distance (m_begin, m_end); } + /* Return true if this range is empty. */ + bool empty () const + { return m_begin == m_end; } + private: IteratorType m_begin, m_end; }; diff --git a/gdbsupport/parallel-for.h b/gdbsupport/parallel-for.h index c485c36..7f3fef9 100644 --- a/gdbsupport/parallel-for.h +++ b/gdbsupport/parallel-for.h @@ -21,116 +21,92 @@ #define GDBSUPPORT_PARALLEL_FOR_H #include <algorithm> -#include <type_traits> +#include <atomic> +#include <tuple> +#include "gdbsupport/iterator-range.h" #include "gdbsupport/thread-pool.h" -#include "gdbsupport/function-view.h" +#include "gdbsupport/work-queue.h" namespace gdb { -/* A very simple "parallel for". This splits the range of iterators - into subranges, and then passes each subrange to the callback. The - work may or may not be done in separate threads. +/* If enabled, print debug info about the inner workings of the parallel for + each functions. */ +constexpr bool parallel_for_each_debug = false; - This approach was chosen over having the callback work on single - items because it makes it simple for the caller to do - once-per-subrange initialization and destruction. +/* A "parallel-for" implementation using a shared work queue. Work items get + popped in batches of size up to BATCH_SIZE from the queue and handed out to + worker threads. - The parameter N says how batching ought to be done -- there will be - at least N elements processed per thread. Setting N to 0 is not - allowed. */ + Each worker thread instantiates an object of type Worker, forwarding ARGS to + its constructor. The Worker object can be used to keep some per-worker + thread state. -template<class RandomIt, class RangeFunction> + Worker threads call Worker::operator() repeatedly until the queue is + empty. + + This function is synchronous, meaning that it blocks and returns once the + processing is complete. */ + +template<std::size_t batch_size, class RandomIt, class Worker, + class... WorkerArgs> void -parallel_for_each (unsigned n, RandomIt first, RandomIt last, - RangeFunction callback) +parallel_for_each (const RandomIt first, const RandomIt last, + WorkerArgs &&...worker_args) { - /* If enabled, print debug info about how the work is distributed across - the threads. */ - const bool parallel_for_each_debug = false; + gdb_assert (first <= last); - size_t n_worker_threads = thread_pool::g_thread_pool->thread_count (); - size_t n_threads = n_worker_threads; - size_t n_elements = last - first; - size_t elts_per_thread = 0; - size_t elts_left_over = 0; - - if (n_threads > 1) + if (parallel_for_each_debug) { - /* Require that there should be at least N elements in a - thread. */ - gdb_assert (n > 0); - if (n_elements / n_threads < n) - n_threads = std::max (n_elements / n, (size_t) 1); - elts_per_thread = n_elements / n_threads; - elts_left_over = n_elements % n_threads; - /* n_elements == n_threads * elts_per_thread + elts_left_over. */ + debug_printf ("Parallel for: n elements: %zu\n", + static_cast<std::size_t> (last - first)); + debug_printf ("Parallel for: batch size: %zu\n", batch_size); } - size_t count = n_threads == 0 ? 0 : n_threads - 1; std::vector<gdb::future<void>> results; + work_queue<RandomIt, batch_size> queue (first, last); - if (parallel_for_each_debug) - { - debug_printf (_("Parallel for: n_elements: %zu\n"), n_elements); - debug_printf (_("Parallel for: minimum elements per thread: %u\n"), n); - debug_printf (_("Parallel for: elts_per_thread: %zu\n"), elts_per_thread); - } + /* The worker thread task. + + We need to capture args as a tuple, because it's not possible to capture + the parameter pack directly in C++17. Once we migrate to C++20, the + capture can be simplified to: - for (int i = 0; i < count; ++i) + ... args = std::forward<Args>(args) + + and `args` can be used as-is in the lambda. */ + auto args_tuple + = std::forward_as_tuple (std::forward<WorkerArgs> (worker_args)...); + auto task = [&queue, first, &args_tuple] () { - RandomIt end; - end = first + elts_per_thread; - if (i < elts_left_over) - /* Distribute the leftovers over the worker threads, to avoid having - to handle all of them in a single thread. */ - end++; - - /* This case means we don't have enough elements to really - distribute them. Rather than ever submit a task that does - nothing, we short-circuit here. */ - if (first == end) - end = last; - - if (end == last) - { - /* We're about to dispatch the last batch of elements, which - we normally process in the main thread. So just truncate - the result list here. This avoids submitting empty tasks - to the thread pool. */ - count = i; - break; - } + /* Instantiate the user-defined worker. */ + auto worker = std::make_from_tuple<Worker> (args_tuple); - if (parallel_for_each_debug) + for (;;) { - debug_printf (_("Parallel for: elements on worker thread %i\t: %zu"), - i, (size_t)(end - first)); - debug_printf (_("\n")); + const auto batch = queue.pop_batch (); + + if (batch.empty ()) + break; + + if (parallel_for_each_debug) + debug_printf ("Processing %zu items, range [%zu, %zu[\n", + batch.size (), + batch.begin () - first, + batch.end () - first); + + worker (batch); } - results.push_back (gdb::thread_pool::g_thread_pool->post_task ([=] () - { - return callback (first, end); - })); - first = end; - } + }; - for (int i = count; i < n_worker_threads; ++i) - if (parallel_for_each_debug) - { - debug_printf (_("Parallel for: elements on worker thread %i\t: 0"), i); - debug_printf (_("\n")); - } + /* Start N_WORKER_THREADS tasks. */ + const size_t n_worker_threads + = std::max<size_t> (thread_pool::g_thread_pool->thread_count (), 1); - /* Process all the remaining elements in the main thread. */ - if (parallel_for_each_debug) - { - debug_printf (_("Parallel for: elements on main thread\t\t: %zu"), - (size_t)(last - first)); - debug_printf (_("\n")); - } - callback (first, last); + for (int i = 0; i < n_worker_threads; ++i) + results.push_back (gdb::thread_pool::g_thread_pool->post_task (task)); + /* Wait for all of them to be finished. */ for (auto &fut : results) fut.get (); } @@ -139,12 +115,142 @@ parallel_for_each (unsigned n, RandomIt first, RandomIt last, when debugging multi-threading behavior, and you want to limit multi-threading in a fine-grained way. */ -template<class RandomIt, class RangeFunction> +template<class RandomIt, class Worker, class... WorkerArgs> +void +sequential_for_each (RandomIt first, RandomIt last, WorkerArgs &&...worker_args) +{ + if (first == last) + return; + + Worker (std::forward<WorkerArgs> (worker_args)...) ({ first, last }); +} + +namespace detail +{ + +/* Type to hold the state shared between threads of + gdb::parallel_for_each_async. */ + +template<std::size_t min_batch_size, typename RandomIt, typename... WorkerArgs> +struct pfea_state +{ + pfea_state (RandomIt first, RandomIt last, std::function<void ()> &&done, + WorkerArgs &&...worker_args) + : first (first), + last (last), + worker_args_tuple (std::forward_as_tuple + (std::forward<WorkerArgs> (worker_args)...)), + queue (first, last), + m_done (std::move (done)) + {} + + DISABLE_COPY_AND_ASSIGN (pfea_state); + + /* This gets called by the last worker thread that drops its reference on + the shared state, thus when the processing is complete. */ + ~pfea_state () + { + if (m_done) + m_done (); + } + + /* The interval to process. */ + const RandomIt first, last; + + /* Tuple of arguments to pass when constructing the user's worker object. + + Use std::decay_t to avoid storing references to the caller's local + variables. If we didn't use it and the caller passed an lvalue `foo *`, + we would store it as a reference to `foo *`, thus storing a reference to + the caller's local variable. + + The downside is that it's not possible to pass arguments by reference, + callers need to pass pointers or std::reference_wrappers. */ + std::tuple<std::decay_t<WorkerArgs>...> worker_args_tuple; + + /* Work queue that worker threads pull work items from. */ + work_queue<RandomIt, min_batch_size> queue; + +private: + /* Callable called when the parallel-for is done. */ + std::function<void ()> m_done; +}; + +} /* namespace detail */ + +/* A "parallel-for" implementation using a shared work queue. Work items get + popped in batches from the queue and handed out to worker threads. + + Batch sizes are proportional to the number of remaining items in the queue, + but always greater or equal to MIN_BATCH_SIZE. + + The DONE callback is invoked when processing is done. + + Each worker thread instantiates an object of type Worker, forwarding ARGS to + its constructor. The Worker object can be used to keep some per-worker + thread state. This version does not support passing references as arguments + to the worker. Use std::reference_wrapper or pointers instead. + + Worker threads call Worker::operator() repeatedly until the queue is + empty. + + This function is asynchronous. An arbitrary worker thread will call the DONE + callback when processing is done. */ + +template<std::size_t min_batch_size, class RandomIt, class Worker, + class... WorkerArgs> void -sequential_for_each (unsigned n, RandomIt first, RandomIt last, - RangeFunction callback) +parallel_for_each_async (const RandomIt first, const RandomIt last, + std::function<void ()> &&done, + WorkerArgs &&...worker_args) { - callback (first, last); + gdb_assert (first <= last); + + if (parallel_for_each_debug) + { + debug_printf ("Parallel for: n elements: %zu\n", + static_cast<std::size_t> (last - first)); + debug_printf ("Parallel for: min batch size: %zu\n", min_batch_size); + } + + const size_t n_worker_threads + = std::max<size_t> (thread_pool::g_thread_pool->thread_count (), 1); + + /* The state shared between all worker threads. All worker threads get a + reference on the shared pointer through the lambda below. The last worker + thread to drop its reference will cause this object to be destroyed, which + will call the DONE callback. */ + using state_t = detail::pfea_state<min_batch_size, RandomIt, WorkerArgs...>; + auto state + = std::make_shared<state_t> (first, last, std::move (done), + std::forward<WorkerArgs> (worker_args)...); + + /* The worker thread task. */ + auto task = [state] () + { + /* Instantiate the user-defined worker. */ + auto worker = std::make_from_tuple<Worker> (state->worker_args_tuple); + + for (;;) + { + const auto batch = state->queue.pop_batch (); + + if (batch.empty ()) + break; + + if (parallel_for_each_debug) + debug_printf ("Processing %zu items, range [%zu, %zu[\n", + batch.size (), + batch.begin () - state->first, + batch.end () - state->first); + + worker (batch); + } + }; + + /* Start N_WORKER_THREADS tasks. */ + for (int i = 0; i < n_worker_threads; ++i) + gdb::thread_pool::g_thread_pool->post_task (task); } } diff --git a/gdbsupport/pathstuff.cc b/gdbsupport/pathstuff.cc index ce01c95..8142bd5 100644 --- a/gdbsupport/pathstuff.cc +++ b/gdbsupport/pathstuff.cc @@ -89,34 +89,25 @@ std::string gdb_realpath_keepfile (const char *filename) { const char *base_name = lbasename (filename); - char *dir_name; /* Extract the basename of filename, and return immediately a copy of filename if it does not contain any directory prefix. */ if (base_name == filename) return filename; - dir_name = (char *) alloca ((size_t) (base_name - filename + 2)); - /* Allocate enough space to store the dir_name + plus one extra - character sometimes needed under Windows (see below), and - then the closing \000 character. */ - strncpy (dir_name, filename, base_name - filename); - dir_name[base_name - filename] = '\000'; + std::string dir_name (filename, base_name - filename); #ifdef HAVE_DOS_BASED_FILE_SYSTEM /* We need to be careful when filename is of the form 'd:foo', which is equivalent of d:./foo, which is totally different from d:/foo. */ - if (strlen (dir_name) == 2 && isalpha (dir_name[0]) && dir_name[1] == ':') - { - dir_name[2] = '.'; - dir_name[3] = '\000'; - } + if (dir_name.size () == 2 && c_isalpha (dir_name[0]) && dir_name[1] == ':') + dir_name += '.'; #endif /* Canonicalize the directory prefix, and build the resulting filename. If the dirname realpath already contains an ending directory separator, avoid doubling it. */ - gdb::unique_xmalloc_ptr<char> path_storage = gdb_realpath (dir_name); + gdb::unique_xmalloc_ptr<char> path_storage = gdb_realpath (dir_name.c_str ()); const char *real_path = path_storage.get (); return path_join (real_path, base_name); } diff --git a/gdbsupport/poison.h b/gdbsupport/poison.h index 791a18f..a91ee5d 100644 --- a/gdbsupport/poison.h +++ b/gdbsupport/poison.h @@ -183,7 +183,7 @@ xnewvar (size_t s) { static_assert (IsMallocable<T>::value, "Trying to use XNEWVAR with a \ non-POD data type."); - return XNEWVAR (T, s);; + return XNEWVAR (T, s); } #undef XNEWVAR diff --git a/gdbsupport/print-utils.cc b/gdbsupport/print-utils.cc index 84a7485..8514720 100644 --- a/gdbsupport/print-utils.cc +++ b/gdbsupport/print-utils.cc @@ -145,7 +145,7 @@ static int thirty_two = 32; /* See print-utils.h. */ const char * -phex (ULONGEST l, int sizeof_l) +phex_ulongest (ULONGEST l, int sizeof_l) { char *str; @@ -170,7 +170,7 @@ phex (ULONGEST l, int sizeof_l) xsnprintf (str, PRINT_CELL_SIZE, "%02x", (unsigned short) (l & 0xff)); break; default: - return phex (l, sizeof (l)); + return phex (l); break; } @@ -180,7 +180,7 @@ phex (ULONGEST l, int sizeof_l) /* See print-utils.h. */ const char * -phex_nz (ULONGEST l, int sizeof_l) +phex_nz_ulongest (ULONGEST l, int sizeof_l) { char *str; @@ -212,7 +212,7 @@ phex_nz (ULONGEST l, int sizeof_l) xsnprintf (str, PRINT_CELL_SIZE, "%x", (unsigned short) (l & 0xff)); break; default: - return phex_nz (l, sizeof (l)); + return phex_nz (l); break; } @@ -226,7 +226,7 @@ hex_string (LONGEST num) { char *result = get_print_cell (); - xsnprintf (result, PRINT_CELL_SIZE, "0x%s", phex_nz (num, sizeof (num))); + xsnprintf (result, PRINT_CELL_SIZE, "0x%s", phex_nz (num)); return result; } @@ -237,7 +237,7 @@ hex_string_custom (LONGEST num, int width) { char *result = get_print_cell (); char *result_end = result + PRINT_CELL_SIZE - 1; - const char *hex = phex_nz (num, sizeof (num)); + const char *hex = phex_nz (num); int hex_len = strlen (hex); if (hex_len > width) @@ -304,8 +304,7 @@ core_addr_to_string (const CORE_ADDR addr) { char *str = get_print_cell (); - strcpy (str, "0x"); - strcat (str, phex (addr, sizeof (addr))); + xsnprintf (str, PRINT_CELL_SIZE, "0x%s", phex (addr)); return str; } @@ -316,8 +315,7 @@ core_addr_to_string_nz (const CORE_ADDR addr) { char *str = get_print_cell (); - strcpy (str, "0x"); - strcat (str, phex_nz (addr, sizeof (addr))); + xsnprintf (str, PRINT_CELL_SIZE, "0x%s", phex_nz (addr)); return str; } diff --git a/gdbsupport/print-utils.h b/gdbsupport/print-utils.h index e50d96f..dc5011c 100644 --- a/gdbsupport/print-utils.h +++ b/gdbsupport/print-utils.h @@ -34,15 +34,35 @@ extern const char *pulongest (ULONGEST u); extern const char *plongest (LONGEST l); -/* Convert a ULONGEST into a HEX string, like %lx, with leading zeros. +/* Convert L (of type ULONGEST) into a hex string, like %lx, with leading + zeros. The result is stored in a circular static buffer, NUMCELLS + deep. */ + +extern const char *phex_ulongest (ULONGEST l, int sizeof_l); + +/* Convert L into a HEX string, like %lx, with leading zeros. The result is stored in a circular static buffer, NUMCELLS deep. */ -extern const char *phex (ULONGEST l, int sizeof_l); +template<typename T> +const char *phex (T l, int sizeof_l = sizeof (T)) +{ + return phex_ulongest (l, sizeof_l); +} + +/* Convert L (of type ULONGEST) into a hex string, like %lx, without leading + zeros. The result is stored in a circular static buffer, NUMCELLS + deep. */ -/* Convert a ULONGEST into a HEX string, like %lx, without leading zeros. - The result is stored in a circular static buffer, NUMCELLS deep. */ +extern const char *phex_nz_ulongest (ULONGEST l, int sizeof_l); + +/* Convert L into a hex string, like %lx, without leading zeros. + The result is stored in a circular static buffer, NUMCELLS deep. */ -extern const char *phex_nz (ULONGEST l, int sizeof_l); +template<typename T> +const char *phex_nz (T l, int sizeof_l = sizeof (T)) +{ + return phex_nz_ulongest (l, sizeof_l); +} /* Converts a LONGEST to a C-format hexadecimal literal and stores it in a static string. Returns a pointer to this string. */ diff --git a/gdbsupport/reference-to-pointer-iterator.h b/gdbsupport/reference-to-pointer-iterator.h index af58e38..67a8d54 100644 --- a/gdbsupport/reference-to-pointer-iterator.h +++ b/gdbsupport/reference-to-pointer-iterator.h @@ -38,9 +38,8 @@ struct reference_to_pointer_iterator /* Construct a reference_to_pointer_iterator, passing args to the underlying iterator. */ - template <typename... Args> - reference_to_pointer_iterator (Args &&...args) - : m_it (std::forward<Args> (args)...) + explicit reference_to_pointer_iterator (IteratorType it) + : m_it (std::move (it)) {} /* Create a past-the-end iterator. diff --git a/gdbsupport/remote-args.cc b/gdbsupport/remote-args.cc new file mode 100644 index 0000000..2493433 --- /dev/null +++ b/gdbsupport/remote-args.cc @@ -0,0 +1,43 @@ +/* Copyright (C) 2023-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 "gdbsupport/common-defs.h" +#include "gdbsupport/remote-args.h" +#include "gdbsupport/common-inferior.h" +#include "gdbsupport/buildargv.h" + +/* See remote-args.h. */ + +std::vector<std::string> +gdb::remote_args::split (const std::string &args) +{ + std::vector<std::string> results; + + gdb_argv argv (args.c_str ()); + for (int i = 0; argv[i] != nullptr; i++) + results.emplace_back (argv[i]); + + return results; +} + +/* See remote-args.h. */ + +std::string +gdb::remote_args::join (const std::vector<char *> &args) +{ + return construct_inferior_arguments (args, true); +} diff --git a/gdbsupport/remote-args.h b/gdbsupport/remote-args.h new file mode 100644 index 0000000..0533da6 --- /dev/null +++ b/gdbsupport/remote-args.h @@ -0,0 +1,60 @@ +/* Functions to help when passing arguments between GDB and gdbserver. + + Copyright (C) 2023-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/>. */ + +#ifndef GDBSUPPORT_REMOTE_ARGS_H +#define GDBSUPPORT_REMOTE_ARGS_H + +/* The functions declared here are used when passing inferior arguments + from GDB to gdbserver. + + The remote protocol requires that arguments are passed as a vector of + separate argument while GDB stores the arguments as a single string, and + gdbserver also requires the arguments be a single string. + + These functions then provide a mechanism to split up an argument string + and recombine it within gdbserver while preserving escaping of special + characters within the argument string. */ + +namespace gdb +{ + +namespace remote_args +{ + +/* ARGS is an inferior argument string. This function splits ARGS into + individual arguments and returns a vector containing each argument. */ + +extern std::vector<std::string> split (const std::string &args); + +/* Join together the separate arguments in ARGS and build a single + inferior argument string. The string returned by this function will be + equivalent, but not necessarily identical to the string passed to + ::split, for example passing the string '"a b"' (without the single + quotes, but including the double quotes) to ::split, will return an + argument of 'a b' (without the single quotes). When this argument is + passed through ::join we will get back the string 'a\ b' (without the + single quotes), that is, we choose to escape the white space, rather + than wrap the argument in quotes. */ +extern std::string join (const std::vector<char *> &args); + +} /* namespace remote_args */ + +} /* namespace gdb */ + +#endif /* GDBSUPPORT_REMOTE_ARGS_H */ diff --git a/gdbsupport/run-time-clock.cc b/gdbsupport/run-time-clock.cc index 43da1d9..cda60b0 100644 --- a/gdbsupport/run-time-clock.cc +++ b/gdbsupport/run-time-clock.cc @@ -37,14 +37,51 @@ timeval_to_microseconds (struct timeval *tv) } #endif +/* See run-time-clock.h. */ + +bool +get_run_time_thread_scope_available () +{ +#if defined HAVE_GETRUSAGE && defined RUSAGE_THREAD + return true; +#else + return false; +#endif +} + void -run_time_clock::now (user_cpu_time_clock::time_point &user, - system_cpu_time_clock::time_point &system) noexcept +get_run_time (user_cpu_time_clock::time_point &user, + system_cpu_time_clock::time_point &system, + run_time_scope scope) noexcept { #ifdef HAVE_GETRUSAGE + + /* If we can't provide thread scope run time usage, fallback to + process scope. This will at least be right if GDB is built + without threading support in the first place (or is set to use + zero worker threads). */ +# ifndef RUSAGE_THREAD +# define RUSAGE_THREAD RUSAGE_SELF +# endif + struct rusage rusage; + int who; + + switch (scope) + { + case run_time_scope::thread: + who = RUSAGE_THREAD; + break; + + case run_time_scope::process: + who = RUSAGE_SELF; + break; + + default: + gdb_assert_not_reached ("invalid run_time_scope value"); + } - getrusage (RUSAGE_SELF, &rusage); + getrusage (who, &rusage); microseconds utime = timeval_to_microseconds (&rusage.ru_utime); microseconds stime = timeval_to_microseconds (&rusage.ru_stime); diff --git a/gdbsupport/run-time-clock.h b/gdbsupport/run-time-clock.h index a961f4c..2743514 100644 --- a/gdbsupport/run-time-clock.h +++ b/gdbsupport/run-time-clock.h @@ -51,6 +51,33 @@ struct system_cpu_time_clock static time_point now () noexcept = delete; }; +/* Whether to measure time run time for the whole process or just one + thread. */ + +enum class run_time_scope +{ + process, + thread, +}; + +/* Return the user/system time as separate time points, if + supported. If not supported, then the combined user+kernel time + is returned in USER and SYSTEM is set to zero. + + SCOPE indicates whether to return the run time for the whole + process or just for the calling thread. If the latter isn't + supported, then returns the run time for the whole process even if + run_time_scope::thread is requested. */ + +void get_run_time (user_cpu_time_clock::time_point &user, + system_cpu_time_clock::time_point &system, + run_time_scope scope) noexcept; + +/* Returns true if is it possible for get_run_time above to return the + run time for just the calling thread. */ + +bool get_run_time_thread_scope_available (); + /* Count the total amount of time spent executing in userspace+kernel mode. */ @@ -64,12 +91,6 @@ struct run_time_clock static constexpr bool is_steady = true; static time_point now () noexcept; - - /* Return the user/system time as separate time points, if - supported. If not supported, then the combined user+kernel time - is returned in USER and SYSTEM is set to zero. */ - static void now (user_cpu_time_clock::time_point &user, - system_cpu_time_clock::time_point &system) noexcept; }; #endif /* GDBSUPPORT_RUN_TIME_CLOCK_H */ diff --git a/gdbsupport/safe-iterator.h b/gdbsupport/safe-iterator.h index 5388160..4cb9a19 100644 --- a/gdbsupport/safe-iterator.h +++ b/gdbsupport/safe-iterator.h @@ -50,30 +50,24 @@ public: typedef typename Iterator::iterator_category iterator_category; typedef typename Iterator::difference_type difference_type; - /* Construct the begin iterator using the given arguments; the end iterator is - default constructed. */ - template<typename... Args> - explicit basic_safe_iterator (Args &&...args) - : m_it (std::forward<Args> (args)...), - m_next (m_it) - { - if (m_it != m_end) - ++m_next; - } + /* Construct the iterator using the underlying iterator BEGIN; the end + iterator is default constructed. */ + explicit basic_safe_iterator (Iterator begin) + : basic_safe_iterator (std::move (begin), Iterator {}) + {} - /* Construct the iterator using the first argument, and construct - the end iterator using the second argument. */ - template<typename Arg> - explicit basic_safe_iterator (Arg &&arg, Arg &&arg2) - : m_it (std::forward<Arg> (arg)), + /* Construct the iterator using the underlying iterators BEGIN and END. */ + basic_safe_iterator (Iterator begin, Iterator end) + : m_it (std::move (begin)), m_next (m_it), - m_end (std::forward<Arg> (arg2)) + m_end (std::move (end)) { if (m_it != m_end) ++m_next; } - /* Create a one-past-end iterator. */ + /* Create a one-past-end iterator. The underlying end iterator is obtained + by default-constructing. */ basic_safe_iterator () {} diff --git a/gdbsupport/thread-pool.h b/gdbsupport/thread-pool.h index f3ac94b..b5b2934 100644 --- a/gdbsupport/thread-pool.h +++ b/gdbsupport/thread-pool.h @@ -24,99 +24,12 @@ #include <vector> #include <functional> #include <chrono> -#if CXX_STD_THREAD -#include <thread> -#include <mutex> -#include <condition_variable> -#include <future> -#endif #include <optional> -namespace gdb -{ - -#if CXX_STD_THREAD - -/* Simply use the standard future. */ -template<typename T> -using future = std::future<T>; - -/* ... and the standard future_status. */ -using future_status = std::future_status; - -#else /* CXX_STD_THREAD */ - -/* A compatibility enum for std::future_status. This is just the - subset needed by gdb. */ -enum class future_status -{ - ready, - timeout, -}; - -/* A compatibility wrapper for std::future. Once <thread> and - <future> are available in all GCC builds -- should that ever happen - -- this can be removed. GCC does not implement threading for - MinGW, see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93687. - - Meanwhile, in this mode, there are no threads. Tasks submitted to - the thread pool are invoked immediately and their result is stored - here. The base template here simply wraps a T and provides some - std::future compatibility methods. The provided methods are chosen - based on what GDB needs presently. */ - -template<typename T> -class future -{ -public: - - explicit future (T value) - : m_value (std::move (value)) - { - } - - future () = default; - future (future &&other) = default; - future (const future &other) = delete; - future &operator= (future &&other) = default; - future &operator= (const future &other) = delete; +#include "gdbsupport/cxx-thread.h" - void wait () const { } - - template<class Rep, class Period> - future_status wait_for (const std::chrono::duration<Rep,Period> &duration) - const - { - return future_status::ready; - } - - T get () { return std::move (m_value); } - -private: - - T m_value; -}; - -/* A specialization for void. */ - -template<> -class future<void> +namespace gdb { -public: - void wait () const { } - - template<class Rep, class Period> - future_status wait_for (const std::chrono::duration<Rep,Period> &duration) - const - { - return future_status::ready; - } - - void get () { } -}; - -#endif /* CXX_STD_THREAD */ - /* A thread pool. diff --git a/gdbsupport/work-queue.h b/gdbsupport/work-queue.h new file mode 100644 index 0000000..66f073d --- /dev/null +++ b/gdbsupport/work-queue.h @@ -0,0 +1,96 @@ +/* Synchronized work queue. + + Copyright (C) 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/>. */ + +#ifndef GDBSUPPORT_WORK_QUEUE_H +#define GDBSUPPORT_WORK_QUEUE_H + +#include "gdbsupport/iterator-range.h" + +namespace gdb +{ + +/* Implementation of a thread-safe work queue. + + The work items are specified by two iterators of type RandomIt. + + BATCH_SIZE is the number of work items to pop in a batch. */ + +template<typename RandomIt, std::size_t batch_size> +class work_queue +{ +public: + /* The work items are specified by the range `[first, last[`. */ + work_queue (const RandomIt first, const RandomIt last) noexcept + : m_next (first), + m_last (last) + { + gdb_assert (first <= last); + } + + DISABLE_COPY_AND_ASSIGN (work_queue); + + /* Pop a batch of work items. + + The return value is an iterator range delimiting the work items. */ + iterator_range<RandomIt> pop_batch () noexcept + { + for (;;) + { + /* Grab a snapshot of M_NEXT. */ + auto next = m_next.load (); + gdb_assert (next <= m_last); + + /* The number of items remaining in the queue. */ + const auto n_remaining = static_cast<std::size_t> (m_last - next); + + /* Are we done? */ + if (n_remaining == 0) + return { m_last, m_last }; + + /* The batch size is proportional to the number of items remaining in + the queue. We do this to try to stike a balance, avoiding + synchronization overhead when there are many items to process at the + start, and avoiding workload imbalance when there are few items to + process at the end. */ + const auto this_batch_size = std::min (batch_size, n_remaining); + + /* The range of items in this batch. */ + const auto this_batch_first = next; + const auto this_batch_last = next + this_batch_size; + + /* Update M_NEXT. If the current value of M_NEXT doesn't match NEXT, it + means another thread updated it concurrently, restart. */ + if (!m_next.compare_exchange_weak (next, this_batch_last)) + continue; + + return { this_batch_first, this_batch_last }; + } + } + +private: + /* The next work item to hand out. */ + std::atomic<RandomIt> m_next; + + /* The end of the work item range. */ + RandomIt m_last; +}; + +} /* namespace gdb */ + +#endif /* GDBSUPPORT_WORK_QUEUE_H */ diff --git a/gdbsupport/x86-xstate.h b/gdbsupport/x86-xstate.h index 5d563ff..6657c45 100644 --- a/gdbsupport/x86-xstate.h +++ b/gdbsupport/x86-xstate.h @@ -28,6 +28,7 @@ #define X86_XSTATE_ZMM_H_ID 6 #define X86_XSTATE_ZMM_ID 7 #define X86_XSTATE_PKRU_ID 9 +#define X86_XSTATE_CET_U_ID 11 /* The extended state feature bits. */ #define X86_XSTATE_X87 (1ULL << X86_XSTATE_X87_ID) @@ -42,6 +43,7 @@ | X86_XSTATE_ZMM) #define X86_XSTATE_PKRU (1ULL << X86_XSTATE_PKRU_ID) +#define X86_XSTATE_CET_U (1ULL << X86_XSTATE_CET_U_ID) /* Total size of the XSAVE area extended region and offsets of register states within the region. Offsets are set to 0 to @@ -83,8 +85,11 @@ constexpr bool operator!= (const x86_xsave_layout &lhs, #define X86_XSTATE_AVX_AVX512_PKU_MASK (X86_XSTATE_AVX_MASK\ | X86_XSTATE_AVX512 | X86_XSTATE_PKRU) -#define X86_XSTATE_ALL_MASK (X86_XSTATE_AVX_AVX512_PKU_MASK) +/* Supported mask of state-component bitmap xstate_bv. The SDM defines + xstate_bv as XCR0 | IA32_XSS. */ +#define X86_XSTATE_ALL_MASK (X86_XSTATE_AVX_AVX512_PKU_MASK\ + | X86_XSTATE_CET_U) #define X86_XSTATE_SSE_SIZE 576 #define X86_XSTATE_AVX_SIZE 832 |