8#include "../macros.hpp"
9#include "../win32/win32.hpp"
10#include "console_win32.hpp"
11#include "exception.hpp"
12#include "debugger_intf.hpp"
13#include "debugger_utils.hpp"
24hi_export_module(hikogui.utility.debugger : impl);
29hi_warning_ignore_msvc(6320);
31hi_export
namespace hi {
38 DWORD dwProcessorArchitecture;
41 ULONG64 lpExceptionAddress;
42 ULONG64 lpExceptionRecord;
43 ULONG64 lpContextRecord;
48inline HANDLE jit_debug_handle = NULL;
58inline bool launch_jit_debugger() noexcept
60 using namespace std::literals;
62 auto debugger_enabled = win32_RegGetValue<std::string>(
63 HKEY_LOCAL_MACHINE,
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug",
"Auto");
64 if (not debugger_enabled or *debugger_enabled !=
"1") {
69 auto debugger = win32_RegGetValue<std::string>(
70 HKEY_LOCAL_MACHINE,
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug",
"Debugger");
76 auto executable_name = win32_GetModuleFileName();
77 if (not executable_name) {
82 auto executable_is_excluded = win32_RegGetValue<uint32_t>(
83 HKEY_LOCAL_MACHINE,
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug\\AutoExclusionList", executable_name->filename().string());
84 if (executable_is_excluded and *executable_is_excluded == 1) {
89 auto const num_arguments =
count(*debugger,
"%ld") +
count(*debugger,
"%p");
90 auto const cmd_line_fmt =
replace(
replace(*debugger,
"%ld",
"{}"),
"%p",
"{:x}");
92 auto const process_id = GetCurrentProcessId();
94 if (num_arguments >= 2 and jit_debug_handle == NULL) {
95 if (
auto handle = win32_CreateEvent()) {
96 jit_debug_handle = *handle;
99 set_debug_message(
"Could not create event object for JIT debugger.");
104 auto cmd_line = [&] {
106 if (num_arguments == 1) {
107 return std::vformat(cmd_line_fmt, std::make_format_args(process_id));
109 }
else if (num_arguments == 2) {
111 return std::vformat(cmd_line_fmt, std::make_format_args(process_id, jit_debug_handle_));
113 }
else if (num_arguments == 3) {
115 auto const jit_debug_info_ = std::bit_cast<uintptr_t>(&jit_debug_info);
116 return std::vformat(cmd_line_fmt, std::make_format_args(process_id, jit_debug_handle_, jit_debug_info_));
119 set_debug_message(
"JIT debugger accepts an invalid number of arguments.");
128 auto startup_info = STARTUPINFOW{};
129 startup_info.cb =
sizeof(STARTUPINFOW);
131 auto process_info = win32_CreateProcess(
142 if (not process_info) {
143 set_debug_message(
"Could not executed JIT debugger.");
147 auto const debugger_is_attached = [&] {
148 auto end_time = std::chrono::utc_clock::time_point::max();
149 while (std::chrono::utc_clock::now() < end_time) {
150 if (IsDebuggerPresent()) {
156 if (end_time == std::chrono::utc_clock::time_point::max()) {
157 auto exit_code = win32_GetExitCodeProcess(process_info->hProcess);
159 if (exit_code and *exit_code == 0) {
161 end_time = std::chrono::utc_clock::now() + 60s;
163 }
else if (exit_code and *exit_code != 0) {
167 }
else if (exit_code.error() != win32_error::status_pending) {
169 set_debug_message(
"GetExitCodeProcess() return unknown error");
177 set_debug_message(
"Debugger did not attach within 60s after being selected.");
182 CloseHandle(process_info->hThread);
183 CloseHandle(process_info->hProcess);
185 return debugger_is_attached;
193 switch (ep.ExceptionRecord->ExceptionCode) {
194 case static_cast<DWORD
>(STATUS_ASSERTION_FAILURE): r +=
"Assertion Failure";
break;
195 case static_cast<DWORD
>(EXCEPTION_ACCESS_VIOLATION): r +=
"Access Violation";
break;
196 case static_cast<DWORD
>(EXCEPTION_ARRAY_BOUNDS_EXCEEDED): r +=
"Array Bounds Exceeded";
break;
197 case static_cast<DWORD
>(EXCEPTION_BREAKPOINT): r +=
"Breakpoint";
break;
198 case static_cast<DWORD
>(EXCEPTION_DATATYPE_MISALIGNMENT): r +=
"Datatype Misalignment";
break;
199 case static_cast<DWORD
>(EXCEPTION_FLT_DENORMAL_OPERAND): r +=
"Floating Point Denormal Operand";
break;
200 case static_cast<DWORD
>(EXCEPTION_FLT_DIVIDE_BY_ZERO): r +=
"Floating Point Divide by Zero";
break;
201 case static_cast<DWORD
>(EXCEPTION_FLT_INEXACT_RESULT): r +=
"Floating Point Inexact Result";
break;
202 case static_cast<DWORD
>(EXCEPTION_FLT_INVALID_OPERATION): r +=
"Floating Point Invalid Operation";
break;
203 case static_cast<DWORD
>(EXCEPTION_FLT_OVERFLOW): r +=
"Floating Point Overflow";
break;
204 case static_cast<DWORD
>(EXCEPTION_FLT_STACK_CHECK): r +=
"Floating Point Stack Check";
break;
205 case static_cast<DWORD
>(EXCEPTION_FLT_UNDERFLOW): r +=
"Floating Point Underflow";
break;
206 case static_cast<DWORD
>(EXCEPTION_ILLEGAL_INSTRUCTION): r +=
"Illegal Instruction";
break;
207 case static_cast<DWORD
>(EXCEPTION_IN_PAGE_ERROR): r +=
"In Page Error";
break;
208 case static_cast<DWORD
>(EXCEPTION_INT_DIVIDE_BY_ZERO): r +=
"Integer Divide By Zero";
break;
209 case static_cast<DWORD
>(EXCEPTION_INT_OVERFLOW): r +=
"Integer Overflow";
break;
210 case static_cast<DWORD
>(EXCEPTION_INVALID_DISPOSITION): r +=
"Invalid Disposition";
break;
211 case static_cast<DWORD
>(EXCEPTION_NONCONTINUABLE_EXCEPTION): r +=
"Non-continuable Exception";
break;
212 case static_cast<DWORD
>(EXCEPTION_PRIV_INSTRUCTION): r +=
"Priviledged Instruction";
break;
213 case static_cast<DWORD
>(EXCEPTION_SINGLE_STEP): r +=
"Single Step";
break;
214 case static_cast<DWORD
>(EXCEPTION_STACK_OVERFLOW): r +=
"Stack Overflow";
break;
215 default: r +=
"Unknown Operating System Exception";
break;
222[[nodiscard]]
constexpr bool is_debugable_exception(EXCEPTION_POINTERS
const &ep)
noexcept
225 switch (ep.ExceptionRecord->ExceptionCode) {
226 case static_cast<DWORD
>(STATUS_ASSERTION_FAILURE): return true;
227 case static_cast<DWORD
>(EXCEPTION_ACCESS_VIOLATION): return true;
228 case static_cast<DWORD
>(EXCEPTION_ARRAY_BOUNDS_EXCEEDED): return true;
229 case static_cast<DWORD
>(EXCEPTION_BREAKPOINT): return true;
230 case static_cast<DWORD
>(EXCEPTION_DATATYPE_MISALIGNMENT): return true;
231 case static_cast<DWORD
>(EXCEPTION_FLT_DENORMAL_OPERAND): return true;
232 case static_cast<DWORD
>(EXCEPTION_FLT_DIVIDE_BY_ZERO): return true;
233 case static_cast<DWORD
>(EXCEPTION_FLT_INEXACT_RESULT): return true;
234 case static_cast<DWORD
>(EXCEPTION_FLT_INVALID_OPERATION): return true;
235 case static_cast<DWORD
>(EXCEPTION_FLT_OVERFLOW): return true;
236 case static_cast<DWORD
>(EXCEPTION_FLT_STACK_CHECK): return true;
237 case static_cast<DWORD
>(EXCEPTION_FLT_UNDERFLOW): return true;
238 case static_cast<DWORD
>(EXCEPTION_ILLEGAL_INSTRUCTION): return true;
239 case static_cast<DWORD
>(EXCEPTION_IN_PAGE_ERROR): return true;
240 case static_cast<DWORD
>(EXCEPTION_INT_DIVIDE_BY_ZERO): return true;
241 case static_cast<DWORD
>(EXCEPTION_INT_OVERFLOW): return true;
242 case static_cast<DWORD
>(EXCEPTION_INVALID_DISPOSITION): return true;
243 case static_cast<DWORD
>(EXCEPTION_NONCONTINUABLE_EXCEPTION): return true;
244 case static_cast<DWORD
>(EXCEPTION_PRIV_INSTRUCTION): return true;
245 case static_cast<DWORD
>(EXCEPTION_SINGLE_STEP): return false;
246 case static_cast<DWORD
>(EXCEPTION_STACK_OVERFLOW): return true;
247 default:
return false;
252inline LONG exception_handler(EXCEPTION_POINTERS *p)
noexcept
254 if (not is_debugable_exception(*p)) {
255 return EXCEPTION_CONTINUE_SEARCH;
259 jit_debug_info.dwSize =
sizeof(JIT_DEBUG_INFO);
260#if HI_PROCESSOR == HI_CPU_X86
261 jit_debug_info.dwProcessorArchitecture = PROCESSOR_ARCHITECTURE_INTEL;
262#elif HI_PROCESSOR == HI_CPU_X86_64
263 jit_debug_info.dwProcessorArchitecture = PROCESSOR_ARCHITECTURE_AMD64;
264#elif HI_PROCESSOR == HI_CPU_ARM
265 jit_debug_info.dwProcessorArchitecture = PROCESSOR_ARCHITECTURE_ARM;
266#elif HI_PROCESSOR == HI_CPU_ARM64
267 jit_debug_info.dwProcessorArchitecture = PROCESSOR_ARCHITECTURE_ARM64;
269#error "Not implemented."
271 jit_debug_info.dwThreadID = GetCurrentThreadId();
272 jit_debug_info.dwReserved0 = 0;
273 jit_debug_info.lpExceptionAddress = std::bit_cast<ULONG64>(p->ExceptionRecord->ExceptionAddress);
274 jit_debug_info.lpExceptionRecord = std::bit_cast<ULONG64>(&p->ExceptionRecord);
275 jit_debug_info.lpContextRecord = std::bit_cast<ULONG64>(p->ContextRecord);
277 if (IsDebuggerPresent()) {
280 return EXCEPTION_CONTINUE_SEARCH;
282 }
else if (launch_jit_debugger()) {
287 ::hi::set_debug_message(
nullptr);
288 return EXCEPTION_CONTINUE_EXECUTION;
290 }
else if (p->ExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT) {
293#if HI_PROCESSOR == HI_CPU_X86_64
295 p->ContextRecord->Rip++;
296#elif HI_PROCESSOR == HI_CPU_X86
298 p->ContextRecord->Eip++;
300#error "Not implemented."
302 return EXCEPTION_CONTINUE_EXECUTION;
305 if (not (p->ExceptionRecord->ExceptionCode == STATUS_ASSERTION_FAILURE and has_debug_message())) {
308 set_debug_message(exception_str.c_str());
313 _set_abort_behavior(0, _CALL_REPORTFAULT);
325 _CrtSetReportMode(_CRT_WARN, 0);
326 _CrtSetReportMode(_CRT_ERROR, 0);
327 _CrtSetReportMode(_CRT_ASSERT, 0);
332 AddVectoredExceptionHandler(0, detail::exception_handler);
Rules for working with win32 headers.
The HikoGUI namespace.
Definition array_generic.hpp:20
void enable_debugger() noexcept
Enable the JIT debugger to be attached.
Definition debugger_generic_impl.hpp:17
uint32_t win32_HANDLE_to_int(HANDLE handle) noexcept
Convert a HANDLE to a 32-bit unsigned integer.
Definition utility.hpp:25
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
Definition debugger_win32_impl.hpp:36