From e239210279c65c3baa959c8eb7ec12a9248507fd Mon Sep 17 00:00:00 2001 From: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com> Date: Mon, 17 Aug 2026 05:32:28 +0200 Subject: [PATCH] Rewrite RenderDoc capture flow and host exceptions --- 3rdparty/renderdoc/renderdoc_app.h | 875 ++++++++++++++++++ CMakeLists.txt | 1 + src/common/hostException.cpp | 89 +- src/graphics/guest_gpu/graphicsRun.cpp | 13 +- src/graphics/host_gpu/vulkanCommon.h | 5 - src/graphics/presentation/renderDoc.cpp | 346 ++----- src/graphics/presentation/renderDoc.h | 12 +- .../presentation/window/swapchain.cpp | 2 - .../presentation/window/vulkanWindow.cpp | 12 +- src/kernel/memory.cpp | 11 - src/kernel/memory.h | 1 - src/loader/runtimeLinker.cpp | 226 +---- 12 files changed, 988 insertions(+), 605 deletions(-) create mode 100644 3rdparty/renderdoc/renderdoc_app.h diff --git a/3rdparty/renderdoc/renderdoc_app.h b/3rdparty/renderdoc/renderdoc_app.h new file mode 100644 index 0000000..3cee3bd --- /dev/null +++ b/3rdparty/renderdoc/renderdoc_app.h @@ -0,0 +1,875 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2015-2026 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#pragma once + +////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Documentation for the API is available at https://renderdoc.org/docs/in_application_api.html +// + +#if !defined(RENDERDOC_NO_STDINT) +#include +#endif + +#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) +#define RENDERDOC_CC __cdecl +#elif defined(__linux__) || defined(__FreeBSD__) || defined(__sun__) || defined(__OpenBSD__) +#define RENDERDOC_CC +#elif defined(__APPLE__) +#define RENDERDOC_CC +#else +#error "Unknown platform" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Constants not used directly in below API + +// This is a GUID/magic value used for when applications pass a path where shader debug +// information can be found to match up with a stripped shader. +// the define can be used like so: const GUID RENDERDOC_ShaderDebugMagicValue = +// RENDERDOC_ShaderDebugMagicValue_value +#define RENDERDOC_ShaderDebugMagicValue_struct \ + { \ + 0xeab25520, 0x6670, 0x4865, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \ + } + +// as an alternative when you want a byte array (assuming x86 endianness): +#define RENDERDOC_ShaderDebugMagicValue_bytearray \ + { \ + 0x20, 0x55, 0xb2, 0xea, 0x70, 0x66, 0x65, 0x48, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \ + } + +// truncated version when only a uint64_t is available (e.g. Vulkan tags): +#define RENDERDOC_ShaderDebugMagicValue_truncated 0x48656670eab25520ULL + +// this is a magic value for vulkan user tags to indicate which dispatchable API objects are which +// for object annotations +#define RENDERDOC_APIObjectAnnotationHelper 0xfbb3b337b664d0adULL + +////////////////////////////////////////////////////////////////////////////////////////////////// +// RenderDoc capture options +// + +typedef enum RENDERDOC_CaptureOption +{ + // Allow the application to enable vsync + // + // Default - enabled + // + // 1 - The application can enable or disable vsync at will + // 0 - vsync is force disabled + eRENDERDOC_Option_AllowVSync = 0, + + // Allow the application to enable fullscreen + // + // Default - enabled + // + // 1 - The application can enable or disable fullscreen at will + // 0 - fullscreen is force disabled + eRENDERDOC_Option_AllowFullscreen = 1, + + // Record API debugging events and messages + // + // Default - disabled + // + // 1 - Enable built-in API debugging features and records the results into + // the capture, which is matched up with events on replay + // 0 - no API debugging is forcibly enabled + eRENDERDOC_Option_APIValidation = 2, + eRENDERDOC_Option_DebugDeviceMode = 2, // deprecated name of this enum + + // Capture CPU callstacks for API events + // + // Default - disabled + // + // 1 - Enables capturing of callstacks + // 0 - no callstacks are captured + eRENDERDOC_Option_CaptureCallstacks = 3, + + // When capturing CPU callstacks, only capture them from actions. + // This option does nothing without the above option being enabled + // + // Default - disabled + // + // 1 - Only captures callstacks for actions. + // Ignored if CaptureCallstacks is disabled + // 0 - Callstacks, if enabled, are captured for every event. + eRENDERDOC_Option_CaptureCallstacksOnlyDraws = 4, + eRENDERDOC_Option_CaptureCallstacksOnlyActions = 4, + + // Specify a delay in seconds to wait for a debugger to attach, after + // creating or injecting into a process, before continuing to allow it to run. + // + // 0 indicates no delay, and the process will run immediately after injection + // + // Default - 0 seconds + // + eRENDERDOC_Option_DelayForDebugger = 5, + + // Verify buffer access. This includes checking the memory returned by a Map() call to + // detect any out-of-bounds modification, as well as initialising buffers with undefined contents + // to a marker value to catch use of uninitialised memory. + // + // NOTE: This option is only valid for OpenGL and D3D11. Explicit APIs such as D3D12 and Vulkan do + // not do the same kind of interception & checking and undefined contents are really undefined. + // + // Default - disabled + // + // 1 - Verify buffer access + // 0 - No verification is performed, and overwriting bounds may cause crashes or corruption in + // RenderDoc. + eRENDERDOC_Option_VerifyBufferAccess = 6, + + // The old name for eRENDERDOC_Option_VerifyBufferAccess was eRENDERDOC_Option_VerifyMapWrites. + // This option now controls the filling of uninitialised buffers with 0xdddddddd which was + // previously always enabled + eRENDERDOC_Option_VerifyMapWrites = eRENDERDOC_Option_VerifyBufferAccess, + + // Hooks any system API calls that create child processes, and injects + // RenderDoc into them recursively with the same options. + // + // Default - disabled + // + // 1 - Hooks into spawned child processes + // 0 - Child processes are not hooked by RenderDoc + eRENDERDOC_Option_HookIntoChildren = 7, + + // By default RenderDoc only includes resources in the final capture necessary + // for that frame, this allows you to override that behaviour. + // + // Default - disabled + // + // 1 - all live resources at the time of capture are included in the capture + // and available for inspection + // 0 - only the resources referenced by the captured frame are included + eRENDERDOC_Option_RefAllResources = 8, + + // **NOTE**: As of RenderDoc v1.1 this option has been deprecated. Setting or + // getting it will be ignored, to allow compatibility with older versions. + // In v1.1 the option acts as if it's always enabled. + // + // By default RenderDoc skips saving initial states for resources where the + // previous contents don't appear to be used, assuming that writes before + // reads indicate previous contents aren't used. + // + // Default - disabled + // + // 1 - initial contents at the start of each captured frame are saved, even if + // they are later overwritten or cleared before being used. + // 0 - unless a read is detected, initial contents will not be saved and will + // appear as black or empty data. + eRENDERDOC_Option_SaveAllInitials = 9, + + // In APIs that allow for the recording of command lists to be replayed later, + // RenderDoc may choose to not capture command lists before a frame capture is + // triggered, to reduce overheads. This means any command lists recorded once + // and replayed many times will not be available and may cause a failure to + // capture. + // + // NOTE: This is only true for APIs where multithreading is difficult or + // discouraged. Newer APIs like Vulkan and D3D12 will ignore this option + // and always capture all command lists since the API is heavily oriented + // around it and the overheads have been reduced by API design. + // + // 1 - All command lists are captured from the start of the application + // 0 - Command lists are only captured if their recording begins during + // the period when a frame capture is in progress. + eRENDERDOC_Option_CaptureAllCmdLists = 10, + + // Mute API debugging output when the API validation mode option is enabled + // + // Default - enabled + // + // 1 - Mute any API debug messages from being displayed or passed through + // 0 - API debugging is displayed as normal + eRENDERDOC_Option_DebugOutputMute = 11, + + // Option to allow vendor extensions to be used even when they may be + // incompatible with RenderDoc and cause corrupted replays or crashes. + // + // Default - inactive + // + // No values are documented, this option should only be used when absolutely + // necessary as directed by a RenderDoc developer. + eRENDERDOC_Option_AllowUnsupportedVendorExtensions = 12, + + // Define a soft memory limit which some APIs may aim to keep overhead under where + // possible. Anything above this limit will where possible be saved directly to disk during + // capture. + // This will cause increased disk space use (which may cause a capture to fail if disk space is + // exhausted) as well as slower capture times. + // + // Not all memory allocations may be deferred like this so it is not a guarantee of a memory + // limit. + // + // Units are in MBs, suggested values would range from 200MB to 1000MB. + // + // Default - 0 Megabytes + eRENDERDOC_Option_SoftMemoryLimit = 13, +} RENDERDOC_CaptureOption; + +// Sets an option that controls how RenderDoc behaves on capture. +// +// Returns 1 if the option and value are valid +// Returns 0 if either is invalid and the option is unchanged +typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionU32)(RENDERDOC_CaptureOption opt, uint32_t val); +typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionF32)(RENDERDOC_CaptureOption opt, float val); + +// Gets the current value of an option as a uint32_t +// +// If the option is invalid, 0xffffffff is returned +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionU32)(RENDERDOC_CaptureOption opt); + +// Gets the current value of an option as a float +// +// If the option is invalid, -FLT_MAX is returned +typedef float(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionF32)(RENDERDOC_CaptureOption opt); + +typedef enum RENDERDOC_InputButton +{ + // '0' - '9' matches ASCII values + eRENDERDOC_Key_0 = 0x30, + eRENDERDOC_Key_1 = 0x31, + eRENDERDOC_Key_2 = 0x32, + eRENDERDOC_Key_3 = 0x33, + eRENDERDOC_Key_4 = 0x34, + eRENDERDOC_Key_5 = 0x35, + eRENDERDOC_Key_6 = 0x36, + eRENDERDOC_Key_7 = 0x37, + eRENDERDOC_Key_8 = 0x38, + eRENDERDOC_Key_9 = 0x39, + + // 'A' - 'Z' matches ASCII values + eRENDERDOC_Key_A = 0x41, + eRENDERDOC_Key_B = 0x42, + eRENDERDOC_Key_C = 0x43, + eRENDERDOC_Key_D = 0x44, + eRENDERDOC_Key_E = 0x45, + eRENDERDOC_Key_F = 0x46, + eRENDERDOC_Key_G = 0x47, + eRENDERDOC_Key_H = 0x48, + eRENDERDOC_Key_I = 0x49, + eRENDERDOC_Key_J = 0x4A, + eRENDERDOC_Key_K = 0x4B, + eRENDERDOC_Key_L = 0x4C, + eRENDERDOC_Key_M = 0x4D, + eRENDERDOC_Key_N = 0x4E, + eRENDERDOC_Key_O = 0x4F, + eRENDERDOC_Key_P = 0x50, + eRENDERDOC_Key_Q = 0x51, + eRENDERDOC_Key_R = 0x52, + eRENDERDOC_Key_S = 0x53, + eRENDERDOC_Key_T = 0x54, + eRENDERDOC_Key_U = 0x55, + eRENDERDOC_Key_V = 0x56, + eRENDERDOC_Key_W = 0x57, + eRENDERDOC_Key_X = 0x58, + eRENDERDOC_Key_Y = 0x59, + eRENDERDOC_Key_Z = 0x5A, + + // leave the rest of the ASCII range free + // in case we want to use it later + eRENDERDOC_Key_NonPrintable = 0x100, + + eRENDERDOC_Key_Divide, + eRENDERDOC_Key_Multiply, + eRENDERDOC_Key_Subtract, + eRENDERDOC_Key_Plus, + + eRENDERDOC_Key_F1, + eRENDERDOC_Key_F2, + eRENDERDOC_Key_F3, + eRENDERDOC_Key_F4, + eRENDERDOC_Key_F5, + eRENDERDOC_Key_F6, + eRENDERDOC_Key_F7, + eRENDERDOC_Key_F8, + eRENDERDOC_Key_F9, + eRENDERDOC_Key_F10, + eRENDERDOC_Key_F11, + eRENDERDOC_Key_F12, + + eRENDERDOC_Key_Home, + eRENDERDOC_Key_End, + eRENDERDOC_Key_Insert, + eRENDERDOC_Key_Delete, + eRENDERDOC_Key_PageUp, + eRENDERDOC_Key_PageDn, + + eRENDERDOC_Key_Backspace, + eRENDERDOC_Key_Tab, + eRENDERDOC_Key_PrtScrn, + eRENDERDOC_Key_Pause, + + eRENDERDOC_Key_Max, +} RENDERDOC_InputButton; + +// Sets which key or keys can be used to toggle focus between multiple windows +// +// If keys is NULL or num is 0, toggle keys will be disabled +typedef void(RENDERDOC_CC *pRENDERDOC_SetFocusToggleKeys)(RENDERDOC_InputButton *keys, int num); + +// Sets which key or keys can be used to capture the next frame +// +// If keys is NULL or num is 0, captures keys will be disabled +typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureKeys)(RENDERDOC_InputButton *keys, int num); + +typedef enum RENDERDOC_OverlayBits +{ + // This single bit controls whether the overlay is enabled or disabled globally + eRENDERDOC_Overlay_Enabled = 0x1, + + // Show the average framerate over several seconds as well as min/max + eRENDERDOC_Overlay_FrameRate = 0x2, + + // Show the current frame number + eRENDERDOC_Overlay_FrameNumber = 0x4, + + // Show a list of recent captures, and how many captures have been made + eRENDERDOC_Overlay_CaptureList = 0x8, + + // Default values for the overlay mask + eRENDERDOC_Overlay_Default = (eRENDERDOC_Overlay_Enabled | eRENDERDOC_Overlay_FrameRate | + eRENDERDOC_Overlay_FrameNumber | eRENDERDOC_Overlay_CaptureList), + + // Enable all bits + eRENDERDOC_Overlay_All = 0x7ffffff, + + // Disable all bits + eRENDERDOC_Overlay_None = 0, +} RENDERDOC_OverlayBits; + +// returns the overlay bits that have been set +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetOverlayBits)(void); +// sets the overlay bits with an and & or mask +typedef void(RENDERDOC_CC *pRENDERDOC_MaskOverlayBits)(uint32_t And, uint32_t Or); + +// this function will attempt to remove RenderDoc's hooks in the application. +// +// Note: that this can only work correctly if done immediately after +// the module is loaded, before any API work happens. RenderDoc will remove its +// injected hooks and shut down. Behaviour is undefined if this is called +// after any API functions have been called, and there is still no guarantee of +// success. +typedef void(RENDERDOC_CC *pRENDERDOC_RemoveHooks)(void); + +// DEPRECATED: compatibility for code compiled against pre-1.4.1 headers. +typedef pRENDERDOC_RemoveHooks pRENDERDOC_Shutdown; + +// This function will unload RenderDoc's crash handler. +// +// If you use your own crash handler and don't want RenderDoc's handler to +// intercede, you can call this function to unload it and any unhandled +// exceptions will pass to the next handler. +typedef void(RENDERDOC_CC *pRENDERDOC_UnloadCrashHandler)(void); + +// Sets the capture file path template +// +// pathtemplate is a UTF-8 string that gives a template for how captures will be named +// and where they will be saved. +// +// Any extension is stripped off the path, and captures are saved in the directory +// specified, and named with the filename and the frame number appended. If the +// directory does not exist it will be created, including any parent directories. +// +// If pathtemplate is NULL, the template will remain unchanged +// +// Example: +// +// SetCaptureFilePathTemplate("my_captures/example"); +// +// Capture #1 -> my_captures/example_frame123.rdc +// Capture #2 -> my_captures/example_frame456.rdc +typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFilePathTemplate)(const char *pathtemplate); + +// returns the current capture path template, see SetCaptureFileTemplate above, as a UTF-8 string +typedef const char *(RENDERDOC_CC *pRENDERDOC_GetCaptureFilePathTemplate)(void); + +// DEPRECATED: compatibility for code compiled against pre-1.1.2 headers. +typedef pRENDERDOC_SetCaptureFilePathTemplate pRENDERDOC_SetLogFilePathTemplate; +typedef pRENDERDOC_GetCaptureFilePathTemplate pRENDERDOC_GetLogFilePathTemplate; + +// returns the number of captures that have been made +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetNumCaptures)(void); + +// This function returns the details of a capture, by index. New captures are added +// to the end of the list. +// +// filename will be filled with the absolute path to the capture file, as a UTF-8 string +// pathlength will be written with the length in bytes of the filename string +// timestamp will be written with the time of the capture, in seconds since the Unix epoch +// +// Any of the parameters can be NULL and they'll be skipped. +// +// The function will return 1 if the capture index is valid, or 0 if the index is invalid +// If the index is invalid, the values will be unchanged +// +// Note: when captures are deleted in the UI they will remain in this list, so the +// capture path may not exist anymore. +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCapture)(uint32_t idx, char *filename, + uint32_t *pathlength, uint64_t *timestamp); + +// Sets the comments associated with a capture file. These comments are displayed in the +// UI program when opening. +// +// filePath should be a path to the capture file to add comments to. If set to NULL or "" +// the most recent capture file created made will be used instead. +// comments should be a NULL-terminated UTF-8 string to add as comments. +// +// Any existing comments will be overwritten. +typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFileComments)(const char *filePath, + const char *comments); + +// returns 1 if the RenderDoc UI is connected to this application, 0 otherwise +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsTargetControlConnected)(void); + +// DEPRECATED: compatibility for code compiled against pre-1.1.1 headers. +// This was renamed to IsTargetControlConnected in API 1.1.1, the old typedef is kept here for +// backwards compatibility with old code, it is castable either way since it's ABI compatible +// as the same function pointer type. +typedef pRENDERDOC_IsTargetControlConnected pRENDERDOC_IsRemoteAccessConnected; + +// This function will launch the Replay UI associated with the RenderDoc library injected +// into the running application. +// +// if connectTargetControl is 1, the Replay UI will be launched with a command line parameter +// to connect to this application +// cmdline is the rest of the command line, as a UTF-8 string. E.g. a captures to open +// if cmdline is NULL, the command line will be empty. +// +// returns the PID of the replay UI if successful, 0 if not successful. +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_LaunchReplayUI)(uint32_t connectTargetControl, + const char *cmdline); + +// RenderDoc can return a higher version than requested if it's backwards compatible, +// this function returns the actual version returned. If a parameter is NULL, it will be +// ignored and the others will be filled out. +typedef void(RENDERDOC_CC *pRENDERDOC_GetAPIVersion)(int *major, int *minor, int *patch); + +// Requests that the replay UI show itself (if hidden or not the current top window). This can be +// used in conjunction with IsTargetControlConnected and LaunchReplayUI to intelligently handle +// showing the UI after making a capture. +// +// This will return 1 if the request was successfully passed on, though it's not guaranteed that +// the UI will be on top in all cases depending on OS rules. It will return 0 if there is no current +// target control connection to make such a request, or if there was another error +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_ShowReplayUI)(void); + +////////////////////////////////////////////////////////////////////////// +// Capturing functions +// + +// A device pointer is a pointer to the API's root handle. +// +// This would be an ID3D11Device, HGLRC/GLXContext, ID3D12Device, etc +typedef void *RENDERDOC_DevicePointer; + +// A window handle is the OS's native window handle +// +// This would be an HWND, GLXDrawable, etc +typedef void *RENDERDOC_WindowHandle; + +// A helper macro for Vulkan, where the device handle cannot be used directly. +// +// Passing the VkInstance to this macro will return the RENDERDOC_DevicePointer to use. +// +// Specifically, the value needed is the dispatch table pointer, which sits as the first +// pointer-sized object in the memory pointed to by the VkInstance. Thus we cast to a void** and +// indirect once. +#define RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(inst) (*((void **)(inst))) + +// This sets the RenderDoc in-app overlay in the API/window pair as 'active' and it will +// respond to keypresses. Neither parameter can be NULL +typedef void(RENDERDOC_CC *pRENDERDOC_SetActiveWindow)(RENDERDOC_DevicePointer device, + RENDERDOC_WindowHandle wndHandle); + +// capture the next frame on whichever window and API is currently considered active +typedef void(RENDERDOC_CC *pRENDERDOC_TriggerCapture)(void); + +// capture the next N frames on whichever window and API is currently considered active +typedef void(RENDERDOC_CC *pRENDERDOC_TriggerMultiFrameCapture)(uint32_t numFrames); + +// When choosing either a device pointer or a window handle to capture, you can pass NULL. +// Passing NULL specifies a 'wildcard' match against anything. This allows you to specify +// any API rendering to a specific window, or a specific API instance rendering to any window, +// or in the simplest case of one window and one API, you can just pass NULL for both. +// +// In either case, if there are two or more possible matching (device,window) pairs it +// is undefined which one will be captured. +// +// Note: for headless rendering you can pass NULL for the window handle and either specify +// a device pointer or leave it NULL as above. + +// Immediately starts capturing API calls on the specified device pointer and window handle. +// +// If there is no matching thing to capture (e.g. no supported API has been initialised), +// this will do nothing. +// +// The results are undefined (including crashes) if two captures are started overlapping, +// even on separate devices and/oror windows. +typedef void(RENDERDOC_CC *pRENDERDOC_StartFrameCapture)(RENDERDOC_DevicePointer device, + RENDERDOC_WindowHandle wndHandle); + +// Returns whether or not a frame capture is currently ongoing anywhere. +// +// This will return 1 if a capture is ongoing, and 0 if there is no capture running +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsFrameCapturing)(void); + +// Ends capturing immediately. +// +// This will return 1 if the capture succeeded, and 0 if there was an error capturing. +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_EndFrameCapture)(RENDERDOC_DevicePointer device, + RENDERDOC_WindowHandle wndHandle); + +// Ends capturing immediately and discard any data stored without saving to disk. +// +// This will return 1 if the capture was discarded, and 0 if there was an error or no capture +// was in progress +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_DiscardFrameCapture)(RENDERDOC_DevicePointer device, + RENDERDOC_WindowHandle wndHandle); + +// Only valid to be called between a call to StartFrameCapture and EndFrameCapture. Gives a custom +// title to the capture produced which will be displayed in the UI. +// +// If multiple captures are ongoing, this title will be applied to the first capture to end after +// this call. The second capture to end will have no title, unless this function is called again. +// +// Calling this function has no effect if no capture is currently running, and if it is called +// multiple times only the last title will be used. +typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureTitle)(const char *title); + +// Annotations API: +// +// These functions allow you to specify annotations either on a per-command level, or a per-object +// level. +// +// Basic types of annotations are supported, as well as vector versions and references to API objects. +// +// The annotations are stored as keys, with the key being a dot-separated path allowing arbitrary +// nesting and user organisation. The keys are sorted in human order so `foo.2.bar` will be displayed +// before `foo.10.bar` to allow creation of arrays if desired. +// +// Deleting an annotation can be done by assigning an empty value to it. + +// the type of an annotation value, or Empty to delete an annotation +typedef enum RENDERDOC_AnnotationType +{ + eRENDERDOC_Empty, + eRENDERDOC_Bool, + eRENDERDOC_Int32, + eRENDERDOC_UInt32, + eRENDERDOC_Int64, + eRENDERDOC_UInt64, + eRENDERDOC_Float, + eRENDERDOC_Double, + eRENDERDOC_String, + eRENDERDOC_APIObject, + eRENDERDOC_AnnotationMax = 0x7FFFFFFF, +} RENDERDOC_AnnotationType; + +// a union with vector annotation value data +typedef union RENDERDOC_AnnotationVectorValue +{ + bool boolean[4]; + int32_t int32[4]; + int64_t int64[4]; + uint32_t uint32[4]; + uint64_t uint64[4]; + float float32[4]; + double float64[4]; +} RENDERDOC_AnnotationVectorValue; + +// a union with scalar annotation value data +typedef union RENDERDOC_AnnotationValue +{ + bool boolean; + int32_t int32; + int64_t int64; + uint32_t uint32; + uint64_t uint64; + float float32; + double float64; + + RENDERDOC_AnnotationVectorValue vector; + + const char *string; + void *apiObject; +} RENDERDOC_AnnotationValue; + +// a struct for specifying a GL object, as we don't have pointers we can use so instead we specify a +// pointer to this struct giving both the type and the name +typedef struct RENDERDOC_GLResourceReference +{ + // this is the same GLenum identifier as passed to glObjectLabel + uint32_t identifier; + uint32_t name; +} GLResourceReference; + +// simple C++ helpers to avoid the need for a temporary objects for value passing and GL object specification +#ifdef __cplusplus +struct RDGLObjectHelper +{ + RENDERDOC_GLResourceReference gl; + + RDGLObjectHelper(uint32_t identifier, uint32_t name) + { + gl.identifier = identifier; + gl.name = name; + } + + operator RENDERDOC_GLResourceReference *() { return ≷ } +}; + +struct RDAnnotationHelper +{ + RENDERDOC_AnnotationValue val; + + RDAnnotationHelper(bool b) { val.boolean = b; } + RDAnnotationHelper(int32_t i) { val.int32 = i; } + RDAnnotationHelper(int64_t i) { val.int64 = i; } + RDAnnotationHelper(uint32_t i) { val.uint32 = i; } + RDAnnotationHelper(uint64_t i) { val.uint64 = i; } + RDAnnotationHelper(float f) { val.float32 = f; } + RDAnnotationHelper(double d) { val.float64 = d; } + RDAnnotationHelper(const char *s) { val.string = s; } + + operator RENDERDOC_AnnotationValue *() { return &val; } +}; +#endif + +// The device is specified in the same way as other API calls that take a RENDERDOC_DevicePointer +// to specify the device. +// +// The object or queue/commandbuffer will depend on the graphics API in question. +// +// Return value: +// 0 - The annotation was applied successfully. +// 1 - The device is unknown/invalid +// 2 - The device is valid but the annotation is not supported for API-specific reasons, such as an +// unrecognised or invalid object or queue/commandbuffer +// 3 - The call is ill-formed or invalid e.g. empty is specified with a value pointer, or non-empty +// is specified with a NULL value pointer +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_SetObjectAnnotation)(RENDERDOC_DevicePointer device, + void *object, const char *key, + RENDERDOC_AnnotationType valueType, + uint32_t valueVectorWidth, + const RENDERDOC_AnnotationValue *value); + +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_SetCommandAnnotation)( + RENDERDOC_DevicePointer device, void *queueOrCommandBuffer, const char *key, + RENDERDOC_AnnotationType valueType, uint32_t valueVectorWidth, + const RENDERDOC_AnnotationValue *value); + +////////////////////////////////////////////////////////////////////////////////////////////////// +// RenderDoc API versions +// + +// RenderDoc uses semantic versioning (http://semver.org/). +// +// MAJOR version is incremented when incompatible API changes happen. +// MINOR version is incremented when functionality is added in a backwards-compatible manner. +// PATCH version is incremented when backwards-compatible bug fixes happen. +// +// Note that this means the API returned can be higher than the one you might have requested. +// e.g. if you are running against a newer RenderDoc that supports 1.0.1, it will be returned +// instead of 1.0.0. You can check this with the GetAPIVersion entry point +typedef enum RENDERDOC_Version +{ + eRENDERDOC_API_Version_1_0_0 = 10000, // RENDERDOC_API_1_0_0 = 1 00 00 + eRENDERDOC_API_Version_1_0_1 = 10001, // RENDERDOC_API_1_0_1 = 1 00 01 + eRENDERDOC_API_Version_1_0_2 = 10002, // RENDERDOC_API_1_0_2 = 1 00 02 + eRENDERDOC_API_Version_1_1_0 = 10100, // RENDERDOC_API_1_1_0 = 1 01 00 + eRENDERDOC_API_Version_1_1_1 = 10101, // RENDERDOC_API_1_1_1 = 1 01 01 + eRENDERDOC_API_Version_1_1_2 = 10102, // RENDERDOC_API_1_1_2 = 1 01 02 + eRENDERDOC_API_Version_1_2_0 = 10200, // RENDERDOC_API_1_2_0 = 1 02 00 + eRENDERDOC_API_Version_1_3_0 = 10300, // RENDERDOC_API_1_3_0 = 1 03 00 + eRENDERDOC_API_Version_1_4_0 = 10400, // RENDERDOC_API_1_4_0 = 1 04 00 + eRENDERDOC_API_Version_1_4_1 = 10401, // RENDERDOC_API_1_4_1 = 1 04 01 + eRENDERDOC_API_Version_1_4_2 = 10402, // RENDERDOC_API_1_4_2 = 1 04 02 + eRENDERDOC_API_Version_1_5_0 = 10500, // RENDERDOC_API_1_5_0 = 1 05 00 + eRENDERDOC_API_Version_1_6_0 = 10600, // RENDERDOC_API_1_6_0 = 1 06 00 + eRENDERDOC_API_Version_1_7_0 = 10700, // RENDERDOC_API_1_7_0 = 1 07 00 +} RENDERDOC_Version; + +// API version changelog: +// +// 1.0.0 - initial release +// 1.0.1 - Bugfix: IsFrameCapturing() was returning false for captures that were triggered +// by keypress or TriggerCapture, instead of Start/EndFrameCapture. +// 1.0.2 - Refactor: Renamed eRENDERDOC_Option_DebugDeviceMode to eRENDERDOC_Option_APIValidation +// 1.1.0 - Add feature: TriggerMultiFrameCapture(). Backwards compatible with 1.0.x since the new +// function pointer is added to the end of the struct, the original layout is identical +// 1.1.1 - Refactor: Renamed remote access to target control (to better disambiguate from remote +// replay/remote server concept in replay UI) +// 1.1.2 - Refactor: Renamed "log file" in function names to just capture, to clarify that these +// are captures and not debug logging files. This is the first API version in the v1.0 +// branch. +// 1.2.0 - Added feature: SetCaptureFileComments() to add comments to a capture file that will be +// displayed in the UI program on load. +// 1.3.0 - Added feature: New capture option eRENDERDOC_Option_AllowUnsupportedVendorExtensions +// which allows users to opt-in to allowing unsupported vendor extensions to function. +// Should be used at the user's own risk. +// Refactor: Renamed eRENDERDOC_Option_VerifyMapWrites to +// eRENDERDOC_Option_VerifyBufferAccess, which now also controls initialisation to +// 0xdddddddd of uninitialised buffer contents. +// 1.4.0 - Added feature: DiscardFrameCapture() to discard a frame capture in progress and stop +// capturing without saving anything to disk. +// 1.4.1 - Refactor: Renamed Shutdown to RemoveHooks to better clarify what is happening +// 1.4.2 - Refactor: Renamed 'draws' to 'actions' in callstack capture option. +// 1.5.0 - Added feature: ShowReplayUI() to request that the replay UI show itself if connected +// 1.6.0 - Added feature: SetCaptureTitle() which can be used to set a title for a +// capture made with StartFrameCapture() or EndFrameCapture() +// 1.7.0 - Added feature: SetObjectAnnotation() / SetCommandAnnotation() for adding rich +// annotations to objects and command streams + +typedef struct RENDERDOC_API_1_7_0 +{ + pRENDERDOC_GetAPIVersion GetAPIVersion; + + pRENDERDOC_SetCaptureOptionU32 SetCaptureOptionU32; + pRENDERDOC_SetCaptureOptionF32 SetCaptureOptionF32; + + pRENDERDOC_GetCaptureOptionU32 GetCaptureOptionU32; + pRENDERDOC_GetCaptureOptionF32 GetCaptureOptionF32; + + pRENDERDOC_SetFocusToggleKeys SetFocusToggleKeys; + pRENDERDOC_SetCaptureKeys SetCaptureKeys; + + pRENDERDOC_GetOverlayBits GetOverlayBits; + pRENDERDOC_MaskOverlayBits MaskOverlayBits; + + // Shutdown was renamed to RemoveHooks in 1.4.1. + // These unions allow old code to continue compiling without changes + union + { + pRENDERDOC_Shutdown Shutdown; + pRENDERDOC_RemoveHooks RemoveHooks; + }; + pRENDERDOC_UnloadCrashHandler UnloadCrashHandler; + + // Get/SetLogFilePathTemplate was renamed to Get/SetCaptureFilePathTemplate in 1.1.2. + // These unions allow old code to continue compiling without changes + union + { + // deprecated name + pRENDERDOC_SetLogFilePathTemplate SetLogFilePathTemplate; + // current name + pRENDERDOC_SetCaptureFilePathTemplate SetCaptureFilePathTemplate; + }; + union + { + // deprecated name + pRENDERDOC_GetLogFilePathTemplate GetLogFilePathTemplate; + // current name + pRENDERDOC_GetCaptureFilePathTemplate GetCaptureFilePathTemplate; + }; + + pRENDERDOC_GetNumCaptures GetNumCaptures; + pRENDERDOC_GetCapture GetCapture; + + pRENDERDOC_TriggerCapture TriggerCapture; + + // IsRemoteAccessConnected was renamed to IsTargetControlConnected in 1.1.1. + // This union allows old code to continue compiling without changes + union + { + // deprecated name + pRENDERDOC_IsRemoteAccessConnected IsRemoteAccessConnected; + // current name + pRENDERDOC_IsTargetControlConnected IsTargetControlConnected; + }; + pRENDERDOC_LaunchReplayUI LaunchReplayUI; + + pRENDERDOC_SetActiveWindow SetActiveWindow; + + pRENDERDOC_StartFrameCapture StartFrameCapture; + pRENDERDOC_IsFrameCapturing IsFrameCapturing; + pRENDERDOC_EndFrameCapture EndFrameCapture; + + // new function in 1.1.0 + pRENDERDOC_TriggerMultiFrameCapture TriggerMultiFrameCapture; + + // new function in 1.2.0 + pRENDERDOC_SetCaptureFileComments SetCaptureFileComments; + + // new function in 1.4.0 + pRENDERDOC_DiscardFrameCapture DiscardFrameCapture; + + // new function in 1.5.0 + pRENDERDOC_ShowReplayUI ShowReplayUI; + + // new function in 1.6.0 + pRENDERDOC_SetCaptureTitle SetCaptureTitle; + + // new functions in 1.7.0 + pRENDERDOC_SetObjectAnnotation SetObjectAnnotation; + pRENDERDOC_SetCommandAnnotation SetCommandAnnotation; +} RENDERDOC_API_1_7_0; + +typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_0_0; +typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_0_1; +typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_0_2; +typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_1_0; +typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_1_1; +typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_1_2; +typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_2_0; +typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_3_0; +typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_4_0; +typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_4_1; +typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_4_2; +typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_5_0; +typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_6_0; + +////////////////////////////////////////////////////////////////////////////////////////////////// +// RenderDoc API entry point +// +// This entry point can be obtained via GetProcAddress/dlsym if RenderDoc is available. +// +// The name is the same as the typedef - "RENDERDOC_GetAPI" +// +// This function is not thread safe, and should not be called on multiple threads at once. +// Ideally, call this once as early as possible in your application's startup, before doing +// any API work, since some configuration functionality etc has to be done also before +// initialising any APIs. +// +// Parameters: +// version is a single value from the RENDERDOC_Version above. +// +// outAPIPointers will be filled out with a pointer to the corresponding struct of function +// pointers. +// +// Returns: +// 1 - if the outAPIPointers has been filled with a pointer to the API struct requested +// 0 - if the requested version is not supported or the arguments are invalid. +// +typedef int(RENDERDOC_CC *pRENDERDOC_GetAPI)(RENDERDOC_Version version, void **outAPIPointers); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/CMakeLists.txt b/CMakeLists.txt index 9766387..9f4a00b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -329,6 +329,7 @@ set(inc_headers ${KYTY_THIRD_PARTY_DIR}/fmt/include ${KYTY_THIRD_PARTY_DIR}/xxhash ${KYTY_THIRD_PARTY_DIR}/cpuinfo/include + ${KYTY_THIRD_PARTY_DIR}/renderdoc ${KYTY_THIRD_PARTY_DIR}/stb ) diff --git a/src/common/hostException.cpp b/src/common/hostException.cpp index 0137a41..ea86fe0 100644 --- a/src/common/hostException.cpp +++ b/src/common/hostException.cpp @@ -2,7 +2,6 @@ #include #include -#include #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS #include // IWYU pragma: keep @@ -28,54 +27,14 @@ namespace Common::HostException { static std::atomic g_handler {nullptr}; static std::atomic_uint32_t g_install_state {0}; -static thread_local bool g_in_exception_filter = false; static_assert(decltype(g_handler)::is_always_lock_free); static_assert(decltype(g_install_state)::is_always_lock_free); - -[[noreturn]] static void FailFast(const char* reason) noexcept { - std::fputs("HostException fail-fast: ", stderr); - std::fputs(reason != nullptr ? reason : "unspecified", stderr); - std::fputc('\n', stderr); - std::fflush(stderr); -#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS - TerminateProcess(GetCurrentProcess(), static_cast(EXCEPTION_NONCONTINUABLE_EXCEPTION)); -#endif - std::_Exit(321); -} - -class FilterScope final { -public: - FilterScope() noexcept { - if (g_in_exception_filter) { - FailFast("nested exception while resolving a host fault"); - } - g_in_exception_filter = true; - } - - ~FilterScope() { g_in_exception_filter = false; } - - KYTY_CLASS_NO_COPY(FilterScope); -}; - -static Handler LoadInstalledHandler() noexcept { - if (g_install_state.load(std::memory_order_acquire) == 0) { - FailFast("host exception handler is not installed"); - } - - const auto handler = g_handler.load(std::memory_order_acquire); - if (handler == nullptr) { - FailFast("host exception callback is null"); - } - return handler; -} #endif #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS -static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) { - FilterScope filter_scope; - +static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) noexcept { auto* exception_record = exception->ExceptionRecord; if (exception_record->ExceptionCode == DBG_PRINTEXCEPTION_C || @@ -105,12 +64,6 @@ static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) { } else if (exception_record->ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION) { info.type = ExceptionType::IllegalInstruction; } else { - printf("Unhandled win exception: code=0x%08" PRIx32 ", addr=0x%016" PRIx64 - ", rip=0x%016" PRIx64 ", rsp=0x%016" PRIx64 ", rbp=0x%016" PRIx64 "\n", - static_cast(exception_record->ExceptionCode), - reinterpret_cast(exception_record->ExceptionAddress), - exception->ContextRecord->Rip, exception->ContextRecord->Rsp, - exception->ContextRecord->Rbp); return EXCEPTION_CONTINUE_SEARCH; } @@ -131,28 +84,21 @@ static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) { info.r14 = exception->ContextRecord->R14; info.r15 = exception->ContextRecord->R15; - const auto handler = LoadInstalledHandler(); - - return handler(info) ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_CONTINUE_SEARCH; + const auto handler = g_handler.load(std::memory_order_acquire); + if (handler != nullptr && handler(info)) { + return EXCEPTION_CONTINUE_EXECUTION; + } + return EXCEPTION_CONTINUE_SEARCH; } #elif defined(__APPLE__) static std::atomic g_handler {nullptr}; static std::atomic_uint32_t g_install_state {0}; -static thread_local bool g_in_exception_filter = false; static_assert(decltype(g_handler)::is_always_lock_free); static_assert(decltype(g_install_state)::is_always_lock_free); -[[noreturn]] static void FailFast(const char* reason) noexcept { - std::fputs("HostException fail-fast: ", stderr); - std::fputs(reason != nullptr ? reason : "unspecified", stderr); - std::fputc('\n', stderr); - std::fflush(stderr); - std::_Exit(321); -} - // Translate the x86-64 page-fault error code (mcontext __es.__err) into an access type. // bit 1 (0x2) = write, bit 4 (0x10) = instruction fetch, otherwise a read. static AccessViolationType DecodeAccess(uint64_t err) { @@ -170,11 +116,6 @@ static AccessViolationType DecodeAccess(uint64_t err) { // re-executing the faulting instruction against the now-fixed protection. An unresolved // fault restores the default disposition so the retry terminates the process. static void SignalHandler(int sig, siginfo_t* si, void* uctx) { - if (g_in_exception_filter) { - FailFast("nested exception while resolving a host fault"); - } - g_in_exception_filter = true; - auto* uc = static_cast(uctx); const auto* mc = uc->uc_mcontext; const auto& ss = mc->__ss; @@ -210,14 +151,7 @@ static void SignalHandler(int sig, siginfo_t* si, void* uctx) { info.r15 = ss.__r15; const auto handler = g_handler.load(std::memory_order_acquire); - if (handler == nullptr) { - FailFast("host exception callback is null"); - } - - const bool resolved = handler(info); - g_in_exception_filter = false; - - if (resolved) { + if (handler != nullptr && handler(info)) { return; // retry the faulting instruction against the fixed mapping } @@ -244,8 +178,6 @@ static void ChainToDefault(int signal_number) noexcept { } static void SignalHandler(int signal_number, siginfo_t* signal_info, void* native_context) { - FilterScope filter_scope; - auto* context = static_cast(native_context); auto* gregs = context->uc_mcontext.gregs; @@ -289,9 +221,8 @@ static void SignalHandler(int signal_number, siginfo_t* signal_info, void* nativ info.r14 = static_cast(gregs[REG_R14]); info.r15 = static_cast(gregs[REG_R15]); - const auto handler = LoadInstalledHandler(); - - if (handler(info)) { + const auto handler = g_handler.load(std::memory_order_acquire); + if (handler != nullptr && handler(info)) { return; } @@ -313,7 +244,7 @@ bool InstallHandler(Handler handler) { g_handler.store(handler, std::memory_order_release); #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS - if (AddVectoredExceptionHandler(1, ExceptionFilter) == nullptr) { + if (AddVectoredExceptionHandler(0, ExceptionFilter) == nullptr) { g_handler.store(nullptr, std::memory_order_release); g_install_state.store(0, std::memory_order_release); printf("AddVectoredExceptionHandler() failed\n"); diff --git a/src/graphics/guest_gpu/graphicsRun.cpp b/src/graphics/guest_gpu/graphicsRun.cpp index 707c036..74057a4 100644 --- a/src/graphics/guest_gpu/graphicsRun.cpp +++ b/src/graphics/guest_gpu/graphicsRun.cpp @@ -13,6 +13,7 @@ #include "graphics/host_gpu/renderer/render.h" #include "graphics/host_gpu/renderer/renderContext.h" #include "graphics/host_gpu/renderer/sync.h" +#include "graphics/presentation/renderDoc.h" #include "graphics/presentation/videoOut.h" #include "graphics/presentation/window.h" #include "graphics/shader/shader.h" @@ -278,6 +279,12 @@ void GpuState::Done() { if (!IsGpuThread()) { WaitForIdle(); } + if (RenderDocCaptureInProgress()) { + SendCommandSync([this] { + Common::LockGuard render_lock(m_renderer.GetMutex()); + RenderDocEndCapture(); + }); + } m_graphics_done = true; m_done_num++; } @@ -638,8 +645,12 @@ void GpuState::ThreadRun(void* data) { } bool GpuState::Process(Submission& submission) { - auto& cp = GetProcessor(submission.queue_id); const bool first_slice = !submission.started; + if (first_slice && RenderDocCaptureRequested()) { + Common::LockGuard render_lock(m_renderer.GetMutex()); + RenderDocStartCapture(); + } + auto& cp = GetProcessor(submission.queue_id); if (first_slice && submission.reset_processor) { cp.Reset(); diff --git a/src/graphics/host_gpu/vulkanCommon.h b/src/graphics/host_gpu/vulkanCommon.h index cb48533..c9b0fa4 100644 --- a/src/graphics/host_gpu/vulkanCommon.h +++ b/src/graphics/host_gpu/vulkanCommon.h @@ -22,11 +22,6 @@ namespace Libs::Graphics { using VulkanMemoryBarrier = vk::MemoryBarrier; -template -[[nodiscard]] void* VulkanHandleToPointer(Handle handle) { - return reinterpret_cast(static_cast(handle)); -} - std::string VulkanToString(vk::Result value); std::string VulkanToString(vk::Format value); std::string VulkanToString(vk::ImageLayout value); diff --git a/src/graphics/presentation/renderDoc.cpp b/src/graphics/presentation/renderDoc.cpp index 70c7fa1..07e1392 100644 --- a/src/graphics/presentation/renderDoc.cpp +++ b/src/graphics/presentation/renderDoc.cpp @@ -1,15 +1,12 @@ #include "graphics/presentation/renderDoc.h" -#include "SDL_syswm.h" -#include "SDL_version.h" -#include "SDL_video.h" #include "common/logging/log.h" #include #include -#include -#include +#include #include +#include #include #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS @@ -21,362 +18,161 @@ #undef max #else #include - -// RenderDoc uses Windows-style names in its cross-platform API. -#define __cdecl -using HMODULE = void*; #endif namespace Libs::Graphics { -using RenderDocDevicePointer = void*; -using RenderDocWindowHandle = void*; - -enum RenderDocVersion { - eRENDERDOC_API_Version_1_4_2 = 10402, -}; - -enum RenderDocInputButton { - eRENDERDOC_Key_NonPrintable = 0x100, - eRENDERDOC_Key_Divide, - eRENDERDOC_Key_Multiply, - eRENDERDOC_Key_Subtract, - eRENDERDOC_Key_Plus, - eRENDERDOC_Key_F1, -}; - -using pRENDERDOC_SetCaptureKeys = void(__cdecl*)(RenderDocInputButton* keys, int num); -using pRENDERDOC_SetCaptureFilePathTemplate = void(__cdecl*)(const char* pathtemplate); -using pRENDERDOC_GetCaptureFilePathTemplate = const char*(__cdecl*)(); -using pRENDERDOC_GetNumCaptures = uint32_t(__cdecl*)(); -using pRENDERDOC_GetCapture = uint32_t(__cdecl*)(uint32_t idx, char* filename, uint32_t* pathlength, - uint64_t* timestamp); -using pRENDERDOC_UnloadCrashHandler = void(__cdecl*)(); -using pRENDERDOC_SetActiveWindow = void(__cdecl*)(RenderDocDevicePointer device, - RenderDocWindowHandle wndHandle); -using pRENDERDOC_StartFrameCapture = void(__cdecl*)(RenderDocDevicePointer device, - RenderDocWindowHandle wndHandle); -using pRENDERDOC_IsFrameCapturing = uint32_t(__cdecl*)(); -using pRENDERDOC_EndFrameCapture = uint32_t(__cdecl*)(RenderDocDevicePointer device, - RenderDocWindowHandle wndHandle); -using pRENDERDOC_GetAPI = int(__cdecl*)(RenderDocVersion version, void** out_api_pointers); - -struct RenderDocApi { - void* GetAPIVersion; - void* SetCaptureOptionU32; - void* SetCaptureOptionF32; - void* GetCaptureOptionU32; - void* GetCaptureOptionF32; - void* SetFocusToggleKeys; - pRENDERDOC_SetCaptureKeys SetCaptureKeys; - void* GetOverlayBits; - void* MaskOverlayBits; - void* RemoveHooks; - pRENDERDOC_UnloadCrashHandler UnloadCrashHandler; - pRENDERDOC_SetCaptureFilePathTemplate SetCaptureFilePathTemplate; - pRENDERDOC_GetCaptureFilePathTemplate GetCaptureFilePathTemplate; - pRENDERDOC_GetNumCaptures GetNumCaptures; - pRENDERDOC_GetCapture GetCapture; - void* TriggerCapture; - void* IsTargetControlConnected; - void* LaunchReplayUI; - pRENDERDOC_SetActiveWindow SetActiveWindow; - pRENDERDOC_StartFrameCapture StartFrameCapture; - pRENDERDOC_IsFrameCapturing IsFrameCapturing; - pRENDERDOC_EndFrameCapture EndFrameCapture; - void* TriggerMultiFrameCapture; - void* SetCaptureFileComments; - void* DiscardFrameCapture; -}; - enum class RenderDocState : uint32_t { Idle, Requested, Capturing, }; -static RenderDocApi* g_api = nullptr; -static HMODULE g_module = nullptr; -static RenderDocDevicePointer g_device = nullptr; -static RenderDocWindowHandle g_window = nullptr; +static RENDERDOC_API_1_6_0* g_api = nullptr; static std::atomic g_state = RenderDocState::Idle; -static std::atomic_bool g_init_done = false; static std::atomic_bool g_unavailable_log = false; -static RenderDocDevicePointer GetRenderDocDevicePointer(vk::Instance instance) { - if (instance == nullptr) { - return nullptr; - } - - return VulkanHandleToPointer(instance); -} - #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS static bool BindRenderDocApi(HMODULE module) { auto* get_api = reinterpret_cast(GetProcAddress(module, "RENDERDOC_GetAPI")); - if (get_api == nullptr) { - return false; - } - - void* api = nullptr; - if (get_api(eRENDERDOC_API_Version_1_4_2, &api) == 0 || api == nullptr) { - return false; - } - - g_module = module; - g_api = static_cast(api); - - g_api->SetCaptureFilePathTemplate("_RenderDoc/kyty"); - g_api->SetCaptureKeys(nullptr, 0); - g_api->UnloadCrashHandler(); - - char module_path[MAX_PATH] = {}; - GetModuleFileNameA(module, module_path, sizeof(module_path)); - LOGF("RenderDoc: bound API from %s\n", module_path); - return true; -} - -static RenderDocWindowHandle GetRenderDocWindowHandle(SDL_Window* window) { - if (window == nullptr) { - return nullptr; - } - - SDL_SysWMinfo info {}; - SDL_VERSION(&info.version); - - if (SDL_GetWindowWMInfo(window, &info) != SDL_TRUE || info.subsystem != SDL_SYSWM_WINDOWS) { - return nullptr; - } - - return info.info.win.window; -} - #else - -static bool BindRenderDocApi(HMODULE module) { +static bool BindRenderDocApi(void* module) { auto* get_api = reinterpret_cast(::dlsym(module, "RENDERDOC_GetAPI")); +#endif if (get_api == nullptr) { return false; } void* api = nullptr; - if (get_api(eRENDERDOC_API_Version_1_4_2, &api) == 0 || api == nullptr) { + if (get_api(eRENDERDOC_API_Version_1_6_0, &api) != 1 || api == nullptr) { return false; } - g_module = module; - g_api = static_cast(api); - - g_api->SetCaptureFilePathTemplate("_RenderDoc/kyty"); + g_api = static_cast(api); g_api->SetCaptureKeys(nullptr, 0); g_api->UnloadCrashHandler(); - - Dl_info info {}; - if (::dladdr(reinterpret_cast(get_api), &info) != 0 && info.dli_fname != nullptr) { - LOGF("RenderDoc: bound API from %s\n", info.dli_fname); - } else { - LOGF("RenderDoc: bound API\n"); - } + LOGF("RenderDoc: API 1.6.0 bound\n"); return true; } -static RenderDocWindowHandle GetRenderDocWindowHandle(SDL_Window* window) { - if (window == nullptr) { - return nullptr; - } - - SDL_SysWMinfo info {}; - SDL_VERSION(&info.version); - - if (SDL_GetWindowWMInfo(window, &info) != SDL_TRUE) { - return nullptr; - } - -#if defined(SDL_VIDEO_DRIVER_X11) - if (info.subsystem == SDL_SYSWM_X11) { - // RenderDoc takes the raw xlib Window id in the pointer slot, not a Display*. - return reinterpret_cast( - static_cast(info.info.x11.window)); - } -#endif - - // Wayland capture works without an active-window handle. - static std::atomic_bool logged = false; - if (!logged.exchange(true)) { - LOGF("RenderDoc: no native window handle for SDL subsystem %d (Wayland?); the in-app " - "overlay is unavailable, but --rd captures still work\n", - static_cast(info.subsystem)); - } - return nullptr; -} - -#endif - -#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS - -static bool IsAvailable() { - return g_api != nullptr && g_device != nullptr && g_window != nullptr; -} - -#else - -static bool IsAvailable() { - return g_api != nullptr && g_device != nullptr; -} - -#endif - #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS void RenderDocInit() { - bool expected = false; - if (!g_init_done.compare_exchange_strong(expected, true)) { + if (g_api != nullptr) { return; } - HKEY h_reg_key; - LONG result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, - L"SOFTWARE\\Classes\\RenderDoc.RDCCapture.1\\DefaultIcon\\", 0, - KEY_READ, &h_reg_key); - if (result != ERROR_SUCCESS) { - return; - } - std::array key_str {}; - DWORD str_sz_out {key_str.size()}; - result = RegQueryValueExW(h_reg_key, L"", 0, NULL, (LPBYTE)key_str.data(), &str_sz_out); - RegCloseKey(h_reg_key); - if (result != ERROR_SUCCESS) { - return; - } - - std::filesystem::path path {key_str.cbegin(), key_str.cend()}; - path = path.parent_path().append("renderdoc.dll"); - const auto path_to_lib = path.generic_string(); - auto* module = LoadLibraryA(path_to_lib.c_str()); + auto* module = GetModuleHandleA("renderdoc.dll"); if (module == nullptr) { - return; + HKEY key = nullptr; + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Classes\\RenderDoc.RDCCapture.1\\DefaultIcon\\", 0, KEY_READ, + &key) != ERROR_SUCCESS) { + return; + } + + std::array path_buffer {}; + DWORD path_size = static_cast(path_buffer.size() * sizeof(wchar_t)); + const auto result = RegQueryValueExW( + key, L"", nullptr, nullptr, reinterpret_cast(path_buffer.data()), &path_size); + RegCloseKey(key); + if (result != ERROR_SUCCESS) { + return; + } + + auto path = std::filesystem::path(path_buffer.data()).parent_path() / "renderdoc.dll"; + module = LoadLibraryW(path.c_str()); + if (module == nullptr) { + return; + } } if (!BindRenderDocApi(module)) { - LOGF("RenderDoc: API 1.4.2 is not available; in-app capture disabled\n"); - FreeLibrary(module); - return; + LOGF("RenderDoc: API 1.6.0 is unavailable\n"); } } #else void RenderDocInit() { - bool expected = false; - if (!g_init_done.compare_exchange_strong(expected, true)) { + if (g_api != nullptr) { return; } - // Prefer an injected RenderDoc instance. auto* module = ::dlopen("librenderdoc.so", RTLD_NOW | RTLD_NOLOAD); if (module == nullptr) { module = ::dlopen("librenderdoc.so", RTLD_NOW); } if (module == nullptr) { - LOGF("RenderDoc: librenderdoc.so was not found; in-app capture disabled\n"); return; } if (!BindRenderDocApi(module)) { - LOGF("RenderDoc: API 1.4.2 is not available; in-app capture disabled\n"); + LOGF("RenderDoc: API 1.6.0 is unavailable\n"); ::dlclose(module); - return; } } #endif -void RenderDocSetActiveWindow(vk::Instance instance, SDL_Window* window) { - if (g_api == nullptr) { - return; - } - - g_device = GetRenderDocDevicePointer(instance); - g_window = GetRenderDocWindowHandle(window); - - if (g_device == nullptr || g_window == nullptr) { - LOGF("RenderDoc: active Vulkan window was not registered\n"); - return; - } - - g_api->SetActiveWindow(g_device, g_window); - LOGF("RenderDoc: active Vulkan window registered\n"); -} - void RenderDocRequestCapture() { - if (!IsAvailable()) { + if (g_api == nullptr) { if (!g_unavailable_log.exchange(true)) { - LOGF("RenderDoc: capture requested, but RenderDoc is not available\n"); + LOGF("RenderDoc: capture requested, but RenderDoc is unavailable\n"); } return; } RenderDocState expected = RenderDocState::Idle; if (g_state.compare_exchange_strong(expected, RenderDocState::Requested)) { - LOGF("RenderDoc: capture requested; next complete presented frame will be captured\n"); - } else { - LOGF("RenderDoc: capture request ignored because a capture is already pending\n"); + LOGF("RenderDoc: capture requested\n"); } } -static void LogNewestCapture() { - const auto count = g_api->GetNumCaptures(); - if (count == 0) { - return; - } - - char filename[4096] = {}; - uint32_t path_length = sizeof(filename); - uint64_t timestamp = 0; - - if (g_api->GetCapture(count - 1, filename, &path_length, ×tamp) != 0) { - filename[sizeof(filename) - 1] = '\0'; - LOGF("RenderDoc: wrote capture %s\n", filename); - } +bool RenderDocCaptureRequested() { + return g_state.load(std::memory_order_acquire) == RenderDocState::Requested; } -void RenderDocOnPresent() { - if (!IsAvailable()) { +bool RenderDocCaptureInProgress() { + return g_state.load(std::memory_order_acquire) == RenderDocState::Capturing; +} + +void RenderDocStartCapture() { + RenderDocState expected = RenderDocState::Requested; + if (g_api == nullptr || !g_state.compare_exchange_strong(expected, RenderDocState::Capturing, + std::memory_order_acq_rel)) { return; } - switch (g_state.load()) { - case RenderDocState::Idle: return; - case RenderDocState::Requested: - if (g_api->IsFrameCapturing() != 0) { - LOGF("RenderDoc: capture request ignored because RenderDoc is already capturing\n"); - g_state.store(RenderDocState::Idle); - return; - } + if (g_api->IsFrameCapturing() != 0) { + g_state.store(RenderDocState::Idle, std::memory_order_release); + LOGF("RenderDoc: capture request ignored because a capture is already active\n"); + return; + } - g_api->StartFrameCapture(nullptr, nullptr); - if (g_api->IsFrameCapturing() == 0) { - LOGF("RenderDoc: StartFrameCapture returned, but RenderDoc is not capturing\n"); - g_state.store(RenderDocState::Idle); - return; - } - g_state.store(RenderDocState::Capturing); - LOGF("RenderDoc: capture started\n"); - return; - case RenderDocState::Capturing: break; + const auto capture_id = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + const auto capture_path = "_RenderDoc/kyty_" + std::to_string(capture_id); + g_api->SetCaptureFilePathTemplate(capture_path.c_str()); + g_api->StartFrameCapture(nullptr, nullptr); + if (g_api->IsFrameCapturing() == 0) { + g_state.store(RenderDocState::Idle, std::memory_order_release); + LOGF("RenderDoc: capture failed to start\n"); + return; + } + LOGF("RenderDoc: capture started\n"); +} + +void RenderDocEndCapture() { + if (g_api == nullptr || !RenderDocCaptureInProgress()) { + return; } const auto ok = g_api->EndFrameCapture(nullptr, nullptr); - g_state.store(RenderDocState::Idle); - - if (ok != 0) { - const auto count = g_api->GetNumCaptures(); - LOGF("RenderDoc: capture finished, count=%u\n", count); - LogNewestCapture(); - } else { - LOGF("RenderDoc: capture failed\n"); - } + g_state.store(RenderDocState::Idle, std::memory_order_release); + LOGF(ok != 0 ? "RenderDoc: capture finished\n" : "RenderDoc: capture failed\n"); } } // namespace Libs::Graphics diff --git a/src/graphics/presentation/renderDoc.h b/src/graphics/presentation/renderDoc.h index ec275f0..f9ab900 100644 --- a/src/graphics/presentation/renderDoc.h +++ b/src/graphics/presentation/renderDoc.h @@ -1,17 +1,15 @@ #ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_RENDERDOC_H_ #define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_RENDERDOC_H_ -#include "common/common.h" -#include "graphics/host_gpu/vulkanCommon.h" - -struct SDL_Window; - namespace Libs::Graphics { void RenderDocInit(); -void RenderDocSetActiveWindow(vk::Instance instance, SDL_Window* window); void RenderDocRequestCapture(); -void RenderDocOnPresent(); +void RenderDocStartCapture(); +void RenderDocEndCapture(); + +[[nodiscard]] bool RenderDocCaptureRequested(); +[[nodiscard]] bool RenderDocCaptureInProgress(); } // namespace Libs::Graphics diff --git a/src/graphics/presentation/window/swapchain.cpp b/src/graphics/presentation/window/swapchain.cpp index c1467a6..4b9e2d4 100644 --- a/src/graphics/presentation/window/swapchain.cpp +++ b/src/graphics/presentation/window/swapchain.cpp @@ -32,7 +32,6 @@ #include "graphics/host_gpu/vulkanCommon.h" #include "graphics/presentation/imeOverlay.h" #include "graphics/presentation/presenter.h" -#include "graphics/presentation/renderDoc.h" #include "graphics/presentation/videoOut.h" #include "graphics/presentation/window/windowInternal.h" #include "libs/controller.h" @@ -866,7 +865,6 @@ void Presenter::Present(Frame& frame, bool reuse) { continue; } - RenderDocOnPresent(); m_impl->presented_ime_revision.store(ime_visual.revision, std::memory_order_release); window.UpdateTitle(); m_impl->frames.Release(&frame, true); diff --git a/src/graphics/presentation/window/vulkanWindow.cpp b/src/graphics/presentation/window/vulkanWindow.cpp index 1a6d322..ac39823 100644 --- a/src/graphics/presentation/window/vulkanWindow.cpp +++ b/src/graphics/presentation/window/vulkanWindow.cpp @@ -32,7 +32,6 @@ #include "graphics/host_gpu/vulkanCommon.h" #include "graphics/presentation/imeOverlay.h" #include "graphics/presentation/presenter.h" -#include "graphics/presentation/renderDoc.h" #include "graphics/presentation/videoOut.h" #include "graphics/presentation/window.h" #include "graphics/presentation/window/windowInternal.h" @@ -601,11 +600,11 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V robustness2_ext_enabled && robustness2.robustImageAccess2 == VK_TRUE ? "true" : "false"); vk::DeviceCreateInfo create_info {}; - create_info.sType = vk::StructureType::eDeviceCreateInfo; - create_info.pNext = &features13; - create_info.flags = {}; - create_info.pQueueCreateInfos = &queue_create_info; - create_info.queueCreateInfoCount = 1; + create_info.sType = vk::StructureType::eDeviceCreateInfo; + create_info.pNext = &features13; + create_info.flags = {}; + create_info.pQueueCreateInfos = &queue_create_info; + create_info.queueCreateInfoCount = 1; create_info.enabledExtensionCount = static_cast(device_extensions.size()); create_info.ppEnabledExtensionNames = device_extensions.data(); create_info.pEnabledFeatures = &device_features; @@ -986,7 +985,6 @@ void WindowContext::CreateVulkan() { render_context = std::make_unique(graphic_ctx); LibKernel::Memory::InstallGpuResources(&render_context->GetGpuResources()); presenter = std::make_unique(*this); - RenderDocSetActiveWindow(graphic_ctx.instance, window); } void WindowContext::RefreshSurfaceCapabilities() { diff --git a/src/kernel/memory.cpp b/src/kernel/memory.cpp index e44b620..ac0e00c 100644 --- a/src/kernel/memory.cpp +++ b/src/kernel/memory.cpp @@ -3447,17 +3447,6 @@ bool TestGuestFreeRangeBounds() { } #endif -bool KernelHandleReservedRangeAccessViolation(uint64_t vaddr) { - std::lock_guard memory_operation_lock(g_memory_operation_mutex); - - VirtualRanges::Range range {}; - if (!g_virtual_ranges->Query(vaddr, 0, &range) || - std::strncmp(range.name, "AMM", KERNEL_MAXIMUM_NAME_LENGTH) != 0) { - return false; - } - EXIT("AMM virtual-memory unmap is unsupported: addr=0x%016" PRIx64 "\n", vaddr); -} - int KYTY_SYSV_ABI KernelVirtualQuery(const void* addr, int flags, VirtualQueryInfo* info, uint64_t info_size) { PRINT_NAME(); diff --git a/src/kernel/memory.h b/src/kernel/memory.h index e51f9d2..dd0c28a 100644 --- a/src/kernel/memory.h +++ b/src/kernel/memory.h @@ -151,7 +151,6 @@ int KYTY_SYSV_ABI KernelVirtualQuery(const void* addr, int flags, VirtualQueryIn uint64_t info_size); int KYTY_SYSV_ABI KernelIsStack(void* addr, void** start, void** end); int KYTY_SYSV_ABI KernelReserveVirtualRange(void** addr, size_t len, int flags, size_t alignment); -bool KernelHandleReservedRangeAccessViolation(uint64_t vaddr); int KYTY_SYSV_ABI KernelAvailableFlexibleMemorySize(size_t* size); int KYTY_SYSV_ABI KernelConfiguredFlexibleMemorySize(size_t* size); int KYTY_SYSV_ABI KernelMprotect(const void* addr, size_t len, int prot); diff --git a/src/loader/runtimeLinker.cpp b/src/loader/runtimeLinker.cpp index 8354483..3ad30d4 100644 --- a/src/loader/runtimeLinker.cpp +++ b/src/loader/runtimeLinker.cpp @@ -38,7 +38,6 @@ #endif #include #else -#include #if defined(__APPLE__) #include #include @@ -780,15 +779,6 @@ static bool IsReadableRange(uint64_t addr, uint64_t size) { return true; } -static bool IsDumpableRange(uint64_t addr, uint64_t size) { -#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX - return IsReadableRange(addr, size); -#else - (void)size; - return addr != 0; -#endif -} - static bool KytyExceptionHandler(const Common::HostException::ExceptionInfo& exception_info) { const auto* info = &exception_info; @@ -798,217 +788,19 @@ static bool KytyExceptionHandler(const Common::HostException::ExceptionInfo& exc } if (info->type == Common::HostException::ExceptionType::AccessViolation) { - using CoreAccess = Common::HostException::AccessViolationType; - using GpuAccess = Libs::Graphics::PageFaultAccess; - const auto access = [&]() { - switch (info->access_violation_type) { - case CoreAccess::Read: return GpuAccess::Read; - case CoreAccess::Write: return GpuAccess::Write; - case CoreAccess::Execute: return GpuAccess::Execute; - case CoreAccess::Unknown: - EXIT("unknown access type for page fault at 0x%016" PRIx64 "\n", - info->access_violation_vaddr); - } - EXIT("invalid access type for page fault at 0x%016" PRIx64 "\n", - info->access_violation_vaddr); - }(); + using CoreAccess = Common::HostException::AccessViolationType; + using GpuAccess = Libs::Graphics::PageFaultAccess; + GpuAccess access; + switch (info->access_violation_type) { + case CoreAccess::Read: access = GpuAccess::Read; break; + case CoreAccess::Write: access = GpuAccess::Write; break; + case CoreAccess::Execute: access = GpuAccess::Execute; break; + case CoreAccess::Unknown: return false; + } if (Libs::LibKernel::Memory::HandleGpuFault(access, info->access_violation_vaddr)) { return true; } - - if (Libs::LibKernel::Memory::KernelHandleReservedRangeAccessViolation( - info->access_violation_vaddr)) { - return true; - } } - - LOGF("kyty_exception_handler: %016" PRIx64 "\n", info->exception_address); -#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS - HMODULE owner_module = nullptr; - if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | - GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, - reinterpret_cast(info->exception_address), &owner_module) != 0 && - owner_module != nullptr) { - char module_name[MAX_PATH] = {}; - if (GetModuleFileNameA(owner_module, module_name, MAX_PATH) != 0) { - LOGF("exception module: %s\n", module_name); - } - } -#else - Dl_info module_info {}; - if (::dladdr(reinterpret_cast(info->exception_address), &module_info) != 0 && - module_info.dli_fname != nullptr) { - LOGF("exception module: %s\n", module_info.dli_fname); - } -#endif - if (info->exception_address != 0) { -#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS - MEMORY_BASIC_INFORMATION mem_info = {}; - auto* dump_ptr = reinterpret_cast(info->exception_address - 32); - if (VirtualQuery(dump_ptr, &mem_info, sizeof(mem_info)) != 0 && - mem_info.State == MEM_COMMIT && mem_info.Protect != PAGE_NOACCESS && - (mem_info.Protect & PAGE_GUARD) == 0) { - const auto dump_start = reinterpret_cast(dump_ptr); - const auto region_end = - reinterpret_cast(mem_info.BaseAddress) + mem_info.RegionSize; - const auto dump_size = - (dump_start + 64 <= region_end ? 64u - : static_cast(region_end - dump_start)); - LOGF("code-32:"); - for (uint32_t i = 0; i < dump_size; i++) { - LOGF(" %02" PRIx32, static_cast(dump_ptr[i])); - } - LOGF("\n"); - } else { - LOGF("code-32: unavailable\n"); - } -#else - const auto fault_addr = info->exception_address; - const auto dump_start = (fault_addr >= 32 ? fault_addr - 32 : fault_addr); - if (IsReadableRange(dump_start, 64)) { - auto* dump_ptr = reinterpret_cast(dump_start); - LOGF("code-32:"); - for (uint32_t i = 0; i < 64; i++) { - LOGF(" %02" PRIx32, static_cast(dump_ptr[i])); - } - LOGF("\n"); - } else { - LOGF("code-32: unavailable\n"); - } -#endif - } else { - LOGF("code: unavailable\n"); - } - LOGF("exception: type=%s, av_type=%s, av_addr=%016" PRIx64 ", native_code=%08" PRIx32 "\n", - Common::EnumName(info->type).c_str(), - Common::EnumName(info->access_violation_type).c_str(), info->access_violation_vaddr, - info->native_code); - LOGF("regs: rax=%016" PRIx64 " rbx=%016" PRIx64 " rcx=%016" PRIx64 " rdx=%016" PRIx64 "\n", - info->rax, info->rbx, info->rcx, info->rdx); - LOGF("regs: rsi=%016" PRIx64 " rdi=%016" PRIx64 " rbp=%016" PRIx64 " rsp=%016" PRIx64 "\n", - info->rsi, info->rdi, info->rbp, info->rsp); - LOGF("regs: r8 =%016" PRIx64 " r9 =%016" PRIx64 " r10=%016" PRIx64 " r11=%016" PRIx64 "\n", - info->r8, info->r9, info->r10, info->r11); - LOGF("regs: r12=%016" PRIx64 " r13=%016" PRIx64 " r14=%016" PRIx64 " r15=%016" PRIx64 "\n", - info->r12, info->r13, info->r14, info->r15); - - if (IsReadableRange(info->rsp, 16u * sizeof(uint64_t))) { - auto* stack = reinterpret_cast(info->rsp); - LOGF("stack:"); - for (uint64_t i = 0; i < 16; i++) { - LOGF(" [%02" PRIu64 "]=%016" PRIx64, i, stack[i]); - } - LOGF("\n"); - } else { - LOGF("stack: unavailable\n"); - } - - auto dump_guest_code = [](const char* name, uint64_t addr) { - auto* p = Common::Singleton::Instance()->FindProgramByAddr(addr); - if (p == nullptr || addr < p->base_vaddr) { - return; - } - -#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS - MEMORY_BASIC_INFORMATION mbi {}; - auto* dump_ptr = reinterpret_cast(addr >= 16 ? addr - 16 : addr); - if (VirtualQuery(dump_ptr, &mbi, sizeof(mbi)) == 0 || mbi.State != MEM_COMMIT || - (mbi.Protect & (PAGE_NOACCESS | PAGE_GUARD)) != 0) { - return; - } - const auto dump_start = reinterpret_cast(dump_ptr); - const auto region_end = reinterpret_cast(mbi.BaseAddress) + mbi.RegionSize; - const auto dump_size = - (dump_start + 32 <= region_end ? 32u : static_cast(region_end - dump_start)); -#else - auto* dump_ptr = reinterpret_cast(addr >= 16 ? addr - 16 : addr); - const auto dump_size = 32u; - if (!IsReadableRange(reinterpret_cast(dump_ptr), dump_size)) { - return; - } -#endif - - LOGF("%s code: addr=%016" PRIx64 ", off=%016" PRIx64 ", module=%s:", name, addr, - addr - p->base_vaddr, - Common::FilenameWithoutDirectory(Common::PathToGenericString(p->file_name)).c_str()); - for (uint32_t i = 0; i < dump_size; i++) { - LOGF(" %02" PRIx32, static_cast(dump_ptr[i])); - } - LOGF("\n"); - }; - - dump_guest_code("guest rax[0]", info->rax); - dump_guest_code("guest rbx[0]", info->rbx); - dump_guest_code("guest rcx[0]", info->rcx); - dump_guest_code("guest rsi[0]", info->rsi); - if (IsDumpableRange(info->rsp, 16u * sizeof(uint64_t))) { - auto* stack = reinterpret_cast(info->rsp); - for (uint64_t i = 0; i < 16; i++) { - char name[32] {}; - std::snprintf(name, sizeof(name), "stack[%" PRIu64 "]", i); - dump_guest_code(name, stack[i]); - } - } - - if (info->type == Common::HostException::ExceptionType::AccessViolation) { - if (info->rbp != 0) { - Common::Singleton::Instance()->StackTrace(info->rbp, info->rsp); - } - - auto dump_guest_qwords = [](const char* name, uint64_t addr) { - if (addr == 0) { - LOGF("%s = 0\n", name); - return; - } - -#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS - MEMORY_BASIC_INFORMATION mbi {}; - if (VirtualQuery(reinterpret_cast(addr), &mbi, sizeof(mbi)) == 0 || - mbi.State != MEM_COMMIT || (mbi.Protect & (PAGE_NOACCESS | PAGE_GUARD)) != 0) { - LOGF("%s = %016" PRIx64 " (unmapped)\n", name, addr); - return; - } -#endif - - if (!IsReadableRange(addr, 8u * sizeof(uint64_t))) { - LOGF("%s = %016" PRIx64 " (unmapped)\n", name, addr); - return; - } - - auto* q = reinterpret_cast(addr); - LOGF("%s = %016" PRIx64 ": %016" PRIx64 " %016" PRIx64 " %016" PRIx64 " %016" PRIx64 - " %016" PRIx64 " %016" PRIx64 " %016" PRIx64 " %016" PRIx64 "\n", - name, addr, q[0], q[1], q[2], q[3], q[4], q[5], q[6], q[7]); - }; - - dump_guest_qwords("guest rbx", info->rbx); - dump_guest_qwords("guest rax", info->rax); - dump_guest_qwords("guest rcx", info->rcx); - dump_guest_qwords("guest rsi", info->rsi); - dump_guest_qwords("guest rdi", info->rdi); - dump_guest_qwords("guest r8 ", info->r8); - dump_guest_qwords("guest r9 ", info->r9); - dump_guest_qwords("guest r10", info->r10); - dump_guest_qwords("guest r11", info->r11); - dump_guest_qwords("guest r12", info->r12); - dump_guest_qwords("guest r13", info->r13); - dump_guest_qwords("guest r14", info->r14); - dump_guest_qwords("guest r15", info->r15); - - if (info->exception_address == 0x000000090064364e && - IsDumpableRange(info->rbx, sizeof(uint64_t))) { - auto* local = reinterpret_cast(info->rbx); - dump_guest_qwords("vorbis obj", local[0]); - dump_guest_qwords("vorbis len", info->rcx); - } - - EXIT("Access violation: %s [%016" PRIx64 "] %s\n", - Common::EnumName(info->access_violation_type).c_str(), info->access_violation_vaddr, - (info->access_violation_vaddr == g_invalid_memory ? "(Unpatched object)" : "")); - return false; - } - - EXIT("Unknown exception!!! (%08" PRIx32 ")", info->native_code); return false; }