public inbox for gdb-patches@sourceware.org
 help / color / mirror / Atom feed
* [PATCH v3] gdb/testsuite: add test for backtracing for threaded inferiors from a corefile
@ 2023-10-25 11:42 Guinevere Larsen
  2023-11-14 10:51 ` [PING] " Guinevere Larsen
                   ` (3 more replies)
  0 siblings, 4 replies; 8+ messages in thread
From: Guinevere Larsen @ 2023-10-25 11:42 UTC (permalink / raw)
  To: gdb-patches; +Cc: Guinevere Larsen, Andrew Burgess

This patch is based on an out-of-tree patch that fedora has been
carrying for a while. It tests if GDB is able to properly unwind a
threaded program in the following situations:
* regular threads
* in a signal handler
* in a signal handler executing on an alternate stack

And the final frame can either be in a syscall or in an infinite loop.

The test works by running the inferior until a crash to generate a
corefile, or until right before the crash. Then applies a backtrace to
all threads to see if any frame can't be identified, and the order of
the threads in GDB. Finally, it goes thread by thread and tries to
collect a large part of the backtrace, to confirm that everything is
being unwound correctly.

Co-Authored-By: Andrew Burgess <aburgess@redhat.com>
---

Changes for v3:
* Resolved Lancelot's comment
* undid early exit in favor of more readable gdb_test usage to load the
  corefile

Changes for v2:
* Linaro CI identified an issue with the test, which made the test fail
  when using read1. Fixed here
* Also added early exit on corefile tests, if the corefile isn't
  properly loaded

---
 gdb/testsuite/gdb.threads/threadcrash.c   | 443 ++++++++++++++++++++++
 gdb/testsuite/gdb.threads/threadcrash.exp | 209 ++++++++++
 2 files changed, 652 insertions(+)
 create mode 100644 gdb/testsuite/gdb.threads/threadcrash.c
 create mode 100644 gdb/testsuite/gdb.threads/threadcrash.exp

diff --git a/gdb/testsuite/gdb.threads/threadcrash.c b/gdb/testsuite/gdb.threads/threadcrash.c
new file mode 100644
index 00000000000..e476ae7b07d
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/threadcrash.c
@@ -0,0 +1,443 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2023 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>
+#include <signal.h>
+#include <unistd.h>
+
+/* The delay that the main thread gives once all the worker threads have
+   reached the barrier before the main thread enters the function on which
+   GDB will have placed a breakpoint.  */
+
+#define MAIN_THREAD_DELAY 2
+
+/* The maximum time we allow this test program to run for before an alarm
+   signal is sent and everything will exit.  */
+#define WATCHDOG_ALARM_TIME 600
+
+/* Aliases for the signals used within this script.  Each signal
+   corresponds to an action (from the FINAL_ACTION enum) that the signal
+   handler will perform.  */
+
+#define SPIN_SIGNAL SIGUSR1
+#define SYSCALL_SIGNAL SIGUSR2
+
+/* Describe the final action that a thread should perform.  */
+
+enum final_action
+  {
+    /* Thread should spin in an infinite loop.  */
+    SPIN = 0,
+
+    /* Thread should block in a syscall.  */
+    SYSCALL,
+
+    /* This is just a marker to allow for looping over the enum.  */
+    LAST_ACTION
+  };
+
+/* Where should the thread perform this action?  */
+
+enum exec_location
+  {
+    /* Just a normal thread, on a normal stack.  */
+    NORMAL = 0,
+
+    /* In a signal handler, but use the normal stack.  */
+    SIGNAL_HANDLER,
+
+    /* In a signal handler using an alternative stack.  */
+    SIGNAL_ALT_STACK,
+
+    /* This is just a marker to allow for looping over the enum.  */
+    LAST_LOCACTION
+  };
+
+/* A descriptor for a single thread job.  We create a new thread for each
+   job_description.  */
+
+struct job_description
+{
+  /* What action should this thread perform.  */
+  enum final_action action;
+
+  /* Where should the thread perform the action.  */
+  enum exec_location location;
+
+  /* The actual thread handle, so we can join with the thread.  */
+  pthread_t thread;
+};
+
+/* A pthread barrier, used to (try) and synchronise the threads.  */
+pthread_barrier_t global_barrier;
+
+/* Return a list of jobs, and place the length of the list in *COUNT.  */
+
+struct job_description *
+get_job_list (int *count)
+{
+  /* The number of jobs.  */
+  int num = LAST_ACTION * LAST_LOCACTION;
+
+  /* The uninitialised array of jobs.  */
+  struct job_description *list
+    = malloc (num * sizeof (struct job_description));
+  assert (list != NULL);
+
+  /* Fill the array with all possible jobs.  */
+  for (int i = 0; i < (int) LAST_ACTION; ++i)
+    for (int j = 0; j < (int) LAST_LOCACTION; ++j)
+      {
+	int idx = (i * LAST_LOCACTION) + j;
+	list[idx].action = (enum final_action) i;
+	list[idx].location = (enum exec_location) j;
+      }
+
+  /* Return the array of jobs.  */
+  *count = num;
+  return list;
+}
+
+/* This function should never be called.  If it is then an assertion will
+   trigger.  */
+
+void
+assert_not_reached (void)
+{
+  assert (0);
+}
+
+/* The function for a SPIN action.  Just spins in a loop.  The LOCATION
+   argument exists so GDB can identify the expected context for this
+   function.  */
+
+void
+do_spin_task (enum exec_location location)
+{
+  (void) location;
+
+  /* Let everyone know that we're about to perform our action.  */
+  int res = pthread_barrier_wait (&global_barrier);
+  assert (res == PTHREAD_BARRIER_SERIAL_THREAD || res == 0);
+
+  while (1)
+    {
+      /* Nothing.  */
+    }
+}
+
+/* The function for a SYSCALL action.  Just spins in a loop.  The LOCATION
+   argument exists so GDB can identify the expected context for this
+   function.  */
+
+void
+do_syscall_task (enum exec_location location)
+{
+  (void) location;
+
+  /* Let everyone know that we're about to perform our action.  */
+  int res = pthread_barrier_wait (&global_barrier);
+  assert (res == PTHREAD_BARRIER_SERIAL_THREAD || res == 0);
+
+  sleep (600);
+}
+
+/* Return the required size for a sigaltstack.  We start with a single
+   page, but do check against the system defined minimums.  We don't run
+   much on the alternative stacks, so we don't need a huge one.  */
+
+size_t
+get_stack_size (void)
+{
+  size_t size = getpagesize ();	/* Arbitrary starting size.  */
+  if (size < SIGSTKSZ)
+    size = SIGSTKSZ;
+  if (size < MINSIGSTKSZ)
+    size = MINSIGSTKSZ;
+  return size;
+}
+
+/* A descriptor for an alternative stack.  */
+
+struct stack_descriptor
+{
+  /* The base address of the alternative stack.  This is the address that
+     must be freed to release the memory used by this stack.  */
+  void *base;
+
+  /* The size of this alternative stack.  Tracked just so we can query this
+     from GDB.  */
+  size_t size;
+};
+
+/* Install an alternative signal stack.  Return a descriptor for the newly
+   allocated alternative stack.  */
+
+struct stack_descriptor
+setup_alt_stack (void)
+{
+  size_t stack_size = get_stack_size ();
+
+  void *stack_area = malloc (stack_size);
+
+  stack_t stk;
+  stk.ss_sp = stack_area;
+  stk.ss_flags = 0;
+  stk.ss_size = stack_size;
+
+  int res = sigaltstack (&stk, NULL);
+  assert (res == 0);
+
+  struct stack_descriptor desc;
+  desc.base = stack_area;
+  desc.size = stack_size;
+
+  return desc;
+}
+
+/* Return true (non-zero) if we are currently on the alternative stack,
+   otherwise, return false (zero).  */
+
+int
+on_alt_stack_p (void)
+{
+  stack_t stk;
+  int res = sigaltstack (NULL, &stk);
+  assert (res == 0);
+
+  return (stk.ss_flags & SS_ONSTACK) != 0;
+}
+
+/* The signal handler function.  All signals call here, so we use SIGNO
+   (the signal that was delivered) to decide what action to perform.  This
+   function might, or might not, have been called on an alternative signal
+   stack.  */
+
+void
+signal_handler (int signo)
+{
+  enum exec_location location
+    = on_alt_stack_p () ? SIGNAL_ALT_STACK : SIGNAL_HANDLER;
+
+  switch (signo)
+    {
+    case SPIN_SIGNAL:
+      do_spin_task (location);
+      break;
+
+    case SYSCALL_SIGNAL:
+      do_syscall_task (location);
+      break;
+
+    default:
+      assert_not_reached ();
+    }
+}
+
+/* The thread worker function.  ARG is a job_description pointer which
+   describes what this thread is expected to do.  This function always
+   returns a NULL pointer.  */
+
+void *
+thread_function (void *arg)
+{
+  struct job_description *job = (struct job_description *) arg;
+  struct stack_descriptor desc = { NULL, 0 };
+  int sa_flags = 0;
+
+  switch (job->location)
+    {
+    case NORMAL:
+      /* This thread performs the worker action on the current thread,
+	 select the correct worker function based on the requested
+	 action.  */
+      switch (job->action)
+	{
+	case SPIN:
+	  do_spin_task (NORMAL);
+	  break;
+
+	case SYSCALL:
+	  do_syscall_task (NORMAL);
+	  break;
+
+	default:
+	  assert_not_reached ();
+	}
+      break;
+
+    case SIGNAL_ALT_STACK:
+      /* This thread is to perform its action in a signal handler on the
+	 alternative stack.  Install the alternative stack now, and then
+	 fall through to the normal signal handler location code.  */
+      desc = setup_alt_stack ();
+      assert (desc.base != NULL);
+      assert (desc.size > 0);
+      sa_flags = SA_ONSTACK;
+
+      /* Fall through.  */
+    case SIGNAL_HANDLER:
+      {
+	/* This thread is to perform its action in a signal handler.  We
+	   might have just installed an alternative signal stack.  */
+	int signo, res;
+
+	/* Select the correct signal number so that the signal handler will
+	   perform the required action.  */
+	switch (job->action)
+	  {
+	  case SPIN:
+	    signo = SPIN_SIGNAL;
+	    break;
+
+	  case SYSCALL:
+	    signo = SYSCALL_SIGNAL;
+	    break;
+
+	  default:
+	    assert_not_reached ();
+	  }
+
+	/* Now setup the signal handler.  */
+	struct sigaction sa;
+	sa.sa_handler = signal_handler;
+	sigfillset (&sa.sa_mask);
+	sa.sa_flags = sa_flags;
+	res = sigaction (signo, &sa, NULL);
+	assert (res == 0);
+
+	/* Send the signal to this thread.  */
+	res = pthread_kill (job->thread, signo);
+	assert (res == 0);
+      }
+      break;
+
+    default:
+      assert_not_reached ();
+    };
+
+  /* Free the alt-stack if we allocated one, if not DESC.BASE will be
+     NULL so this call is fine.  */
+  free (desc.base);
+
+  /* Thread complete.  */
+  return NULL;
+}
+
+void
+start_job (struct job_description *job)
+{
+  int res;
+
+  res = pthread_create (&job->thread, NULL, thread_function, job);
+  assert (res == 0);
+}
+
+/* Join with the thread for JOB.  This will block until the thread for JOB
+   has finished.  */
+
+void
+finalise_job (struct job_description *job)
+{
+  int res;
+  void *retval;
+
+  res = pthread_join (job->thread, &retval);
+  assert (res == 0);
+  assert (retval == NULL);
+}
+
+/* Function that GDB can place a breakpoint on.  */
+
+void
+breakpt (void)
+{
+  /* Nothing.  */
+}
+
+/* Function that triggers a crash, if the user has setup their environment
+   correctly this will dump a core file, which GDB can then examine.  */
+
+void
+crash_function (void)
+{
+  volatile int *p = 0;
+  volatile int n = *p;
+  (void) n;
+}
+
+/* Entry point.  */
+
+int
+main ()
+{
+  int job_count, res;
+  struct job_description *jobs = get_job_list (&job_count);
+
+  /* This test is going to park some threads inside infinite loops.  Just
+     in case this program is left running, install an alarm that will cause
+     everything to exit.  */
+  alarm (WATCHDOG_ALARM_TIME);
+
+  /* We want each worker thread (of which there are JOB_COUNT) plus the
+     main thread (hence + 1) to wait at the barrier.  */
+  res = pthread_barrier_init (&global_barrier, NULL, job_count + 1);
+  assert (res == 0);
+
+  /* Start all the jobs.  */
+  for (int i = 0; i < job_count; ++i)
+    start_job (&jobs[i]);
+
+  /* Notify all the worker threads that we're waiting for them.  */
+  res = pthread_barrier_wait (&global_barrier);
+  assert (res == PTHREAD_BARRIER_SERIAL_THREAD || res == 0);
+
+  /* All we know at this point is that all the worker threads have reached
+     the barrier, which is just before they perform their action.  But we
+     really want them to start their action.
+
+     There's really no way we can be 100% certain that the worker threads
+     have started their action, all we can do is wait for a short while and
+     hope that the machine we're running on is not too slow.  */
+  sleep (MAIN_THREAD_DELAY);
+
+  /* A function that GDB can place a breakpoint on.  By the time we get
+     here we are as sure as we can be that all of the worker threads have
+     started and are in their worker action (spinning, or syscall).  */
+  breakpt ();
+
+  /* If GDB is not attached then this function will cause a crash, which
+     can be used to dump a core file, which GDB can then analyse.  */
+  crash_function ();
+
+  /* Due to the crash we never expect to get here.  Plus the worker actions
+     never terminate.  But for completeness, here's where we join with all
+     the worker threads.  */
+  for (int i = 0; i < job_count; ++i)
+    finalise_job (&jobs[i]);
+
+  /* Cleanup the barrier.  */
+  res = pthread_barrier_destroy (&global_barrier);
+  assert (res == 0);
+
+  /* And clean up the jobs list.  */
+  free (jobs);
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/threadcrash.exp b/gdb/testsuite/gdb.threads/threadcrash.exp
new file mode 100644
index 00000000000..ee81b5c32f2
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/threadcrash.exp
@@ -0,0 +1,209 @@
+# This testcase is part of GDB, the GNU debugger.
+
+# Copyright 2023 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/>.
+
+# This test case looks at GDB's ability to get correct backtraces for a
+# crashed inferior, recreating it from a live inferior, a corefile and
+# a gcore.
+
+
+# First check that we have 7 threads.
+
+proc test_thread_count {} {
+    set thread_count 0
+
+    gdb_test_multiple "info threads" "getting thread count" -lbl {
+	-re "Thread" {
+	    incr thread_count
+	    exp_continue
+	}
+	-re "$::gdb_prompt " {
+	    gdb_assert {$thread_count == 7}
+	}
+    }
+
+    return $thread_count
+}
+
+# Apply all to quickly check if all expected states are
+# present.  Then, save the full desired backtrace in a list
+# so we can check full backtraces later.
+
+proc thread_apply_all {} {
+    global test_list
+
+    set unwind_fail false
+
+    gdb_test_multiple "thread apply all backtrace" \
+	"Get thread information" -lbl {
+	    -re "#\[0-9\]+\\\?\\\?\[^\n\]*" {
+		set unwind_fail true
+		exp_continue
+	    }
+	    -re "\[^\n\]*syscall_task .location=SIGNAL_ALT_STACK\[^\n\]*" {
+		set test_list [linsert $test_list end [multi_line ".*sleep.*" \
+					      ".*do_syscall_task .location=SIGNAL_ALT_STACK.*" \
+					      ".*signal_handler.*" \
+					      ".*signal handler called.*" \
+					      ".*pthread_kill.*" \
+					      ".*thread_function.*"]]
+		exp_continue
+	    }
+	    -re "\[^\n\]*syscall_task .location=SIGNAL_HANDLER\[^\n\]*" {
+		set test_list [linsert $test_list end [multi_line ".*sleep.*" \
+					      ".*do_syscall_task .location=SIGNAL_HANDLER.*" \
+					      ".*signal_handler.*" \
+					      ".*signal handler called.*" \
+					      ".*pthread_kill.*" \
+					      ".*thread_function.*"]]
+		exp_continue
+	    }
+	    -re "\[^\n\]*syscall_task .location=NORMAL\[^\n\]*" {
+		set test_list [linsert $test_list end [multi_line ".*sleep.*" \
+					      ".*do_syscall_task .location=NORMAL.*" \
+					      ".*thread_function.*"]]
+		exp_continue
+	    }
+	    -re "\[^\n\]*spin_task .location=SIGNAL_ALT_STACK\[^\n\]*" {
+		set test_list [linsert $test_list end [multi_line ".*do_spin_task .location=SIGNAL_ALT_STACK.*" \
+					      ".*signal_handler.*" \
+					      ".*signal handler called.*" \
+					      ".*pthread_kill.*" \
+					      ".*thread_function.*"]]
+		exp_continue
+	    }
+	    -re "\[^\n\]*spin_task .location=SIGNAL_HANDLER\[^\n\]*" {
+		set test_list [linsert $test_list end [multi_line ".*do_spin_task .location=SIGNAL_HANDLER.*" \
+					      ".*signal_handler.*" \
+					      ".*signal handler called.*" \
+					      ".*pthread_kill.*" \
+					      ".*thread_function.*"]]
+		exp_continue
+	    }
+	    -re "\[^\n\]*spin_task .location=NORMAL\[^\n\]*" {
+		set test_list [linsert $test_list end [multi_line ".*do_spin_task .location=NORMAL..*" \
+					      ".*thread_function.*"]]
+		exp_continue
+	    }
+	    -re "\[^\n\]*main\[^\n\]*" {
+		set test_list [linsert $test_list end ".*main.*"]
+		exp_continue
+	    }
+	    -re "$::gdb_prompt " {
+		pass $gdb_test_name
+	    }
+    }
+
+    gdb_assert {$unwind_fail == false}
+}
+
+proc do_full_test {} {
+    global test_list
+    set thread_count [test_thread_count]
+
+    thread_apply_all
+
+    gdb_assert {$thread_count == [llength $test_list]}
+
+    for {set i 0} {$i < $thread_count } {incr i} {
+	set thread_num [expr [llength $test_list] - $i]
+
+	gdb_test "thread apply $thread_num backtrace" [lindex $test_list $i]
+    }
+}
+
+proc_with_prefix test_corefile {} {
+    set corefile [core_find $::binfile]
+    if { $corefile == "" } {
+	untested "couldn't generate corefile"
+	return
+    }
+    set corefile [gdb_remote_download host $corefile]
+
+    gdb_test "core-file $corefile" \
+	     "" \
+	     "loading_corefile" \
+	     "A program is being debugged already\\\.  Kill it\\\? \\\(y or n\\\) " \
+	     "y"
+
+    do_full_test
+}
+
+proc_with_prefix test_gcore {} {
+
+    clean_restart "$::binfile"
+
+    gdb_test "handle SIGUSR1 nostop print pass" \
+	".*SIGUSR1.*No.*Yes.*Yes.*User defined signal 1" \
+	"setup SIGUSR1"
+    gdb_test "handle SIGUSR2 nostop print pass" \
+	".*SIGUSR2.*No.*Yes.*Yes.*User defined signal 2" \
+	"setup SIGUSR2"
+
+    gdb_test "run" ".*Segmentation fault.*" "continue to crash"
+
+    set gcore_name "${::binfile}.gcore"
+    set gcore_supported [gdb_gcore_cmd "$gcore_name" "saving gcore"]
+
+    if {!$gcore_supported} {
+	unsupported "couldn't generate gcore file"
+	return
+    }
+
+    set corefile [gdb_remote_download host $gcore_name]
+
+    gdb_test "core-file $corefile" \
+	     "" \
+	     "loading_corefile" \
+	     "A program is being debugged already\\\.  Kill it\\\? \\\(y or n\\\) " \
+	     "y"
+
+    do_full_test
+}
+
+standard_testfile
+
+if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } {
+    return -1
+}
+
+clean_restart ${binfile}
+
+gdb_test_no_output "set backtrace limit unlimited"
+
+set test_list { }
+
+with_test_prefix "live inferior" {
+    gdb_test "handle SIGUSR1 nostop print pass" \
+	".*SIGUSR1.*No.*Yes.*Yes.*User defined signal 1" \
+	"setup SIGUSR1"
+    gdb_test "handle SIGUSR2 nostop print pass" \
+	".*SIGUSR2.*No.*Yes.*Yes.*User defined signal 2" \
+	"setup SIGUSR2"
+
+    gdb_breakpoint "breakpt"
+    gdb_test "run" ".*breakpt.*" "run to break function"
+
+    do_full_test
+}
+
+set test_list { }
+
+test_corefile
+
+set test_list { }
+
+test_gcore
-- 
2.41.0


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PING] [PATCH v3] gdb/testsuite: add test for backtracing for threaded inferiors from a corefile
  2023-10-25 11:42 [PATCH v3] gdb/testsuite: add test for backtracing for threaded inferiors from a corefile Guinevere Larsen
