[2/3] Adjust gdb.threads/multiple-successive-infcall.exp for Cygwin

Message ID 20260505124210.1450101-3-pedro@palves.net
State New
Headers
Series Adjust a few gdb.threads/ testcases for Cygwin |

Commit Message

Pedro Alves May 5, 2026, 12:42 p.m. UTC
  Running gdb.threads/multiple-successive-infcall.exp on Cygwin results
in a series of cascading timeouts.  There are two problems:

 #1 - Cygwin/Windows start a few threads for their own use.  When you
      reach "main" with "(gdb) start", "info threads" will already
      show more than one thread.  The testcase spawns four threads
      with pthread_create, and it assumes that they get GDB thread ids
      2 through 5.  That is incorrect for Windows.

E.g.:

 (gdb) info threads
   Id   Target Id                                                      Frame
   1    Thread 4104.0x1e60 "multiple-successive-infcall"               0x00007fff3c44dbb4 in ntdll!ZwWaitForMultipleObjects () from /cygdrive/c/Windows/SYSTEM32/ntdll.dll
   2    Thread 4104.0x2490                                             0x00007fff3c450ad4 in ntdll!ZwWaitForWorkViaWorkerFactory ()
    from /cygdrive/c/Windows/SYSTEM32/ntdll.dll
   3    Thread 4104.0x2bdc                                             0x00007fff3c450ad4 in ntdll!ZwWaitForWorkViaWorkerFactory ()
    from /cygdrive/c/Windows/SYSTEM32/ntdll.dll
   4    Thread 4104.0x1714 "sig"                                       0x00007fff3c44d124 in ntdll!ZwReadFile () from /cygdrive/c/Windows/SYSTEM32/ntdll.dll
   5    Thread 4104.0x211c "multiple-successive-infcall"               0x00007fff3c44dbb4 in ntdll!ZwWaitForMultipleObjects () from /cygdrive/c/Windows/SYSTEM32/ntdll.dll
   6    Thread 4104.0x2070 "multiple-successive-infcall"               0x00007fff3c44dbb4 in ntdll!ZwWaitForMultipleObjects () from /cygdrive/c/Windows/SYSTEM32/ntdll.dll
   7    Thread 4104.0x1d3c "multiple-successive-infcall"               0x00007fff3c44dbb4 in ntdll!ZwWaitForMultipleObjects () from /cygdrive/c/Windows/SYSTEM32/ntdll.dll
 * 8    Thread 4104.0xa54 "multiple-successive-infcall"                thread_function (args=0x10040704c <thread_ids+12>)
     at /cygdrive/c/Users/alves/rocm/gdb/src/gdb/testsuite/gdb.threads/multiple-successive-infcall.c:75

 #2 - The test enables scheduler locking and switches to each thread
      in turn, and then does an infcall.  On Windows, system calls
      aren't interruptible like on Linux.  There's no concept of EINTR
      and syscall restart.  So if a thread is in a syscall, actually
      running kernel code, then an infcall will only complete once the
      syscall returns.  If the syscall is blocked waiting for another
      thread to unblock it, then the infcall never completes.  You can
      pause it with Ctrl-C, but you'll see that the thread hasn't
      executed any user space instruction.

      For example:

       (gdb) t 8
       [Switching to thread 8 (Thread 4104.0xa54)]
       #0  0x00007fff3c44dbb4 in ntdll!ZwWaitForMultipleObjects () from /cygdrive/c/Windows/SYSTEM32/ntdll.dll
       (gdb) p get_value ()
       * hang *

       * press ctrl-c *

       Thread 8 "multiple-successive-infcall" stopped.
       get_value () at /cygdrive/c/Users/alves/rocm/gdb/src/gdb/testsuite/gdb.threads/multiple-successive-infcall.c:38
       38      get_value ()
       The program being debugged was signaled while in a function called from GDB.
       GDB remains in the frame where the signal was received.
       To change this behavior use "set unwindonsignal on".
       Evaluation of the expression containing the function
       (get_value) will be abandoned.
       When the function is done executing, GDB will silently stop.
       (gdb) bt
       #0  get_value () at /cygdrive/c/Users/alves/rocm/gdb/src/gdb/testsuite/gdb.threads/multiple-successive-infcall.c:38
       #1  <function called from gdb>
       #2  0x00007fff3c44dbb4 in ntdll!ZwWaitForMultipleObjects () from /cygdrive/c/Windows/SYSTEM32/ntdll.dll
       #3  0x00007fff39cd1b40 in WaitForMultipleObjectsEx () from /cygdrive/c/Windows/System32/KERNELBASE.dll
       ...
       (gdb) si
       * hang *

    This results in failures like these:

       call get_value()
       FAIL: gdb.threads/multiple-successive-infcall.exp: thread=4: call inferior function (timeout)

