Compare commits

...

35 Commits

Author SHA1 Message Date
Pedro Alves
41330f5d32 Fix process-dies-after-detach
- Need to flush pending kernel-side events

 - I realized that while we're detaching, we want to pass exceptions
   down to the inferior with DBG_EXCEPTION_NOT_HANDLED, instead of
   losing them.  I ended up reusing a bit of code from the Linux
   target.

Change-Id: Ifaa96b4a41bb83d868079af4d47633715c0e1940
2024-05-17 20:21:48 +01:00
Pedro Alves
5223cde0d5 Mention Windows non-stop support in NEWS
Add a note to gdb/NEWS mentioning Windows non-stop support.

Change-Id: Id0e28525c06e57120c47b07f978581d1627d70f3
2024-05-17 20:21:45 +01:00
Pedro Alves
7d6c04c580 Windows gdb: Watchpoints while running (internal vs external stops)
Teach the Windows target to temporarily pause all threads when we
change the debug registers for a watchpoint.  Implements the same
logic as Linux uses:

    ~~~
      /*                             (...) if threads are running when the
         mirror changes, a temporary and transparent stop on all threads
         is forced so they can get their copy of the debug registers
         updated on re-resume.   (...)  */
    ~~~

On Linux, we send each thread a SIGSTOP to step them.  On Windows,
SuspendThread itself doesn't cause any asynchronous debug event to be
reported.  However, we've implemented windows_nat_target::stop such
that it uses SuspendThread, and then queues a pending GDB_SIGNAL_0
stop on the thread.  That results in a user-visible stop, while here
we want a non-user-visible stop.  So what we do is re-use that
windows_nat_target::stop stopping mechanism, but add an external vs
internal stopping kind distinction.  An internal stop results in
windows_nat_target::wait immediately re-resuming the thread.

Note we don't make the debug registers poking code SuspendThread ->
write debug registers -> ContinueThread itself, because SuspendThread
is actually asynchronous and may take a bit to stop the thread (a
following GetThreadContext blocks until the thread is actually
suspended), and, there will be several debug register writes when a
watchpoint is set, because we have to set all of DR0, DR1, DR2, DR3,
and DR7.  Defering the actualy writes to ::wait avoids a bunch of
SuspendThread/ResumeThread sequences, so in principle should be
faster.

Change-Id: I39c2492c7aac06d23ef8f287f4afe3747b7bc53f
2024-05-17 20:14:26 +01:00
Pedro Alves
650daf1818 Windows gdb: Add non-stop support
This patch adds non-stop support to the native Windows target.

This is made possible by the ContinueDebugEvent DBG_REPLY_LATER flag:

https://learn.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-continuedebugevent

  Supported in Windows 10, version 1507 or above, this flag causes
  dwThreadId to replay the existing breaking event after the target
  continues. By calling the SuspendThread API against dwThreadId, a
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  debugger can resume other threads in the process and later return to
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  the breaking.
  ^^^^^^^^^^^^

The patch adds a new comment section in gdb/windows-nat.c providing an
overall picture of how all-stop / non-stop work.

Without DBG_REPLY_LATER, if we SuspendThread the thread, and then
immediately ContinueDebugThread(DBG_CONTINUE) before getting back to
the prompt, we could still have non-stop mode working, however, then
users wouldn't have a chance to decide whether to pass the signal to
the inferior the next time they resume the program, as that is done by
passing DBG_EXCEPTION_NOT_HANDLED to ContinueDebugEvent, and that has
already been called.

The patch adds that DBG_REPLY_LATER handling, and also adds support
for target_stop, so the core can pause threads at its discretion.
This pausing does not use the same mechanisms used in
windows_nat_target::interrupt, as those inject a new thread in the
inferior.  Instead, for each thread the core wants paused, it uses
SuspendThread, and enqueues a pending GDB_SIGNAL_0 stop on the thread.

Since DBG_REPLY_LATER only exists on Windows 10 and later, we only
enable non-stop mode on Windows 10 and later.

Since having the target backend work in non-stop mode adds features
compared to old all-stop mode (signal/exception passing/suppression is
truly per-thread), this patch also switches the backend to do
all-stop-on-top-of-non-stop, by having
windows_nat_target::always_non_stop_p return true if non-stop mode is
possible.  To be clear, this just changes how the backend works in
coordination with infrun.  The user-visible mode default mode is still
all-stop.  The difference is that infrun is responsible for stopping
all threads when needed, instead of the backend (actually the kernel)
always doing that before reporting an event to infrun.

There is no displaced stepping support, but that's "just" a missed
optimization to be done later.

Cygwin signals handling was a major headache, but I managed to get it
working.  See the "Cygwin signals" description section I added at the
top of windows-nat.c.

Change-Id: Id71aef461c43c244120635b5bedc638fe77c31fb
2024-05-17 20:14:23 +01:00
Pedro Alves
6b25d11b01 Windows gdb+gdbserver: Check whether DBG_REPLY_LATER is available
Per
<https://learn.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-continuedebugevent>,
DBG_REPLY_LATER is "Supported in Windows 10, version 1507 or above, ..."

Since we support versions of Windows older than 10, we need to know
whether DBG_REPLY_LATER is available.  And we need to know this before
starting any inferior.

This adds a function that probes for support (and caches the result),
by trying to call ContinueDebugEvent on pid=0,tid=0 with
DBG_REPLY_LATER, and inspecting the resulting error.

Suggested-by: Hannes Domani <ssbssa@yahoo.de>
Suggested-by: Eli Zaretskii <eliz@gnu.org>
Change-Id: Ia27b981aeecaeef430ec90cebc5b3abdce00449d
2024-05-10 11:26:41 +01:00
Pedro Alves
af7fc7ff9e Windows gdb: Avoid writing debug registers if watchpoint hit pending
Several watchpoint-related testcases, such as
gdb.threads/watchthreads.exp for example, when tested with the backend
in non-stop mode, exposed an interesting detail of the Windows debug
API that wasn't considered before.  The symptom observed is spurious
SIGTRAPs, like:

  Thread 1 "watchthreads" received signal SIGTRAP, Trace/breakpoint trap.
  0x00000001004010b1 in main () at .../src/gdb/testsuite/gdb.threads/watchthreads.c:48
  48              args[i] = 1; usleep (1); /* Init value.  */

After a good amount of staring at logs and headscratching, I realized
the problem:

 #0 - It all starts in the fact that multiple threads can hit an event
      at the same time.  Say, a watchpoint for thread A, and a
      breakpoint for thread B.

 #1 - Say, WaitForDebugEvent reports the breakpoint hit for thread B
      first, then GDB for some reason decides to update debug
      registers, and continue.  Updating debug registers means writing
      the debug registers to _all_ threads, with SetThreadContext.

 #2 - WaitForDebugEvent reports the watchpoint hit for thread A.
      Watchpoint hits are reported as EXCEPTION_SINGLE_STEP.

 #3 - windows-nat checks the Dr6 debug register to check if the step
      was a watchpoint or hardware breakpoint stop, and finds that Dr6
      is completely cleared.  So windows-nat reports a plain SIGTRAP
      (given EXCEPTION_SINGLE_STEP) to the core.

 #4 - Thread A was not supposed to be stepping, so infrun reports the
      SIGTRAP to the user as a random signal.

The strange part is #3 above.  Why was Dr6 cleared?

Turns out what (at least in Windows 10 & 11), writing to _any_ debug
register has the side effect of clearing Dr6, even if you write the
same values the registers already had, back to the registers.

I confirmed it clearly by adding this hack to GDB:

  if (th->context.ContextFlags == 0)
    {
      th->context.ContextFlags = CONTEXT_DEBUGGER_DR;

      /* Get current values of debug registers.  */
      CHECK (GetThreadContext (th->h, &th->context));

      DEBUG_EVENTS ("For 0x%x (once),  Dr6=0x%llx", th->tid, th->context.Dr6);

      /* Write debug registers back to thread, same values,
	 and re-read them.  */
      CHECK (SetThreadContext (th->h, &th->context));
      CHECK (GetThreadContext (th->h, &th->context));

      DEBUG_EVENTS ("For 0x%x (twice), Dr6=0x%llx", th->tid, th->context.Dr6);
    }

Which showed Dr6=0 after the write + re-read:

  [windows events] fill_thread_context: For 0x6a0 (once),  Dr6=0xffff0ff1
  [windows events] fill_thread_context: For 0x6a0 (twice), Dr6=0x0

This commit fixes the issue by detecting that a thread has a pending
watchpoint hit to report (Dr6 has interesting bits set), and if so,
avoid mofiying any debug register.  Instead, let the pending
watchpoint hit be reported by WaitForDebugEvent.  If infrun did want
to modify watchpoints, it will still be done when the thread is
eventually re-resumed after the pending watchpoint hit is reported.
(infrun knows how to gracefully handle the case of a watchpoint hit
for a watchpoint that has since been deleted.)

Change-Id: I21a3daa9e34eecfa054f0fea706e5ab40aabe70a
2024-05-10 11:26:16 +01:00
Pedro Alves
acd3803fa9 windows_per_inferior::continue_one_thread, unify WoW64/non-WoW64 paths
Consolidate the WoW64 & non-WoW64 paths in
windows_per_inferior::continue_one_thread to avoid code duplication.

The next patch will add more code to this function, and this
unification avoids writing that new code twice.

Change-Id: I794aadb412a3b8081212e4acf2af80d3edba7392
2024-05-10 11:26:01 +01:00
Pedro Alves
13c13b58d2 Windows gdb: cygwin_set_dr => windows_set_dr, etc.
The Windows backend functions that manipulate the x86 debug registers
are called "cygwin_foo", which is outdated, because native MinGW gdb
also uses those functions, they are not Cygwin-specific.  Rename them
to "windows_foo" to avoid confusion.

Change-Id: I46df3b44f5272adadf960da398342a3cbdb98533
2024-05-10 11:26:01 +01:00
Pedro Alves
7ad98390f7 Windows gdb: Change serial_event management
windows_nat_target::windows_continue, when it finds a resumed thread
that has a pending event, does:

	  /* There's no need to really continue, because there's already
	     another event pending.  However, we do need to inform the
	     event loop of this.  */
	  serial_event_set (m_wait_event);
	  return TRUE;

If we have more than one pending event ready to be consumed, and,
windows_nat_target::wait returns without calling
windows_nat_target::windows_continue, which it will with the non-stop
support in the following patch, then we will miss waking up the event
loop.

This patch makes windows-nat.c manage the serial_event similarly to
how linux-nat.c does it.  Clear it on entry to
windows_nat_target::wait, and set it if there may be more events to
process.  With this, there's no need to set it from
windows_nat_target::wait_for_debug_event_main_thread, so the patch
also makes us not do it.