@ 2023-11-14 10:51 ` Guinevere Larsen
  2023-11-21 17:24 ` [PING][PATCH " Guinevere Larsen
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 8+ messages in thread
From: Guinevere Larsen @ 2023-11-14 10:51 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Ping,

-- 
Cheers,
Guinevere Larsen
She/Her/Hers

On 25/10/2023 13:42, Guinevere Larsen wrote:
> This patch is based on an out-of-tree patch that fedora has been
> carrying for a while. It tests if GDB is able to properly unwind a
> threaded program in the following situations:
> * regular threads
> * in a signal handler
> * in a signal handler executing on an alternate stack
>
> And the final frame can either be in a syscall or in an infinite loop.
>
> The test works by running the inferior until a crash to generate a
> corefile, or until right before the crash. Then applies a backtrace to
> all threads to see if any frame can't be identified, and the order of
> the threads in GDB. Finally, it goes thread by thread and tries to
> collect a large part of the backtrace, to confirm that everything is
> being unwound correctly.
>
> Co-Authored-By: Andrew Burgess <aburgess@redhat.com>
> ---
>
> Changes for v3:
> * Resolved Lancelot's comment
> * undid early exit in favor of more readable gdb_test usage to load the
>    corefile
>
> Changes for v2:
> * Linaro CI identified an issue with the test, which made the test fail
>    when using read1. Fixed here
> * Also added early exit on corefile tests, if the corefile isn't
>    properly loaded
>
> ---
>   gdb/testsuite/gdb.threads/threadcrash.c   | 443 ++++++++++++++++++++++
>   gdb/testsuite/gdb.threads/threadcrash.exp | 209 ++++++++++
>   2 files changed, 652 insertions(+)
>   create mode 100644 gdb/testsuite/gdb.threads/threadcrash.c
>   create mode 100644 gdb/testsuite/gdb.threads/threadcrash.exp
>
> diff --git a/gdb/testsuite/gdb.threads/threadcrash.c b/gdb/testsuite/gdb.threads/threadcrash.c
> new file mode 100644
> index 00000000000..e476ae7b07d
> --- /dev/null
> +++ b/gdb/testsuite/gdb.threads/threadcrash.c
> @@ -0,0 +1,443 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> +   Copyright 2023 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>
> +#include <signal.h>
> +#include <unistd.h>
> +
> +/* The delay that the main thread gives once all the worker threads have
> +   reached the barrier before the main thread enters the function on which
> +   GDB will have placed a breakpoint.  */
> +
> +#define MAIN_THREAD_DELAY 2
> +
> +/* The maximum time we allow this test program to run for before an alarm
> +   signal is sent and everything will exit.  */
> +#define WATCHDOG_ALARM_TIME 600
> +
> +/* Aliases for the signals used within this script.  Each signal
> +   corresponds to an action (from the FINAL_ACTION enum) that the signal
> +   handler will perform.  */
> +
> +#define SPIN_SIGNAL SIGUSR1
> +#define SYSCALL_SIGNAL SIGUSR2
> +
> +/* Describe the final action that a thread should perform.  */
> +
> +enum final_action
> +  {
> +    /* Thread should spin in an infinite loop.  */
> +    SPIN = 0,
> +
> +    /* Thread should block in a syscall.  */
> +    SYSCALL,
> +
> +    /* This is just a marker to allow for looping over the enum.  */
> +    LAST_ACTION
> +  };
> +
> +/* Where should the thread perform this action?  */
> +
> +enum exec_location
> +  {
> +    /* Just a normal thread, on a normal stack.  */
> +    NORMAL = 0,
> +
> +    /* In a signal handler, but use the normal stack.  */
> +    SIGNAL_HANDLER,
> +
> +    /* In a signal handler using an alternative stack.  */
> +    SIGNAL_ALT_STACK,
> +
> +    /* This is just a marker to allow for looping over the enum.  */
> +    LAST_LOCACTION
> +  };
> +
> +/* A descriptor for a single thread job.  We create a new thread for each
> +   job_description.  */
> +
> +struct job_description
> +{
> +  /* What action should this thread perform.  */
> +  enum final_action action;
> +
> +  /* Where should the thread perform the action.  */
> +  enum exec_location location;
> +
> +  /* The actual thread handle, so we can join with the thread.  */
> +  pthread_t thread;
> +};
> +
> +/* A pthread barrier, used to (try) and synchronise the threads.  */
> +pthread_barrier_t global_barrier;
> +
> +/* Return a list of jobs, and place the length of the list in *COUNT.  */
> +
> +struct job_description *
> +get_job_list (int *count)
> +{
> +  /* The number of jobs.  */
> +  int num = LAST_ACTION * LAST_LOCACTION;
> +
> +  /* The uninitialised array of jobs.  */
> +  struct job_description *list
> +    = malloc (num * sizeof (struct job_description));
> +  assert (list != NULL);
> +
> +  /* Fill the array with all possible jobs.  */
> +  for (int i = 0; i < (int) LAST_ACTION; ++i)
> +    for (int j = 0; j < (int) LAST_LOCACTION; ++j)
> +      {
> +	int idx = (i * LAST_LOCACTION) + j;
> +	list[idx].action = (enum final_action) i;
> +	list[idx].location = (enum exec_location) j;
> +      }
> +
> +  /* Return the array of jobs.  */
> +  *count = num;
> +  return list;
> +}
> +
> +/* This function should never be called.  If it is then an assertion will
> +   trigger.  */
> +
> +void
> +assert_not_reached (void)
> +{
> +  assert (0);
> +}
> +
> +/* The function for a SPIN action.  Just spins in a loop.  The LOCATION
> +   argument exists so GDB can identify the expected context for this
> +   function.  */
> +
> +void
> +do_spin_task (enum exec_location location)
> +{
> +  (void) location;
> +
> +  /* Let everyone know that we're about to perform our action.  */
> +  int res = pthread_barrier_wait (&global_barrier);
> +  assert (res == PTHREAD_BARRIER_SERIAL_THREAD || res == 0);
> +
> +  while (1)
> +    {
> +      /* Nothing.  */
> +    }
> +}
> +
> +/* The function for a SYSCALL action.  Just spins in a loop.  The LOCATION
> +   argument exists so GDB can identify the expected context for this
> +   function.  */
> +
> +void
> +do_syscall_task (enum exec_location location)
> +{
> +  (void) location;
> +
> +  /* Let everyone know that we're about to perform our action.  */
> +  int res = pthread_barrier_wait (&global_barrier);
> +  assert (res == PTHREAD_BARRIER_SERIAL_THREAD || res == 0);
> +
> +  sleep (600);
> +}
> +
> +/* Return the required size for a sigaltstack.  We start with a single
> +   page, but do check against the system defined minimums.  We don't run
> +   much on the alternative stacks, so we don't need a huge one.  */
> +
> +size_t
> +get_stack_size (void)
> +{
> +  size_t size = getpagesize ();	/* Arbitrary starting size.  */
> +  if (size < SIGSTKSZ)
> +    size = SIGSTKSZ;
> +  if (size < MINSIGSTKSZ)
> +    size = MINSIGSTKSZ;
> +  return size;
> +}
> +
> +/* A descriptor for an alternative stack.  */
> +
> +struct stack_descriptor
> +{
> +  /* The base address of the alternative stack.  This is the address that
> +     must be freed to release the memory used by this stack.  */
> +  void *base;
> +
> +  /* The size of this alternative stack.  Tracked just so we can query this
> +     from GDB.  */
> +  size_t size;
> +};
> +
> +/* Install an alternative signal stack.  Return a descriptor for the newly
> +   allocated alternative stack.  */
> +
> +struct stack_descriptor
> +setup_alt_stack (void)
> +{
> +  size_t stack_size = get_stack_size ();
> +
> +  void *stack_area = malloc (stack_size);
> +
> +  stack_t stk;
> +  stk.ss_sp = stack_area;
> +  stk.ss_flags = 0;
> +  stk.ss_size = stack_size;
> +
> +  int res = sigaltstack (&stk, NULL);
> +  assert (res == 0);
> +
> +  struct stack_descriptor desc;
> +  desc.base = stack_area;
> +  desc.size = stack_size;
> +
> +  return desc;
> +}
> +
> +/* Return true (non-zero) if we are currently on the alternative stack,
> +   otherwise, return false (zero).  */
> +
> +int
> +on_alt_stack_p (void)
> +{
> +  stack_t stk;
> +  int res = sigaltstack (NULL, &stk);
> +  assert (res == 0);
> +
> +  return (stk.ss_flags & SS_ONSTACK) != 0;
> +}
> +
> +/* The signal handler function.  All signals call here, so we use SIGNO
> +   (the signal that was delivered) to decide what action to perform.  This
> +   function might, or might not, have been called on an alternative signal
> +   stack.  */
> +
> +void
> +signal_handler (int signo)
> +{
> +  enum exec_location location
> +    = on_alt_stack_p () ? SIGNAL_ALT_STACK : SIGNAL_HANDLER;
> +
> +  switch (signo)
> +    {
> +    case SPIN_SIGNAL:
> +      do_spin_task (location);
> +      break;
> +
> +    case SYSCALL_SIGNAL:
> +      do_syscall_task (location);
> +      break;
> +
> +    default:
> +      assert_not_reached ();
> +    }
> +}
> +
> +/* The thread worker function.  ARG is a job_description pointer which
> +   describes what this thread is expected to do.  This function always
> +   returns a NULL pointer.  */
> +
> +void *
> +thread_function (void *arg)
> +{
> +  struct job_description *job = (struct job_description *) arg;
> +  struct stack_descriptor desc = { NULL, 0 };
> +  int sa_flags = 0;
> +
> +  switch (job->location)
> +    {
> +    case NORMAL:
> +      /* This thread performs the worker action on the current thread,
> +	 select the correct worker function based on the requested
> +	 action.  */
> +      switch (job->action)
> +	{
> +	case SPIN:
> +	  do_spin_task (NORMAL);
> +	  break;
> +
> +	case SYSCALL:
> +	  do_syscall_task (NORMAL);
> +	  break;
> +
> +	default:
> +	  assert_not_reached ();
> +	}
> +      break;
> +
> +    case SIGNAL_ALT_STACK:
> +      /* This thread is to perform its action in a signal handler on the
> +	 alternative stack.  Install the alternative stack now, and then
> +	 fall through to the normal signal handler location code.  */
> +      desc = setup_alt_stack ();
> +      assert (desc.base != NULL);
> +      assert (desc.size > 0);
> +      sa_flags = SA_ONSTACK;
> +
> +      /* Fall through.  */
> +    case SIGNAL_HANDLER:
> +      {
> +	/* This thread is to perform its action in a signal handler.  We
> +	   might have just installed an alternative signal stack.  */
> +	int signo, res;
> +
> +	/* Select the correct signal number so that the signal handler will
> +	   perform the required action.  */
> +	switch (job->action)
> +	  {
> +	  case SPIN:
> +	    signo = SPIN_SIGNAL;
> +	    break;
> +
> +	  case SYSCALL:
> +	    signo = SYSCALL_SIGNAL;
> +	    break;
> +
> +	  default:
> +	    assert_not_reached ();
> +	  }
> +
> +	/* Now setup the signal handler.  */
> +	struct sigaction sa;
> +	sa.sa_handler = signal_handler;
> +	sigfillset (&sa.sa_mask);
> +	sa.sa_flags = sa_flags;
> +	res = sigaction (signo, &sa, NULL);
> +	assert (res == 0);
> +
> +	/* Send the signal to this thread.  */
> +	res = pthread_kill (job->thread, signo);
> +	assert (res == 0);
> +      }
> +      break;
> +
> +    default:
> +      assert_not_reached ();
> +    };
> +
> +  /* Free the alt-stack if we allocated one, if not DESC.BASE will be
> +     NULL so this call is fine.  */
> +  free (desc.base);
> +
> +  /* Thread complete.  */
> +  return NULL;
> +}
> +
> +void
> +start_job (struct job_description *job)
> +{
> +  int res;
> +
> +  res = pthread_create (&job->thread, NULL, thread_function, job);
> +  assert (res == 0);
> +}
> +
> +/* Join with the thread for JOB.  This will block until the thread for JOB
> +   has finished.  */
> +
> +void
> +finalise_job (struct job_description *job)
> +{
> +  int res;
> +  void *retval;
> +
> +  res = pthread_join (job->thread, &retval);
> +  assert (res == 0);
> +  assert (retval == NULL);
> +}
> +
> +/* Function that GDB can place a breakpoint on.  */
> +
> +void
> +breakpt (void)
> +{
> +  /* Nothing.  */
> +}
> +
> +/* Function that triggers a crash, if the user has setup their environment
> +   correctly this will dump a core file, which GDB can then examine.  */
> +
> +void
> +crash_function (void)
> +{
> +  volatile int *p = 0;
> +  volatile int n = *p;
> +  (void) n;
> +}
> +
> +/* Entry point.  */
> +
> +int
> +main ()
> +{
> +  int job_count, res;
> +  struct job_description *jobs = get_job_list (&job_count);
> +
> +  /* This test is going to park some threads inside infinite loops.  Just
> +     in case this program is left running, install an alarm that will cause
> +     everything to exit.  */
> +  alarm (WATCHDOG_ALARM_TIME);
> +
> +  /* We want each worker thread (of which there are JOB_COUNT) plus the
> +     main thread (hence + 1) to wait at the barrier.  */
> +  res = pthread_barrier_init (&global_barrier, NULL, job_count + 1);
> +  assert (res == 0);
> +
> +  /* Start all the jobs.  */
> +  for (int i = 0; i < job_count; ++i)
> +    start_job (&jobs[i]);
> +
> +  /* Notify all the worker threads that we're waiting for them.  */
> +  res = pthread_barrier_wait (&global_barrier);
> +  assert (res == PTHREAD_BARRIER_SERIAL_THREAD || res == 0);
> +
> +  /* All we know at this point is that all the worker threads have reached
> +     the barrier, which is just before they perform their action.  But we
> +     really want them to start their action.
> +
> +     There's really no way we can be 100% certain that the worker threads
> +     have started their action, all we can do is wait for a short while and
> +     hope that the machine we're running on is not too slow.  */
> +  sleep (MAIN_THREAD_DELAY);
> +
> +  /* A function that GDB can place a breakpoint on.  By the time we get
> +     here we are as sure as we can be that all of the worker threads have
> +     started and are in their worker action (spinning, or syscall).  */
> +  breakpt ();
> +
> +  /* If GDB is not attached then this function will cause a crash, which
> +     can be used to dump a core file, which GDB can then analyse.  */
> +  crash_function ();
> +
> +  /* Due to the crash we never expect to get here.  Plus the worker actions
> +     never terminate.  But for completeness, here's where we join with all
> +     the worker threads.  */
> +  for (int i = 0; i < job_count; ++i)
> +    finalise_job (&jobs[i]);
> +
> +  /* Cleanup the barrier.  */
> +  res = pthread_barrier_destroy (&global_barrier);
> +  assert (res == 0);
> +
> +  /* And clean up the jobs list.  */
> +  free (jobs);
> +
> +  return 0;
> +}
> diff --git a/gdb/testsuite/gdb.threads/threadcrash.exp b/gdb/testsuite/gdb.threads/threadcrash.exp
> new file mode 100644
> index 00000000000..ee81b5c32f2
> --- /dev/null
> +++ b/gdb/testsuite/gdb.threads/threadcrash.exp
> @@ -0,0 +1,209 @@
> +# This testcase is part of GDB, the GNU debugger.
> +
> +# Copyright 2023 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/>.
> +
> +# This test case looks at GDB's ability to get correct backtraces for a
> +# crashed inferior, recreating it from a live inferior, a corefile and
> +# a gcore.
> +
> +
> +# First check that we have 7 threads.
> +
> +proc test_thread_count {} {
> +    set thread_count 0
> +
> +    gdb_test_multiple "info threads" "getting thread count" -lbl {
> +	-re "Thread" {
> +	    incr thread_count
> +	    exp_continue
> +	}
> +	-re "$::gdb_prompt " {
> +	    gdb_assert {$thread_count == 7}
> +	}
> +    }
> +
> +    return $thread_count
> +}
> +
> +# Apply all to quickly check if all expected states are
> +# present.  Then, save the full desired backtrace in a list
> +# so we can check full backtraces later.
> +
> +proc thread_apply_all {} {
> +    global test_list
> +
> +    set unwind_fail false
> +
> +    gdb_test_multiple "thread apply all backtrace" \
> +	"Get thread information" -lbl {
> +	    -re "#\[0-9\]+\\\?\\\?\[^\n\]*" {
> +		set unwind_fail true
> +		exp_continue
> +	    }
> +	    -re "\[^\n\]*syscall_task .location=SIGNAL_ALT_STACK\[^\n\]*" {
> +		set test_list [linsert $test_list end [multi_line ".*sleep.*" \
> +					      ".*do_syscall_task .location=SIGNAL_ALT_STACK.*" \
> +					      ".*signal_handler.*" \
> +					      ".*signal handler called.*" \
> +					      ".*pthread_kill.*" \
> +					      ".*thread_function.*"]]
> +		exp_continue
> +	    }
> +	    -re "\[^\n\]*syscall_task .location=SIGNAL_HANDLER\[^\n\]*" {
> +		set test_list [linsert $test_list end [multi_line ".*sleep.*" \
> +					      ".*do_syscall_task .location=SIGNAL_HANDLER.*" \
> +					      ".*signal_handler.*" \
> +					      ".*signal handler called.*" \
> +					      ".*pthread_kill.*" \
> +					      ".*thread_function.*"]]
> +		exp_continue
> +	    }
> +	    -re "\[^\n\]*syscall_task .location=NORMAL\[^\n\]*" {
> +		set test_list [linsert $test_list end [multi_line ".*sleep.*" \
> +					      ".*do_syscall_task .location=NORMAL.*" \
> +					      ".*thread_function.*"]]
> +		exp_continue
> +	    }
> +	    -re "\[^\n\]*spin_task .location=SIGNAL_ALT_STACK\[^\n\]*" {
> +		set test_list [linsert $test_list end [multi_line ".*do_spin_task .location=SIGNAL_ALT_STACK.*" \
> +					      ".*signal_handler.*" \
> +					      ".*signal handler called.*" \
> +					      ".*pthread_kill.*" \
> +					      ".*thread_function.*"]]
> +		exp_continue
> +	    }
> +	    -re "\[^\n\]*spin_task .location=SIGNAL_HANDLER\[^\n\]*" {
> +		set test_list [linsert $test_list end [multi_line ".*do_spin_task .location=SIGNAL_HANDLER.*" \
> +					      ".*signal_handler.*" \
> +					      ".*signal handler called.*" \
> +					      ".*pthread_kill.*" \
> +					      ".*thread_function.*"]]
> +		exp_continue
> +	    }
> +	    -re "\[^\n\]*spin_task .location=NORMAL\[^\n\]*" {
> +		set test_list [linsert $test_list end [multi_line ".*do_spin_task .location=NORMAL..*" \
> +					      ".*thread_function.*"]]
> +		exp_continue
> +	    }
> +	    -re "\[^\n\]*main\[^\n\]*" {
> +		set test_list [linsert $test_list end ".*main.*"]
> +		exp_continue
> +	    }
> +	    -re "$::gdb_prompt " {
> +		pass $gdb_test_name
> +	    }
> +    }
> +
> +    gdb_assert {$unwind_fail == false}
> +}
> +
> +proc do_full_test {} {
> +    global test_list
> +    set thread_count [test_thread_count]
> +
> +    thread_apply_all
> +
> +    gdb_assert {$thread_count == [llength $test_list]}
> +
> +    for {set i 0} {$i < $thread_count } {incr i} {
> +	set thread_num [expr [llength $test_list] - $i]
> +
> +	gdb_test "thread apply $thread_num backtrace" [lindex $test_list $i]
> +    }
> +}
> +
> +proc_with_prefix test_corefile {} {
> +    set corefile [core_find $::binfile]
> +    if { $corefile == "" } {
> +	untested "couldn't generate corefile"
> +	return
> +    }
> +    set corefile [gdb_remote_download host $corefile]
> +
> +    gdb_test "core-file $corefile" \
> +	     "" \
> +	     "loading_corefile" \
> +	     "A program is being debugged already\\\.  Kill it\\\? \\\(y or n\\\) " \
> +	     "y"
> +
> +    do_full_test
> +}
> +
> +proc_with_prefix test_gcore {} {
> +
> +    clean_restart "$::binfile"
> +
> +    gdb_test "handle SIGUSR1 nostop print pass" \
> +	".*SIGUSR1.*No.*Yes.*Yes.*User defined signal 1" \
> +	"setup SIGUSR1"
> +    gdb_test "handle SIGUSR2 nostop print pass" \
> +	".*SIGUSR2.*No.*Yes.*Yes.*User defined signal 2" \
> +	"setup SIGUSR2"
> +
> +    gdb_test "run" ".*Segmentation fault.*" "continue to crash"
> +
> +    set gcore_name "${::binfile}.gcore"
> +    set gcore_supported [gdb_gcore_cmd "$gcore_name" "saving gcore"]
> +
> +    if {!$gcore_supported} {
> +	unsupported "couldn't generate gcore file"
> +	return
> +    }
> +
> +    set corefile [gdb_remote_download host $gcore_name]
> +
> +    gdb_test "core-file $corefile" \
> +	     "" \
> +	     "loading_corefile" \
> +	     "A program is being debugged already\\\.  Kill it\\\? \\\(y or n\\\) " \
> +	     "y"
> +
> +    do_full_test
> +}
> +
> +standard_testfile
> +
> +if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } {
> +    return -1
> +}
> +
> +clean_restart ${binfile}
> +
> +gdb_test_no_output "set backtrace limit unlimited"
> +
> +set test_list { }
> +
> +with_test_prefix "live inferior" {
> +    gdb_test "handle SIGUSR1 nostop print pass" \
> +	".*SIGUSR1.*No.*Yes.*Yes.*User defined signal 1" \
> +	"setup SIGUSR1"
> +    gdb_test "handle SIGUSR2 nostop print pass" \
> +	".*SIGUSR2.*No.*Yes.*Yes.*User defined signal 2" \
> +	"setup SIGUSR2"
> +
> +    gdb_breakpoint "breakpt"
> +    gdb_test "run" ".*breakpt.*" "run to break function"
> +
> +    do_full_test
> +}
> +
> +set test_list { }
> +
> +test_corefile
> +
> +set test_list { }
> +
> +test_gcore


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PING][PATCH v3] gdb/testsuite: add test for backtracing for threaded inferiors from a corefile
  2023-10-25 11:42 [PATCH v3] gdb/testsuite: add test for backtracing for threaded inferiors from a corefile Guinevere Larsen
  2023-11-14 10:51 ` [PING] " Guinevere Larsen
@ 2023-11-21 17:24 ` Guinevere Larsen
  2023-11-24 12:26 ` [PATCH " Luis Machado
  2023-11-28 12:07 ` Andrew Burgess
  3 siblings, 0 replies; 8+ messages in thread
From: Guinevere Larsen @ 2023-11-21 17:24 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

ping!

-- 
Cheers,
Guinevere Larsen
She/Her/Hers

