diff --git a/.github/workflows/ci-examples.sh b/.github/workflows/ci-examples.sh index 7372d70a7a..157f8c3041 100755 --- a/.github/workflows/ci-examples.sh +++ b/.github/workflows/ci-examples.sh @@ -170,6 +170,9 @@ function run_sample { pushd "$bin_dir" 1>/dev/null 2>&1 if [[ ! "$dry_run" = true ]]; then ./"$sample" "${args[@]}" || result=$? + if [[ -f ./"log-$sample.txt" ]]; then + cat ./"log-$sample.txt" + fi fi if [[ $result -eq 0 ]]; then summary=("${summary[@]}" " success") diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index e46f41e7a9..759d99994e 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,4 +1,6 @@ function(example dir) + cmake_parse_arguments(ARG "WIN32_EXECUTABLE" "" "" ${ARGN}) + set(debug_dir ${CMAKE_CURRENT_BINARY_DIR}/${dir}) file( @@ -30,6 +32,22 @@ function(example dir) ) endif() + # Libraries providing a main function that prints stack traces on exceptions + if(CMAKE_SYSTEM_NAME MATCHES "Windows") + # On Windows we have two different versions: main for "console applications" and + # WinMain for normal Windows applications. + if(${ARG_WIN32_EXECUTABLE}) + set(main_wrapper_libraries example-winmain) + else() + set(main_wrapper_libraries example-main) + endif() + # Add stack printing support + set(main_wrapper_libraries ${main_wrapper_libraries} stacktrace-windows) + set(main_wrapper_libraries ${main_wrapper_libraries} dbghelp.lib) + else() + set(main_wrapper_libraries example-main) + endif() + slang_add_target( ${dir} EXECUTABLE @@ -42,7 +60,9 @@ function(example dir) gfx-util platform $<$:CUDA::cuda_driver> + ${main_wrapper_libraries} EXTRA_COMPILE_DEFINITIONS_PRIVATE + SLANG_EXAMPLE_NAME=${dir} $<$:SLANG_ENABLE_XLIB> REQUIRED_BY all-examples OPTIONAL_REQUIRES ${copy_assets_target} copy-prebuilt-binaries @@ -68,6 +88,9 @@ if(SLANG_ENABLE_EXAMPLES) $<$:CUDA::cuda_driver> FOLDER examples ) + slang_add_target(example-main STATIC FOLDER examples) + slang_add_target(example-winmain STATIC FOLDER examples EXCLUDE_FROM_ALL) + slang_add_target(stacktrace-windows STATIC FOLDER examples EXCLUDE_FROM_ALL) add_custom_target( all-examples diff --git a/examples/autodiff-texture/main.cpp b/examples/autodiff-texture/main.cpp index d0c35d003f..d99f9f341f 100644 --- a/examples/autodiff-texture/main.cpp +++ b/examples/autodiff-texture/main.cpp @@ -823,4 +823,4 @@ struct AutoDiffTexture : public WindowedAppBase } }; -PLATFORM_UI_MAIN(innerMain) +EXAMPLE_MAIN(innerMain); diff --git a/examples/cpu-com-example/main.cpp b/examples/cpu-com-example/main.cpp index 382b3cacd0..6c67215b46 100644 --- a/examples/cpu-com-example/main.cpp +++ b/examples/cpu-com-example/main.cpp @@ -175,7 +175,7 @@ static SlangResult _innerMain(int argc, char** argv) return SLANG_OK; } -int main(int argc, char** argv) +int exampleMain(int argc, char** argv) { return SLANG_SUCCEEDED(_innerMain(argc, argv)) ? 0 : -1; } diff --git a/examples/cpu-hello-world/main.cpp b/examples/cpu-hello-world/main.cpp index 60a24fa8c1..76ca5af1de 100644 --- a/examples/cpu-hello-world/main.cpp +++ b/examples/cpu-hello-world/main.cpp @@ -217,7 +217,7 @@ static SlangResult _innerMain(int argc, char** argv) return SLANG_OK; } -int main(int argc, char** argv) +int exampleMain(int argc, char** argv) { return SLANG_SUCCEEDED(_innerMain(argc, argv)) ? 0 : -1; } diff --git a/examples/example-base/example-base.h b/examples/example-base/example-base.h index 6988d613be..9aabac8d44 100644 --- a/examples/example-base/example-base.h +++ b/examples/example-base/example-base.h @@ -10,6 +10,19 @@ void _Win32OutputDebugString(const char* str); #endif +#define SLANG_STRINGIFY(x) #x +#define SLANG_EXPAND_STRINGIFY(x) SLANG_STRINGIFY(x) + +#ifdef _WIN32 +#define EXAMPLE_MAIN(innerMain) \ + extern const char* const g_logFileName = \ + "log-" SLANG_EXPAND_STRINGIFY(SLANG_EXAMPLE_NAME) ".txt"; \ + PLATFORM_UI_MAIN(innerMain); + +#else +#define EXAMPLE_MAIN(innerMain) PLATFORM_UI_MAIN(innerMain) +#endif // _WIN32 + struct WindowedAppBase : public TestBase { protected: diff --git a/examples/example-main/main.cpp b/examples/example-main/main.cpp new file mode 100644 index 0000000000..46ffc7278d --- /dev/null +++ b/examples/example-main/main.cpp @@ -0,0 +1,32 @@ +#include "../stacktrace-windows/common.h" + +#include +#include + +extern int exampleMain(int argc, char** argv); + +#if defined(_WIN32) + +#include + +int main(int argc, char** argv) +{ + __try + { + return exampleMain(argc, argv); + } + __except (exceptionFilter(stdout, GetExceptionInformation())) + { + ::exit(1); + } +} + +#else // defined(_WIN32) + +int main(int argc, char** argv) +{ + // TODO: Catch exception and print stack trace also on non-Windows platforms. + return exampleMain(argc, argv); +} + +#endif diff --git a/examples/example-winmain/main.cpp b/examples/example-winmain/main.cpp new file mode 100644 index 0000000000..fbf7e87574 --- /dev/null +++ b/examples/example-winmain/main.cpp @@ -0,0 +1,28 @@ +#include "../stacktrace-windows/common.h" + +#include +#include +#include + +extern int exampleMain(int argc, char** argv); +extern const char* const g_logFileName; + +int WinMain( + HINSTANCE /* instance */, + HINSTANCE /* prevInstance */, + LPSTR /* commandLine */, + int /*showCommand*/) + +{ + FILE * logFile = fopen(g_logFileName, "w"); + __try + { + int argc = 0; + char** argv = nullptr; + return exampleMain(argc, argv); + } + __except (exceptionFilter(logFile, GetExceptionInformation())) + { + ::exit(1); + } +} diff --git a/examples/gpu-printing/main.cpp b/examples/gpu-printing/main.cpp index bbc300dba4..27a77a82b5 100644 --- a/examples/gpu-printing/main.cpp +++ b/examples/gpu-printing/main.cpp @@ -152,7 +152,7 @@ struct ExampleProgram : public TestBase } }; -int main(int argc, char* argv[]) +int exampleMain(int argc, char** argv) { ExampleProgram app; if (SLANG_FAILED(app.execute(argc, argv))) diff --git a/examples/hello-world/main.cpp b/examples/hello-world/main.cpp index 7e84d83e5c..bcf421dd9d 100644 --- a/examples/hello-world/main.cpp +++ b/examples/hello-world/main.cpp @@ -22,6 +22,7 @@ struct HelloWorldExample : public TestBase { // The Vulkan functions pointers result from loading the vulkan library. VulkanAPI vkAPI; + bool vkAPIValid = false; // Vulkan objects used in this example. VkQueue queue; @@ -66,7 +67,8 @@ struct HelloWorldExample : public TestBase ~HelloWorldExample(); }; -int main(int argc, char* argv[]) + +int exampleMain(int argc, char** argv) { initDebugCallback(); HelloWorldExample example; @@ -95,6 +97,7 @@ int HelloWorldExample::initVulkanInstanceAndDevice() printf("Failed to load Vulkan.\n"); return -1; } + vkAPIValid = true; VkCommandPoolCreateInfo poolCreateInfo = {}; poolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; @@ -511,15 +514,18 @@ int HelloWorldExample::printComputeResults() HelloWorldExample::~HelloWorldExample() { - vkAPI.vkDestroyPipeline(vkAPI.device, pipeline, nullptr); - for (int i = 0; i < 3; i++) + if (vkAPIValid) { - vkAPI.vkDestroyBuffer(vkAPI.device, inOutBuffers[i], nullptr); - vkAPI.vkFreeMemory(vkAPI.device, bufferMemories[i], nullptr); + vkAPI.vkDestroyPipeline(vkAPI.device, pipeline, nullptr); + for (int i = 0; i < 3; i++) + { + vkAPI.vkDestroyBuffer(vkAPI.device, inOutBuffers[i], nullptr); + vkAPI.vkFreeMemory(vkAPI.device, bufferMemories[i], nullptr); + } + vkAPI.vkDestroyBuffer(vkAPI.device, stagingBuffer, nullptr); + vkAPI.vkFreeMemory(vkAPI.device, stagingMemory, nullptr); + vkAPI.vkDestroyPipelineLayout(vkAPI.device, pipelineLayout, nullptr); + vkAPI.vkDestroyDescriptorSetLayout(vkAPI.device, descriptorSetLayout, nullptr); + vkAPI.vkDestroyCommandPool(vkAPI.device, commandPool, nullptr); } - vkAPI.vkDestroyBuffer(vkAPI.device, stagingBuffer, nullptr); - vkAPI.vkFreeMemory(vkAPI.device, stagingMemory, nullptr); - vkAPI.vkDestroyPipelineLayout(vkAPI.device, pipelineLayout, nullptr); - vkAPI.vkDestroyDescriptorSetLayout(vkAPI.device, descriptorSetLayout, nullptr); - vkAPI.vkDestroyCommandPool(vkAPI.device, commandPool, nullptr); } diff --git a/examples/model-viewer/main.cpp b/examples/model-viewer/main.cpp index ecca818f18..8bbc8ec88c 100644 --- a/examples/model-viewer/main.cpp +++ b/examples/model-viewer/main.cpp @@ -969,4 +969,4 @@ struct ModelViewer : WindowedAppBase // This macro instantiates an appropriate main function to // run the application defined above. -PLATFORM_UI_MAIN(innerMain) +EXAMPLE_MAIN(innerMain); diff --git a/examples/nv-aftermath-example/main.cpp b/examples/nv-aftermath-example/main.cpp index 9d85f1ff4f..ed6db43a2b 100644 --- a/examples/nv-aftermath-example/main.cpp +++ b/examples/nv-aftermath-example/main.cpp @@ -599,4 +599,4 @@ void AftermathCrashExample::renderFrame(int frameBufferIndex) // This macro instantiates an appropriate main function to // run the application defined above. -PLATFORM_UI_MAIN(innerMain) +EXAMPLE_MAIN(innerMain) diff --git a/examples/platform-test/main.cpp b/examples/platform-test/main.cpp index 159e26c553..865e4eab79 100644 --- a/examples/platform-test/main.cpp +++ b/examples/platform-test/main.cpp @@ -122,4 +122,4 @@ struct PlatformTest : public WindowedAppBase // This macro instantiates an appropriate main function to // run the application defined above. -PLATFORM_UI_MAIN(innerMain) +EXAMPLE_MAIN(innerMain); diff --git a/examples/ray-tracing-pipeline/main.cpp b/examples/ray-tracing-pipeline/main.cpp index 0fbb857f02..0307cc564f 100644 --- a/examples/ray-tracing-pipeline/main.cpp +++ b/examples/ray-tracing-pipeline/main.cpp @@ -708,4 +708,4 @@ struct RayTracing : public WindowedAppBase // This macro instantiates an appropriate main function to // run the application defined above. -PLATFORM_UI_MAIN(innerMain) +EXAMPLE_MAIN(innerMain); diff --git a/examples/ray-tracing/main.cpp b/examples/ray-tracing/main.cpp index 11529a7538..649196dafe 100644 --- a/examples/ray-tracing/main.cpp +++ b/examples/ray-tracing/main.cpp @@ -672,4 +672,4 @@ struct RayTracing : public WindowedAppBase // This macro instantiates an appropriate main function to // run the application defined above. -PLATFORM_UI_MAIN(innerMain) +EXAMPLE_MAIN(innerMain); diff --git a/examples/reflection-api/main.cpp b/examples/reflection-api/main.cpp index 5c157b7976..c072c641b9 100644 --- a/examples/reflection-api/main.cpp +++ b/examples/reflection-api/main.cpp @@ -1469,7 +1469,7 @@ struct ExampleProgram : public TestBase } }; -int main(int argc, char* argv[]) +int exampleMain(int argc, char** argv) { ExampleProgram app; if (SLANG_FAILED(app.execute(argc, argv))) diff --git a/examples/shader-object/main.cpp b/examples/shader-object/main.cpp index f5c02141f2..1010cdcb91 100644 --- a/examples/shader-object/main.cpp +++ b/examples/shader-object/main.cpp @@ -131,7 +131,7 @@ Result loadShaderProgram( } // Main body of the example. -int main(int argc, char* argv[]) +int exampleMain(int argc, char** argv) { testBase.parseOption(argc, argv); diff --git a/examples/shader-toy/main.cpp b/examples/shader-toy/main.cpp index 185d182461..42054beaeb 100644 --- a/examples/shader-toy/main.cpp +++ b/examples/shader-toy/main.cpp @@ -408,4 +408,4 @@ struct ShaderToyApp : public WindowedAppBase // This macro instantiates an appropriate main function to // run the application defined above. -PLATFORM_UI_MAIN(innerMain) +EXAMPLE_MAIN(innerMain); diff --git a/examples/stacktrace-windows/common.cpp b/examples/stacktrace-windows/common.cpp new file mode 100644 index 0000000000..7b331c6ab2 --- /dev/null +++ b/examples/stacktrace-windows/common.cpp @@ -0,0 +1,202 @@ +#include "common.h" + +#include +#include +#include +#include +// dbghelp.h needs to be included after windows.h +// clang-format off +#include +// clang-format on + +#define SLANG_EXAMPLE_LOG_ERROR(...) \ + fprintf(file, "error: %s: %d: ", __FILE__, __LINE__); \ + print(file, __VA_ARGS__); \ + fprintf(file, "\n"); + +static void print(FILE* /* file */) {} +static void print(FILE* file, unsigned int n) +{ + fprintf(file, "%u", n); +} + + +static bool getModuleFileNameAtAddress(FILE* file, DWORD64 const address, std::string& fileName) +{ + HMODULE module = NULL; + { + BOOL result = GetModuleHandleEx( + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + (LPCTSTR)address, + &module); + if (result == 0) + { + SLANG_EXAMPLE_LOG_ERROR(GetLastError()); + return false; + } + if (module == NULL) + { + SLANG_EXAMPLE_LOG_ERROR(); + return false; + } + } + + std::vector buffer(1U << 8U); + uint32_t constexpr maxBufferSize = 1U << 20; + while (buffer.size() < maxBufferSize) + { + DWORD result = GetModuleFileNameA(module, buffer.data(), buffer.size()); + if (result == 0) + { + SLANG_EXAMPLE_LOG_ERROR(GetLastError()); + return false; + } + else if (result == ERROR_INSUFFICIENT_BUFFER) + { + buffer.resize(buffer.size() << 1U); + } + else + { + break; + } + } + if (buffer.size() == maxBufferSize) + { + SLANG_EXAMPLE_LOG_ERROR(); + return false; + } + + fileName = std::string(buffer.data(), buffer.data() + buffer.size()); + return true; +} + +// NOTE: This function is not thread-safe, due to usage of StackWalk64 and static buffers. +static bool printStack(FILE* file, HANDLE process, HANDLE thread, CONTEXT const& context) +{ +#if defined(_M_AMD64) + DWORD constexpr machineType = IMAGE_FILE_MACHINE_AMD64; +#else +#error Unsupported machine type +#endif + + static char symbolBuffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; + + // StackWalk64 may modify the context record + CONTEXT contextCopy; + memcpy(&contextCopy, &context, sizeof(CONTEXT)); + + STACKFRAME64 frame = {}; + constexpr uint32_t maxFrameCount = 1U << 10; + uint32_t frameIndex = 0U; + while (frameIndex < maxFrameCount) + { + // Use the default routine + PREAD_PROCESS_MEMORY_ROUTINE64 readMemoryRoutine = NULL; + // Not sure what this is for, but documentation says most callers can pass NULL + PTRANSLATE_ADDRESS_ROUTINE64 translateAddressRoutine = NULL; + { + BOOL result = StackWalk64( + machineType, + process, + thread, + &frame, + &contextCopy, + readMemoryRoutine, + SymFunctionTableAccess64, + SymGetModuleBase64, + translateAddressRoutine); + if (result == FALSE) + break; + } + + PSYMBOL_INFO maybeSymbol = (PSYMBOL_INFO)symbolBuffer; + { + maybeSymbol->SizeOfStruct = sizeof(SYMBOL_INFO); + maybeSymbol->MaxNameLen = MAX_SYM_NAME; + DWORD64 address = frame.AddrPC.Offset; + // Not required, we want to look up the symbol exactly at the address + PDWORD64 displacement = NULL; + BOOL result = SymFromAddr(process, address, displacement, maybeSymbol); + if (result == FALSE) + { + SLANG_EXAMPLE_LOG_ERROR(GetLastError()); + maybeSymbol = NULL; + } + } + + fprintf(file, "%u", frameIndex); + + std::string moduleFileName; + if (getModuleFileNameAtAddress(file, frame.AddrPC.Offset, moduleFileName)) + fprintf(file, ": %s", moduleFileName.c_str()); + + if (maybeSymbol) + { + PSYMBOL_INFO& symbol = maybeSymbol; + + IMAGEHLP_LINE64 line = {}; + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + + DWORD displacement; + if (SymGetLineFromAddr64(process, frame.AddrPC.Offset, &displacement, &line)) + { + fprintf(file, ": %s: %s: %lu", symbol->Name, line.FileName, line.LineNumber); + } + else + { + fprintf(file, ": %s", symbol->Name); + } + + fprintf(file, ": 0x%.16" PRIXPTR, symbol->Address); + } + fprintf(file, "\n"); + + frameIndex++; + } + + return frameIndex < maxFrameCount; +} + +int exceptionFilter(FILE* logFile, _EXCEPTION_POINTERS* exception) +{ + FILE* file = logFile ? logFile : stdout; + fprintf( + file, + "error: Exception 0x%x occurred. Stack trace:\n", + exception->ExceptionRecord->ExceptionCode); + + HANDLE process = GetCurrentProcess(); + HANDLE thread = GetCurrentThread(); + + bool symbolsLoaded = false; + { + // The default search paths should suffice + PCSTR symbolFileSearchPath = NULL; + BOOL loadSymbolsOfLoadedModules = TRUE; + BOOL result = SymInitialize(process, symbolFileSearchPath, loadSymbolsOfLoadedModules); + if (result == FALSE) + { + fprintf(file, "warning: Failed to load symbols\n"); + } + else + { + symbolsLoaded = true; + } + } + + if (!printStack(file, process, thread, *exception->ContextRecord)) + { + fprintf(file, "warning: Failed to print complete stack trace!\n"); + } + + if (symbolsLoaded) + { + BOOL result = SymCleanup(process); + if (result == FALSE) + { + SLANG_EXAMPLE_LOG_ERROR(GetLastError()); + } + } + + return EXCEPTION_EXECUTE_HANDLER; +} diff --git a/examples/stacktrace-windows/common.h b/examples/stacktrace-windows/common.h new file mode 100644 index 0000000000..0f375c4314 --- /dev/null +++ b/examples/stacktrace-windows/common.h @@ -0,0 +1,4 @@ +#pragma once +#include + +int exceptionFilter(FILE* logFile, struct _EXCEPTION_POINTERS* exception); diff --git a/examples/triangle/main.cpp b/examples/triangle/main.cpp index f757b59c70..6fd36f72d7 100644 --- a/examples/triangle/main.cpp +++ b/examples/triangle/main.cpp @@ -405,4 +405,4 @@ struct HelloWorld : public WindowedAppBase // This macro instantiates an appropriate main function to // run the application defined above. -PLATFORM_UI_MAIN(innerMain) +EXAMPLE_MAIN(innerMain); diff --git a/tests/expected-example-failure-github.txt b/tests/expected-example-failure-github.txt index ee3ad2fa12..5ad2dc362a 100644 --- a/tests/expected-example-failure-github.txt +++ b/tests/expected-example-failure-github.txt @@ -11,6 +11,5 @@ macos:aarch64:(debug|release):hello-world # See issue 5520 macos:aarch64:(debug|release):model-viewer # See issue 5520 macos:aarch64:(debug|release):ray-tracing # See issue 5520 macos:aarch64:(debug|release):ray-tracing-pipeline # See issue 5520 -windows:x86_64:debug:hello-world # See issue 5520 windows:x86_64:debug:ray-tracing # See issue 5520 windows:x86_64:debug:ray-tracing-pipeline # See issue 5520 diff --git a/tools/platform/window.h b/tools/platform/window.h index 4ff9e245f6..90d3f637a0 100644 --- a/tools/platform/window.h +++ b/tools/platform/window.h @@ -240,30 +240,14 @@ class Application #ifndef GFX_DUMP_LEAK #define GFX_DUMP_LEAK #endif -#define PLATFORM_UI_MAIN(APPLICATION_ENTRY) \ - int __stdcall wWinMain( \ - void* /*instance*/, \ - void* /* prevInstance */, \ - void* /* commandLine */, \ - int /*showCommand*/ \ - ) \ - { \ - platform::Application::init(); \ - auto result = APPLICATION_ENTRY(0, nullptr); \ - platform::Application::dispose(); \ - GFX_DUMP_LEAK \ - return result; \ - } -#else +#endif #define PLATFORM_UI_MAIN(APPLICATION_ENTRY) \ - int main(int argc, char** argv) \ + int exampleMain(int argc, char** argv) \ { \ platform::Application::init(); \ auto rs = APPLICATION_ENTRY(argc, argv); \ platform::Application::dispose(); \ return rs; \ } - -#endif