aboutsummaryrefslogtreecommitdiff
path: root/tools/native-frame-capture/NativeFrameCapture.cs
diff options
context:
space:
mode:
Diffstat (limited to 'tools/native-frame-capture/NativeFrameCapture.cs')
-rw-r--r--tools/native-frame-capture/NativeFrameCapture.cs387
1 files changed, 347 insertions, 40 deletions
diff --git a/tools/native-frame-capture/NativeFrameCapture.cs b/tools/native-frame-capture/NativeFrameCapture.cs
index 2d2593a..47774f5 100644
--- a/tools/native-frame-capture/NativeFrameCapture.cs
+++ b/tools/native-frame-capture/NativeFrameCapture.cs
@@ -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;
- else
+ string identityEvidence;
+ if (!VerifyFrameTerminationIdentity(identityProcess, processId, expectedPath,
+ expectedStartTicks, out identityEvidence))
{
- fatal = true;
processExitUnconfirmed = true;
- Log("FATAL scratch termination was not confirmed before detach");
+ fatal = true;
+ Log("FINAL_TERMINATION_REFUSED identity=" + identityEvidence + " state=" + state);
+ }
+ else
+ {
+ 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))
+ {
+ processExitUnconfirmed = true;
+ Log("DETACH_FAILURE_TERMINATION_REFUSED identity=" + terminationEvidence);
+ }
+ else
{
- int terminateError = Marshal.GetLastWin32Error();
- Log("DETACH_FAILURE_TERMINATE_FAILED error=" + terminateError);
+ 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;
}
}
}