On 25/10/2023 13:42, Guinevere Larsen wrote:
> This patch is based on an out-of-tree patch that fedora has been
> carrying for a while. It tests if GDB is able to properly unwind a
> threaded program in the following situations:
> * regular threads
> * in a signal handler
> * in a signal handler executing on an alternate stack
>
> And the final frame can either be in a syscall or in an infinite loop.
>
> The test works by running the inferior until a crash to generate a
> corefile, or until right before the crash. Then applies a backtrace to
> all threads to see if any frame can't be identified, and the order of
> the threads in GDB. Finally, it goes thread by thread and tries to
> collect a large part of the backtrace, to confirm that everything is
> being unwound correctly.
>
> Co-Authored-By: Andrew Burgess <aburgess@redhat.com>
> ---
>
> Changes for v3:
> * Resolved Lancelot's comment
> * undid early exit in favor of more readable gdb_test usage to load the
>    corefile
>
> Changes for v2:
> * Linaro CI identified an issue with the test, which made the test fail
>    when using read1. Fixed here
> * Also added early exit on corefile tests, if the corefile isn't
>    properly loaded
>
> ---
>   gdb/testsuite/gdb.threads/threadcrash.c   | 443 ++++++++++++++++++++++
>   gdb/testsuite/gdb.threads/threadcrash.exp | 209 ++++++++++
>   2 files changed, 652 insertions(+)
>   create mode 100644 gdb/testsuite/gdb.threads/threadcrash.c
>   create mode 100644 gdb/testsuite/gdb.threads/threadcrash.exp
>
> diff --git a/gdb/testsuite/gdb.threads/threadcrash.c b/gdb/testsuite/gdb.threads/threadcrash.c
> new file mode 100644
> index 00000000000..e476ae7b07d
> --- /dev/null
> +++ b/gdb/testsuite/gdb.threads/threadcrash.c
> @@ -0,0 +1,443 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> +   Copyright 2023 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>
> +#include <signal.h>
> +#include <unistd.h>
> +
> +/* The delay that the main thread gives once all the worker threads have
> +   reached the barrier before the main thread enters the function on which
> +   GDB will have placed a breakpoint.  */
> +
> +#define MAIN_THREAD_DELAY 2
> +
> +/* The maximum time we allow this test program to run for before an alarm
> +   signal is sent and everything will exit.  */
> +#define WATCHDOG_ALARM_TIME 600
> +
> +/* Aliases for the signals used within this script.  Each signal
> +   corresponds to an action (from the FINAL_ACTION enum) that the signal
> +   handler will perform.  */
> +
> +#define SPIN_SIGNAL SIGUSR1
> +#define SYSCALL_SIGNAL SIGUSR2
> +
> +/* Describe the final action that a thread should perform.  */
> +
> +enum final_action
> +  {
> +    /* Thread should spin in an infinite loop.  */
> +    SPIN = 0,
> +
> +    /* Thread should block in a syscall.  */
> +    SYSCALL,
> +
> +    /* This is just a marker to allow for looping over the enum.  */
> +    LAST_ACTION
> +  };
> +
> +/* Where should the thread perform this action?  */
> +
> +enum exec_location
> +  {
> +    /* Just a normal thread, on a normal stack.  */
> +    NORMAL = 0,
> +
> +    /* In a signal handler, but use the normal stack.  */
> +    SIGNAL_HANDLER,
> +
> +    /* In a signal handler using an alternative stack.  */
> +    SIGNAL_ALT_STACK,
> +
> +    /* This is just a marker to allow for looping over the enum.  */
> +    LAST_LOCACTION
> +  };
> +
> +/* A descriptor for a single thread job.  We create a new thread for each
> +   job_description.  */
> +
> +struct job_description
> +{
> +  /* What action should this thread perform.  */
> +  enum final_action action;
> +
> +  /* Where should the thread perform the action.  */
> +  enum exec_location location;
> +
> +  /* The actual thread handle, so we can join with the thread.  */
> +  pthread_t thread;
> +};
> +
> +/* A pthread barrier, used to (try) and synchronise the threads.  */
> +pthread_barrier_t global_barrier;
> +
> +/* Return a list of jobs, and place the length of the list in *COUNT.  */
> +
> +struct job_description *
> +get_job_list (int *count)
> +{
> +  /* The number of jobs.  */
> +  int num = LAST_ACTION * LAST_LOCACTION;
> +
> +  /* The uninitialised array of jobs.  */
> +  struct job_description *list
> +    = malloc (num * sizeof (struct job_description));
> +  assert (list != NULL);
> +
> +  /* Fill the array with all possible jobs.  */
> +  for (int i = 0; i < (int) LAST_ACTION; ++i)
> +    for (int j = 0; j < (int) LAST_LOCACTION; ++j)
> +      {
> +	int idx = (i * LAST_LOCACTION) + j;
> +	list[idx].action = (enum final_action) i;
> +	list[idx].location = (enum exec_location) j;
> +      }
> +
> +  /* Return the array of jobs.  */
> +  *count = num;
> +  return list;
> +}
> +
> +/* This function should never be called.  If it is then an assertion will
> +   trigger.  */
> +
> +void
> +assert_not_reached (void)
> +{
> +  assert (0);
> +}
> +
> +/* The function for a SPIN action.  Just spins in a loop.  The LOCATION
> +   argument exists so GDB can identify the expected context for this
> +   function.  */
> +
> +void
> +do_spin_task (enum exec_location location)
> +{
> +  (void) location;
> +
> +  /* Let everyone know that we're about to perform our action.  */
> +  int res = pthread_barrier_wait (&global_barrier);
> +  assert (res == PTHREAD_BARRIER_SERIAL_THREAD || res == 0);
> +
> +  while (1)
> +    {
> +      /* Nothing.  */
> +    }
> +}
> +
> +/* The function for a SYSCALL action.  Just spins in a loop.  The LOCATION
> +   argument exists so GDB can identify the expected context for this
> +   function.  */
> +
> +void
> +do_syscall_task (enum exec_location location)
> +{
> +  (void) location;
> +
> +  /* Let everyone know that we're about to perform our action.  */
> +  int res = pthread_barrier_wait (&global_barrier);
> +  assert (res == PTHREAD_BARRIER_SERIAL_THREAD || res == 0);
> +
> +  sleep (600);
> +}
> +
> +/* Return the required size for a sigaltstack.  We start with a single
> +   page, but do check against the system defined minimums.  We don't run
> +   much on the alternative stacks, so we don't need a huge one.  */
> +
> +size_t
> +get_stack_size (void)
> +{
> +  size_t size = getpagesize ();	/* Arbitrary starting size.  */
> +  if (size < SIGSTKSZ)
> +    size = SIGSTKSZ;
> +  if (size < MINSIGSTKSZ)
> +    size = MINSIGSTKSZ;
> +  return size;
> +}
> +
> +/* A descriptor for an alternative stack.  */
> +
> +struct stack_descriptor
> +{
> +  /* The base address of the alternative stack.  This is the address that
> +     must be freed to release the memory used by this stack.  */
> +  void *base;
> +
> +  /* The size of this alternative stack.  Tracked just so we can query this
> +     from GDB.  */
> +  size_t size;
> +};
> +
> +/* Install an alternative signal stack.  Return a descriptor for the newly
> +   allocated alternative stack.  */
> +
> +struct stack_descriptor
> +setup_alt_stack (void)
> +{
> +  size_t stack_size = get_stack_size ();
> +
> +  void *stack_area = malloc (stack_size);
> +
> +  stack_t stk;
> +  stk.ss_sp = stack_area;
> +  stk.ss_flags = 0;
> +  stk.ss_size = stack_size;
> +
> +  int res = sigaltstack (&stk, NULL);
> +  assert (res == 0);
> +
> +  struct stack_descriptor desc;
> +  desc.base = stack_area;
> +  desc.size = stack_size;
> +
> +  return desc;
> +}
> +
> +/* Return true (non-zero) if we are currently on the alternative stack,
> +   otherwise, return false (zero).  */
> +
> +int
> +on_alt_stack_p (void)
> +{
> +  stack_t stk;
> +  int res = sigaltstack (NULL, &stk);
> +  assert (res == 0);
> +
> +  return (stk.ss_flags & SS_ONSTACK) != 0;
> +}
> +
> +/* The signal handler function.  All signals call here, so we use SIGNO
> +   (the signal that was delivered) to decide what action to perform.  This
> +   function might, or might not, have been called on an alternative signal
> +   stack.  */
> +
> +void
> +signal_handler (int signo)
> +{
> +  enum exec_location location
> +    = on_alt_stack_p () ? SIGNAL_ALT_STACK : SIGNAL_HANDLER;
> +
> +  switch (signo)
> +    {
> +    case SPIN_SIGNAL:
> +      do_spin_task (location);
> +      break;
> +
> +    case SYSCALL_SIGNAL:
> +      do_syscall_task (location);
> +      break;
> +
> +    default:
> +      assert_not_reached ();
> +    }
> +}
> +
> +/* The thread worker function.  ARG is a job_description pointer which
> +   describes what this thread is expected to do.  This function always
> +   returns a NULL pointer.  */
> +
> +void *
> +thread_function (void *arg)
> +{
> +  struct job_description *job = (struct job_description *) arg;
> +  struct stack_descriptor desc = { NULL, 0 };
> +  int sa_flags = 0;
> +
> +  switch (job->location)
> +    {
> +    case NORMAL:
> +      /* This thread performs the worker action on the current thread,
> +	 select the correct worker function based on the requested
> +	 action.  */
> +      switch (job->action)
> +	{
> +	case SPIN:
> +	  do_spin_task (NORMAL);
> +	  break;
> +
> +	case SYSCALL:
> +	  do_syscall_task (NORMAL);
> +	  break;
> +
> +	default:
> +	  assert_not_reached ();
> +	}
> +      break;
> +
> +    case SIGNAL_ALT_STACK:
> +      /* This thread is to perform its action in a signal handler on the
> +	 alternative stack.  Install the alternative stack now, and then
> +	 fall through to the normal signal handler location code.  */
> +      desc = setup_alt_stack ();
> +      assert (desc.base != NULL);
> +      assert (desc.size > 0);
> +      sa_flags = SA_ONSTACK;
> +
> +      /* Fall through.  */
> +    case SIGNAL_HANDLER:
> +      {
> +	/* This thread is to perform its action in a signal handler.  We
> +	   might have just installed an alternative signal stack.  */
> +	int signo, res;
> +
> +	/* Select the correct signal number so that the signal handler will
> +	   perform the required action.  */
> +	switch (job->action)
> +	  {
> +	  case SPIN:
> +	    signo = SPIN_SIGNAL;
> +	    break;
> +
> +	  case SYSCALL:
> +	    signo = SYSCALL_SIGNAL;
> +	    break;
> +
> +	  default:
> +	    assert_not_reached ();
> +	  }
> +
> +	/* Now setup the signal handler.  */
> +	struct sigaction sa;
> +	sa.sa_handler = signal_handler;
> +	sigfillset (&sa.sa_mask);
> +	sa.sa_flags = sa_flags;
> +	res = sigaction (signo, &sa, NULL);
> +	assert (res == 0);
> +
> +	/* Send the signal to this thread.  */
> +	res = pthread_kill (job->thread, signo);
> +	assert (res == 0);
> +      }
> +      break;
> +
> +    default:
> +      assert_not_reached ();
> +    };
> +
> +  /* Free the alt-stack if we allocated one, if not DESC.BASE will be
> +     NULL so this call is fine.  */
> +  free (desc.base);
> +
> +  /* Thread complete.  */
> +  return NULL;
> +}
> +
> +void
> +start_job (struct job_description *job)
> +{
> +  int res;
> +
> +  res = pthread_create (&job->thread, NULL, thread_function, job);
> +  assert (res == 0);
> +}
> +
> +/* Join with the thread for JOB.  This will block until the thread for JOB
> +   has finished.  */
> +
> +void
> +finalise_job (struct job_description *job)
> +{
> +  int res;
> +  void *retval;
> +
> +  res = pthread_join (job->thread, &retval);
> +  assert (res == 0);
> +  assert (retval == NULL);
> +}
> +
> +/* Function that GDB can place a breakpoint on.  */
> +
> +void
> +breakpt (void)
> +{
> +  /* Nothing.  */
> +}
> +
> +/* Function that triggers a crash, if the user has setup their environment
> +   correctly this will dump a core file, which GDB can then examine.  */
> +
> +void
> +crash_function (void)
> +{
> +  volatile int *p = 0;
> +  volatile int n = *p;
> +  (void) n;
> +}
> +
> +/* Entry point.  */
> +
> +int
> +main ()
> +{
> +  int job_count, res;
> +  struct job_description *jobs = get_job_list (&job_count);
> +
> +  /* This test is going to park some threads inside infinite loops.  Just
> +     in case this program is left running, install an alarm that will cause
> +     everything to exit.  */
> +  alarm (WATCHDOG_ALARM_TIME);
> +
> +  /* We want each worker thread (of which there are JOB_COUNT) plus the
> +     main thread (hence + 1) to wait at the barrier.  */
> +  res = pthread_barrier_init (&global_barrier, NULL, job_count + 1);
> +  assert (res == 0);
> +
> +  /* Start all the jobs.  */
> +  for (int i = 0; i < job_count; ++i)
> +    start_job (&jobs[i]);
> +
> +  /* Notify all the worker threads that we're waiting for them.  */
> +  res = pthread_barrier_wait (&global_barrier);
> +  assert (res == PTHREAD_BARRIER_SERIAL_THREAD || res == 0);
> +
> +  /* All we know at this point is that all the worker threads have reached
> +     the barrier, which is just before they perform their action.  But we
> +     really want them to start their action.
> +
> +     There's really no way we can be 100% certain that the worker threads
> +     have started their action, all we can do is wait for a short while and
> +     hope that the machine we're running on is not too slow.  */
> +  sleep (MAIN_THREAD_DELAY);
> +
> +  /* A function that GDB can place a breakpoint on.  By the time we get
> +     here we are as sure as we can be that all of the worker threads have
> +     started and are in their worker action (spinning, or syscall).  */
> +  breakpt ();
> +
> +  /* If GDB is not attached then this function will cause a crash, which
> +     can be used to dump a core file, which GDB can then analyse.  */
> +  crash_function ();
> +
> +  /* Due to the crash we never expect to get here.  Plus the worker actions
> +     never terminate.  But for completeness, here's where we join with all
> +     the worker threads.  */
> +  for (int i = 0; i < job_count; ++i)
> +    finalise_job (&jobs[i]);
> +
> +  /* Cleanup the barrier.  */
> +  res = pthread_barrier_destroy (&global_barrier);
> +  assert (res == 0);
> +
> +  /* And clean up the jobs list.  */
> +  free (jobs);
> +
> +  return 0;
> +}
> diff --git a/gdb/testsuite/gdb.threads/threadcrash.exp b/gdb/testsuite/gdb.threads/threadcrash.exp
> new file mode 100644
> index 00000000000..ee81b5c32f2
> --- /dev/null
> +++ b/gdb/testsuite/gdb.threads/threadcrash.exp
> @@ -0,0 +1,209 @@
> +# This testcase is part of GDB, the GNU debugger.
> +
> +# Copyright 2023 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/>.
> +
> +# This test case looks at GDB's ability to get correct backtraces for a
> +# crashed inferior, recreating it from a live inferior, a corefile and
> +# a gcore.
> +
> +
> +# First check that we have 7 threads.
> +
> +proc test_thread_count {} {
> +    set thread_count 0
> +
> +    gdb_test_multiple "info threads" "getting thread count" -lbl {
> +	-re "Thread" {
> +	    incr thread_count
> +	    exp_continue
> +	}
> +	-re "$::gdb_prompt " {
> +	    gdb_assert {$thread_count == 7}
> +	}
> +    }
> +
> +    return $thread_count
> +}
> +
> +# Apply all to quickly check if all expected states are
> +# present.  Then, save the full desired backtrace in a list
> +# so we can check full backtraces later.
> +
> +proc thread_apply_all {} {
> +    global test_list
> +
> +    set unwind_fail false
> +
> +    gdb_test_multiple "thread apply all backtrace" \
> +	"Get thread information" -lbl {
> +	    -re "#\[0-9\]+\\\?\\\?\[^\n\]*" {
> +		set unwind_fail true
> +		exp_continue
> +	    }
> +	    -re "\[^\n\]*syscall_task .location=SIGNAL_ALT_STACK\[^\n\]*" {
> +		set test_list [linsert $test_list end [multi_line ".*sleep.*" \
> +					      ".*do_syscall_task .location=SIGNAL_ALT_STACK.*" \
> +					      ".*signal_handler.*" \
> +					      ".*signal handler called.*" \
> +					      ".*pthread_kill.*" \
> +					      ".*thread_function.*"]]
> +		exp_continue
> +	    }
> +	    -re "\[^\n\]*syscall_task .location=SIGNAL_HANDLER\[^\n\]*" {
> +		set test_list [linsert $test_list end [multi_line ".*sleep.*" \
> +					      ".*do_syscall_task .location=SIGNAL_HANDLER.*" \
> +					      ".*signal_handler.*" \
> +					      ".*signal handler called.*" \
> +					      ".*pthread_kill.*" \
> +					      ".*thread_function.*"]]
> +		exp_continue
> +	    }
> +	    -re "\[^\n\]*syscall_task .location=NORMAL\[^\n\]*" {
> +		set test_list [linsert $test_list end [multi_line ".*sleep.*" \
> +					      ".*do_syscall_task .location=NORMAL.*" \
> +					      ".*thread_function.*"]]
> +		exp_continue
> +	    }
> +	    -re "\[^\n\]*spin_task .location=SIGNAL_ALT_STACK\[^\n\]*" {
> +		set test_list [linsert $test_list end [multi_line ".*do_spin_task .location=SIGNAL_ALT_STACK.*" \
> +					      ".*signal_handler.*" \
> +					      ".*signal handler called.*" \
> +					      ".*pthread_kill.*" \
> +					      ".*thread_function.*"]]
> +		exp_continue
> +	    }
> +	    -re "\[^\n\]*spin_task .location=SIGNAL_HANDLER\[^\n\]*" {
> +		set test_list [linsert $test_list end [multi_line ".*do_spin_task .location=SIGNAL_HANDLER.*" \
> +					      ".*signal_handler.*" \
> +					      ".*signal handler called.*" \
> +					      ".*pthread_kill.*" \
> +					      ".*thread_function.*"]]
> +		exp_continue
> +	    }
> +	    -re "\[^\n\]*spin_task .location=NORMAL\[^\n\]*" {
> +		set test_list [linsert $test_list end [multi_line ".*do_spin_task .location=NORMAL..*" \
> +					      ".*thread_function.*"]]
> +		exp_continue
> +	    }
> +	    -re "\[^\n\]*main\[^\n\]*" {
> +		set test_list [linsert $test_list end ".*main.*"]
> +		exp_continue
> +	    }
> +	    -re "$::gdb_prompt " {
> +		pass $gdb_test_name
> +	    }
> +    }
> +
> +    gdb_assert {$unwind_fail == false}
> +}
> +
> +proc do_full_test {} {
> +    global test_list
> +    set thread_count [test_thread_count]
> +
> +    thread_apply_all
> +
> +    gdb_assert {$thread_count == [llength $test_list]}
> +
> +    for {set i 0} {$i < $thread_count } {incr i} {
> +	set thread_num [expr [llength $test_list] - $i]
> +
> +	gdb_test "thread apply $thread_num backtrace" [lindex $test_list $i]
> +    }
> +}
> +
> +proc_with_prefix test_corefile {} {
> +    set corefile [core_find $::binfile]
> +    if { $corefile == "" } {
> +	untested "couldn't generate corefile"
> +	return
> +    }
> +    set corefile [gdb_remote_download host $corefile]
> +
> +    gdb_test "core-file $corefile" \
> +	     "" \
> +	     "loading_corefile" \
> +	     "A program is being debugged already\\\.  Kill it\\\? \\\(y or n\\\) " \
> +	     "y"
> +
> +    do_full_test
> +}
> +
> +proc_with_prefix test_gcore {} {
> +
> +    clean_restart "$::binfile"
> +
> +    gdb_test "handle SIGUSR1 nostop print pass" \
> +	".*SIGUSR1.*No.*Yes.*Yes.*User defined signal 1" \
> +	"setup SIGUSR1"
> +    gdb_test "handle SIGUSR2 nostop print pass" \
> +	".*SIGUSR2.*No.*Yes.*Yes.*User defined signal 2" \
> +	"setup SIGUSR2"
> +
> +    gdb_test "run" ".*Segmentation fault.*" "continue to crash"
> +
> +    set gcore_name "${::binfile}.gcore"
> +    set gcore_supported [gdb_gcore_cmd "$gcore_name" "saving gcore"]
> +
> +    if {!$gcore_supported} {
> +	unsupported "couldn't generate gcore file"
> +	return
> +    }
> +
> +    set corefile [gdb_remote_download host $gcore_name]
> +
> +    gdb_test "core-file $corefile" \
> +	     "" \
> +	     "loading_corefile" \
> +	     "A program is being debugged already\\\.  Kill it\\\? \\\(y or n\\\) " \
> +	     "y"
> +
> +    do_full_test
> +}
> +
> +standard_testfile
> +
> +if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } {
> +    return -1
> +}
> +
> +clean_restart ${binfile}
> +
> +gdb_test_no_output "set backtrace limit unlimited"
> +
> +set test_list { }
> +
> +with_test_prefix "live inferior" {
> +    gdb_test "handle SIGUSR1 nostop print pass" \
> +	".*SIGUSR1.*No.*Yes.*Yes.*User defined signal 1" \
> +	"setup SIGUSR1"
> +    gdb_test "handle SIGUSR2 nostop print pass" \
> +	".*SIGUSR2.*No.*Yes.*Yes.*User defined signal 2" \
> +	"setup SIGUSR2"
> +
> +    gdb_breakpoint "breakpt"
> +    gdb_test "run" ".*breakpt.*" "run to break function"
> +
> +    do_full_test
> +}
> +
> +set test_list { }
> +
> +test_corefile
> +
> +set test_list { }
> +
> +test_gcore


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v3] gdb/testsuite: add test for backtracing for threaded inferiors from a corefile
  2023-10-25 11:42 [PATCH v3] gdb/testsuite: add test for backtracing for threaded inferiors from a corefile Guinevere Larsen
  2023-11-14 10:51 ` [PING] " Guinevere Larsen
  2023-11-21 17:24 ` [PING][PATCH " Guinevere Larsen
@ 2023-11-24 12:26 ` Luis Machado
  2023-11-24 12:59   ` Luis Machado
  2023-11-28 12:07 ` Andrew Burgess
  3 siblings, 1 reply; 8+ messages in thread
From: Luis Machado @ 2023-11-24 12:26 UTC (permalink / raw)
  To: Guinevere Larsen, gdb-patches; +Cc: Andrew Burgess

On 10/25/23 12:42, Guinevere Larsen wrote:
> This patch is based on an out-of-tree patch that fedora has been
> carrying for a while. It tests if GDB is able to properly unwind a
> threaded program in the following situations:
> * regular threads
> * in a signal handler
> * in a signal handler executing on an alternate stack
> 
> And the final frame can either be in a syscall or in an infinite loop.
> 
> The test works by running the inferior until a crash to generate a
> corefile, or until right before the crash. Then applies a backtrace to
> all threads to see if any frame can't be identified, and the order of
> the threads in GDB. Finally, it goes thread by thread and tries to
> collect a large part of the backtrace, to confirm that everything is
> being unwound correctly.
> 
> Co-Authored-By: Andrew Burgess <aburgess@redhat.com>
> ---
> 
> Changes for v3:
> * Resolved Lancelot's comment
> * undid early exit in favor of more readable gdb_test usage to load the
>   corefile
> 
> Changes for v2:
> * Linaro CI identified an issue with the test, which made the test fail
>   when using read1. Fixed here
> * Also added early exit on corefile tests, if the corefile isn't
>   properly loaded
> 
> ---
>  gdb/testsuite/gdb.threads/threadcrash.c   | 443 ++++++++++++++++++++++
>  gdb/testsuite/gdb.threads/threadcrash.exp | 209 ++++++++++
>  2 files changed, 652 insertions(+)
>  create mode 100644 gdb/testsuite/gdb.threads/threadcrash.c
>  create mode 100644 gdb/testsuite/gdb.threads/threadcrash.exp
> 
> diff --git a/gdb/testsuite/gdb.threads/threadcrash.c b/gdb/testsuite/gdb.threads/threadcrash.c
> new file mode 100644
> index 00000000000..e476ae7b07d
> --- /dev/null
> +++ b/gdb/testsuite/gdb.threads/threadcrash.c
> @@ -0,0 +1,443 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> +   Copyright 2023 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>
> +#include <signal.h>
> +#include <unistd.h>
> +
> +/* The delay that the main thread gives once all the worker threads have
> +   reached the barrier before the main thread enters the function on which
> +   GDB will have placed a breakpoint.  */
> +
> +#define MAIN_THREAD_DELAY 2
> +
> +/* The maximum time we allow this test program to run for before an alarm
> +   signal is sent and everything will exit.  */
> +#define WATCHDOG_ALARM_TIME 600
> +
> +/* Aliases for the signals used within this script.  Each signal
> +   corresponds to an action (from the FINAL_ACTION enum) that the signal
> +   handler will perform.  */
> +
> +#define SPIN_SIGNAL SIGUSR1
> +#define SYSCALL_SIGNAL SIGUSR2
> +
> +/* Describe the final action that a thread should perform.  */
> +
> +enum final_action
> +  {
> +    /* Thread should spin in an infinite loop.  */
> +    SPIN = 0,
> +
> +    /* Thread should block in a syscall.  */
> +    SYSCALL,
> +
> +    /* This is just a marker to allow for looping over the enum.  */
> +    LAST_ACTION
> +  };
> +
> +/* Where should the thread perform this action?  */
> +
> +enum exec_location
> +  {
> +    /* Just a normal thread, on a normal stack.  */
> +    NORMAL = 0,
> +
> +    /* In a signal handler, but use the normal stack.  */
> +    SIGNAL_HANDLER,
> +
> +    /* In a signal handler using an alternative stack.  */
> +    SIGNAL_ALT_STACK,
> +
> +    /* This is just a marker to allow for looping over the enum.  */
> +    LAST_LOCACTION
> +  };
> +
> +/* A descriptor for a single thread job.  We create a new thread for each
> +   job_description.  */
> +
> +struct job_description
> +{
> +  /* What action should this thread perform.  */
> +  enum final_action action;
> +
> +  /* Where should the thread perform the action.  */
> +  enum exec_location location;
> +
> +  /* The actual thread handle, so we can join with the thread.  */
> +  pthread_t thread;
> +};
> +
> +/* A pthread barrier, used to (try) and synchronise the threads.  */
> +pthread_barrier_t global_barrier;
> +
> +/* Return a list of jobs, and place the length of the list in *COUNT.  */
> +
> +struct job_description *
> +get_job_list (int *count)
> +{
> +  /* The number of jobs.  */
> +  int num = LAST_ACTION * LAST_LOCACTION;
> +
> +  /* The uninitialised array of jobs.  */
> +  struct job_description *list
> +    = malloc (num * sizeof (struct job_description));
> +  assert (list != NULL);
> +
> +  /* Fill the array with all possible jobs.  */
> +  for (int i = 0; i < (int) LAST_ACTION; ++i)
> +    for (int j = 0; j < (int) LAST_LOCACTION; ++j)
> +      {
> +	int idx = (i * LAST_LOCACTION) + j;
> +	list[idx].action = (enum final_action) i;
> +	list[idx].location = (enum exec_location) j;
> +      }
> +
> +  /* Return the array of jobs.  */
> +  *count = num;
> +  return list;
> +}
> +
> +/* This function should never be called.  If it is then an assertion will
> +   trigger.  */
> +
> +void
> +assert_not_reached (void)
> +{
> +  assert (0);
> +}
> +
> +/* The function for a SPIN action.  Just spins in a loop.  The LOCATION
> +   argument exists so GDB can identify the expected context for this
> +   function.  */
> +
> +void
> +do_spin_task (enum exec_location location)
> +{
> +  (void) location;
> +
> +  /* Let everyone know that we're about to perform our action.  */
> +  int res = pthread_barrier_wait (&global_barrier);
> +  assert (res == PTHREAD_BARRIER_SERIAL_THREAD || res == 0);
> +
> +  while (1)
> +    {
> +      /* Nothing.  */
> +    }
> +}
> +
> +/* The function for a SYSCALL action.  Just spins in a loop.  The LOCATION
> +   argument exists so GDB can identify the expected context for this
> +   function.  */
> +
> +void
> +do_syscall_task (enum exec_location location)
> +{
> +  (void) location;
> +
> +  /* Let everyone know that we're about to perform our action.  */
> +  int res = pthread_barrier_wait (&global_barrier);
> +  assert (res == PTHREAD_BARRIER_SERIAL_THREAD || res == 0);
> +
> +  sleep (600);
> +}
> +
> +/* Return the required size for a sigaltstack.  We start with a single
> +   page, but do check against the system defined minimums.  We don't run
> +   much on the alternative stacks, so we don't need a huge one.  */
> +
> +size_t
> +get_stack_size (void)
> +{
> +  size_t size = getpagesize ();	/* Arbitrary starting size.  */
> +  if (size < SIGSTKSZ)
> +    size = SIGSTKSZ;
> +  if (size < MINSIGSTKSZ)
> +    size = MINSIGSTKSZ;
> +  return size;
> +}
> +
> +/* A descriptor for an alternative stack.  */
> +
> +struct stack_descriptor
> +{
> +  /* The base address of the alternative stack.  This is the address that
> +     must be freed to release the memory used by this stack.  */
> +  void *base;
> +
> +  /* The size of this alternative stack.  Tracked just so we can query this
> +     from GDB.  */
> +  size_t size;
> +};
> +
> +/* Install an alternative signal stack.  Return a descriptor for the newly
> +   allocated alternative stack.  */
> +
> +struct stack_descriptor
> +setup_alt_stack (void)
> +{
> +  size_t stack_size = get_stack_size ();
> +
> +  void *stack_area = malloc (stack_size);
> +
> +  stack_t stk;
> +  stk.ss_sp = stack_area;
> +  stk.ss_flags = 0;
> +  stk.ss_size = stack_size;
> +
> +  int res = sigaltstack (&stk, NULL);
> +  assert (res == 0);
> +
> +  struct stack_descriptor desc;
> +  desc.base = stack_area;
> +  desc.size = stack_size;
> +
> +  return desc;
> +}
> +
> +/* Return true (non-zero) if we are currently on the alternative stack,
> +   otherwise, return false (zero).  */
> +
> +int
> +on_alt_stack_p (void)
> +{
> +  stack_t stk;
> +  int res = sigaltstack (NULL, &stk);
> +  assert (res == 0);
> +
> +  return (stk.ss_flags & SS_ONSTACK) != 0;
> +}
> +
> +/* The signal handler function.  All signals call here, so we use SIGNO
> +   (the signal that was delivered) to decide what action to perform.  This
> +   function might, or might not, have been called on an alternative signal
> +   stack.  */
> +
> +void
> +signal_handler (int signo)
> +{
> +  enum exec_location location
> +    = on_alt_stack_p () ? SIGNAL_ALT_STACK : SIGNAL_HANDLER;
> +
> +  switch (signo)
> +    {
> +    case SPIN_SIGNAL:
> +      do_spin_task (location);
> +      break;
> +
> +    case SYSCALL_SIGNAL:
> +      do_syscall_task (location);
> +      break;
> +
> +    default:
> +      assert_not_reached ();
> +    }
> +}
> +
> +/* The thread worker function.  ARG is a job_description pointer which
> +   describes what this thread is expected to do.  This function always
> +   returns a NULL pointer.  */
> +
> +void *
> +thread_function (void *arg)
> +{
> +  struct job_description *job = (struct job_description *) arg;
> +  struct stack_descriptor desc = { NULL, 0 };
> +  int sa_flags = 0;
> +
> +  switch (job->location)
> +    {
> +    case NORMAL:
> +      /* This thread performs the worker action on the current thread,
> +	 select the correct worker function based on the requested
> +	 action.  */
> +      switch (job->action)
> +	{
> +	case SPIN:
> +	  do_spin_task (NORMAL);
> +	  break;
> +
> +	case SYSCALL:
> +	  do_syscall_task (NORMAL);
> +	  break;
> +
> +	default:
> +	  assert_not_reached ();
> +	}
> +      break;
> +
> +    case SIGNAL_ALT_STACK:
> +      /* This thread is to perform its action in a signal handler on the
> +	 alternative stack.  Install the alternative stack now, and then
> +	 fall through to the normal signal handler location code.  */
> +      desc = setup_alt_stack ();
> +      assert (desc.base != NULL);
> +      assert (desc.size > 0);
> +      sa_flags = SA_ONSTACK;
> +
> +      /* Fall through.  */
> +    case SIGNAL_HANDLER:
> +      {
> +	/* This thread is to perform its action in a signal handler.  We
> +	   might have just installed an alternative signal stack.  */
> +	int signo, res;
> +
> +	/* Select the correct signal number so that the signal handler will
> +	   perform the required action.  */
> +	switch (job->action)
> +	  {
> +	  case SPIN:
> +	    signo = SPIN_SIGNAL;
> +	    break;
> +
> +	  case SYSCALL:
> +	    signo = SYSCALL_SIGNAL;
> +	    break;
> +
> +	  default:
> +	    assert_not_reached ();
> +	  }
> +
> +	/* Now setup the signal handler.  */
> +	struct sigaction sa;
> +	sa.sa_handler = signal_handler;
> +	sigfillset (&sa.sa_mask);
> +	sa.sa_flags = sa_flags;
> +	res = sigaction (signo, &sa, NULL);
> +	assert (res == 0);
> +
> +	/* Send the signal to this thread.  */
> +	res = pthread_kill (job->thread, signo);
> +	assert (res == 0);
> +      }
> +      break;
> +
> +    default:
> +      assert_not_reached ();
> +    };
> +
> +  /* Free the alt-stack if we allocated one, if not DESC.BASE will be
> +     NULL so this call is fine.  */
> +  free (desc.base);
> +
> +  /* Thread complete.  */
> +  return NULL;
> +}
> +
> +void
> +start_job (struct job_description *job)
> +{
> +  int res;
> +
> +  res = pthread_create (&job->thread, NULL, thread_function, job);
> +  assert (res == 0);
> +}
> +
> +/* Join with the thread for JOB.  This will block until the thread for JOB
> +   has finished.  */
> +
> +void
> +finalise_job (struct job_description *job)
> +{
> +  int res;
> +  void *retval;
> +
> +  res = pthread_join (job->thread, &retval);
> +  assert (res == 0);
> +  assert (retval == NULL);
> +}
> +
> +/* Function that GDB can place a breakpoint on.  */
> +
> +void
> +breakpt (void)
> +{
> +  /* Nothing.  */
> +}
> +
> +/* Function that triggers a crash, if the user has setup their environment
> +   correctly this will dump a core file, which GDB can then examine.  */
> +
> +void
> +crash_function (void)
> +{
> +  volatile int *p = 0;
> +  volatile int n = *p;
> +  (void) n;
> +}
> +
> +/* Entry point.  */
> +
> +int
> +main ()
> +{
> +  int job_count, res;
> +  struct job_description *jobs = get_job_list (&job_count);
> +
> +  /* This test is going to park some threads inside infinite loops.  Just
> +     in case this program is left running, install an alarm that will cause
> +     everything to exit.  */
> +  alarm (WATCHDOG_ALARM_TIME);
> +
> +  /* We want each worker thread (of which there are JOB_COUNT) plus the
> +     main thread (hence + 1) to wait at the barrier.  */
> +  res = pthread_barrier_init (&global_barrier, NULL, job_count + 1);
> +  assert (res == 0);
> +
> +  /* Start all the jobs.  */
> +  for (int i = 0; i < job_count; ++i)
> +    start_job (&jobs[i]);
> +
> +  /* Notify all the worker threads that we're waiting for them.  */
> +  res = pthread_barrier_wait (&global_barrier);
> +  assert (res == PTHREAD_BARRIER_SERIAL_THREAD || res == 0);
> +
> +  /* All we know at this point is that all the worker threads have reached
> +     the barrier, which is just before they perform their action.  But we
> +     really want them to start their action.
> +
> +     There's really no way we can be 100% certain that the worker threads
> +     have started their action, all we can do is wait for a short while and
> +     hope that the machine we're running on is not too slow.  */
> +  sleep (MAIN_THREAD_DELAY);
> +
> +  /* A function that GDB can place a breakpoint on.  By the time we get
> +     here we are as sure as we can be that all of the worker threads have
> +     started and are in their worker action (spinning, or syscall).  */
> +  breakpt ();
> +
> +  /* If GDB is not attached then this function will cause a crash, which
> +     can be used to dump a core file, which GDB can then analyse.  */
> +  crash_function ();
> +
> +  /* Due to the crash we never expect to get here.  Plus the worker actions
> +     never terminate.  But for completeness, here's where we join with all
> +     the worker threads.  */
> +  for (int i = 0; i < job_count; ++i)
> +    finalise_job (&jobs[i]);
> +
> +  /* Cleanup the barrier.  */
> +  res = pthread_barrier_destroy (&global_barrier);
> +  assert (res == 0);
> +
> +  /* And clean up the jobs list.  */
> +  free (jobs);
> +
> +  return 0;
> +}
> diff --git a/gdb/testsuite/gdb.threads/threadcrash.exp b/gdb/testsuite/gdb.threads/threadcrash.exp
> new file mode 100644
> index 00000000000..ee81b5c32f2
> --- /dev/null
> +++ b/gdb/testsuite/gdb.threads/threadcrash.exp
> @@ -0,0 +1,209 @@
> +# This testcase is part of GDB, the GNU debugger.
> +
> +# Copyright 2023 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/>.
> +
> +# This test case looks at GDB's ability to get correct backtraces for a
> +# crashed inferior, recreating it from a live inferior, a corefile and
> +# a gcore.
> +
> +
> +# First check that we have 7 threads.
> +
> +proc test_thread_count {} {
> +    set thread_count 0
> +
> +    gdb_test_multiple "info threads" "getting thread count" -lbl {
> +	-re "Thread" {
> +	    incr thread_count
> +	    exp_continue
> +	}
> +	-re "$::gdb_prompt " {
> +	    gdb_assert {$thread_count == 7}
> +	}
> +    }
> +
> +    return $thread_count
> +}
> +
> +# Apply all to quickly check if all expected states are
> +# present.  Then, save the full desired backtrace in a list
> +# so we can check full backtraces later.
> +
> +proc thread_apply_all {} {
> +    global test_list
> +
> +    set unwind_fail false
> +
> +    gdb_test_multiple "thread apply all backtrace" \
> +	"Get thread information" -lbl {
> +	    -re "#\[0-9\]+\\\?\\\?\[^\n\]*" {
> +		set unwind_fail true
> +		exp_continue
> +	    }
> +	    -re "\[^\n\]*syscall_task .location=SIGNAL_ALT_STACK\[^\n\]*" {
> +		set test_list [linsert $test_list end [multi_line ".*sleep.*" \
> +					      ".*do_syscall_task .location=SIGNAL_ALT_STACK.*" \
> +					      ".*signal_handler.*" \
> +					      ".*signal handler called.*" \
> +					      ".*pthread_kill.*" \
> +					      ".*thread_function.*"]]
> +		exp_continue
> +	    }
> +	    -re "\[^\n\]*syscall_task .location=SIGNAL_HANDLER\[^\n\]*" {
> +		set test_list [linsert $test_list end [multi_line ".*sleep.*" \
> +					      ".*do_syscall_task .location=SIGNAL_HANDLER.*" \
> +					      ".*signal_handler.*" \
> +					      ".*signal handler called.*" \
> +					      ".*pthread_kill.*" \
> +					      ".*thread_function.*"]]
> +		exp_continue
> +	    }
> +	    -re "\[^\n\]*syscall_task .location=NORMAL\[^\n\]*" {
> +		set test_list [linsert $test_list end [multi_line ".*sleep.*" \
> +					      ".*do_syscall_task .location=NORMAL.*" \
> +					      ".*thread_function.*"]]
> +		exp_continue
> +	    }
> +	    -re "\[^\n\]*spin_task .location=SIGNAL_ALT_STACK\[^\n\]*" {
> +		set test_list [linsert $test_list end [multi_line ".*do_spin_task .location=SIGNAL_ALT_STACK.*" \
> +					      ".*signal_handler.*" \
> +					      ".*signal handler called.*" \
> +					      ".*pthread_kill.*" \
> +					      ".*thread_function.*"]]
> +		exp_continue
> +	    }
> +	    -re "\[^\n\]*spin_task .location=SIGNAL_HANDLER\[^\n\]*" {
> +		set test_list [linsert $test_list end [multi_line ".*do_spin_task .location=SIGNAL_HANDLER.*" \
> +					      ".*signal_handler.*" \
> +					      ".*signal handler called.*" \
> +					      ".*pthread_kill.*" \
> +					      ".*thread_function.*"]]
> +		exp_continue
> +	    }
> +	    -re "\[^\n\]*spin_task .location=NORMAL\[^\n\]*" {
> +		set test_list [linsert $test_list end [multi_line ".*do_spin_task .location=NORMAL..*" \
> +					      ".*thread_function.*"]]
> +		exp_continue
> +	    }
> +	    -re "\[^\n\]*main\[^\n\]*" {
> +		set test_list [linsert $test_list end ".*main.*"]
> +		exp_continue
> +	    }
> +	    -re "$::gdb_prompt " {
> +		pass $gdb_test_name
> +	    }
> +    }
> +
> +    gdb_assert {$unwind_fail == false}
> +}
> +
> +proc do_full_test {} {
> +    global test_list
> +    set thread_count [test_thread_count]
> +
> +    thread_apply_all
> +
> +    gdb_assert {$thread_count == [llength $test_list]}
> +
> +    for {set i 0} {$i < $thread_count } {incr i} {
> +	set thread_num [expr [llength $test_list] - $i]
> +
> +	gdb_test "thread apply $thread_num backtrace" [lindex $test_list $i]
> +    }
> +}
> +
> +proc_with_prefix test_corefile {} {
> +    set corefile [core_find $::binfile]
> +    if { $corefile == "" } {
> +	untested "couldn't generate corefile"
> +	return
> +    }
> +    set corefile [gdb_remote_download host $corefile]
> +
> +    gdb_test "core-file $corefile" \
> +	     "" \
> +	     "loading_corefile" \
> +	     "A program is being debugged already\\\.  Kill it\\\? \\\(y or n\\\) " \
> +	     "y"
> +
> +    do_full_test
> +}
> +
> +proc_with_prefix test_gcore {} {
> +
> +    clean_restart "$::binfile"
> +
> +    gdb_test "handle SIGUSR1 nostop print pass" \
> +	".*SIGUSR1.*No.*Yes.*Yes.*User defined signal 1" \
> +	"setup SIGUSR1"
> +    gdb_test "handle SIGUSR2 nostop print pass" \
> +	".*SIGUSR2.*No.*Yes.*Yes.*User defined signal 2" \
> +	"setup SIGUSR2"
> +
> +    gdb_test "run" ".*Segmentation fault.*" "continue to crash"
> +
> +    set gcore_name "${::binfile}.gcore"
> +    set gcore_supported [gdb_gcore_cmd "$gcore_name" "saving gcore"]
> +
> +    if {!$gcore_supported} {
> +	unsupported "couldn't generate gcore file"
> +	return
> +    }
> +
> +    set corefile [gdb_remote_download host $gcore_name]
> +
> +    gdb_test "core-file $corefile" \
> +	     "" \
> +	     "loading_corefile" \
> +	     "A program is being debugged already\\\.  Kill it\\\? \\\(y or n\\\) " \
> +	     "y"
> +
> +    do_full_test
> +}
> +
> +standard_testfile
> +
> +if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } {
> +    return -1
> +}
> +
> +clean_restart ${binfile}
> +
> +gdb_test_no_output "set backtrace limit unlimited"
> +
> +set test_list { }
> +
> +with_test_prefix "live inferior" {
> +    gdb_test "handle SIGUSR1 nostop print pass" \
> +	".*SIGUSR1.*No.*Yes.*Yes.*User defined signal 1" \
> +	"setup SIGUSR1"
> +    gdb_test "handle SIGUSR2 nostop print pass" \
> +	".*SIGUSR2.*No.*Yes.*Yes.*User defined signal 2" \
> +	"setup SIGUSR2"
> +
> +    gdb_breakpoint "breakpt"
> +    gdb_test "run" ".*breakpt.*" "run to break function"
> +
> +    do_full_test
> +}
> +
> +set test_list { }
> +
> +test_corefile
> +
> +set test_list { }
> +
> +test_gcore

I gave this a try on aarch64-linux arm-linux and it works as expected.

Unless others have further objections, I think this looks good.

Reviewed-By:  Luis Machado  <luis.machado@arm.com>

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v3] gdb/testsuite: add test for backtracing for threaded inferiors from a corefile
  2023-11-24 12:26 ` [PATCH " Luis Machado
@ 2023-11-24 12:59   ` Luis Machado
  2023-11-28 11:34     ` Andrew Burgess
  0 siblings, 1 reply; 8+ messages in thread
From: Luis Machado @ 2023-11-24 12:59 UTC (permalink / raw)
  To: Guinevere Larsen, gdb-patches; +Cc: Andrew Burgess

On 11/24/23 12:26, Luis Machado wrote:
> On 10/25/23 12:42, Guinevere Larsen wrote:
>> This patch is based on an out-of-tree patch that fedora has been
>> carrying for a while. It tests if GDB is able to properly unwind a
>> threaded program in the following situations:
>> * regular threads
>> * in a signal handler
>> * in a signal handler executing on an alternate stack
>>
>> And the final frame can either be in a syscall or in an infinite loop.
>>
>> The test works by running the inferior until a crash to generate a
>> corefile, or until right before the crash. Then applies a backtrace to
>> all threads to see if any frame can't be identified, and the order of
>> the threads in GDB. Finally, it goes thread by thread and tries to
>> collect a large part of the backtrace, to confirm that everything is
>> being unwound correctly.
>>
>> Co-Authored-By: Andrew Burgess <aburgess@redhat.com>
>> ---
>>
>> Changes for v3:
>> * Resolved Lancelot's comment
>> * undid early exit in favor of more readable gdb_test usage to load the
>>   corefile
>>
>> Changes for v2:
>> * Linaro CI identified an issue with the test, which made the test fail
>>   when using read1. Fixed here
>> * Also added early exit on corefile tests, if the corefile isn't
>>   properly loaded
>>
>> ---
>>  gdb/testsuite/gdb.threads/threadcrash.c   | 443 ++++++++++++++++++++++
>>  gdb/testsuite/gdb.threads/threadcrash.exp | 209 ++++++++++
>>  2 files changed, 652 insertions(+)
>>  create mode 100644 gdb/testsuite/gdb.threads/threadcrash.c
>>  create mode 100644 gdb/testsuite/gdb.threads/threadcrash.exp
>>
>> diff --git a/gdb/testsuite/gdb.threads/threadcrash.c b/gdb/testsuite/gdb.threads/threadcrash.c
>> new file mode 100644
>> index 00000000000..e476ae7b07d
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.threads/threadcrash.c
>> @@ -0,0 +1,443 @@
>> +/* This testcase is part of GDB, the GNU debugger.
>> +
>> +   Copyright 2023 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>
>> +#include <signal.h>
>> +#include <unistd.h>
>> +
>> +/* The delay that the main thread gives once all the worker threads have
>> +   reached the barrier before the main thread enters the function on which
>> +   GDB will have placed a breakpoint.  */
>> +
>> +#define MAIN_THREAD_DELAY 2
>> +
>> +/* The maximum time we allow this test program to run for before an alarm
>> +   signal is sent and everything will exit.  */
>> +#define WATCHDOG_ALARM_TIME 600
>> +
>> +/* Aliases for the signals used within this script.  Each signal
>> +   corresponds to an action (from the FINAL_ACTION enum) that the signal
>> +   handler will perform.  */
>> +
>> +#define SPIN_SIGNAL SIGUSR1
>> +#define SYSCALL_SIGNAL SIGUSR2
>> +
>> +/* Describe the final action that a thread should perform.  */
>> +
>> +enum final_action
>> +  {
>> +    /* Thread should spin in an infinite loop.  */
>> +    SPIN = 0,
>> +
>> +    /* Thread should block in a syscall.  */
>> +    SYSCALL,
>> +
>> +    /* This is just a marker to allow for looping over the enum.  */
>> +    LAST_ACTION
>> +  };
>> +
>> +/* Where should the thread perform this action?  */
>> +
>> +enum exec_location
>> +  {
>> +    /* Just a normal thread, on a normal stack.  */
>> +    NORMAL = 0,
>> +
>> +    /* In a signal handler, but use the normal stack.  */
>> +    SIGNAL_HANDLER,
>> +
>> +    /* In a signal handler using an alternative stack.  */
>> +    SIGNAL_ALT_STACK,
>> +
>> +    /* This is just a marker to allow for looping over the enum.  */
>> +    LAST_LOCACTION
>> +  };
>> +
>> +/* A descriptor for a single thread job.  We create a new thread for each
>> +   job_description.  */
>> +
>> +struct job_description
>> +{
>> +  /* What action should this thread perform.  */
>> +  enum final_action action;
>> +
>> +  /* Where should the thread perform the action.  */
>> +  enum exec_location location;
>> +
>> +  /* The actual thread handle, so we can join with the thread.  */
>> +  pthread_t thread;
>> +};
>> +
>> +/* A pthread barrier, used to (try) and synchronise the threads.  */
>> +pthread_barrier_t global_barrier;
>> +
>> +/* Return a list of jobs, and place the length of the list in *COUNT.  */
>> +
>> +struct job_description *
>> +get_job_list (int *count)
>> +{
>> +  /* The number of jobs.  */
>> +  int num = LAST_ACTION * LAST_LOCACTION;
>> +
>> +  /* The uninitialised array of jobs.  */
>> +  struct job_description *list
>> +    = malloc (num * sizeof (struct job_description));
>> +  assert (list != NULL);
>> +
>> +  /* Fill the array with all possible jobs.  */
>> +  for (int i = 0; i < (int) LAST_ACTION; ++i)
>> +    for (int j = 0; j < (int) LAST_LOCACTION; ++j)
>> +      {
>> +	int idx = (i * LAST_LOCACTION) + j;
>> +	list[idx].action = (enum final_action) i;
>> +	list[idx].location = (enum exec_location) j;
>> +      }
>> +
>> +  /* Return the array of jobs.  */
>> +  *count = num;
>> +  return list;
>> +}
>> +
>> +/* This function should never be called.  If it is then an assertion will
>> +   trigger.  */
>> +
>> +void
>> +assert_not_reached (void)
>> +{
>> +  assert (0);
>> +}
>> +
>> +/* The function for a SPIN action.  Just spins in a loop.  The LOCATION
>> +   argument exists so GDB can identify the expected context for this
>> +   function.  */
>> +
>> +void
>> +do_spin_task (enum exec_location location)
>> +{
>> +  (void) location;
>> +
>> +  /* Let everyone know that we're about to perform our action.  */
>> +  int res = pthread_barrier_wait (&global_barrier);
>> +  assert (res == PTHREAD_BARRIER_SERIAL_THREAD || res == 0);
>> +
>> +  while (1)
>> +    {
>> +      /* Nothing.  */
>> +    }
>> +}
>> +
>> +/* The function for a SYSCALL action.  Just spins in a loop.  The LOCATION
>> +   argument exists so GDB can identify the expected context for this
>> +   function.  */
>> +
>> +void
>> +do_syscall_task (enum exec_location location)
>> +{
>> +  (void) location;
>> +
>> +  /* Let everyone know that we're about to perform our action.  */
>> +  int res = pthread_barrier_wait (&global_barrier);
>> +  assert (res == PTHREAD_BARRIER_SERIAL_THREAD || res == 0);
>> +
>> +  sleep (600);
>> +}
>> +
>> +/* Return the required size for a sigaltstack.  We start with a single
>> +   page, but do check against the system defined minimums.  We don't run
>> +   much on the alternative stacks, so we don't need a huge one.  */
>> +
>> +size_t
>> +get_stack_size (void)
>> +{
>> +  size_t size = getpagesize ();	/* Arbitrary starting size.  */
>> +  if (size < SIGSTKSZ)
>> +    size = SIGSTKSZ;
>> +  if (size < MINSIGSTKSZ)
>> +    size = MINSIGSTKSZ;
>> +  return size;
>> +}
>> +
>> +/* A descriptor for an alternative stack.  */
>> +
>> +struct stack_descriptor
>> +{
>> +  /* The base address of the alternative stack.  This is the address that
>> +     must be freed to release the memory used by this stack.  */
>> +  void *base;
>> +
>> +  /* The size of this alternative stack.  Tracked just so we can query this
>> +     from GDB.  */
>> +  size_t size;
>> +};
>> +
>> +/* Install an alternative signal stack.  Return a descriptor for the newly
>> +   allocated alternative stack.  */
>> +
>> +struct stack_descriptor
>> +setup_alt_stack (void)
>> +{
>> +  size_t stack_size = get_stack_size ();
>> +
>> +  void *stack_area = malloc (stack_size);
>> +
>> +  stack_t stk;
>> +  stk.ss_sp = stack_area;
>> +  stk.ss_flags = 0;
>> +  stk.ss_size = stack_size;
>> +
>> +  int res = sigaltstack (&stk, NULL);
>> +  assert (res == 0);
>> +
>> +  struct stack_descriptor desc;
>> +  desc.base = stack_area;
>> +  desc.size = stack_size;
>> +
>> +  return desc;
>> +}
>> +
>> +/* Return true (non-zero) if we are currently on the alternative stack,
>> +   otherwise, return false (zero).  */
>> +
>> +int
>> +on_alt_stack_p (void)
>> +{
>> +  stack_t stk;
>> +  int res = sigaltstack (NULL, &stk);
>> +  assert (res == 0);
>> +
>> +  return (stk.ss_flags & SS_ONSTACK) != 0;
>> +}
>> +
>> +/* The signal handler function.  All signals call here, so we use SIGNO
>> +   (the signal that was delivered) to decide what action to perform.  This
>> +   function might, or might not, have been called on an alternative signal
>> +   stack.  */
>> +
>> +void
>> +signal_handler (int signo)
>> +{
>> +  enum exec_location location
>> +    = on_alt_stack_p () ? SIGNAL_ALT_STACK : SIGNAL_HANDLER;
>> +
>> +  switch (signo)
>> +    {
>> +    case SPIN_SIGNAL:
>> +      do_spin_task (location);
>> +      break;
>> +
>> +    case SYSCALL_SIGNAL:
>> +      do_syscall_task (location);
>> +      break;
>> +
>> +    default:
>> +      assert_not_reached ();
>> +    }
>> +}
>> +
>> +/* The thread worker function.  ARG is a job_description pointer which
>> +   describes what this thread is expected to do.  This function always
>> +   returns a NULL pointer.  */
>> +
>> +void *
>> +thread_function (void *arg)
>> +{
>> +  struct job_description *job = (struct job_description *) arg;
>> +  struct stack_descriptor desc = { NULL, 0 };
>> +  int sa_flags = 0;
>> +
>> +  switch (job->location)
>> +    {
>> +    case NORMAL:
>> +      /* This thread performs the worker action on the current thread,
>> +	 select the correct worker function based on the requested
>> +	 action.  */
>> +      switch (job->action)
>> +	{
>> +	case SPIN:
>> +	  do_spin_task (NORMAL);
>> +	  break;
>> +
>> +	case SYSCALL:
>> +	  do_syscall_task (NORMAL);
>> +	  break;
>> +
>> +	default:
>> +	  assert_not_reached ();
>> +	}
>> +      break;
>> +
>> +    case SIGNAL_ALT_STACK:
>> +      /* This thread is to perform its action in a signal handler on the
>> +	 alternative stack.  Install the alternative stack now, and then
>> +	 fall through to the normal signal handler location code.  */
>> +      desc = setup_alt_stack ();
>> +      assert (desc.base != NULL);
>> +      assert (desc.size > 0);
>> +      sa_flags = SA_ONSTACK;
>> +
>> +      /* Fall through.  */
>> +    case SIGNAL_HANDLER:
>> +      {
>> +	/* This thread is to perform its action in a signal handler.  We
>> +	   might have just installed an alternative signal stack.  */
>> +	int signo, res;
>> +
>> +	/* Select the correct signal number so that the signal handler will
>> +	   perform the required action.  */
>> +	switch (job->action)
>> +	  {
>> +	  case SPIN:
>> +	    signo = SPIN_SIGNAL;
>> +	    break;
>> +
>> +	  case SYSCALL:
>> +	    signo = SYSCALL_SIGNAL;
>> +	    break;
>> +
>> +	  default:
>> +	    assert_not_reached ();
>> +	  }
>> +
>> +	/* Now setup the signal handler.  */
>> +	struct sigaction sa;
>> +	sa.sa_handler = signal_handler;
>> +	sigfillset (&sa.sa_mask);
>> +	sa.sa_flags = sa_flags;
>> +	res = sigaction (signo, &sa, NULL);
>> +	assert (res == 0);
>> +
>> +	/* Send the signal to this thread.  */
>> +	res = pthread_kill (job->thread, signo);
>> +	assert (res == 0);
>> +      }
>> +      break;
>> +
>> +    default:
>> +      assert_not_reached ();
>> +    };
>> +
>> +  /* Free the alt-stack if we allocated one, if not DESC.BASE will be
>> +     NULL so this call is fine.  */
>> +  free (desc.base);
>> +
>> +  /* Thread complete.  */
>> +  return NULL;
>> +}
>> +
>> +void
>> +start_job (struct job_description *job)
>> +{
>> +  int res;
>> +
>> +  res = pthread_create (&job->thread, NULL, thread_function, job);
>> +  assert (res == 0);
>> +}
>> +
>> +/* Join with the thread for JOB.  This will block until the thread for JOB
>> +   has finished.  */
>> +
>> +void
>> +finalise_job (struct job_description *job)
>> +{
>> +  int res;
>> +  void *retval;
>> +
>> +  res = pthread_join (job->thread, &retval);
>> +  assert (res == 0);
>> +  assert (retval == NULL);
>> +}
>> +
>> +/* Function that GDB can place a breakpoint on.  */
>> +
>> +void
>> +breakpt (void)
>> +{
>> +  /* Nothing.  */
>> +}
>> +
>> +/* Function that triggers a crash, if the user has setup their environment
>> +   correctly this will dump a core file, which GDB can then examine.  */
>> +
>> +void
>> +crash_function (void)
>> +{
>> +  volatile int *p = 0;
>> +  volatile int n = *p;
>> +  (void) n;
>> +}
>> +
>> +/* Entry point.  */
>> +
>> +int
>> +main ()
>> +{
>> +  int job_count, res;
>> +  struct job_description *jobs = get_job_list (&job_count);
>> +
>> +  /* This test is going to park some threads inside infinite loops.  Just
>> +     in case this program is left running, install an alarm that will cause
>> +     everything to exit.  */
>> +  alarm (WATCHDOG_ALARM_TIME);
>> +
>> +  /* We want each worker thread (of which there are JOB_COUNT) plus the
>> +     main thread (hence + 1) to wait at the barrier.  */
>> +  res = pthread_barrier_init (&global_barrier, NULL, job_count + 1);
>> +  assert (res == 0);
>> +
>> +  /* Start all the jobs.  */
>> +  for (int i = 0; i < job_count; ++i)
>> +    start_job (&jobs[i]);
>> +
>> +  /* Notify all the worker threads that we're waiting for them.  */
>> +  res = pthread_barrier_wait (&global_barrier);
>> +  assert (res == PTHREAD_BARRIER_SERIAL_THREAD || res == 0);
>> +
>> +  /* All we know at this point is that all the worker threads have reached
>> +     the barrier, which is just before they perform their action.  But we
>> +     really want them to start their action.
>> +
>> +     There's really no way we can be 100% certain that the worker threads
>> +     have started their action, all we can do is wait for a short while and
>> +     hope that the machine we're running on is not too slow.  */
>> +  sleep (MAIN_THREAD_DELAY);
>> +
>> +  /* A function that GDB can place a breakpoint on.  By the time we get
>> +     here we are as sure as we can be that all of the worker threads have
>> +     started and are in their worker action (spinning, or syscall).  */
>> +  breakpt ();
>> +
>> +  /* If GDB is not attached then this function will cause a crash, which
>> +     can be used to dump a core file, which GDB can then analyse.  */
>> +  crash_function ();
>> +
>> +  /* Due to the crash we never expect to get here.  Plus the worker actions
>> +     never terminate.  But for completeness, here's where we join with all
>> +     the worker threads.  */
>> +  for (int i = 0; i < job_count; ++i)
>> +    finalise_job (&jobs[i]);
>> +
>> +  /* Cleanup the barrier.  */
>> +  res = pthread_barrier_destroy (&global_barrier);
>> +  assert (res == 0);
>> +
>> +  /* And clean up the jobs list.  */
>> +  free (jobs);
>> +
>> +  return 0;
>> +}
>> diff --git a/gdb/testsuite/gdb.threads/threadcrash.exp b/gdb/testsuite/gdb.threads/threadcrash.exp
>> new file mode 100644
>> index 00000000000..ee81b5c32f2
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.threads/threadcrash.exp
>> @@ -0,0 +1,209 @@
>> +# This testcase is part of GDB, the GNU debugger.
>> +
>> +# Copyright 2023 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/>.
>> +
>> +# This test case looks at GDB's ability to get correct backtraces for a
>> +# crashed inferior, recreating it from a live inferior, a corefile and
>> +# a gcore.
>> +
>> +
>> +# First check that we have 7 threads.
>> +
>> +proc test_thread_count {} {
>> +    set thread_count 0
>> +
>> +    gdb_test_multiple "info threads" "getting thread count" -lbl {
>> +	-re "Thread" {
>> +	    incr thread_count
>> +	    exp_continue
>> +	}
>> +	-re "$::gdb_prompt " {
>> +	    gdb_assert {$thread_count == 7}
>> +	}
>> +    }
>> +
>> +    return $thread_count
>> +}
>> +
>> +# Apply all to quickly check if all expected states are
>> +# present.  Then, save the full desired backtrace in a list
>> +# so we can check full backtraces later.
>> +
>> +proc thread_apply_all {} {
>> +    global test_list
>> +
>> +    set unwind_fail false
>> +
>> +    gdb_test_multiple "thread apply all backtrace" \
>> +	"Get thread information" -lbl {
>> +	    -re "#\[0-9\]+\\\?\\\?\[^\n\]*" {
>> +		set unwind_fail true
>> +		exp_continue
>> +	    }
>> +	    -re "\[^\n\]*syscall_task .location=SIGNAL_ALT_STACK\[^\n\]*" {
>> +		set test_list [linsert $test_list end [multi_line ".*sleep.*" \
>> +					      ".*do_syscall_task .location=SIGNAL_ALT_STACK.*" \
>> +					      ".*signal_handler.*" \
>> +					      ".*signal handler called.*" \
>> +					      ".*pthread_kill.*" \
>> +					      ".*thread_function.*"]]
>> +		exp_continue
>> +	    }
>> +	    -re "\[^\n\]*syscall_task .location=SIGNAL_HANDLER\[^\n\]*" {
>> +		set test_list [linsert $test_list end [multi_line ".*sleep.*" \
>> +					      ".*do_syscall_task .location=SIGNAL_HANDLER.*" \
>> +					      ".*signal_handler.*" \
>> +					      ".*signal handler called.*" \
>> +					      ".*pthread_kill.*" \
>> +					      ".*thread_function.*"]]
>> +		exp_continue
>> +	    }
>> +	    -re "\[^\n\]*syscall_task .location=NORMAL\[^\n\]*" {
>> +		set test_list [linsert $test_list end [multi_line ".*sleep.*" \
>> +					      ".*do_syscall_task .location=NORMAL.*" \
>> +					      ".*thread_function.*"]]
>> +		exp_continue
>> +	    }
>> +	    -re "\[^\n\]*spin_task .location=SIGNAL_ALT_STACK\[^\n\]*" {
>> +		set test_list [linsert $test_list end [multi_line ".*do_spin_task .location=SIGNAL_ALT_STACK.*" \
>> +					      ".*signal_handler.*" \
>> +					      ".*signal handler called.*" \
>> +					      ".*pthread_kill.*" \
>> +					      ".*thread_function.*"]]
>> +		exp_continue
>> +	    }
>> +	    -re "\[^\n\]*spin_task .location=SIGNAL_HANDLER\[^\n\]*" {
>> +		set test_list [linsert $test_list end [multi_line ".*do_spin_task .location=SIGNAL_HANDLER.*" \
>> +					      ".*signal_handler.*" \
>> +					      ".*signal handler called.*" \
>> +					      ".*pthread_kill.*" \
>> +					      ".*thread_function.*"]]
>> +		exp_continue
>> +	    }
>> +	    -re "\[^\n\]*spin_task .location=NORMAL\[^\n\]*" {
>> +		set test_list [linsert $test_list end [multi_line ".*do_spin_task .location=NORMAL..*" \
>> +					      ".*thread_function.*"]]
>> +		exp_continue
>> +	    }
>> +	    -re "\[^\n\]*main\[^\n\]*" {
>> +		set test_list [linsert $test_list end ".*main.*"]
>> +		exp_continue
>> +	    }
>> +	    -re "$::gdb_prompt " {
>> +		pass $gdb_test_name
>> +	    }
>> +    }
>> +
>> +    gdb_assert {$unwind_fail == false}
>> +}
>> +
>> +proc do_full_test {} {
>> +    global test_list
>> +    set thread_count [test_thread_count]
>> +
>> +    thread_apply_all
>> +
>> +    gdb_assert {$thread_count == [llength $test_list]}
>> +
>> +    for {set i 0} {$i < $thread_count } {incr i} {
>> +	set thread_num [expr [llength $test_list] - $i]
>> +
>> +	gdb_test "thread apply $thread_num backtrace" [lindex $test_list $i]
>> +    }
>> +}
>> +
>> +proc_with_prefix test_corefile {} {
>> +    set corefile [core_find $::binfile]
>> +    if { $corefile == "" } {
>> +	untested "couldn't generate corefile"
>> +	return
>> +    }
>> +    set corefile [gdb_remote_download host $corefile]
>> +
>> +    gdb_test "core-file $corefile" \
>> +	     "" \
>> +	     "loading_corefile" \
>> +	     "A program is being debugged already\\\.  Kill it\\\? \\\(y or n\\\) " \
>> +	     "y"
>> +
>> +    do_full_test
>> +}
>> +
>> +proc_with_prefix test_gcore {} {
>> +
>> +    clean_restart "$::binfile"
>> +
>> +    gdb_test "handle SIGUSR1 nostop print pass" \
>> +	".*SIGUSR1.*No.*Yes.*Yes.*User defined signal 1" \
>> +	"setup SIGUSR1"
>> +    gdb_test "handle SIGUSR2 nostop print pass" \
>> +	".*SIGUSR2.*No.*Yes.*Yes.*User defined signal 2" \
>> +	"setup SIGUSR2"
>> +
>> +    gdb_test "run" ".*Segmentation fault.*" "continue to crash"
>> +
>> +    set gcore_name "${::binfile}.gcore"
>> +    set gcore_supported [gdb_gcore_cmd "$gcore_name" "saving gcore"]
>> +
>> +    if {!$gcore_supported} {
>> +	unsupported "couldn't generate gcore file"
>> +	return
>> +    }
>> +
>> +    set corefile [gdb_remote_download host $gcore_name]
>> +
>> +    gdb_test "core-file $corefile" \
>> +	     "" \
>> +	     "loading_corefile" \
>> +	     "A program is being debugged already\\\.  Kill it\\\? \\\(y or n\\\) " \
>> +	     "y"
>> +
>> +    do_full_test
>> +}
>> +
>> +standard_testfile
>> +
>> +if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } {
>> +    return -1
>> +}
>> +
>> +clean_restart ${binfile}
>> +
>> +gdb_test_no_output "set backtrace limit unlimited"
>> +
>> +set test_list { }
>> +
>> +with_test_prefix "live inferior" {
>> +    gdb_test "handle SIGUSR1 nostop print pass" \
>> +	".*SIGUSR1.*No.*Yes.*Yes.*User defined signal 1" \
>> +	"setup SIGUSR1"
>> +    gdb_test "handle SIGUSR2 nostop print pass" \
>> +	".*SIGUSR2.*No.*Yes.*Yes.*User defined signal 2" \
>> +	"setup SIGUSR2"
>> +
>> +    gdb_breakpoint "breakpt"
>> +    gdb_test "run" ".*breakpt.*" "run to break function"
>> +
>> +    do_full_test
>> +}
>> +
>> +set test_list { }
>> +
>> +test_corefile
>> +
>> +set test_list { }
>> +
>> +test_gcore
> 
> I gave this a try on aarch64-linux arm-linux and it works as expected.
> 
> Unless others have further objections, I think this looks good.
> 
> Reviewed-By:  Luis Machado  <luis.machado@arm.com>


Just to complement, a small nit about the current patch is that it throws an ERROR when running the
testsuite using the native-gdbserver board.

ERROR: gdbserver does not support run without extended-remote
    while executing
"error "gdbserver does not support $command without extended-remote""

Maybe we should punt when testing against native-gdbserver, or start the program differently.

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v3] gdb/testsuite: add test for backtracing for threaded inferiors from a corefile
  2023-11-24 12:59   ` Luis Machado
@ 2023-11-28 11:34     ` Andrew Burgess
  0 siblings, 0 replies; 8+ messages in thread
