Compare commits

...

2 Commits

Author SHA1 Message Date
Pedro Alves
73a029bd60 struct packed: add fallback byte array implementation, use gcc_struct on Windows, unit tests
https://sourceware.org/bugzilla/show_bug.cgi?id=29373

- Windows hosts need attribute gcc_struct.

- attribute gcc_struct seemingly doesn't work with Clang, so add a
  fallback standard-conforming implementation based on arrays.

- add unit tests to make sure both implementations behave the same
  way.

- add more relational operators while at it.

Change-Id: I023315ee03622c59c397bf4affc0b68179c32374
2022-07-19 15:44:07 +01:00
Pedro Alves
6994a75776 Don't stop all threads prematurely after first step of "step N"
In all-stop mode, when the target is itself in non-stop mode (like
GNU/Linux), if you use the "step N" (or "stepi/next/nexti N") to step
a thread a number of times:

 (gdb) help step
 step, s
 Step program until it reaches a different source line.
 Usage: step [N]
 Argument N means step N times (or till program stops for another reason).

... GDB prematurely stops all threads after the first step, and
doesn't re-resume them for the subsequent N-1 steps.  It's as if for
the 2nd and subsequent steps, the command was running with
scheduler-locking enabled.

This can be observed with the testcase added by this commit, which
looks like this:

 static pthread_barrier_t barrier;

 static void *
 thread_func (void *arg)
 {
   pthread_barrier_wait (&barrier);
   return NULL;
 }

 int
 main ()
 {
   pthread_t thread;
   int ret;

   pthread_barrier_init (&barrier, NULL, 2);

   /* We run to this line below, and then issue "next 3".  That should
      step over the 3 lines below and land on the return statement.  If
      GDB prematurely stops the thread_func thread after the first of
      the 3 nexts (and never resumes it again), then the join won't
      ever return.  */
   pthread_create (&thread, NULL, thread_func, NULL); /* set break here */
   pthread_barrier_wait (&barrier);
   pthread_join (thread, NULL);

   return 0;
 }

The test hangs and times out without the GDB fix:

 (gdb) next 3
 [New Thread 0x7ffff7d89700 (LWP 525772)]
 FAIL: gdb.threads/step-N-all-progress.exp: non-stop=off: target-non-stop=on: next 3 (timeout)

The problem is a core gdb bug.

When you do "step/stepi/next/nexti N", GDB internally creates a
thread_fsm object and associates it with the stepping thread.  For the
stepping commands, the FSM's class is step_command_fsm.  That object
is what keeps track of how many steps are left to make.  When one step
finishes, handle_inferior_event calls stop_waiting and returns, and
then fetch_inferior_event calls the "should_stop" method of the event
thread's FSM.  The implementation of that method decrements the
steps-left counter.  If the counter is 0, it returns true and we
proceed to presenting the stop to the user.  If it isn't 0 yet, then
the method returns false, indicating to fetch_inferior_event to "keep
going".

Focusing now on when the first step finishes -- we're in "all-stop"
mode, with the target in non-stop mode.  When a step finishes,
handle_inferior_event calls stop_waiting, which itself calls
stop_all_threads to stop everything.  I.e., after the first step
completes, all threads are stopped, before handle_inferior_event
returns.  And after that, now in fetch_inferior_event, we consult the
thread's thread_fsm::should_stop, which as we've seen, for the first
step returns false -- i.e., we need to keep_going for another step.
However, since the target is in non-stop mode, keep_going resumes
_only_ the current thread.  All the other threads remain stopped,
inadvertently.

If the target is in non-stop mode, we don't actually need to stop all
threads right after each first step finishes, and then re-resume them
again.  We can instead defer stopping all threads until all the steps
are completed.

So fix this by delaying the stopping of all threads until after we
called the FSM's "should_stop" method.  I.e., move it from
stop_waiting, to handle_inferior_events's callers,
fetch_inferior_event and wait_for_inferior.

New test included.  Tested on x86-64 GNU/Linux native and gdbserver.

Change-Id: Iaad50dcfea4464c84bdbac853a89df92ade6ae01
2022-07-18 19:54:54 +01:00
6 changed files with 343 additions and 35 deletions

View File

