// Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) #ifndef RADDBG_MARKUP_H #define RADDBG_MARKUP_H //////////////////////////////// //~ Implementation Overrides #if !defined(RADDBG_MARKUP_VSNPRINTF) # define RADDBG_MARKUP_DEFAULT_VSNPRINTF 1 # define RADDBG_MARKUP_VSNPRINTF vsnprintf #endif #if !defined(RADDBG_MARKUP_STL_TYPE_VIEWS) # define RADDBG_MARKUP_STL_TYPE_VIEWS 1 #endif //////////////////////////////// //~ Usage Macros #if !defined(RADDBG_MARKUP_STUBS) # define raddbg_is_attached(...) raddbg_is_attached__impl() # define raddbg_thread_id(...) raddbg_thread_id__impl() # define raddbg_thread_name(...) raddbg_thread_name__impl(raddbg_thread_id(), __VA_ARGS__) # define raddbg_thread_id_name(id, ...) raddbg_thread_name__impl((id), __VA_ARGS__) # define raddbg_thread_color_u32(u32) raddbg_thread_color__impl(raddbg_thread_id(), (u32)) # define raddbg_thread_color_rgba(r, g, b, a) raddbg_thread_color__impl(raddbg_thread_id(), ((unsigned int)((r)*255) << 24) | ((unsigned int)((g)*255) << 16) | ((unsigned int)((b)*255) << 8) | ((unsigned int)(a)*255)) # define raddbg_thread_id_color_u32(id, u32) raddbg_thread_color__impl((id), (u32)) # define raddbg_thread_id_color_rgba(id, r, g, b, a) raddbg_thread_color__impl((id), ((unsigned int)((r)*255) << 24) | ((unsigned int)((g)*255) << 16) | ((unsigned int)((b)*255) << 8) | ((unsigned int)(a)*255)) # define raddbg_break(...) raddbg_break__impl() # define raddbg_break_if(expr, ...) ((expr) ? raddbg_break__impl() : (void)0) # define raddbg_watch(fmt, ...) raddbg_watch__impl((fmt), __VA_ARGS__) # define raddbg_pin(expr, ...) /* NOTE(rjf): inspected by debugger ui - does not change program execution */ # define raddbg_log(...) raddbg_log__impl(__VA_ARGS__) # define raddbg_entry_point(...) raddbg_exe_data char raddbg_gen_data_id()[] = ("entry_point: \"" #__VA_ARGS__ "\"") # define raddbg_type_view(type, ...) raddbg_exe_data char raddbg_gen_data_id()[] = ("type_view: {type: ```" #type "```, expr: ```" #__VA_ARGS__ "```}") # define raddbg_add_breakpoint(ptr, size, r, w, x) raddbg_add_or_remove_breakpoint__impl((ptr), (1), (size), (r), (w), (x)) # define raddbg_remove_breakpoint(ptr, size, r, w, x) raddbg_add_or_remove_breakpoint__impl((ptr), (0), (size), (r), (w), (x)) # define raddbg_annotate_vaddr_range(ptr, size, ...) raddbg_annotate_vaddr_range__impl((ptr), (size), __VA_ARGS__) #else # define raddbg_is_attached(...) (0) # define raddbg_thread_id(...) ((void)0) # define raddbg_thread_name(fmt, ...) ((void)0) # define raddbg_thread_id_name(id, fmt, ...) ((void)0) # define raddbg_thread_color_u32(u32) ((void)0) # define raddbg_thread_color_rgba(r, g, b, a) ((void)0) # define raddbg_thread_id_color_u32(id, u32) ((void)0) # define raddbg_thread_id_color_rgba(id, r, g, b, a) ((void)0) # define raddbg_break(...) ((void)0) # define raddbg_break_if(expr, ...) ((void)expr) # define raddbg_watch(fmt, ...) ((void)0) # define raddbg_pin(expr, ...) # define raddbg_log(fmt, ...) ((void)0) # define raddbg_entry_point(...) struct raddbg_gen_data_id(){int __unused__;} # define raddbg_type_view(type, ...) struct raddbg_gen_data_id(){int __unused__;} # define raddbg_add_breakpoint(ptr, size, r, w, x) ((void)0) # define raddbg_remove_breakpoint(ptr, size, r, w, x) ((void)0) # define raddbg_annotate_vaddr_range(ptr, size, ...) ((void)0) #endif //////////////////////////////// //~ Helpers #define raddbg_glue_(a, b) a##b #define raddbg_glue(a, b) raddbg_glue_(a, b) #define raddbg_gen_data_id() raddbg_glue(raddbg_data__, __COUNTER__) //////////////////////////////// //~ Global Symbols #if !defined(RADDBG_MARKUP_STUBS) extern unsigned char raddbg_is_attached_byte_marker[1]; int raddbg_is_attached__impl(void); int raddbg_thread_id__impl(void); void raddbg_thread_name__impl(int id, char *fmt, ...); void raddbg_thread_color__impl(int id, unsigned int hexcode); void raddbg_watch__impl(char *fmt, ...); void raddbg_log__impl(char *fmt, ...); void raddbg_add_or_remove_breakpoint__impl(void *ptr, int set, int size, int r, int w, int x); void raddbg_annotate_vaddr_range__impl(void *ptr, unsigned __int64 size, char *fmt, ...); #endif //////////////////////////////// //~ Win32 Implementations #if defined(_WIN32) && !defined(RADDBG_MARKUP_STUBS) //- section allocating #pragma section(".raddbg", read, write) #define raddbg_exe_data __declspec(allocate(".raddbg")) //- one-time implementations #if defined(RADDBG_MARKUP_IMPLEMENTATION) //- default includes #if RADDBG_MARKUP_DEFAULT_VSNPRINTF #include #endif //- special section gets "is attached" byte #pragma section(".rdbgia", read, write) __declspec(allocate(".rdbgia")) unsigned char raddbg_is_attached_byte_marker[1] = {0}; //- types typedef int BOOL; typedef long LONG; typedef unsigned long ULONG; typedef unsigned __int64 ULONG_PTR, *PULONG_PTR; typedef unsigned long DWORD; typedef wchar_t WCHAR; typedef char const *LPCSTR; typedef const WCHAR *LPCWSTR, *PCWSTR; typedef LONG HRESULT; typedef void *HANDLE; struct HINSTANCE__; typedef struct HINSTANCE__ *HMODULE; typedef __int64 INT_PTR; typedef INT_PTR (*FARPROC)(); //- prototypes #include #if defined(__cplusplus) extern "C" { #endif __declspec(dllimport) HMODULE LoadLibraryA(LPCSTR name); __declspec(dllimport) FARPROC GetProcAddress(HMODULE module, LPCSTR name); __declspec(dllimport) BOOL FreeLibrary(HMODULE mod); __declspec(dllimport) HANDLE GetCurrentThread(void); __declspec(dllimport) DWORD GetCurrentThreadId(void); __declspec(dllimport) void RaiseException(DWORD dwExceptionCode, DWORD dwExceptionFlags, DWORD nNumberOfArguments, const ULONG_PTR *lpArguments); __declspec(dllimport) void OutputDebugStringA(LPCSTR buffer); long long _InterlockedCompareExchange64(long long volatile*, long long, long long); long long _InterlockedExchangeAdd64(long long volatile*, long long); #pragma intrinsic(_InterlockedCompareExchange64) #pragma intrinsic(_InterlockedExchangeAdd64) #if RADDBG_MARKUP_DEFAULT_VSNPRINTF int RADDBG_MARKUP_VSNPRINTF(char * const, unsigned long long const, const char * const, va_list); #endif #if defined(__cplusplus) } #endif //- helpers typedef struct RADDBG_MARKUP_UnicodeDecode RADDBG_MARKUP_UnicodeDecode; struct RADDBG_MARKUP_UnicodeDecode { unsigned __int32 inc; unsigned __int32 codepoint; }; static __int8 raddbg_utf8_class[32] = {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,2,2,2,2,3,3,4,5}; static RADDBG_MARKUP_UnicodeDecode raddbg_decode_utf8(char *str, unsigned __int64 max) { RADDBG_MARKUP_UnicodeDecode result = {1, 0xffffffff}; unsigned __int8 byte = str[0]; unsigned __int8 byte_class = raddbg_utf8_class[byte >> 3]; switch(byte_class) { case 1: { result.codepoint = byte; }break; case 2: if(2 < max) { unsigned char cont_byte = str[1]; if(raddbg_utf8_class[cont_byte >> 3] == 0) { result.codepoint = (byte & 0x0000001f) << 6; result.codepoint |= (cont_byte & 0x0000003f); result.inc = 2; } }break; case 3: if(2 < max) { unsigned char cont_byte[2] = {(unsigned char)str[1], (unsigned char)str[2]}; if(raddbg_utf8_class[cont_byte[0] >> 3] == 0 && raddbg_utf8_class[cont_byte[1] >> 3] == 0) { result.codepoint = (byte & 0x0000000f) << 12; result.codepoint |= ((cont_byte[0] & 0x0000003f) << 6); result.codepoint |= (cont_byte[1] & 0x0000003f); result.inc = 3; } }break; case 4: if(3 < max) { unsigned char cont_byte[3] = {(unsigned char)str[1], (unsigned char)str[2], (unsigned char)str[3]}; if(raddbg_utf8_class[cont_byte[0] >> 3] == 0 && raddbg_utf8_class[cont_byte[1] >> 3] == 0 && raddbg_utf8_class[cont_byte[2] >> 3] == 0) { result.codepoint = (byte & 0x00000007) << 18; result.codepoint |= ((cont_byte[0] & 0x0000003f) << 12); result.codepoint |= ((cont_byte[1] & 0x0000003f) << 6); result.codepoint |= (cont_byte[2] & 0x0000003f); result.inc = 4; } } } return result; } static unsigned __int32 raddbg_encode_utf16(wchar_t *str, unsigned __int32 codepoint) { unsigned __int32 inc = 1; if(codepoint == 0xffffffff) { str[0] = (wchar_t)'?'; } else if(codepoint < 0x10000) { str[0] = (wchar_t)codepoint; } else { unsigned __int32 v = codepoint - 0x10000; str[0] = (wchar_t)(0xD800 + (v >> 10)); str[1] = (wchar_t)(0xDC00 + (v & 0x000003ff)); inc = 2; } return inc; } //- implementations int raddbg_is_attached__impl(void) { return !!raddbg_is_attached_byte_marker[0]; } int raddbg_thread_id__impl(void) { DWORD result = GetCurrentThreadId(); return result; } void raddbg_thread_name__impl(int id, char *fmt, ...) { // rjf: resolve variadic arguments char buffer[512] = {0}; char *name = buffer; { va_list args; va_start(args, fmt); RADDBG_MARKUP_VSNPRINTF(buffer, sizeof(buffer), fmt, args); va_end(args); } // rjf: get windows 10 style procedure HRESULT (*SetThreadDescription_function)(HANDLE hThread, PCWSTR lpThreadDescription) = 0; { static HRESULT (*global_SetThreadDescription_function)(HANDLE hThread, PCWSTR lpThreadDescription); static volatile __int64 global_SetThreadDescription_init_started; static volatile __int64 global_SetThreadDescription_init_done; __int64 do_init = !_InterlockedCompareExchange64(&global_SetThreadDescription_init_started, 1, 0); if(do_init) { HMODULE module = LoadLibraryA("kernel32.dll"); global_SetThreadDescription_function = (HRESULT (*)(HANDLE, PCWSTR))GetProcAddress(module, "SetThreadDescription"); FreeLibrary(module); _InterlockedExchangeAdd64(&global_SetThreadDescription_init_done, 1); } for(;_InterlockedExchangeAdd64(&global_SetThreadDescription_init_done, 0) == 0;) { // NOTE(rjf): busy-loop, until init is done } SetThreadDescription_function = global_SetThreadDescription_function; } // rjf: set thread name, windows 10 style if(SetThreadDescription_function && id == GetCurrentThreadId()) { WCHAR buffer16[1024] = {0}; int name_length = 0; for(;name[name_length]; name_length += 1); int write_offset = 0; for(int idx = 0; idx < name_length;) { RADDBG_MARKUP_UnicodeDecode decode = raddbg_decode_utf8(name+idx, name_length-idx); write_offset += raddbg_encode_utf16(buffer16 + write_offset, decode.codepoint); idx += decode.inc; } SetThreadDescription_function(GetCurrentThread(), buffer16); } // rjf: set thread name, raise-exception style { #pragma pack(push, 8) typedef struct THREADNAME_INFO THREADNAME_INFO; struct THREADNAME_INFO { DWORD dwType; LPCSTR szName; DWORD dwThreadID; DWORD dwFlags; }; #pragma pack(pop) THREADNAME_INFO info; info.dwType = 0x1000; info.szName = name; info.dwThreadID = id; info.dwFlags = 0; #pragma warning(push) #pragma warning(disable: 6320 6322) __try { RaiseException(0x406D1388u, 0, sizeof(info) / sizeof(void *), (const ULONG_PTR *)&info); } __except(1) { } #pragma warning(pop) } } void raddbg_thread_color__impl(int id, unsigned int hexcode) { if(raddbg_is_attached()) { #pragma pack(push, 8) typedef struct RADDBG_ThreadColorInfo RADDBG_ThreadColorInfo; struct RADDBG_ThreadColorInfo { DWORD dwThreadID; DWORD _pad_0; DWORD rgba; DWORD _pad_1; }; #pragma pack(pop) RADDBG_ThreadColorInfo info; info.dwThreadID = id; info.rgba = hexcode; #pragma warning(push) #pragma warning(disable: 6320 6322) __try { RaiseException(0x00524144u, 0, sizeof(info) / sizeof(void *), (const ULONG_PTR *)&info); } __except(1) { } #pragma warning(pop) } } #define raddbg_break__impl() (__debugbreak()) void raddbg_watch__impl(char *fmt, ...) { // TODO(rjf) } void raddbg_log__impl(char *fmt, ...) { // rjf: resolve variadic arguments char buffer[4096]; { va_list args; va_start(args, fmt); RADDBG_MARKUP_VSNPRINTF(buffer, sizeof(buffer), fmt, args); va_end(args); } // rjf: output debug string OutputDebugStringA(buffer); } void raddbg_add_or_remove_breakpoint__impl(void *ptr, int set, int size, int r, int w, int x) { if(raddbg_is_attached()) { #pragma pack(push, 8) typedef struct RADDBG_AddBreakpointInfo RADDBG_AddBreakpointInfo; struct RADDBG_AddBreakpointInfo { unsigned __int64 vaddr; unsigned __int64 size; unsigned __int64 r; unsigned __int64 w; unsigned __int64 x; unsigned __int64 add; }; #pragma pack(pop) RADDBG_AddBreakpointInfo info; info.vaddr = (unsigned __int64)ptr; info.size = size; info.r = r; info.w = w; info.x = x; info.add = set; #pragma warning(push) #pragma warning(disable: 6320 6322) __try { RaiseException(0x00524145u, 0, sizeof(info) / sizeof(void *), (const ULONG_PTR *)&info); } __except(1) { } #pragma warning(pop) } } void raddbg_annotate_vaddr_range__impl(void *ptr, unsigned __int64 size, char *fmt, ...) { if(raddbg_is_attached()) { // rjf: resolve variadic arguments char buffer[4096]; int buffer_size = 0; { va_list args; va_start(args, fmt); buffer_size = RADDBG_MARKUP_VSNPRINTF(buffer, sizeof(buffer), fmt, args); buffer_size = ((buffer_size < 0) ? 0 : (buffer_size > sizeof(buffer)) ? sizeof(buffer) : buffer_size); va_end(args); } // rjf: send annotation info via exception #pragma pack(push, 8) typedef struct RADDBG_VaddrRangeAnnotationInfo RADDBG_VaddrRangeAnnotationInfo; struct RADDBG_VaddrRangeAnnotationInfo { unsigned __int64 vaddr; unsigned __int64 size; void *name; unsigned __int64 name_size; }; #pragma pack(pop) RADDBG_VaddrRangeAnnotationInfo info; info.vaddr = (unsigned __int64)ptr; info.size = size; info.name = buffer; info.name_size = buffer_size; #pragma warning(push) #pragma warning(disable: 6320 6322) __try { RaiseException(0x00524156u, 0, sizeof(info) / sizeof(void *), (const ULONG_PTR *)&info); } __except(1) { } #pragma warning(pop) } } #endif // defined(RADDBG_MARKUP_IMPLEMENTATION) #endif // defined(_WIN32) && !defined(RADDBG_MARKUP_STUBS) //////////////////////////////// //~ Win32 STL Type Views #if defined(_WIN32) && defined(RADDBG_MARKUP_IMPLEMENTATION) && RADDBG_MARKUP_STL_TYPE_VIEWS # if defined(_VECTOR_) raddbg_type_view(std::vector, slice(_Mypair._Myval2)); # endif # if defined(_MEMORY_) raddbg_type_view(std::unique_ptr, _Mypair._Myval2); # endif # if defined(_STRING_) raddbg_type_view(std::basic_string, _Mypair._Myval2._Myres <= 15 ? _Mypair._Myval2._Bx._Buf : array(_Mypair._Myval2._Bx._Ptr, _Mypair._Myval2._Mysize)); # endif # if defined(_STRING_VIEW_) raddbg_type_view(std::basic_string_view, array(_Mydata, _Mysize)); # endif #endif // defined(_WIN32) && defined(RADDBG_MARKUP_IMPLEMENTATION) && RADDBG_MARKUP_STL_TYPE_VIEWS #endif // RADDBG_MARKUP_H