From: Andrew Burgess @ 2023-11-28 11:34 UTC (permalink / raw)
  To: Luis Machado, Guinevere Larsen, gdb-patches

Luis Machado <luis.machado@arm.com> writes:

> On 11/24/23 12:26, Luis Machado wrote:
>> On 10/25/23 12:42, Guinevere Larsen wrote:
>>> This patch is based on an out-of-tree patch that fedora has been
>>> carrying for a while. It tests if GDB is able to properly unwind a
>>> threaded program in the following situations:
>>> * regular threads
>>> * in a signal handler
>>> * in a signal handler executing on an alternate stack
>>>
>>> And the final frame can either be in a syscall or in an infinite loop.
>>>
>>> The test works by running the inferior until a crash to generate a
>>> corefile, or until right before the crash. Then applies a backtrace to
>>> all threads to see if any frame can't be identified, and the order of
>>> the threads in GDB. Finally, it goes thread by thread and tries to
>>> collect a large part of the backtrace, to confirm that everything is
>>> being unwound correctly.
>>>
>>> Co-Authored-By: Andrew Burgess <aburgess@redhat.com>
>>> ---
>>>
>>> Changes for v3:
>>> * Resolved Lancelot's comment
>>> * undid early exit in favor of more readable gdb_test usage to load the
>>>   corefile
>>>
>>> Changes for v2:
>>> * Linaro CI identified an issue with the test, which made the test fail
>>>   when using read1. Fixed here
>>> * Also added early exit on corefile tests, if the corefile isn't
>>>   properly loaded
>>>
>>> ---
>>>  gdb/testsuite/gdb.threads/threadcrash.c   | 443 ++++++++++++++++++++++
>>>  gdb/testsuite/gdb.threads/threadcrash.exp | 209 ++++++++++
>>>  2 files changed, 652 insertions(+)
>>>  create mode 100644 gdb/testsuite/gdb.threads/threadcrash.c
>>>  create mode 100644 gdb/testsuite/gdb.threads/threadcrash.exp
>>>
>>> diff --git a/gdb/testsuite/gdb.threads/threadcrash.c b/gdb/testsuite/gdb.threads/threadcrash.c
>>> new file mode 100644
>>> index 00000000000..e476ae7b07d
>>> --- /dev/null
>>> +++ b/gdb/testsuite/gdb.threads/threadcrash.c
>>> @@ -0,0 +1,443 @@
>>> +/* This testcase is part of GDB, the GNU debugger.
>>> +
>>> +   Copyright 2023 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>
>>> +#include <signal.h>
>>> +#include <unistd.h>
>>> +
>>> +/* The delay that the main thread gives once all the worker threads have
>>> +   reached the barrier before the main thread enters the function on which
>>> +   GDB will have placed a breakpoint.  */
>>> +
>>> +#define MAIN_THREAD_DELAY 2
>>> +
>>> +/* The maximum time we allow this test program to run for before an alarm
>>> +   signal is sent and everything will exit.  */
>>> +#define WATCHDOG_ALARM_TIME 600
>>> +
>>> +/* Aliases for the signals used within this script.  Each signal
>>> +   corresponds to an action (from the FINAL_ACTION enum) that the signal
>>> +   handler will perform.  */
>>> +
>>> +#define SPIN_SIGNAL SIGUSR1
>>> +#define SYSCALL_SIGNAL SIGUSR2
>>> +
>>> +/* Describe the final action that a thread should perform.  */
>>> +
>>> +enum final_action
>>> +  {
>>> +    /* Thread should spin in an infinite loop.  */
>>> +    SPIN = 0,
>>> +
>>> +    /* Thread should block in a syscall.  */
>>> +    SYSCALL,
>>> +
>>> +    /* This is just a marker to allow for looping over the enum.  */
>>> +    LAST_ACTION
>>> +  };
>>> +
>>> +/* Where should the thread perform this action?  */
>>> +
>>> +enum exec_location
>>> +  {
>>> +    /* Just a normal thread, on a normal stack.  */
>>> +    NORMAL = 0,
>>> +
>>> +    /* In a signal handler, but use the normal stack.  */
>>> +    SIGNAL_HANDLER,
>>> +
>>> +    /* In a signal handler using an alternative stack.  */
>>> +    SIGNAL_ALT_STACK,
>>> +
>>> +    /* This is just a marker to allow for looping over the enum.  */
>>> +    LAST_LOCACTION
>>> +  };
>>> +
>>> +/* A descriptor for a single thread job.  We create a new thread for each
>>> +   job_description.  */
>>> +
>>> +struct job_description
>>> +{
>>> +  /* What action should this thread perform.  */
>>> +  enum final_action action;
>>> +
>>> +  /* Where should the thread perform the action.  */
>>> +  enum exec_location location;
>>> +
>>> +  /* The actual thread handle, so we can join with the thread.  */
>>> +  pthread_t thread;
>>> +};
>>> +
>>> +/* A pthread barrier, used to (try) and synchronise the threads.  */
>>> +pthread_barrier_t global_barrier;
>>> +
>>> +/* Return a list of jobs, and place the length of the list in *COUNT.  */
>>> +
>>> +struct job_description *
>>> +get_job_list (int *count)
>>> +{
>>> +  /* The number of jobs.  */
>>> +  int num = LAST_ACTION * LAST_LOCACTION;
>>> +
>>> +  /* The uninitialised array of jobs.  */
>>> +  struct job_description *list
>>> +    = malloc (num * sizeof (struct job_description));
>>> +  assert (list != NULL);
>>> +
>>> +  /* Fill the array with all possible jobs.  */
>>> +  for (int i = 0; i < (int) LAST_ACTION; ++i)
>>> +    for (int j = 0; j < (int) LAST_LOCACTION; ++j)
>>> +      {
>>> +	int idx = (i * LAST_LOCACTION) + j;
>>> +	list[idx].action = (enum final_action) i;
>>> +	list[idx].location = (enum exec_location) j;
>>> +      }
>>> +
>>> +  /* Return the array of jobs.  */
>>> +  *count = num;
>>> +  return list;
>>> +}
>>> +
>>> +/* This function should never be called.  If it is then an assertion will
>>> +   trigger.  */
>>> +
>>> +void
>>> +assert_not_reached (void)
>>> +{
>>> +  assert (0);
>>> +}
>>> +
>>> +/* The function for a SPIN action.  Just spins in a loop.  The LOCATION
>>> +   argument exists so GDB can identify the expected context for this
>>> +   function.  */
>>> +
>>> +void
>>> +do_spin_task (enum exec_location location)
>>> +{
>>> +  (void) location;
>>> +
>>> +  /* Let everyone know that we're about to perform our action.  */
>>> +  int res = pthread_barrier_wait (&global_barrier);
>>> +  assert (res == PTHREAD_BARRIER_SERIAL_THREAD || res == 0);
>>> +
>>> +  while (1)
>>> +    {
>>> +      /* Nothing.  */
>>> +    }
>>> +}
>>> +
>>> +/* The function for a SYSCALL action.  Just spins in a loop.  The LOCATION
>>> +   argument exists so GDB can identify the expected context for this
>>> +   function.  */
>>> +
>>> +void
>>> +do_syscall_task (enum exec_location location)
>>> +{
>>> +  (void) location;
>>> +
>>> +  /* Let everyone know that we're about to perform our action.  */
>>> +  int res = pthread_barrier_wait (&global_barrier);
>>> +  assert (res == PTHREAD_BARRIER_SERIAL_THREAD || res == 0);
>>> +
>>> +  sleep (600);
>>> +}
>>> +
>>> +/* Return the required size for a sigaltstack.  We start with a single
>>> +   page, but do check against the system defined minimums.  We don't run
>>> +   much on the alternative stacks, so we don't need a huge one.  */
>>> +
>>> +size_t
>>> +get_stack_size (void)
>>> +{
>>> +  size_t size = getpagesize ();	/* Arbitrary starting size.  */
>>> +  if (size < SIGSTKSZ)
>>> +    size = SIGSTKSZ;
>>> +  if (size < MINSIGSTKSZ)
>>> +    size = MINSIGSTKSZ;
>>> +  return size;
>>> +}
>>> +
>>> +/* A descriptor for an alternative stack.  */
>>> +
>>> +struct stack_descriptor
>>> +{
>>> +  /* The base address of the alternative stack.  This is the address that
>>> +     must be freed to release the memory used by this stack.  */
>>> +  void *base;
>>> +
>>> +  /* The size of this alternative stack.  Tracked just so we can query this
>>> +     from GDB.  */
>>> +  size_t size;
>>> +};
>>> +
>>> +/* Install an alternative signal stack.  Return a descriptor for the newly
>>> +   allocated alternative stack.  */
>>> +
>>> +struct stack_descriptor
>>> +setup_alt_stack (void)
>>> +{
>>> +  size_t stack_size = get_stack_size ();
>>> +
>>> +  void *stack_area = malloc (stack_size);
>>> +
>>> +  stack_t stk;
>>> +  stk.ss_sp = stack_area;
>>> +  stk.ss_flags = 0;
>>> +  stk.ss_size = stack_size;
>>> +
>>> +  int res = sigaltstack (&stk, NULL);
>>> +  assert (res == 0);
>>> +
>>> +  struct stack_descriptor desc;
>>> +  desc.base = stack_area;
>>> +  desc.size = stack_size;
>>> +
>>> +  return desc;
>>> +}
>>> +
>>> +/* Return true (non-zero) if we are currently on the alternative stack,
>>> +   otherwise, return false (zero).  */
>>> +
>>> +int
>>> +on_alt_stack_p (void)
>>> +{
>>> +  stack_t stk;
>>> +  int res = sigaltstack (NULL, &stk);
>>> +  assert (res == 0);
>>> +
>>> +  return (stk.ss_flags & SS_ONSTACK) != 0;
>>> +}
>>> +
>>> +/* The signal handler function.  All signals call here, so we use SIGNO
>>> +   (the signal that was delivered) to decide what action to perform.  This
>>> +   function might, or might not, have been called on an alternative signal
>>> +   stack.  */
>>> +
>>> +void
>>> +signal_handler (int signo)
>>> +{
>>> +  enum exec_location location
>>> +    = on_alt_stack_p () ? SIGNAL_ALT_STACK : SIGNAL_HANDLER;
>>> +
>>> +  switch (signo)
>>> +    {
>>> +    case SPIN_SIGNAL:
>>> +      do_spin_task (location);
>>> +      break;
>>> +
>>> +    case SYSCALL_SIGNAL:
>>> +      do_syscall_task (location);
>>> +      break;
>>> +
>>> +    default:
>>> +      assert_not_reached ();
>>> +    }
>>> +}
>>> +
>>> +/* The thread worker function.  ARG is a job_description pointer which
>>> +   describes what this thread is expected to do.  This function always
>>> +   returns a NULL pointer.  */
>>> +
>>> +void *
>>> +thread_function (void *arg)
>>> +{
>>> +  struct job_description *job = (struct job_description *) arg;
>>> +  struct stack_descriptor desc = { NULL, 0 };
>>> +  int sa_flags = 0;
>>> +
>>> +  switch (job->location)
>>> +    {
>>> +    case NORMAL:
>>> +      /* This thread performs the worker action on the current thread,
>>> +	 select the correct worker function based on the requested
>>> +	 action.  */
>>> +      switch (job->action)
>>> +	{
>>> +	case SPIN:
>>> +	  do_spin_task (NORMAL);
>>> +	  break;
>>> +
>>> +	case SYSCALL:
>>> +	  do_syscall_task (NORMAL);
>>> +	  break;
>>> +
>>> +	default:
>>> +	  assert_not_reached ();
>>> +	}
>>> +      break;
>>> +
>>> +    case SIGNAL_ALT_STACK:
>>> +      /* This thread is to perform its action in a signal handler on the
>>> +	 alternative stack.  Install the alternative stack now, and then
>>> +	 fall through to the normal signal handler location code.  */
>>> +      desc = setup_alt_stack ();
>>> +      assert (desc.base != NULL);
>>> +      assert (desc.size > 0);
>>> +      sa_flags = SA_ONSTACK;
>>> +
>>> +      /* Fall through.  */
>>> +    case SIGNAL_HANDLER:
>>> +      {
>>> +	/* This thread is to perform its action in a signal handler.  We
>>> +	   might have just installed an alternative signal stack.  */
>>> +	int signo, res;
>>> +
>>> +	/* Select the correct signal number so that the signal handler will
>>> +	   perform the required action.  */
>>> +	switch (job->action)
>>> +	  {
>>> +	  case SPIN:
>>> +	    signo = SPIN_SIGNAL;
>>> +	    break;
>>> +
>>> +	  case SYSCALL:
>>> +	    signo = SYSCALL_SIGNAL;
>>> +	    break;
>>> +
>>> +	  default:
>>> +	    assert_not_reached ();
>>> +	  }
>>> +
>>> +	/* Now setup the signal handler.  */
>>> +	struct sigaction sa;
>>> +	sa.sa_handler = signal_handler;
>>> +	sigfillset (&sa.sa_mask);
>>> +	sa.sa_flags = sa_flags;
>>> +	res = sigaction (signo, &sa, NULL);
>>> +	assert (res == 0);
>>> +
>>> +	/* Send the signal to this thread.  */
>>> +	res = pthread_kill (job->thread, signo);
>>> +	assert (res == 0);
>>> +      }
>>> +      break;
>>> +
>>> +    default:
>>> +      assert_not_reached ();
>>> +    };
>>> +
>>> +  /* Free the alt-stack if we allocated one, if not DESC.BASE will be
>>> +     NULL so this call is fine.  */
>>> +  free (desc.base);
>>> +
>>> +  /* Thread complete.  */
>>> +  return NULL;
>>> +}
>>> +
>>> +void
>>> +start_job (struct job_description *job)
>>> +{
>>> +  int res;
>>> +
>>> +  res = pthread_create (&job->thread, NULL, thread_function, job);
>>> +  assert (res == 0);
>>> +}
>>> +
>>> +/* Join with the thread for JOB.  This will block until the thread for JOB
>>> +   has finished.  */
>>> +
>>> +void
>>> +finalise_job (struct job_description *job)
>>> +{
>>> +  int res;
>>> +  void *retval;
>>> +
>>> +  res = pthread_join (job->thread, &retval);
>>> +  assert (res == 0);
>>> +  assert (retval == NULL);
>>> +}
>>> +
>>> +/* Function that GDB can place a breakpoint on.  */
>>> +
>>> +void
>>> +breakpt (void)
>>> +{
>>> +  /* Nothing.  */
>>> +}
>>> +
>>> +/* Function that triggers a crash, if the user has setup their environment
>>> +   correctly this will dump a core file, which GDB can then examine.  */
>>> +
>>> +void
>>> +crash_function (void)
>>> +{
>>> +  volatile int *p = 0;
>>> +  volatile int n = *p;
>>> +  (void) n;
>>> +}
>>> +
>>> +/* Entry point.  */
>>> +
>>> +int
>>> +main ()
>>> +{
>>> +  int job_count, res;
>>> +  struct job_description *jobs = get_job_list (&job_count);
>>> +
>>> +  /* This test is going to park some threads inside infinite loops.  Just
>>> +     in case this program is left running, install an alarm that will cause
>>> +     everything to exit.  */
>>> +  alarm (WATCHDOG_ALARM_TIME);
>>> +
>>> +  /* We want each worker thread (of which there are JOB_COUNT) plus the
>>> +     main thread (hence + 1) to wait at the barrier.  */
>>> +  res = pthread_barrier_init (&global_barrier, NULL, job_count + 1);
>>> +  assert (res == 0);
>>> +
>>> +  /* Start all the jobs.  */
>>> +  for (int i = 0; i < job_count; ++i)
>>> +    start_job (&jobs[i]);
>>> +
>>> +  /* Notify all the worker threads that we're waiting for them.  */
>>> +  res = pthread_barrier_wait (&global_barrier);
>>> +  assert (res == PTHREAD_BARRIER_SERIAL_THREAD || res == 0);
>>> +
>>> +  /* All we know at this point is that all the worker threads have reached
>>> +     the barrier, which is just before they perform their action.  But we
>>> +     really want them to start their action.
>>> +
>>> +     There's really no way we can be 100% certain that the worker threads
>>> +     have started their action, all we can do is wait for a short while and
>>> +     hope that the machine we're running on is not too slow.  */
>>> +  sleep (MAIN_THREAD_DELAY);
>>> +
>>> +  /* A function that GDB can place a breakpoint on.  By the time we get
>>> +     here we are as sure as we can be that all of the worker threads have
>>> +     started and are in their worker action (spinning, or syscall).  */
>>> +  breakpt ();
>>> +
>>> +  /* If GDB is not attached then this function will cause a crash, which
>>> +     can be used to dump a core file, which GDB can then analyse.  */
>>> +  crash_function ();
>>> +
>>> +  /* Due to the crash we never expect to get here.  Plus the worker actions
>>> +     never terminate.  But for completeness, here's where we join with all
>>> +     the worker threads.  */
>>> +  for (int i = 0; i < job_count; ++i)
>>> +    finalise_job (&jobs[i]);
>>> +
>>> +  /* Cleanup the barrier.  */
>>> +  res = pthread_barrier_destroy (&global_barrier);
>>> +  assert (res == 0);
>>> +
>>> +  /* And clean up the jobs list.  */
>>> +  free (jobs);
>>> +
>>> +  return 0;
>>> +}
>>> diff --git a/gdb/testsuite/gdb.threads/threadcrash.exp b/gdb/testsuite/gdb.threads/threadcrash.exp
>>> new file mode 100644
>>> index 00000000000..ee81b5c32f2
>>> --- /dev/null
>>> +++ b/gdb/testsuite/gdb.threads/threadcrash.exp
>>> @@ -0,0 +1,209 @@
>>> +# This testcase is part of GDB, the GNU debugger.
>>> +
>>> +# Copyright 2023 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/>.
>>> +
>>> +# This test case looks at GDB's ability to get correct backtraces for a
>>> +# crashed inferior, recreating it from a live inferior, a corefile and
>>> +# a gcore.
>>> +
>>> +
>>> +# First check that we have 7 threads.
>>> +
>>> +proc test_thread_count {} {
>>> +    set thread_count 0
>>> +
>>> +    gdb_test_multiple "info threads" "getting thread count" -lbl {
>>> +	-re "Thread" {
>>> +	    incr thread_count
>>> +	    exp_continue
>>> +	}
>>> +	-re "$::gdb_prompt " {
>>> +	    gdb_assert {$thread_count == 7}
>>> +	}
>>> +    }
>>> +
>>> +    return $thread_count
>>> +}
>>> +
>>> +# Apply all to quickly check if all expected states are
>>> +# present.  Then, save the full desired backtrace in a list
>>> +# so we can check full backtraces later.
>>> +
>>> +proc thread_apply_all {} {
>>> +    global test_list
>>> +
>>> +    set unwind_fail false
>>> +
>>> +    gdb_test_multiple "thread apply all backtrace" \
>>> +	"Get thread information" -lbl {
>>> +	    -re "#\[0-9\]+\\\?\\\?\[^\n\]*" {
>>> +		set unwind_fail true
>>> +		exp_continue
>>> +	    }
>>> +	    -re "\[^\n\]*syscall_task .location=SIGNAL_ALT_STACK\[^\n\]*" {
>>> +		set test_list [linsert $test_list end [multi_line ".*sleep.*" \
>>> +					      ".*do_syscall_task .location=SIGNAL_ALT_STACK.*" \
>>> +					      ".*signal_handler.*" \
>>> +					      ".*signal handler called.*" \
>>> +					      ".*pthread_kill.*" \
>>> +					      ".*thread_function.*"]]
>>> +		exp_continue
>>> +	    }
>>> +	    -re "\[^\n\]*syscall_task .location=SIGNAL_HANDLER\[^\n\]*" {
>>> +		set test_list [linsert $test_list end [multi_line ".*sleep.*" \
>>> +					      ".*do_syscall_task .location=SIGNAL_HANDLER.*" \
>>> +					      ".*signal_handler.*" \
>>> +					      ".*signal handler called.*" \
>>> +					      ".*pthread_kill.*" \
>>> +					      ".*thread_function.*"]]
>>> +		exp_continue
>>> +	    }
>>> +	    -re "\[^\n\]*syscall_task .location=NORMAL\[^\n\]*" {
>>> +		set test_list [linsert $test_list end [multi_line ".*sleep.*" \
>>> +					      ".*do_syscall_task .location=NORMAL.*" \
>>> +					      ".*thread_function.*"]]
>>> +		exp_continue
>>> +	    }
>>> +	    -re "\[^\n\]*spin_task .location=SIGNAL_ALT_STACK\[^\n\]*" {
>>> +		set test_list [linsert $test_list end [multi_line ".*do_spin_task .location=SIGNAL_ALT_STACK.*" \
>>> +					      ".*signal_handler.*" \
>>> +					      ".*signal handler called.*" \
>>> +					      ".*pthread_kill.*" \
>>> +					      ".*thread_function.*"]]
>>> +		exp_continue
>>> +	    }
>>> +	    -re "\[^\n\]*spin_task .location=SIGNAL_HANDLER\[^\n\]*" {
>>> +		set test_list [linsert $test_list end [multi_line ".*do_spin_task .location=SIGNAL_HANDLER.*" \
>>> +					      ".*signal_handler.*" \
>>> +					      ".*signal handler called.*" \
>>> +					      ".*pthread_kill.*" \
>>> +					      ".*thread_function.*"]]
>>> +		exp_continue
>>> +	    }
>>> +	    -re "\[^\n\]*spin_task .location=NORMAL\[^\n\]*" {
>>> +		set test_list [linsert $test_list end [multi_line ".*do_spin_task .location=NORMAL..*" \
>>> +					      ".*thread_function.*"]]
>>> +		exp_continue
>>> +	    }
>>> +	    -re "\[^\n\]*main\[^\n\]*" {
>>> +		set test_list [linsert $test_list end ".*main.*"]
>>> +		exp_continue
>>> +	    }
>>> +	    -re "$::gdb_prompt " {
>>> +		pass $gdb_test_name
>>> +	    }
>>> +    }
>>> +
>>> +    gdb_assert {$unwind_fail == false}
>>> +}
>>> +
>>> +proc do_full_test {} {
>>> +    global test_list
>>> +    set thread_count [test_thread_count]
>>> +
>>> +    thread_apply_all
>>> +
>>> +    gdb_assert {$thread_count == [llength $test_list]}
>>> +
>>> +    for {set i 0} {$i < $thread_count } {incr i} {
>>> +	set thread_num [expr [llength $test_list] - $i]
>>> +
>>> +	gdb_test "thread apply $thread_num backtrace" [lindex $test_list $i]
>>> +    }
>>> +}
>>> +
>>> +proc_with_prefix test_corefile {} {
>>> +    set corefile [core_find $::binfile]
>>> +    if { $corefile == "" } {
>>> +	untested "couldn't generate corefile"
>>> +	return
>>> +    }
>>> +    set corefile [gdb_remote_download host $corefile]
>>> +
>>> +    gdb_test "core-file $corefile" \
>>> +	     "" \
>>> +	     "loading_corefile" \
>>> +	     "A program is being debugged already\\\.  Kill it\\\? \\\(y or n\\\) " \
>>> +	     "y"
>>> +
>>> +    do_full_test
>>> +}
>>> +
>>> +proc_with_prefix test_gcore {} {
>>> +
>>> +    clean_restart "$::binfile"
>>> +
>>> +    gdb_test "handle SIGUSR1 nostop print pass" \
>>> +	".*SIGUSR1.*No.*Yes.*Yes.*User defined signal 1" \
>>> +	"setup SIGUSR1"
>>> +    gdb_test "handle SIGUSR2 nostop print pass" \
>>> +	".*SIGUSR2.*No.*Yes.*Yes.*User defined signal 2" \
>>> +	"setup SIGUSR2"
>>> +
>>> +    gdb_test "run" ".*Segmentation fault.*" "continue to crash"
>>> +
>>> +    set gcore_name "${::binfile}.gcore"
>>> +    set gcore_supported [gdb_gcore_cmd "$gcore_name" "saving gcore"]
>>> +
>>> +    if {!$gcore_supported} {
>>> +	unsupported "couldn't generate gcore file"
>>> +	return
>>> +    }
>>> +
>>> +    set corefile [gdb_remote_download host $gcore_name]
>>> +
>>> +    gdb_test "core-file $corefile" \
>>> +	     "" \
>>> +	     "loading_corefile" \
>>> +	     "A program is being debugged already\\\.  Kill it\\\? \\\(y or n\\\) " \
>>> +	     "y"
>>> +
>>> +    do_full_test
>>> +}
>>> +
>>> +standard_testfile
>>> +
>>> +if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } {
>>> +    return -1
>>> +}
>>> +
>>> +clean_restart ${binfile}
>>> +
>>> +gdb_test_no_output "set backtrace limit unlimited"
>>> +
>>> +set test_list { }
>>> +
>>> +with_test_prefix "live inferior" {
>>> +    gdb_test "handle SIGUSR1 nostop print pass" \
>>> +	".*SIGUSR1.*No.*Yes.*Yes.*User defined signal 1" \
>>> +	"setup SIGUSR1"
>>> +    gdb_test "handle SIGUSR2 nostop print pass" \
>>> +	".*SIGUSR2.*No.*Yes.*Yes.*User defined signal 2" \
>>> +	"setup SIGUSR2"
>>> +
>>> +    gdb_breakpoint "breakpt"
>>> +    gdb_test "run" ".*breakpt.*" "run to break function"
>>> +
>>> +    do_full_test
>>> +}
>>> +
>>> +set test_list { }
>>> +
>>> +test_corefile
>>> +
>>> +set test_list { }
>>> +
>>> +test_gcore
>> 
>> I gave this a try on aarch64-linux arm-linux and it works as expected.
>> 
>> Unless others have further objections, I think this looks good.
>> 
>> Reviewed-By:  Luis Machado  <luis.machado@arm.com>
>
>
> Just to complement, a small nit about the current patch is that it throws an ERROR when running the
> testsuite using the native-gdbserver board.
>
> ERROR: gdbserver does not support run without extended-remote
>     while executing
> "error "gdbserver does not support $command without extended-remote""
>
> Maybe we should punt when testing against native-gdbserver, or start
> the program differently.