@@ -464,6 +464,7 @@ SELFTESTS_SRCS = \
unittests/offset-type-selftests.c \
unittests/observable-selftests.c \
unittests/optional-selftests.c \
unittests/packed-selftests.c \
unittests/parallel-for-selftests.c \
unittests/parse-connection-spec-selftests.c \
unittests/path-join-selftests.c \

View File

@@ -3971,6 +3971,16 @@ prepare_for_detach (void)
}
}
/* If all-stop, but there exists a non-stop target, stop all threads
now that we're presenting the stop to the user. */
static void
stop_all_threads_if_all_stop_mode ()
{
if (!non_stop && exists_non_stop_target ())
stop_all_threads ("presenting stop to user in all-stop");
}
/* Wait for control to return from inferior to debugger.
If inferior gets a signal, we may decide to start it up again
@@ -4017,6 +4027,8 @@ wait_for_inferior (inferior *inf)
break;
}
stop_all_threads_if_all_stop_mode ();
/* No error, don't finish the state yet. */
finish_state.release ();
}
@@ -4240,6 +4252,8 @@ fetch_inferior_event ()
bool should_notify_stop = true;
int proceeded = 0;
stop_all_threads_if_all_stop_mode ();
clean_up_just_stopped_threads_fsms (ecs);
if (thr != nullptr && thr->thread_fsm () != nullptr)
@@ -8138,11 +8152,6 @@ stop_waiting (struct execution_control_state *ecs)
/* Let callers know we don't want to wait for the inferior anymore. */
ecs->wait_some_more = 0;
/* If all-stop, but there exists a non-stop target, stop all
threads now that we're presenting the stop to the user. */
if (!non_stop && exists_non_stop_target ())
stop_all_threads ("presenting stop to user in all-stop");
}
/* Like keep_going, but passes the signal to the inferior, even if the

View File

@@ -0,0 +1,49 @@
/* This testcase is part of GDB, the GNU debugger.
Copyright 2022 Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
#include <pthread.h>
#include <assert.h>
#include <stdlib.h>
static pthread_barrier_t barrier;
static void *
thread_func (void *arg)
{
pthread_barrier_wait (&barrier);
return NULL;
}
int
main ()
{
pthread_t thread;
int ret;
pthread_barrier_init (&barrier, NULL, 2);
/* We run to this line below, and then issue "next 3". That should
step over the 3 lines below and land on the return statement. If
GDB prematurely stops the thread_func thread after the first of
the 3 nexts (and never resumes it again), then the join won't
ever return. */
pthread_create (&thread, NULL, thread_func, NULL); /* set break here */
pthread_barrier_wait (&barrier);
pthread_join (thread, NULL);
return 0;
}

View File

@@ -0,0 +1,59 @@
# Copyright 2022 Free Software Foundation, Inc.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Test that with "next N", all threads are resumed at each of the N
# steps. GDB used to have a bug where in target-non-stop targets, and
# in all-stop mode, after the first "next" (or "step/stepi/nexti"),
# GDB would prematurely stop all threads. For the subsequent N-1
# steps, only the stepped thread would continue running, while all the
# other threads would remain stopped.
standard_testfile
if { [build_executable "failed to prepare" $testfile \
$srcfile {debug pthreads}] == -1 } {
return
}
proc test {non-stop target-non-stop} {
save_vars ::GDBFLAGS {
append ::GDBFLAGS " -ex \"maintenance set target-non-stop ${target-non-stop}\""
append ::GDBFLAGS " -ex \"set non-stop ${non-stop}\""
clean_restart $::binfile
}
if { ![runto_main] } {
return
}
set lineno [gdb_get_line_number "set break here"]
gdb_breakpoint $lineno
gdb_continue_to_breakpoint "break here"
gdb_test "next 3" "return 0;"
}
foreach_with_prefix non-stop {off on} {
foreach_with_prefix target-non-stop {off on} {
if {${non-stop} == "on" && ${target-non-stop} == "off"} {
# Invalid combination.
continue
}
test ${non-stop} ${target-non-stop}
}
}

View File