This commit fixes this by adjusting the testcase to make it work on
Cygwin too.  First, it makes the .exp file figure out which threads
are the ones created by the testcase.  The current testcase also tries
the infcall on the main thread.  It's simpler to not worry about the
main thread being blocked in a syscall.  So we just no longer test the
syscall through that thread.  That's OK, because that was not
important for the original motivation for the testcase.

With this, the testcase now passes cleanly on Cygwin.

Change-Id: I5f69bafe6cecb83f18fb22ba7ee2368229fc4f9f
commit-id: 717edd68
---
 .../gdb.threads/multiple-successive-infcall.c |  10 +-
 .../multiple-successive-infcall.exp           | 103 ++++++++++++++----
 2 files changed, 90 insertions(+), 23 deletions(-)
  

Patch

diff --git a/gdb/testsuite/gdb.threads/multiple-successive-infcall.c b/gdb/testsuite/gdb.threads/multiple-successive-infcall.c
index 8e397266dff..aa3ebbd9332 100644
--- a/gdb/testsuite/gdb.threads/multiple-successive-infcall.c
+++ b/gdb/testsuite/gdb.threads/multiple-successive-infcall.c
@@ -41,8 +41,9 @@  get_value ()
       if (pthread_equal (threads[tid], pthread_self ()))
 	return thread_ids[tid];
     }
-  /* Value for the main thread.  */
-  return 1;
+
+  /* Should not get here.  */
+  return -1;
 }
 
 /* Return the nth Fibonacci number.  */