Disagree that this is a small nit :)  I think this does need fixing
before the patch can be merged.

Thanks,
Andrew


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v3] gdb/testsuite: add test for backtracing for threaded inferiors from a corefile
  2023-10-25 11:42 [PATCH v3] gdb/testsuite: add test for backtracing for threaded inferiors from a corefile Guinevere Larsen
                   ` (2 preceding siblings ...)
  2023-11-24 12:26 ` [PATCH " Luis Machado
@ 2023-11-28 12:07 ` Andrew Burgess
  2023-12-01 14:28   ` Guinevere Larsen
  3 siblings, 1 reply; 8+ messages in thread
From: Andrew Burgess @ 2023-11-28 12:07 UTC (permalink / raw)
  To: Guinevere Larsen, gdb-patches; +Cc: Guinevere Larsen

Guinevere Larsen <blarsen@redhat.com> writes:

> This patch is based on an out-of-tree patch that fedora has been
> carrying for a while. It tests if GDB is able to properly unwind a
> threaded program in the following situations:
> * regular threads
> * in a signal handler
> * in a signal handler executing on an alternate stack
>
> And the final frame can either be in a syscall or in an infinite loop.
>
> The test works by running the inferior until a crash to generate a
> corefile, or until right before the crash. Then applies a backtrace to
> all threads to see if any frame can't be identified, and the order of
> the threads in GDB. Finally, it goes thread by thread and tries to
> collect a large part of the backtrace, to confirm that everything is
> being unwound correctly.
>
> Co-Authored-By: Andrew Burgess <aburgess@redhat.com>
> ---
>
> Changes for v3:
> * Resolved Lancelot's comment
> * undid early exit in favor of more readable gdb_test usage to load the
>   corefile
>
> Changes for v2:
> * Linaro CI identified an issue with the test, which made the test fail
>   when using read1. Fixed here
> * Also added early exit on corefile tests, if the corefile isn't
>   properly loaded
>
> ---
>  gdb/testsuite/gdb.threads/threadcrash.c   | 443 ++++++++++++++++++++++
>  gdb/testsuite/gdb.threads/threadcrash.exp | 209 ++++++++++
>  2 files changed, 652 insertions(+)
>  create mode 100644 gdb/testsuite/gdb.threads/threadcrash.c
>  create mode 100644 gdb/testsuite/gdb.threads/threadcrash.exp
>
> diff --git a/gdb/testsuite/gdb.threads/threadcrash.c b/gdb/testsuite/gdb.threads/threadcrash.c
> new file mode 100644
> index 00000000000..e476ae7b07d
> --- /dev/null
> +++ b/gdb/testsuite/gdb.threads/threadcrash.c
> @@ -0,0 +1,443 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> +   Copyright 2023 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>
> +#include <signal.h>
> +#include <unistd.h>
> +
> +/* The delay that the main thread gives once all the worker threads have
> +   reached the barrier before the main thread enters the function on which
> +   GDB will have placed a breakpoint.  */
> +
> +#define MAIN_THREAD_DELAY 2
> +
> +/* The maximum time we allow this test program to run for before an alarm
> +   signal is sent and everything will exit.  */
> +#define WATCHDOG_ALARM_TIME 600
> +
> +/* Aliases for the signals used within this script.  Each signal
> +   corresponds to an action (from the FINAL_ACTION enum) that the signal
> +   handler will perform.  */
> +
> +#define SPIN_SIGNAL SIGUSR1
> +#define SYSCALL_SIGNAL SIGUSR2
> +
> +/* Describe the final action that a thread should perform.  */
> +
> +enum final_action
> +  {
> +    /* Thread should spin in an infinite loop.  */
> +    SPIN = 0,
> +
> +    /* Thread should block in a syscall.  */
> +    SYSCALL,
> +
> +    /* This is just a marker to allow for looping over the enum.  */
> +    LAST_ACTION
> +  };
> +
> +/* Where should the thread perform this action?  */
> +
> +enum exec_location
> +  {
> +    /* Just a normal thread, on a normal stack.  */
> +    NORMAL = 0,
> +
> +    /* In a signal handler, but use the normal stack.  */
> +    SIGNAL_HANDLER,
> +
> +    /* In a signal handler using an alternative stack.  */
> +    SIGNAL_ALT_STACK,
> +
> +    /* This is just a marker to allow for looping over the enum.  */
> +    LAST_LOCACTION
> +  };
> +
> +/* A descriptor for a single thread job.  We create a new thread for each
> +   job_description.  */
> +
> +struct job_description
> +{
> +  /* What action should this thread perform.  */
> +  enum final_action action;
> +
> +  /* Where should the thread perform the action.  */
> +  enum exec_location location;
> +
> +  /* The actual thread handle, so we can join with the thread.  */
> +  pthread_t thread;
> +};
> +
> +/* A pthread barrier, used to (try) and synchronise the threads.  */
> +pthread_barrier_t global_barrier;
> +
> +/* Return a list of jobs, and place the length of the list in *COUNT.  */
> +
> +struct job_description *
> +get_job_list (int *count)
> +{
> +  /* The number of jobs.  */
> +  int num = LAST_ACTION * LAST_LOCACTION;
> +
> +  /* The uninitialised array of jobs.  */
> +  struct job_description *list
> +    = malloc (num * sizeof (struct job_description));
> +  assert (list != NULL);
> +
> +  /* Fill the array with all possible jobs.  */
> +  for (int i = 0; i < (int) LAST_ACTION; ++i)
> +    for (int j = 0; j < (int) LAST_LOCACTION; ++j)
> +      {
> +	int idx = (i * LAST_LOCACTION) + j;
> +	list[idx].action = (enum final_action) i;
> +	list[idx].location = (enum exec_location) j;
> +      }
> +
> +  /* Return the array of jobs.  */
> +  *count = num;
> +  return list;
> +}
> +
> +/* This function should never be called.  If it is then an assertion will
> +   trigger.  */
> +
> +void
> +assert_not_reached (void)
> +{
> +  assert (0);
> +}
> +
> +/* The function for a SPIN action.  Just spins in a loop.  The LOCATION
> +   argument exists so GDB can identify the expected context for this
> +   function.  */
> +
> +void
> +do_spin_task (enum exec_location location)
> +{
> +  (void) location;
> +
> +  /* Let everyone know that we're about to perform our action.  */
> +  int res = pthread_barrier_wait (&global_barrier);
> +  assert (res == PTHREAD_BARRIER_SERIAL_THREAD || res == 0);
> +
> +  while (1)
> +    {
> +      /* Nothing.  */
> +    }
> +}
> +
> +/* The function for a SYSCALL action.  Just spins in a loop.  The LOCATION
> +   argument exists so GDB can identify the expected context for this
> +   function.  */
> +
> +void
> +do_syscall_task (enum exec_location location)
> +{
> +  (void) location;
> +
> +  /* Let everyone know that we're about to perform our action.  */
> +  int res = pthread_barrier_wait (&global_barrier);
> +  assert (res == PTHREAD_BARRIER_SERIAL_THREAD || res == 0);
> +
> +  sleep (600);
> +}
> +
> +/* Return the required size for a sigaltstack.  We start with a single
> +   page, but do check against the system defined minimums.  We don't run
> +   much on the alternative stacks, so we don't need a huge one.  */
> +
> +size_t
> +get_stack_size (void)
> +{
> +  size_t size = getpagesize ();	/* Arbitrary starting size.  */
> +  if (size < SIGSTKSZ)
> +    size = SIGSTKSZ;
> +  if (size < MINSIGSTKSZ)
> +    size = MINSIGSTKSZ;
> +  return size;
> +}
> +
> +/* A descriptor for an alternative stack.  */
> +
> +struct stack_descriptor
> +{
> +  /* The base address of the alternative stack.  This is the address that
> +     must be freed to release the memory used by this stack.  */
> +  void *base;
> +
> +  /* The size of this alternative stack.  Tracked just so we can query this
> +     from GDB.  */
> +  size_t size;
> +};
> +
> +/* Install an alternative signal stack.  Return a descriptor for the newly
> +   allocated alternative stack.  */
> +
> +struct stack_descriptor
> +setup_alt_stack (void)
> +{
> +  size_t stack_size = get_stack_size ();
> +
> +  void *stack_area = malloc (stack_size);
> +
> +  stack_t stk;
> +  stk.ss_sp = stack_area;
> +  stk.ss_flags = 0;
> +  stk.ss_size = stack_size;
> +
> +  int res = sigaltstack (&stk, NULL);
> +  assert (res == 0);
> +
> +  struct stack_descriptor desc;
> +  desc.base = stack_area;
> +  desc.size = stack_size;
> +
> +  return desc;
> +}
> +
> +/* Return true (non-zero) if we are currently on the alternative stack,
> +   otherwise, return false (zero).  */
> +
> +int
> +on_alt_stack_p (void)
> +{
> +  stack_t stk;
> +  int res = sigaltstack (NULL, &stk);
> +  assert (res == 0);
> +
> +  return (stk.ss_flags & SS_ONSTACK) != 0;
> +}
> +
> +/* The signal handler function.  All signals call here, so we use SIGNO
> +   (the signal that was delivered) to decide what action to perform.  This
> +   function might, or might not, have been called on an alternative signal
> +   stack.  */
> +
> +void
> +signal_handler (int signo)
> +{
> +  enum exec_location location
> +    = on_alt_stack_p () ? SIGNAL_ALT_STACK : SIGNAL_HANDLER;
> +
> +  switch (signo)
> +    {
> +    case SPIN_SIGNAL:
> +      do_spin_task (location);
> +      break;
> +
> +    case SYSCALL_SIGNAL:
> +      do_syscall_task (location);
> +      break;
> +
> +    default:
> +      assert_not_reached ();
> +    }
> +}
> +
> +/* The thread worker function.  ARG is a job_description pointer which
> +   describes what this thread is expected to do.  This function always
> +   returns a NULL pointer.  */
> +
> +void *
> +thread_function (void *arg)
> +{
> +  struct job_description *job = (struct job_description *) arg;
> +  struct stack_descriptor desc = { NULL, 0 };
> +  int sa_flags = 0;
> +
> +  switch (job->location)
> +    {
> +    case NORMAL:
> +      /* This thread performs the worker action on the current thread,
> +	 select the correct worker function based on the requested
> +	 action.  */
> +      switch (job->action)
> +	{
> +	case SPIN:
> +	  do_spin_task (NORMAL);
> +	  break;
> +
> +	case SYSCALL:
> +	  do_syscall_task (NORMAL);
> +	  break;
> +
> +	default:
> +	  assert_not_reached ();
> +	}
> +      break;
> +
> +    case SIGNAL_ALT_STACK:
> +      /* This thread is to perform its action in a signal handler on the
> +	 alternative stack.  Install the alternative stack now, and then
> +	 fall through to the normal signal handler location code.  */
> +      desc = setup_alt_stack ();
> +      assert (desc.base != NULL);
> +      assert (desc.size > 0);
> +      sa_flags = SA_ONSTACK;
> +
> +      /* Fall through.  */
> +    case SIGNAL_HANDLER:
> +      {
> +	/* This thread is to perform its action in a signal handler.  We
> +	   might have just installed an alternative signal stack.  */
> +	int signo, res;
> +
> +	/* Select the correct signal number so that the signal handler will
> +	   perform the required action.  */
> +	switch (job->action)
> +	  {
> +	  case SPIN:
> +	    signo = SPIN_SIGNAL;
> +	    break;
> +
> +	  case SYSCALL:
> +	    signo = SYSCALL_SIGNAL;
> +	    break;
> +
> +	  default:
> +	    assert_not_reached ();
> +	  }
> +
> +	/* Now setup the signal handler.  */
> +	struct sigaction sa;
> +	sa.sa_handler = signal_handler;
> +	sigfillset (&sa.sa_mask);
> +	sa.sa_flags = sa_flags;
> +	res = sigaction (signo, &sa, NULL);
> +	assert (res == 0);
> +
> +	/* Send the signal to this thread.  */
> +	res = pthread_kill (job->thread, signo);
> +	assert (res == 0);
> +      }
> +      break;
> +
> +    default:
> +      assert_not_reached ();
> +    };
> +
> +  /* Free the alt-stack if we allocated one, if not DESC.BASE will be
> +     NULL so this call is fine.  */
> +  free (desc.base);
> +
> +  /* Thread complete.  */
> +  return NULL;
> +}
> +
> +void
> +start_job (struct job_description *job)
> +{
> +  int res;
> +
> +  res = pthread_create (&job->thread, NULL, thread_function, job);
> +  assert (res == 0);
> +}
> +
> +/* Join with the thread for JOB.  This will block until the thread for JOB
> +   has finished.  */
> +
> +void
> +finalise_job (struct job_description *job)
> +{
> +  int res;
> +  void *retval;
> +
> +  res = pthread_join (job->thread, &retval);
> +  assert (res == 0);
> +  assert (retval == NULL);
> +}
> +
> +/* Function that GDB can place a breakpoint on.  */
> +
> +void
> +breakpt (void)
> +{
> +  /* Nothing.  */
> +}
> +
> +/* Function that triggers a crash, if the user has setup their environment
> +   correctly this will dump a core file, which GDB can then examine.  */
> +
> +void
> +crash_function (void)
> +{
> +  volatile int *p = 0;
> +  volatile int n = *p;
> +  (void) n;
> +}
> +
> +/* Entry point.  */
> +
> +int
> +main ()
> +{
> +  int job_count, res;
> +  struct job_description *jobs = get_job_list (&job_count);
> +
> +  /* This test is going to park some threads inside infinite loops.  Just
> +     in case this program is left running, install an alarm that will cause
> +     everything to exit.  */
> +  alarm (WATCHDOG_ALARM_TIME);
> +
> +  /* We want each worker thread (of which there are JOB_COUNT) plus the
> +     main thread (hence + 1) to wait at the barrier.  */
> +  res = pthread_barrier_init (&global_barrier, NULL, job_count + 1);
> +  assert (res == 0);
> +
> +  /* Start all the jobs.  */
> +  for (int i = 0; i < job_count; ++i)
> +    start_job (&jobs[i]);
> +
> +  /* Notify all the worker threads that we're waiting for them.  */
> +  res = pthread_barrier_wait (&global_barrier);
> +  assert (res == PTHREAD_BARRIER_SERIAL_THREAD || res == 0);
> +
> +  /* All we know at this point is that all the worker threads have reached
> +     the barrier, which is just before they perform their action.  But we
> +     really want them to start their action.
> +
> +     There's really no way we can be 100% certain that the worker threads
> +     have started their action, all we can do is wait for a short while and
> +     hope that the machine we're running on is not too slow.  */
> +  sleep (MAIN_THREAD_DELAY);
> +
> +  /* A function that GDB can place a breakpoint on.  By the time we get
> +     here we are as sure as we can be that all of the worker threads have
> +     started and are in their worker action (spinning, or syscall).  */
> +  breakpt ();
> +
> +  /* If GDB is not attached then this function will cause a crash, which
> +     can be used to dump a core file, which GDB can then analyse.  */
> +  crash_function ();
> +
> +  /* Due to the crash we never expect to get here.  Plus the worker actions
> +     never terminate.  But for completeness, here's where we join with all
> +     the worker threads.  */
> +  for (int i = 0; i < job_count; ++i)
> +    finalise_job (&jobs[i]);
> +
> +  /* Cleanup the barrier.  */
> +  res = pthread_barrier_destroy (&global_barrier);
> +  assert (res == 0);
> +
> +  /* And clean up the jobs list.  */
> +  free (jobs);
> +
> +  return 0;
> +}
> diff --git a/gdb/testsuite/gdb.threads/threadcrash.exp b/gdb/testsuite/gdb.threads/threadcrash.exp
> new file mode 100644
> index 00000000000..ee81b5c32f2
> --- /dev/null
> +++ b/gdb/testsuite/gdb.threads/threadcrash.exp
> @@ -0,0 +1,209 @@
> +# This testcase is part of GDB, the GNU debugger.
> +
> +# Copyright 2023 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/>.
> +
> +# This test case looks at GDB's ability to get correct backtraces for a
> +# crashed inferior, recreating it from a live inferior, a corefile and
> +# a gcore.

Generally fine with this patch, but I do have a small style issue.

> +
> +
> +# First check that we have 7 threads.
> +

You've commented the first two functions, but I think we can be more
descriptive, I'd say:

  # Check that the inferior has 7 threads, and return the number of
  # threads (7).

> +proc test_thread_count {} {
> +    set thread_count 0
> +
> +    gdb_test_multiple "info threads" "getting thread count" -lbl {
> +	-re "Thread" {
> +	    incr thread_count
> +	    exp_continue
> +	}
> +	-re "$::gdb_prompt " {
> +	    gdb_assert {$thread_count == 7}
> +	}
> +    }
> +
> +    return $thread_count
> +}
> +
> +# Apply all to quickly check if all expected states are
> +# present.  Then, save the full desired backtrace in a list
> +# so we can check full backtraces later.

It took me a while to figure out what this function was doing.  I'd say:

  # Use 'thread apply all backtrace' to check if all the expected
  # threads are present, and stopped in the expected locations.  Set the
  # global TEST_LIST to a list of regexp that is expected to match the
  # backtrace of each thread.  The order of TEST_LIST will vary based on
  # the order in which GDB sees the threads.

It was this last part that puzzled me on first reading -- why wasn't the
global TEST_LIST just setup once at the start of the test?  I believe it
must be because the threads can show up in different orders, right?

> +
> +proc thread_apply_all {} {
> +    global test_list

I'd be tempted to clear TEST_LIST within the function, rather than doing
it elsewhere ... unless there's a reason why that wouldn't work.  But
surely, if TEST_LIST has content at this point then adding the items
below is going to not do the right thing?

> +
> +    set unwind_fail false
> +
> +    gdb_test_multiple "thread apply all backtrace" \
> +	"Get thread information" -lbl {
> +	    -re "#\[0-9\]+\\\?\\\?\[^\n\]*" {
> +		set unwind_fail true
> +		exp_continue
> +	    }
> +	    -re "\[^\n\]*syscall_task .location=SIGNAL_ALT_STACK\[^\n\]*" {
> +		set test_list [linsert $test_list end [multi_line ".*sleep.*" \

Could we use lappend here?  Or am I missing something?


> +					      ".*do_syscall_task .location=SIGNAL_ALT_STACK.*" \
> +					      ".*signal_handler.*" \
> +					      ".*signal handler called.*" \
> +					      ".*pthread_kill.*" \
> +					      ".*thread_function.*"]]
> +		exp_continue
> +	    }
> +	    -re "\[^\n\]*syscall_task .location=SIGNAL_HANDLER\[^\n\]*" {
> +		set test_list [linsert $test_list end [multi_line ".*sleep.*" \
> +					      ".*do_syscall_task .location=SIGNAL_HANDLER.*" \
> +					      ".*signal_handler.*" \
> +					      ".*signal handler called.*" \
> +					      ".*pthread_kill.*" \
> +					      ".*thread_function.*"]]
> +		exp_continue
> +	    }
> +	    -re "\[^\n\]*syscall_task .location=NORMAL\[^\n\]*" {
> +		set test_list [linsert $test_list end [multi_line ".*sleep.*" \
> +					      ".*do_syscall_task .location=NORMAL.*" \
> +					      ".*thread_function.*"]]
> +		exp_continue
> +	    }
> +	    -re "\[^\n\]*spin_task .location=SIGNAL_ALT_STACK\[^\n\]*" {
> +		set test_list [linsert $test_list end [multi_line ".*do_spin_task .location=SIGNAL_ALT_STACK.*" \
> +					      ".*signal_handler.*" \
> +					      ".*signal handler called.*" \
> +					      ".*pthread_kill.*" \
> +					      ".*thread_function.*"]]
> +		exp_continue
> +	    }
> +	    -re "\[^\n\]*spin_task .location=SIGNAL_HANDLER\[^\n\]*" {
> +		set test_list [linsert $test_list end [multi_line ".*do_spin_task .location=SIGNAL_HANDLER.*" \
> +					      ".*signal_handler.*" \
> +					      ".*signal handler called.*" \
> +					      ".*pthread_kill.*" \
> +					      ".*thread_function.*"]]
> +		exp_continue
> +	    }
> +	    -re "\[^\n\]*spin_task .location=NORMAL\[^\n\]*" {
> +		set test_list [linsert $test_list end [multi_line ".*do_spin_task .location=NORMAL..*" \
> +					      ".*thread_function.*"]]
> +		exp_continue
> +	    }
> +	    -re "\[^\n\]*main\[^\n\]*" {
> +		set test_list [linsert $test_list end ".*main.*"]
> +		exp_continue
> +	    }
> +	    -re "$::gdb_prompt " {
> +		pass $gdb_test_name
> +	    }
> +    }
> +
> +    gdb_assert {$unwind_fail == false}
> +}
> +

And the function comments just ... stop.  I think it's clearer if we
have a comment that states our intentions.

> +proc do_full_test {} {
> +    global test_list
> +    set thread_count [test_thread_count]
> +
> +    thread_apply_all
> +
> +    gdb_assert {$thread_count == [llength $test_list]}
> +
> +    for {set i 0} {$i < $thread_count } {incr i} {
> +	set thread_num [expr [llength $test_list] - $i]
> +
> +	gdb_test "thread apply $thread_num backtrace" [lindex $test_list $i]
> +    }
> +}
> +
> +proc_with_prefix test_corefile {} {
> +    set corefile [core_find $::binfile]
> +    if { $corefile == "" } {
> +	untested "couldn't generate corefile"
> +	return
> +    }
> +    set corefile [gdb_remote_download host $corefile]
> +
> +    gdb_test "core-file $corefile" \
> +	     "" \
> +	     "loading_corefile" \
> +	     "A program is being debugged already\\\.  Kill it\\\? \\\(y or n\\\) " \
> +	     "y"
> +
> +    do_full_test
> +}
> +
> +proc_with_prefix test_gcore {} {
> +
> +    clean_restart "$::binfile"
> +
> +    gdb_test "handle SIGUSR1 nostop print pass" \
> +	".*SIGUSR1.*No.*Yes.*Yes.*User defined signal 1" \
> +	"setup SIGUSR1"
> +    gdb_test "handle SIGUSR2 nostop print pass" \
> +	".*SIGUSR2.*No.*Yes.*Yes.*User defined signal 2" \
> +	"setup SIGUSR2"
> +
> +    gdb_test "run" ".*Segmentation fault.*" "continue to crash"
> +
> +    set gcore_name "${::binfile}.gcore"
> +    set gcore_supported [gdb_gcore_cmd "$gcore_name" "saving gcore"]
> +
> +    if {!$gcore_supported} {
> +	unsupported "couldn't generate gcore file"
> +	return
> +    }
> +
> +    set corefile [gdb_remote_download host $gcore_name]
> +
> +    gdb_test "core-file $corefile" \
> +	     "" \
> +	     "loading_corefile" \
> +	     "A program is being debugged already\\\.  Kill it\\\? \\\(y or n\\\) " \
> +	     "y"
> +
> +    do_full_test
> +}
> +
> +standard_testfile
> +
> +if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } {
> +    return -1
> +}
> +