@@ -0,0 +1,125 @@
/* Self tests for packed for GDB, the GNU debugger.
Copyright (C) 2022 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/selftest.h"
#include "gdbsupport/packed.h"
#include <atomic>
namespace selftests {
namespace packed_tests {
gdb_static_assert (sizeof (packed<unsigned int, 1>) == 1);
gdb_static_assert (sizeof (packed<unsigned int, 2>) == 2);
gdb_static_assert (sizeof (packed<unsigned int, 3>) == 3);
gdb_static_assert (sizeof (packed<unsigned int, 4>) == 4);
gdb_static_assert (alignof (packed<unsigned int, 1>) == 1);
gdb_static_assert (alignof (packed<unsigned int, 2>) == 1);
gdb_static_assert (alignof (packed<unsigned int, 3>) == 1);
gdb_static_assert (alignof (packed<unsigned int, 4>) == 1);
/* Triviality checks. */
#define CHECK_TRAIT(TRAIT) \
static_assert (std::TRAIT<packed<unsigned int, 1>>::value, "")
#if HAVE_IS_TRIVIALLY_COPYABLE
CHECK_TRAIT (is_trivially_copyable);
CHECK_TRAIT (is_trivially_copy_constructible);
CHECK_TRAIT (is_trivially_move_constructible);
CHECK_TRAIT (is_trivially_copy_assignable);
CHECK_TRAIT (is_trivially_move_assignable);
#endif
#undef CHECK_TRAIT
/* Entry point. */
static void
run_tests ()
{
typedef packed<unsigned int, 2> packed_2;
packed_2 p1;
packed_2 p2 (0x0102);
p1 = 0x0102;
SELF_CHECK (p1 == p1);
SELF_CHECK (p1 == p2);
SELF_CHECK (p1 == 0x0102);
SELF_CHECK (0x0102 == p1);
SELF_CHECK (p1 != 0);
SELF_CHECK (0 != p1);
SELF_CHECK (p1 != 0x0103);
SELF_CHECK (0x0103 != p1);
SELF_CHECK (p1 != 0x01020102);
SELF_CHECK (0x01020102 != p1);
SELF_CHECK (p1 != 0x01020000);
SELF_CHECK (0x01020000 != p1);
/* Check truncation. */
p1 = 0x030102;
SELF_CHECK (p1 == 0x0102);
SELF_CHECK (p1 != 0x030102);
/* Check that the custom std::atomic/packed/T relational operators
work as intended. No need for fully comprehensive tests, as all
operators are defined in the same way, via a macro. We just want
to make sure that we can (non-atomically) compare atomic-wrapped
packed, with packed, and with the packed underlying type. */
std::atomic<packed<unsigned int, 2>> atomic_packed_2 (0x0102);
SELF_CHECK (atomic_packed_2 == atomic_packed_2);
SELF_CHECK (atomic_packed_2 == p1);
SELF_CHECK (p1 == atomic_packed_2);
SELF_CHECK (atomic_packed_2 == 0x0102u);
SELF_CHECK (0x0102u == atomic_packed_2);
SELF_CHECK (atomic_packed_2 >= 0x0102u);
SELF_CHECK (atomic_packed_2 <= 0x0102u);
SELF_CHECK (atomic_packed_2 > 0u);
SELF_CHECK (atomic_packed_2 < 0x0103u);
SELF_CHECK (atomic_packed_2 >= 0u);
SELF_CHECK (atomic_packed_2 <= 0x0102u);
SELF_CHECK (!(atomic_packed_2 > 0x0102u));
SELF_CHECK (!(atomic_packed_2 < 0x0102u));
/* Check std::atomic<packed> truncation behaves the same as without
std::atomic. */
atomic_packed_2 = 0x030102;
SELF_CHECK (atomic_packed_2 == 0x0102u);
SELF_CHECK (atomic_packed_2 != 0x030102u);
}
} /* namespace packed_tests */
} /* namespace selftests */
void _initialize_packed_selftests ();
void
_initialize_packed_selftests ()
{
selftests::register_test ("packed", selftests::packed_tests::run_tests);
}

View File