Change-Id: I44e1682721aa4866f1dbb052b3cfb4870fb13579
2024-05-10 11:26:01 +01:00
Pedro Alves
467b267632 Windows gdb+gdbserver: Eliminate struct pending_stop
After the previous patches, struct pending_stop only contains one
field.  So move that field into the windows_thread_info structure
directly, and eliminate struct pending_stop.

Change-Id: I7955884b3f378d8b39b908f6252d215f6568b367
2024-05-10 11:26:00 +01:00
Pedro Alves
a0bc4291f1 Windows gdb+gdbserver: Share $_siginfo reading code
Both GDB and GDBserver have similar code to read the $_siginfo data.
This patch moves the bulk of it to gdb/nat/windows-nat.c so it can be
shared.

Change-Id: I47fc0d3323be5b6f6fcfe912b768051a41910666
2024-05-10 11:26:00 +01:00
Pedro Alves
41f9f2d609 Add backpointer from windows_thread_info to windows_process_info
The next patch will move some duplicated code in gdb and gdbserver to
gdb/nat/windows-nat.c, where it would be convenient to get at the
Windows process info of a given Windows thread info, from within a
windows_thread_info method.

I first thought of passing down the windows_process_info pointer as
argument to the windows_thread_info method, but that looked a bit odd.
I think it looks better to just add a back pointer, so that's what
this patch does.  The following patch will then add a use of it.

I suspect this will help moving more duplicated code to
gdb/nat/windows-nat.c in the future, too.

Change-Id: I47fc0d3323be5b6f6fcfe912b768051a41910666
2024-05-10 11:26:00 +01:00
Pedro Alves
0c6b048236 Windows gdb+gdbserver: Make siginfo_er per-thread state
With non-stop mode support, each thread has its own "last event", and
so printing $_siginfo should print the siginfo of the selected thread.
Likewise, with all-stop and scheduler-locking.

This patch reworks the siginfo functions in gdb/windows-nat.c and
gdbserver/win32-low.cc to reuse the exception record already saved
within each thread's 'last_event' field.

Here's an example of what you'll see after the whole non-stop series:

  (gdb) thread apply all p -pretty -- $_siginfo

  Thread 3 (Thread 2612.0x1470):
  $1 = {
    ExceptionCode = DBG_CONTROL_C,
    ExceptionFlags = 0,
    ExceptionRecord = 0x0,
    ExceptionAddress = 0x7ffd0583e929 <KERNELBASE!EncodeRemotePointer+8249>,
    NumberParameters = 0,
    {
      ExceptionInformation = {0 <repeats 15 times>},
      AccessViolationInformation = {
	Type = READ_ACCESS_VIOLATION,
	Address = 0x0
      }
    }
  }

  Thread 2 (Thread 2612.0x1704):
  $2 = {
    ExceptionCode = SINGLE_STEP,
    ExceptionFlags = 0,
    ExceptionRecord = 0x0,
    ExceptionAddress = 0x7ffd080ad6e4 <ntdll!ZwDelayExecution+20>,
    NumberParameters = 0,
    {
      ExceptionInformation = {0 <repeats 15 times>},
      AccessViolationInformation = {
	Type = READ_ACCESS_VIOLATION,
	Address = 0x0
      }
    }
  }

  Thread 1 (Thread 2612.0x434):
  $3 = {
    ExceptionCode = BREAKPOINT,
    ExceptionFlags = 0,
    ExceptionRecord = 0x0,
    ExceptionAddress = 0x7ff6f691174c <main+185>,
    NumberParameters = 1,
    {
      ExceptionInformation = {0 <repeats 15 times>},
      AccessViolationInformation = {
	Type = READ_ACCESS_VIOLATION,
	Address = 0x0
      }
    }
  }
  (gdb)

This was in non-stop mode, and the program originally had two threads.
Thread 1 stopped for a breakpoint, then thread 2 was manually
interrupted/paused and then single-stepped.  And then I typed Ctrl-C
in the inferior's terminal, which made Windows inject thread 3 in the
inferior, and report a DBG_CONTROL_C exception for it.

Change-Id: I5d4f1b62f59e8aef3606642c6524df2362b0fb7d
2024-05-10 11:26:00 +01:00
Pedro Alves
f5e5342583 Windows gdb+gdbserver: Make last_sig per-thread state
With non-stop mode, each thread is controlled independently of the
others, and each thread has its own independent reason for its last
stop.

Thus, any thread-specific state that is currently per-process must be
converted to per-thread state.

This patch converts windows_process_info::last_sig to per-thread
state, moving it to windows_thread_info instead.

This adjusts both native gdb and gdbserver.

Change-Id: Ice09a5d932c912210608d5af25e1898f823e3c99
2024-05-10 11:26:00 +01:00
Pedro Alves
f428cac615 Windows gdb+gdbserver: Make current_event per-thread state
With non-stop mode, each thread is controlled independently of the
others, and each thread has its own independent reason for its last
stop.

Thus, any thread-specific state that is currently per-process must be
converted to per-thread state.

This patch converts windows_process_info::current_event, moving it to
windows_thread_info instead, renamed to last_event.

Since each thread will have its own copy of its last Windows debug
event, we no longer need the same information stored in struct
pending_stop.

Since windows_process.current_event no longer exists, we need to pass
the current event as parameter to a number of methods.

This adjusts both native gdb and gdbserver.

Change-Id: Ice09a5d932c912210608d5af25e1898f823e3c99
2024-05-10 11:26:00 +01:00
Pedro Alves
11adfeba32 Windows gdbserver: Eliminate soft-interrupt mechanism
I noticed that faked_breakpoint is write only.  And then I hacked
win32_process_target::request_interrupt to force it to stop threads
using the soft_interrupt_requested mechanism (which suspends threads,
and then fakes a breakpoint event in the main thread), and saw that it
no longer works -- gdbserver crashes accessing a NULL current_thread,
because fake_breakpoint_event does not switch to a thread.

This code was originally added for Windows CE, as neither
GenerateConsoleCtrlEvent nor DebugBreakProcess worked there.

We nowadays require Windows XP or later, and XP has DebugBreakProcess.

The soft_interrupt_requested mechanism has other problems, like for
example faking the event in the main thread, even if that thread was
previously stopped, due to scheduler-locking.

A following patch will add a similar mechanism stopping all threads
with SuspendThread to native GDB, for non-stop mode, which doesn't
have these problems.  It's different enough from this old code that I
think we should just rip the old code out, and reimplement it from
scratch (based on gdb's version) when we need it.

Change-Id: I89e98233a9c40c6dcba7c8e1dacee08603843fb1
2024-05-10 11:25:59 +01:00
Pedro Alves
5ad78cc624 Windows gdb: Enable "set scheduler-locking on"
Surprisingly (to me), enabling scheduler locking on Windows currently
fails:

 (gdb)
 set scheduler-locking on
 Target 'native' cannot support this command.

The backend itself does support scheduler-locking.  This patch
implements windows_nat_target::get_thread_control_capabilities so that
the core knows schedlocking works for this target.

Change-Id: Ie762d3768fd70e4ac398c8bcc03c3213bfa26a6a
2024-05-10 11:25:59 +01:00
Pedro Alves
02ff02e05f Windows gdbserver: Fix scheduler-locking
This rewrites win32_process_target::resume such that scheduler-locking
is implemented properly.

It also uses the new get_last_debug_event_ptid function to avoid
considering passing a signal to the wrong thread, like done for the
native side in a previous patch.

Note this code/comment being removed:

 -    /* Yes, we're ignoring resume_info[0].thread.  It'd be tricky to make
 -       the Windows resume code do the right thing for thread switching.  */
 -    tid = windows_process.current_event.dwThreadId;

This meant that scheduler-locking currently is broken badly unless you
stay in the thread that last reported an event.  If you switch to a
different thread from the one that last reported an event and step,
you get a spurious SIGTRAP in the thread that last reported a stop,
not the one that you tried to step:

 (gdb) t 1
 [Switching to thread 1 (Thread 3908)]
 #0  0x00007fffc768d6e4 in ntdll!ZwDelayExecution () from target:C:/Windows/SYSTEM32/ntdll.dll
 (gdb) set scheduler-locking on
 (gdb) set disassemble-next-line
 "on", "off" or "auto" expected.
 (gdb) set disassemble-next-line on
 (gdb) frame
 #0  0x00007fffc768d6e4 in ntdll!ZwDelayExecution () from target:C:/Windows/SYSTEM32/ntdll.dll
 => 0x00007fffc768d6e4 <ntdll!ZwDelayExecution+20>:       c3                      ret
 (gdb) si

 Thread 3 received signal SIGTRAP, Trace/breakpoint trap.
 [Switching to Thread 2660]
 0x00007fffc4e4e92e in KERNELBASE!EncodeRemotePointer () from target:C:/Windows/System32/KernelBase.dll
 => 0x00007fffc4e4e92e <KERNELBASE!EncodeRemotePointer+8254>:    eb 78                   jmp    0x7fffc4e4e9a8 <KERNELBASE!EncodeRemotePointer+8376>
 (gdb)

Note how we switched to thread 1, stepped, and GDBserver still stepped
thread 3...  This is fixed by this patch.  We now get:

  (gdb) info threads
    Id   Target Id         Frame
    1    Thread 920        0x00007ffe0372d6e4 in ntdll!ZwDelayExecution () from target:C:/Windows/SYSTEM32/ntdll.dll
    2    Thread 8528       0x00007ffe03730ad4 in ntdll!ZwWaitForWorkViaWorkerFactory () from target:C:/Windows/SYSTEM32/ntdll.dll
    3    Thread 3128       0x00007ffe03730ad4 in ntdll!ZwWaitForWorkViaWorkerFactory () from target:C:/Windows/SYSTEM32/ntdll.dll
  * 4    Thread 7164       0x00007ffe0102e929 in KERNELBASE!EncodeRemotePointer () from target:C:/Windows/System32/KernelBase.dll
    5    Thread 8348       0x00007ffe0372d6e4 in ntdll!ZwDelayExecution () from target:C:/Windows/SYSTEM32/ntdll.dll
    6    Thread 2064       0x00007ffe0372d6e4 in ntdll!ZwDelayExecution () from target:C:/Windows/SYSTEM32/ntdll.dll
  (gdb) t 1
  [Switching to thread 1 (Thread 920)]
  #0  0x00007ffe0372d6e4 in ntdll!ZwDelayExecution () from target:C:/Windows/SYSTEM32/ntdll.dll
  (gdb) set scheduler-locking on
  (gdb) si
  0x00007ffe0372d6e4 in ntdll!ZwDelayExecution () from target:C:/Windows/SYSTEM32/ntdll.dll
  (gdb) si
  0x00007ffe00f9b44e in SleepEx () from target:C:/Windows/System32/KernelBase.dll
  (gdb) si
  0x00007ffe00f9b453 in SleepEx () from target:C:/Windows/System32/KernelBase.dll

I.e., we kept stepping the right thread, thread 1.

Note we stopped again at 0x00007ffe0372d6e4 the first time (same PC
the thread already was at before the first stepi) because the thread
had been stopped at a syscall, so that's normal:

 (gdb) disassemble
 Dump of assembler code for function ntdll!ZwDelayExecution:
    0x00007ffe0372d6d0 <+0>:     mov    %rcx,%r10
    0x00007ffe0372d6d3 <+3>:     mov    $0x34,%eax
    0x00007ffe0372d6d8 <+8>:     testb  $0x1,0x7ffe0308
    0x00007ffe0372d6e0 <+16>:    jne    0x7ffe0372d6e5 <ntdll!ZwDelayExecution+21>
    0x00007ffe0372d6e2 <+18>:    syscall
 => 0x00007ffe0372d6e4 <+20>:    ret

Change-Id: I44f4fe4cb98592517569c6716b9d189f42db25a0
2024-05-10 11:25:59 +01:00
Pedro Alves
4ae1d3c664 Windows gdb: Can't pass signal to thread other than last stopped thread
Passing a signal to a thread other than the one that last reported an
event would be later possible with DBG_REPLY_LATER and a backend
working in non-stop mode.

With an all-stop backend that isn't possible, so at least don't
incorrectly consider passing DBG_EXCEPTION_NOT_HANDLED if the thread
that we're going to call ContinueDebugEvent for is not the one that
the user issued "signal SIG" on.

Change-Id: I32915623b5036fb902f9830ce2d6f0b1ccf1f5cf
2024-05-10 11:25:59 +01:00
Pedro Alves
3a84bb42a3 Windows gdb+gdbserver: Introduce get_last_debug_event_ptid
This will be used in subsequent patches to avoid using
DBG_EXCEPTION_NOT_HANDLED on the wrong thread.

Change-Id: I32915623b5036fb902f9830ce2d6f0b1ccf1f5cf
2024-05-10 11:25:59 +01:00
Pedro Alves
3f6f23ee4e Windows gdb+gdbserver: Elim desired_stop_thread_id / rework pending_stops
windows_process.desired_stop_thread_id doesn't work for non-stop, as
in that case every thread will have its own independent
WaitForDebugEvent event.

Instead, detect whether we have been reported a stop that was not
supposed to be reported by simply checking whether the thread that is
reporting the event is suspended.  This is now easilly possible since
each thread's suspend state is kept in sync with whether infrun wants
the thread executing or not.

windows_process.desired_stop_thread_id was also used as thread to pass
to windows_continue.  However, we don't really need that either.
windows_continue is used to update the thread's registers, unsuspend
them, and then finally call ContinueDebugEvent.  In most cases, we
only need the ContinueDebugEvent step, so we can convert the
windows_continue calls to continue_last_debug_event_main_thread calls.
The exception is when we see a thread creation event -- in that case,
we need to update the debug registers of the new thread.  We can use
continue_one_thread for that.

Since the pending stop is now stored in windows_thread_info,
get_windows_debug_event needs to avoid reaching the bottom code if
there's no thread associated with the event anymore (i.e.,
EXIT_THREAD_DEBUG_EVENT / EXIT_PROCESS_DEBUG_EVENT).