You can still use prepare_for_testing here, I think this should do what
you want:

  if [prepare_for_testing "failed to prepare" $testfile $srcfile {debug pthreads}] {
    return -1
  }

The use of 'pthreads' in the flags will cause this to call
gdb_compile_pthreads under the hood.

> +clean_restart ${binfile}
> +
> +gdb_test_no_output "set backtrace limit unlimited"
> +
> +set test_list { }

These I'm hoping can be eliminated by moving this line into
thread_apply_all. 

Thanks,
Andrew
> +
> +with_test_prefix "live inferior" {
> +    gdb_test "handle SIGUSR1 nostop print pass" \
> +	".*SIGUSR1.*No.*Yes.*Yes.*User defined signal 1" \
> +	"setup SIGUSR1"
> +    gdb_test "handle SIGUSR2 nostop print pass" \
> +	".*SIGUSR2.*No.*Yes.*Yes.*User defined signal 2" \
> +	"setup SIGUSR2"
> +
> +    gdb_breakpoint "breakpt"
> +    gdb_test "run" ".*breakpt.*" "run to break function"
> +
> +    do_full_test
> +}
> +
> +set test_list { }
> +
> +test_corefile
> +
> +set test_list { }
> +
> +test_gcore
> -- 
> 2.41.0


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v3] gdb/testsuite: add test for backtracing for threaded inferiors from a corefile
  2023-11-28 12:07 ` Andrew Burgess
@ 2023-12-01 14:28   ` Guinevere Larsen
  0 siblings, 0 replies; 8+ messages in thread