@@ -93,8 +94,9 @@  main (void)
   printf ("Spawning worker threads\n");
   for (int tid = 0; tid < THREADCOUNT; ++tid)
     {
-      /* Add 2 so the value maps to the debugger's thread identifiers.  */
-      thread_ids[tid] = tid + 2; /* prethreadcreationmarker */
+      /* Add 1 so the value maps to the testcase's thread
+	 identifiers.  */
+      thread_ids[tid] = tid + 1; /* prethreadcreationmarker */
       err = pthread_create (&threads[tid], NULL, thread_function,
 			    (void *) &thread_ids[tid]);
       if (err != 0)
diff --git a/gdb/testsuite/gdb.threads/multiple-successive-infcall.exp b/gdb/testsuite/gdb.threads/multiple-successive-infcall.exp
index 8d438673cf0..7ad841844bb 100644
--- a/gdb/testsuite/gdb.threads/multiple-successive-infcall.exp
+++ b/gdb/testsuite/gdb.threads/multiple-successive-infcall.exp
@@ -13,8 +13,7 @@ 
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
-# multiple-successive-infcall.exp -- Test if GDB can invoke functions on
-# multiple inferiors, one after the other.
+# Test invoking functions on multiple threads, one after the other.
 
 standard_testfile
 
@@ -29,31 +28,97 @@  if {![runto_main]} {
    return 0
 }
 
-# Ensure that each new thread is detected by GDB in the order that the
-# test case creates them, so the thread identifiers match between
-# test and test case.
+# Ensure that each new pthread is spawned one at a time so we can
+# extract its target ID, and not the ID of some auxiliary thread
+# spawned by the runtime.
 gdb_breakpoint [gdb_get_line_number "prethreadcreationmarker"]
 gdb_continue_to_breakpoint "prethreadcreationmarker"
-set after_new_thread_message "created new thread"
-foreach_with_prefix thread {5 4 3}  {
-  gdb_test_multiple "continue" "${after_new_thread_message}" {
-    -re "\\\[New Thread ${hex} \\\(LWP ${::decimal}\\\) \\\(id $decimal\\\)\\\].*${gdb_prompt}" {
-      pass "${after_new_thread_message}"
-    }
-    -re -wrap "\\\[New Thread $decimal\\.$decimal\\\ \\\(id $decimal\\\)]\r\n.*" {
-      pass $gdb_test_name
+set after_new_thread_message "detected new pthread"
+
+# List of the target ids detected.
+set pthread_list {}
+
+# The first 3 pthreads are detected here.
+foreach_with_prefix pthread {1 2 3}  {
+    gdb_test_multiple "continue" "${after_new_thread_message}" {
+	-re -wrap "\\\[New (Thread \[^\r\n\]+) \\\(id $decimal\\\)\\\]\r\n.*" {
+	    set thr $expect_out(1,string)
+	    verbose -log "detected pthread: $thr"
+	    lappend pthread_list $thr
+	    pass $gdb_test_name
+	}
     }
-  }
 }
 
+verbose -log "detected pthread list: $pthread_list"
+
 gdb_breakpoint [gdb_get_line_number "testmarker01"]
-gdb_continue_to_breakpoint "testmarker01"
+
+# Continue to breakpoint.  This detects that last pthread.  It also
+# ensures that no pthread ends up blocked in a syscall, which is
+# important for Windows -- the infcall wouldn't be able to complete
+# until the syscall returns, meaning, the infcall would deadlock.
+with_test_prefix "pthread 4" {
+    gdb_test_multiple "continue" "${after_new_thread_message}" {
+	-re "\\\[New (Thread \[^\r\n\]+) \\\(id $decimal\\\)\\\]\r\n" {
+	    set thr $expect_out(1,string)
+	    verbose -log "detected pthread: $thr"
+	    lappend pthread_list $thr
+	    pass $gdb_test_name
+	}
+    }
+}
+
+# Consume the breakpoint hit and the prompt.
+gdb_test_multiple "" "stop at testmarker01" {
+    -re -wrap "hit Breakpoint .* testmarker01 .*" {
+	pass $gdb_test_name
+    }
+}
+
 gdb_test_no_output "set scheduler-locking on"
 gdb_test "show scheduler-locking" \
   "Mode for locking scheduler during execution is \"on\"."
 
-foreach_with_prefix thread {5 4 3 2 1}  {
-  gdb_test "thread ${thread}" "Switching to .*"
-  gdb_test "call get_value()" "= ${thread}" \
-      "call inferior function"
+set thread_count [get_valueof "" "\$_inferior_thread_count" 0]
+
+# pthread id to be used in test messages in the loop below.
+set testing_pthread 0
+
+for {set thread 1} {$thread <= $thread_count} {incr thread} {
+
+    # 1 if found, 0 if not found, -1 if the test failed.
+    set found -1
+    gdb_test_multiple "thread ${thread}" "extract thread target id" {
+	-re -wrap "Switching to thread $decimal \\((Thread \[^\r\n\]+)\\)\\\]\r\n.*" {
+	    set thr $expect_out(1,string)
+	    verbose -log "extracted: $thr"
+	    if {[lsearch -exact $pthread_list $thr] != -1} {
+		set found 1
+	    } else {
+		set found 0
+	    }
+	}
+    }
+    if {$found == -1} {
+	# gdb_test_multiple already issued a FAIL above.  No point in
+	# continuing.
+	return
+    } elseif {$found == 0} {
+	# Not recognized as a testcase pthread, so it must be a thread
+	# spawned by the runtime or the OS.  Don't use it for testing
+	# the infcall, as the thread may be blocked in a syscall -- on
+	# Windows, the infcall wouldn't be able to complete until the
+	# syscall returns, meaning, the infcall would deadlock.
+	verbose -log "not a known pthread"
+	continue
+    } else {
+	incr testing_pthread
+	with_test_prefix "pthread $testing_pthread" {
+	    gdb_test "call get_value()" "= $testing_pthread" \
+		"call inferior function"
+	}
+    }
 }
+
+gdb_assert {$testing_pthread == 4} "tested all pthreads"