@@ -27,6 +27,26 @@
bit-fields (and ENUM_BITFIELD), when the fields must have separate
memory locations to avoid data races. */
/* There are two implementations here -- one standard compliant, using
a byte array for internal representation, and another that relies on
bitfields and attribute packed (and attribute gcc_struct on
Windows). The latter is preferable, as it is more convenient to
debug -- an enum wrapped in struct packed is printed by GDB as an
enum -- but may not work on all compilers. */
/* Clang does not support attribute gcc_struct. */
#if defined _WIN32 && defined __clang__
# define PACKED_USE_ARRAY 1
#else
# define PACKED_USE_ARRAY 0
#endif
#if !PACKED_USE_ARRAY && defined _WIN32
# define ATTRIBUTE_GCC_STRUCT __attribute__((__gcc_struct__))
#else
# define ATTRIBUTE_GCC_STRUCT
#endif
template<typename T, size_t Bytes = sizeof (T)>
struct packed
{
@@ -35,7 +55,18 @@ public:
packed (T val)
{
gdb_static_assert (sizeof (size_t) >= sizeof (T));
#if PACKED_USE_ARRAY
size_t tmp = val;
for (int i = (Bytes - 1); i >= 0; --i)
{
m_bytes[i] = tmp & 0xff;
tmp >>= 8;
}
#else
m_val = val;
#endif
/* Ensure size and aligment are what we expect. */
gdb_static_assert (sizeof (packed) == Bytes);
@@ -53,44 +84,78 @@ public:
operator T () const noexcept
{
#if PACKED_USE_ARRAY
size_t tmp = 0;
for (int i = 0;;)
{
tmp |= m_bytes[i];
if (++i == Bytes)
break;
tmp <<= 8;
}
return (T) tmp;
#else
return m_val;
#endif
}
private:
#if PACKED_USE_ARRAY
gdb_byte m_bytes[Bytes];
#else
T m_val : (Bytes * HOST_CHAR_BIT) ATTRIBUTE_PACKED;
};
#endif
} ATTRIBUTE_GCC_STRUCT;
/* Add some comparisons between std::atomic<packed<T>> and T. We need
this because the regular comparisons would require two implicit
conversions to go from T to std::atomic<packed<T>>:
/* Add some (non-atomic) comparisons between std::atomic<packed<T>>
and packed<T> and T. We need this because even though
std::atomic<T> doesn't define these operators, the relational
expressions still work via implicit conversions. Thos wouldn't
work when wrapped in packed without these operators, because we'd
require two implicit conversions to go from T to packed<T> to
std::atomic<packed<T>> (and back), and C++ only does one. */
T -> packed<T>
packed<T> -> std::atomic<packed<T>>
#define PACKED_ATOMIC_OP(OP) \
template<typename T, size_t Bytes> \
bool operator OP (const std::atomic<packed<T, Bytes>> &lhs, \
const std::atomic<packed<T, Bytes>> &rhs) \
{ \
return lhs.load () OP rhs.load (); \
} \
\
template<typename T, size_t Bytes> \
bool operator OP (T lhs, const std::atomic<packed<T, Bytes>> &rhs) \
{ \
return lhs OP rhs.load (); \
} \
\
template<typename T, size_t Bytes> \
bool operator OP (const std::atomic<packed<T, Bytes>> &lhs, T rhs) \
{ \
return lhs.load () OP rhs; \
} \
\
template<typename T, size_t Bytes> \
bool operator OP (const std::atomic<packed<T, Bytes>> &lhs, \
packed<T, Bytes> rhs) \
{ \
return lhs.load () OP rhs; \
} \
\
template<typename T, size_t Bytes> \
bool operator OP (packed<T, Bytes> lhs, \
const std::atomic<packed<T, Bytes>> &rhs) \
{ \
return lhs OP rhs.load (); \
}
and C++ only does one. */
PACKED_ATOMIC_OP (==)
PACKED_ATOMIC_OP (!=)
PACKED_ATOMIC_OP (>)
PACKED_ATOMIC_OP (<)
PACKED_ATOMIC_OP (>=)
PACKED_ATOMIC_OP (<=)
template<typename T, size_t Bytes>
bool operator== (T lhs, const std::atomic<packed<T, Bytes>> &rhs)
{
return lhs == rhs.load ();
}
template<typename T, size_t Bytes>
bool operator== (const std::atomic<packed<T, Bytes>> &lhs, T rhs)
{
return lhs.load () == rhs;
}
template<typename T, size_t Bytes>
bool operator!= (T lhs, const std::atomic<packed<T, Bytes>> &rhs)
{
return !(lhs == rhs);
}
template<typename T, size_t Bytes>
bool operator!= (const std::atomic<packed<T, Bytes>> &lhs, T rhs)
{
return !(lhs == rhs);
}
#undef PACKED_ATOMIC_OP
#endif