I considered whether it would be possible to keep the pending_stop
handling code shared in gdb/nat/windows-nat.c, in this patch and
throughout the series, but I conclused that it isn't worth it, until
gdbserver is taught about async and non-stop as well.

The pending_stop struct will eventually be eliminated later down the
series.

Change-Id: Ib7c8e8d16edc0900b7c411976c5d70cf93931c1c
2024-05-10 11:25:59 +01:00
Pedro Alves
734404845d Windows gdb: Pending stop and current_event
I noticed that windows_nat_target::get_windows_debug_event does not
copy the event recorded in pending stop to
windows_process.current_event.  This seems like an oversight.  The
equivalent code in gdbserver/win32-low.cc does copy it.

This change will become moot later in the series, but I figure its
still clearer to correct the buglet as preparatory patch.

Change-Id: Ic8935d854cf67a3a3c4edcbc1a1e8957b800d907
2024-05-10 11:25:59 +01:00
Pedro Alves
8055ea0bb6 Windows gdb: Factor code out of windows_nat_target::windows_continue
This factors some code out of windows_nat_target::windows_continue
into a new windows_continue_one function.  This will make the
following patch easier to read (as well as the resulting code itself).

Change-Id: I14a0386b1b8b03015e86273060af173b5130e375
2024-05-10 11:25:58 +01:00
Pedro Alves
49fcf1ccc7 Windows gdb: Introduce windows_continue_flags
windows_continue already has two boolean parameters:

  (..., int killed, bool last_call = false)

A patch later in the series would need a third.  Instead, convert
windows_continue to use an optional enum-flags parameter instead of
multiple booleans.

Change-Id: I17c4d8a12b662190f972c380f838cb3317bd2e1e
2024-05-10 11:25:58 +01:00
Pedro Alves
44fff407b7 Windows gdb: Introduce continue_last_debug_event_main_thread
We have code using do_synchronously to call continue_last_debug_event,
and later patches in the series would need to add the same code in few
more places.  Factor it out to a continue_last_debug_event_main_thread
function so these other places in future patches can just call it.

Change-Id: I945e668d2b3daeb9de968219925a7b3c7c7ce9ed
2024-05-10 11:25:42 +01:00
Pedro Alves
552616cbb7 Windows gdb+gdbserver: Move suspending thread to when returning event
The current code suspends a thread just before calling
GetThreadContext.  You can only call GetThreadContext if the thread is
suspended.  But, after WaitForDebugEvent, all threads are implicitly
suspended.  So I don't think we even needed to call SuspendThread
explictly at all before our GetThreadContext calls.

However, suspending threads when we're about to present a stop to gdb
simplifies adding non-stop support later.  This way, the windows
SuspendThread state corresponds to whether a thread is suspended or
resumed from the core's perspective.  Curiously, I noticed that Wine's
winedbg does something similar:
234943344f/programs/winedbg/gdbproxy.c (L651)

This makes it much easier to reason about a thread's suspend state,
and simplifies adding non-stop mode later on.

Change-Id: Ifd6889a8afc041fad33cd1c4500e38941da6781b
2024-05-08 00:39:56 +01:00
Pedro Alves
87e727c0c6 Windows gdb: Simplify windows_nat_target::wait
The logic in windows_nat_target::wait, where we decide what to do
depending on the result from get_windows_debug_event is harder to
grasp than it looks.

It is not easy to tell what should happen when in async mode
get_windows_debug_event returns that there's no event to process.

And then, if get_windows_debug_event returns null_ptid /
TARGET_WAITKIND_SPURIOUS, then we need to issue a ContinueDebugEvent.

There's also this comment in windows_nat_target::wait, which we're not
really implementing today:

~~~~
  /* We loop when we get a non-standard exception rather than return
     with a SPURIOUS because resume can try and step or modify things,
     which needs a current_thread->h.  But some of these exceptions mark
     the birth or death of threads, which mean that the current thread
     isn't necessarily what you think it is.  */
~~~~

This patch changes things a bit so that the code is more obvious:

 - look at the status kind, instead of ptid_t.

 - add an explicit early return case for no-event.

 - add an explicit case for TARGET_WAITKIND_SPURIOUS.

 - with those, we no longer need to handle the case of find_thread not
   finding a thread, so we can drop one indentation level.

Change-Id: I76c41762e1f893a7ff23465856ccf6a44af1f0e7
2024-05-08 00:39:56 +01:00
Pedro Alves
f99b2890dd Windows gdb+gdbserver: Eliminate windows_process_info::thread_rec
After the previous patches, thread_rec is no longer called anywhere.
Delete it.

Change-Id: Ib14e5807fc427e1c3c4a393a9ea7b36b6047a2d7
2024-05-08 00:39:56 +01:00
Pedro Alves
b67852992b Windows gdb+gdbserver: Eliminate DONT_SUSPEND
There's a single call to thread_rec(DONT_SUSPEND), in
windows_process_info::handle_exception.

In GDB, the windows-nat.c thread_rec implementation avoids actually
calling SuspendThread on the event thread by doing:

               th->suspended = -1;

I am not exactly sure why, but it kind of looks like it is done as an
optimization, avoiding a SuspendThread call?  It is probably done for
the same reason as the code touched in the previous patch avoided
suspending the event thread.

This however gets in the way of non-stop mode, which will really want
to SuspendThread the event thread for DBG_REPLY_LATER.

In gdbserver's thread_rec implementation DONT_SUSPEND is ignored, and
thread_rec actually always suspends, which really suggests that
SuspendThread on the event thread is really not a problem.  I really
can't imagine why it would be.

DONT_SUSPEND invalidates the thread's context, but there is no need to
invalidate the context when we get an event for a thread, because we
invalidate it when we previously resumed the thread.

So, we can just remove the thread_rec call from
windows_process_info::handle_exception.  That's what this patch does.

Change-Id: I0f328542bda6d8268814ca1ee4ae7a478098ecf2
2024-05-08 00:39:55 +01:00
Pedro Alves
c151e663fa Windows gdb+gdbserver: Eliminate thread_rec(INVALIDATE_CONTEXT) calls
Replace thread_rec(INVALIDATE_CONTEXT) calls with find_thread, and
invalidate_context / suspend calls in the spots that might need those.

I don't know why does the INVALIDATE_CONTEXT implementation in GDB
avoid suspending the event thread:

	case INVALIDATE_CONTEXT:
	  if (ptid.lwp () != current_event.dwThreadId)
	    th->suspend ();

Checks for a global "current_event" get in the way of non-stop support
later in the series, as each thread will have its own "last debug
event".  Regardless, it should be fine to suspend the event thread.
As a data point, the GDBserver implementation always suspends.  So
this patch does not try to avoid suspending the event thread on the
native side either.

Change-Id: I8d2f0a749d23329956e62362a7007189902dddb5
2024-05-08 00:39:55 +01:00
Pedro Alves
78ef7c48d1 Windows gdb: Eliminate reload_context
We don't need reload_context, because we can get the same information
out of th->context.ContextFlags.  If ContextFlags is zero, then we
need to fetch the context out of the inferior thread.  This is what
gdbserver does too.

