Fix native frame capture debugger cleanup
Docs Deploy / Build and Deploy MkDocs (push) Successful in 41s

This commit is contained in:
2026-10-12 00:32:51 +04:00
parent bb3ae9cc3a
commit 3d97aa5ba6
2 changed files with 350 additions and 41 deletions
+347 -40
View File
@@ -276,6 +276,9 @@ internal static partial class NativeFrameCapture
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool DebugActiveProcessStop(uint processId);
[DllImport("kernel32.dll")]
private static extern uint GetCurrentThreadId();
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool DebugSetProcessKillOnExit(bool killOnExit);
@@ -1717,6 +1720,7 @@ internal static partial class NativeFrameCapture
SelfCheckCameraInput();
SelfCheckNativeFogPairing();
SelfCheckRenderInvocationGates();
SelfCheckDebugEventHandleOwnership();
uint relocated = 0x10000000u + RelocatedGlobalRva;
if (relocated != 0x1079518Cu) throw new InvalidOperationException("relocation self-check failed");
bool offsetsMatch = Marshal.SizeOf(typeof(X86ContextLayout)) == X86ContextSize
@@ -2179,6 +2183,194 @@ internal static partial class NativeFrameCapture
throw new InvalidOperationException("Self-check failed: nullable fog JSON, unknown caller gate, or bounded sample list.");
}
private static void SelfCheckDebugEventHandleOwnership()
{
BreakpointInfo owned = new BreakpointInfo { Address = 0x12345678, Armed = true };
uint ownedStatus = FrameDebugEventContinueStatus(ExceptionDebugEvent, 0x80000003,
owned.Address, new BreakpointInfo[] { owned });
uint unrelatedStatus = FrameDebugEventContinueStatus(ExceptionDebugEvent, 0x80000003,
0x12345679, new BreakpointInfo[] { owned });
if (!ShouldExplicitlyCloseDebugEventFileHandle(CreateProcessDebugEvent)
|| ShouldExplicitlyCloseDebugEventFileHandle(CreateThreadDebugEvent)
|| !ShouldExplicitlyCloseDebugEventFileHandle(LoadDllDebugEvent)
|| ShouldExplicitlyCloseDebugEventFileHandle(ExitThreadDebugEvent)
|| ShouldExplicitlyCloseDebugEventFileHandle(ExitProcessDebugEvent)
|| ownedStatus != DbgContinue || unrelatedStatus != DbgExceptionNotHandled
|| FrameDebugEventContinueStatus(CreateThreadDebugEvent, 0, 0, null) != DbgContinue)
throw new InvalidOperationException("Debug-event handle ownership self-check failed.");
}
// Win32 debug-event process/thread handles are system-owned and closed on
// the matching EXIT event's ContinueDebugEvent. Only image-file handles
// are explicitly closed by the debugger. See:
// https://learn.microsoft.com/en-us/windows/win32/debug/writing-the-debugger-s-main-loop
// https://learn.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-continuedebugevent
private static bool ShouldExplicitlyCloseDebugEventFileHandle(uint eventCode)
{
return eventCode == CreateProcessDebugEvent || eventCode == LoadDllDebugEvent;
}
private static void CloseExplicitDebugEventFileHandle(IntPtr eventBuffer, uint eventCode)
{
if (!ShouldExplicitlyCloseDebugEventFileHandle(eventCode)) return;
IntPtr handle = Marshal.ReadIntPtr(eventBuffer, 12);
if (handle != IntPtr.Zero && !CloseHandle(handle))
Log("DEBUG_EVENT_FILE_HANDLE_CLOSE_FAILED code=" + eventCode + " offset=12"
+ " error=" + Marshal.GetLastWin32Error());
}
private static uint FrameDebugEventContinueStatus(uint eventCode, uint exceptionCode,
uint exceptionAddress, BreakpointInfo[] breakpoints)
{
if (eventCode != ExceptionDebugEvent) return DbgContinue;
if (exceptionCode == 0x80000003 && breakpoints != null)
{
for (int i = 0; i < breakpoints.Length; i++)
if (breakpoints[i] != null && breakpoints[i].Armed
&& breakpoints[i].Address == exceptionAddress)
return DbgContinue;
}
return DbgExceptionNotHandled;
}
private static bool VerifyFrameTerminationIdentity(Process identity, uint processId,
string expectedPath, long expectedStartTicks, out string evidence)
{
evidence = "identity_unavailable";
try
{
if (identity == null) return false;
identity.Refresh();
if (identity.HasExited) { evidence = "already_exited"; return false; }
string actualPath = Path.GetFullPath(identity.MainModule.FileName);
long actualTicks = identity.StartTime.ToUniversalTime().Ticks;
if ((uint)identity.Id != processId || identity.ProcessName != "iron_3d"
|| actualTicks != expectedStartTicks
|| !String.Equals(actualPath, expectedPath, StringComparison.OrdinalIgnoreCase))
{
evidence = "pid=" + identity.Id + ";path=" + actualPath + ";startTicks=" + actualTicks;
return false;
}
string hash = HashFile(actualPath);
if (!String.Equals(hash, ExpectedSha256[GameExe], StringComparison.OrdinalIgnoreCase))
{
evidence = "pid=" + processId + ";path=" + actualPath + ";startTicks=" + actualTicks + ";sha256=" + hash;
return false;
}
evidence = "pid=" + processId + ";path=" + actualPath + ";startTicks=" + actualTicks + ";sha256=" + hash;
return true;
}
catch (Exception error)
{
evidence = error.GetType().Name + ":" + error.Message;
return false;
}
}
private static bool DrainFrameTerminationEvents(IntPtr process, IntPtr eventBuffer, uint processId,
ref bool eventOutstanding, ref uint outstandingPid, ref uint outstandingTid,
ref uint outstandingCode, ref uint outstandingContinueStatus, ref bool outstandingFileHandleClosed,
BreakpointInfo[] breakpoints, out string evidence)
{
DateTime deadline = DateTime.UtcNow.AddSeconds(5);
bool targetExitEventContinued = false;
uint drainThreadId = GetCurrentThreadId();
if (eventOutstanding)
{
targetExitEventContinued = outstandingCode == ExitProcessDebugEvent && outstandingPid == processId;
if (!outstandingFileHandleClosed)
{
CloseExplicitDebugEventFileHandle(eventBuffer, outstandingCode);
outstandingFileHandleClosed = true;
}
if (!ContinueDebugEvent(outstandingPid, outstandingTid, outstandingContinueStatus))
{
int error = Marshal.GetLastWin32Error();
evidence = "continue_outstanding_failed;code=" + outstandingCode + ";pid=" + outstandingPid
+ ";tid=" + outstandingTid + ";status=0x" + outstandingContinueStatus.ToString("X8")
+ ";osThreadId=" + GetCurrentThreadId() + ";error=" + error;
return false;
}
Log("FRAME_TERMINATION_OUTSTANDING_EVENT_CONTINUED code=" + outstandingCode
+ " pid=" + outstandingPid + " tid=" + outstandingTid
+ " status=0x" + outstandingContinueStatus.ToString("X8")
+ " osThreadId=" + GetCurrentThreadId() + " targetExitEvent=" + targetExitEventContinued);
eventOutstanding = false;
outstandingPid = outstandingTid = outstandingCode = 0;
outstandingContinueStatus = DbgContinue;
outstandingFileHandleClosed = false;
}
while (!targetExitEventContinued && DateTime.UtcNow < deadline)
{
uint waitThreadId = GetCurrentThreadId();
int remaining = (int)Math.Max(1, Math.Min(250, (deadline - DateTime.UtcNow).TotalMilliseconds));
if (!WaitForDebugEvent(eventBuffer, (uint)remaining))
{
int error = Marshal.GetLastWin32Error();
if (error == 121 || error == 258) continue;
evidence = "wait_for_exit_event_failed;osThreadId=" + GetCurrentThreadId() + ";error=" + error;
return false;
}
uint code = unchecked((uint)Marshal.ReadInt32(eventBuffer, 0));
uint pid = unchecked((uint)Marshal.ReadInt32(eventBuffer, 4));
uint tid = unchecked((uint)Marshal.ReadInt32(eventBuffer, 8));
uint exceptionCode = code == ExceptionDebugEvent
? unchecked((uint)Marshal.ReadInt32(eventBuffer, 12)) : 0;
uint exceptionAddress = code == ExceptionDebugEvent
? unchecked((uint)Marshal.ReadInt32(eventBuffer, 24)) : 0;
uint status = FrameDebugEventContinueStatus(code, exceptionCode, exceptionAddress, breakpoints);
eventOutstanding = true;
outstandingPid = pid;
outstandingTid = tid;
outstandingCode = code;
outstandingContinueStatus = status;
outstandingFileHandleClosed = false;
Log("FRAME_TERMINATION_DEBUG_EVENT code=" + code + " pid=" + pid + " tid=" + tid
+ " waitOsThreadId=" + waitThreadId + " continueStatus=0x" + status.ToString("X8")
+ (code == ExceptionDebugEvent ? " exception=0x" + exceptionCode.ToString("X8")
+ " address=0x" + exceptionAddress.ToString("X8") : ""));
CloseExplicitDebugEventFileHandle(eventBuffer, code);
outstandingFileHandleClosed = true;
uint continueThreadId = GetCurrentThreadId();
if (!ContinueDebugEvent(pid, tid, status))
{
int error = Marshal.GetLastWin32Error();
evidence = "continue_exit_drain_event_failed;code=" + code + ";pid=" + pid + ";tid=" + tid
+ ";status=0x" + status.ToString("X8") + ";osThreadId=" + continueThreadId + ";error=" + error;
return false;
}
Log("FRAME_TERMINATION_DEBUG_EVENT_CONTINUED code=" + code + " pid=" + pid + " tid=" + tid
+ " status=0x" + status.ToString("X8") + " osThreadId=" + continueThreadId);
eventOutstanding = false;
outstandingPid = outstandingTid = outstandingCode = 0;
outstandingContinueStatus = DbgContinue;
outstandingFileHandleClosed = false;
if (code == ExitProcessDebugEvent && pid == processId) targetExitEventContinued = true;
}
if (!targetExitEventContinued)
{
evidence = "target_exit_process_event_not_drained_within_5s;osThreadId=" + drainThreadId;
return false;
}
uint waitResult = WaitForSingleObject(process, 2000);
uint exitCode;
bool exitRead = GetExitCodeProcess(process, out exitCode);
if (waitResult != 0)
{
evidence = "exit_event_continued_but_process_not_signaled;waitResult=0x" + waitResult.ToString("X8")
+ ";exitCode=" + (exitRead ? "0x" + exitCode.ToString("X8") : "unreadable")
+ ";osThreadId=" + GetCurrentThreadId();
return false;
}
evidence = "targetExitEventContinued=true;waitResult=0x" + waitResult.ToString("X8")
+ ";exitCode=" + (exitRead ? "0x" + exitCode.ToString("X8") : "unreadable")
+ ";osThreadId=" + GetCurrentThreadId();
return exitRead;
}
private static int Main(string[] args)
{
try
@@ -2284,6 +2476,7 @@ internal static partial class NativeFrameCapture
bool fatal = false;
bool processExited = false;
bool processExitUnconfirmed = false;
int detachError = 0;
bool captured = false;
SurfaceReadback completedSurface = null;
FrameSnapshot completedFrame = null;
@@ -2310,6 +2503,13 @@ internal static partial class NativeFrameCapture
PendingStep pendingStep = null;
RemoteReadbackSession remote = null;
RemoteCameraSetterSession cameraSetter = null;
bool eventOutstanding = false, outstandingFileHandleClosed = false;
uint outstandingPid = 0, outstandingTid = 0, outstandingCode = 0, outstandingContinueStatus = DbgContinue;
uint attachOsThreadId = 0, lastWaitOsThreadId = 0, lastContinueOsThreadId = 0;
uint debugEventsSeen = 0, debugEventsContinued = 0, lastDebugEventCode = 0;
uint debugCreateThreadEvents = 0, debugExitThreadEvents = 0, debugLoadDllEvents = 0;
uint debugExceptionEvents = 0, debugSingleStepEvents = 0, lastDebugEventPid = 0, lastDebugEventTid = 0;
const uint ProcessAccessForFrameCapture = 0x0001 | 0x0008 | 0x0010 | 0x0020 | 0x0400 | 0x00100000;
FrameSnapshot frame = new FrameSnapshot();
frame.SelectedCameraRequested = selectedCameraInput != null;
SelectedCameraState selectedCamera = selectedCameraInput == null ? null
@@ -2321,19 +2521,27 @@ internal static partial class NativeFrameCapture
|| !String.Equals(Path.GetFullPath(identityProcess.MainModule.FileName), expectedPath,
StringComparison.OrdinalIgnoreCase))
throw new InvalidOperationException("The retained process identity changed before debugger attach.");
process = OpenProcess(ProcessAccessForFrameCapture, false, processId);
if (process == IntPtr.Zero) throw new InvalidOperationException("OpenProcess failed: " + Marshal.GetLastWin32Error());
eventBuffer = Marshal.AllocHGlobal(128);
attachOsThreadId = GetCurrentThreadId();
Log("DEBUG_ATTACH_REQUEST osThreadId=" + attachOsThreadId + " pid=" + processId
+ " processAccess=0x" + ProcessAccessForFrameCapture.ToString("X8")
+ " openProcessBeforeAttach=true threadContextAccess=0x" + (ThreadGetContext | ThreadSetContext).ToString("X8"));
if (!DebugActiveProcess(processId))
throw new InvalidOperationException("DebugActiveProcess failed: " + Marshal.GetLastWin32Error());
attached = true;
Log("DEBUG_ATTACH_RESULT success=true osThreadId=" + GetCurrentThreadId() + " pid=" + processId);
if (identityProcess.HasExited || identityProcess.StartTime.ToUniversalTime().Ticks != expectedStartTicks
|| !String.Equals(Path.GetFullPath(identityProcess.MainModule.FileName), expectedPath,
StringComparison.OrdinalIgnoreCase))
throw new InvalidOperationException("The retained process identity changed during debugger attach.");
if (!DebugSetProcessKillOnExit(false))
throw new InvalidOperationException("DebugSetProcessKillOnExit(false) failed: " + Marshal.GetLastWin32Error());
process = OpenProcess(ProcessTerminate | ProcessVmOperation | ProcessVmRead | ProcessVmWrite
| ProcessQueryInformation | Synchronize, false, processId);
if (process == IntPtr.Zero) throw new InvalidOperationException("OpenProcess failed: " + Marshal.GetLastWin32Error());
eventBuffer = Marshal.AllocHGlobal(128);
Log("DEBUG_KILL_ON_EXIT_DISABLED osThreadId=" + GetCurrentThreadId());
Log("DEBUG_ACCESS processMask=0x" + ProcessAccessForFrameCapture.ToString("X8")
+ " threadContextMask=0x" + (ThreadGetContext | ThreadSetContext).ToString("X8")
+ " remoteThreadCreation=false");
Log("frame capture attached; waiting for verified camera/projection/present boundaries for "
+ timeoutSeconds + " seconds");
@@ -2379,6 +2587,7 @@ internal static partial class NativeFrameCapture
fatal = true;
continue;
}
lastWaitOsThreadId = GetCurrentThreadId();
if (!WaitForDebugEvent(eventBuffer, 1000))
{
int waitError = Marshal.GetLastWin32Error();
@@ -2389,6 +2598,34 @@ internal static partial class NativeFrameCapture
uint eventCode = unchecked((uint)Marshal.ReadInt32(eventBuffer, 0));
uint eventPid = unchecked((uint)Marshal.ReadInt32(eventBuffer, 4));
uint eventTid = unchecked((uint)Marshal.ReadInt32(eventBuffer, 8));
uint eventExceptionCode = eventCode == ExceptionDebugEvent
? unchecked((uint)Marshal.ReadInt32(eventBuffer, 12)) : 0;
uint eventExceptionAddress = eventCode == ExceptionDebugEvent
? unchecked((uint)Marshal.ReadInt32(eventBuffer, 24)) : 0;
eventOutstanding = true;
outstandingPid = eventPid;
outstandingTid = eventTid;
outstandingCode = eventCode;
outstandingContinueStatus = DbgContinue;
outstandingFileHandleClosed = false;
debugEventsSeen++;
lastDebugEventCode = eventCode;
lastDebugEventPid = eventPid;
lastDebugEventTid = eventTid;
if (eventCode == CreateThreadDebugEvent) debugCreateThreadEvents++;
else if (eventCode == ExitThreadDebugEvent) debugExitThreadEvents++;
else if (eventCode == LoadDllDebugEvent) debugLoadDllEvents++;
else if (eventCode == ExceptionDebugEvent)
{
debugExceptionEvents++;
if (eventExceptionCode == 0x80000004) debugSingleStepEvents++;
}
if (eventCode != ExceptionDebugEvent || eventExceptionCode != 0x80000004)
Log("DEBUG_EVENT sequence=" + debugEventsSeen + " code=" + eventCode
+ " pid=" + eventPid + " tid=" + eventTid
+ " exception=0x" + eventExceptionCode.ToString("X8")
+ " address=0x" + eventExceptionAddress.ToString("X8")
+ " waitOsThreadId=" + lastWaitOsThreadId);
uint continueStatus = DbgContinue;
bool shouldStop = false;
BreakpointInfo hitBreakpoint = null;
@@ -2398,21 +2635,17 @@ internal static partial class NativeFrameCapture
if (eventCode == CreateProcessDebugEvent)
{
IntPtr imageFile = Marshal.ReadIntPtr(eventBuffer, 12);
IntPtr processHandle = Marshal.ReadIntPtr(eventBuffer, 16);
IntPtr threadHandle = Marshal.ReadIntPtr(eventBuffer, 20);
string imagePath = PathForHandle(imageFile);
if (imagePath.Length != 0 && !String.Equals(imagePath, NormalizePath(expectedPath), StringComparison.OrdinalIgnoreCase))
throw new InvalidOperationException("CREATE_PROCESS image path mismatch: " + imagePath);
if (processHandle != IntPtr.Zero) CloseHandle(processHandle);
if (threadHandle != IntPtr.Zero) CloseHandle(threadHandle);
if (imageFile != IntPtr.Zero) CloseHandle(imageFile);
Log("CREATE_PROCESS path=" + imagePath);
CloseExplicitDebugEventFileHandle(eventBuffer, eventCode);
outstandingFileHandleClosed = true;
Log("CREATE_PROCESS path=" + imagePath
+ " debuggerOwnedProcessInitialThreadHandles=retainedForSystemExitCleanup");
}
else if (eventCode == CreateThreadDebugEvent)
{
IntPtr threadHandle = Marshal.ReadIntPtr(eventBuffer, 12);
if (threadHandle != IntPtr.Zero) CloseHandle(threadHandle);
Log("CREATE_THREAD tid=" + eventTid + " handleClosed=" + (threadHandle != IntPtr.Zero));
Log("CREATE_THREAD tid=" + eventTid + " debuggerOwnedThreadHandle=retainedForSystemExitCleanup");
}
else if (eventCode == ExitThreadDebugEvent)
{
@@ -2510,7 +2743,11 @@ internal static partial class NativeFrameCapture
presentOriginalByte, ref presentBreakpoint);
}
}
finally { if (imageFile != IntPtr.Zero) CloseHandle(imageFile); }
finally
{
CloseExplicitDebugEventFileHandle(eventBuffer, eventCode);
outstandingFileHandleClosed = true;
}
}
else if (eventCode == ExceptionDebugEvent)
{
@@ -3295,14 +3532,28 @@ internal static partial class NativeFrameCapture
}
}
outstandingContinueStatus = continueStatus;
lastContinueOsThreadId = GetCurrentThreadId();
if (!ContinueDebugEvent(eventPid, eventTid, continueStatus))
{
fatal = true;
Log("FATAL ContinueDebugEvent failed=" + Marshal.GetLastWin32Error());
int continueError = Marshal.GetLastWin32Error();
Log("FATAL ContinueDebugEvent failed=" + continueError + " code=" + eventCode
+ " pid=" + eventPid + " tid=" + eventTid + " status=0x" + continueStatus.ToString("X8")
+ " osThreadId=" + lastContinueOsThreadId);
if (process != IntPtr.Zero && !TerminateProcess(process, 0xE009))
Log("FATAL ContinueDebugEvent recovery terminate failed=" + Marshal.GetLastWin32Error());
break;
}
eventOutstanding = false;
outstandingPid = outstandingTid = outstandingCode = 0;
outstandingContinueStatus = DbgContinue;
outstandingFileHandleClosed = false;
debugEventsContinued++;
if (eventCode != ExceptionDebugEvent || eventExceptionCode != 0x80000004)
Log("DEBUG_CONTINUE_OK sequence=" + debugEventsContinued + " code=" + eventCode
+ " pid=" + eventPid + " tid=" + eventTid + " status=0x" + continueStatus.ToString("X8")
+ " osThreadId=" + lastContinueOsThreadId);
if (shouldStop || CanStopCapture(captured, stopAfterAttempt, remote != null || cameraSetter != null,
pendingStep != null, selectedCamera != null && selectedCamera.Applied && !selectedCamera.RestoreVerified)) break;
}
@@ -3316,7 +3567,7 @@ internal static partial class NativeFrameCapture
{
bool cameraMutationUnrestored = selectedCamera != null && selectedCamera.Applied && !selectedCamera.RestoreVerified;
if (!processExited && process != IntPtr.Zero && (remote != null || cameraSetter != null || pendingStep != null
|| cameraMutationUnrestored
|| cameraMutationUnrestored || eventOutstanding
|| cameraBreakpoint != null && cameraBreakpoint.Armed
|| projectionBreakpoint != null && projectionBreakpoint.Armed
|| returnBreakpoint != null && returnBreakpoint.Armed
@@ -3328,26 +3579,63 @@ internal static partial class NativeFrameCapture
string state = remote != null ? "REMOTE_STUB_IN_FLIGHT" : (cameraSetter != null ? "CAMERA_SETTER_IN_FLIGHT"
: (cameraMutationUnrestored ? "SELECTED_CAMERA_MUTATION_NOT_RESTORED"
: (pendingStep != null ? "SINGLE_STEP_IN_FLIGHT" : "ARMED_BREAKPOINTS_WITHOUT_STOPPED_EVENT")));
Log("FINAL_" + state + "; terminating only the path/tick/hash-verified scratch process before detach");
if (!TerminateProcess(process, 0xE006))
Log("FINAL_TERMINATE_FAILED error=" + Marshal.GetLastWin32Error());
if (WaitForSingleObject(process, 2000) == 0) processExited = true;
string identityEvidence;
if (!VerifyFrameTerminationIdentity(identityProcess, processId, expectedPath,
expectedStartTicks, out identityEvidence))
{
processExitUnconfirmed = true;
fatal = true;
Log("FINAL_TERMINATION_REFUSED identity=" + identityEvidence + " state=" + state);
}
else
{
fatal = true;
processExitUnconfirmed = true;
Log("FATAL scratch termination was not confirmed before detach");
Log("FINAL_" + state + "; terminating verified scratch before detach identity=" + identityEvidence);
if (!TerminateProcess(process, 0xE006))
Log("FINAL_TERMINATE_FAILED error=" + Marshal.GetLastWin32Error());
string drainEvidence;
if (DrainFrameTerminationEvents(process, eventBuffer, processId,
ref eventOutstanding, ref outstandingPid, ref outstandingTid, ref outstandingCode,
ref outstandingContinueStatus, ref outstandingFileHandleClosed,
new BreakpointInfo[] { cameraBreakpoint, projectionBreakpoint, returnBreakpoint,
atmosphereBreakpoint, fogConsumerBreakpoint, presentBreakpoint, missionBreakpoint },
out drainEvidence))
{
processExited = true;
Log("FINAL_TERMINATION_DRAIN_CONFIRMED " + drainEvidence);
}
else
{
fatal = true;
processExitUnconfirmed = true;
Log("FATAL_FINAL_TERMINATION_DRAIN_FAILED " + drainEvidence);
}
}
}
if (attached && !processExitUnconfirmed)
{
Log("DEBUG_DETACH_REQUEST osThreadId=" + GetCurrentThreadId()
+ " attachOsThreadId=" + attachOsThreadId + " processId=" + processId
+ " eventOutstanding=" + eventOutstanding + " outstandingCode=" + outstandingCode
+ " outstandingPid=" + outstandingPid + " outstandingTid=" + outstandingTid
+ " outstandingContinueStatus=0x" + outstandingContinueStatus.ToString("X8")
+ " waitOsThreadId=" + lastWaitOsThreadId + " lastContinueOsThreadId=" + lastContinueOsThreadId
+ " lastEventCode=" + lastDebugEventCode + " lastEventPid=" + lastDebugEventPid
+ " lastEventTid=" + lastDebugEventTid + " eventsSeen=" + debugEventsSeen
+ " eventsContinued=" + debugEventsContinued + " createThreads=" + debugCreateThreadEvents
+ " exitThreads=" + debugExitThreadEvents + " dllLoads=" + debugLoadDllEvents
+ " exceptions=" + debugExceptionEvents + " singleSteps=" + debugSingleStepEvents);
detached = DebugActiveProcessStop(processId);
if (detached) Log("DEBUG_DETACHED");
if (detached) Log("DEBUG_DETACHED osThreadId=" + GetCurrentThreadId());
else
{
fatal = true;
int detachError = Marshal.GetLastWin32Error();
Log("FATAL DebugActiveProcessStop failed=" + detachError);
detachError = Marshal.GetLastWin32Error();
Log("FATAL DebugActiveProcessStop failed=" + detachError
+ " osThreadId=" + GetCurrentThreadId() + " eventOutstanding=" + eventOutstanding
+ " eventsSeen=" + debugEventsSeen + " eventsContinued=" + debugEventsContinued
+ " lastEventCode=" + lastDebugEventCode + " lastEventPid=" + lastDebugEventPid
+ " lastEventTid=" + lastDebugEventTid + " lastWaitOsThreadId=" + lastWaitOsThreadId
+ " lastContinueOsThreadId=" + lastContinueOsThreadId);
if (process != IntPtr.Zero)
{
uint waitBeforeRecovery = WaitForSingleObject(process, 0);
@@ -3360,22 +3648,41 @@ internal static partial class NativeFrameCapture
+ " waitError=" + waitBeforeError + " exitError=" + exitBeforeError);
if (waitBeforeRecovery == 0x00000102 && exitRead && exitBeforeRecovery == 0x00000103)
{
Log("DETACH_FAILURE_TARGET_STILL_ACTIVE; terminating only exact verified scratch after state snapshot");
if (!TerminateProcess(process, 0xE00A))
string terminationEvidence;
if (!VerifyFrameTerminationIdentity(identityProcess, processId, expectedPath,
expectedStartTicks, out terminationEvidence))
{
int terminateError = Marshal.GetLastWin32Error();
Log("DETACH_FAILURE_TERMINATE_FAILED error=" + terminateError);
processExitUnconfirmed = true;
Log("DETACH_FAILURE_TERMINATION_REFUSED identity=" + terminationEvidence);
}
else
{
Log("DETACH_FAILURE_TARGET_STILL_ACTIVE; terminating verified scratch identity=" + terminationEvidence);
if (!TerminateProcess(process, 0xE00A))
Log("DETACH_FAILURE_TERMINATE_FAILED error=" + Marshal.GetLastWin32Error());
string drainEvidence;
if (DrainFrameTerminationEvents(process, eventBuffer, processId,
ref eventOutstanding, ref outstandingPid, ref outstandingTid, ref outstandingCode,
ref outstandingContinueStatus, ref outstandingFileHandleClosed,
new BreakpointInfo[] { cameraBreakpoint, projectionBreakpoint, returnBreakpoint,
atmosphereBreakpoint, fogConsumerBreakpoint, presentBreakpoint, missionBreakpoint },
out drainEvidence))
{
processExited = true;
Log("DETACH_FAILURE_TERMINATION_DRAIN_CONFIRMED " + drainEvidence);
uint detachThreadId = GetCurrentThreadId();
bool detachedAfterExit = DebugActiveProcessStop(processId);
int detachAfterExitError = detachedAfterExit ? 0 : Marshal.GetLastWin32Error();
Log("DEBUG_DETACH_AFTER_TERMINATION success=" + detachedAfterExit
+ " osThreadId=" + detachThreadId + " error=" + detachAfterExitError);
detached = detachedAfterExit;
}
else
{
processExitUnconfirmed = true;
Log("DETACH_FAILURE_TERMINATION_DRAIN_FAILED " + drainEvidence);
}
}
uint waitAfterRecovery = WaitForSingleObject(process, 2000);
int waitAfterError = waitAfterRecovery == 0xFFFFFFFF ? Marshal.GetLastWin32Error() : 0;
uint exitAfterRecovery;
bool exitAfterRead = GetExitCodeProcess(process, out exitAfterRecovery);
int exitAfterError = exitAfterRead ? 0 : Marshal.GetLastWin32Error();
Log("PROCESS_STATE_AFTER_DETACH_FAILURE_RECOVERY waitResult=0x" + waitAfterRecovery.ToString("X8")
+ " exitCode=" + (exitAfterRead ? "0x" + exitAfterRecovery.ToString("X8") : "unreadable")
+ " waitError=" + waitAfterError + " exitError=" + exitAfterError);
if (waitAfterRecovery == 0) processExited = true;
else processExitUnconfirmed = true;
}
}
}