From: Guinevere Larsen @ 2023-12-01 14:28 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches

On 28/11/2023 13:07, Andrew Burgess wrote:
> Guinevere Larsen <blarsen@redhat.com> writes:
>
>> This patch is based on an out-of-tree patch that fedora has been
>> carrying for a while. It tests if GDB is able to properly unwind a
>> threaded program in the following situations:
>> * regular threads
>> * in a signal handler
>> * in a signal handler executing on an alternate stack
>>
>> And the final frame can either be in a syscall or in an infinite loop.
>>
>> The test works by running the inferior until a crash to generate a
>> corefile, or until right before the crash. Then applies a backtrace to
>> all threads to see if any frame can't be identified, and the order of
>> the threads in GDB. Finally, it goes thread by thread and tries to
>> collect a large part of the backtrace, to confirm that everything is
>> being unwound correctly.
>>
>> Co-Authored-By: Andrew Burgess <aburgess@redhat.com>
>> ---
>>
>> Changes for v3:
>> * Resolved Lancelot's comment
>> * undid early exit in favor of more readable gdb_test usage to load the
>>    corefile
>>
>> Changes for v2:
>> * Linaro CI identified an issue with the test, which made the test fail
>>    when using read1. Fixed here
>> * Also added early exit on corefile tests, if the corefile isn't
>>    properly loaded
>>
>> ---
>>   gdb/testsuite/gdb.threads/threadcrash.c   | 443 ++++++++++++++++++++++
>>   gdb/testsuite/gdb.threads/threadcrash.exp | 209 ++++++++++
>>   2 files changed, 652 insertions(+)
>>   create mode 100644 gdb/testsuite/gdb.threads/threadcrash.c
>>   create mode 100644 gdb/testsuite/gdb.threads/threadcrash.exp
>>
>> diff --git a/gdb/testsuite/gdb.threads/threadcrash.c b/gdb/testsuite/gdb.threads/threadcrash.c
>> new file mode 100644
>> index 00000000000..e476ae7b07d
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.threads/threadcrash.c
>> @@ -0,0 +1,443 @@
>> +/* This testcase is part of GDB, the GNU debugger.
>> +
>> +   Copyright 2023 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>
>> +#include <signal.h>
>> +#include <unistd.h>
>> +
>> +/* The delay that the main thread gives once all the worker threads have
>> +   reached the barrier before the main thread enters the function on which
>> +   GDB will have placed a breakpoint.  */
>> +
>> +#define MAIN_THREAD_DELAY 2
>> +
>> +/* The maximum time we allow this test program to run for before an alarm
>> +   signal is sent and everything will exit.  */
>> +#define WATCHDOG_ALARM_TIME 600
>> +
>> +/* Aliases for the signals used within this script.  Each signal
>> +   corresponds to an action (from the FINAL_ACTION enum) that the signal
>> +   handler will perform.  */
>> +
>> +#define SPIN_SIGNAL SIGUSR1
>> +#define SYSCALL_SIGNAL SIGUSR2
>> +
>> +/* Describe the final action that a thread should perform.  */
>> +
>> +enum final_action
>> +  {
>> +    /* Thread should spin in an infinite loop.  */
>> +    SPIN = 0,
>> +
>> +    /* Thread should block in a syscall.  */
>> +    SYSCALL,
>> +
>> +    /* This is just a marker to allow for looping over the enum.  */
>> +    LAST_ACTION
>> +  };
>> +
>> +/* Where should the thread perform this action?  */
>> +
>> +enum exec_location
>> +  {
>> +    /* Just a normal thread, on a normal stack.  */
>> +    NORMAL = 0,
>> +
>> +    /* In a signal handler, but use the normal stack.  */
>> +    SIGNAL_HANDLER,
>> +
>> +    /* In a signal handler using an alternative stack.  */
>> +    SIGNAL_ALT_STACK,
>> +
>> +    /* This is just a marker to allow for looping over the enum.  */
>> +    LAST_LOCACTION
>> +  };
>> +
>> +/* A descriptor for a single thread job.  We create a new thread for each
>> +   job_description.  */
>> +
>> +struct job_description
>> +{
>> +  /* What action should this thread perform.  */
>> +  enum final_action action;
>> +
>> +  /* Where should the thread perform the action.  */
>> +  enum exec_location location;
>> +
>> +  /* The actual thread handle, so we can join with the thread.  */
>> +  pthread_t thread;
>> +};
>> +
>> +/* A pthread barrier, used to (try) and synchronise the threads.  */
>> +pthread_barrier_t global_barrier;
>> +
>> +/* Return a list of jobs, and place the length of the list in *COUNT.  */
>> +
>> +struct job_description *
>> +get_job_list (int *count)
>> +{
>> +  /* The number of jobs.  */
>> +  int num = LAST_ACTION * LAST_LOCACTION;
>> +
>> +  /* The uninitialised array of jobs.  */
>> +  struct job_description *list
>> +    = malloc (num * sizeof (struct job_description));
>> +  assert (list != NULL);
>> +
>> +  /* Fill the array with all possible jobs.  */
>> +  for (int i = 0; i < (int) LAST_ACTION; ++i)
>> +    for (int j = 0; j < (int) LAST_LOCACTION; ++j)
>> +      {
>> +	int idx = (i * LAST_LOCACTION) + j;
>> +	list[idx].action = (enum final_action) i;
>> +	list[idx].location = (enum exec_location) j;
>> +      }
>> +
>> +  /* Return the array of jobs.  */
>> +  *count = num;
>> +  return list;
>> +}
>> +
>> +/* This function should never be called.  If it is then an assertion will
>> +   trigger.  */
>> +
>> +void
>> +assert_not_reached (void)
>> +{
>> +  assert (0);
>> +}
>> +
>> +/* The function for a SPIN action.  Just spins in a loop.  The LOCATION
>> +   argument exists so GDB can identify the expected context for this
>> +   function.  */
>> +
>> +void
>> +do_spin_task (enum exec_location location)
>> +{
>> +  (void) location;
>> +
>> +  /* Let everyone know that we're about to perform our action.  */
>> +  int res = pthread_barrier_wait (&global_barrier);
>> +  assert (res == PTHREAD_BARRIER_SERIAL_THREAD || res == 0);
>> +
>> +  while (1)
>> +    {
>> +      /* Nothing.  */
>> +    }
>> +}
>> +
>> +/* The function for a SYSCALL action.  Just spins in a loop.  The LOCATION
>> +   argument exists so GDB can identify the expected context for this
>> +   function.  */
>> +
>> +void
>> +do_syscall_task (enum exec_location location)
>> +{
>> +  (void) location;
>> +
>> +  /* Let everyone know that we're about to perform our action.  */
>> +  int res = pthread_barrier_wait (&global_barrier);
>> +  assert (res == PTHREAD_BARRIER_SERIAL_THREAD || res == 0);
>> +
>> +  sleep (600);
>> +}
>> +
>> +/* Return the required size for a sigaltstack.  We start with a single
>> +   page, but do check against the system defined minimums.  We don't run
>> +   much on the alternative stacks, so we don't need a huge one.  */
>> +
>> +size_t
>> +get_stack_size (void)
>> +{
>> +  size_t size = getpagesize ();	/* Arbitrary starting size.  */
>> +  if (size < SIGSTKSZ)
>> +    size = SIGSTKSZ;
>> +  if (size < MINSIGSTKSZ)
>> +    size = MINSIGSTKSZ;
>> +  return size;
>> +}
>> +
>> +/* A descriptor for an alternative stack.  */
>> +
>> +struct stack_descriptor
>> +{
>> +  /* The base address of the alternative stack.  This is the address that
>> +     must be freed to release the memory used by this stack.  */
>> +  void *base;
>> +
>> +  /* The size of this alternative stack.  Tracked just so we can query this
>> +     from GDB.  */
>> +  size_t size;
>> +};
>> +
>> +/* Install an alternative signal stack.  Return a descriptor for the newly
>> +   allocated alternative stack.  */
>> +
>> +struct stack_descriptor
>> +setup_alt_stack (void)
>> +{
>> +  size_t stack_size = get_stack_size ();
>> +
>> +  void *stack_area = malloc (stack_size);
>> +
>> +  stack_t stk;
>> +  stk.ss_sp = stack_area;
>> +  stk.ss_flags = 0;
>> +  stk.ss_size = stack_size;
>> +
>> +  int res = sigaltstack (&stk, NULL);
>> +  assert (res == 0);
>> +
>> +  struct stack_descriptor desc;
>> +  desc.base = stack_area;
>> +  desc.size = stack_size;
>> +
>> +  return desc;
>> +}
>> +
>> +/* Return true (non-zero) if we are currently on the alternative stack,
>> +   otherwise, return false (zero).  */
>> +
>> +int
>> +on_alt_stack_p (void)
>> +{
>> +  stack_t stk;
>> +  int res = sigaltstack (NULL, &stk);
>> +  assert (res == 0);
>> +
>> +  return (stk.ss_flags & SS_ONSTACK) != 0;
>> +}
>> +
>> +/* The signal handler function.  All signals call here, so we use SIGNO
>> +   (the signal that was delivered) to decide what action to perform.  This
>> +   function might, or might not, have been called on an alternative signal
>> +   stack.  */
>> +
>> +void
>> +signal_handler (int signo)
>> +{
>> +  enum exec_location location
>> +    = on_alt_stack_p () ? SIGNAL_ALT_STACK : SIGNAL_HANDLER;
>> +
>> +  switch (signo)
>> +    {
>> +    case SPIN_SIGNAL:
>> +      do_spin_task (location);
>> +      break;
>> +
>> +    case SYSCALL_SIGNAL:
>> +      do_syscall_task (location);
>> +      break;
>> +
>> +    default:
>> +      assert_not_reached ();
>> +    }
>> +}
>> +
>> +/* The thread worker function.  ARG is a job_description pointer which
>> +   describes what this thread is expected to do.  This function always
>> +   returns a NULL pointer.  */
>> +
>> +void *
>> +thread_function (void *arg)
>> +{
>> +  struct job_description *job = (struct job_description *) arg;
>> +  struct stack_descriptor desc = { NULL, 0 };
>> +  int sa_flags = 0;
>> +
>> +  switch (job->location)
>> +    {
>> +    case NORMAL:
>> +      /* This thread performs the worker action on the current thread,
>> +	 select the correct worker function based on the requested
>> +	 action.  */
>> +      switch (job->action)
>> +	{
>> +	case SPIN:
>> +	  do_spin_task (NORMAL);
>> +	  break;
>> +
>> +	case SYSCALL:
>> +	  do_syscall_task (NORMAL);
>> +	  break;
>> +
>> +	default:
>> +	  assert_not_reached ();
>> +	}
>> +      break;
>> +
>> +    case SIGNAL_ALT_STACK:
>> +      /* This thread is to perform its action in a signal handler on the
>> +	 alternative stack.  Install the alternative stack now, and then
>> +	 fall through to the normal signal handler location code.  */
>> +      desc = setup_alt_stack ();
>> +      assert (desc.base != NULL);
>> +      assert (desc.size > 0);
>> +      sa_flags = SA_ONSTACK;
>> +
>> +      /* Fall through.  */
>> +    case SIGNAL_HANDLER:
>> +      {
>> +	/* This thread is to perform its action in a signal handler.  We
>> +	   might have just installed an alternative signal stack.  */
>> +	int signo, res;
>> +
>> +	/* Select the correct signal number so that the signal handler will
>> +	   perform the required action.  */
>> +	switch (job->action)
>> +	  {
>> +	  case SPIN:
>> +	    signo = SPIN_SIGNAL;
>> +	    break;
>> +
>> +	  case SYSCALL:
>> +	    signo = SYSCALL_SIGNAL;
>> +	    break;
>> +
>> +	  default:
>> +	    assert_not_reached ();
>> +	  }
>> +
>> +	/* Now setup the signal handler.  */
>> +	struct sigaction sa;
>> +	sa.sa_handler = signal_handler;
>> +	sigfillset (&sa.sa_mask);
>> +	sa.sa_flags = sa_flags;
>> +	res = sigaction (signo, &sa, NULL);
>> +	assert (res == 0);
>> +
>> +	/* Send the signal to this thread.  */
>> +	res = pthread_kill (job->thread, signo);
>> +	assert (res == 0);
>> +      }
>> +      break;
>> +
>> +    default:
>> +      assert_not_reached ();
>> +    };
>> +
>> +  /* Free the alt-stack if we allocated one, if not DESC.BASE will be
>> +     NULL so this call is fine.  */
>> +  free (desc.base);
>> +
>> +  /* Thread complete.  */
>> +  return NULL;
>> +}
>> +
>> +void
>> +start_job (struct job_description *job)
>> +{
>> +  int res;
>> +
>> +  res = pthread_create (&job->thread, NULL, thread_function, job);
>> +  assert (res == 0);
>> +}
>> +
>> +/* Join with the thread for JOB.  This will block until the thread for JOB
>> +   has finished.  */
>> +
>> +void
>> +finalise_job (struct job_description *job)
>> +{
>> +  int res;
>> +  void *retval;
>> +
>> +  res = pthread_join (job->thread, &retval);
>> +  assert (res == 0);
>> +  assert (retval == NULL);
>> +}
>> +
>> +/* Function that GDB can place a breakpoint on.  */
>> +
>> +void
>> +breakpt (void)
>> +{
>> +  /* Nothing.  */
>> +}
>> +
>> +/* Function that triggers a crash, if the user has setup their environment
>> +   correctly this will dump a core file, which GDB can then examine.  */
>> +
>> +void
>> +crash_function (void)
>> +{
>> +  volatile int *p = 0;
>> +  volatile int n = *p;
>> +  (void) n;
>> +}
>> +
>> +/* Entry point.  */
>> +
>> +int
>> +main ()
>> +{
>> +  int job_count, res;
>> +  struct job_description *jobs = get_job_list (&job_count);
>> +
>> +  /* This test is going to park some threads inside infinite loops.  Just
>> +     in case this program is left running, install an alarm that will cause
>> +     everything to exit.  */
>> +  alarm (WATCHDOG_ALARM_TIME);
>> +
>> +  /* We want each worker thread (of which there are JOB_COUNT) plus the
>> +     main thread (hence + 1) to wait at the barrier.  */
>> +  res = pthread_barrier_init (&global_barrier, NULL, job_count + 1);
>> +  assert (res == 0);
>> +
>> +  /* Start all the jobs.  */
>> +  for (int i = 0; i < job_count; ++i)
>> +    start_job (&jobs[i]);
>> +
>> +  /* Notify all the worker threads that we're waiting for them.  */
>> +  res = pthread_barrier_wait (&global_barrier);
>> +  assert (res == PTHREAD_BARRIER_SERIAL_THREAD || res == 0);
>> +
>> +  /* All we know at this point is that all the worker threads have reached
>> +     the barrier, which is just before they perform their action.  But we
>> +     really want them to start their action.
>> +
>> +     There's really no way we can be 100% certain that the worker threads
>> +     have started their action, all we can do is wait for a short while and
>> +     hope that the machine we're running on is not too slow.  */
>> +  sleep (MAIN_THREAD_DELAY);
>> +
>> +  /* A function that GDB can place a breakpoint on.  By the time we get
>> +     here we are as sure as we can be that all of the worker threads have
>> +     started and are in their worker action (spinning, or syscall).  */
>> +  breakpt ();
>> +
>> +  /* If GDB is not attached then this function will cause a crash, which
>> +     can be used to dump a core file, which GDB can then analyse.  */
>> +  crash_function ();
>> +
>> +  /* Due to the crash we never expect to get here.  Plus the worker actions
>> +     never terminate.  But for completeness, here's where we join with all
>> +     the worker threads.  */
>> +  for (int i = 0; i < job_count; ++i)
>> +    finalise_job (&jobs[i]);
>> +
>> +  /* Cleanup the barrier.  */
>> +  res = pthread_barrier_destroy (&global_barrier);
>> +  assert (res == 0);
>> +
>> +  /* And clean up the jobs list.  */
>> +  free (jobs);
>> +
>> +  return 0;
>> +}
>> diff --git a/gdb/testsuite/gdb.threads/threadcrash.exp b/gdb/testsuite/gdb.threads/threadcrash.exp
>> new file mode 100644
>> index 00000000000..ee81b5c32f2
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.threads/threadcrash.exp
>> @@ -0,0 +1,209 @@
>> +# This testcase is part of GDB, the GNU debugger.
>> +
>> +# Copyright 2023 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/>.
>> +
>> +# This test case looks at GDB's ability to get correct backtraces for a
>> +# crashed inferior, recreating it from a live inferior, a corefile and
>> +# a gcore.
> Generally fine with this patch, but I do have a small style issue.
>
>> +
>> +
>> +# First check that we have 7 threads.
>> +
> You've commented the first two functions, but I think we can be more
> descriptive, I'd say:
>
>    # Check that the inferior has 7 threads, and return the number of
>    # threads (7).
>
>> +proc test_thread_count {} {
>> +    set thread_count 0
>> +
>> +    gdb_test_multiple "info threads" "getting thread count" -lbl {
>> +	-re "Thread" {
>> +	    incr thread_count
>> +	    exp_continue
>> +	}
>> +	-re "$::gdb_prompt " {
>> +	    gdb_assert {$thread_count == 7}
>> +	}
>> +    }
>> +
>> +    return $thread_count
>> +}
>> +
>> +# Apply all to quickly check if all expected states are
>> +# present.  Then, save the full desired backtrace in a list
>> +# so we can check full backtraces later.
> It took me a while to figure out what this function was doing.  I'd say:
>
>    # Use 'thread apply all backtrace' to check if all the expected
>    # threads are present, and stopped in the expected locations.  Set the
>    # global TEST_LIST to a list of regexp that is expected to match the
>    # backtrace of each thread.  The order of TEST_LIST will vary based on
>    # the order in which GDB sees the threads.
>
> It was this last part that puzzled me on first reading -- why wasn't the
> global TEST_LIST just setup once at the start of the test?  I believe it
> must be because the threads can show up in different orders, right?
Yes, that's exactly it. I tried with hardcoding and changing sleep times 
and such, but I could never get the order to be consistent, so I figured 
building the list based on the actual final order of the threads would 
be better.
>
>> +
>> +proc thread_apply_all {} {
>> +    global test_list
> I'd be tempted to clear TEST_LIST within the function, rather than doing
> it elsewhere ... unless there's a reason why that wouldn't work.  But
> surely, if TEST_LIST has content at this point then adding the items
> below is going to not do the right thing?
good idea! I'll do this.
>
>> +
>> +    set unwind_fail false
>> +
>> +    gdb_test_multiple "thread apply all backtrace" \
>> +	"Get thread information" -lbl {
>> +	    -re "#\[0-9\]+\\\?\\\?\[^\n\]*" {
>> +		set unwind_fail true
>> +		exp_continue
>> +	    }
>> +	    -re "\[^\n\]*syscall_task .location=SIGNAL_ALT_STACK\[^\n\]*" {
>> +		set test_list [linsert $test_list end [multi_line ".*sleep.*" \
> Could we use lappend here?  Or am I missing something?

We can, TCL really isn't my forte :).

>
>
>> +					      ".*do_syscall_task .location=SIGNAL_ALT_STACK.*" \
>> +					      ".*signal_handler.*" \
>> +					      ".*signal handler called.*" \
>> +					      ".*pthread_kill.*" \
>> +					      ".*thread_function.*"]]
>> +		exp_continue
>> +	    }
>> +	    -re "\[^\n\]*syscall_task .location=SIGNAL_HANDLER\[^\n\]*" {
>> +		set test_list [linsert $test_list end [multi_line ".*sleep.*" \
>> +					      ".*do_syscall_task .location=SIGNAL_HANDLER.*" \
>> +					      ".*signal_handler.*" \
>> +					      ".*signal handler called.*" \
>> +					      ".*pthread_kill.*" \
>> +					      ".*thread_function.*"]]
>> +		exp_continue
>> +	    }
>> +	    -re "\[^\n\]*syscall_task .location=NORMAL\[^\n\]*" {
>> +		set test_list [linsert $test_list end [multi_line ".*sleep.*" \
>> +					      ".*do_syscall_task .location=NORMAL.*" \
>> +					      ".*thread_function.*"]]
>> +		exp_continue
>> +	    }
>> +	    -re "\[^\n\]*spin_task .location=SIGNAL_ALT_STACK\[^\n\]*" {
>> +		set test_list [linsert $test_list end [multi_line ".*do_spin_task .location=SIGNAL_ALT_STACK.*" \
>> +					      ".*signal_handler.*" \
>> +					      ".*signal handler called.*" \
>> +					      ".*pthread_kill.*" \
>> +					      ".*thread_function.*"]]
>> +		exp_continue
>> +	    }
>> +	    -re "\[^\n\]*spin_task .location=SIGNAL_HANDLER\[^\n\]*" {
>> +		set test_list [linsert $test_list end [multi_line ".*do_spin_task .location=SIGNAL_HANDLER.*" \
>> +					      ".*signal_handler.*" \
>> +					      ".*signal handler called.*" \
>> +					      ".*pthread_kill.*" \
>> +					      ".*thread_function.*"]]
>> +		exp_continue
>> +	    }
>> +	    -re "\[^\n\]*spin_task .location=NORMAL\[^\n\]*" {
>> +		set test_list [linsert $test_list end [multi_line ".*do_spin_task .location=NORMAL..*" \
>> +					      ".*thread_function.*"]]
>> +		exp_continue
>> +	    }
>> +	    -re "\[^\n\]*main\[^\n\]*" {
>> +		set test_list [linsert $test_list end ".*main.*"]
>> +		exp_continue
>> +	    }
>> +	    -re "$::gdb_prompt " {
>> +		pass $gdb_test_name
>> +	    }
>> +    }
>> +
>> +    gdb_assert {$unwind_fail == false}
>> +}
>> +
> And the function comments just ... stop.  I think it's clearer if we
> have a comment that states our intentions.
I'll add some comments and send them in the next version.
>
>> +proc do_full_test {} {
>> +    global test_list
>> +    set thread_count [test_thread_count]
>> +
>> +    thread_apply_all
>> +
>> +    gdb_assert {$thread_count == [llength $test_list]}
>> +
>> +    for {set i 0} {$i < $thread_count } {incr i} {
>> +	set thread_num [expr [llength $test_list] - $i]
>> +
>> +	gdb_test "thread apply $thread_num backtrace" [lindex $test_list $i]
>> +    }
>> +}
>> +
>> +proc_with_prefix test_corefile {} {
>> +    set corefile [core_find $::binfile]
>> +    if { $corefile == "" } {
>> +	untested "couldn't generate corefile"
>> +	return
>> +    }
>> +    set corefile [gdb_remote_download host $corefile]
>> +
>> +    gdb_test "core-file $corefile" \
>> +	     "" \
>> +	     "loading_corefile" \
>> +	     "A program is being debugged already\\\.  Kill it\\\? \\\(y or n\\\) " \
>> +	     "y"
>> +
>> +    do_full_test
>> +}
>> +
>> +proc_with_prefix test_gcore {} {
>> +
>> +    clean_restart "$::binfile"
>> +
>> +    gdb_test "handle SIGUSR1 nostop print pass" \
>> +	".*SIGUSR1.*No.*Yes.*Yes.*User defined signal 1" \
>> +	"setup SIGUSR1"
>> +    gdb_test "handle SIGUSR2 nostop print pass" \
>> +	".*SIGUSR2.*No.*Yes.*Yes.*User defined signal 2" \
>> +	"setup SIGUSR2"
>> +
>> +    gdb_test "run" ".*Segmentation fault.*" "continue to crash"
>> +
>> +    set gcore_name "${::binfile}.gcore"
>> +    set gcore_supported [gdb_gcore_cmd "$gcore_name" "saving gcore"]
>> +
>> +    if {!$gcore_supported} {
>> +	unsupported "couldn't generate gcore file"
>> +	return
>> +    }
>> +
>> +    set corefile [gdb_remote_download host $gcore_name]
>> +
>> +    gdb_test "core-file $corefile" \
>> +	     "" \
>> +	     "loading_corefile" \
>> +	     "A program is being debugged already\\\.  Kill it\\\? \\\(y or n\\\) " \
>> +	     "y"
>> +
>> +    do_full_test
>> +}
>> +
>> +standard_testfile
>> +
>> +if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } {
>> +    return -1
>> +}
>> +
> You can still use prepare_for_testing here, I think this should do what
> you want:
>
>    if [prepare_for_testing "failed to prepare" $testfile $srcfile {debug pthreads}] {
>      return -1
>    }
>
> The use of 'pthreads' in the flags will cause this to call
> gdb_compile_pthreads under the hood.
Will do. I guess I used an outdated test as an example for this one.

