102 Patches: Detours to the Rescue
C reference for DttR maintainers and modders.
Loading...
Searching...
No Matches
watchdog.c
Go to the documentation of this file.
1#include <dttr_config.h>
2#include <dttr_crashdump.h>
3#include <dttr_errors.h>
4#include <dttr_loader.h>
5#include <dttr_log.h>
6#include <stdbool.h>
7#include <stdint.h>
8#include <string.h>
9#include <windows.h>
10
11enum { WATCHDOG_TIMEOUT_MS = 30000 };
12static const char WATCHDOG_SENTINEL[] = "DTTR_SIDECAR_ENTRYPOINT";
13
14typedef BOOL(WINAPI *is_wow64_process2_fn)(HANDLE, USHORT *, USHORT *);
15
16static bool watchdog_attached = false;
17
18static void detach_watchdog(DWORD process_id) {
19 if (!watchdog_attached) {
20 return;
21 }
22
23 DebugActiveProcessStop(process_id);
24 watchdog_attached = false;
25 DTTR_LOG_DEBUG("Watchdog detached");
26}
27
29 const HMODULE kernel32 = GetModuleHandleA("kernel32.dll");
31 is_wow64_process2 = (is_wow64_process2_fn)(kernel32 ? GetProcAddress(
32 kernel32,
33 "IsWow64Process2"
34 )
35 : NULL);
36
37 if (is_wow64_process2) {
38 uint16_t process_machine = IMAGE_FILE_MACHINE_UNKNOWN;
39
40 uint16_t native_machine = IMAGE_FILE_MACHINE_UNKNOWN;
41 if (is_wow64_process2(GetCurrentProcess(), &process_machine, &native_machine)) {
43 "Watchdog host machine detection: process=0x%X native=0x%X",
44 process_machine,
45 native_machine
46 );
47 return native_machine == IMAGE_FILE_MACHINE_ARM64;
48 }
49 }
50
51 SYSTEM_INFO system_info = {0};
52 GetNativeSystemInfo(&system_info);
54 "Watchdog fallback architecture detection: native_arch=0x%X",
55 system_info.wProcessorArchitecture
56 );
57
58 return system_info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM64;
59}
60
61static void write_child_dump(HANDLE process, DWORD pid, DWORD tid, DWORD exception_code) {
62 HANDLE thread = OpenThread(THREAD_ALL_ACCESS, FALSE, tid);
63 if (!thread) {
64 DTTR_ERROR("Failed to open crashing thread %lu", tid);
65 return;
66 }
67
68 CONTEXT thread_context = {.ContextFlags = CONTEXT_ALL};
69 if (!GetThreadContext(thread, &thread_context)) {
70 CloseHandle(thread);
71 DTTR_ERROR("Failed to read crashing thread %lu context", tid);
72 return;
73 }
74
75 EXCEPTION_RECORD fake_record = {.ExceptionCode = exception_code};
76 EXCEPTION_POINTERS ptrs = {
77 .ExceptionRecord = &fake_record,
78 .ContextRecord = &thread_context,
79 };
80
81 sds filename = DTTR_CrashDump_Write(process, pid, tid, &ptrs);
82 sds stack_trace = DTTR_CrashDump_FormatStackTrace(process, thread, &thread_context);
83 CloseHandle(thread);
84
85 sds summary = sdsempty();
86 if (filename) {
87 summary = sdscatprintf(
88 summary,
89 "Game crashed (exception 0x%08lX). Crash dump written to %s.",
90 exception_code,
91 filename
92 );
93 sdsfree(filename);
94 } else {
95 summary = sdscatprintf(
96 summary,
97 "Game crashed (exception 0x%08lX). Failed to write crash dump.",
98 exception_code
99 );
100 }
101
102 sds report_message = DTTR_CrashDump_AppendReportMessage(summary, stack_trace);
103
104 sdsfree(stack_trace);
105
106 DTTR_CrashDump_LogAndTraceReport(report_message);
107
108 if (dttr_config.show_crash_popup) {
110 }
111
112 sdsfree(report_message);
113}
114
115void DTTR_Loader_WatchdogAttach(const PROCESS_INFORMATION *child_info) {
116 watchdog_attached = false;
117
120 "Skipping watchdog debugger because debugging is not available on this "
121 "machine"
122 );
123 return;
124 }
125
126 if (!DebugActiveProcess(child_info->dwProcessId)) {
128 "Could not attach debugger to child process; skipping early crash detection"
129 );
130 return;
131 }
132
133 watchdog_attached = true;
134 DTTR_LOG_DEBUG("Watchdog attached to PID %lu", child_info->dwProcessId);
135}
136
137void DTTR_Loader_WatchdogDetach(const PROCESS_INFORMATION *child_info) {
138 detach_watchdog(child_info->dwProcessId);
139}
140
141static bool is_sentinel(HANDLE process, const OUTPUT_DEBUG_STRING_INFO *info) {
142 if (info->fUnicode || info->nDebugStringLength < sizeof(WATCHDOG_SENTINEL)) {
143 return false;
144 }
145
146 char buf[sizeof(WATCHDOG_SENTINEL)];
147 SIZE_T bytes_read = 0;
148
149 if (!ReadProcessMemory(
150 process,
151 info->lpDebugStringData,
152 buf,
153 sizeof(buf),
154 &bytes_read
155 )) {
156 return false;
157 }
158
159 return bytes_read == sizeof(WATCHDOG_SENTINEL)
160 && memcmp(buf, WATCHDOG_SENTINEL, sizeof(WATCHDOG_SENTINEL)) == 0;
161}
162
163bool DTTR_Loader_WatchdogWait(const PROCESS_INFORMATION *child_info) {
164 if (!watchdog_attached) {
165 DTTR_LOG_DEBUG("Watchdog not attached; skipping early crash monitoring");
166 return true;
167 }
168
170 "Watching for early crash or ready sentinel (timeout=%dms)",
172 );
173
174 DEBUG_EVENT evt = {0};
175 DWORD remaining = WATCHDOG_TIMEOUT_MS;
176 bool saw_sentinel = false;
177 bool saw_failure = false;
178
179 while (remaining > 0) {
180 const DWORD start = GetTickCount();
181
182 if (!WaitForDebugEvent(&evt, remaining)) {
183 break;
184 }
185
186 DWORD continue_status = DBG_CONTINUE;
187 bool done = false;
188
189 switch (evt.dwDebugEventCode) {
190 case EXCEPTION_DEBUG_EVENT: {
191 const DWORD code = evt.u.Exception.ExceptionRecord.ExceptionCode;
192
193 if (evt.u.Exception.dwFirstChance) {
194 if (code != EXCEPTION_BREAKPOINT) {
195 continue_status = DBG_EXCEPTION_NOT_HANDLED;
196 }
197
198 break;
199 }
200
202 child_info->hProcess,
203 child_info->dwProcessId,
204 evt.dwThreadId,
205 code
206 );
207 saw_failure = true;
208 done = true;
209 break;
210 }
211
212 case OUTPUT_DEBUG_STRING_EVENT:
213 if (is_sentinel(child_info->hProcess, &evt.u.DebugString)) {
214 DTTR_LOG_INFO("Sidecar confirmed entrypoint entered!");
215 saw_sentinel = true;
216 done = true;
217 }
218
219 break;
220
221 case CREATE_PROCESS_DEBUG_EVENT:
222 if (evt.u.CreateProcessInfo.hFile) {
223 CloseHandle(evt.u.CreateProcessInfo.hFile);
224 }
225
226 break;
227
228 case LOAD_DLL_DEBUG_EVENT:
229 if (evt.u.LoadDll.hFile) {
230 CloseHandle(evt.u.LoadDll.hFile);
231 }
232
233 break;
234
235 case EXIT_PROCESS_DEBUG_EVENT:
236 saw_failure = true;
238 "Game exited unexpectedly within %ds (code %lu)." DTTR_REPORT_SUFFIX,
239 WATCHDOG_TIMEOUT_MS / 1000,
240 evt.u.ExitProcess.dwExitCode
241 );
242 done = true;
243 break;
244
245 default:
246 break;
247 }
248
249 ContinueDebugEvent(evt.dwProcessId, evt.dwThreadId, continue_status);
250
251 if (done) {
252 break;
253 }
254
255 const DWORD elapsed = GetTickCount() - start;
256 remaining -= (elapsed < remaining) ? elapsed : remaining;
257 }
258
259 detach_watchdog(child_info->dwProcessId);
260 if (!saw_sentinel && !saw_failure) {
262 "Sidecar did not report entrypoint within %ds; aborting "
263 "launch." DTTR_REPORT_SUFFIX,
265 );
266 return false;
267 }
268
269 return saw_sentinel && !saw_failure;
270}
DTTR_Graphics_COM_Direct3DDevice7 DWORD block DTTR_Graphics_COM_Direct3DDevice7 DWORD block DTTR_Graphics_COM_Direct3DDevice7 void void void void DWORD f BOOL
DTTR_Graphics_COM_Direct3DDevice7 DWORD block DTTR_Graphics_COM_Direct3DDevice7 DWORD block DTTR_Graphics_COM_Direct3DDevice7 void void void void DWORD f FALSE
DTTR_Graphics_COM_DirectDrawSurface7 DWORD flags void NULL
DTTR_Config dttr_config
Definition defaults.c:53
void DTTR_CrashDump_LogAndTraceReport(const char *message)
Definition crashdump.c:486
sds DTTR_CrashDump_Write(HANDLE process, DWORD pid, DWORD tid, EXCEPTION_POINTERS *exception_info)
Definition crashdump.c:428
sds DTTR_CrashDump_FormatStackTrace(HANDLE process, HANDLE thread, const CONTEXT *context)
Formats a stack trace from a thread context. Caller frees the returned sds.
Definition crashdump.c:496
sds DTTR_CrashDump_AppendReportMessage(sds message, const char *stack_trace)
Definition crashdump.c:416
#define DTTR_ERROR_TITLE
Definition dttr_errors.h:15
#define DTTR_REPORT_SUFFIX
Definition dttr_errors.h:26
#define DTTR_ERROR(error_message,...)
Definition dttr_errors.h:18
void DTTR_Errors_ShowMessage(const char *title, const char *message)
Definition errors.c:10
#define DTTR_LOG_DEBUG(...)
Definition dttr_log.h:28
#define DTTR_LOG_WARN(...)
Definition dttr_log.h:30
#define DTTR_LOG_INFO(...)
Definition dttr_log.h:29
static bool is_sentinel(HANDLE process, const OUTPUT_DEBUG_STRING_INFO *info)
Definition watchdog.c:141
void DTTR_Loader_WatchdogDetach(const PROCESS_INFORMATION *child_info)
Definition watchdog.c:137
static const char WATCHDOG_SENTINEL[]
Definition watchdog.c:12
BOOL(* is_wow64_process2_fn)(HANDLE, USHORT *, USHORT *)
Definition watchdog.c:14
static void detach_watchdog(DWORD process_id)
Definition watchdog.c:18
static bool watchdog_attached
Definition watchdog.c:16
static void write_child_dump(HANDLE process, DWORD pid, DWORD tid, DWORD exception_code)
Definition watchdog.c:61
bool DTTR_Loader_WatchdogWait(const PROCESS_INFORMATION *child_info)
Definition watchdog.c:163
void DTTR_Loader_WatchdogAttach(const PROCESS_INFORMATION *child_info)
Definition watchdog.c:115
@ WATCHDOG_TIMEOUT_MS
Definition watchdog.c:11
static bool should_disable_watchdog()
Definition watchdog.c:28