Change-Id: Ied566037c81383414c46c77713bdd1aec6377b23
2024-05-08 00:39:55 +01:00
Pedro Alves
ea80c32fa7 Windows gdb: handle_output_debug_string return type
handle_output_debug_string returns a Windows thread id, so it should
return a DWORD instead of an int.

Change-Id: Icbd071a1a37de8a0fc8918bd13254a8d40311e32
2024-05-08 00:39:55 +01:00
Pedro Alves
446babcd70 Windows gdb+gdbserver: New find_thread, replaces thread_rec(DONT_INVALIDATE_CONTEXT)
The goal of the next few patches is to eliminate thread_rec
completely.  This is the first patch in that effort.

thread_rec(DONT_INVALIDATE_CONTEXT) is really just a thread lookup
with no side effects, so this adds a find_thread function that lets
you do that.

Change-Id: Ie486badce00e234b10caa478b066c34537103e3f
2024-05-08 00:39:55 +01:00
Pedro Alves
cefdca26f9 Windows gdb: Eliminate global current_process.dr[8] global
current_process.dr needs to be per-thread for non-stop.  Actually, it
doesn't even need to exist at all.  We have the x86_debug_reg_state
recording intent, and then the
cygwin_get_dr/cygwin_get_dr6/cygwin_get_dr7 functions are registered
as x86_dr_low_type vector functions, so they should return the current
value in the inferior's registers.  See this comment in x86-dregs.c:

~~~
  /* In non-stop/async, threads can be running while we change the
     global dr_mirror (and friends).  Say, we set a watchpoint, and
     let threads resume.  Now, say you delete the watchpoint, or
     add/remove watchpoints such that dr_mirror changes while threads
     are running.  On targets that support non-stop,
     inserting/deleting watchpoints updates the global dr_mirror only.
     It does not update the real thread's debug registers; that's only
     done prior to resume.  Instead, if threads are running when the
     mirror changes, a temporary and transparent stop on all threads
     is forced so they can get their copy of the debug registers
     updated on re-resume.  Now, say, a thread hit a watchpoint before
     having been updated with the new dr_mirror contents, and we
     haven't yet handled the corresponding SIGTRAP.  If we trusted
     dr_mirror below, we'd mistake the real trapped address (from the
     last time we had updated debug registers in the thread) with
     whatever was currently in dr_mirror.  So to fix this, dr_mirror
     always represents intention, what we _want_ threads to have in
     debug registers.  To get at the address and cause of the trap, we
     need to read the state the thread still has in its debug
     registers.

     In sum, always get the current debug register values the current
     thread has, instead of trusting the global mirror.  If the thread
     was running when we last changed watchpoints, the mirror no
     longer represents what was set in this thread's debug
     registers.  */
~~~

This patch makes the Windows native target follow that model as well.

I don't understand why would windows_nat_target::resume want to call
SetThreadContext itself.  That duplicates things as it is currently
worrying about setting the debug registers as well.  windows_continue
also does that, and windows_nat_target::resume always calls it.  So
this patch simplifies windows_nat_target::resume too.

Change-Id: I2fe460341b598ad293ea60d5f702b10cefc30711
2024-05-08 00:39:54 +01:00
Pedro Alves
b93d04b777 Windows gdb: Dead code in windows_nat_target::do_initial_windows_stuff
In windows_nat_target::do_initial_windows_stuff, there's no point in
setting windows_process.current_event.dwProcessId.  It's a nop, given
the following memset.

Change-Id: I2fe460341b598ad293ea60d5f702b10cefc30711
2024-05-08 00:39:54 +01:00
9 changed files with 1797 additions and 901 deletions

View File

@@ -9,6 +9,9 @@
* Building GDB and GDBserver now requires a C++17 compiler.
For example, GCC 9 or later.
* The Windows native target now supports non-stop mode. This feature
requires Windows 10 or later.
* GDB index now contains information about the main function. This speeds up
startup when it is being used for some large binaries.

View File

@@ -365,6 +365,42 @@ update_signals_program_target (void)
target_program_signals (signal_program);
}
/* See infrun.h. */
gdb_signal
get_detach_signal (process_stratum_target *proc_target, ptid_t ptid)
{
thread_info *tp = proc_target->find_thread (ptid);
gdb_signal signo = GDB_SIGNAL_0;
if (target_is_non_stop_p () && !tp->executing ())
{
if (tp->has_pending_waitstatus ())
{
/* If the thread has a pending event, and it was stopped
with a signal, use that signal to resume it. If it has a
pending event of another kind, it was not stopped with a
signal, so resume it without a signal. */
if (tp->pending_waitstatus ().kind () == TARGET_WAITKIND_STOPPED)
signo = tp->pending_waitstatus ().sig ();
}
else
signo = tp->stop_signal ();
}
else if (!target_is_non_stop_p ())
{
ptid_t last_ptid;
process_stratum_target *last_target;
get_last_target_status (&last_target, &last_ptid, nullptr);
if (last_target == proc_target && ptid == last_ptid)
signo = tp->stop_signal ();
}
return signo;
}
/* Value to pass to target_resume() to cause all threads to resume. */
#define RESUME_ALL minus_one_ptid

View File