v4 should go out shortly.

-- 
Cheers,
Guinevere Larsen
She/Her/Hers

>
>> +clean_restart ${binfile}
>> +
>> +gdb_test_no_output "set backtrace limit unlimited"
>> +
>> +set test_list { }
> These I'm hoping can be eliminated by moving this line into
> thread_apply_all.
>
> Thanks,
> Andrew
>> +
>> +with_test_prefix "live inferior" {
>> +    gdb_test "handle SIGUSR1 nostop print pass" \
>> +	".*SIGUSR1.*No.*Yes.*Yes.*User defined signal 1" \
>> +	"setup SIGUSR1"
>> +    gdb_test "handle SIGUSR2 nostop print pass" \
>> +	".*SIGUSR2.*No.*Yes.*Yes.*User defined signal 2" \
>> +	"setup SIGUSR2"
>> +
>> +    gdb_breakpoint "breakpt"
>> +    gdb_test "run" ".*breakpt.*" "run to break function"
>> +
>> +    do_full_test
>> +}
>> +
>> +set test_list { }
>> +
>> +test_corefile
>> +
>> +set test_list { }
>> +
>> +test_gcore
>> -- 
>> 2.41.0


^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2023-12-01 14:28 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-10-25 11:42 [PATCH v3] gdb/testsuite: add test for backtracing for threaded inferiors from a corefile Guinevere Larsen
2023-11-14 10:51 ` [PING] " Guinevere Larsen
2023-11-21 17:24 ` [PING][PATCH " Guinevere Larsen
2023-11-24 12:26 ` [PATCH " Luis Machado
2023-11-24 12:59   ` Luis Machado
2023-11-28 11:34     ` Andrew Burgess
2023-11-28 12:07 ` Andrew Burgess
2023-12-01 14:28   ` Guinevere Larsen

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).