@@ -319,6 +319,12 @@ extern void all_uis_on_sync_execution_starting (void);
detach. */
extern void restart_after_all_stop_detach (process_stratum_target *proc_target);
/* While detaching, return the signal PTID was supposed to be resumed
with, if it were resumed, so we can pass it down to PTID while
detaching. */
extern gdb_signal get_detach_signal (process_stratum_target *proc_target,
ptid_t ptid);
/* RAII object to temporarily disable the requirement for target
stacks to commit their resumed threads.

View File

@@ -1286,13 +1286,13 @@ detach_one_pid (int pid, int signo)
pid, strsignal (signo));
}
/* Get pending signal of THREAD as a host signal number, for detaching
/* Get pending signal of LP as a host signal number, for detaching
purposes. This is the signal the thread last stopped for, which we
need to deliver to the thread when detaching, otherwise, it'd be
suppressed/lost. */
static int
get_detach_signal (struct lwp_info *lp)
get_lwp_detach_signal (struct lwp_info *lp)
{
enum gdb_signal signo = GDB_SIGNAL_0;
@@ -1322,37 +1322,7 @@ get_detach_signal (struct lwp_info *lp)
else if (lp->status)
signo = gdb_signal_from_host (WSTOPSIG (lp->status));
else
{
thread_info *tp = linux_target->find_thread (lp->ptid);
if (target_is_non_stop_p () && !tp->executing ())
{
if (tp->has_pending_waitstatus ())
{
/* If the thread has a pending event, and it was stopped with a
signal, use that signal to resume it. If it has a pending
event of another kind, it was not stopped with a signal, so
resume it without a signal. */
if (tp->pending_waitstatus ().kind () == TARGET_WAITKIND_STOPPED)
signo = tp->pending_waitstatus ().sig ();
else
signo = GDB_SIGNAL_0;
}
else
signo = tp->stop_signal ();
}
else if (!target_is_non_stop_p ())
{
ptid_t last_ptid;
process_stratum_target *last_target;
get_last_target_status (&last_target, &last_ptid, nullptr);
if (last_target == linux_target
&& lp->ptid.lwp () == last_ptid.lwp ())
signo = tp->stop_signal ();
}
}
signo = get_detach_signal (linux_target, lp->ptid);
if (signo == GDB_SIGNAL_0)
{

View File

@@ -153,6 +153,55 @@ windows_thread_info::thread_name ()
return name.get ();
}
/* Read Windows signal info. See nat/windows-nat.h. */
bool
windows_thread_info::xfer_siginfo (gdb_byte *readbuf,
ULONGEST offset, ULONGEST len,
ULONGEST *xfered_len)
{
if (last_event.dwDebugEventCode != EXCEPTION_DEBUG_EVENT)
return false;
if (readbuf == nullptr)
return false;
EXCEPTION_RECORD &er = last_event.u.Exception.ExceptionRecord;
char *buf = (char *) &er;
size_t bufsize = sizeof (er);
#ifdef __x86_64__
EXCEPTION_RECORD32 er32;
if (proc->wow64_process)
{
buf = (char *) &er32;
bufsize = sizeof (er32);
er32.ExceptionCode = er.ExceptionCode;
er32.ExceptionFlags = er.ExceptionFlags;
er32.ExceptionRecord
= (uintptr_t) er.ExceptionRecord;
er32.ExceptionAddress
= (uintptr_t) er.ExceptionAddress;
er32.NumberParameters = er.NumberParameters;
for (int i = 0; i < EXCEPTION_MAXIMUM_PARAMETERS; i++)
er32.ExceptionInformation[i] = er.ExceptionInformation[i];
}
#endif
if (offset > bufsize)
return false;
if (offset + len > bufsize)
len = bufsize - offset;
memcpy (readbuf, buf + offset, len);
*xfered_len = len;
return true;
}
/* Try to determine the executable filename.
EXE_NAME_RET is a pointer to a buffer whose size is EXE_NAME_MAX_LEN.
@@ -310,8 +359,10 @@ get_image_name (HANDLE h, void *address, int unicode)
/* See nat/windows-nat.h. */
bool
windows_process_info::handle_ms_vc_exception (const EXCEPTION_RECORD *rec)
windows_process_info::handle_ms_vc_exception (const DEBUG_EVENT &current_event)
{
const EXCEPTION_RECORD *rec = &current_event.u.Exception.ExceptionRecord;
if (rec->NumberParameters >= 3
&& (rec->ExceptionInformation[0] & 0xffffffff) == 0x1000)
{
@@ -325,9 +376,8 @@ windows_process_info::handle_ms_vc_exception (const EXCEPTION_RECORD *rec)
if (named_thread_id == (DWORD) -1)
named_thread_id = current_event.dwThreadId;
named_thread = thread_rec (ptid_t (current_event.dwProcessId,
named_thread_id, 0),
DONT_INVALIDATE_CONTEXT);
named_thread = find_thread (ptid_t (current_event.dwProcessId,
named_thread_id, 0));
if (named_thread != NULL)
{
int thread_name_len;
@@ -353,7 +403,8 @@ windows_process_info::handle_ms_vc_exception (const EXCEPTION_RECORD *rec)
#define MS_VC_EXCEPTION 0x406d1388
handle_exception_result
windows_process_info::handle_exception (struct target_waitstatus *ourstatus,
windows_process_info::handle_exception (DEBUG_EVENT &current_event,
struct target_waitstatus *ourstatus,
bool debug_exceptions)
{
#define DEBUG_EXCEPTION_SIMPLE(x) if (debug_exceptions) \
@@ -365,14 +416,6 @@ windows_process_info::handle_exception (struct target_waitstatus *ourstatus,
DWORD code = rec->ExceptionCode;
handle_exception_result result = HANDLE_EXCEPTION_HANDLED;
memcpy (&siginfo_er, rec, sizeof siginfo_er);
/* Record the context of the current thread. */
thread_rec (ptid_t (current_event.dwProcessId, current_event.dwThreadId, 0),
DONT_SUSPEND);
last_sig = GDB_SIGNAL_0;
switch (code)
{
case EXCEPTION_ACCESS_VIOLATION:
@@ -485,7 +528,7 @@ windows_process_info::handle_exception (struct target_waitstatus *ourstatus,
break;
case MS_VC_EXCEPTION:
DEBUG_EXCEPTION_SIMPLE ("MS_VC_EXCEPTION");
if (handle_ms_vc_exception (rec))
if (handle_ms_vc_exception (current_event))
{
ourstatus->set_stopped (GDB_SIGNAL_TRAP);
result = HANDLE_EXCEPTION_IGNORED;
@@ -506,7 +549,11 @@ windows_process_info::handle_exception (struct target_waitstatus *ourstatus,
}
if (ourstatus->kind () == TARGET_WAITKIND_STOPPED)
last_sig = ourstatus->sig ();
{
ptid_t ptid (current_event.dwProcessId, current_event.dwThreadId, 0);
windows_thread_info *th = find_thread (ptid);
th->last_sig = ourstatus->sig ();
}
return result;
@@ -639,11 +686,11 @@ windows_process_info::add_dll (LPVOID load_addr)
/* See nat/windows-nat.h. */
void
windows_process_info::dll_loaded_event ()
windows_process_info::dll_loaded_event (const DEBUG_EVENT &current_event)
{
gdb_assert (current_event.dwDebugEventCode == LOAD_DLL_DEBUG_EVENT);
LOAD_DLL_DEBUG_INFO *event = &current_event.u.LoadDll;
const LOAD_DLL_DEBUG_INFO *event = &current_event.u.LoadDll;
const char *dll_name;
/* Try getting the DLL name via the lpImageName field of the event.
@@ -671,69 +718,29 @@ windows_process_info::add_all_dlls ()
/* See nat/windows-nat.h. */
bool
windows_process_info::matching_pending_stop (bool debug_events)
ptid_t
get_last_debug_event_ptid ()
{
/* If there are pending stops, and we might plausibly hit one of
them, we don't want to actually continue the inferior -- we just
want to report the stop. In this case, we just pretend to
continue. See the comment by the definition of "pending_stops"
for details on why this is needed. */
for (const auto &item : pending_stops)
{
if (desired_stop_thread_id == -1
|| desired_stop_thread_id == item.thread_id)
{
DEBUG_EVENTS ("pending stop anticipated, desired=0x%x, item=0x%x",
desired_stop_thread_id, item.thread_id);
return true;
}
}
return false;
}
/* See nat/windows-nat.h. */
std::optional<pending_stop>
windows_process_info::fetch_pending_stop (bool debug_events)
{
std::optional<pending_stop> result;
for (auto iter = pending_stops.begin ();
iter != pending_stops.end ();
++iter)
{
if (desired_stop_thread_id == -1
|| desired_stop_thread_id == iter->thread_id)
{
result = *iter;
current_event = iter->event;
DEBUG_EVENTS ("pending stop found in 0x%x (desired=0x%x)",
iter->thread_id, desired_stop_thread_id);
pending_stops.erase (iter);
break;
}
}
return result;
return ptid_t (last_wait_event.dwProcessId, last_wait_event.dwThreadId, 0);
}
/* See nat/windows-nat.h. */
BOOL
continue_last_debug_event (DWORD continue_status, bool debug_events)
continue_last_debug_event (DWORD cont_status, bool debug_events)
{
DEBUG_EVENTS ("ContinueDebugEvent (cpid=%d, ctid=0x%x, %s)",
(unsigned) last_wait_event.dwProcessId,
(unsigned) last_wait_event.dwThreadId,
continue_status == DBG_CONTINUE ?
"DBG_CONTINUE" : "DBG_EXCEPTION_NOT_HANDLED");
DEBUG_EVENTS
("ContinueDebugEvent (cpid=%d, ctid=0x%x, %s)",
(unsigned) last_wait_event.dwProcessId,
(unsigned) last_wait_event.dwThreadId,
cont_status == DBG_CONTINUE ? "DBG_CONTINUE" :
cont_status == DBG_EXCEPTION_NOT_HANDLED ? "DBG_EXCEPTION_NOT_HANDLED" :
cont_status == DBG_REPLY_LATER ? "DBG_REPLY_LATER" :
"DBG_???");
return ContinueDebugEvent (last_wait_event.dwProcessId,
last_wait_event.dwThreadId,
continue_status);
cont_status);
}
/* See nat/windows-nat.h. */
@@ -937,6 +944,26 @@ disable_randomization_available ()
/* See windows-nat.h. */
bool
dbg_reply_later_available ()
{
static int available = -1;
if (available == -1)
{
/* DBG_REPLY_LATER is supported since Windows 10, Version 1507.
If supported, this fails with ERROR_INVALID_HANDLE (tested on
Win10 and Win11). If not supported, it fails with
ERROR_INVALID_PARAMETER (tested on Win7). */
if (ContinueDebugEvent (0, 0, DBG_REPLY_LATER))
internal_error (_("ContinueDebugEvent call should not "
"have succeeded"));
available = (GetLastError () != ERROR_INVALID_PARAMETER);
}
return available;
}
/* See windows-nat.h. */
bool
initialize_loadable ()
{

View File

@@ -32,12 +32,34 @@
namespace windows_nat
{
struct windows_process_info;
/* The reason for explicitly stopping a thread. Note the enumerators
are ordered such that when comparing two stopping_kind's numerical
value, the highest should prevail. */
enum stopping_kind
{
/* Not really stopping the thread. */
SK_NOT_STOPPING = 0,
/* We're stopping the thread for internal reasons, the stop should
not be reported as an event to the core. */
SK_INTERNAL = 1,
/* We're stopping the thread for external reasons, meaning, the
core/user asked us to stop the thread, so we must report a stop
event to the core. */
SK_EXTERNAL = 2,
};
/* Thread information structure used to track extra information about
each thread. */
struct windows_thread_info
{
windows_thread_info (DWORD tid_, HANDLE h_, CORE_ADDR tlb)
: tid (tid_),
windows_thread_info (windows_process_info *proc_,
DWORD tid_, HANDLE h_, CORE_ADDR tlb)
: proc (proc_),
tid (tid_),
h (h_),
thread_local_base (tlb)
{
@@ -56,6 +78,18 @@ struct windows_thread_info
the next call. */
const char *thread_name ();
/* Read LEN bytes of the thread's $_siginfo into READBUF, starting
at OFFSET. Store the number of actually-read bytes in
XFERED_LEN. Returns true on success, false on failure. Passing
READBUF as NULL indicates that the caller is trying to write to
$_siginfo, which is a failure case. */
bool xfer_siginfo (gdb_byte *readbuf,
ULONGEST offset, ULONGEST len,
ULONGEST *xfered_len);
/* The process this thread belongs to. */
windows_process_info *proc;
/* The Win32 thread identifier. */
DWORD tid;
@@ -65,12 +99,79 @@ struct windows_thread_info
/* Thread Information Block address. */
CORE_ADDR thread_local_base;
#ifdef __CYGWIN__
/* These two fields are used to handle Cygwin signals. When a
thread is signaled, the "sig" thread inside the Cygwin runtime
reports the fact to us via a special OutputDebugString message.
In order to make stepping into a signal handler work, we can only
resume the "sig" thread when we also resume the target signaled
thread. When we intercept a Cygwin signal, we set up a cross
link between the two threads using the two fields below, so we
can always identify one from the other. See the "Cygwin signals"
description in gdb/windows-nat.c for more. */
/* If this thread received a signal, then 'cygwin_sig_thread' points
to the "sig" thread within the Cygwin runtime. */
windows_thread_info *cygwin_sig_thread = nullptr;
/* If this thread is the Cygwin runtime's "sig" thread, then
'signaled_thread' points at the thread that received a
signal. */
windows_thread_info *signaled_thread = nullptr;
#endif
/* If the thread had its event postponed with DBG_REPLY_LATER, when
we later ResumeThread this thread, WaitForDebugEvent will
re-report the postponed event. This field holds the continue
status value to be automatically passed to ContinueDebugEvent
when we encounter this re-reported event. 0 if the thread has
not had its event postponed with DBG_REPLY_LATER. */
DWORD reply_later = 0;
/* This keeps track of whether SuspendThread was called on this
thread. -1 means there was a failure or that the thread was
explicitly not suspended, 1 means it was called, and 0 means it
was not. */
int suspended = 0;
/* This flag indicates whether we are explicitly stopping this
thread in response to a target_stop request or for
backend-internal reasons. This allows distinguishing between
threads that are explicitly stopped by the debugger and threads
that are stopped due to other reasons.
Typically, when we want to stop a thread, we suspend it, enqueue
a pending GDB_SIGNAL_0 stop status on the thread, and then set
this flag to true. However, if the thread has had its event
previously postponed with DBG_REPLY_LATER, it means that it
already has an event to report. In such case, we simply set the
'stopping' flag without suspending the thread or enqueueing a
pending stop. See stop_one_thread. */
stopping_kind stopping = SK_NOT_STOPPING;
/* Info about a potential pending stop.
Sometimes, Windows will report a stop on a thread that has been
ostensibly suspended. We believe what happens here is that two
threads hit a breakpoint simultaneously, and the Windows kernel
queues the stop events. However, this can result in the strange
effect of trying to single step thread A -- leaving all other
threads suspended -- and then seeing a stop in thread B. To handle
this scenario, we queue all such "pending" stops here, and then
process them once the step has completed. See PR gdb/22992.
TARGET_WAITKIND_IGNORE if the thread does not have a pending
stop. */
target_waitstatus pending_status;
/* The last Windows event returned by WaitForDebugEvent for this
thread. */
DEBUG_EVENT last_event {};
/* The last signal reported for this thread, extracted out of
last_event. */
enum gdb_signal last_sig = GDB_SIGNAL_0;
/* The context of the thread, including any manipulations. */
union
{
@@ -84,10 +185,6 @@ struct windows_thread_info
the thread. */
bool debug_registers_changed = false;
/* Nonzero if CONTEXT is invalidated and must be re-read from the
inferior thread. */
bool reload_context = false;
/* True if this thread is currently stopped at a software
breakpoint. This is used to offset the PC when needed. */
bool stopped_at_software_breakpoint = false;
@@ -101,34 +198,6 @@ struct windows_thread_info
gdb::unique_xmalloc_ptr<char> name;
};
/* Possible values to pass to 'thread_rec'. */
enum thread_disposition_type
{
/* Do not invalidate the thread's context, and do not suspend the
thread. */
DONT_INVALIDATE_CONTEXT,
/* Invalidate the context, but do not suspend the thread. */
DONT_SUSPEND,
/* Invalidate the context and suspend the thread. */
INVALIDATE_CONTEXT
};
/* A single pending stop. See "pending_stops" for more
information. */
struct pending_stop
{
/* The thread id. */
DWORD thread_id;
/* The target waitstatus we computed. */
target_waitstatus status;
/* The event. A few fields of this can be referenced after a stop,
and it seemed simplest to store the entire event. */
DEBUG_EVENT event;
};
enum handle_exception_result
{
HANDLE_EXCEPTION_UNHANDLED = 0,
@@ -144,31 +213,8 @@ struct windows_process_info
{
/* The process handle */
HANDLE handle = 0;
DWORD process_id = 0;
DWORD main_thread_id = 0;
enum gdb_signal last_sig = GDB_SIGNAL_0;
/* The current debug event from WaitForDebugEvent or from a pending
stop. */
DEBUG_EVENT current_event {};
/* The ID of the thread for which we anticipate a stop event.
Normally this is -1, meaning we'll accept an event in any
thread. */
DWORD desired_stop_thread_id = -1;
/* A vector of pending stops. Sometimes, Windows will report a stop
on a thread that has been ostensibly suspended. We believe what
happens here is that two threads hit a breakpoint simultaneously,
and the Windows kernel queues the stop events. However, this can
result in the strange effect of trying to single step thread A --
leaving all other threads suspended -- and then seeing a stop in
thread B. To handle this scenario, we queue all such "pending"
stops here, and then process them once the step has completed. See
PR gdb/22992. */
std::vector<pending_stop> pending_stops;
/* Contents of $_siginfo */
EXCEPTION_RECORD siginfo_er {};
#ifdef __x86_64__
/* The target is a WOW64 process */
@@ -177,24 +223,21 @@ struct windows_process_info
bool ignore_first_breakpoint = false;
#endif
/* Find a thread record given a thread id. THREAD_DISPOSITION
controls whether the thread is suspended, and whether the context
is invalidated.
/* Find a thread record given a thread id.
This function must be supplied by the embedding application. */
virtual windows_thread_info *thread_rec (ptid_t ptid,
thread_disposition_type disposition) = 0;
virtual windows_thread_info *find_thread (ptid_t ptid) = 0;
/* Handle OUTPUT_DEBUG_STRING_EVENT from child process. Updates
OURSTATUS and returns the thread id if this represents a thread
change (this is specific to Cygwin), otherwise 0.
OURSTATUS and returns true if this represents a Cygwin signal,
otherwise false.
Cygwin prepends its messages with a "cygwin:". Interpret this as
a Cygwin signal. Otherwise just print the string as a warning.
This function must be supplied by the embedding application. */
virtual int handle_output_debug_string (struct target_waitstatus *ourstatus) = 0;
virtual bool handle_output_debug_string (const DEBUG_EVENT &current_event,
struct target_waitstatus *ourstatus) = 0;
/* Handle a DLL load event.
@@ -215,7 +258,7 @@ struct windows_process_info
This function must be supplied by the embedding application. */
virtual void handle_unload_dll () = 0;
virtual void handle_unload_dll (const DEBUG_EVENT &current_event) = 0;
/* When EXCEPTION_ACCESS_VIOLATION is processed, we give the embedding
application a chance to change it to be considered "unhandled".
@@ -224,30 +267,26 @@ struct windows_process_info
virtual bool handle_access_violation (const EXCEPTION_RECORD *rec) = 0;
/* Fill in the thread's CONTEXT/WOW64_CONTEXT, if it wasn't filled
in yet.
This function must be supplied by the embedding application. */
virtual void fill_thread_context (windows_thread_info *th) = 0;
handle_exception_result handle_exception
(struct target_waitstatus *ourstatus, bool debug_exceptions);
(DEBUG_EVENT &current_event,
struct target_waitstatus *ourstatus, bool debug_exceptions);
/* Call to indicate that a DLL was loaded. */
void dll_loaded_event ();
void dll_loaded_event (const DEBUG_EVENT &current_event);
/* Iterate over all DLLs currently mapped by our inferior, and
add them to our list of solibs. */
void add_all_dlls ();
/* Return true if there is a pending stop matching
desired_stop_thread_id. If DEBUG_EVENTS is true, logging will be
enabled. */
bool matching_pending_stop (bool debug_events);
/* See if a pending stop matches DESIRED_STOP_THREAD_ID. If so,
remove it from the list of pending stops, set 'current_event', and
return it. Otherwise, return an empty optional. */
std::optional<pending_stop> fetch_pending_stop (bool debug_events);
const char *pid_to_exec_file (int);
private:
@@ -258,7 +297,7 @@ private:
Return true if the exception was handled; return false otherwise. */
bool handle_ms_vc_exception (const EXCEPTION_RECORD *rec);
bool handle_ms_vc_exception (const DEBUG_EVENT &current_event);
/* Iterate over all DLLs currently mapped by our inferior, looking for
a DLL which is loaded at LOAD_ADDR. If found, add the DLL to our
@@ -289,6 +328,11 @@ private:
extern BOOL continue_last_debug_event (DWORD continue_status,
bool debug_events);
/* Return the ptid_t of the thread that the last waited-for event was
for. */
extern ptid_t get_last_debug_event_ptid ();
/* A simple wrapper for WaitForDebugEvent that also sets the internal
'last_wait_event' on success. */
@@ -433,6 +477,15 @@ extern DeleteProcThreadAttributeList_ftype *DeleteProcThreadAttributeList;
extern bool disable_randomization_available ();
/* This is available starting with Windows 10. */
#ifndef DBG_REPLY_LATER
# define DBG_REPLY_LATER 0x40010001L
#endif
/* Return true if it's possible to use DBG_REPLY_LATER with
ContinueDebugEvent on this host. */
extern bool dbg_reply_later_available ();
/* Load any functions which may not be available in ancient versions
of Windows. */

File diff suppressed because it is too large Load Diff

View File

@@ -141,18 +141,21 @@ win32_require_context (windows_thread_info *th)
/* See nat/windows-nat.h. */
void
gdbserver_windows_process::fill_thread_context (windows_thread_info *th)
{
win32_require_context (th);
}
/* See nat/windows-nat.h. */
windows_thread_info *
gdbserver_windows_process::thread_rec
(ptid_t ptid, thread_disposition_type disposition)
gdbserver_windows_process::find_thread (ptid_t ptid)
{
thread_info *thread = find_thread_ptid (ptid);
if (thread == NULL)
return NULL;
windows_thread_info *th = (windows_thread_info *) thread_target_data (thread);
if (disposition != DONT_INVALIDATE_CONTEXT)
win32_require_context (th);
return th;
if (thread == nullptr)
return nullptr;
return (windows_thread_info *) thread_target_data (thread);
}
/* Add a thread to the thread list. */
@@ -162,7 +165,7 @@ child_add_thread (DWORD pid, DWORD tid, HANDLE h, void *tlb)
windows_thread_info *th;
ptid_t ptid = ptid_t (pid, tid, 0);
if ((th = windows_process.thread_rec (ptid, DONT_INVALIDATE_CONTEXT)))
if ((th = windows_process.find_thread (ptid)))
return th;
CORE_ADDR base = (CORE_ADDR) (uintptr_t) tlb;
@@ -172,7 +175,7 @@ child_add_thread (DWORD pid, DWORD tid, HANDLE h, void *tlb)
if (windows_process.wow64_process)
base += 2 * 4096; /* page size = 4096 */
#endif
th = new windows_thread_info (tid, h, base);
th = new windows_thread_info (&windows_process, tid, h, base);
add_thread (ptid, th);
@@ -307,17 +310,12 @@ do_initial_child_stuff (HANDLE proch, DWORD pid, int attached)
{
struct process_info *proc;
windows_process.last_sig = GDB_SIGNAL_0;
windows_process.handle = proch;
windows_process.process_id = pid;
windows_process.main_thread_id = 0;
windows_process.soft_interrupt_requested = 0;
windows_process.faked_breakpoint = 0;
windows_process.open_process_used = true;
memset (&windows_process.current_event, 0,
sizeof (windows_process.current_event));
#ifdef __x86_64__
BOOL wow64;
if (!IsWow64Process (proch, &wow64))
@@ -424,24 +422,20 @@ continue_one_thread (thread_info *thread, int thread_id)
}
th->resume ();
th->last_sig = GDB_SIGNAL_0;
}
}
}
static BOOL
child_continue (DWORD continue_status, int thread_id)
child_continue_for_kill (DWORD continue_status, int thread_id)
{
windows_process.desired_stop_thread_id = thread_id;
if (windows_process.matching_pending_stop (debug_threads))
return TRUE;
/* The inferior will only continue after the ContinueDebugEvent
call. */
for_each_thread ([&] (thread_info *thread)
{
continue_one_thread (thread, thread_id);
});
windows_process.faked_breakpoint = 0;
return continue_last_debug_event (continue_status, debug_threads);
}
@@ -452,8 +446,8 @@ child_fetch_inferior_registers (struct regcache *regcache, int r)
{
int regno;
windows_thread_info *th
= windows_process.thread_rec (current_thread_ptid (),
INVALIDATE_CONTEXT);
= windows_process.find_thread (current_thread_ptid ());
win32_require_context (th);
if (r == -1 || r > NUM_REGS)
child_fetch_inferior_registers (regcache, NUM_REGS);
else
@@ -468,8 +462,8 @@ child_store_inferior_registers (struct regcache *regcache, int r)
{
int regno;
windows_thread_info *th
= windows_process.thread_rec (current_thread_ptid (),
INVALIDATE_CONTEXT);
= windows_process.find_thread (current_thread_ptid ());
win32_require_context (th);
if (r == -1 || r == 0 || r > NUM_REGS)
child_store_inferior_registers (regcache, NUM_REGS);
else
@@ -631,9 +625,10 @@ win32_process_target::attach (unsigned long pid)
/* See nat/windows-nat.h. */
int
bool
gdbserver_windows_process::handle_output_debug_string
(struct target_waitstatus *ourstatus)
(const DEBUG_EVENT &current_event,
struct target_waitstatus *ourstatus)
{
#define READ_BUFFER_LEN 1024
CORE_ADDR addr;
@@ -641,7 +636,7 @@ gdbserver_windows_process::handle_output_debug_string
DWORD nbytes = current_event.u.DebugString.nDebugStringLength;
if (nbytes == 0)
return 0;
return false;
if (nbytes > READ_BUFFER_LEN)
nbytes = READ_BUFFER_LEN;
@@ -660,7 +655,7 @@ gdbserver_windows_process::handle_output_debug_string
else
{
if (read_inferior_memory (addr, (unsigned char *) s, nbytes) != 0)
return 0;
return false;
}
if (!startswith (s, "cYg"))
@@ -668,14 +663,14 @@ gdbserver_windows_process::handle_output_debug_string
if (!server_waiting)
{
OUTMSG2(("%s", s));
return 0;
return false;
}
monitor_output (s);
}
#undef READ_BUFFER_LEN
return 0;
return false;
}
static void
@@ -688,7 +683,6 @@ win32_clear_inferiors (void)
}
for_each_thread (delete_thread_info);
windows_process.siginfo_er.ExceptionCode = 0;
clear_inferiors ();
}
@@ -700,16 +694,15 @@ win32_process_target::kill (process_info *process)
TerminateProcess (windows_process.handle, 0);
for (;;)
{
if (!child_continue (DBG_CONTINUE, -1))
if (!child_continue_for_kill (DBG_CONTINUE, -1))
break;
if (!wait_for_debug_event (&windows_process.current_event, INFINITE))
DEBUG_EVENT current_event;
if (!wait_for_debug_event (&current_event, INFINITE))
break;
if (windows_process.current_event.dwDebugEventCode
== EXIT_PROCESS_DEBUG_EVENT)
if (current_event.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT)
break;
else if (windows_process.current_event.dwDebugEventCode
== OUTPUT_DEBUG_STRING_EVENT)
windows_process.handle_output_debug_string (nullptr);
else if (current_event.dwDebugEventCode == OUTPUT_DEBUG_STRING_EVENT)
windows_process.handle_output_debug_string (current_event, nullptr);
}
win32_clear_inferiors ();
@@ -767,96 +760,108 @@ win32_process_target::thread_alive (ptid_t ptid)
return find_thread_ptid (ptid) != NULL;
}
static void
resume_one_thread (thread_info *thread, bool step, gdb_signal sig,
DWORD *continue_status)
{
auto *th = (windows_thread_info *) thread_target_data (thread);
if (sig != GDB_SIGNAL_0)
{
/* Allow continuing with the same signal that interrupted us.
Otherwise complain. */
if (thread->id != get_last_debug_event_ptid ())
{
/* ContinueDebugEvent will be for a different thread. */
OUTMSG (("Cannot continue with signal %d here. "
"Not last-event thread", sig));
}
else if (th->last_event.dwDebugEventCode != EXCEPTION_DEBUG_EVENT)
{
OUTMSG (("Cannot continue with signal %s here. "
"Not stopped for EXCEPTION_DEBUG_EVENT.\n",
gdb_signal_to_string (sig)));
}
else if (sig == th->last_sig)
*continue_status = DBG_EXCEPTION_NOT_HANDLED;
else
OUTMSG (("Can only continue with received signal %s.\n",
gdb_signal_to_string (th->last_sig)));
}
win32_prepare_to_resume (th);
DWORD *context_flags;
#ifdef __x86_64__
if (windows_process.wow64_process)
context_flags = &th->wow64_context.ContextFlags;
else
#endif
context_flags = &th->context.ContextFlags;
if (*context_flags)
{
/* Move register values from the inferior into the thread
context structure. */
regcache_invalidate ();
if (step)
{
if (the_low_target.single_step != NULL)
(*the_low_target.single_step) (th);
else
error ("Single stepping is not supported "
"in this configuration.\n");
}
win32_set_thread_context (th);
*context_flags = 0;
}
continue_one_thread (thread, th->tid);
}
/* Resume the inferior process. RESUME_INFO describes how we want
to resume. */
void
win32_process_target::resume (thread_resume *resume_info, size_t n)
{
DWORD tid;
enum gdb_signal sig;
int step;
windows_thread_info *th;
DWORD continue_status = DBG_CONTINUE;
ptid_t ptid;
bool any_pending = false;
/* This handles the very limited set of resume packets that GDB can
currently produce. */
if (n == 1 && resume_info[0].thread == minus_one_ptid)
tid = -1;
else if (n > 1)
tid = -1;
else
/* Yes, we're ignoring resume_info[0].thread. It'd be tricky to make
the Windows resume code do the right thing for thread switching. */
tid = windows_process.current_event.dwThreadId;
if (resume_info[0].thread != minus_one_ptid)
for (thread_info *thread : all_threads)
{
sig = gdb_signal_from_host (resume_info[0].sig);
step = resume_info[0].kind == resume_step;
}
else
{
sig = GDB_SIGNAL_0;
step = 0;
}
auto *th = (windows_thread_info *) thread_target_data (thread);
if (sig != GDB_SIGNAL_0)
{
if (windows_process.current_event.dwDebugEventCode
!= EXCEPTION_DEBUG_EVENT)
for (int ndx = 0; ndx < n; ndx++)
{
OUTMSG (("Cannot continue with signal %s here.\n",
gdb_signal_to_string (sig)));
}
else if (sig == windows_process.last_sig)
continue_status = DBG_EXCEPTION_NOT_HANDLED;
else
OUTMSG (("Can only continue with received signal %s.\n",
gdb_signal_to_string (windows_process.last_sig)));
}
thread_resume &r = resume_info[ndx];
ptid_t ptid = r.thread;
gdb_signal sig = gdb_signal_from_host (r.sig);
bool step = r.kind == resume_step;
windows_process.last_sig = GDB_SIGNAL_0;
/* Get context for the currently selected thread. */
ptid = debug_event_ptid (&windows_process.current_event);
th = windows_process.thread_rec (ptid, DONT_INVALIDATE_CONTEXT);
if (th)
{
win32_prepare_to_resume (th);
DWORD *context_flags;
#ifdef __x86_64__
if (windows_process.wow64_process)
context_flags = &th->wow64_context.ContextFlags;
else
#endif
context_flags = &th->context.ContextFlags;
if (*context_flags)
{
/* Move register values from the inferior into the thread
context structure. */
regcache_invalidate ();
if (step)
if (ptid == minus_one_ptid || ptid == thread->id
/* Handle both 'pPID' and 'pPID.-1' as meaning 'all threads
of PID'. */
|| (ptid.pid () == pid_of (thread)
&& (ptid.is_pid () || ptid.lwp () == -1)))
{
if (the_low_target.single_step != NULL)
(*the_low_target.single_step) (th);
else
error ("Single stepping is not supported "
"in this configuration.\n");
}
/* Ignore (wildcard) resume requests for already-resumed
threads. */
if (!th->suspended)
continue;
win32_set_thread_context (th);
*context_flags = 0;
resume_one_thread (thread, step, sig, &continue_status);
break;
}
}
if (!th->suspended
&& th->pending_status.kind () != TARGET_WAITKIND_IGNORE)
any_pending = true;
}
/* Allow continuing with the same signal that interrupted us.
Otherwise complain. */
child_continue (continue_status, tid);
if (!any_pending)
continue_last_debug_event (continue_status, debug_threads);
}
/* See nat/windows-nat.h. */
@@ -916,7 +921,7 @@ gdbserver_windows_process::handle_load_dll (const char *name, LPVOID base)
/* See nat/windows-nat.h. */
void
gdbserver_windows_process::handle_unload_dll ()
gdbserver_windows_process::handle_unload_dll (const DEBUG_EVENT &current_event)
{
CORE_ADDR load_addr =
(CORE_ADDR) (uintptr_t) current_event.u.UnloadDll.lpBaseOfDll;
@@ -936,23 +941,6 @@ suspend_one_thread (thread_info *thread)
th->suspend ();
}
static void
fake_breakpoint_event (void)
{
OUTMSG2(("fake_breakpoint_event\n"));
windows_process.faked_breakpoint = 1;
memset (&windows_process.current_event, 0,
sizeof (windows_process.current_event));
windows_process.current_event.dwThreadId = windows_process.main_thread_id;
windows_process.current_event.dwDebugEventCode = EXCEPTION_DEBUG_EVENT;
windows_process.current_event.u.Exception.ExceptionRecord.ExceptionCode
= EXCEPTION_BREAKPOINT;
for_each_thread (suspend_one_thread);
}
/* See nat/windows-nat.h. */
bool
@@ -967,20 +955,19 @@ gdbserver_windows_process::handle_access_violation
PC. */
static void
maybe_adjust_pc ()
maybe_adjust_pc (const DEBUG_EVENT &current_event)
{
struct regcache *regcache = get_thread_regcache (current_thread, 1);
child_fetch_inferior_registers (regcache, -1);
windows_thread_info *th
= windows_process.thread_rec (current_thread_ptid (),
DONT_INVALIDATE_CONTEXT);
= windows_process.find_thread (current_thread_ptid ());
th->stopped_at_software_breakpoint = false;
if (windows_process.current_event.dwDebugEventCode == EXCEPTION_DEBUG_EVENT
&& ((windows_process.current_event.u.Exception.ExceptionRecord.ExceptionCode
if (current_event.dwDebugEventCode == EXCEPTION_DEBUG_EVENT
&& ((current_event.u.Exception.ExceptionRecord.ExceptionCode
== EXCEPTION_BREAKPOINT)
|| (windows_process.current_event.u.Exception.ExceptionRecord.ExceptionCode
|| (current_event.u.Exception.ExceptionRecord.ExceptionCode
== STATUS_WX86_BREAKPOINT))
&& windows_process.child_initialization_done)
{
@@ -995,43 +982,39 @@ maybe_adjust_pc ()
static int
get_child_debug_event (DWORD *continue_status,
struct target_waitstatus *ourstatus)
struct target_waitstatus *ourstatus,
DEBUG_EVENT *current_event)
{
ptid_t ptid;
windows_process.last_sig = GDB_SIGNAL_0;
ourstatus->set_spurious ();
*continue_status = DBG_CONTINUE;
/* Check if GDB sent us an interrupt request. */
check_remote_input_interrupt_request ();
DEBUG_EVENT *current_event = &windows_process.current_event;
if (windows_process.soft_interrupt_requested)
{
windows_process.soft_interrupt_requested = 0;
fake_breakpoint_event ();
goto gotevent;
}
windows_process.attaching = 0;
{
std::optional<pending_stop> stop
= windows_process.fetch_pending_stop (debug_threads);
if (stop.has_value ())
for (thread_info *thread : all_threads)
{
*ourstatus = stop->status;
windows_process.current_event = stop->event;
ptid = debug_event_ptid (&windows_process.current_event);
switch_to_thread (find_thread_ptid (ptid));
return 1;
auto *th = (windows_thread_info *) thread_target_data (thread);
if (!th->suspended
&& th->pending_status.kind () != TARGET_WAITKIND_IGNORE)
{
*ourstatus = th->pending_status;
th->pending_status.set_ignore ();
*current_event = th->last_event;
ptid = debug_event_ptid (current_event);
switch_to_thread (find_thread_ptid (ptid));
return 1;
}
}
/* Keep the wait time low enough for comfortable remote
interruption, but high enough so gdbserver doesn't become a
bottleneck. */
if (!wait_for_debug_event (&windows_process.current_event, 250))
if (!wait_for_debug_event (current_event, 250))
{
DWORD e = GetLastError();
@@ -1049,8 +1032,6 @@ get_child_debug_event (DWORD *continue_status,
}
}
gotevent:
switch (current_event->dwDebugEventCode)
{
case CREATE_THREAD_DEBUG_EVENT:
@@ -1117,7 +1098,7 @@ get_child_debug_event (DWORD *continue_status,
else
ourstatus->set_signalled (gdb_signal_from_host (exit_signal));
}
child_continue (DBG_CONTINUE, windows_process.desired_stop_thread_id);
continue_last_debug_event (DBG_CONTINUE, debug_threads);
break;
case LOAD_DLL_DEBUG_EVENT:
@@ -1128,7 +1109,7 @@ get_child_debug_event (DWORD *continue_status,
CloseHandle (current_event->u.LoadDll.hFile);
if (! windows_process.child_initialization_done)
break;
windows_process.dll_loaded_event ();
windows_process.dll_loaded_event (*current_event);
ourstatus->set_loaded ();
break;
@@ -1140,7 +1121,7 @@ get_child_debug_event (DWORD *continue_status,
(unsigned) current_event->dwThreadId));
if (! windows_process.child_initialization_done)
break;
windows_process.handle_unload_dll ();
windows_process.handle_unload_dll (*current_event);
ourstatus->set_loaded ();
break;
@@ -1149,7 +1130,8 @@ get_child_debug_event (DWORD *continue_status,
"for pid=%u tid=%x\n",
(unsigned) current_event->dwProcessId,
(unsigned) current_event->dwThreadId));
if (windows_process.handle_exception (ourstatus, debug_threads)
if (windows_process.handle_exception (*current_event,
ourstatus, debug_threads)
== HANDLE_EXCEPTION_UNHANDLED)
*continue_status = DBG_EXCEPTION_NOT_HANDLED;
break;
@@ -1160,7 +1142,7 @@ get_child_debug_event (DWORD *continue_status,
"for pid=%u tid=%x\n",
(unsigned) current_event->dwProcessId,
(unsigned) current_event->dwThreadId));
windows_process.handle_output_debug_string (nullptr);
windows_process.handle_output_debug_string (*current_event, nullptr);
break;
default:
@@ -1172,19 +1154,22 @@ get_child_debug_event (DWORD *continue_status,
break;
}
ptid = debug_event_ptid (&windows_process.current_event);
ptid = debug_event_ptid (current_event);
if (windows_process.desired_stop_thread_id != -1
&& windows_process.desired_stop_thread_id != ptid.lwp ())
windows_thread_info *th = windows_process.find_thread (ptid);
th->last_event = *current_event;
if (th != nullptr && th->suspended)
{
/* Pending stop. See the comment by the definition of
"pending_stops" for details on why this is needed. */
"windows_thread_info::pending_status" for details on why this
is needed. */
OUTMSG2 (("get_windows_debug_event - "
"unexpected stop in 0x%lx (expecting 0x%x)\n",
ptid.lwp (), windows_process.desired_stop_thread_id));
maybe_adjust_pc ();
windows_process.pending_stops.push_back
({(DWORD) ptid.lwp (), *ourstatus, *current_event});
"unexpected stop in suspended thread 0x%x\n",
th->tid));
maybe_adjust_pc (*current_event);
th->pending_status = *ourstatus;
ourstatus->set_spurious ();
}
else
@@ -1208,13 +1193,16 @@ win32_process_target::wait (ptid_t ptid, target_waitstatus *ourstatus,
fails). Report it now. */
*ourstatus = windows_process.cached_status;
windows_process.cached_status.set_ignore ();
return debug_event_ptid (&windows_process.current_event);
return ptid_t (windows_process.process_id,
windows_process.main_thread_id, 0);
}
while (1)
{
DWORD continue_status;
if (!get_child_debug_event (&continue_status, ourstatus))
DEBUG_EVENT current_event;
if (!get_child_debug_event (&continue_status, ourstatus,
&current_event))
continue;
switch (ourstatus->kind ())
@@ -1223,15 +1211,20 @@ win32_process_target::wait (ptid_t ptid, target_waitstatus *ourstatus,
OUTMSG2 (("Child exited with retcode = %x\n",
ourstatus->exit_status ()));
win32_clear_inferiors ();
return ptid_t (windows_process.current_event.dwProcessId);
return ptid_t (windows_process.process_id);
case TARGET_WAITKIND_STOPPED:
case TARGET_WAITKIND_SIGNALLED:
case TARGET_WAITKIND_LOADED:
{
OUTMSG2 (("Child Stopped with signal = %d \n",
ourstatus->sig ()));
maybe_adjust_pc ();
return debug_event_ptid (&windows_process.current_event);
maybe_adjust_pc (current_event);
/* All-stop, suspend all threads until they are explicitly
resumed. */
for_each_thread (suspend_one_thread);
return debug_event_ptid (&current_event);
}
default:
OUTMSG (("Ignoring unknown internal event, %d\n",
@@ -1239,8 +1232,7 @@ win32_process_target::wait (ptid_t ptid, target_waitstatus *ourstatus,
[[fallthrough]];
case TARGET_WAITKIND_SPURIOUS:
/* do nothing, just continue */
child_continue (continue_status,
windows_process.desired_stop_thread_id);
continue_last_debug_event (continue_status, debug_threads);
break;
}
}
@@ -1298,8 +1290,7 @@ win32_process_target::request_interrupt ()
if (DebugBreakProcess (windows_process.handle))
return;
/* Last resort, suspend all threads manually. */
windows_process.soft_interrupt_requested = 1;
OUTMSG2 (("Could not interrupt.\n"));
}
bool
@@ -1322,45 +1313,14 @@ win32_process_target::qxfer_siginfo (const char *annex,
unsigned const char *writebuf,
CORE_ADDR offset, int len)
{
if (windows_process.siginfo_er.ExceptionCode == 0)
windows_thread_info *th
= windows_process.find_thread (current_thread_ptid ());
ULONGEST xfered_len;
if (th->xfer_siginfo (readbuf, offset, len, &xfered_len))
return xfered_len;
else
return -1;
if (readbuf == nullptr)
return -1;
char *buf = (char *) &windows_process.siginfo_er;
size_t bufsize = sizeof (windows_process.siginfo_er);
#ifdef __x86_64__
EXCEPTION_RECORD32 er32;
if (windows_process.wow64_process)
{
buf = (char *) &er32;
bufsize = sizeof (er32);
er32.ExceptionCode = windows_process.siginfo_er.ExceptionCode;
er32.ExceptionFlags = windows_process.siginfo_er.ExceptionFlags;
er32.ExceptionRecord
= (uintptr_t) windows_process.siginfo_er.ExceptionRecord;
er32.ExceptionAddress
= (uintptr_t) windows_process.siginfo_er.ExceptionAddress;
er32.NumberParameters = windows_process.siginfo_er.NumberParameters;
int i;
for (i = 0; i < EXCEPTION_MAXIMUM_PARAMETERS; i++)
er32.ExceptionInformation[i]
= windows_process.siginfo_er.ExceptionInformation[i];
}
#endif
if (offset > bufsize)
return -1;
if (offset + len > bufsize)
len = bufsize - offset;
memcpy (readbuf, buf + offset, len);
return len;
}
bool
@@ -1374,8 +1334,7 @@ win32_process_target::supports_get_tib_address ()
int
win32_process_target::get_tib_address (ptid_t ptid, CORE_ADDR *addr)
{
windows_thread_info *th;
th = windows_process.thread_rec (ptid, DONT_INVALIDATE_CONTEXT);
windows_thread_info *th = windows_process.find_thread (ptid);
if (th == NULL)
return 0;
if (addr != NULL)
@@ -1396,8 +1355,7 @@ bool
win32_process_target::stopped_by_sw_breakpoint ()
{
windows_thread_info *th
= windows_process.thread_rec (current_thread_ptid (),
DONT_INVALIDATE_CONTEXT);
= windows_process.find_thread (current_thread_ptid ());
return th == nullptr ? false : th->stopped_at_software_breakpoint;
}
@@ -1423,8 +1381,7 @@ const char *
win32_process_target::thread_name (ptid_t thread)
{
windows_thread_info *th
= windows_process.thread_rec (current_thread_ptid (),
DONT_INVALIDATE_CONTEXT);
= windows_process.find_thread (current_thread_ptid ());
return th->thread_name ();
}

View File

@@ -174,14 +174,15 @@ public:
struct gdbserver_windows_process : public windows_nat::windows_process_info
{
windows_nat::windows_thread_info *thread_rec
(ptid_t ptid,
windows_nat::thread_disposition_type disposition) override;
int handle_output_debug_string (struct target_waitstatus *ourstatus) override;
windows_nat::windows_thread_info *find_thread (ptid_t ptid) override;
bool handle_output_debug_string (const DEBUG_EVENT &current_event,
struct target_waitstatus *ourstatus) override;
void handle_load_dll (const char *dll_name, LPVOID base) override;
void handle_unload_dll () override;
void handle_unload_dll (const DEBUG_EVENT &current_event) override;
bool handle_access_violation (const EXCEPTION_RECORD *rec) override;
void fill_thread_context (windows_nat::windows_thread_info *th) override;
int attaching = 0;
/* A status that hasn't been reported to the core yet, and so
@@ -189,14 +190,6 @@ struct gdbserver_windows_process : public windows_nat::windows_process_info
debug event off the win32 API. */
struct target_waitstatus cached_status;
/* Non zero if an interrupt request is to be satisfied by suspending
all threads. */
int soft_interrupt_requested = 0;
/* Non zero if the inferior is stopped in a simulated breakpoint done
by suspending all the threads. */
int faked_breakpoint = 0;
/* True if current_process_handle needs to be closed. */
bool open_process_used = false;