From 71e4d4d4e46ecaf33de34f14e904827ec95ae60b Mon Sep 17 00:00:00 2001 From: shookapic Date: Thu, 9 Apr 2026 23:15:41 +0900 Subject: [PATCH 1/6] feat: add native Windows build and runtime port --- CMakeLists.txt | 37 +- README.md | 18 +- cmake/logger/coretraceLog.cmake | 18 +- cmake/stackUsageAnalyzer.cmake | 261 ++++- config/tool-config.json | 2 +- docs/configuration.md | 2 +- docs/windows-port.md | 88 ++ include/App/Platform.hpp | 48 + include/App/ToolResolver.hpp | 98 ++ include/Config/config.hpp | 102 +- include/Process/Ipc/IpcStrategy.hpp | 176 +++- include/Process/Process.hpp | 1 + include/Process/ProcessFactory.hpp | 12 +- include/Process/Tools/AnalysisTools.hpp | 27 +- include/Process/Tools/ToolsInvoker.hpp | 8 +- include/Process/WinProcess.hpp | 194 +++- include/ctrace_tools/mangle.hpp | 36 +- scripts/build-windows.ps1 | 257 +++++ src/App/ToolConfig.cpp | 5 +- src/ArgumentParser/ArgumentParserFactory.cpp | 6 + .../Tools/TscancodeToolImplementation.cpp | 6 +- src/ThirdParty/CoretraceLoggerWindows.cpp | 567 +++++++++++ .../windows/ct_runtime_alloc.cpp | 901 ++++++++++++++++++ .../windows/ct_runtime_backtrace.cpp | 97 ++ .../windows/ct_runtime_env.cpp | 142 +++ .../windows/ct_runtime_helpers.h | 61 ++ .../windows/ct_runtime_internal.h | 254 +++++ .../windows/ct_runtime_vtable.cpp | 672 +++++++++++++ .../windows/mangle.cpp | 91 ++ .../windows/mangle.hpp | 44 + 30 files changed, 4050 insertions(+), 181 deletions(-) create mode 100644 docs/windows-port.md create mode 100644 include/App/Platform.hpp create mode 100644 include/App/ToolResolver.hpp create mode 100644 scripts/build-windows.ps1 create mode 100644 src/ThirdParty/CoretraceLoggerWindows.cpp create mode 100644 src/ThirdParty/coretrace-compiler/windows/ct_runtime_alloc.cpp create mode 100644 src/ThirdParty/coretrace-compiler/windows/ct_runtime_backtrace.cpp create mode 100644 src/ThirdParty/coretrace-compiler/windows/ct_runtime_env.cpp create mode 100644 src/ThirdParty/coretrace-compiler/windows/ct_runtime_helpers.h create mode 100644 src/ThirdParty/coretrace-compiler/windows/ct_runtime_internal.h create mode 100644 src/ThirdParty/coretrace-compiler/windows/ct_runtime_vtable.cpp create mode 100644 src/ThirdParty/coretrace-stack-analyzer/windows/mangle.cpp create mode 100644 src/ThirdParty/coretrace-stack-analyzer/windows/mangle.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 35c1def..6e9ec7e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,23 @@ # SPDX-License-Identifier: Apache-2.0 cmake_minimum_required(VERSION 3.28) +cmake_policy(SET CMP0091 NEW) project(ArgumentManagerProject VERSION 1.0 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) +if(MSVC) + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +endif() + +function(coretrace_force_msvc_runtime target_name) + if(MSVC AND TARGET "${target_name}") + set_property(TARGET "${target_name}" PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + endif() +endfunction() + include(${CMAKE_SOURCE_DIR}/cmake/CoreTraceVersion.cmake) coretrace_resolve_version(CTRACE_VERSION_STRING CTRACE_VERSION_SOURCE) @@ -42,17 +54,21 @@ set(COMMON_SOURCES main.cpp ) -set(SPECIFIC_SOURCES - src/ArgumentParser/GetOpt/GetoptArgumentParser.cpp - src/ArgumentParser/CLI11/CLI11ArgumentParser.cpp -) +if (PARSER_TYPE STREQUAL "Getopt") + if (WIN32) + message(FATAL_ERROR "PARSER_TYPE=Getopt is not supported on Windows. Use CLI11 instead.") + endif() +elseif (PARSER_TYPE STREQUAL "CLI11") +else() + message(FATAL_ERROR "Unsupported PARSER_TYPE: ${PARSER_TYPE}") +endif() set(SOURCES ${COMMON_SOURCES} - ${SPECIFIC_SOURCES} ) add_executable(ctrace ${SOURCES}) +coretrace_force_msvc_runtime(ctrace) if (PARSER_TYPE STREQUAL "Getopt") target_compile_definitions(ctrace PRIVATE USE_GETOPT) @@ -85,6 +101,10 @@ target_link_libraries(ctrace PRIVATE coretrace::logger) include(${CMAKE_SOURCE_DIR}/cmake/httpLib.cmake) target_link_libraries(ctrace PRIVATE httplib::httplib) +if (WIN32) + target_link_libraries(ctrace PRIVATE ws2_32) +endif() + # === OPTIONAL DEPENDENCIES === option(USE_EXTERNAL_CLI11 "Download CLI11" OFF) @@ -174,11 +194,14 @@ add_executable(ctrace_config_tests src/ArgumentParser/BaseArgumentParser.cpp src/ArgumentParser/ArgumentManager.cpp src/ArgumentParser/ArgumentParserFactory.cpp - src/ArgumentParser/CLI11/CLI11ArgumentParser.cpp - src/ArgumentParser/GetOpt/GetoptArgumentParser.cpp src/ctrace_tools/strings.cpp ) +coretrace_force_msvc_runtime(ctrace_config_tests) target_link_libraries(ctrace_config_tests PRIVATE nlohmann_json::nlohmann_json coretrace::logger) +if (WIN32) + target_link_libraries(ctrace_config_tests PRIVATE ws2_32) +endif() + add_test(NAME ctrace_config_tests COMMAND ctrace_config_tests) diff --git a/README.md b/README.md index 4021360..7d5f5f5 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,18 @@ cmake .. \ make -j4 ``` +### WINDOWS + +CoreTrace can be built as a native `ctrace.exe` on Windows. The recommended path is documented in [`docs/windows-port.md`](docs/windows-port.md) and automated with [`scripts/build-windows.ps1`](scripts/build-windows.ps1). + +Example: + +```powershell +powershell -ExecutionPolicy Bypass -File .\scripts\build-windows.ps1 ` + -LLVMDir "C:\Program Files\LLVM\lib\cmake\llvm" ` + -PackageZip +``` + ### CODE STYLE (clang-format) - Version cible : `clang-format` 17 (utilisée dans la CI). @@ -80,8 +92,8 @@ Options: --invoke Invokes specific tools (comma-separated). Available tools: flawfinder, ikos, cppcheck, tscancode, ctrace_stack_analyzer. --input Specifies the source files to analyse (comma-separated). - --ipc Specifies the IPC method to use (e.g., fifo, socket). - --ipc-path Specifies the IPC path (default: /tmp/coretrace_ipc). + --ipc Specifies the IPC method to use (e.g., socket). + --ipc-path Specifies the IPC path (platform-specific default). --serve-host HTTP server host when --ipc=serve. --serve-port HTTP server port when --ipc=serve. --shutdown-token Token required for POST /shutdown (server mode). @@ -154,7 +166,7 @@ curl -X POST http://127.0.0.1:8080/api \ "report_file": "ctrace-report.txt", "output_file": "ctrace.out", "ipc": "serve", - "ipc_path": "/tmp/coretrace_ipc", + "ipc_path": "", "async": false, "verbose": true } diff --git a/cmake/logger/coretraceLog.cmake b/cmake/logger/coretraceLog.cmake index 3fb6da6..79e3c2d 100644 --- a/cmake/logger/coretraceLog.cmake +++ b/cmake/logger/coretraceLog.cmake @@ -9,4 +9,20 @@ FetchContent_Declare(coretrace-logger GIT_TAG main EXCLUDE_FROM_ALL ) -FetchContent_MakeAvailable(coretrace-logger) + +FetchContent_GetProperties(coretrace-logger) +if(NOT coretrace-logger_POPULATED) + FetchContent_Populate(coretrace-logger) +endif() + +if(WIN32) + file(COPY_FILE + "${CMAKE_SOURCE_DIR}/src/ThirdParty/CoretraceLoggerWindows.cpp" + "${coretrace-logger_SOURCE_DIR}/src/logger.cpp" + ONLY_IF_DIFFERENT + ) +endif() + +if(NOT TARGET coretrace_logger) + FetchContent_MakeAvailable(coretrace-logger) +endif() diff --git a/cmake/stackUsageAnalyzer.cmake b/cmake/stackUsageAnalyzer.cmake index 6ee7cf5..9c0b8d8 100644 --- a/cmake/stackUsageAnalyzer.cmake +++ b/cmake/stackUsageAnalyzer.cmake @@ -1,6 +1,219 @@ # SPDX-License-Identifier: Apache-2.0 include(FetchContent) +set(CORETRACE_LOGGER_BUILD_EXAMPLES OFF CACHE BOOL "Disable logger examples" FORCE) +set(CORETRACE_LOGGER_BUILD_TESTS OFF CACHE BOOL "Disable logger tests" FORCE) + +function(_coretrace_patch_imported_link_property _target _property _stale_value _replacement_value) + if(NOT TARGET "${_target}") + return() + endif() + + get_target_property(_current_value "${_target}" "${_property}") + if(NOT _current_value OR _current_value STREQUAL "_current_value-NOTFOUND") + return() + endif() + + string(REPLACE "${_stale_value}" "${_replacement_value}" _updated_value "${_current_value}") + if(NOT _updated_value STREQUAL _current_value) + set_target_properties("${_target}" PROPERTIES + ${_property} "${_updated_value}" + ) + endif() +endfunction() + +function(_coretrace_patch_llvm_diaguids _replacement_path) + if(NOT WIN32 OR NOT TARGET LLVMDebugInfoPDB) + return() + endif() + + set(_coretrace_stale_diaguids_path + "C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/DIA SDK/lib/amd64/diaguids.lib") + + foreach(_property_name + INTERFACE_LINK_LIBRARIES + IMPORTED_LINK_INTERFACE_LIBRARIES + IMPORTED_LINK_INTERFACE_LIBRARIES_DEBUG + IMPORTED_LINK_INTERFACE_LIBRARIES_RELEASE + IMPORTED_LINK_INTERFACE_LIBRARIES_RELWITHDEBINFO + IMPORTED_LINK_INTERFACE_LIBRARIES_MINSIZEREL) + _coretrace_patch_imported_link_property( + LLVMDebugInfoPDB + ${_property_name} + "${_coretrace_stale_diaguids_path}" + "${_replacement_path}" + ) + endforeach() +endfunction() + +function(_coretrace_patch_fetched_file _path _search _replacement) + if(NOT EXISTS "${_path}") + return() + endif() + + file(READ "${_path}" _content) + string(REPLACE "${_search}" "${_replacement}" _updated "${_content}") + if(NOT _updated STREQUAL _content) + file(WRITE "${_path}" "${_updated}") + endif() +endfunction() + +function(_coretrace_force_msvc_runtime target_name) + if(MSVC AND TARGET "${target_name}") + set_property(TARGET "${target_name}" PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>" + ) + endif() +endfunction() + +FetchContent_Declare( + coretrace-logger + GIT_REPOSITORY https://github.com/CoreTrace/coretrace-log.git + GIT_TAG main + EXCLUDE_FROM_ALL +) + +FetchContent_GetProperties(coretrace-logger) +if(NOT coretrace-logger_POPULATED) + FetchContent_Populate(coretrace-logger) +endif() + +if(WIN32) + file(COPY_FILE + "${CMAKE_SOURCE_DIR}/src/ThirdParty/CoretraceLoggerWindows.cpp" + "${coretrace-logger_SOURCE_DIR}/src/logger.cpp" + ONLY_IF_DIFFERENT + ) +endif() + +if(NOT TARGET coretrace_logger) + add_subdirectory("${coretrace-logger_SOURCE_DIR}" "${coretrace-logger_BINARY_DIR}" EXCLUDE_FROM_ALL) +endif() +_coretrace_force_msvc_runtime(coretrace_logger) + +FetchContent_Declare( + cc + GIT_REPOSITORY https://github.com/CoreTrace/coretrace-compiler.git + GIT_TAG main + EXCLUDE_FROM_ALL +) + +FetchContent_GetProperties(cc) +if(NOT cc_POPULATED) + FetchContent_Populate(cc) +endif() + +_coretrace_patch_fetched_file( + "${cc_SOURCE_DIR}/CMakeLists.txt" + [=[message(STATUS "Using Clang resource dir: ${CLANG_RESOURCE_DIR}")]=] + [=[file(TO_CMAKE_PATH "${CLANG_RESOURCE_DIR}" CLANG_RESOURCE_DIR) +message(STATUS "Using Clang resource dir: ${CLANG_RESOURCE_DIR}")]=] +) +_coretrace_patch_fetched_file( + "${cc_SOURCE_DIR}/CMakeLists.txt" + "if(NOT LLVM_ENABLE_RTTI)\n target_compile_options(compilerlib_static PRIVATE -fno-rtti)\nendif()" + "if(NOT LLVM_ENABLE_RTTI)\n if(MSVC)\n target_compile_options(compilerlib_static PRIVATE /GR-)\n else()\n target_compile_options(compilerlib_static PRIVATE -fno-rtti)\n endif()\nendif()" +) +_coretrace_patch_fetched_file( + "${cc_SOURCE_DIR}/CMakeLists.txt" + "if(NOT LLVM_ENABLE_RTTI)\n target_compile_options(compilerlib_shared PRIVATE -fno-rtti)\nendif()" + "if(NOT LLVM_ENABLE_RTTI)\n if(MSVC)\n target_compile_options(compilerlib_shared PRIVATE /GR-)\n else()\n target_compile_options(compilerlib_shared PRIVATE -fno-rtti)\n endif()\nendif()" +) +_coretrace_patch_fetched_file( + "${cc_SOURCE_DIR}/CMakeLists.txt" + "if(NOT LLVM_ENABLE_RTTI)\n target_compile_options(cc PRIVATE -fno-rtti)\nendif()" + "if(NOT LLVM_ENABLE_RTTI)\n if(MSVC)\n target_compile_options(cc PRIVATE /GR-)\n else()\n target_compile_options(cc PRIVATE -fno-rtti)\n endif()\nendif()" +) + +if(WIN32) + set(_coretrace_diaguids_candidates) + file(GLOB_RECURSE _coretrace_diaguids_candidates + LIST_DIRECTORIES false + "C:/Program Files/Microsoft Visual Studio/*/*/DIA SDK/lib/amd64/diaguids.lib" + "C:/Program Files (x86)/Microsoft Visual Studio/*/*/DIA SDK/lib/amd64/diaguids.lib" + ) + + if(_coretrace_diaguids_candidates) + list(GET _coretrace_diaguids_candidates 0 _coretrace_diaguids_path) + endif() + + # Import and patch LLVM/Clang targets before dependent subprojects link + # against them. Some Windows LLVM packages ship a stale DIA SDK path in the + # LLVMDebugInfoPDB imported target. + find_package(LLVM REQUIRED CONFIG) + find_package(Clang REQUIRED CONFIG) + + if(_coretrace_diaguids_path) + _coretrace_patch_llvm_diaguids("${_coretrace_diaguids_path}") + endif() + + set(_coretrace_cc_windows_runtime_dir + "${CMAKE_SOURCE_DIR}/src/ThirdParty/coretrace-compiler/windows") + + foreach(_runtime_file + ct_runtime_internal.h + ct_runtime_helpers.h + ct_runtime_alloc.cpp + ct_runtime_backtrace.cpp + ct_runtime_env.cpp + ct_runtime_vtable.cpp) + file(COPY_FILE + "${_coretrace_cc_windows_runtime_dir}/${_runtime_file}" + "${cc_SOURCE_DIR}/src/runtime/${_runtime_file}" + ONLY_IF_DIFFERENT + ) + endforeach() +endif() + +if(NOT TARGET compilerlib_static) + add_subdirectory("${cc_SOURCE_DIR}" "${cc_BINARY_DIR}" EXCLUDE_FROM_ALL) +endif() + +set(_coretrace_cc_extra_llvm_libs + LLVMAArch64CodeGen + LLVMAArch64AsmParser + LLVMAArch64Desc + LLVMAArch64Info + LLVMAArch64Utils + LLVMARMCodeGen + LLVMARMAsmParser + LLVMARMDesc + LLVMARMInfo + LLVMARMUtils + LLVMBPFCodeGen + LLVMBPFAsmParser + LLVMBPFDesc + LLVMBPFInfo + LLVMWebAssemblyCodeGen + LLVMWebAssemblyAsmParser + LLVMWebAssemblyDesc + LLVMWebAssemblyInfo + LLVMWebAssemblyUtils + LLVMRISCVCodeGen + LLVMRISCVAsmParser + LLVMRISCVDesc + LLVMRISCVInfo + LLVMRISCVTargetMCA + LLVMNVPTXCodeGen + LLVMNVPTXDesc + LLVMNVPTXInfo +) +if(TARGET compilerlib_static) + target_link_libraries(compilerlib_static PUBLIC ${_coretrace_cc_extra_llvm_libs}) +endif() +if(TARGET compilerlib_shared) + target_link_libraries(compilerlib_shared PRIVATE ${_coretrace_cc_extra_llvm_libs}) +endif() + +_coretrace_force_msvc_runtime(ct_instrument_runtime) +_coretrace_force_msvc_runtime(compilerlib_static) +_coretrace_force_msvc_runtime(compilerlib_shared) +_coretrace_force_msvc_runtime(cc) + +if(WIN32 AND _coretrace_diaguids_path) + _coretrace_patch_llvm_diaguids("${_coretrace_diaguids_path}") +endif() + FetchContent_Declare( stack_analyzer GIT_REPOSITORY https://github.com/CoreTrace/coretrace-stack-analyzer.git @@ -8,7 +221,53 @@ FetchContent_Declare( EXCLUDE_FROM_ALL ) -FetchContent_MakeAvailable(stack_analyzer) +FetchContent_GetProperties(stack_analyzer) +if(NOT stack_analyzer_POPULATED) + FetchContent_Populate(stack_analyzer) +endif() + +_coretrace_patch_fetched_file( + "${stack_analyzer_SOURCE_DIR}/CMakeLists.txt" + "set(LLVM_LINK_LLVM_DYLIB ON)" + "if(WIN32)\n set(LLVM_LINK_LLVM_DYLIB OFF)\nelse()\n set(LLVM_LINK_LLVM_DYLIB ON)\nendif()" +) +_coretrace_patch_fetched_file( + "${stack_analyzer_SOURCE_DIR}/CMakeLists.txt" + "if(ENABLE_STACK_USAGE)\n target_compile_options(stack_usage_analyzer_lib PRIVATE -fstack-usage)\nendif()" + "if(ENABLE_STACK_USAGE AND NOT MSVC)\n target_compile_options(stack_usage_analyzer_lib PRIVATE -fstack-usage)\nendif()" +) +_coretrace_patch_fetched_file( + "${stack_analyzer_SOURCE_DIR}/CMakeLists.txt" + " if(ENABLE_STACK_USAGE)\n target_compile_options(stack_usage_analyzer PRIVATE -fstack-usage)\n endif()" + " if(ENABLE_STACK_USAGE AND NOT MSVC)\n target_compile_options(stack_usage_analyzer PRIVATE -fstack-usage)\n endif()" +) + +if(WIN32) + set(_coretrace_stack_windows_dir + "${CMAKE_SOURCE_DIR}/src/ThirdParty/coretrace-stack-analyzer/windows") + + foreach(_stack_file + mangle.hpp + mangle.cpp) + if(_stack_file STREQUAL "mangle.hpp") + set(_stack_destination "${stack_analyzer_SOURCE_DIR}/include/${_stack_file}") + else() + set(_stack_destination "${stack_analyzer_SOURCE_DIR}/src/${_stack_file}") + endif() + + file(COPY_FILE + "${_coretrace_stack_windows_dir}/${_stack_file}" + "${_stack_destination}" + ONLY_IF_DIFFERENT + ) + endforeach() +endif() + +if(NOT TARGET stack_usage_analyzer_lib) + add_subdirectory("${stack_analyzer_SOURCE_DIR}" "${stack_analyzer_BINARY_DIR}" EXCLUDE_FROM_ALL) +endif() +_coretrace_force_msvc_runtime(stack_usage_analyzer_lib) +_coretrace_force_msvc_runtime(stack_usage_analyzer) # Copy upstream default models into config/models/ so that tool-config.json # can reference them with paths relative to the config directory, without diff --git a/config/tool-config.json b/config/tool-config.json index 2a29a36..566426d 100644 --- a/config/tool-config.json +++ b/config/tool-config.json @@ -25,7 +25,7 @@ "runtime": { "async": false, "ipc": "standardIO", - "ipc_path": "/tmp/coretrace_ipc" + "ipc_path": "" }, "server": { "host": "127.0.0.1", diff --git a/docs/configuration.md b/docs/configuration.md index 7ef9c6f..aee1e5b 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -149,7 +149,7 @@ CLI: `--ipc` - `runtime.ipc_path` Type: `string` -Default: `"/tmp/coretrace_ipc"` +Default: platform-specific temp socket path when omitted or empty Allowed: socket path. Description: IPC socket path. Impact: used when IPC mode is socket. diff --git a/docs/windows-port.md b/docs/windows-port.md new file mode 100644 index 0000000..f796812 --- /dev/null +++ b/docs/windows-port.md @@ -0,0 +1,88 @@ +# Windows Port Plan + +This project can be ported to Windows without rewriting the application, but the port needs to be treated as two separate deliverables: + +1. A native host application: `ctrace.exe` +2. A tool runtime strategy for every analyzer that `ctrace` orchestrates + +The host application is a good fit for native Windows. The analyzer toolchain is mixed: some tools have native Windows stories, some do not. For feature parity, keep the host native and make tool execution configurable per tool. + +## Target Architecture + +Use a layered design: + +1. Core host + Native C++/CMake application with the same CLI, config loading, HTTP server mode, and report generation on every OS. +2. Platform adapters + Isolate process launching, local socket IPC, temp-path defaults, and packaging behind Windows-safe abstractions. +3. Tool adapters + Resolve each analyzer through a dedicated launcher instead of hard-coded Unix paths. +4. Packaging + Build an install tree containing `bin/ctrace.exe`, required runtime DLLs, and `config/`. + +The current tree now includes: + +- A real Windows process launcher in `include/Process/WinProcess.hpp` +- Cross-platform local socket IPC in `include/Process/Ipc/IpcStrategy.hpp` +- Platform-aware IPC defaults in `include/App/Platform.hpp` +- Tool command resolution with environment overrides in `include/App/ToolResolver.hpp` +- A Windows build helper in `scripts/build-windows.ps1` + +## Build Requirements + +Install: + +- Visual Studio 2022 with C++ build tools +- CMake 3.28+ +- LLVM/Clang development packages with `LLVMConfig.cmake` and `ClangConfig.cmake` + +Recommended configure path: + +```powershell +powershell -ExecutionPolicy Bypass -File .\scripts\build-windows.ps1 ` + -LLVMDir "C:\Program Files\LLVM\lib\cmake\llvm" ` + -PackageZip +``` + +Expected output: + +- `dist\windows\bin\ctrace.exe` +- `dist\windows\config\tool-config.json` +- `coretrace-windows-Release.zip` when `-PackageZip` is used + +## Tool Runtime Strategy + +Do not keep analyzer paths hard-coded in source for Windows deployments. Use environment overrides instead: + +- `CORETRACE_CPPCHECK_BIN` +- `CORETRACE_TSCANCODE_BIN` +- `CORETRACE_IKOS_BIN` +- `CORETRACE_FLAWFINDER_LAUNCHER` +- `CORETRACE_FLAWFINDER_SCRIPT` +- `CORETRACE_PYTHON_BIN` + +Recommended approach: + +- Run `cppcheck` natively on Windows +- Run `ctrace_stack_analyzer` natively on Windows with LLVM/Clang +- Run `tscancode` natively if you have a Windows-capable binary available +- Run `ikos` and `flawfinder` through a compatibility layer or wrapper if native Windows binaries are not part of your supported toolchain + +For production, prefer wrapper executables or scripts per analyzer instead of embedding platform rules directly in `ctrace`. That keeps the host binary stable and lets you swap tool installations without recompiling. + +## Packaging Guidance + +For an industry-ready Windows release: + +1. Build `ctrace.exe` with MSVC in `Release` +2. Install with `cmake --install` into a staging directory +3. Bundle required runtime DLLs next to the executable +4. Keep mutable assets in `config/` +5. Publish a portable ZIP first +6. Add an installer only after the portable artifact is stable + +If you need a Windows installer later, add a separate packaging layer such as CPack/NSIS or WiX, but keep the portable install tree as the canonical artifact. + +## Important Constraint + +A native `ctrace.exe` is straightforward. Full native parity for every external analyzer depends on whether each analyzer itself is supported on Windows. If one analyzer is Linux-only, the correct architecture is a native Windows host plus a compatibility-backed adapter for that analyzer, not a partial reimplementation inside `ctrace`. diff --git a/include/App/Platform.hpp b/include/App/Platform.hpp new file mode 100644 index 0000000..7985236 --- /dev/null +++ b/include/App/Platform.hpp @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: Apache-2.0 +#ifndef APP_PLATFORM_HPP +#define APP_PLATFORM_HPP + +#include +#include +#include +#include + +namespace ctrace::platform +{ + [[nodiscard]] inline bool isWindows() noexcept + { +#ifdef _WIN32 + return true; +#else + return false; +#endif + } + + [[nodiscard]] inline std::string defaultIpcPath() + { +#ifdef _WIN32 + const char* tempDir = std::getenv("TEMP"); + std::filesystem::path baseDir; + if (tempDir != nullptr && *tempDir != '\0') + { + baseDir = tempDir; + } + else + { + try + { + baseDir = std::filesystem::temp_directory_path(); + } + catch (const std::exception&) + { + baseDir = "."; + } + } + return (baseDir / "coretrace_ipc.sock").string(); +#else + return "/tmp/coretrace_ipc.sock"; +#endif + } +} // namespace ctrace::platform + +#endif // APP_PLATFORM_HPP diff --git a/include/App/ToolResolver.hpp b/include/App/ToolResolver.hpp new file mode 100644 index 0000000..2043acb --- /dev/null +++ b/include/App/ToolResolver.hpp @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: Apache-2.0 +#ifndef APP_TOOL_RESOLVER_HPP +#define APP_TOOL_RESOLVER_HPP + +#include +#include +#include +#include +#include + +namespace ctrace +{ + struct ResolvedToolCommand + { + std::string executable; + std::vector prefixArguments; + }; + + namespace detail + { + [[nodiscard]] inline bool hasEnvValue(const char* name) + { + const char* value = std::getenv(name); + return value != nullptr && *value != '\0'; + } + + [[nodiscard]] inline std::string envOrDefault(const char* name, std::string_view fallback) + { + const char* value = std::getenv(name); + if (value == nullptr || *value == '\0') + { + return std::string(fallback); + } + return value; + } + + [[nodiscard]] inline std::string repoLocalExecutable(std::string_view relativeStem) + { + std::filesystem::path path(relativeStem); +#ifdef _WIN32 + path += ".exe"; +#endif + return path.make_preferred().string(); + } + } // namespace detail + + [[nodiscard]] inline ResolvedToolCommand resolveCppcheckCommand() + { + return {detail::envOrDefault("CORETRACE_CPPCHECK_BIN", "cppcheck"), {}}; + } + + [[nodiscard]] inline ResolvedToolCommand resolveTscancodeCommand() + { +#ifdef _WIN32 + constexpr std::string_view fallback = "tscancode"; +#else + const std::string fallback = detail::repoLocalExecutable("./tscancode/src/tscancode/trunk/tscancode"); +#endif + return {detail::envOrDefault("CORETRACE_TSCANCODE_BIN", + fallback), + {}}; + } + + [[nodiscard]] inline ResolvedToolCommand resolveIkosCommand() + { +#ifdef _WIN32 + constexpr std::string_view fallback = "ikos"; +#else + const std::string fallback = detail::repoLocalExecutable("./ikos/src/ikos-build/bin/ikos"); +#endif + return {detail::envOrDefault("CORETRACE_IKOS_BIN", + fallback), + {}}; + } + + [[nodiscard]] inline ResolvedToolCommand resolveFlawfinderCommand() + { + const bool hasCustomLauncher = detail::hasEnvValue("CORETRACE_FLAWFINDER_LAUNCHER"); +#ifdef _WIN32 + const std::string pythonExe = detail::envOrDefault("CORETRACE_PYTHON_BIN", "python"); +#else + const std::string pythonExe = detail::envOrDefault("CORETRACE_PYTHON_BIN", "python3"); +#endif + + ResolvedToolCommand command{detail::envOrDefault("CORETRACE_FLAWFINDER_LAUNCHER", pythonExe), + {}}; + + const std::string scriptPath = detail::envOrDefault( + "CORETRACE_FLAWFINDER_SCRIPT", "./flawfinder/src/flawfinder-build/flawfinder.py"); + if (!hasCustomLauncher) + { + command.prefixArguments.push_back(scriptPath); + } + return command; + } +} // namespace ctrace + +#endif // APP_TOOL_RESOLVER_HPP diff --git a/include/Config/config.hpp b/include/Config/config.hpp index b95e7d0..ce8fd3b 100644 --- a/include/Config/config.hpp +++ b/include/Config/config.hpp @@ -13,6 +13,7 @@ #include #include "ArgumentParser/ArgumentManager.hpp" #include "ArgumentParser/ArgumentParserFactory.hpp" +#include "App/Platform.hpp" #include "App/SupportedTools.hpp" #include "App/Version.hpp" #include "ctrace_tools/strings.hpp" @@ -22,57 +23,54 @@ static void printHelp(void) { - std::cout << R"(ctrace - Static & Dynamic C/C++ Code Analysis Tool - -Usage: - ctrace [options] - -Options: - --help Displays this help message. - --version Displays build version information. - --verbose Enables detailed (verbose) output. - --sarif-format Generates a report in SARIF format. - --report-file Specifies the path to the report file (default: ctrace-report.txt). - --output-file Specifies the output file for the analysed binary (default: ctrace.out). - --entry-points Sets the entry points for analysis (default: main). Accepts a comma-separated list. - --config Loads settings from a JSON config file. - --compile-commands Path to compile_commands.json for tools that support it. - --include-compdb-deps Includes dependency entries (e.g. _deps) when auto-loading files from compile_commands.json. - --analysis-profile

Stack analyzer profile: fast|full. - --smt Enables/disables SMT refinement in stack analyzer. - --smt-backend Primary SMT backend (e.g. z3, interval). - --smt-secondary-backend Secondary backend for multi-solver modes. - --smt-mode SMT mode: single|portfolio|cross-check|dual-consensus. - --smt-timeout-ms SMT timeout in milliseconds. - --smt-budget-nodes SMT node budget per query. - --smt-rules Comma-separated SMT-enabled rules. - --resource-model Path to the resource lifetime model for stack analyzer. - --escape-model Path to the stack escape model for stack analyzer. - --buffer-model Path to the buffer overflow model for stack analyzer. - --demangle Displays demangled function names in supported tools. - --static Enables static analysis. - --dyn Enables dynamic analysis. - --invoke Invokes specific tools (comma-separated). - Available tools: flawfinder, ikos, cppcheck, tscancode, ctrace_stack_analyzer. - --input Specifies the source files to analyse (comma-separated). - --timing Enables stack analyzer timing output. - --ipc Specifies the IPC method to use (e.g., fifo, socket). - --ipc-path Specifies the IPC path (default: /tmp/coretrace_ipc). - --serve-host HTTP server host when --ipc=serve. - --serve-port HTTP server port when --ipc=serve. - --async Enables asynchronous execution. - --shutdown-token Token required for POST /shutdown (server mode). - --shutdown-timeout-ms Graceful shutdown timeout in ms (0 = wait indefinitely). - -Examples: - ctrace --input main.cpp,util.cpp --static --invoke=cppcheck,flawfinder - ctrace --verbose --report-file=analysis.txt --sarif-format - -Description: - ctrace is a modular C/C++ code analysis tool that combines both static and dynamic - analysis. It can be finely configured to detect vulnerabilities, security issues, - and memory misuse. -)" << std::endl; + std::cout << "ctrace - Static & Dynamic C/C++ Code Analysis Tool\n\n" + << "Usage:\n" + << " ctrace [options]\n\n" + << "Options:\n" + << " --help Displays this help message.\n" + << " --version Displays build version information.\n" + << " --verbose Enables detailed (verbose) output.\n" + << " --sarif-format Generates a report in SARIF format.\n" + << " --report-file Specifies the path to the report file (default: ctrace-report.txt).\n" + << " --output-file Specifies the output file for the analysed binary (default: ctrace.out).\n" + << " --entry-points Sets the entry points for analysis (default: main). Accepts a comma-separated list.\n" + << " --config Loads settings from a JSON config file.\n" + << " --compile-commands Path to compile_commands.json for tools that support it.\n" + << " --include-compdb-deps Includes dependency entries (e.g. _deps) when auto-loading files from compile_commands.json.\n" + << " --analysis-profile

Stack analyzer profile: fast|full.\n" + << " --smt Enables/disables SMT refinement in stack analyzer.\n" + << " --smt-backend Primary SMT backend (e.g. z3, interval).\n" + << " --smt-secondary-backend Secondary backend for multi-solver modes.\n" + << " --smt-mode SMT mode: single|portfolio|cross-check|dual-consensus.\n" + << " --smt-timeout-ms SMT timeout in milliseconds.\n" + << " --smt-budget-nodes SMT node budget per query.\n" + << " --smt-rules Comma-separated SMT-enabled rules.\n" + << " --resource-model Path to the resource lifetime model for stack analyzer.\n" + << " --escape-model Path to the stack escape model for stack analyzer.\n" + << " --buffer-model Path to the buffer overflow model for stack analyzer.\n" + << " --demangle Displays demangled function names in supported tools.\n" + << " --static Enables static analysis.\n" + << " --dyn Enables dynamic analysis.\n" + << " --invoke Invokes specific tools (comma-separated).\n" + << " Available tools: flawfinder, ikos, cppcheck, tscancode, ctrace_stack_analyzer.\n" + << " --input Specifies the source files to analyse (comma-separated).\n" + << " --timing Enables stack analyzer timing output.\n" + << " --ipc Specifies the IPC method to use (e.g., socket).\n" + << " --ipc-path Specifies the IPC path (default: " + << ctrace::platform::defaultIpcPath() << ").\n" + << " --serve-host HTTP server host when --ipc=serve.\n" + << " --serve-port HTTP server port when --ipc=serve.\n" + << " --async Enables asynchronous execution.\n" + << " --shutdown-token Token required for POST /shutdown (server mode).\n" + << " --shutdown-timeout-ms Graceful shutdown timeout in ms (0 = wait indefinitely).\n\n" + << "Examples:\n" + << " ctrace --input main.cpp,util.cpp --static --invoke=cppcheck,flawfinder\n" + << " ctrace --verbose --report-file=analysis.txt --sarif-format\n\n" + << "Description:\n" + << " ctrace is a modular C/C++ code analysis tool that combines both static and dynamic\n" + << " analysis. It can be finely configured to detect vulnerabilities, security issues,\n" + << " and memory misuse.\n" + << std::endl; } static void printVersion(void) @@ -121,7 +119,7 @@ namespace ctrace bool hasInvokedSpecificTools = false; ///< Indicates if specific tools are invoked. std::string ipc = ctrace_defs::IPC_TYPES.front(); ///< IPC method to use (e.g., fifo, socket). - std::string ipcPath = "/tmp/coretrace_ipc"; ///< Path for IPC communication. + std::string ipcPath = ctrace::platform::defaultIpcPath(); ///< Path for IPC communication. std::string serverHost = "127.0.0.1"; ///< Host for server IPC (if applicable). int serverPort = 8080; ///< Port for server IPC (if applicable). std::string shutdownToken; ///< Token required for POST /shutdown. diff --git a/include/Process/Ipc/IpcStrategy.hpp b/include/Process/Ipc/IpcStrategy.hpp index f3d9d24..45c24c8 100644 --- a/include/Process/Ipc/IpcStrategy.hpp +++ b/include/Process/Ipc/IpcStrategy.hpp @@ -2,24 +2,29 @@ #ifndef IPC_STRATEGY_HPP #define IPC_STRATEGY_HPP -#include +#include +#include +#include +#include +#include -#include "../ProcessFactory.hpp" -#include "../ThreadProcess.hpp" - -#include -#include -#include +#ifdef _WIN32 +#define NOMINMAX +#include +#include +#include +#else #include #include -#include +#include +#endif class IpcStrategy { public: virtual ~IpcStrategy() = default; virtual void write(const std::string& data) = 0; - virtual void close() = 0; // Pour cleanup RAII + virtual void close() = 0; }; namespace ctrace::ipc @@ -32,56 +37,161 @@ namespace ctrace::ipc { } }; + +#ifdef _WIN32 + class WinsockSession + { + public: + WinsockSession() + { + WSADATA wsaData{}; + const int result = WSAStartup(MAKEWORD(2, 2), &wsaData); + if (result != 0) + { + throw SocketError("WSAStartup failed: " + std::to_string(result)); + } + } + + ~WinsockSession() + { + WSACleanup(); + } + }; + + [[nodiscard]] inline const WinsockSession& winsockSession() + { + static const WinsockSession session; + return session; + } + + [[nodiscard]] inline std::string lastSocketError() + { + return "Winsock error " + std::to_string(WSAGetLastError()); + } +#else + [[nodiscard]] inline std::string lastSocketError() + { + return std::strerror(errno); + } +#endif } // namespace ctrace::ipc -class UnixSocketStrategy : public IpcStrategy +class LocalSocketStrategy : public IpcStrategy { - private: - int sock; - std::string path; - public: - UnixSocketStrategy(const std::string& socketPath) : path(socketPath), sock(-1) + explicit LocalSocketStrategy(const std::string& socketPath) : path_(socketPath) { - sock = socket(AF_UNIX, SOCK_STREAM, 0); - if (sock == -1) +#ifdef _WIN32 + (void)ctrace::ipc::winsockSession(); + socket_ = ::socket(AF_UNIX, SOCK_STREAM, 0); + if (socket_ == INVALID_SOCKET) + { + throw ctrace::ipc::SocketError("Error creating AF_UNIX socket: " + + ctrace::ipc::lastSocketError()); + } +#else + socket_ = ::socket(AF_UNIX, SOCK_STREAM, 0); + if (socket_ == -1) { - throw std::runtime_error("Error socket creation: " + std::string(strerror(errno))); + throw ctrace::ipc::SocketError("Error creating AF_UNIX socket: " + + ctrace::ipc::lastSocketError()); } - sockaddr_un addr; - memset(&addr, 0, sizeof(addr)); +#endif + + sockaddr_un addr{}; addr.sun_family = AF_UNIX; - strncpy(addr.sun_path, path.c_str(), sizeof(addr.sun_path) - 1); - if (connect(sock, (sockaddr*)&addr, sizeof(addr)) == -1) + if (path_.size() >= sizeof(addr.sun_path)) { - throw std::runtime_error("Error socket connexion: " + std::string(strerror(errno))); + close(); + throw ctrace::ipc::SocketError("Socket path is too long: " + path_); + } + std::memcpy(addr.sun_path, path_.c_str(), path_.size()); + addr.sun_path[path_.size()] = '\0'; + + const int connectResult = + ::connect(socket_, reinterpret_cast(&addr), sizeof(addr)); +#ifdef _WIN32 + if (connectResult == SOCKET_ERROR) +#else + if (connectResult == -1) +#endif + { + const std::string error = ctrace::ipc::lastSocketError(); + close(); + throw ctrace::ipc::SocketError("Error connecting to socket '" + path_ + + "': " + error); } } - ~UnixSocketStrategy() + + ~LocalSocketStrategy() override { close(); } + void write(const std::string& data) override { - if (sock == -1) + if (data.empty()) { - throw ctrace::ipc::SocketError(std::string("Socket is not connected: ") + - strerror(errno)); + return; } - if (send(sock, data.c_str(), data.size(), 0) == -1) + + if (!isOpen()) { - throw ctrace::ipc::SocketError(std::string("Connection failed: ") + strerror(errno)); + throw ctrace::ipc::SocketError("Socket is not connected"); + } + + std::size_t offset = 0; + while (offset < data.size()) + { +#ifdef _WIN32 + const int sent = + ::send(socket_, data.c_str() + offset, static_cast(data.size() - offset), 0); + if (sent == SOCKET_ERROR) +#else + const auto sent = ::send(socket_, data.c_str() + offset, data.size() - offset, 0); + if (sent == -1) +#endif + { + throw ctrace::ipc::SocketError("Failed to send IPC payload: " + + ctrace::ipc::lastSocketError()); + } + offset += static_cast(sent); } } + void close() override { - if (sock != -1) +#ifdef _WIN32 + if (socket_ != INVALID_SOCKET) + { + ::closesocket(socket_); + socket_ = INVALID_SOCKET; + } +#else + if (socket_ != -1) { - ::close(sock); - sock = -1; - // unlink(path.c_str()); // Optionnel + ::close(socket_); + socket_ = -1; } +#endif } + + private: + [[nodiscard]] bool isOpen() const + { +#ifdef _WIN32 + return socket_ != INVALID_SOCKET; +#else + return socket_ != -1; +#endif + } + + std::string path_; +#ifdef _WIN32 + SOCKET socket_ = INVALID_SOCKET; +#else + int socket_ = -1; +#endif }; #endif // IPC_STRATEGY_HPP diff --git a/include/Process/Process.hpp b/include/Process/Process.hpp index d0e4359..97113f2 100644 --- a/include/Process/Process.hpp +++ b/include/Process/Process.hpp @@ -5,6 +5,7 @@ #include #include #include +#include class Process { diff --git a/include/Process/ProcessFactory.hpp b/include/Process/ProcessFactory.hpp index 19f7489..5ea33d4 100644 --- a/include/Process/ProcessFactory.hpp +++ b/include/Process/ProcessFactory.hpp @@ -4,10 +4,16 @@ #include #include +#include #include "Process.hpp" +#include "ThreadProcess.hpp" + +#if defined(_WIN32) +#include "WinProcess.hpp" +#elif defined(__linux__) || defined(__APPLE__) #include "LinuxProcess.hpp" #include "UnixProcess.hpp" -#include "WinProcess.hpp" +#endif /** * @brief Factory class for creating platform-specific `Process` objects. @@ -46,6 +52,10 @@ class ProcessFactory ctrace::Thread::Output::cout("Creating macOS process for command: " + command); // return std::make_unique(command, args); // doesn't work with tscancode return std::make_unique(command, args); +#else + static_cast(command); + static_cast(args); + throw std::runtime_error("Unsupported platform for process execution"); #endif } }; diff --git a/include/Process/Tools/AnalysisTools.hpp b/include/Process/Tools/AnalysisTools.hpp index 32149f9..de8eff0 100644 --- a/include/Process/Tools/AnalysisTools.hpp +++ b/include/Process/Tools/AnalysisTools.hpp @@ -6,6 +6,7 @@ // #include "IAnalysisTools.hpp" #include "AnalysisToolsBase.hpp" +#include "App/ToolResolver.hpp" #include "ctrace_tools/languageType.hpp" #include "ctrace_tools/mangle.hpp" #include "../ProcessFactory.hpp" @@ -132,8 +133,11 @@ namespace ctrace argsProcess.push_back("--report-file=" + report_file); argsProcess.push_back(src_file); - auto process = ProcessFactory::createProcess( - "./ikos/src/ikos-build/bin/ikos", argsProcess); // ou "cmd.exe" pour Windows + const auto command = ctrace::resolveIkosCommand(); + argsProcess.insert(argsProcess.begin(), command.prefixArguments.begin(), + command.prefixArguments.end()); + auto process = + ProcessFactory::createProcess(command.executable, argsProcess); // std::this_thread::sleep_for(std::chrono::seconds(5)); process->execute(); ctrace::Thread::Output::tool_out(process->logOutput); @@ -182,7 +186,6 @@ namespace ctrace { std::vector argsProcess; // = {"flawfinder.py", "-F", "-c", "-C", "-D", "main.c"}; - argsProcess.push_back("./flawfinder/src/flawfinder-build/flawfinder.py"); // argsProcess.push_back("-F"); argsProcess.push_back("-c"); argsProcess.push_back("-C"); @@ -192,8 +195,11 @@ namespace ctrace argsProcess.push_back("--sarif"); } argsProcess.push_back(src_file); - auto process = ProcessFactory::createProcess( - "python3", argsProcess); // or "cmd.exe" for Windows + const auto command = ctrace::resolveFlawfinderCommand(); + argsProcess.insert(argsProcess.begin(), command.prefixArguments.begin(), + command.prefixArguments.end()); + auto process = + ProcessFactory::createProcess(command.executable, argsProcess); process->execute(); if (config.global.ipc == "standardIO") @@ -234,7 +240,7 @@ namespace ctrace public: void execute(const std::string& file, ctrace::ProgramConfig config) const override { - ctrace::Thread::Output::cout("Running ikos on " + file); + ctrace::Thread::Output::cout("Running cppcheck on " + file); bool has_sarif_format = config.global.hasSarifFormat; std::string src_file = file; std::string entry_points = config.global.entry_points; @@ -250,8 +256,11 @@ namespace ctrace // argsProcess.push_back("--enable=all"); argsProcess.push_back(src_file); - auto process = ProcessFactory::createProcess( - "/opt/homebrew/bin/cppcheck", argsProcess); // ou "cmd.exe" pour Windows + const auto command = ctrace::resolveCppcheckCommand(); + argsProcess.insert(argsProcess.begin(), command.prefixArguments.begin(), + command.prefixArguments.end()); + auto process = + ProcessFactory::createProcess(command.executable, argsProcess); process->execute(); ctrace::Thread::Output::tool_out(process->logOutput); } @@ -263,7 +272,7 @@ namespace ctrace } std::string name() const override { - return "ikos"; + return "cppcheck"; } }; diff --git a/include/Process/Tools/ToolsInvoker.hpp b/include/Process/Tools/ToolsInvoker.hpp index 75c4551..f4be4d2 100644 --- a/include/Process/Tools/ToolsInvoker.hpp +++ b/include/Process/Tools/ToolsInvoker.hpp @@ -136,15 +136,19 @@ namespace ctrace m_ipc = nullptr; // Use std::cout directely coretrace::log(coretrace::Level::Debug, "Using standardIO for IPC.\n"); } - else + else if (m_config.global.ipc == "socket") { - m_ipc = std::make_shared(m_config.global.ipcPath); + m_ipc = std::make_shared(m_config.global.ipcPath); for (auto& [_, tool] : tools) { tool->setIpcStrategy(m_ipc); } } + else + { + m_ipc = nullptr; + } if (m_policy == std::launch::async) { diff --git a/include/Process/WinProcess.hpp b/include/Process/WinProcess.hpp index 848454a..b54cf95 100644 --- a/include/Process/WinProcess.hpp +++ b/include/Process/WinProcess.hpp @@ -3,102 +3,206 @@ #ifdef _WIN32 -#include +#include "Process.hpp" + +#define NOMINMAX #include + #include +#include +#include +#include class WindowsProcess : public Process { public: - WindowsProcess(const std::string& command) : command(command) {} + WindowsProcess(std::string command, std::vector args) + : m_command(std::move(command)), m_arguments_extra(std::move(args)) + { + } protected: void prepare() override { - std::cout << "Preparing Windows process\n"; prepareArguments(); } void run() override { - std::cout << "Running Windows process\n"; + SECURITY_ATTRIBUTES sa{}; + sa.nLength = sizeof(sa); + sa.bInheritHandle = TRUE; + + HANDLE stdoutRead = nullptr; + HANDLE stdoutWrite = nullptr; - // Préparer la commande avec les arguments - std::string cmdLine = command; - for (const auto& arg : arguments) + if (!CreatePipe(&stdoutRead, &stdoutWrite, &sa, 0)) { - cmdLine += " " + arg; + throw std::runtime_error("Failed to create stdout pipe"); } - - STARTUPINFO si = {sizeof(si)}; - PROCESS_INFORMATION pi; - SECURITY_ATTRIBUTES sa = {sizeof(sa), NULL, TRUE}; - - // Créer un fichier pour capturer les logs - HANDLE logFile = CreateFile("process.log", GENERIC_WRITE, FILE_SHARE_READ, &sa, - CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - if (logFile == INVALID_HANDLE_VALUE) + if (!SetHandleInformation(stdoutRead, HANDLE_FLAG_INHERIT, 0)) { - throw std::runtime_error("Failed to create log file"); + CloseHandle(stdoutRead); + CloseHandle(stdoutWrite); + throw std::runtime_error("Failed to configure stdout pipe"); } - si.hStdOutput = logFile; - si.hStdError = logFile; - si.dwFlags |= STARTF_USESTDHANDLES; + STARTUPINFOA si{}; + si.cb = sizeof(si); + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdOutput = stdoutWrite; + si.hStdError = stdoutWrite; + si.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + + PROCESS_INFORMATION pi{}; + std::string commandLine = buildCommandLine(); + std::vector mutableCommandLine(commandLine.begin(), commandLine.end()); + mutableCommandLine.push_back('\0'); - // Lancer le processus - if (!CreateProcess(NULL, const_cast(cmdLine.c_str()), NULL, NULL, TRUE, 0, NULL, - NULL, &si, &pi)) + const BOOL created = + CreateProcessA(nullptr, mutableCommandLine.data(), nullptr, nullptr, TRUE, + CREATE_NO_WINDOW, nullptr, nullptr, &si, &pi); + + CloseHandle(stdoutWrite); + if (!created) { - CloseHandle(logFile); - throw std::runtime_error("Failed to create process"); + CloseHandle(stdoutRead); + throw std::runtime_error("Failed to create process: " + lastErrorMessage()); } - // Attendre que le processus se termine WaitForSingleObject(pi.hProcess, INFINITE); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); - CloseHandle(logFile); + m_stdoutRead = stdoutRead; captureLogs(); } void cleanup() override { - std::cout << "Cleaning up Windows process\n"; - DeleteFile("process.log"); // Supprimer le fichier de logs temporaire + if (m_stdoutRead != nullptr) + { + CloseHandle(m_stdoutRead); + m_stdoutRead = nullptr; + } } void prepareArguments() override { - std::cout << "Preparing arguments for Windows\n"; - arguments.clear(); - arguments.push_back("/C"); // Exemple pour `cmd.exe` - arguments.push_back("dir"); // Commande `dir` pour lister les fichiers + m_arguments = m_arguments_extra; } void captureLogs() override { - std::cout << "Capturing logs for Windows\n"; - FILE* logFile = fopen("process.log", "r"); - if (!logFile) + if (m_stdoutRead == nullptr) { - throw std::runtime_error("Failed to open log file"); + return; } - char buffer[1024]; logOutput.clear(); - while (fgets(buffer, sizeof(buffer), logFile)) + char buffer[4096]; + DWORD bytesRead = 0; + while (ReadFile(m_stdoutRead, buffer, sizeof(buffer), &bytesRead, nullptr) && + bytesRead > 0) { - logOutput += buffer; + logOutput.append(buffer, buffer + bytesRead); } - fclose(logFile); - - std::cout << "Logs captured: " << logOutput << "\n"; } private: - std::string command; + [[nodiscard]] static std::string quoteArgument(const std::string& argument) + { + if (argument.empty()) + { + return "\"\""; + } + + bool needsQuotes = false; + for (const char ch : argument) + { + if (ch == ' ' || ch == '\t' || ch == '"') + { + needsQuotes = true; + break; + } + } + if (!needsQuotes) + { + return argument; + } + + std::string quoted = "\""; + std::size_t backslashCount = 0; + for (const char ch : argument) + { + if (ch == '\\') + { + ++backslashCount; + continue; + } + if (ch == '"') + { + quoted.append(backslashCount * 2 + 1, '\\'); + quoted.push_back('"'); + backslashCount = 0; + continue; + } + + if (backslashCount > 0) + { + quoted.append(backslashCount, '\\'); + backslashCount = 0; + } + quoted.push_back(ch); + } + + if (backslashCount > 0) + { + quoted.append(backslashCount * 2, '\\'); + } + quoted.push_back('"'); + return quoted; + } + + [[nodiscard]] static std::string lastErrorMessage() + { + const DWORD error = GetLastError(); + LPSTR messageBuffer = nullptr; + const DWORD size = FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&messageBuffer), 0, nullptr); + + std::string message = + (size > 0 && messageBuffer != nullptr) ? std::string(messageBuffer, size) + : ("Windows error " + std::to_string(error)); + if (messageBuffer != nullptr) + { + LocalFree(messageBuffer); + } + while (!message.empty() && + (message.back() == '\r' || message.back() == '\n' || message.back() == ' ')) + { + message.pop_back(); + } + return message; + } + + [[nodiscard]] std::string buildCommandLine() const + { + std::string commandLine = quoteArgument(m_command); + for (const auto& argument : m_arguments) + { + commandLine.push_back(' '); + commandLine += quoteArgument(argument); + } + return commandLine; + } + + std::string m_command; + std::vector m_arguments_extra; + HANDLE m_stdoutRead = nullptr; }; #endif diff --git a/include/ctrace_tools/mangle.hpp b/include/ctrace_tools/mangle.hpp index e0480e9..a07e25a 100644 --- a/include/ctrace_tools/mangle.hpp +++ b/include/ctrace_tools/mangle.hpp @@ -1,10 +1,13 @@ // SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include #include +#include #include -#include -#include -#include -#include + +#include namespace ctrace_tools::mangle { @@ -15,39 +18,28 @@ namespace ctrace_tools::mangle * requiring it can be implicitly converted to `std::string_view`. */ template concept StringLike = std::convertible_to; - // TODO: add mangling for windows - /** * @brief Checks if a given name is a mangled C++ symbol. * - * This function determines whether a given name follows the Itanium C++ ABI - * mangling conventions (e.g., names starting with `_Z`). + * This function uses LLVM's cross-platform demangler so it can recognize + * both Itanium-style names (`_Z...`) and Microsoft-style names (`?...`). * * @tparam T A type satisfying the `StringLike` concept. * @param name The name to check for mangling. * @return `true` if the name is mangled, `false` otherwise. - * - * @note This function uses `abi::__cxa_demangle` to attempt demangling. - * If the demangling succeeds, the name is considered mangled. - * @note This implementation is specific to platforms using the Itanium C++ ABI - * (e.g., Linux, macOS). Windows mangling is not yet supported. - * @note The function is marked `[[nodiscard]]`, meaning the return value - * should not be ignored. It is also `noexcept`, indicating that it - * does not throw exceptions. */ [[nodiscard]] bool isMangled(StringLike auto name) noexcept { - int status = 0; - std::string_view sv{name}; + const std::string_view sv{name}; - if (sv.length() < 2 || sv.substr(0, 2) != "_Z") + const bool looksMangled = + sv.starts_with("_Z") || sv.starts_with("?") || sv.starts_with(".?"); + if (!looksMangled) { return false; } - std::unique_ptr demangled( - abi::__cxa_demangle(sv.data(), nullptr, nullptr, &status), std::free); - return status == 0; + return llvm::demangle(sv) != sv; } /** diff --git a/scripts/build-windows.ps1 b/scripts/build-windows.ps1 new file mode 100644 index 0000000..28cdd7d --- /dev/null +++ b/scripts/build-windows.ps1 @@ -0,0 +1,257 @@ +# SPDX-License-Identifier: Apache-2.0 +[CmdletBinding()] +param( + [Parameter(Mandatory = $true)] + [string]$LLVMDir, + + [string]$BuildDir = "build-win", + [string]$InstallDir = "dist\windows", + [string]$Configuration = "Release", + [string]$Generator = "Ninja Multi-Config", + [string]$Arch = "x64", + [string]$Toolset = "", + [string]$ParserType = "CLI11", + [string]$VersionOverride = "", + [switch]$PackageZip +) + +$ErrorActionPreference = "Stop" + +function Invoke-NativeCommand { + param( + [Parameter(Mandatory = $true)] + [string]$FilePath, + + [Parameter(ValueFromRemainingArguments = $true)] + [string[]]$Arguments + ) + + & $FilePath @Arguments + if ($LASTEXITCODE -ne 0) + { + throw "Command failed with exit code ${LASTEXITCODE}: $FilePath $($Arguments -join ' ')" + } +} + +function New-PatchedCMakePackageDir { + param( + [Parameter(Mandatory = $true)] + [string]$SourceDir, + + [Parameter(Mandatory = $true)] + [string]$DestinationDir, + + [Parameter(Mandatory = $true)] + [string]$OldValue, + + [Parameter(Mandatory = $true)] + [string]$NewValue, + + [string]$ImportPrefix = "" + ) + + New-Item -ItemType Directory -Force -Path $DestinationDir | Out-Null + Copy-Item -Path (Join-Path $SourceDir "*") -Destination $DestinationDir -Recurse -Force + + Get-ChildItem -Path $DestinationDir -Recurse -Filter *.cmake | ForEach-Object { + $content = Get-Content -Path $_.FullName -Raw + $updated = $content.Replace($OldValue, $NewValue) + if ($ImportPrefix -ne "") + { + $updated = [regex]::Replace( + $updated, + '(?ms)# Compute the installation prefix relative to this file\.\r?\nget_filename_component\(_IMPORT_PREFIX "\$\{CMAKE_CURRENT_LIST_FILE\}" PATH\)\r?\nget_filename_component\(_IMPORT_PREFIX "\$\{_IMPORT_PREFIX\}" PATH\)\r?\nget_filename_component\(_IMPORT_PREFIX "\$\{_IMPORT_PREFIX\}" PATH\)\r?\nget_filename_component\(_IMPORT_PREFIX "\$\{_IMPORT_PREFIX\}" PATH\)\r?\nif\(_IMPORT_PREFIX STREQUAL "/"\)\r?\n\s*set\(_IMPORT_PREFIX ""\)\r?\nendif\(\)', + "# Compute the installation prefix relative to this file.`nset(_IMPORT_PREFIX `"$ImportPrefix`")" + ) + $updated = [regex]::Replace( + $updated, + '(?ms)# Compute the installation prefix from this LLVMConfig\.cmake file location\.\r?\nget_filename_component\(LLVM_INSTALL_PREFIX "\$\{CMAKE_CURRENT_LIST_FILE\}" PATH\)\r?\nget_filename_component\(LLVM_INSTALL_PREFIX "\$\{LLVM_INSTALL_PREFIX\}" PATH\)\r?\nget_filename_component\(LLVM_INSTALL_PREFIX "\$\{LLVM_INSTALL_PREFIX\}" PATH\)\r?\nget_filename_component\(LLVM_INSTALL_PREFIX "\$\{LLVM_INSTALL_PREFIX\}" PATH\)', + "# Compute the installation prefix from this LLVMConfig.cmake file location.`nset(LLVM_INSTALL_PREFIX `"$ImportPrefix`")" + ) + $updated = [regex]::Replace( + $updated, + '(?ms)# Compute the installation prefix from this LLVMConfig\.cmake file location\.\r?\nget_filename_component\(CLANG_INSTALL_PREFIX "\$\{CMAKE_CURRENT_LIST_FILE\}" PATH\)\r?\nget_filename_component\(CLANG_INSTALL_PREFIX "\$\{CLANG_INSTALL_PREFIX\}" PATH\)\r?\nget_filename_component\(CLANG_INSTALL_PREFIX "\$\{CLANG_INSTALL_PREFIX\}" PATH\)\r?\nget_filename_component\(CLANG_INSTALL_PREFIX "\$\{CLANG_INSTALL_PREFIX\}" PATH\)', + "# Compute the installation prefix from this LLVMConfig.cmake file location.`nset(CLANG_INSTALL_PREFIX `"$ImportPrefix`")" + ) + } + if ($updated -ne $content) + { + Set-Content -Path $_.FullName -Value $updated -NoNewline + } + } +} + +function Reset-BuildDirectoryIfGeneratorChanged { + param( + [Parameter(Mandatory = $true)] + [string]$BuildDirectory, + + [Parameter(Mandatory = $true)] + [string]$GeneratorName, + + [string]$PlatformName = "", + + [string]$ToolsetName = "" + ) + + $cachePath = Join-Path $BuildDirectory "CMakeCache.txt" + if (-not (Test-Path $cachePath)) + { + return + } + + $cacheContent = Get-Content $cachePath -ErrorAction Stop + $cachedGeneratorLine = $cacheContent | Where-Object { $_ -like "CMAKE_GENERATOR:INTERNAL=*" } | Select-Object -First 1 + $cachedPlatformLine = $cacheContent | Where-Object { $_ -like "CMAKE_GENERATOR_PLATFORM:INTERNAL=*" } | Select-Object -First 1 + $cachedToolsetLine = $cacheContent | Where-Object { $_ -like "CMAKE_GENERATOR_TOOLSET:INTERNAL=*" } | Select-Object -First 1 + + $cachedGenerator = if ($cachedGeneratorLine) { $cachedGeneratorLine.Split("=", 2)[1] } else { "" } + $cachedPlatform = if ($cachedPlatformLine) { $cachedPlatformLine.Split("=", 2)[1] } else { "" } + $cachedToolset = if ($cachedToolsetLine) { $cachedToolsetLine.Split("=", 2)[1] } else { "" } + + if ($cachedGenerator -eq $GeneratorName -and $cachedPlatform -eq $PlatformName -and $cachedToolset -eq $ToolsetName) + { + return + } + + Write-Host "Resetting build directory '$BuildDirectory' because cached generator/platform/toolset '$cachedGenerator'/'$cachedPlatform'/'$cachedToolset' does not match '$GeneratorName'/'$PlatformName'/'$ToolsetName'." + Remove-Item -LiteralPath $BuildDirectory -Recurse -Force +} + +$repoRoot = Split-Path -Parent $PSScriptRoot +$resolvedBuildDir = Join-Path $repoRoot $BuildDir +$resolvedInstallDir = Join-Path $repoRoot $InstallDir +$installedLLVMDir = (Resolve-Path $LLVMDir).Path +$installedClangDir = Join-Path (Split-Path $installedLLVMDir -Parent) "clang" +$resolvedLLVMDir = $installedLLVMDir +$resolvedClangDir = $installedClangDir +$llvmRoot = Split-Path (Split-Path (Split-Path $resolvedLLVMDir -Parent) -Parent) -Parent +$llvmBinDir = Join-Path $llvmRoot "bin" + +if (Test-Path $llvmBinDir) +{ + $env:PATH = "$llvmBinDir;$env:PATH" +} + +$clangClPath = Join-Path $llvmBinDir "clang-cl.exe" +if (-not (Test-Path $clangClPath)) +{ + throw "clang-cl.exe was not found in '$llvmBinDir'." +} + +$vswhere = Join-Path ${env:ProgramFiles(x86)} "Microsoft Visual Studio\Installer\vswhere.exe" +if (-not (Test-Path $vswhere)) +{ + throw "Unable to find vswhere.exe." +} + +$vsPath = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath +if (-not $vsPath) +{ + throw "Unable to find a Visual Studio installation with C++ build tools." +} + +$platformForCache = if ($Generator -like "Visual Studio*") { $Arch } else { "" } +$toolsetForCache = if ($Generator -like "Visual Studio*") { $Toolset } else { "" } +Reset-BuildDirectoryIfGeneratorChanged -BuildDirectory $resolvedBuildDir -GeneratorName $Generator -PlatformName $platformForCache -ToolsetName $toolsetForCache + +$diaguidsCandidate = Join-Path $vsPath "DIA SDK\lib\amd64\diaguids.lib" +if (-not (Test-Path $diaguidsCandidate)) +{ + $diaguidsCandidate = Get-ChildItem -Path $vsPath -Recurse -Filter diaguids.lib -ErrorAction SilentlyContinue | + Where-Object { $_.FullName -like "*DIA SDK\\lib\\amd64\\diaguids.lib" } | + Select-Object -First 1 -ExpandProperty FullName +} + +$staleDiaPath = "C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/DIA SDK/lib/amd64/diaguids.lib" +$llvmExportsPath = Join-Path $installedLLVMDir "LLVMExports.cmake" +if ((Test-Path $llvmExportsPath) -and (Test-Path $diaguidsCandidate)) +{ + $llvmExportsContent = Get-Content -Path $llvmExportsPath -Raw + if ($llvmExportsContent.Contains($staleDiaPath)) + { + $patchedRoot = Join-Path $resolvedBuildDir "__llvm_cmake_patched" + $patchedCMakeRoot = Join-Path $patchedRoot "lib\\cmake" + $patchedLLVMDir = Join-Path $patchedCMakeRoot "llvm" + $patchedClangDir = Join-Path $patchedCMakeRoot "clang" + $replacementDiaPath = $diaguidsCandidate.Replace("\", "/") + $importPrefix = $llvmRoot.Replace("\", "/") + + New-PatchedCMakePackageDir -SourceDir $installedLLVMDir -DestinationDir $patchedLLVMDir -OldValue $staleDiaPath -NewValue $replacementDiaPath -ImportPrefix $importPrefix + New-PatchedCMakePackageDir -SourceDir $installedClangDir -DestinationDir $patchedClangDir -OldValue $staleDiaPath -NewValue $replacementDiaPath -ImportPrefix $importPrefix + + $resolvedLLVMDir = $patchedLLVMDir + $resolvedClangDir = $patchedClangDir + } +} + +$cmakeArgs = @( + "-S", $repoRoot, + "-B", $resolvedBuildDir, + "-G", $Generator, + "-DPARSER_TYPE=$ParserType", + "-DUSE_THREAD_SANITIZER=OFF", + "-DUSE_ADDRESS_SANITIZER=OFF", + "-DLLVM_DIR=$resolvedLLVMDir", + "-DClang_DIR=$resolvedClangDir" +) + +if ($Generator -like "Visual Studio*") +{ + $cmakeArgs += @("-A", $Arch) + if ($Toolset -ne "") + { + $cmakeArgs += @("-T", $Toolset) + } +} +else +{ + $cmakeArgs += @( + "-DCMAKE_C_COMPILER=$clangClPath", + "-DCMAKE_CXX_COMPILER=$clangClPath" + ) +} + +if ($VersionOverride -ne "") +{ + $cmakeArgs += "-DCORETRACE_VERSION_OVERRIDE=$VersionOverride" +} + +if ($Generator -like "Visual Studio*") +{ + Invoke-NativeCommand cmake @cmakeArgs +} +else +{ + $devShell = Join-Path $vsPath "Common7\Tools\Launch-VsDevShell.ps1" + if (-not (Test-Path $devShell)) + { + throw "Unable to find Launch-VsDevShell.ps1 at '$devShell'." + } + + . $devShell -Arch amd64 -HostArch amd64 | Out-Null + Invoke-NativeCommand cmake @cmakeArgs +} + +Invoke-NativeCommand cmake --build $resolvedBuildDir --config $Configuration +Invoke-NativeCommand cmake --install $resolvedBuildDir --config $Configuration --prefix $resolvedInstallDir + +$binaryPath = Join-Path $resolvedInstallDir "bin\ctrace.exe" +if (-not (Test-Path $binaryPath)) +{ + throw "Expected output binary was not produced: $binaryPath" +} + +if ($PackageZip) +{ + $zipPath = Join-Path $repoRoot "coretrace-windows-$Configuration.zip" + if (Test-Path $zipPath) + { + Remove-Item -LiteralPath $zipPath -Force + } + Compress-Archive -Path (Join-Path $resolvedInstallDir "*") -DestinationPath $zipPath + Write-Host "Created package: $zipPath" +} + +Write-Host "Build completed successfully." +Write-Host "Executable: $binaryPath" diff --git a/src/App/ToolConfig.cpp b/src/App/ToolConfig.cpp index 51346a2..5140461 100644 --- a/src/App/ToolConfig.cpp +++ b/src/App/ToolConfig.cpp @@ -865,7 +865,10 @@ namespace ctrace } if (hasValue) { - config.global.ipcPath = stringValue; + if (!stringValue.empty()) + { + config.global.ipcPath = stringValue; + } } return true; diff --git a/src/ArgumentParser/ArgumentParserFactory.cpp b/src/ArgumentParser/ArgumentParserFactory.cpp index c5563ec..62fb223 100644 --- a/src/ArgumentParser/ArgumentParserFactory.cpp +++ b/src/ArgumentParser/ArgumentParserFactory.cpp @@ -1,7 +1,13 @@ // SPDX-License-Identifier: Apache-2.0 #include "ArgumentParser/ArgumentParserFactory.hpp" + +#if !defined(USE_GETOPT) #include "ArgumentParser/CLI11/CLI11ArgumentParser.hpp" +#endif + +#if defined(USE_GETOPT) #include "ArgumentParser/GetOpt/GetOptArgumentParser.hpp" +#endif std::unique_ptr createArgumentParser() { diff --git a/src/Process/Tools/TscancodeToolImplementation.cpp b/src/Process/Tools/TscancodeToolImplementation.cpp index 7780f00..bc57ce9 100644 --- a/src/Process/Tools/TscancodeToolImplementation.cpp +++ b/src/Process/Tools/TscancodeToolImplementation.cpp @@ -20,8 +20,10 @@ namespace ctrace argsProcess.push_back("--enable=all"); argsProcess.push_back(src_file); - auto process = ProcessFactory::createProcess( - "./tscancode/src/tscancode/trunk/tscancode", argsProcess); + const auto command = ctrace::resolveTscancodeCommand(); + argsProcess.insert(argsProcess.begin(), command.prefixArguments.begin(), + command.prefixArguments.end()); + auto process = ProcessFactory::createProcess(command.executable, argsProcess); process->execute(); ctrace::Thread::Output::tool_out(process->logOutput); ctrace::Thread::Output::cout("Finished tscancode on " + file); diff --git a/src/ThirdParty/CoretraceLoggerWindows.cpp b/src/ThirdParty/CoretraceLoggerWindows.cpp new file mode 100644 index 0000000..463f036 --- /dev/null +++ b/src/ThirdParty/CoretraceLoggerWindows.cpp @@ -0,0 +1,567 @@ +// SPDX-License-Identifier: Apache-2.0 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace coretrace +{ + namespace + { + std::atomic g_log_enabled{false}; + std::atomic g_min_level{static_cast(Level::Info)}; + std::atomic g_thread_safe{true}; + std::atomic g_timestamps_enabled{false}; + std::atomic g_source_location_enabled{false}; + std::atomic g_sink{nullptr}; + + std::mutex g_state_mutex; + std::mutex g_output_mutex; + std::once_flag g_init_once; + + std::string g_prefix = "==ct=="; + bool g_min_level_set_explicitly = false; + bool g_modules_set_explicitly = false; + constexpr int kMaxModules = 32; + std::string g_modules[kMaxModules]; + int g_module_count = 0; + bool g_module_filter_active = false; + + [[nodiscard]] bool asciiCaseEqual(std::string_view lhs, std::string_view rhs) + { + if (lhs.size() != rhs.size()) + { + return false; + } + + for (std::size_t i = 0; i < lhs.size(); ++i) + { + const char left = static_cast(std::tolower(static_cast(lhs[i]))); + const char right = + static_cast(std::tolower(static_cast(rhs[i]))); + if (left != right) + { + return false; + } + } + return true; + } + + [[nodiscard]] bool use_color() + { + static const bool enabled = []() + { + if (std::getenv("NO_COLOR") != nullptr) + { + return false; + } + return _isatty(_fileno(stderr)) != 0; + }(); + return enabled; + } + + [[nodiscard]] std::string current_prefix() + { + std::lock_guard lock(g_state_mutex); + return g_prefix; + } + + void add_module_locked(std::string_view name) + { + for (int i = 0; i < g_module_count; ++i) + { + if (g_modules[i] == name) + { + return; + } + } + + if (g_module_count >= kMaxModules) + { + return; + } + + g_modules[g_module_count++] = std::string(name); + g_module_filter_active = true; + } + + [[nodiscard]] int parse_level_from_env(const char* value) + { + if (value == nullptr) + { + return static_cast(Level::Info); + } + if (asciiCaseEqual(value, "debug")) + { + return static_cast(Level::Debug); + } + if (asciiCaseEqual(value, "warn")) + { + return static_cast(Level::Warn); + } + if (asciiCaseEqual(value, "error")) + { + return static_cast(Level::Error); + } + return static_cast(Level::Info); + } + + void init_from_env() + { + if (!g_min_level_set_explicitly) + { + g_min_level.store(parse_level_from_env(std::getenv("CT_LOG_LEVEL")), + std::memory_order_release); + } + + if (!g_modules_set_explicitly) + { + const char* env_debug = std::getenv("CT_DEBUG"); + if (env_debug == nullptr || *env_debug == '\0') + { + return; + } + + std::lock_guard lock(g_state_mutex); + const char* start = env_debug; + while (*start != '\0') + { + const char* end = start; + while (*end != '\0' && *end != ',') + { + ++end; + } + if (end > start) + { + add_module_locked(std::string_view(start, static_cast(end - start))); + } + start = (*end == ',') ? end + 1 : end; + } + } + } + + [[nodiscard]] std::string make_timestamp_prefix() + { + using clock = std::chrono::system_clock; + const auto now = clock::now(); + const auto time = clock::to_time_t(now); + const auto millis = std::chrono::duration_cast( + now.time_since_epoch()) % + 1000; + + std::tm tmBuf{}; + gmtime_s(&tmBuf, &time); + + char buffer[40]; + std::snprintf(buffer, sizeof(buffer), "[%04d-%02d-%02dT%02d:%02d:%02d.%03d] ", + tmBuf.tm_year + 1900, tmBuf.tm_mon + 1, tmBuf.tm_mday, tmBuf.tm_hour, + tmBuf.tm_min, tmBuf.tm_sec, static_cast(millis.count())); + return buffer; + } + + [[nodiscard]] const char* basename_of(const char* path) + { + if (path == nullptr) + { + return ""; + } + + const char* last = path; + for (const char* p = path; *p != '\0'; ++p) + { + if (*p == '/' || *p == '\\') + { + last = p + 1; + } + } + return last; + } + } // namespace + + void enable_logging() + { + g_log_enabled.store(true, std::memory_order_release); + } + + void disable_logging() + { + g_log_enabled.store(false, std::memory_order_release); + } + + [[nodiscard]] bool log_is_enabled() + { + return g_log_enabled.load(std::memory_order_acquire); + } + + void set_prefix(std::string_view prefix) + { + std::lock_guard lock(g_state_mutex); + g_prefix.assign(prefix); + } + + void set_min_level(Level level) + { + g_min_level_set_explicitly = true; + init_once(); + g_min_level.store(static_cast(level), std::memory_order_release); + } + + [[nodiscard]] Level min_level() + { + return static_cast(g_min_level.load(std::memory_order_acquire)); + } + + void enable_module(std::string_view name) + { + if (name.empty()) + { + return; + } + + g_modules_set_explicitly = true; + init_once(); + std::lock_guard lock(g_state_mutex); + add_module_locked(name); + } + + void disable_module(std::string_view name) + { + if (name.empty()) + { + return; + } + + g_modules_set_explicitly = true; + init_once(); + std::lock_guard lock(g_state_mutex); + for (int i = 0; i < g_module_count; ++i) + { + if (g_modules[i] == name) + { + for (int j = i; j < g_module_count - 1; ++j) + { + g_modules[j] = g_modules[j + 1]; + } + --g_module_count; + if (g_module_count == 0) + { + g_module_filter_active = false; + } + break; + } + } + } + + void enable_all_modules() + { + g_modules_set_explicitly = true; + init_once(); + std::lock_guard lock(g_state_mutex); + g_module_count = 0; + g_module_filter_active = false; + } + + [[nodiscard]] bool module_is_enabled(std::string_view name) + { + std::lock_guard lock(g_state_mutex); + if (!g_module_filter_active) + { + return true; + } + for (int i = 0; i < g_module_count; ++i) + { + if (g_modules[i] == name) + { + return true; + } + } + return false; + } + + void set_thread_safe(bool enabled) + { + g_thread_safe.store(enabled, std::memory_order_release); + } + + void set_sink(SinkFn fn) + { + g_sink.store(fn, std::memory_order_release); + } + + void reset_sink() + { + g_sink.store(nullptr, std::memory_order_release); + } + + void set_timestamps(bool enabled) + { + g_timestamps_enabled.store(enabled, std::memory_order_release); + } + + void set_source_location(bool enabled) + { + g_source_location_enabled.store(enabled, std::memory_order_release); + } + + [[nodiscard]] std::string_view color(Color c) + { + if (!use_color()) + { + return {}; + } + + switch (c) + { + case Color::Reset: + return "\x1b[0m"; + case Color::Dim: + return "\x1b[2m"; + case Color::Bold: + return "\x1b[1m"; + case Color::Underline: + return "\x1b[4m"; + case Color::Italic: + return "\x1b[3m"; + case Color::Blink: + return "\x1b[5m"; + case Color::Reverse: + return "\x1b[7m"; + case Color::Hidden: + return "\x1b[8m"; + case Color::Strike: + return "\x1b[9m"; + case Color::Black: + return "\x1b[30m"; + case Color::Red: + return "\x1b[31m"; + case Color::Green: + return "\x1b[32m"; + case Color::Yellow: + return "\x1b[33m"; + case Color::Blue: + return "\x1b[34m"; + case Color::Magenta: + return "\x1b[35m"; + case Color::Cyan: + return "\x1b[36m"; + case Color::White: + return "\x1b[37m"; + case Color::Gray: + return "\x1b[90m"; + case Color::BrightRed: + return "\x1b[91m"; + case Color::BrightGreen: + return "\x1b[92m"; + case Color::BrightYellow: + return "\x1b[93m"; + case Color::BrightBlue: + return "\x1b[94m"; + case Color::BrightMagenta: + return "\x1b[95m"; + case Color::BrightCyan: + return "\x1b[96m"; + case Color::BrightWhite: + return "\x1b[97m"; + case Color::BgBlack: + return "\x1b[40m"; + case Color::BgRed: + return "\x1b[41m"; + case Color::BgGreen: + return "\x1b[42m"; + case Color::BgYellow: + return "\x1b[43m"; + case Color::BgBlue: + return "\x1b[44m"; + case Color::BgMagenta: + return "\x1b[45m"; + case Color::BgCyan: + return "\x1b[46m"; + case Color::BgWhite: + return "\x1b[47m"; + case Color::BgGray: + return "\x1b[100m"; + case Color::BgBrightRed: + return "\x1b[101m"; + case Color::BgBrightGreen: + return "\x1b[102m"; + case Color::BgBrightYellow: + return "\x1b[103m"; + case Color::BgBrightBlue: + return "\x1b[104m"; + case Color::BgBrightMagenta: + return "\x1b[105m"; + case Color::BgBrightCyan: + return "\x1b[106m"; + case Color::BgBrightWhite: + return "\x1b[107m"; + } + + return {}; + } + + [[nodiscard]] std::string_view level_label(Level level) + { + switch (level) + { + case Level::Debug: + return "DEBUG"; + case Level::Info: + return "INFO"; + case Level::Warn: + return "WARN"; + case Level::Error: + return "ERROR"; + } + return "INFO"; + } + + [[nodiscard]] std::string_view level_color(Level level) + { + switch (level) + { + case Level::Debug: + return color(Color::Cyan); + case Level::Info: + return color(Color::Green); + case Level::Warn: + return color(Color::Yellow); + case Level::Error: + return color(Color::Red); + } + return color(Color::Cyan); + } + + void write_raw(const char* data, size_t size) + { + if (data == nullptr || size == 0) + { + return; + } + + const auto sink = g_sink.load(std::memory_order_acquire); + if (sink != nullptr) + { + sink(data, size); + return; + } + + (void)fwrite(data, 1, size, stderr); + (void)fflush(stderr); + } + + void write_str(std::string_view value) + { + write_raw(value.data(), value.size()); + } + + void write_dec(size_t value) + { + write_str(std::to_string(value)); + } + + void write_hex(uintptr_t value) + { + write_str(std::format("0x{:x}", value)); + } + + [[nodiscard]] int pid() + { + return static_cast(GetCurrentProcessId()); + } + + [[nodiscard]] unsigned long long thread_id() + { + return static_cast(GetCurrentThreadId()); + } + + void write_prefix(Level level) + { + const std::string prefix = current_prefix(); + write_str(color(Color::Dim)); + write_str(std::format("|{}|", pid())); + write_str(color(Color::Reset)); + write_raw(" ", 1); + write_str(color(Color::Gray)); + write_str(color(Color::Italic)); + write_str(prefix); + write_raw(" ", 1); + write_str(color(Color::Reset)); + write_str(level_color(level)); + write_str("["); + write_str(level_label(level)); + write_str("]"); + write_str(color(Color::Reset)); + write_raw(" ", 1); + } + + void init_once() + { + std::call_once(g_init_once, init_from_env); + } + + void write_log_line(Level level, std::string_view module, std::string_view message, + const std::source_location& loc) + { + std::unique_lock lock(g_output_mutex, std::defer_lock); + if (g_thread_safe.load(std::memory_order_acquire)) + { + lock.lock(); + } + + std::string line; + if (g_timestamps_enabled.load(std::memory_order_acquire)) + { + line += make_timestamp_prefix(); + } + + line += color(Color::Dim); + line += std::format("|{}|", pid()); + line += color(Color::Reset); + line += " "; + line += color(Color::Gray); + line += color(Color::Italic); + line += current_prefix(); + line += " "; + line += color(Color::Reset); + line += level_color(level); + line += "["; + line += level_label(level); + line += "]"; + line += color(Color::Reset); + + if (g_source_location_enabled.load(std::memory_order_acquire)) + { + line += " "; + line += color(Color::Dim); + line += basename_of(loc.file_name()); + line += ":"; + line += std::to_string(loc.line()); + line += color(Color::Reset); + } + + if (!module.empty()) + { + line += " "; + line += color(Color::Dim); + line += "("; + line.append(module.data(), module.size()); + line += ")"; + line += color(Color::Reset); + } + + line += " "; + line.append(message.data(), message.size()); + write_raw(line.data(), line.size()); + } +} // namespace coretrace diff --git a/src/ThirdParty/coretrace-compiler/windows/ct_runtime_alloc.cpp b/src/ThirdParty/coretrace-compiler/windows/ct_runtime_alloc.cpp new file mode 100644 index 0000000..7e6b103 --- /dev/null +++ b/src/ThirdParty/coretrace-compiler/windows/ct_runtime_alloc.cpp @@ -0,0 +1,901 @@ +// SPDX-License-Identifier: Apache-2.0 +#include "ct_runtime_internal.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include + +namespace +{ + enum CtAllocKind : unsigned char + { + CT_ALLOC_KIND_MALLOC = 0, + CT_ALLOC_KIND_NEW = 1, + CT_ALLOC_KIND_NEW_ARRAY = 2, + CT_ALLOC_KIND_MMAP = 3, + CT_ALLOC_KIND_SBRK = 4, + CT_ALLOC_KIND_ALIGNED = 5 + }; + + struct CtAllocEntry + { + size_t size = 0; + size_t req_size = 0; + const char* site = nullptr; + unsigned char state = CT_ENTRY_EMPTY; + unsigned char kind = CT_ALLOC_KIND_MALLOC; + }; + + std::mutex ct_alloc_mutex; + std::unordered_map ct_alloc_table; + + CT_NODISCARD CT_NOINSTR const char* ct_kind_name(unsigned char kind) + { + switch (kind) + { + case CT_ALLOC_KIND_MALLOC: + return "malloc"; + case CT_ALLOC_KIND_NEW: + return "new"; + case CT_ALLOC_KIND_NEW_ARRAY: + return "new[]"; + case CT_ALLOC_KIND_MMAP: + return "mmap"; + case CT_ALLOC_KIND_SBRK: + return "sbrk"; + case CT_ALLOC_KIND_ALIGNED: + return "aligned"; + default: + return "unknown"; + } + } + + CT_NODISCARD CT_NOINSTR bool ct_is_power_of_two(size_t value) + { + return value != 0 && (value & (value - 1)) == 0; + } + + CT_NOINSTR void ct_track_shadow_alloc(void* ptr, size_t req_size, size_t alloc_size) + { + if (!ptr || !ct_is_enabled(CT_FEATURE_SHADOW)) + { + return; + } + + const size_t live_size = req_size ? req_size : alloc_size; + if (live_size != 0) + { + ct_shadow_unpoison_range(ptr, live_size); + } + if (alloc_size > live_size) + { + ct_shadow_poison_range(static_cast(ptr) + live_size, alloc_size - live_size); + } + } + + CT_NOINSTR void ct_track_shadow_free(void* ptr, size_t size) + { + if (!ptr || !ct_is_enabled(CT_FEATURE_SHADOW) || size == 0) + { + return; + } + ct_shadow_poison_range(ptr, size); + } + + CT_NOINSTR void ct_log_alloc_event(const char* action, void* ptr, size_t size, const char* site, + unsigned char kind) + { + if (!ct_is_enabled(CT_FEATURE_ALLOC_TRACE)) + { + return; + } + + ct_log(CTLevel::Info, "ct: {} ptr={:p} size={} kind={} site={}\n", action, ptr, size, + ct_kind_name(kind), ct_site_name(site)); + } + + CT_NOINSTR void ct_log_skip_event(const char* action, void* ptr, const char* reason) + { + ct_log(CTLevel::Warn, "ct: {} skipped ptr={:p} ({})\n", action, ptr, reason); + } + + CT_NODISCARD CT_NOINSTR int ct_table_remove_with_state(void* ptr, unsigned char new_state, + size_t* size_out, size_t* req_size_out, + const char** site_out, + unsigned char* kind_out) + { + if (!ptr) + { + return 0; + } + + auto it = ct_alloc_table.find(ptr); + if (it == ct_alloc_table.end()) + { + return 0; + } + + CtAllocEntry& entry = it->second; + if (size_out) + { + *size_out = entry.size; + } + if (req_size_out) + { + *req_size_out = entry.req_size; + } + if (site_out) + { + *site_out = entry.site; + } + if (kind_out) + { + *kind_out = entry.kind; + } + + if (entry.state == CT_ENTRY_FREED || entry.state == CT_ENTRY_AUTOFREED) + { + return -1; + } + + entry.state = new_state; + return 1; + } + + CT_NOINSTR void ct_release_memory(void* ptr, unsigned char kind) + { + if (!ptr) + { + return; + } + + switch (kind) + { + case CT_ALLOC_KIND_NEW: + ::operator delete(ptr); + return; + case CT_ALLOC_KIND_NEW_ARRAY: + ::operator delete[](ptr); + return; + case CT_ALLOC_KIND_MMAP: + (void)VirtualFree(ptr, 0, MEM_RELEASE); + return; + case CT_ALLOC_KIND_ALIGNED: + _aligned_free(ptr); + return; + default: + std::free(ptr); + return; + } + } + + CT_NOINSTR void ct_release_unknown_delete(void* ptr, bool is_array) + { + if (!ptr) + { + return; + } + + if (is_array) + { + ::operator delete[](ptr); + } + else + { + ::operator delete(ptr); + } + } + + CT_NODISCARD CT_NOINSTR DWORD ct_translate_page_protection(int prot) + { + constexpr int kProtRead = 0x1; + constexpr int kProtWrite = 0x2; + constexpr int kProtExec = 0x4; + + const bool can_read = (prot & kProtRead) != 0; + const bool can_write = (prot & kProtWrite) != 0; + const bool can_exec = (prot & kProtExec) != 0; + + if (can_exec && can_write) + { + return PAGE_EXECUTE_READWRITE; + } + if (can_exec && can_read) + { + return PAGE_EXECUTE_READ; + } + if (can_exec) + { + return PAGE_EXECUTE; + } + if (can_write) + { + return PAGE_READWRITE; + } + if (can_read) + { + return PAGE_READONLY; + } + return PAGE_NOACCESS; + } + + CT_NODISCARD CT_NOINSTR void* ct_record_alloc(void* ptr, size_t req_size, size_t alloc_size, + const char* site, unsigned char kind) + { + if (!ptr) + { + return ptr; + } + + ct_lock_acquire(); + (void)ct_table_insert(ptr, req_size, alloc_size, site, kind); + ct_lock_release(); + + ct_track_shadow_alloc(ptr, req_size, alloc_size); + ct_log_alloc_event("alloc", ptr, req_size ? req_size : alloc_size, site, kind); + return ptr; + } + + CT_NOINSTR void ct_record_realloc(void* old_ptr, void* new_ptr, size_t size, const char* site) + { + if (!new_ptr) + { + return; + } + + ct_lock_acquire(); + if (old_ptr && old_ptr != new_ptr) + { + (void)ct_table_remove_with_state(old_ptr, CT_ENTRY_FREED, nullptr, nullptr, nullptr, nullptr); + } + + auto& entry = ct_alloc_table[new_ptr]; + entry.size = size; + entry.req_size = size; + entry.site = site; + entry.state = CT_ENTRY_USED; + entry.kind = CT_ALLOC_KIND_MALLOC; + ct_lock_release(); + + ct_track_shadow_alloc(new_ptr, size, size); + ct_log_alloc_event("realloc", new_ptr, size, site, CT_ALLOC_KIND_MALLOC); + } + + CT_NODISCARD CT_NOINSTR int ct_remove_for_release(void* ptr, unsigned char new_state, + size_t* size_out, const char** site_out, + unsigned char* kind_out) + { + size_t req_size = 0; + return ct_table_remove_with_state(ptr, new_state, size_out, &req_size, site_out, kind_out); + } + + CT_NOINSTR void ct_autofree_impl(void* ptr, bool is_array) + { + ct_init_env_once(); + if (!ct_is_enabled(CT_FEATURE_ALLOC) || !ct_is_enabled(CT_FEATURE_AUTOFREE)) + { + return; + } + if (!ptr) + { + ct_log_skip_event("auto-free", ptr, "null"); + return; + } + + size_t size = 0; + const char* site = nullptr; + unsigned char kind = CT_ALLOC_KIND_MALLOC; + + ct_lock_acquire(); + const int found = ct_remove_for_release(ptr, CT_ENTRY_AUTOFREED, &size, &site, &kind); + ct_lock_release(); + + if (found <= 0) + { + ct_log_skip_event("auto-free", ptr, found == 0 ? "unknown" : "already freed"); + return; + } + + ct_track_shadow_free(ptr, size); + ct_log(CTLevel::Warn, "ct: auto-free ptr={:p} size={} site={}\n", ptr, size, + ct_site_name(site)); + + if (kind == CT_ALLOC_KIND_NEW_ARRAY || (kind == CT_ALLOC_KIND_NEW && is_array)) + { + ::operator delete[](ptr); + return; + } + if (kind == CT_ALLOC_KIND_NEW || kind == CT_ALLOC_KIND_NEW_ARRAY || kind == CT_ALLOC_KIND_ALIGNED || + kind == CT_ALLOC_KIND_MMAP) + { + ct_release_memory(ptr, kind); + return; + } + + std::free(ptr); + } + + struct CtLeakReporter + { + CT_NOINSTR ~CtLeakReporter() + { + std::vector> leaks; + + ct_lock_acquire(); + for (const auto& [ptr, entry] : ct_alloc_table) + { + if (entry.state == CT_ENTRY_USED) + { + leaks.push_back({ptr, entry}); + } + } + ct_lock_release(); + + if (leaks.empty()) + { + return; + } + + ct_disable_logging(); + ct_write_prefix(CTLevel::Error); + ct_write_cstr("ct: leaks detected count="); + ct_write_dec(leaks.size()); + ct_write_cstr("\n"); + + size_t reported = 0; + for (const auto& [ptr, entry] : leaks) + { + ct_write_prefix(CTLevel::Warn); + ct_write_cstr("ct: leak ptr="); + ct_write_hex(reinterpret_cast(ptr)); + ct_write_cstr(" size="); + ct_write_dec(entry.size); + ct_write_cstr(" site="); + ct_write_cstr(ct_site_name(entry.site)); + ct_write_cstr("\n"); + + if (++reported >= 32) + { + ct_write_prefix(CTLevel::Warn); + ct_write_cstr("ct: leak list truncated\n"); + break; + } + } + } + }; + + CtLeakReporter ct_leak_reporter; +} // namespace + +CT_NOINSTR void ct_lock_acquire(void) +{ + ct_alloc_mutex.lock(); +} + +CT_NOINSTR void ct_lock_release(void) +{ + ct_alloc_mutex.unlock(); +} + +CT_NODISCARD CT_NOINSTR int ct_table_insert(void* ptr, size_t req_size, size_t size, + const char* site, unsigned char kind) +{ + if (!ptr) + { + return 0; + } + + try + { + auto& entry = ct_alloc_table[ptr]; + entry.size = size; + entry.req_size = req_size; + entry.site = site; + entry.state = CT_ENTRY_USED; + entry.kind = kind; + return 1; + } + catch (...) + { + return 0; + } +} + +CT_NODISCARD CT_NOINSTR int ct_table_remove(void* ptr, size_t* size_out, size_t* req_size_out, + const char** site_out) +{ + return ct_table_remove_with_state(ptr, CT_ENTRY_FREED, size_out, req_size_out, site_out, + nullptr); +} + +CT_NODISCARD CT_NOINSTR int ct_table_lookup(const void* ptr, size_t* size_out, size_t* req_size_out, + const char** site_out, unsigned char* state_out) +{ + if (!ptr) + { + return 0; + } + + auto it = ct_alloc_table.find(const_cast(ptr)); + if (it == ct_alloc_table.end()) + { + return 0; + } + + const CtAllocEntry& entry = it->second; + if (size_out) + { + *size_out = entry.size; + } + if (req_size_out) + { + *req_size_out = entry.req_size; + } + if (site_out) + { + *site_out = entry.site; + } + if (state_out) + { + *state_out = entry.state; + } + return 1; +} + +CT_NODISCARD CT_NOINSTR int ct_table_lookup_containing(const void* ptr, void** base_out, + size_t* size_out, size_t* req_size_out, + const char** site_out, + unsigned char* state_out) +{ + if (!ptr) + { + return 0; + } + + const uintptr_t value = reinterpret_cast(ptr); + for (const auto& [base_ptr, entry] : ct_alloc_table) + { + if (entry.state != CT_ENTRY_USED && entry.state != CT_ENTRY_FREED && + entry.state != CT_ENTRY_AUTOFREED) + { + continue; + } + if (!base_ptr || entry.size == 0) + { + continue; + } + + const uintptr_t base = reinterpret_cast(base_ptr); + if (value < base || (value - base) >= entry.size) + { + continue; + } + + if (base_out) + { + *base_out = base_ptr; + } + if (size_out) + { + *size_out = entry.size; + } + if (req_size_out) + { + *req_size_out = entry.req_size; + } + if (site_out) + { + *site_out = entry.site; + } + if (state_out) + { + *state_out = entry.state; + } + return 1; + } + + return 0; +} + +extern "C" +{ + CT_NOINSTR void __ct_free(void* ptr); + + CT_NODISCARD CT_NOINSTR void* __ct_malloc(size_t size, const char* site) + { + ct_init_env_once(); + if (!ct_is_enabled(CT_FEATURE_ALLOC)) + { + return std::malloc(size); + } + return ct_record_alloc(std::malloc(size), size, size, site, CT_ALLOC_KIND_MALLOC); + } + + CT_NODISCARD CT_NOINSTR void* __ct_malloc_unreachable(size_t size, const char* site) + { + return __ct_malloc(size, site); + } + + CT_NODISCARD CT_NOINSTR void* __ct_calloc(size_t count, size_t size, const char* site) + { + ct_init_env_once(); + const size_t total = count * size; + if (!ct_is_enabled(CT_FEATURE_ALLOC)) + { + return std::calloc(count, size); + } + return ct_record_alloc(std::calloc(count, size), total, total, site, CT_ALLOC_KIND_MALLOC); + } + + CT_NODISCARD CT_NOINSTR void* __ct_calloc_unreachable(size_t count, size_t size, + const char* site) + { + return __ct_calloc(count, size, site); + } + + CT_NODISCARD CT_NOINSTR void* __ct_new(size_t size, const char* site) + { + ct_init_env_once(); + if (!ct_is_enabled(CT_FEATURE_ALLOC)) + { + return ::operator new(size); + } + return ct_record_alloc(::operator new(size), size, size, site, CT_ALLOC_KIND_NEW); + } + + CT_NODISCARD CT_NOINSTR void* __ct_new_unreachable(size_t size, const char* site) + { + return __ct_new(size, site); + } + + CT_NODISCARD CT_NOINSTR void* __ct_new_array(size_t size, const char* site) + { + ct_init_env_once(); + if (!ct_is_enabled(CT_FEATURE_ALLOC)) + { + return ::operator new[](size); + } + return ct_record_alloc(::operator new[](size), size, size, site, CT_ALLOC_KIND_NEW_ARRAY); + } + + CT_NODISCARD CT_NOINSTR void* __ct_new_array_unreachable(size_t size, const char* site) + { + return __ct_new_array(size, site); + } + + CT_NODISCARD CT_NOINSTR void* __ct_new_nothrow(size_t size, const char* site) + { + ct_init_env_once(); + if (!ct_is_enabled(CT_FEATURE_ALLOC)) + { + return ::operator new(size, std::nothrow); + } + return ct_record_alloc(::operator new(size, std::nothrow), size, size, site, + CT_ALLOC_KIND_NEW); + } + + CT_NODISCARD CT_NOINSTR void* __ct_new_nothrow_unreachable(size_t size, const char* site) + { + return __ct_new_nothrow(size, site); + } + + CT_NODISCARD CT_NOINSTR void* __ct_new_array_nothrow(size_t size, const char* site) + { + ct_init_env_once(); + if (!ct_is_enabled(CT_FEATURE_ALLOC)) + { + return ::operator new[](size, std::nothrow); + } + return ct_record_alloc(::operator new[](size, std::nothrow), size, size, site, + CT_ALLOC_KIND_NEW_ARRAY); + } + + CT_NODISCARD CT_NOINSTR void* __ct_new_array_nothrow_unreachable(size_t size, const char* site) + { + return __ct_new_array_nothrow(size, site); + } + + CT_NODISCARD CT_NOINSTR void* __ct_realloc(void* ptr, size_t size, const char* site) + { + ct_init_env_once(); + if (!ct_is_enabled(CT_FEATURE_ALLOC)) + { + return std::realloc(ptr, size); + } + if (!ptr) + { + return __ct_malloc(size, site); + } + if (size == 0) + { + __ct_free(ptr); + return nullptr; + } + + void* new_ptr = std::realloc(ptr, size); + if (!new_ptr) + { + return nullptr; + } + + ct_record_realloc(ptr, new_ptr, size, site); + return new_ptr; + } + + CT_NODISCARD CT_NOINSTR int __ct_posix_memalign(void** out, size_t align, size_t size, + const char* site) + { + ct_init_env_once(); + if (!out || !ct_is_power_of_two(align) || align < sizeof(void*)) + { + return EINVAL; + } + + void* ptr = _aligned_malloc(size, align); + if (!ptr) + { + return errno ? errno : ENOMEM; + } + + *out = ptr; + if (ct_is_enabled(CT_FEATURE_ALLOC)) + { + (void)ct_record_alloc(ptr, size, size, site, CT_ALLOC_KIND_ALIGNED); + } + return 0; + } + + CT_NODISCARD CT_NOINSTR void* __ct_aligned_alloc(size_t align, size_t size, const char* site) + { + ct_init_env_once(); + if (!ct_is_power_of_two(align) || align == 0) + { + errno = EINVAL; + return nullptr; + } + + void* ptr = _aligned_malloc(size, align); + if (!ct_is_enabled(CT_FEATURE_ALLOC)) + { + return ptr; + } + return ct_record_alloc(ptr, size, size, site, CT_ALLOC_KIND_ALIGNED); + } + + CT_NODISCARD CT_NOINSTR void* __ct_mmap(void* addr, size_t len, int prot, int flags, int fd, + size_t offset, const char* site) + { + ct_init_env_once(); + (void)flags; + if (fd != -1 || offset != 0 || len == 0) + { + errno = ENOSYS; + return reinterpret_cast(-1); + } + + void* ptr = VirtualAlloc(addr, len, MEM_COMMIT | MEM_RESERVE, ct_translate_page_protection(prot)); + if (!ptr) + { + errno = ENOMEM; + return reinterpret_cast(-1); + } + + if (!ct_is_enabled(CT_FEATURE_ALLOC)) + { + return ptr; + } + return ct_record_alloc(ptr, len, len, site, CT_ALLOC_KIND_MMAP); + } + + CT_NODISCARD CT_NOINSTR int __ct_munmap(void* addr, size_t len, const char* site) + { + ct_init_env_once(); + (void)len; + + size_t size = 0; + const char* alloc_site = nullptr; + unsigned char kind = CT_ALLOC_KIND_MMAP; + + if (ct_is_enabled(CT_FEATURE_ALLOC)) + { + ct_lock_acquire(); + (void)ct_remove_for_release(addr, CT_ENTRY_FREED, &size, &alloc_site, &kind); + ct_lock_release(); + ct_track_shadow_free(addr, size); + } + + const BOOL ok = addr ? VirtualFree(addr, 0, MEM_RELEASE) : TRUE; + if (!ok) + { + errno = EINVAL; + return -1; + } + + ct_log_alloc_event("munmap", addr, size, site ? site : alloc_site, CT_ALLOC_KIND_MMAP); + return 0; + } + + CT_NODISCARD CT_NOINSTR void* __ct_sbrk(size_t incr, const char* site) + { + ct_init_env_once(); + (void)incr; + (void)site; + errno = ENOSYS; + ct_log(CTLevel::Warn, "ct: sbrk is not supported on Windows\n"); + return reinterpret_cast(-1); + } + + CT_NODISCARD CT_NOINSTR void* __ct_brk(void* addr, const char* site) + { + ct_init_env_once(); + (void)addr; + (void)site; + errno = ENOSYS; + ct_log(CTLevel::Warn, "ct: brk is not supported on Windows\n"); + return reinterpret_cast(-1); + } + + CT_NOINSTR void __ct_autofree(void* ptr) + { + ct_autofree_impl(ptr, false); + } + + CT_NOINSTR void __ct_autofree_munmap(void* ptr) + { + ct_autofree_impl(ptr, false); + } + + CT_NOINSTR void __ct_autofree_sbrk(void* ptr) + { + ct_autofree_impl(ptr, false); + } + + CT_NOINSTR void __ct_autofree_delete(void* ptr) + { + ct_autofree_impl(ptr, false); + } + + CT_NOINSTR void __ct_autofree_delete_array(void* ptr) + { + ct_autofree_impl(ptr, true); + } + + CT_NOINSTR void __ct_free(void* ptr) + { + ct_init_env_once(); + if (!ct_is_enabled(CT_FEATURE_ALLOC)) + { + std::free(ptr); + return; + } + + if (!ptr) + { + ct_log_skip_event("free", ptr, "null"); + std::free(ptr); + return; + } + + size_t size = 0; + const char* site = nullptr; + unsigned char kind = CT_ALLOC_KIND_MALLOC; + + ct_lock_acquire(); + const int found = ct_remove_for_release(ptr, CT_ENTRY_FREED, &size, &site, &kind); + ct_lock_release(); + + if (found == -1) + { + ct_log_skip_event("free", ptr, "already freed"); + return; + } + if (found == 0) + { + ct_log_skip_event("free", ptr, "unknown"); + std::free(ptr); + return; + } + + ct_track_shadow_free(ptr, size); + ct_log_alloc_event("free", ptr, size, site, kind); + ct_release_memory(ptr, kind); + } + + CT_NOINSTR void __ct_delete(void* ptr) + { + ct_init_env_once(); + if (!ct_is_enabled(CT_FEATURE_ALLOC)) + { + ::operator delete(ptr); + return; + } + + size_t size = 0; + const char* site = nullptr; + unsigned char kind = CT_ALLOC_KIND_NEW; + + ct_lock_acquire(); + const int found = ct_remove_for_release(ptr, CT_ENTRY_FREED, &size, &site, &kind); + ct_lock_release(); + + if (found == -1) + { + ct_log_skip_event("delete", ptr, "already freed"); + return; + } + if (found == 0) + { + ct_log_skip_event("delete", ptr, "unknown"); + ct_release_unknown_delete(ptr, false); + return; + } + + ct_track_shadow_free(ptr, size); + ct_log_alloc_event("delete", ptr, size, site, kind); + ct_release_memory(ptr, kind); + } + + CT_NOINSTR void __ct_delete_array(void* ptr) + { + ct_init_env_once(); + if (!ct_is_enabled(CT_FEATURE_ALLOC)) + { + ::operator delete[](ptr); + return; + } + + size_t size = 0; + const char* site = nullptr; + unsigned char kind = CT_ALLOC_KIND_NEW_ARRAY; + + ct_lock_acquire(); + const int found = ct_remove_for_release(ptr, CT_ENTRY_FREED, &size, &site, &kind); + ct_lock_release(); + + if (found == -1) + { + ct_log_skip_event("delete[]", ptr, "already freed"); + return; + } + if (found == 0) + { + ct_log_skip_event("delete[]", ptr, "unknown"); + ct_release_unknown_delete(ptr, true); + return; + } + + ct_track_shadow_free(ptr, size); + ct_log_alloc_event("delete[]", ptr, size, site, kind); + ct_release_memory(ptr, kind); + } + + CT_NOINSTR void __ct_delete_nothrow(void* ptr) + { + __ct_delete(ptr); + } + + CT_NOINSTR void __ct_delete_array_nothrow(void* ptr) + { + __ct_delete_array(ptr); + } + + CT_NOINSTR void __ct_delete_destroying(void* ptr) + { + __ct_delete(ptr); + } + + CT_NOINSTR void __ct_delete_array_destroying(void* ptr) + { + __ct_delete_array(ptr); + } +} diff --git a/src/ThirdParty/coretrace-compiler/windows/ct_runtime_backtrace.cpp b/src/ThirdParty/coretrace-compiler/windows/ct_runtime_backtrace.cpp new file mode 100644 index 0000000..b5767d3 --- /dev/null +++ b/src/ThirdParty/coretrace-compiler/windows/ct_runtime_backtrace.cpp @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: Apache-2.0 +#include "ct_runtime_internal.h" + +#include +#include +#include +#include + +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#include + +#pragma comment(lib, "Dbghelp.lib") + +namespace +{ + std::atomic ct_backtrace_installed{0}; + std::once_flag ct_symbols_once; + + CT_NOINSTR void ct_ensure_symbols(void) + { + std::call_once(ct_symbols_once, [] + { + HANDLE process = GetCurrentProcess(); + SymSetOptions(SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); + (void)SymInitialize(process, nullptr, TRUE); + }); + } + + CT_NOINSTR void ct_write_stack_frame(HANDLE process, void* frame) + { + ct_write_prefix(CTLevel::Error); + ct_write_cstr(" at "); + ct_write_hex(reinterpret_cast(frame)); + + char symbol_buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME] = {}; + auto* symbol = reinterpret_cast(symbol_buffer); + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + symbol->MaxNameLen = MAX_SYM_NAME; + + DWORD64 displacement = 0; + if (SymFromAddr(process, reinterpret_cast(frame), &displacement, symbol) != FALSE) + { + ct_write_cstr(" "); + ct_write_cstr(symbol->Name); + } + + ct_write_cstr("\n"); + } + + CT_NOINSTR LONG WINAPI ct_exception_filter(EXCEPTION_POINTERS* exception_info) + { + ct_disable_logging(); + ct_ensure_symbols(); + + DWORD code = exception_info ? exception_info->ExceptionRecord->ExceptionCode + : static_cast(0xFFFFFFFFu); + + ct_write_prefix(CTLevel::Error); + ct_write_cstr("ct: fatal exception code="); + ct_write_hex(static_cast(code)); + ct_write_cstr("\n"); + + void* frames[64] = {}; + const USHORT count = + CaptureStackBackTrace(0, static_cast(std::size(frames)), frames, nullptr); + HANDLE process = GetCurrentProcess(); + for (USHORT index = 0; index < count; ++index) + { + ct_write_stack_frame(process, frames[index]); + } + + return EXCEPTION_EXECUTE_HANDLER; + } +} // namespace + +CT_NOINSTR void ct_maybe_install_backtrace(void) +{ + if (std::getenv("CT_BACKTRACE") == nullptr) + { + return; + } + + int expected = 0; + if (!ct_backtrace_installed.compare_exchange_strong(expected, 1, std::memory_order_acq_rel)) + { + return; + } + + ct_ensure_symbols(); + SetUnhandledExceptionFilter(ct_exception_filter); + + ct_write_prefix(CTLevel::Info); + ct_write_cstr("ct: backtrace handler installed\n"); +} diff --git a/src/ThirdParty/coretrace-compiler/windows/ct_runtime_env.cpp b/src/ThirdParty/coretrace-compiler/windows/ct_runtime_env.cpp new file mode 100644 index 0000000..ca82dca --- /dev/null +++ b/src/ThirdParty/coretrace-compiler/windows/ct_runtime_env.cpp @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: Apache-2.0 +#include "ct_runtime_internal.h" + +#include +#include + +#if defined(_M_IX86) +#define CT_ALTNAME(symbol, fallback) __pragma(comment(linker, "/alternatename:_" #symbol "=_" #fallback)) +#else +#define CT_ALTNAME(symbol, fallback) __pragma(comment(linker, "/alternatename:" #symbol "=" #fallback)) +#endif + +extern "C" +{ + __declspec(selectany) int __ct_config_shadow_default = 0; + __declspec(selectany) int __ct_config_shadow_aggressive_default = 0; + __declspec(selectany) int __ct_config_bounds_no_abort_default = 0; + __declspec(selectany) int __ct_config_disable_alloc_default = 0; + __declspec(selectany) int __ct_config_disable_autofree_default = 0; + __declspec(selectany) int __ct_config_disable_alloc_trace_default = 0; + __declspec(selectany) int __ct_config_vtable_diag_default = 0; + + CT_ALTNAME(__ct_config_shadow, __ct_config_shadow_default) + CT_ALTNAME(__ct_config_shadow_aggressive, __ct_config_shadow_aggressive_default) + CT_ALTNAME(__ct_config_bounds_no_abort, __ct_config_bounds_no_abort_default) + CT_ALTNAME(__ct_config_disable_alloc, __ct_config_disable_alloc_default) + CT_ALTNAME(__ct_config_disable_autofree, __ct_config_disable_autofree_default) + CT_ALTNAME(__ct_config_disable_alloc_trace, __ct_config_disable_alloc_trace_default) + CT_ALTNAME(__ct_config_vtable_diag, __ct_config_vtable_diag_default) + + extern int __ct_config_shadow; + extern int __ct_config_shadow_aggressive; + extern int __ct_config_bounds_no_abort; + extern int __ct_config_disable_alloc; + extern int __ct_config_disable_autofree; + extern int __ct_config_disable_alloc_trace; + extern int __ct_config_vtable_diag; +} + +namespace +{ + std::atomic ct_env_initialized{0}; + + CT_NOINSTR void ct_apply_compiled_config(void) + { + if (__ct_config_shadow || __ct_config_shadow_aggressive) + { + ct_set_enabled(CT_FEATURE_SHADOW, 1); + } + if (__ct_config_shadow_aggressive) + { + ct_set_enabled(CT_FEATURE_SHADOW_AGGR, 1); + } + if (__ct_config_bounds_no_abort) + { + ct_set_bounds_abort(0); + } + if (__ct_config_disable_alloc) + { + ct_set_enabled(CT_FEATURE_ALLOC, 0); + ct_alloc_disabled_by_config = 1; + } + if (__ct_config_disable_autofree) + { + ct_set_enabled(CT_FEATURE_AUTOFREE, 0); + } + if (__ct_config_disable_alloc_trace) + { + ct_set_enabled(CT_FEATURE_ALLOC_TRACE, 0); + } + if (__ct_config_vtable_diag) + { + ct_set_enabled(CT_FEATURE_VTABLE_DIAG, 1); + } + } + + CT_NOINSTR void ct_apply_env_config(void) + { + if (std::getenv("CT_DISABLE_TRACE") != nullptr) + { + ct_set_enabled(CT_FEATURE_TRACE, 0); + } + if (std::getenv("CT_DISABLE_ALLOC") != nullptr) + { + ct_set_enabled(CT_FEATURE_ALLOC, 0); + ct_alloc_disabled_by_env = 1; + } + if (std::getenv("CT_EARLY_TRACE") != nullptr) + { + ct_set_enabled(CT_FEATURE_EARLY_TRACE, 1); + } + if (std::getenv("CT_DISABLE_BOUNDS") != nullptr) + { + ct_set_enabled(CT_FEATURE_BOUNDS, 0); + } + if (std::getenv("CT_BOUNDS_NO_ABORT") != nullptr) + { + ct_set_bounds_abort(0); + } + if (std::getenv("CT_SHADOW") != nullptr) + { + ct_set_enabled(CT_FEATURE_SHADOW, 1); + } + if (std::getenv("CT_SHADOW_AGGRESSIVE") != nullptr) + { + ct_set_enabled(CT_FEATURE_SHADOW, 1); + ct_set_enabled(CT_FEATURE_SHADOW_AGGR, 1); + } + if (std::getenv("CT_DISABLE_AUTOFREE") != nullptr) + { + ct_set_enabled(CT_FEATURE_AUTOFREE, 0); + } + if (std::getenv("CT_DISABLE_ALLOC_TRACE") != nullptr) + { + ct_set_enabled(CT_FEATURE_ALLOC_TRACE, 0); + } + } + + struct CtRuntimeInit + { + CT_NOINSTR CtRuntimeInit() + { + ct_maybe_install_backtrace(); + ct_apply_compiled_config(); + ct_apply_env_config(); + } + }; + + CtRuntimeInit ct_runtime_init; +} // namespace + +CT_NOINSTR void ct_init_env_once(void) +{ + int expected = 0; + if (!ct_env_initialized.compare_exchange_strong(expected, 1, std::memory_order_acq_rel)) + { + return; + } + + ct_apply_compiled_config(); + ct_apply_env_config(); +} diff --git a/src/ThirdParty/coretrace-compiler/windows/ct_runtime_helpers.h b/src/ThirdParty/coretrace-compiler/windows/ct_runtime_helpers.h new file mode 100644 index 0000000..b715ac5 --- /dev/null +++ b/src/ThirdParty/coretrace-compiler/windows/ct_runtime_helpers.h @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#if defined(_WIN32) +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#include + +inline bool ct_demangle(const char* name, std::string& out) +{ + if (!name || name[0] == '\0') + { + return false; + } + + char buffer[1024]; + if (UnDecorateSymbolName(name, buffer, static_cast(sizeof(buffer)), UNDNAME_COMPLETE) == + 0) + { + return false; + } + + out.assign(buffer); + return true; +} +#else +#include +#include + +__attribute__((no_instrument_function)) inline bool ct_demangle(const char* name, std::string& out) +{ + if (!name) + { + return false; + } + if (!(name[0] == '_' && name[1] == 'Z')) + { + return false; + } + + int status = 0; + size_t length = 0; + char* demangled = abi::__cxa_demangle(name, nullptr, &length, &status); + if (status == 0 && demangled) + { + out.assign(demangled); + std::free(demangled); + return true; + } + if (demangled) + { + std::free(demangled); + } + return false; +} +#endif diff --git a/src/ThirdParty/coretrace-compiler/windows/ct_runtime_internal.h b/src/ThirdParty/coretrace-compiler/windows/ct_runtime_internal.h new file mode 100644 index 0000000..0fcc954 --- /dev/null +++ b/src/ThirdParty/coretrace-compiler/windows/ct_runtime_internal.h @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: Apache-2.0 +#ifndef CT_RUNTIME_INTERNAL_H +#define CT_RUNTIME_INTERNAL_H + +#include "compilerlib/attributes.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#include +#endif + +#if defined(_MSC_VER) +#define CT_NOINSTR + +#ifndef __ATOMIC_ACQUIRE +#define __ATOMIC_ACQUIRE 2 +#endif +#ifndef __ATOMIC_RELEASE +#define __ATOMIC_RELEASE 3 +#endif +#ifndef __ATOMIC_ACQ_REL +#define __ATOMIC_ACQ_REL 4 +#endif + +inline int ct_msvc_atomic_exchange(volatile int* object, int desired) +{ + return static_cast( + InterlockedExchange(reinterpret_cast(object), static_cast(desired))); +} + +inline void ct_msvc_atomic_store(volatile int* object, int desired) +{ + (void)InterlockedExchange(reinterpret_cast(object), + static_cast(desired)); +} + +inline bool ct_msvc_atomic_compare_exchange(volatile int* object, int* expected, int desired) +{ + const long prior = + InterlockedCompareExchange(reinterpret_cast(object), + static_cast(desired), static_cast(*expected)); + if (prior == static_cast(*expected)) + { + return true; + } + + *expected = static_cast(prior); + return false; +} + +#define __atomic_exchange_n(object, desired, order) \ + ct_msvc_atomic_exchange(reinterpret_cast(object), static_cast(desired)) +#define __atomic_store_n(object, desired, order) \ + ct_msvc_atomic_store(reinterpret_cast(object), static_cast(desired)) +#define __atomic_compare_exchange_n(object, expected, desired, weak, success_order, \ + failure_order) \ + ct_msvc_atomic_compare_exchange(reinterpret_cast(object), \ + reinterpret_cast(expected), static_cast(desired)) +#else +#define CT_NOINSTR __attribute__((no_instrument_function)) +#endif + +using CTColor = coretrace::Color; +using CTLevel = coretrace::Level; + +enum +{ + CT_ENTRY_EMPTY = 0, + CT_ENTRY_USED = 1, + CT_ENTRY_TOMB = 2, + CT_ENTRY_FREED = 3, + CT_ENTRY_AUTOFREED = 4 +}; + +extern int ct_disable_trace; +extern int ct_disable_alloc; +extern int ct_disable_bounds; +extern int ct_bounds_abort; +extern int ct_shadow_enabled; +extern int ct_shadow_aggressive; +extern int ct_autofree_enabled; +extern int ct_alloc_trace_enabled; +extern int ct_vtable_diag_enabled; +extern int ct_alloc_disabled_by_config; +extern int ct_alloc_disabled_by_env; +extern int ct_early_trace; +extern size_t ct_early_trace_count; +extern size_t ct_early_trace_limit; +extern thread_local const char* ct_current_site; + +#define CT_FEATURE_TRACE (1ull << 0) +#define CT_FEATURE_ALLOC (1ull << 1) +#define CT_FEATURE_BOUNDS (1ull << 2) +#define CT_FEATURE_SHADOW (1ull << 3) +#define CT_FEATURE_SHADOW_AGGR (1ull << 4) +#define CT_FEATURE_AUTOFREE (1ull << 5) +#define CT_FEATURE_ALLOC_TRACE (1ull << 6) +#define CT_FEATURE_VTABLE_DIAG (1ull << 7) +#define CT_FEATURE_EARLY_TRACE (1ull << 8) + +extern "C" +{ + CT_NODISCARD CT_NOINSTR int ct_is_enabled(uint64_t feature); + CT_NOINSTR void ct_set_enabled(uint64_t feature, int enabled); + CT_NODISCARD CT_NOINSTR uint64_t ct_get_features(void); + + CT_NODISCARD CT_NOINSTR int ct_bounds_abort_enabled(void); + CT_NOINSTR void ct_set_bounds_abort(int enabled); + + CT_NODISCARD CT_NOINSTR int ct_early_trace_should_log(void); +} + +CT_NODISCARD CT_NOINSTR size_t ct_strlen(const char* str); +CT_NODISCARD CT_NOINSTR int ct_streq(const char* lhs, const char* rhs); +CT_NODISCARD CT_NOINSTR const char* ct_site_name(const char* site); +CT_NOINSTR void ct_maybe_install_backtrace(void); +CT_NOINSTR void ct_init_env_once(void); +CT_NOINSTR void ct_lock_acquire(void); +CT_NOINSTR void ct_lock_release(void); +CT_NODISCARD CT_NOINSTR int ct_table_insert(void* ptr, size_t req_size, size_t size, + const char* site, unsigned char kind); +CT_NODISCARD CT_NOINSTR int ct_table_remove(void* ptr, size_t* size_out, size_t* req_size_out, + const char** site_out); +CT_NODISCARD CT_NOINSTR int ct_table_lookup(const void* ptr, size_t* size_out, size_t* req_size_out, + const char** site_out, unsigned char* state_out); +CT_NODISCARD CT_NOINSTR int ct_table_lookup_containing(const void* ptr, void** base_out, + size_t* size_out, size_t* req_size_out, + const char** site_out, + unsigned char* state_out); +CT_NOINSTR void ct_shadow_poison_range(const void* addr, size_t size); +CT_NOINSTR void ct_shadow_unpoison_range(const void* addr, size_t size); +CT_NODISCARD CT_NOINSTR int ct_shadow_check_access(const void* ptr, size_t access_size, + const void* base, size_t req_size, + size_t alloc_size, const char* alloc_site, + const char* site, int is_write, + unsigned char state); +CT_NOINSTR void ct_report_bounds_error(const void* base, const void* ptr, size_t access_size, + const char* site, int is_write, size_t req_size, + size_t alloc_size, const char* alloc_site, + unsigned char state); + +CT_NODISCARD CT_NOINSTR inline std::string_view ct_color(CTColor color) +{ + return coretrace::color(color); +} + +CT_NODISCARD CT_NOINSTR inline std::string_view ct_level_label(CTLevel level) +{ + return coretrace::level_label(level); +} + +CT_NODISCARD CT_NOINSTR inline std::string_view ct_level_color(CTLevel level) +{ + return coretrace::level_color(level); +} + +CT_NODISCARD CT_NOINSTR inline int ct_pid(void) +{ + return coretrace::pid(); +} + +CT_NODISCARD CT_NOINSTR inline unsigned long long ct_thread_id(void) +{ + return coretrace::thread_id(); +} + +CT_NODISCARD CT_NOINSTR inline int ct_log_is_enabled(void) +{ + return coretrace::log_is_enabled() ? 1 : 0; +} + +CT_NOINSTR inline void ct_enable_logging(void) +{ + coretrace::enable_logging(); +} + +CT_NOINSTR inline void ct_disable_logging(void) +{ + coretrace::disable_logging(); +} + +CT_NOINSTR inline void ct_write_raw(const char* data, size_t size) +{ + coretrace::write_raw(data, size); +} + +CT_NOINSTR inline void ct_write_str(std::string_view str) +{ + coretrace::write_str(str); +} + +CT_NOINSTR inline void ct_write_cstr(const char* str) +{ + if (!str) + { + return; + } + coretrace::write_str(std::string_view(str)); +} + +CT_NOINSTR inline void ct_write_dec(size_t value) +{ + coretrace::write_dec(value); +} + +CT_NOINSTR inline void ct_write_hex(uintptr_t value) +{ + coretrace::write_hex(value); +} + +CT_NOINSTR inline void ct_write_prefix(CTLevel level) +{ + coretrace::write_prefix(level); +} + +template +CT_NOINSTR inline void ct_log(CTLevel level, std::string_view fmt, Args&&... args) +{ + if (!coretrace::log_is_enabled()) + { + return; + } + + try + { + std::string msg = std::vformat(fmt, std::make_format_args(args...)); + if (msg.empty()) + { + return; + } + + coretrace::write_log_line(level, {}, msg, std::source_location::current()); + } + catch (...) + { + static constexpr char fallback[] = "ct: log format error\n"; + coretrace::write_raw(fallback, sizeof(fallback) - 1); + } +} + +#endif // CT_RUNTIME_INTERNAL_H diff --git a/src/ThirdParty/coretrace-compiler/windows/ct_runtime_vtable.cpp b/src/ThirdParty/coretrace-compiler/windows/ct_runtime_vtable.cpp new file mode 100644 index 0000000..b2af2a7 --- /dev/null +++ b/src/ThirdParty/coretrace-compiler/windows/ct_runtime_vtable.cpp @@ -0,0 +1,672 @@ +// SPDX-License-Identifier: Apache-2.0 +#include "ct_runtime_internal.h" + +#include "ct_runtime_helpers.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#include + +#pragma comment(lib, "Dbghelp.lib") + +namespace +{ + constexpr size_t kBoxMaxValueWidth = 60; + + struct CtRttiCompleteObjectLocator + { + std::uint32_t signature; + std::uint32_t offset; + std::uint32_t cd_offset; + std::int32_t type_descriptor_rva; + std::int32_t class_descriptor_rva; + std::int32_t self_rva; + }; + + struct CtRttiTypeDescriptor + { + const void* vftable; + void* spare; + char name[1]; + }; + + struct CtVtableInfo + { + const void* vtable = nullptr; + ptrdiff_t offset_to_top = 0; + std::string dynamic_type = ""; + bool has_typeinfo = false; + }; + + struct CtBoxLine + { + std::string label; + std::string value; + }; + + struct CtModuleInfo + { + bool resolved = false; + bool is_main = false; + bool exec_known = false; + bool is_exec = false; + HMODULE handle = nullptr; + std::string path; + std::string basename; + }; + + struct CtAddrInfo + { + bool has_module = false; + bool exec_known = false; + bool is_exec = false; + bool on_stack = false; + CtModuleInfo module; + }; + + std::once_flag ct_symbols_once; + std::atomic ct_vtable_state_logged{0}; + + CT_NOINSTR void ct_ensure_symbols(void) + { + std::call_once(ct_symbols_once, [] + { + HANDLE process = GetCurrentProcess(); + SymSetOptions(SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); + (void)SymInitialize(process, nullptr, TRUE); + }); + } + + CT_NODISCARD CT_NOINSTR std::string ct_basename(std::string_view path) + { + if (path.empty()) + { + return {}; + } + + const size_t pos = path.find_last_of("/\\"); + if (pos == std::string_view::npos) + { + return std::string(path); + } + return std::string(path.substr(pos + 1)); + } + + CT_NODISCARD CT_NOINSTR bool ct_is_executable_protection(DWORD protect) + { + const DWORD normalized = protect & 0xFFu; + return normalized == PAGE_EXECUTE || normalized == PAGE_EXECUTE_READ || + normalized == PAGE_EXECUTE_READWRITE || normalized == PAGE_EXECUTE_WRITECOPY; + } + + CT_NODISCARD CT_NOINSTR bool ct_is_readable(const void* addr, size_t size) + { + if (!addr || size == 0) + { + return false; + } + + MEMORY_BASIC_INFORMATION mbi = {}; + if (VirtualQuery(addr, &mbi, sizeof(mbi)) == 0) + { + return false; + } + + if (mbi.State != MEM_COMMIT) + { + return false; + } + if ((mbi.Protect & PAGE_NOACCESS) != 0 || (mbi.Protect & PAGE_GUARD) != 0) + { + return false; + } + + const uintptr_t start = reinterpret_cast(addr); + const uintptr_t end = start + size; + const uintptr_t region_end = reinterpret_cast(mbi.BaseAddress) + mbi.RegionSize; + return end >= start && end <= region_end; + } + + CT_NODISCARD CT_NOINSTR bool ct_lookup_symbol_name(const void* addr, std::string& out) + { + if (!addr) + { + return false; + } + + ct_ensure_symbols(); + + char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME] = {}; + auto* symbol = reinterpret_cast(buffer); + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + symbol->MaxNameLen = MAX_SYM_NAME; + + DWORD64 displacement = 0; + if (SymFromAddr(GetCurrentProcess(), reinterpret_cast(addr), &displacement, symbol) == + FALSE) + { + return false; + } + + out.assign(symbol->Name); + return true; + } + + CT_NODISCARD CT_NOINSTR bool ct_address_on_stack(const void* addr) + { + if (!addr) + { + return false; + } + + using GetCurrentThreadStackLimitsFn = VOID(WINAPI*)(PULONG_PTR, PULONG_PTR); + static auto* fn = reinterpret_cast( + GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "GetCurrentThreadStackLimits")); + if (!fn) + { + return false; + } + + ULONG_PTR low = 0; + ULONG_PTR high = 0; + fn(&low, &high); + + const auto value = reinterpret_cast(addr); + return value >= low && value < high; + } + + CT_NODISCARD CT_NOINSTR bool ct_resolve_module(const void* addr, CtModuleInfo& out) + { + if (!addr) + { + return false; + } + + MEMORY_BASIC_INFORMATION mbi = {}; + if (VirtualQuery(addr, &mbi, sizeof(mbi)) != 0) + { + out.exec_known = true; + out.is_exec = ct_is_executable_protection(mbi.Protect); + } + + HMODULE module = nullptr; + if (!GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast(addr), &module)) + { + return false; + } + + char path_buffer[MAX_PATH] = {}; + const DWORD length = GetModuleFileNameA(module, path_buffer, MAX_PATH); + + out.resolved = true; + out.handle = module; + out.is_main = (module == GetModuleHandleA(nullptr)); + if (length != 0) + { + out.path.assign(path_buffer, length); + out.basename = ct_basename(out.path); + } + return true; + } + + CT_NODISCARD CT_NOINSTR CtAddrInfo ct_resolve_address(const void* addr) + { + CtAddrInfo info; + if (!addr) + { + return info; + } + + if (ct_resolve_module(addr, info.module)) + { + info.has_module = true; + info.exec_known = info.module.exec_known; + info.is_exec = info.module.is_exec; + return info; + } + + info.on_stack = ct_address_on_stack(addr); + if (info.on_stack) + { + info.exec_known = true; + info.is_exec = false; + } + return info; + } + + CT_NODISCARD CT_NOINSTR bool ct_modules_match(const CtModuleInfo& lhs, const CtModuleInfo& rhs) + { + return lhs.resolved && rhs.resolved && lhs.handle != nullptr && lhs.handle == rhs.handle; + } + + CT_NODISCARD CT_NOINSTR std::string ct_module_display_name(const CtModuleInfo& info) + { + if (!info.resolved) + { + return ""; + } + if (info.is_main) + { + return "main"; + } + if (!info.basename.empty()) + { + return info.basename; + } + if (!info.path.empty()) + { + return info.path; + } + return ""; + } + + CT_NODISCARD CT_NOINSTR const CtRttiCompleteObjectLocator* + ct_get_complete_object_locator(const void* vtable) + { + if (!vtable) + { + return nullptr; + } + + auto* table = reinterpret_cast(vtable); + const void* locator_ptr = table[-1]; + if (!ct_is_readable(locator_ptr, sizeof(CtRttiCompleteObjectLocator))) + { + return nullptr; + } + + return reinterpret_cast(locator_ptr); + } + + CT_NODISCARD CT_NOINSTR const CtRttiTypeDescriptor* + ct_get_type_descriptor(const CtRttiCompleteObjectLocator* locator) + { + if (!locator) + { + return nullptr; + } + + const uintptr_t image_base = + reinterpret_cast(locator) - static_cast(locator->self_rva); + const uintptr_t type_addr = + image_base + static_cast(locator->type_descriptor_rva); + if (!ct_is_readable(reinterpret_cast(type_addr), sizeof(CtRttiTypeDescriptor))) + { + return nullptr; + } + + return reinterpret_cast(type_addr); + } + + CT_NODISCARD CT_NOINSTR std::string ct_format_type_name(const CtRttiTypeDescriptor* type_descriptor) + { + if (!type_descriptor || !ct_is_readable(type_descriptor, sizeof(CtRttiTypeDescriptor))) + { + return ""; + } + + const char* raw_name = type_descriptor->name; + if (!raw_name || raw_name[0] == '\0') + { + return ""; + } + + std::string demangled; + if (ct_demangle(raw_name, demangled)) + { + return demangled; + } + + return raw_name; + } + + CT_NODISCARD CT_NOINSTR bool ct_read_vtable_info(void* this_ptr, CtVtableInfo& info) + { + if (!this_ptr) + { + return false; + } + + const void* vtable = *reinterpret_cast(this_ptr); + if (!vtable) + { + return false; + } + + info.vtable = vtable; + + const CtRttiCompleteObjectLocator* locator = ct_get_complete_object_locator(vtable); + if (!locator) + { + return true; + } + + info.offset_to_top = static_cast(locator->offset); + info.dynamic_type = ct_format_type_name(ct_get_type_descriptor(locator)); + info.has_typeinfo = info.dynamic_type != ""; + return true; + } + + CT_NODISCARD CT_NOINSTR bool ct_is_unknown_type(const char* type_name) + { + return !type_name || type_name[0] == '\0' || ct_streq(type_name, ""); + } + + CT_NOINSTR void ct_append_box_line(std::vector& lines, std::string label, + std::string value) + { + lines.push_back({std::move(label), std::move(value)}); + } + + CT_NOINSTR void ct_log_box(CTLevel level, const char* tag, const char* title, + const std::vector& lines) + { + if (lines.empty()) + { + return; + } + + size_t label_width = 0; + size_t value_width = 0; + for (const auto& line : lines) + { + label_width = std::max(label_width, line.label.size()); + value_width = std::max(value_width, std::min(line.value.size(), kBoxMaxValueWidth)); + } + value_width = std::max(value_width, 1); + + const size_t inner_width = label_width + value_width + 5; + std::string border(inner_width, '-'); + + ct_log(level, "[{}]\n", tag ? tag : "BOX"); + ct_log(level, "+{}+\n", border); + ct_log(level, "| {}{}\n", title ? title : "box", + std::string(inner_width > ct_strlen(title ? title : "box") + 1 + ? inner_width - ct_strlen(title ? title : "box") - 1 + : 0, + ' ')); + ct_log(level, "+{}+\n", border); + + for (const auto& line : lines) + { + std::string value = line.value.empty() ? "" : line.value; + size_t offset = 0; + bool first = true; + while (offset < value.size()) + { + const size_t remaining = value.size() - offset; + const size_t chunk = std::min(remaining, value_width); + std::string part = value.substr(offset, chunk); + + std::string label = first ? line.label : std::string(); + if (label.size() < label_width) + { + label.append(label_width - label.size(), ' '); + } + if (part.size() < value_width) + { + part.append(value_width - part.size(), ' '); + } + + ct_log(level, "| {} : {} |\n", label, part); + offset += chunk; + first = false; + } + } + + ct_log(level, "+{}+\n", border); + } + + CT_NOINSTR void ct_log_vtable_diag_state(void) + { + if (!ct_is_enabled(CT_FEATURE_VTABLE_DIAG)) + { + return; + } + + int expected = 0; + if (!ct_vtable_state_logged.compare_exchange_strong(expected, 1, std::memory_order_acq_rel)) + { + return; + } + + if (ct_is_enabled(CT_FEATURE_ALLOC)) + { + ct_log(CTLevel::Info, "[VTABLE-DIAG]: alloc-tracking=enabled\n"); + return; + } + + std::string reason = "unknown"; + if (ct_alloc_disabled_by_env) + { + reason = "env CT_DISABLE_ALLOC"; + } + else if (ct_alloc_disabled_by_config) + { + reason = "compile-time --ct-no-alloc/--ct-modules"; + } + ct_log(CTLevel::Info, "[VTABLE-DIAG]: alloc-tracking=disabled (reason={})\n", reason); + } +} // namespace + +extern "C" +{ + CT_NOINSTR void __ct_vtable_dump(void* this_ptr, const char* site, const char* static_type) + { + ct_init_env_once(); + if (!ct_log_is_enabled()) + { + ct_enable_logging(); + ct_maybe_install_backtrace(); + } + ct_log_vtable_diag_state(); + + CtVtableInfo info; + const bool has_vtable = ct_read_vtable_info(this_ptr, info); + const char* site_name = ct_site_name(site); + + std::vector lines; + ct_append_box_line(lines, "site", site_name ? site_name : ""); + ct_append_box_line(lines, "this", + this_ptr ? std::format("{:p}", this_ptr) : std::string("")); + ct_append_box_line(lines, "vtable", + (has_vtable && info.vtable) ? std::format("{:p}", info.vtable) + : std::string("")); + if (has_vtable) + { + ct_append_box_line(lines, "off_top", std::to_string(info.offset_to_top)); + } + ct_append_box_line(lines, "type", info.dynamic_type); + if (ct_is_enabled(CT_FEATURE_VTABLE_DIAG) && !ct_is_unknown_type(static_type)) + { + ct_append_box_line(lines, "static", static_type); + } + + std::vector warnings; + if (!this_ptr) + { + warnings.push_back("null this pointer"); + } + if (!has_vtable) + { + warnings.push_back("no vptr"); + } + if (has_vtable && !info.has_typeinfo) + { + warnings.push_back("missing RTTI"); + } + + if (has_vtable) + { + const CtAddrInfo vtable_addr = ct_resolve_address(info.vtable); + if (vtable_addr.has_module) + { + ct_append_box_line(lines, "vmod", ct_module_display_name(vtable_addr.module)); + } + else + { + warnings.push_back("vtable resolve failed"); + } + } + + if (ct_is_enabled(CT_FEATURE_ALLOC)) + { + unsigned char state = 0; + ct_lock_acquire(); + const int found = + ct_table_lookup_containing(this_ptr, nullptr, nullptr, nullptr, nullptr, &state); + ct_lock_release(); + if (found && state == CT_ENTRY_FREED) + { + warnings.push_back("vptr on freed object"); + } + } + + if (!ct_is_unknown_type(static_type) && info.dynamic_type != "" && + info.dynamic_type != static_type) + { + warnings.push_back("static!=dynamic type"); + } + + for (const auto& warning : warnings) + { + ct_append_box_line(lines, "warn", warning); + } + + ct_log_box(warnings.empty() ? CTLevel::Info : CTLevel::Warn, "VTABLE", "vtable", lines); + } + + CT_NOINSTR void __ct_vcall_trace(void* this_ptr, void* target, const char* site, + const char* static_type) + { + ct_init_env_once(); + if (!ct_log_is_enabled()) + { + ct_enable_logging(); + ct_maybe_install_backtrace(); + } + ct_log_vtable_diag_state(); + + CtVtableInfo info; + const bool has_vtable = ct_read_vtable_info(this_ptr, info); + const char* site_name = ct_site_name(site); + + std::string symbol_name = ""; + std::string demangled_name = ""; + if (target && ct_lookup_symbol_name(target, symbol_name)) + { + std::string pretty; + if (ct_demangle(symbol_name.c_str(), pretty)) + { + demangled_name = pretty; + } + else + { + demangled_name = symbol_name; + } + } + + std::vector lines; + ct_append_box_line(lines, "site", site_name ? site_name : ""); + ct_append_box_line(lines, "this", + this_ptr ? std::format("{:p}", this_ptr) : std::string("")); + ct_append_box_line(lines, "vtable", + (has_vtable && info.vtable) ? std::format("{:p}", info.vtable) + : std::string("")); + ct_append_box_line(lines, "type", info.dynamic_type); + ct_append_box_line(lines, "target", + target ? std::format("{:p}", target) : std::string("")); + ct_append_box_line(lines, "symbol", symbol_name); + ct_append_box_line(lines, "demangled", demangled_name); + if (ct_is_enabled(CT_FEATURE_VTABLE_DIAG) && !ct_is_unknown_type(static_type)) + { + ct_append_box_line(lines, "static", static_type); + } + + std::vector warnings; + if (!this_ptr) + { + warnings.push_back("null this pointer"); + } + if (!has_vtable) + { + warnings.push_back("no vptr"); + } + if (has_vtable && !info.has_typeinfo) + { + warnings.push_back("missing RTTI"); + } + + const CtAddrInfo vtable_addr = has_vtable ? ct_resolve_address(info.vtable) : CtAddrInfo{}; + const CtAddrInfo target_addr = target ? ct_resolve_address(target) : CtAddrInfo{}; + + if (vtable_addr.has_module) + { + ct_append_box_line(lines, "vmod", ct_module_display_name(vtable_addr.module)); + } + else if (has_vtable) + { + warnings.push_back("vtable resolve failed"); + } + + if (target_addr.has_module) + { + ct_append_box_line(lines, "tmod", ct_module_display_name(target_addr.module)); + } + else if (target_addr.exec_known && !target_addr.is_exec) + { + warnings.push_back("target in non-exec memory"); + } + else if (target_addr.on_stack) + { + warnings.push_back("target points to stack memory"); + } + + if (ct_is_enabled(CT_FEATURE_ALLOC)) + { + unsigned char state = 0; + ct_lock_acquire(); + const int found = + ct_table_lookup_containing(this_ptr, nullptr, nullptr, nullptr, nullptr, &state); + ct_lock_release(); + if (found && state == CT_ENTRY_FREED) + { + warnings.push_back("vptr on freed object"); + } + } + + if (!ct_is_unknown_type(static_type) && info.dynamic_type != "" && + info.dynamic_type != static_type) + { + warnings.push_back("static!=dynamic type"); + } + + if (vtable_addr.has_module && target_addr.has_module && + !ct_modules_match(vtable_addr.module, target_addr.module)) + { + warnings.push_back("module mismatch between vtable and target"); + } + + for (const auto& warning : warnings) + { + ct_append_box_line(lines, "warn", warning); + } + + ct_log_box(warnings.empty() ? CTLevel::Info : CTLevel::Warn, "VCALL", "vcall", lines); + } +} diff --git a/src/ThirdParty/coretrace-stack-analyzer/windows/mangle.cpp b/src/ThirdParty/coretrace-stack-analyzer/windows/mangle.cpp new file mode 100644 index 0000000..33021c2 --- /dev/null +++ b/src/ThirdParty/coretrace-stack-analyzer/windows/mangle.cpp @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: Apache-2.0 +#include "mangle.hpp" + +namespace ctrace_tools +{ + std::string mangleFunction(const std::string& namespaceName, const std::string& functionName, + const std::vector& paramTypes) + { + std::stringstream mangled; + + mangled << "_Z"; + + if (!namespaceName.empty()) + { + mangled << "N"; + mangled << namespaceName.length() << namespaceName; + } + + mangled << functionName.length() << functionName; + + for (const std::string& param : paramTypes) + { + if (param == "int") + { + mangled << "i"; + } + else if (param == "double") + { + mangled << "d"; + } + else if (param == "char") + { + mangled << "c"; + } + else if (param == "std::string") + { + mangled << "Ss"; + } + else if (param == "float") + { + mangled << "f"; + } + else if (param == "bool") + { + mangled << "b"; + } + else if (param == "void") + { + mangled << "v"; + } + else + { + mangled << param.length() << param; + } + } + + if (!namespaceName.empty()) + { + mangled << "E"; + } + + return mangled.str(); + } + + std::string demangle(const char* name) + { + if (name == nullptr) + { + return {}; + } + + return llvm::demangle(name); + } + + std::string canonicalizeMangledName(std::string_view name) + { + std::string result(name); + + for (std::size_t pos = 0; (pos = result.find("St3__1", pos)) != std::string::npos;) + { + result.replace(pos, 6, "St"); + } + + for (std::size_t pos = 0; (pos = result.find("St7__cxx11", pos)) != std::string::npos;) + { + result.replace(pos, 10, "St"); + } + + return result; + } +} // namespace ctrace_tools diff --git a/src/ThirdParty/coretrace-stack-analyzer/windows/mangle.hpp b/src/ThirdParty/coretrace-stack-analyzer/windows/mangle.hpp new file mode 100644 index 0000000..a448317 --- /dev/null +++ b/src/ThirdParty/coretrace-stack-analyzer/windows/mangle.hpp @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace ctrace_tools +{ + template concept StringLike = std::convertible_to; + + [[nodiscard]] inline bool isMangled(StringLike auto name) noexcept + { + const std::string_view sv{name}; + if (sv.empty()) + { + return false; + } + + // Itanium names usually start with _Z, while MSVC names commonly + // start with ? or ??. Use a cheap prefix check before calling into + // LLVM's demangler. + const bool looksMangled = + sv.starts_with("_Z") || sv.starts_with("?") || sv.starts_with(".?"); + if (!looksMangled) + { + return false; + } + + return llvm::demangle(sv) != sv; + } + + [[nodiscard]] std::string mangleFunction(const std::string& namespaceName, + const std::string& functionName, + const std::vector& paramTypes); + + [[nodiscard]] std::string demangle(const char* name); + + [[nodiscard]] std::string canonicalizeMangledName(std::string_view name); +} // namespace ctrace_tools From e54b1dbd8983e255b46fd4085f9cc64da5ce61c0 Mon Sep 17 00:00:00 2001 From: shookapic Date: Fri, 10 Apr 2026 18:03:05 +0900 Subject: [PATCH 2/6] Delete: Windows port patch for third party repos --- .github/workflows/build.yml | 122 +++ .github/workflows/release-binaries.yml | 156 ++- README.md | 8 +- cmake/logger/coretraceLog.cmake | 18 +- cmake/stackUsageAnalyzer.cmake | 265 +----- docs/windows-port.md | 88 -- scripts/build-windows.ps1 | 21 + src/ThirdParty/CoretraceLoggerWindows.cpp | 567 ----------- .../windows/ct_runtime_alloc.cpp | 901 ------------------ .../windows/ct_runtime_backtrace.cpp | 97 -- .../windows/ct_runtime_env.cpp | 142 --- .../windows/ct_runtime_helpers.h | 61 -- .../windows/ct_runtime_internal.h | 254 ----- .../windows/ct_runtime_vtable.cpp | 672 ------------- .../windows/mangle.cpp | 91 -- .../windows/mangle.hpp | 44 - 16 files changed, 316 insertions(+), 3191 deletions(-) delete mode 100644 docs/windows-port.md delete mode 100644 src/ThirdParty/CoretraceLoggerWindows.cpp delete mode 100644 src/ThirdParty/coretrace-compiler/windows/ct_runtime_alloc.cpp delete mode 100644 src/ThirdParty/coretrace-compiler/windows/ct_runtime_backtrace.cpp delete mode 100644 src/ThirdParty/coretrace-compiler/windows/ct_runtime_env.cpp delete mode 100644 src/ThirdParty/coretrace-compiler/windows/ct_runtime_helpers.h delete mode 100644 src/ThirdParty/coretrace-compiler/windows/ct_runtime_internal.h delete mode 100644 src/ThirdParty/coretrace-compiler/windows/ct_runtime_vtable.cpp delete mode 100644 src/ThirdParty/coretrace-stack-analyzer/windows/mangle.cpp delete mode 100644 src/ThirdParty/coretrace-stack-analyzer/windows/mangle.hpp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cbb8abd..83c4cf8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -85,3 +85,125 @@ jobs: run: | set -euo pipefail ./build/ctrace -h + + build-windows: + name: Build on Windows (pinned LLVM 20.1.0) + runs-on: windows-2022 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Resolve dependency ref + shell: pwsh + run: | + $ErrorActionPreference = "Stop" + $ref = if ($env:GITHUB_HEAD_REF) { $env:GITHUB_HEAD_REF } else { $env:GITHUB_REF_NAME } + "DEPENDENCY_REF=$ref" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + + - name: Checkout coretrace-compiler matching ref + id: checkout_compiler_ref + continue-on-error: true + uses: actions/checkout@v4 + with: + repository: CoreTrace/coretrace-compiler + ref: ${{ env.DEPENDENCY_REF }} + path: deps/coretrace-compiler + fetch-depth: 1 + + - name: Checkout coretrace-compiler main fallback + if: steps.checkout_compiler_ref.outcome == 'failure' + uses: actions/checkout@v4 + with: + repository: CoreTrace/coretrace-compiler + ref: main + path: deps/coretrace-compiler + fetch-depth: 1 + + - name: Checkout coretrace-stack-analyzer matching ref + id: checkout_stack_ref + continue-on-error: true + uses: actions/checkout@v4 + with: + repository: CoreTrace/coretrace-stack-analyzer + ref: ${{ env.DEPENDENCY_REF }} + path: deps/coretrace-stack-analyzer + fetch-depth: 1 + + - name: Checkout coretrace-stack-analyzer main fallback + if: steps.checkout_stack_ref.outcome == 'failure' + uses: actions/checkout@v4 + with: + repository: CoreTrace/coretrace-stack-analyzer + ref: main + path: deps/coretrace-stack-analyzer + fetch-depth: 1 + + - name: Checkout coretrace-log matching ref + id: checkout_logger_ref + continue-on-error: true + uses: actions/checkout@v4 + with: + repository: CoreTrace/coretrace-log + ref: ${{ env.DEPENDENCY_REF }} + path: deps/coretrace-log + fetch-depth: 1 + + - name: Checkout coretrace-log main fallback + if: steps.checkout_logger_ref.outcome == 'failure' + uses: actions/checkout@v4 + with: + repository: CoreTrace/coretrace-log + ref: main + path: deps/coretrace-log + fetch-depth: 1 + + - name: Install LLVM 20.1.0 to fixed location + shell: pwsh + run: | + $ErrorActionPreference = "Stop" + $llvmVersion = "20.1.0" + $installerUrl = "https://github.com/llvm/llvm-project/releases/download/llvmorg-$llvmVersion/LLVM-$llvmVersion-win64.exe" + $installerPath = "$env:RUNNER_TEMP\LLVM-$llvmVersion-win64.exe" + $installRoot = "C:\Program Files\LLVM-$llvmVersion" + $llvmCmakeDir = "$installRoot\lib\cmake\llvm" + $clangCmakeDir = "$installRoot\lib\cmake\clang" + + Invoke-WebRequest -Uri $installerUrl -OutFile $installerPath + Start-Process -FilePath $installerPath -ArgumentList @("/S", "/D=$installRoot") -Wait -NoNewWindow + + if (-not (Test-Path "$installRoot\bin\clang-cl.exe")) { + throw "clang-cl.exe not found after LLVM install: $installRoot\bin\clang-cl.exe" + } + if (-not (Test-Path $llvmCmakeDir)) { + throw "LLVM CMake package dir not found: $llvmCmakeDir" + } + if (-not (Test-Path $clangCmakeDir)) { + throw "Clang CMake package dir not found: $clangCmakeDir" + } + + $clangVersion = & "$installRoot\bin\clang-cl.exe" --version + if ($clangVersion -notmatch "clang version 20\.1\.0") { + throw "Unexpected clang-cl version. Got: $clangVersion" + } + + "LLVM_CMAKE_DIR=$llvmCmakeDir" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + "LLVM_DIR=$llvmCmakeDir" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + "Clang_DIR=$clangCmakeDir" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + + - name: Configure + Build + Install + shell: pwsh + run: | + $ErrorActionPreference = "Stop" + powershell -ExecutionPolicy Bypass -File .\scripts\build-windows.ps1 ` + -LLVMDir "${env:LLVM_CMAKE_DIR}" ` + -CompilerSourceDir "${PWD}\deps\coretrace-compiler" ` + -StackAnalyzerSourceDir "${PWD}\deps\coretrace-stack-analyzer" ` + -LoggerSourceDir "${PWD}\deps\coretrace-log" ` + -Configuration Release + + - name: Smoke test + shell: pwsh + run: | + $ErrorActionPreference = "Stop" + .\dist\windows\bin\ctrace.exe -h diff --git a/.github/workflows/release-binaries.yml b/.github/workflows/release-binaries.yml index d07a94b..7cb8e5c 100644 --- a/.github/workflows/release-binaries.yml +++ b/.github/workflows/release-binaries.yml @@ -100,10 +100,161 @@ jobs: dist/${{ matrix.arch }}/*.sha256 if-no-files-found: error + build-windows-binary: + name: Build Windows binary (x64, LLVM 20.1.0) + runs-on: windows-2022 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Resolve dependency ref + shell: pwsh + run: | + $ErrorActionPreference = "Stop" + $ref = if ($env:GITHUB_HEAD_REF) { $env:GITHUB_HEAD_REF } else { $env:GITHUB_REF_NAME } + "DEPENDENCY_REF=$ref" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + + - name: Checkout coretrace-compiler matching ref + id: checkout_compiler_ref + continue-on-error: true + uses: actions/checkout@v4 + with: + repository: CoreTrace/coretrace-compiler + ref: ${{ env.DEPENDENCY_REF }} + path: deps/coretrace-compiler + fetch-depth: 1 + + - name: Checkout coretrace-compiler main fallback + if: steps.checkout_compiler_ref.outcome == 'failure' + uses: actions/checkout@v4 + with: + repository: CoreTrace/coretrace-compiler + ref: main + path: deps/coretrace-compiler + fetch-depth: 1 + + - name: Checkout coretrace-stack-analyzer matching ref + id: checkout_stack_ref + continue-on-error: true + uses: actions/checkout@v4 + with: + repository: CoreTrace/coretrace-stack-analyzer + ref: ${{ env.DEPENDENCY_REF }} + path: deps/coretrace-stack-analyzer + fetch-depth: 1 + + - name: Checkout coretrace-stack-analyzer main fallback + if: steps.checkout_stack_ref.outcome == 'failure' + uses: actions/checkout@v4 + with: + repository: CoreTrace/coretrace-stack-analyzer + ref: main + path: deps/coretrace-stack-analyzer + fetch-depth: 1 + + - name: Checkout coretrace-log matching ref + id: checkout_logger_ref + continue-on-error: true + uses: actions/checkout@v4 + with: + repository: CoreTrace/coretrace-log + ref: ${{ env.DEPENDENCY_REF }} + path: deps/coretrace-log + fetch-depth: 1 + + - name: Checkout coretrace-log main fallback + if: steps.checkout_logger_ref.outcome == 'failure' + uses: actions/checkout@v4 + with: + repository: CoreTrace/coretrace-log + ref: main + path: deps/coretrace-log + fetch-depth: 1 + + - name: Resolve artifact version + id: version + shell: bash + run: | + set -euo pipefail + short_sha="$(git rev-parse --short=12 HEAD)" + if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then + version="${GITHUB_REF_NAME}" + else + version="sha-${short_sha}" + fi + echo "value=${version}" >> "${GITHUB_OUTPUT}" + + - name: Install LLVM 20.1.0 to fixed location + shell: pwsh + run: | + $ErrorActionPreference = "Stop" + $llvmVersion = "20.1.0" + $installerUrl = "https://github.com/llvm/llvm-project/releases/download/llvmorg-$llvmVersion/LLVM-$llvmVersion-win64.exe" + $installerPath = "$env:RUNNER_TEMP\LLVM-$llvmVersion-win64.exe" + $installRoot = "C:\Program Files\LLVM-$llvmVersion" + $llvmCmakeDir = "$installRoot\lib\cmake\llvm" + $clangCmakeDir = "$installRoot\lib\cmake\clang" + + Invoke-WebRequest -Uri $installerUrl -OutFile $installerPath + Start-Process -FilePath $installerPath -ArgumentList @("/S", "/D=$installRoot") -Wait -NoNewWindow + + if (-not (Test-Path "$installRoot\bin\clang-cl.exe")) { + throw "clang-cl.exe not found after LLVM install: $installRoot\bin\clang-cl.exe" + } + if (-not (Test-Path $llvmCmakeDir)) { + throw "LLVM CMake package dir not found: $llvmCmakeDir" + } + if (-not (Test-Path $clangCmakeDir)) { + throw "Clang CMake package dir not found: $clangCmakeDir" + } + + $clangVersion = & "$installRoot\bin\clang-cl.exe" --version + if ($clangVersion -notmatch "clang version 20\.1\.0") { + throw "Unexpected clang-cl version. Got: $clangVersion" + } + + "LLVM_CMAKE_DIR=$llvmCmakeDir" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + "LLVM_DIR=$llvmCmakeDir" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + "Clang_DIR=$clangCmakeDir" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + + - name: Build packaged binary + shell: pwsh + run: | + $ErrorActionPreference = "Stop" + powershell -ExecutionPolicy Bypass -File .\scripts\build-windows.ps1 ` + -LLVMDir "${env:LLVM_CMAKE_DIR}" ` + -CompilerSourceDir "${PWD}\deps\coretrace-compiler" ` + -StackAnalyzerSourceDir "${PWD}\deps\coretrace-stack-analyzer" ` + -LoggerSourceDir "${PWD}\deps\coretrace-log" ` + -Configuration Release ` + -VersionOverride "${{ steps.version.outputs.value }}" ` + -PackageZip + + - name: Stage release assets + shell: pwsh + run: | + $ErrorActionPreference = "Stop" + New-Item -ItemType Directory -Force -Path dist/windows | Out-Null + $zipFile = "coretrace-windows-Release.zip" + if (-not (Test-Path $zipFile)) { + throw "missing archive: $zipFile" + } + Copy-Item -LiteralPath $zipFile -Destination "dist/windows/${{ env.RELEASE_BASENAME }}-${{ steps.version.outputs.value }}-windows-amd64.zip" -Force + + - name: Upload workflow artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ env.RELEASE_BASENAME }}-${{ steps.version.outputs.value }}-windows-amd64 + path: dist/windows/*.zip + if-no-files-found: error + publish-release-assets: name: Attach binaries to GitHub Release runs-on: ubuntu-24.04 - needs: build-linux-binaries + needs: + - build-linux-binaries + - build-windows-binary if: startsWith(github.ref, 'refs/tags/v') permissions: contents: write @@ -112,7 +263,7 @@ jobs: - name: Download build artifacts uses: actions/download-artifact@v4 with: - pattern: ${{ env.RELEASE_BASENAME }}-*-linux-* + pattern: ${{ env.RELEASE_BASENAME }}-* path: dist merge-multiple: true @@ -128,5 +279,6 @@ jobs: files: | dist/*.tar.gz dist/*.sha256 + dist/*.zip fail_on_unmatched_files: true generate_release_notes: true diff --git a/README.md b/README.md index 7d5f5f5..53d7b29 100644 --- a/README.md +++ b/README.md @@ -21,15 +21,19 @@ make -j4 ### WINDOWS -CoreTrace can be built as a native `ctrace.exe` on Windows. The recommended path is documented in [`docs/windows-port.md`](docs/windows-port.md) and automated with [`scripts/build-windows.ps1`](scripts/build-windows.ps1). +CoreTrace can be built as a native `ctrace.exe` on Windows with [`scripts/build-windows.ps1`](scripts/build-windows.ps1). Example: ```powershell powershell -ExecutionPolicy Bypass -File .\scripts\build-windows.ps1 ` - -LLVMDir "C:\Program Files\LLVM\lib\cmake\llvm" ` + -LLVMDir "C:\LLVM\lib\cmake\llvm" ` + -CompilerSourceDir "C:\Users\shookapic\Documents\coretrace-compiler" ` + -StackAnalyzerSourceDir "C:\Users\shookapic\Documents\coretrace-stack-analyzer" ` + -LoggerSourceDir "C:\Users\shookapic\Documents\coretrace-log" ` -PackageZip ``` +! Change **-LLVMDIR** according to your LLVM installation path ! ### CODE STYLE (clang-format) diff --git a/cmake/logger/coretraceLog.cmake b/cmake/logger/coretraceLog.cmake index 79e3c2d..9e21c1d 100644 --- a/cmake/logger/coretraceLog.cmake +++ b/cmake/logger/coretraceLog.cmake @@ -4,25 +4,17 @@ set(CORETRACE_LOGGER_BUILD_TESTS OFF CACHE BOOL "Disable logger tests" FORCE) include(FetchContent) -FetchContent_Declare(coretrace-logger +FetchContent_Declare(coretrace_logger GIT_REPOSITORY https://github.com/CoreTrace/coretrace-log.git GIT_TAG main EXCLUDE_FROM_ALL ) -FetchContent_GetProperties(coretrace-logger) -if(NOT coretrace-logger_POPULATED) - FetchContent_Populate(coretrace-logger) -endif() - -if(WIN32) - file(COPY_FILE - "${CMAKE_SOURCE_DIR}/src/ThirdParty/CoretraceLoggerWindows.cpp" - "${coretrace-logger_SOURCE_DIR}/src/logger.cpp" - ONLY_IF_DIFFERENT - ) +FetchContent_GetProperties(coretrace_logger) +if(NOT coretrace_logger_POPULATED) + FetchContent_Populate(coretrace_logger) endif() if(NOT TARGET coretrace_logger) - FetchContent_MakeAvailable(coretrace-logger) + FetchContent_MakeAvailable(coretrace_logger) endif() diff --git a/cmake/stackUsageAnalyzer.cmake b/cmake/stackUsageAnalyzer.cmake index 9c0b8d8..41ac1b0 100644 --- a/cmake/stackUsageAnalyzer.cmake +++ b/cmake/stackUsageAnalyzer.cmake @@ -1,278 +1,29 @@ # SPDX-License-Identifier: Apache-2.0 include(FetchContent) -set(CORETRACE_LOGGER_BUILD_EXAMPLES OFF CACHE BOOL "Disable logger examples" FORCE) -set(CORETRACE_LOGGER_BUILD_TESTS OFF CACHE BOOL "Disable logger tests" FORCE) - -function(_coretrace_patch_imported_link_property _target _property _stale_value _replacement_value) - if(NOT TARGET "${_target}") - return() - endif() - - get_target_property(_current_value "${_target}" "${_property}") - if(NOT _current_value OR _current_value STREQUAL "_current_value-NOTFOUND") - return() - endif() - - string(REPLACE "${_stale_value}" "${_replacement_value}" _updated_value "${_current_value}") - if(NOT _updated_value STREQUAL _current_value) - set_target_properties("${_target}" PROPERTIES - ${_property} "${_updated_value}" - ) - endif() -endfunction() - -function(_coretrace_patch_llvm_diaguids _replacement_path) - if(NOT WIN32 OR NOT TARGET LLVMDebugInfoPDB) - return() - endif() - - set(_coretrace_stale_diaguids_path - "C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/DIA SDK/lib/amd64/diaguids.lib") - - foreach(_property_name - INTERFACE_LINK_LIBRARIES - IMPORTED_LINK_INTERFACE_LIBRARIES - IMPORTED_LINK_INTERFACE_LIBRARIES_DEBUG - IMPORTED_LINK_INTERFACE_LIBRARIES_RELEASE - IMPORTED_LINK_INTERFACE_LIBRARIES_RELWITHDEBINFO - IMPORTED_LINK_INTERFACE_LIBRARIES_MINSIZEREL) - _coretrace_patch_imported_link_property( - LLVMDebugInfoPDB - ${_property_name} - "${_coretrace_stale_diaguids_path}" - "${_replacement_path}" - ) - endforeach() -endfunction() - -function(_coretrace_patch_fetched_file _path _search _replacement) - if(NOT EXISTS "${_path}") - return() - endif() - - file(READ "${_path}" _content) - string(REPLACE "${_search}" "${_replacement}" _updated "${_content}") - if(NOT _updated STREQUAL _content) - file(WRITE "${_path}" "${_updated}") - endif() -endfunction() - -function(_coretrace_force_msvc_runtime target_name) - if(MSVC AND TARGET "${target_name}") - set_property(TARGET "${target_name}" PROPERTY - MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>" - ) - endif() -endfunction() - -FetchContent_Declare( - coretrace-logger - GIT_REPOSITORY https://github.com/CoreTrace/coretrace-log.git - GIT_TAG main - EXCLUDE_FROM_ALL -) - -FetchContent_GetProperties(coretrace-logger) -if(NOT coretrace-logger_POPULATED) - FetchContent_Populate(coretrace-logger) -endif() - -if(WIN32) - file(COPY_FILE - "${CMAKE_SOURCE_DIR}/src/ThirdParty/CoretraceLoggerWindows.cpp" - "${coretrace-logger_SOURCE_DIR}/src/logger.cpp" - ONLY_IF_DIFFERENT - ) -endif() - -if(NOT TARGET coretrace_logger) - add_subdirectory("${coretrace-logger_SOURCE_DIR}" "${coretrace-logger_BINARY_DIR}" EXCLUDE_FROM_ALL) -endif() -_coretrace_force_msvc_runtime(coretrace_logger) +set(CORETRACE_COMPILER_GIT_TAG "main" CACHE STRING + "Git ref used when fetching coretrace-compiler") +set(CORETRACE_STACK_ANALYZER_GIT_TAG "main" CACHE STRING + "Git ref used when fetching coretrace-stack-analyzer") FetchContent_Declare( cc GIT_REPOSITORY https://github.com/CoreTrace/coretrace-compiler.git - GIT_TAG main + GIT_TAG ${CORETRACE_COMPILER_GIT_TAG} EXCLUDE_FROM_ALL ) - -FetchContent_GetProperties(cc) -if(NOT cc_POPULATED) - FetchContent_Populate(cc) -endif() - -_coretrace_patch_fetched_file( - "${cc_SOURCE_DIR}/CMakeLists.txt" - [=[message(STATUS "Using Clang resource dir: ${CLANG_RESOURCE_DIR}")]=] - [=[file(TO_CMAKE_PATH "${CLANG_RESOURCE_DIR}" CLANG_RESOURCE_DIR) -message(STATUS "Using Clang resource dir: ${CLANG_RESOURCE_DIR}")]=] -) -_coretrace_patch_fetched_file( - "${cc_SOURCE_DIR}/CMakeLists.txt" - "if(NOT LLVM_ENABLE_RTTI)\n target_compile_options(compilerlib_static PRIVATE -fno-rtti)\nendif()" - "if(NOT LLVM_ENABLE_RTTI)\n if(MSVC)\n target_compile_options(compilerlib_static PRIVATE /GR-)\n else()\n target_compile_options(compilerlib_static PRIVATE -fno-rtti)\n endif()\nendif()" -) -_coretrace_patch_fetched_file( - "${cc_SOURCE_DIR}/CMakeLists.txt" - "if(NOT LLVM_ENABLE_RTTI)\n target_compile_options(compilerlib_shared PRIVATE -fno-rtti)\nendif()" - "if(NOT LLVM_ENABLE_RTTI)\n if(MSVC)\n target_compile_options(compilerlib_shared PRIVATE /GR-)\n else()\n target_compile_options(compilerlib_shared PRIVATE -fno-rtti)\n endif()\nendif()" -) -_coretrace_patch_fetched_file( - "${cc_SOURCE_DIR}/CMakeLists.txt" - "if(NOT LLVM_ENABLE_RTTI)\n target_compile_options(cc PRIVATE -fno-rtti)\nendif()" - "if(NOT LLVM_ENABLE_RTTI)\n if(MSVC)\n target_compile_options(cc PRIVATE /GR-)\n else()\n target_compile_options(cc PRIVATE -fno-rtti)\n endif()\nendif()" -) - -if(WIN32) - set(_coretrace_diaguids_candidates) - file(GLOB_RECURSE _coretrace_diaguids_candidates - LIST_DIRECTORIES false - "C:/Program Files/Microsoft Visual Studio/*/*/DIA SDK/lib/amd64/diaguids.lib" - "C:/Program Files (x86)/Microsoft Visual Studio/*/*/DIA SDK/lib/amd64/diaguids.lib" - ) - - if(_coretrace_diaguids_candidates) - list(GET _coretrace_diaguids_candidates 0 _coretrace_diaguids_path) - endif() - - # Import and patch LLVM/Clang targets before dependent subprojects link - # against them. Some Windows LLVM packages ship a stale DIA SDK path in the - # LLVMDebugInfoPDB imported target. - find_package(LLVM REQUIRED CONFIG) - find_package(Clang REQUIRED CONFIG) - - if(_coretrace_diaguids_path) - _coretrace_patch_llvm_diaguids("${_coretrace_diaguids_path}") - endif() - - set(_coretrace_cc_windows_runtime_dir - "${CMAKE_SOURCE_DIR}/src/ThirdParty/coretrace-compiler/windows") - - foreach(_runtime_file - ct_runtime_internal.h - ct_runtime_helpers.h - ct_runtime_alloc.cpp - ct_runtime_backtrace.cpp - ct_runtime_env.cpp - ct_runtime_vtable.cpp) - file(COPY_FILE - "${_coretrace_cc_windows_runtime_dir}/${_runtime_file}" - "${cc_SOURCE_DIR}/src/runtime/${_runtime_file}" - ONLY_IF_DIFFERENT - ) - endforeach() -endif() - -if(NOT TARGET compilerlib_static) - add_subdirectory("${cc_SOURCE_DIR}" "${cc_BINARY_DIR}" EXCLUDE_FROM_ALL) -endif() - -set(_coretrace_cc_extra_llvm_libs - LLVMAArch64CodeGen - LLVMAArch64AsmParser - LLVMAArch64Desc - LLVMAArch64Info - LLVMAArch64Utils - LLVMARMCodeGen - LLVMARMAsmParser - LLVMARMDesc - LLVMARMInfo - LLVMARMUtils - LLVMBPFCodeGen - LLVMBPFAsmParser - LLVMBPFDesc - LLVMBPFInfo - LLVMWebAssemblyCodeGen - LLVMWebAssemblyAsmParser - LLVMWebAssemblyDesc - LLVMWebAssemblyInfo - LLVMWebAssemblyUtils - LLVMRISCVCodeGen - LLVMRISCVAsmParser - LLVMRISCVDesc - LLVMRISCVInfo - LLVMRISCVTargetMCA - LLVMNVPTXCodeGen - LLVMNVPTXDesc - LLVMNVPTXInfo -) -if(TARGET compilerlib_static) - target_link_libraries(compilerlib_static PUBLIC ${_coretrace_cc_extra_llvm_libs}) -endif() -if(TARGET compilerlib_shared) - target_link_libraries(compilerlib_shared PRIVATE ${_coretrace_cc_extra_llvm_libs}) -endif() - -_coretrace_force_msvc_runtime(ct_instrument_runtime) -_coretrace_force_msvc_runtime(compilerlib_static) -_coretrace_force_msvc_runtime(compilerlib_shared) -_coretrace_force_msvc_runtime(cc) - -if(WIN32 AND _coretrace_diaguids_path) - _coretrace_patch_llvm_diaguids("${_coretrace_diaguids_path}") -endif() +FetchContent_MakeAvailable(cc) FetchContent_Declare( stack_analyzer GIT_REPOSITORY https://github.com/CoreTrace/coretrace-stack-analyzer.git - GIT_TAG v0.18.1 + GIT_TAG ${CORETRACE_STACK_ANALYZER_GIT_TAG} EXCLUDE_FROM_ALL ) - -FetchContent_GetProperties(stack_analyzer) -if(NOT stack_analyzer_POPULATED) - FetchContent_Populate(stack_analyzer) -endif() - -_coretrace_patch_fetched_file( - "${stack_analyzer_SOURCE_DIR}/CMakeLists.txt" - "set(LLVM_LINK_LLVM_DYLIB ON)" - "if(WIN32)\n set(LLVM_LINK_LLVM_DYLIB OFF)\nelse()\n set(LLVM_LINK_LLVM_DYLIB ON)\nendif()" -) -_coretrace_patch_fetched_file( - "${stack_analyzer_SOURCE_DIR}/CMakeLists.txt" - "if(ENABLE_STACK_USAGE)\n target_compile_options(stack_usage_analyzer_lib PRIVATE -fstack-usage)\nendif()" - "if(ENABLE_STACK_USAGE AND NOT MSVC)\n target_compile_options(stack_usage_analyzer_lib PRIVATE -fstack-usage)\nendif()" -) -_coretrace_patch_fetched_file( - "${stack_analyzer_SOURCE_DIR}/CMakeLists.txt" - " if(ENABLE_STACK_USAGE)\n target_compile_options(stack_usage_analyzer PRIVATE -fstack-usage)\n endif()" - " if(ENABLE_STACK_USAGE AND NOT MSVC)\n target_compile_options(stack_usage_analyzer PRIVATE -fstack-usage)\n endif()" -) - -if(WIN32) - set(_coretrace_stack_windows_dir - "${CMAKE_SOURCE_DIR}/src/ThirdParty/coretrace-stack-analyzer/windows") - - foreach(_stack_file - mangle.hpp - mangle.cpp) - if(_stack_file STREQUAL "mangle.hpp") - set(_stack_destination "${stack_analyzer_SOURCE_DIR}/include/${_stack_file}") - else() - set(_stack_destination "${stack_analyzer_SOURCE_DIR}/src/${_stack_file}") - endif() - - file(COPY_FILE - "${_coretrace_stack_windows_dir}/${_stack_file}" - "${_stack_destination}" - ONLY_IF_DIFFERENT - ) - endforeach() -endif() - -if(NOT TARGET stack_usage_analyzer_lib) - add_subdirectory("${stack_analyzer_SOURCE_DIR}" "${stack_analyzer_BINARY_DIR}" EXCLUDE_FROM_ALL) -endif() -_coretrace_force_msvc_runtime(stack_usage_analyzer_lib) -_coretrace_force_msvc_runtime(stack_usage_analyzer) +FetchContent_MakeAvailable(stack_analyzer) # Copy upstream default models into config/models/ so that tool-config.json # can reference them with paths relative to the config directory, without # ever pointing into _deps/. -# Custom model files already present in config/models/ are not affected -# (they live at the root of the directory, upstream models are in subdirs). file(COPY "${stack_analyzer_SOURCE_DIR}/models/" DESTINATION "${CMAKE_SOURCE_DIR}/config/models") diff --git a/docs/windows-port.md b/docs/windows-port.md deleted file mode 100644 index f796812..0000000 --- a/docs/windows-port.md +++ /dev/null @@ -1,88 +0,0 @@ -# Windows Port Plan - -This project can be ported to Windows without rewriting the application, but the port needs to be treated as two separate deliverables: - -1. A native host application: `ctrace.exe` -2. A tool runtime strategy for every analyzer that `ctrace` orchestrates - -The host application is a good fit for native Windows. The analyzer toolchain is mixed: some tools have native Windows stories, some do not. For feature parity, keep the host native and make tool execution configurable per tool. - -## Target Architecture - -Use a layered design: - -1. Core host - Native C++/CMake application with the same CLI, config loading, HTTP server mode, and report generation on every OS. -2. Platform adapters - Isolate process launching, local socket IPC, temp-path defaults, and packaging behind Windows-safe abstractions. -3. Tool adapters - Resolve each analyzer through a dedicated launcher instead of hard-coded Unix paths. -4. Packaging - Build an install tree containing `bin/ctrace.exe`, required runtime DLLs, and `config/`. - -The current tree now includes: - -- A real Windows process launcher in `include/Process/WinProcess.hpp` -- Cross-platform local socket IPC in `include/Process/Ipc/IpcStrategy.hpp` -- Platform-aware IPC defaults in `include/App/Platform.hpp` -- Tool command resolution with environment overrides in `include/App/ToolResolver.hpp` -- A Windows build helper in `scripts/build-windows.ps1` - -## Build Requirements - -Install: - -- Visual Studio 2022 with C++ build tools -- CMake 3.28+ -- LLVM/Clang development packages with `LLVMConfig.cmake` and `ClangConfig.cmake` - -Recommended configure path: - -```powershell -powershell -ExecutionPolicy Bypass -File .\scripts\build-windows.ps1 ` - -LLVMDir "C:\Program Files\LLVM\lib\cmake\llvm" ` - -PackageZip -``` - -Expected output: - -- `dist\windows\bin\ctrace.exe` -- `dist\windows\config\tool-config.json` -- `coretrace-windows-Release.zip` when `-PackageZip` is used - -## Tool Runtime Strategy - -Do not keep analyzer paths hard-coded in source for Windows deployments. Use environment overrides instead: - -- `CORETRACE_CPPCHECK_BIN` -- `CORETRACE_TSCANCODE_BIN` -- `CORETRACE_IKOS_BIN` -- `CORETRACE_FLAWFINDER_LAUNCHER` -- `CORETRACE_FLAWFINDER_SCRIPT` -- `CORETRACE_PYTHON_BIN` - -Recommended approach: - -- Run `cppcheck` natively on Windows -- Run `ctrace_stack_analyzer` natively on Windows with LLVM/Clang -- Run `tscancode` natively if you have a Windows-capable binary available -- Run `ikos` and `flawfinder` through a compatibility layer or wrapper if native Windows binaries are not part of your supported toolchain - -For production, prefer wrapper executables or scripts per analyzer instead of embedding platform rules directly in `ctrace`. That keeps the host binary stable and lets you swap tool installations without recompiling. - -## Packaging Guidance - -For an industry-ready Windows release: - -1. Build `ctrace.exe` with MSVC in `Release` -2. Install with `cmake --install` into a staging directory -3. Bundle required runtime DLLs next to the executable -4. Keep mutable assets in `config/` -5. Publish a portable ZIP first -6. Add an installer only after the portable artifact is stable - -If you need a Windows installer later, add a separate packaging layer such as CPack/NSIS or WiX, but keep the portable install tree as the canonical artifact. - -## Important Constraint - -A native `ctrace.exe` is straightforward. Full native parity for every external analyzer depends on whether each analyzer itself is supported on Windows. If one analyzer is Linux-only, the correct architecture is a native Windows host plus a compatibility-backed adapter for that analyzer, not a partial reimplementation inside `ctrace`. diff --git a/scripts/build-windows.ps1 b/scripts/build-windows.ps1 index 28cdd7d..9b7e01c 100644 --- a/scripts/build-windows.ps1 +++ b/scripts/build-windows.ps1 @@ -11,6 +11,9 @@ param( [string]$Arch = "x64", [string]$Toolset = "", [string]$ParserType = "CLI11", + [string]$CompilerSourceDir = "", + [string]$StackAnalyzerSourceDir = "", + [string]$LoggerSourceDir = "", [string]$VersionOverride = "", [switch]$PackageZip ) @@ -196,6 +199,24 @@ $cmakeArgs = @( "-DClang_DIR=$resolvedClangDir" ) +if ($CompilerSourceDir -ne "") +{ + $resolvedCompilerSourceDir = (Resolve-Path $CompilerSourceDir).Path + $cmakeArgs += "-DFETCHCONTENT_SOURCE_DIR_CC=$resolvedCompilerSourceDir" +} + +if ($StackAnalyzerSourceDir -ne "") +{ + $resolvedStackAnalyzerSourceDir = (Resolve-Path $StackAnalyzerSourceDir).Path + $cmakeArgs += "-DFETCHCONTENT_SOURCE_DIR_STACK_ANALYZER=$resolvedStackAnalyzerSourceDir" +} + +if ($LoggerSourceDir -ne "") +{ + $resolvedLoggerSourceDir = (Resolve-Path $LoggerSourceDir).Path + $cmakeArgs += "-DFETCHCONTENT_SOURCE_DIR_CORETRACE_LOGGER=$resolvedLoggerSourceDir" +} + if ($Generator -like "Visual Studio*") { $cmakeArgs += @("-A", $Arch) diff --git a/src/ThirdParty/CoretraceLoggerWindows.cpp b/src/ThirdParty/CoretraceLoggerWindows.cpp deleted file mode 100644 index 463f036..0000000 --- a/src/ThirdParty/CoretraceLoggerWindows.cpp +++ /dev/null @@ -1,567 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace coretrace -{ - namespace - { - std::atomic g_log_enabled{false}; - std::atomic g_min_level{static_cast(Level::Info)}; - std::atomic g_thread_safe{true}; - std::atomic g_timestamps_enabled{false}; - std::atomic g_source_location_enabled{false}; - std::atomic g_sink{nullptr}; - - std::mutex g_state_mutex; - std::mutex g_output_mutex; - std::once_flag g_init_once; - - std::string g_prefix = "==ct=="; - bool g_min_level_set_explicitly = false; - bool g_modules_set_explicitly = false; - constexpr int kMaxModules = 32; - std::string g_modules[kMaxModules]; - int g_module_count = 0; - bool g_module_filter_active = false; - - [[nodiscard]] bool asciiCaseEqual(std::string_view lhs, std::string_view rhs) - { - if (lhs.size() != rhs.size()) - { - return false; - } - - for (std::size_t i = 0; i < lhs.size(); ++i) - { - const char left = static_cast(std::tolower(static_cast(lhs[i]))); - const char right = - static_cast(std::tolower(static_cast(rhs[i]))); - if (left != right) - { - return false; - } - } - return true; - } - - [[nodiscard]] bool use_color() - { - static const bool enabled = []() - { - if (std::getenv("NO_COLOR") != nullptr) - { - return false; - } - return _isatty(_fileno(stderr)) != 0; - }(); - return enabled; - } - - [[nodiscard]] std::string current_prefix() - { - std::lock_guard lock(g_state_mutex); - return g_prefix; - } - - void add_module_locked(std::string_view name) - { - for (int i = 0; i < g_module_count; ++i) - { - if (g_modules[i] == name) - { - return; - } - } - - if (g_module_count >= kMaxModules) - { - return; - } - - g_modules[g_module_count++] = std::string(name); - g_module_filter_active = true; - } - - [[nodiscard]] int parse_level_from_env(const char* value) - { - if (value == nullptr) - { - return static_cast(Level::Info); - } - if (asciiCaseEqual(value, "debug")) - { - return static_cast(Level::Debug); - } - if (asciiCaseEqual(value, "warn")) - { - return static_cast(Level::Warn); - } - if (asciiCaseEqual(value, "error")) - { - return static_cast(Level::Error); - } - return static_cast(Level::Info); - } - - void init_from_env() - { - if (!g_min_level_set_explicitly) - { - g_min_level.store(parse_level_from_env(std::getenv("CT_LOG_LEVEL")), - std::memory_order_release); - } - - if (!g_modules_set_explicitly) - { - const char* env_debug = std::getenv("CT_DEBUG"); - if (env_debug == nullptr || *env_debug == '\0') - { - return; - } - - std::lock_guard lock(g_state_mutex); - const char* start = env_debug; - while (*start != '\0') - { - const char* end = start; - while (*end != '\0' && *end != ',') - { - ++end; - } - if (end > start) - { - add_module_locked(std::string_view(start, static_cast(end - start))); - } - start = (*end == ',') ? end + 1 : end; - } - } - } - - [[nodiscard]] std::string make_timestamp_prefix() - { - using clock = std::chrono::system_clock; - const auto now = clock::now(); - const auto time = clock::to_time_t(now); - const auto millis = std::chrono::duration_cast( - now.time_since_epoch()) % - 1000; - - std::tm tmBuf{}; - gmtime_s(&tmBuf, &time); - - char buffer[40]; - std::snprintf(buffer, sizeof(buffer), "[%04d-%02d-%02dT%02d:%02d:%02d.%03d] ", - tmBuf.tm_year + 1900, tmBuf.tm_mon + 1, tmBuf.tm_mday, tmBuf.tm_hour, - tmBuf.tm_min, tmBuf.tm_sec, static_cast(millis.count())); - return buffer; - } - - [[nodiscard]] const char* basename_of(const char* path) - { - if (path == nullptr) - { - return ""; - } - - const char* last = path; - for (const char* p = path; *p != '\0'; ++p) - { - if (*p == '/' || *p == '\\') - { - last = p + 1; - } - } - return last; - } - } // namespace - - void enable_logging() - { - g_log_enabled.store(true, std::memory_order_release); - } - - void disable_logging() - { - g_log_enabled.store(false, std::memory_order_release); - } - - [[nodiscard]] bool log_is_enabled() - { - return g_log_enabled.load(std::memory_order_acquire); - } - - void set_prefix(std::string_view prefix) - { - std::lock_guard lock(g_state_mutex); - g_prefix.assign(prefix); - } - - void set_min_level(Level level) - { - g_min_level_set_explicitly = true; - init_once(); - g_min_level.store(static_cast(level), std::memory_order_release); - } - - [[nodiscard]] Level min_level() - { - return static_cast(g_min_level.load(std::memory_order_acquire)); - } - - void enable_module(std::string_view name) - { - if (name.empty()) - { - return; - } - - g_modules_set_explicitly = true; - init_once(); - std::lock_guard lock(g_state_mutex); - add_module_locked(name); - } - - void disable_module(std::string_view name) - { - if (name.empty()) - { - return; - } - - g_modules_set_explicitly = true; - init_once(); - std::lock_guard lock(g_state_mutex); - for (int i = 0; i < g_module_count; ++i) - { - if (g_modules[i] == name) - { - for (int j = i; j < g_module_count - 1; ++j) - { - g_modules[j] = g_modules[j + 1]; - } - --g_module_count; - if (g_module_count == 0) - { - g_module_filter_active = false; - } - break; - } - } - } - - void enable_all_modules() - { - g_modules_set_explicitly = true; - init_once(); - std::lock_guard lock(g_state_mutex); - g_module_count = 0; - g_module_filter_active = false; - } - - [[nodiscard]] bool module_is_enabled(std::string_view name) - { - std::lock_guard lock(g_state_mutex); - if (!g_module_filter_active) - { - return true; - } - for (int i = 0; i < g_module_count; ++i) - { - if (g_modules[i] == name) - { - return true; - } - } - return false; - } - - void set_thread_safe(bool enabled) - { - g_thread_safe.store(enabled, std::memory_order_release); - } - - void set_sink(SinkFn fn) - { - g_sink.store(fn, std::memory_order_release); - } - - void reset_sink() - { - g_sink.store(nullptr, std::memory_order_release); - } - - void set_timestamps(bool enabled) - { - g_timestamps_enabled.store(enabled, std::memory_order_release); - } - - void set_source_location(bool enabled) - { - g_source_location_enabled.store(enabled, std::memory_order_release); - } - - [[nodiscard]] std::string_view color(Color c) - { - if (!use_color()) - { - return {}; - } - - switch (c) - { - case Color::Reset: - return "\x1b[0m"; - case Color::Dim: - return "\x1b[2m"; - case Color::Bold: - return "\x1b[1m"; - case Color::Underline: - return "\x1b[4m"; - case Color::Italic: - return "\x1b[3m"; - case Color::Blink: - return "\x1b[5m"; - case Color::Reverse: - return "\x1b[7m"; - case Color::Hidden: - return "\x1b[8m"; - case Color::Strike: - return "\x1b[9m"; - case Color::Black: - return "\x1b[30m"; - case Color::Red: - return "\x1b[31m"; - case Color::Green: - return "\x1b[32m"; - case Color::Yellow: - return "\x1b[33m"; - case Color::Blue: - return "\x1b[34m"; - case Color::Magenta: - return "\x1b[35m"; - case Color::Cyan: - return "\x1b[36m"; - case Color::White: - return "\x1b[37m"; - case Color::Gray: - return "\x1b[90m"; - case Color::BrightRed: - return "\x1b[91m"; - case Color::BrightGreen: - return "\x1b[92m"; - case Color::BrightYellow: - return "\x1b[93m"; - case Color::BrightBlue: - return "\x1b[94m"; - case Color::BrightMagenta: - return "\x1b[95m"; - case Color::BrightCyan: - return "\x1b[96m"; - case Color::BrightWhite: - return "\x1b[97m"; - case Color::BgBlack: - return "\x1b[40m"; - case Color::BgRed: - return "\x1b[41m"; - case Color::BgGreen: - return "\x1b[42m"; - case Color::BgYellow: - return "\x1b[43m"; - case Color::BgBlue: - return "\x1b[44m"; - case Color::BgMagenta: - return "\x1b[45m"; - case Color::BgCyan: - return "\x1b[46m"; - case Color::BgWhite: - return "\x1b[47m"; - case Color::BgGray: - return "\x1b[100m"; - case Color::BgBrightRed: - return "\x1b[101m"; - case Color::BgBrightGreen: - return "\x1b[102m"; - case Color::BgBrightYellow: - return "\x1b[103m"; - case Color::BgBrightBlue: - return "\x1b[104m"; - case Color::BgBrightMagenta: - return "\x1b[105m"; - case Color::BgBrightCyan: - return "\x1b[106m"; - case Color::BgBrightWhite: - return "\x1b[107m"; - } - - return {}; - } - - [[nodiscard]] std::string_view level_label(Level level) - { - switch (level) - { - case Level::Debug: - return "DEBUG"; - case Level::Info: - return "INFO"; - case Level::Warn: - return "WARN"; - case Level::Error: - return "ERROR"; - } - return "INFO"; - } - - [[nodiscard]] std::string_view level_color(Level level) - { - switch (level) - { - case Level::Debug: - return color(Color::Cyan); - case Level::Info: - return color(Color::Green); - case Level::Warn: - return color(Color::Yellow); - case Level::Error: - return color(Color::Red); - } - return color(Color::Cyan); - } - - void write_raw(const char* data, size_t size) - { - if (data == nullptr || size == 0) - { - return; - } - - const auto sink = g_sink.load(std::memory_order_acquire); - if (sink != nullptr) - { - sink(data, size); - return; - } - - (void)fwrite(data, 1, size, stderr); - (void)fflush(stderr); - } - - void write_str(std::string_view value) - { - write_raw(value.data(), value.size()); - } - - void write_dec(size_t value) - { - write_str(std::to_string(value)); - } - - void write_hex(uintptr_t value) - { - write_str(std::format("0x{:x}", value)); - } - - [[nodiscard]] int pid() - { - return static_cast(GetCurrentProcessId()); - } - - [[nodiscard]] unsigned long long thread_id() - { - return static_cast(GetCurrentThreadId()); - } - - void write_prefix(Level level) - { - const std::string prefix = current_prefix(); - write_str(color(Color::Dim)); - write_str(std::format("|{}|", pid())); - write_str(color(Color::Reset)); - write_raw(" ", 1); - write_str(color(Color::Gray)); - write_str(color(Color::Italic)); - write_str(prefix); - write_raw(" ", 1); - write_str(color(Color::Reset)); - write_str(level_color(level)); - write_str("["); - write_str(level_label(level)); - write_str("]"); - write_str(color(Color::Reset)); - write_raw(" ", 1); - } - - void init_once() - { - std::call_once(g_init_once, init_from_env); - } - - void write_log_line(Level level, std::string_view module, std::string_view message, - const std::source_location& loc) - { - std::unique_lock lock(g_output_mutex, std::defer_lock); - if (g_thread_safe.load(std::memory_order_acquire)) - { - lock.lock(); - } - - std::string line; - if (g_timestamps_enabled.load(std::memory_order_acquire)) - { - line += make_timestamp_prefix(); - } - - line += color(Color::Dim); - line += std::format("|{}|", pid()); - line += color(Color::Reset); - line += " "; - line += color(Color::Gray); - line += color(Color::Italic); - line += current_prefix(); - line += " "; - line += color(Color::Reset); - line += level_color(level); - line += "["; - line += level_label(level); - line += "]"; - line += color(Color::Reset); - - if (g_source_location_enabled.load(std::memory_order_acquire)) - { - line += " "; - line += color(Color::Dim); - line += basename_of(loc.file_name()); - line += ":"; - line += std::to_string(loc.line()); - line += color(Color::Reset); - } - - if (!module.empty()) - { - line += " "; - line += color(Color::Dim); - line += "("; - line.append(module.data(), module.size()); - line += ")"; - line += color(Color::Reset); - } - - line += " "; - line.append(message.data(), message.size()); - write_raw(line.data(), line.size()); - } -} // namespace coretrace diff --git a/src/ThirdParty/coretrace-compiler/windows/ct_runtime_alloc.cpp b/src/ThirdParty/coretrace-compiler/windows/ct_runtime_alloc.cpp deleted file mode 100644 index 7e6b103..0000000 --- a/src/ThirdParty/coretrace-compiler/windows/ct_runtime_alloc.cpp +++ /dev/null @@ -1,901 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -#include "ct_runtime_internal.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#ifndef NOMINMAX -#define NOMINMAX -#endif -#include - -namespace -{ - enum CtAllocKind : unsigned char - { - CT_ALLOC_KIND_MALLOC = 0, - CT_ALLOC_KIND_NEW = 1, - CT_ALLOC_KIND_NEW_ARRAY = 2, - CT_ALLOC_KIND_MMAP = 3, - CT_ALLOC_KIND_SBRK = 4, - CT_ALLOC_KIND_ALIGNED = 5 - }; - - struct CtAllocEntry - { - size_t size = 0; - size_t req_size = 0; - const char* site = nullptr; - unsigned char state = CT_ENTRY_EMPTY; - unsigned char kind = CT_ALLOC_KIND_MALLOC; - }; - - std::mutex ct_alloc_mutex; - std::unordered_map ct_alloc_table; - - CT_NODISCARD CT_NOINSTR const char* ct_kind_name(unsigned char kind) - { - switch (kind) - { - case CT_ALLOC_KIND_MALLOC: - return "malloc"; - case CT_ALLOC_KIND_NEW: - return "new"; - case CT_ALLOC_KIND_NEW_ARRAY: - return "new[]"; - case CT_ALLOC_KIND_MMAP: - return "mmap"; - case CT_ALLOC_KIND_SBRK: - return "sbrk"; - case CT_ALLOC_KIND_ALIGNED: - return "aligned"; - default: - return "unknown"; - } - } - - CT_NODISCARD CT_NOINSTR bool ct_is_power_of_two(size_t value) - { - return value != 0 && (value & (value - 1)) == 0; - } - - CT_NOINSTR void ct_track_shadow_alloc(void* ptr, size_t req_size, size_t alloc_size) - { - if (!ptr || !ct_is_enabled(CT_FEATURE_SHADOW)) - { - return; - } - - const size_t live_size = req_size ? req_size : alloc_size; - if (live_size != 0) - { - ct_shadow_unpoison_range(ptr, live_size); - } - if (alloc_size > live_size) - { - ct_shadow_poison_range(static_cast(ptr) + live_size, alloc_size - live_size); - } - } - - CT_NOINSTR void ct_track_shadow_free(void* ptr, size_t size) - { - if (!ptr || !ct_is_enabled(CT_FEATURE_SHADOW) || size == 0) - { - return; - } - ct_shadow_poison_range(ptr, size); - } - - CT_NOINSTR void ct_log_alloc_event(const char* action, void* ptr, size_t size, const char* site, - unsigned char kind) - { - if (!ct_is_enabled(CT_FEATURE_ALLOC_TRACE)) - { - return; - } - - ct_log(CTLevel::Info, "ct: {} ptr={:p} size={} kind={} site={}\n", action, ptr, size, - ct_kind_name(kind), ct_site_name(site)); - } - - CT_NOINSTR void ct_log_skip_event(const char* action, void* ptr, const char* reason) - { - ct_log(CTLevel::Warn, "ct: {} skipped ptr={:p} ({})\n", action, ptr, reason); - } - - CT_NODISCARD CT_NOINSTR int ct_table_remove_with_state(void* ptr, unsigned char new_state, - size_t* size_out, size_t* req_size_out, - const char** site_out, - unsigned char* kind_out) - { - if (!ptr) - { - return 0; - } - - auto it = ct_alloc_table.find(ptr); - if (it == ct_alloc_table.end()) - { - return 0; - } - - CtAllocEntry& entry = it->second; - if (size_out) - { - *size_out = entry.size; - } - if (req_size_out) - { - *req_size_out = entry.req_size; - } - if (site_out) - { - *site_out = entry.site; - } - if (kind_out) - { - *kind_out = entry.kind; - } - - if (entry.state == CT_ENTRY_FREED || entry.state == CT_ENTRY_AUTOFREED) - { - return -1; - } - - entry.state = new_state; - return 1; - } - - CT_NOINSTR void ct_release_memory(void* ptr, unsigned char kind) - { - if (!ptr) - { - return; - } - - switch (kind) - { - case CT_ALLOC_KIND_NEW: - ::operator delete(ptr); - return; - case CT_ALLOC_KIND_NEW_ARRAY: - ::operator delete[](ptr); - return; - case CT_ALLOC_KIND_MMAP: - (void)VirtualFree(ptr, 0, MEM_RELEASE); - return; - case CT_ALLOC_KIND_ALIGNED: - _aligned_free(ptr); - return; - default: - std::free(ptr); - return; - } - } - - CT_NOINSTR void ct_release_unknown_delete(void* ptr, bool is_array) - { - if (!ptr) - { - return; - } - - if (is_array) - { - ::operator delete[](ptr); - } - else - { - ::operator delete(ptr); - } - } - - CT_NODISCARD CT_NOINSTR DWORD ct_translate_page_protection(int prot) - { - constexpr int kProtRead = 0x1; - constexpr int kProtWrite = 0x2; - constexpr int kProtExec = 0x4; - - const bool can_read = (prot & kProtRead) != 0; - const bool can_write = (prot & kProtWrite) != 0; - const bool can_exec = (prot & kProtExec) != 0; - - if (can_exec && can_write) - { - return PAGE_EXECUTE_READWRITE; - } - if (can_exec && can_read) - { - return PAGE_EXECUTE_READ; - } - if (can_exec) - { - return PAGE_EXECUTE; - } - if (can_write) - { - return PAGE_READWRITE; - } - if (can_read) - { - return PAGE_READONLY; - } - return PAGE_NOACCESS; - } - - CT_NODISCARD CT_NOINSTR void* ct_record_alloc(void* ptr, size_t req_size, size_t alloc_size, - const char* site, unsigned char kind) - { - if (!ptr) - { - return ptr; - } - - ct_lock_acquire(); - (void)ct_table_insert(ptr, req_size, alloc_size, site, kind); - ct_lock_release(); - - ct_track_shadow_alloc(ptr, req_size, alloc_size); - ct_log_alloc_event("alloc", ptr, req_size ? req_size : alloc_size, site, kind); - return ptr; - } - - CT_NOINSTR void ct_record_realloc(void* old_ptr, void* new_ptr, size_t size, const char* site) - { - if (!new_ptr) - { - return; - } - - ct_lock_acquire(); - if (old_ptr && old_ptr != new_ptr) - { - (void)ct_table_remove_with_state(old_ptr, CT_ENTRY_FREED, nullptr, nullptr, nullptr, nullptr); - } - - auto& entry = ct_alloc_table[new_ptr]; - entry.size = size; - entry.req_size = size; - entry.site = site; - entry.state = CT_ENTRY_USED; - entry.kind = CT_ALLOC_KIND_MALLOC; - ct_lock_release(); - - ct_track_shadow_alloc(new_ptr, size, size); - ct_log_alloc_event("realloc", new_ptr, size, site, CT_ALLOC_KIND_MALLOC); - } - - CT_NODISCARD CT_NOINSTR int ct_remove_for_release(void* ptr, unsigned char new_state, - size_t* size_out, const char** site_out, - unsigned char* kind_out) - { - size_t req_size = 0; - return ct_table_remove_with_state(ptr, new_state, size_out, &req_size, site_out, kind_out); - } - - CT_NOINSTR void ct_autofree_impl(void* ptr, bool is_array) - { - ct_init_env_once(); - if (!ct_is_enabled(CT_FEATURE_ALLOC) || !ct_is_enabled(CT_FEATURE_AUTOFREE)) - { - return; - } - if (!ptr) - { - ct_log_skip_event("auto-free", ptr, "null"); - return; - } - - size_t size = 0; - const char* site = nullptr; - unsigned char kind = CT_ALLOC_KIND_MALLOC; - - ct_lock_acquire(); - const int found = ct_remove_for_release(ptr, CT_ENTRY_AUTOFREED, &size, &site, &kind); - ct_lock_release(); - - if (found <= 0) - { - ct_log_skip_event("auto-free", ptr, found == 0 ? "unknown" : "already freed"); - return; - } - - ct_track_shadow_free(ptr, size); - ct_log(CTLevel::Warn, "ct: auto-free ptr={:p} size={} site={}\n", ptr, size, - ct_site_name(site)); - - if (kind == CT_ALLOC_KIND_NEW_ARRAY || (kind == CT_ALLOC_KIND_NEW && is_array)) - { - ::operator delete[](ptr); - return; - } - if (kind == CT_ALLOC_KIND_NEW || kind == CT_ALLOC_KIND_NEW_ARRAY || kind == CT_ALLOC_KIND_ALIGNED || - kind == CT_ALLOC_KIND_MMAP) - { - ct_release_memory(ptr, kind); - return; - } - - std::free(ptr); - } - - struct CtLeakReporter - { - CT_NOINSTR ~CtLeakReporter() - { - std::vector> leaks; - - ct_lock_acquire(); - for (const auto& [ptr, entry] : ct_alloc_table) - { - if (entry.state == CT_ENTRY_USED) - { - leaks.push_back({ptr, entry}); - } - } - ct_lock_release(); - - if (leaks.empty()) - { - return; - } - - ct_disable_logging(); - ct_write_prefix(CTLevel::Error); - ct_write_cstr("ct: leaks detected count="); - ct_write_dec(leaks.size()); - ct_write_cstr("\n"); - - size_t reported = 0; - for (const auto& [ptr, entry] : leaks) - { - ct_write_prefix(CTLevel::Warn); - ct_write_cstr("ct: leak ptr="); - ct_write_hex(reinterpret_cast(ptr)); - ct_write_cstr(" size="); - ct_write_dec(entry.size); - ct_write_cstr(" site="); - ct_write_cstr(ct_site_name(entry.site)); - ct_write_cstr("\n"); - - if (++reported >= 32) - { - ct_write_prefix(CTLevel::Warn); - ct_write_cstr("ct: leak list truncated\n"); - break; - } - } - } - }; - - CtLeakReporter ct_leak_reporter; -} // namespace - -CT_NOINSTR void ct_lock_acquire(void) -{ - ct_alloc_mutex.lock(); -} - -CT_NOINSTR void ct_lock_release(void) -{ - ct_alloc_mutex.unlock(); -} - -CT_NODISCARD CT_NOINSTR int ct_table_insert(void* ptr, size_t req_size, size_t size, - const char* site, unsigned char kind) -{ - if (!ptr) - { - return 0; - } - - try - { - auto& entry = ct_alloc_table[ptr]; - entry.size = size; - entry.req_size = req_size; - entry.site = site; - entry.state = CT_ENTRY_USED; - entry.kind = kind; - return 1; - } - catch (...) - { - return 0; - } -} - -CT_NODISCARD CT_NOINSTR int ct_table_remove(void* ptr, size_t* size_out, size_t* req_size_out, - const char** site_out) -{ - return ct_table_remove_with_state(ptr, CT_ENTRY_FREED, size_out, req_size_out, site_out, - nullptr); -} - -CT_NODISCARD CT_NOINSTR int ct_table_lookup(const void* ptr, size_t* size_out, size_t* req_size_out, - const char** site_out, unsigned char* state_out) -{ - if (!ptr) - { - return 0; - } - - auto it = ct_alloc_table.find(const_cast(ptr)); - if (it == ct_alloc_table.end()) - { - return 0; - } - - const CtAllocEntry& entry = it->second; - if (size_out) - { - *size_out = entry.size; - } - if (req_size_out) - { - *req_size_out = entry.req_size; - } - if (site_out) - { - *site_out = entry.site; - } - if (state_out) - { - *state_out = entry.state; - } - return 1; -} - -CT_NODISCARD CT_NOINSTR int ct_table_lookup_containing(const void* ptr, void** base_out, - size_t* size_out, size_t* req_size_out, - const char** site_out, - unsigned char* state_out) -{ - if (!ptr) - { - return 0; - } - - const uintptr_t value = reinterpret_cast(ptr); - for (const auto& [base_ptr, entry] : ct_alloc_table) - { - if (entry.state != CT_ENTRY_USED && entry.state != CT_ENTRY_FREED && - entry.state != CT_ENTRY_AUTOFREED) - { - continue; - } - if (!base_ptr || entry.size == 0) - { - continue; - } - - const uintptr_t base = reinterpret_cast(base_ptr); - if (value < base || (value - base) >= entry.size) - { - continue; - } - - if (base_out) - { - *base_out = base_ptr; - } - if (size_out) - { - *size_out = entry.size; - } - if (req_size_out) - { - *req_size_out = entry.req_size; - } - if (site_out) - { - *site_out = entry.site; - } - if (state_out) - { - *state_out = entry.state; - } - return 1; - } - - return 0; -} - -extern "C" -{ - CT_NOINSTR void __ct_free(void* ptr); - - CT_NODISCARD CT_NOINSTR void* __ct_malloc(size_t size, const char* site) - { - ct_init_env_once(); - if (!ct_is_enabled(CT_FEATURE_ALLOC)) - { - return std::malloc(size); - } - return ct_record_alloc(std::malloc(size), size, size, site, CT_ALLOC_KIND_MALLOC); - } - - CT_NODISCARD CT_NOINSTR void* __ct_malloc_unreachable(size_t size, const char* site) - { - return __ct_malloc(size, site); - } - - CT_NODISCARD CT_NOINSTR void* __ct_calloc(size_t count, size_t size, const char* site) - { - ct_init_env_once(); - const size_t total = count * size; - if (!ct_is_enabled(CT_FEATURE_ALLOC)) - { - return std::calloc(count, size); - } - return ct_record_alloc(std::calloc(count, size), total, total, site, CT_ALLOC_KIND_MALLOC); - } - - CT_NODISCARD CT_NOINSTR void* __ct_calloc_unreachable(size_t count, size_t size, - const char* site) - { - return __ct_calloc(count, size, site); - } - - CT_NODISCARD CT_NOINSTR void* __ct_new(size_t size, const char* site) - { - ct_init_env_once(); - if (!ct_is_enabled(CT_FEATURE_ALLOC)) - { - return ::operator new(size); - } - return ct_record_alloc(::operator new(size), size, size, site, CT_ALLOC_KIND_NEW); - } - - CT_NODISCARD CT_NOINSTR void* __ct_new_unreachable(size_t size, const char* site) - { - return __ct_new(size, site); - } - - CT_NODISCARD CT_NOINSTR void* __ct_new_array(size_t size, const char* site) - { - ct_init_env_once(); - if (!ct_is_enabled(CT_FEATURE_ALLOC)) - { - return ::operator new[](size); - } - return ct_record_alloc(::operator new[](size), size, size, site, CT_ALLOC_KIND_NEW_ARRAY); - } - - CT_NODISCARD CT_NOINSTR void* __ct_new_array_unreachable(size_t size, const char* site) - { - return __ct_new_array(size, site); - } - - CT_NODISCARD CT_NOINSTR void* __ct_new_nothrow(size_t size, const char* site) - { - ct_init_env_once(); - if (!ct_is_enabled(CT_FEATURE_ALLOC)) - { - return ::operator new(size, std::nothrow); - } - return ct_record_alloc(::operator new(size, std::nothrow), size, size, site, - CT_ALLOC_KIND_NEW); - } - - CT_NODISCARD CT_NOINSTR void* __ct_new_nothrow_unreachable(size_t size, const char* site) - { - return __ct_new_nothrow(size, site); - } - - CT_NODISCARD CT_NOINSTR void* __ct_new_array_nothrow(size_t size, const char* site) - { - ct_init_env_once(); - if (!ct_is_enabled(CT_FEATURE_ALLOC)) - { - return ::operator new[](size, std::nothrow); - } - return ct_record_alloc(::operator new[](size, std::nothrow), size, size, site, - CT_ALLOC_KIND_NEW_ARRAY); - } - - CT_NODISCARD CT_NOINSTR void* __ct_new_array_nothrow_unreachable(size_t size, const char* site) - { - return __ct_new_array_nothrow(size, site); - } - - CT_NODISCARD CT_NOINSTR void* __ct_realloc(void* ptr, size_t size, const char* site) - { - ct_init_env_once(); - if (!ct_is_enabled(CT_FEATURE_ALLOC)) - { - return std::realloc(ptr, size); - } - if (!ptr) - { - return __ct_malloc(size, site); - } - if (size == 0) - { - __ct_free(ptr); - return nullptr; - } - - void* new_ptr = std::realloc(ptr, size); - if (!new_ptr) - { - return nullptr; - } - - ct_record_realloc(ptr, new_ptr, size, site); - return new_ptr; - } - - CT_NODISCARD CT_NOINSTR int __ct_posix_memalign(void** out, size_t align, size_t size, - const char* site) - { - ct_init_env_once(); - if (!out || !ct_is_power_of_two(align) || align < sizeof(void*)) - { - return EINVAL; - } - - void* ptr = _aligned_malloc(size, align); - if (!ptr) - { - return errno ? errno : ENOMEM; - } - - *out = ptr; - if (ct_is_enabled(CT_FEATURE_ALLOC)) - { - (void)ct_record_alloc(ptr, size, size, site, CT_ALLOC_KIND_ALIGNED); - } - return 0; - } - - CT_NODISCARD CT_NOINSTR void* __ct_aligned_alloc(size_t align, size_t size, const char* site) - { - ct_init_env_once(); - if (!ct_is_power_of_two(align) || align == 0) - { - errno = EINVAL; - return nullptr; - } - - void* ptr = _aligned_malloc(size, align); - if (!ct_is_enabled(CT_FEATURE_ALLOC)) - { - return ptr; - } - return ct_record_alloc(ptr, size, size, site, CT_ALLOC_KIND_ALIGNED); - } - - CT_NODISCARD CT_NOINSTR void* __ct_mmap(void* addr, size_t len, int prot, int flags, int fd, - size_t offset, const char* site) - { - ct_init_env_once(); - (void)flags; - if (fd != -1 || offset != 0 || len == 0) - { - errno = ENOSYS; - return reinterpret_cast(-1); - } - - void* ptr = VirtualAlloc(addr, len, MEM_COMMIT | MEM_RESERVE, ct_translate_page_protection(prot)); - if (!ptr) - { - errno = ENOMEM; - return reinterpret_cast(-1); - } - - if (!ct_is_enabled(CT_FEATURE_ALLOC)) - { - return ptr; - } - return ct_record_alloc(ptr, len, len, site, CT_ALLOC_KIND_MMAP); - } - - CT_NODISCARD CT_NOINSTR int __ct_munmap(void* addr, size_t len, const char* site) - { - ct_init_env_once(); - (void)len; - - size_t size = 0; - const char* alloc_site = nullptr; - unsigned char kind = CT_ALLOC_KIND_MMAP; - - if (ct_is_enabled(CT_FEATURE_ALLOC)) - { - ct_lock_acquire(); - (void)ct_remove_for_release(addr, CT_ENTRY_FREED, &size, &alloc_site, &kind); - ct_lock_release(); - ct_track_shadow_free(addr, size); - } - - const BOOL ok = addr ? VirtualFree(addr, 0, MEM_RELEASE) : TRUE; - if (!ok) - { - errno = EINVAL; - return -1; - } - - ct_log_alloc_event("munmap", addr, size, site ? site : alloc_site, CT_ALLOC_KIND_MMAP); - return 0; - } - - CT_NODISCARD CT_NOINSTR void* __ct_sbrk(size_t incr, const char* site) - { - ct_init_env_once(); - (void)incr; - (void)site; - errno = ENOSYS; - ct_log(CTLevel::Warn, "ct: sbrk is not supported on Windows\n"); - return reinterpret_cast(-1); - } - - CT_NODISCARD CT_NOINSTR void* __ct_brk(void* addr, const char* site) - { - ct_init_env_once(); - (void)addr; - (void)site; - errno = ENOSYS; - ct_log(CTLevel::Warn, "ct: brk is not supported on Windows\n"); - return reinterpret_cast(-1); - } - - CT_NOINSTR void __ct_autofree(void* ptr) - { - ct_autofree_impl(ptr, false); - } - - CT_NOINSTR void __ct_autofree_munmap(void* ptr) - { - ct_autofree_impl(ptr, false); - } - - CT_NOINSTR void __ct_autofree_sbrk(void* ptr) - { - ct_autofree_impl(ptr, false); - } - - CT_NOINSTR void __ct_autofree_delete(void* ptr) - { - ct_autofree_impl(ptr, false); - } - - CT_NOINSTR void __ct_autofree_delete_array(void* ptr) - { - ct_autofree_impl(ptr, true); - } - - CT_NOINSTR void __ct_free(void* ptr) - { - ct_init_env_once(); - if (!ct_is_enabled(CT_FEATURE_ALLOC)) - { - std::free(ptr); - return; - } - - if (!ptr) - { - ct_log_skip_event("free", ptr, "null"); - std::free(ptr); - return; - } - - size_t size = 0; - const char* site = nullptr; - unsigned char kind = CT_ALLOC_KIND_MALLOC; - - ct_lock_acquire(); - const int found = ct_remove_for_release(ptr, CT_ENTRY_FREED, &size, &site, &kind); - ct_lock_release(); - - if (found == -1) - { - ct_log_skip_event("free", ptr, "already freed"); - return; - } - if (found == 0) - { - ct_log_skip_event("free", ptr, "unknown"); - std::free(ptr); - return; - } - - ct_track_shadow_free(ptr, size); - ct_log_alloc_event("free", ptr, size, site, kind); - ct_release_memory(ptr, kind); - } - - CT_NOINSTR void __ct_delete(void* ptr) - { - ct_init_env_once(); - if (!ct_is_enabled(CT_FEATURE_ALLOC)) - { - ::operator delete(ptr); - return; - } - - size_t size = 0; - const char* site = nullptr; - unsigned char kind = CT_ALLOC_KIND_NEW; - - ct_lock_acquire(); - const int found = ct_remove_for_release(ptr, CT_ENTRY_FREED, &size, &site, &kind); - ct_lock_release(); - - if (found == -1) - { - ct_log_skip_event("delete", ptr, "already freed"); - return; - } - if (found == 0) - { - ct_log_skip_event("delete", ptr, "unknown"); - ct_release_unknown_delete(ptr, false); - return; - } - - ct_track_shadow_free(ptr, size); - ct_log_alloc_event("delete", ptr, size, site, kind); - ct_release_memory(ptr, kind); - } - - CT_NOINSTR void __ct_delete_array(void* ptr) - { - ct_init_env_once(); - if (!ct_is_enabled(CT_FEATURE_ALLOC)) - { - ::operator delete[](ptr); - return; - } - - size_t size = 0; - const char* site = nullptr; - unsigned char kind = CT_ALLOC_KIND_NEW_ARRAY; - - ct_lock_acquire(); - const int found = ct_remove_for_release(ptr, CT_ENTRY_FREED, &size, &site, &kind); - ct_lock_release(); - - if (found == -1) - { - ct_log_skip_event("delete[]", ptr, "already freed"); - return; - } - if (found == 0) - { - ct_log_skip_event("delete[]", ptr, "unknown"); - ct_release_unknown_delete(ptr, true); - return; - } - - ct_track_shadow_free(ptr, size); - ct_log_alloc_event("delete[]", ptr, size, site, kind); - ct_release_memory(ptr, kind); - } - - CT_NOINSTR void __ct_delete_nothrow(void* ptr) - { - __ct_delete(ptr); - } - - CT_NOINSTR void __ct_delete_array_nothrow(void* ptr) - { - __ct_delete_array(ptr); - } - - CT_NOINSTR void __ct_delete_destroying(void* ptr) - { - __ct_delete(ptr); - } - - CT_NOINSTR void __ct_delete_array_destroying(void* ptr) - { - __ct_delete_array(ptr); - } -} diff --git a/src/ThirdParty/coretrace-compiler/windows/ct_runtime_backtrace.cpp b/src/ThirdParty/coretrace-compiler/windows/ct_runtime_backtrace.cpp deleted file mode 100644 index b5767d3..0000000 --- a/src/ThirdParty/coretrace-compiler/windows/ct_runtime_backtrace.cpp +++ /dev/null @@ -1,97 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -#include "ct_runtime_internal.h" - -#include -#include -#include -#include - -#ifndef NOMINMAX -#define NOMINMAX -#endif -#include -#include - -#pragma comment(lib, "Dbghelp.lib") - -namespace -{ - std::atomic ct_backtrace_installed{0}; - std::once_flag ct_symbols_once; - - CT_NOINSTR void ct_ensure_symbols(void) - { - std::call_once(ct_symbols_once, [] - { - HANDLE process = GetCurrentProcess(); - SymSetOptions(SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); - (void)SymInitialize(process, nullptr, TRUE); - }); - } - - CT_NOINSTR void ct_write_stack_frame(HANDLE process, void* frame) - { - ct_write_prefix(CTLevel::Error); - ct_write_cstr(" at "); - ct_write_hex(reinterpret_cast(frame)); - - char symbol_buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME] = {}; - auto* symbol = reinterpret_cast(symbol_buffer); - symbol->SizeOfStruct = sizeof(SYMBOL_INFO); - symbol->MaxNameLen = MAX_SYM_NAME; - - DWORD64 displacement = 0; - if (SymFromAddr(process, reinterpret_cast(frame), &displacement, symbol) != FALSE) - { - ct_write_cstr(" "); - ct_write_cstr(symbol->Name); - } - - ct_write_cstr("\n"); - } - - CT_NOINSTR LONG WINAPI ct_exception_filter(EXCEPTION_POINTERS* exception_info) - { - ct_disable_logging(); - ct_ensure_symbols(); - - DWORD code = exception_info ? exception_info->ExceptionRecord->ExceptionCode - : static_cast(0xFFFFFFFFu); - - ct_write_prefix(CTLevel::Error); - ct_write_cstr("ct: fatal exception code="); - ct_write_hex(static_cast(code)); - ct_write_cstr("\n"); - - void* frames[64] = {}; - const USHORT count = - CaptureStackBackTrace(0, static_cast(std::size(frames)), frames, nullptr); - HANDLE process = GetCurrentProcess(); - for (USHORT index = 0; index < count; ++index) - { - ct_write_stack_frame(process, frames[index]); - } - - return EXCEPTION_EXECUTE_HANDLER; - } -} // namespace - -CT_NOINSTR void ct_maybe_install_backtrace(void) -{ - if (std::getenv("CT_BACKTRACE") == nullptr) - { - return; - } - - int expected = 0; - if (!ct_backtrace_installed.compare_exchange_strong(expected, 1, std::memory_order_acq_rel)) - { - return; - } - - ct_ensure_symbols(); - SetUnhandledExceptionFilter(ct_exception_filter); - - ct_write_prefix(CTLevel::Info); - ct_write_cstr("ct: backtrace handler installed\n"); -} diff --git a/src/ThirdParty/coretrace-compiler/windows/ct_runtime_env.cpp b/src/ThirdParty/coretrace-compiler/windows/ct_runtime_env.cpp deleted file mode 100644 index ca82dca..0000000 --- a/src/ThirdParty/coretrace-compiler/windows/ct_runtime_env.cpp +++ /dev/null @@ -1,142 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -#include "ct_runtime_internal.h" - -#include -#include - -#if defined(_M_IX86) -#define CT_ALTNAME(symbol, fallback) __pragma(comment(linker, "/alternatename:_" #symbol "=_" #fallback)) -#else -#define CT_ALTNAME(symbol, fallback) __pragma(comment(linker, "/alternatename:" #symbol "=" #fallback)) -#endif - -extern "C" -{ - __declspec(selectany) int __ct_config_shadow_default = 0; - __declspec(selectany) int __ct_config_shadow_aggressive_default = 0; - __declspec(selectany) int __ct_config_bounds_no_abort_default = 0; - __declspec(selectany) int __ct_config_disable_alloc_default = 0; - __declspec(selectany) int __ct_config_disable_autofree_default = 0; - __declspec(selectany) int __ct_config_disable_alloc_trace_default = 0; - __declspec(selectany) int __ct_config_vtable_diag_default = 0; - - CT_ALTNAME(__ct_config_shadow, __ct_config_shadow_default) - CT_ALTNAME(__ct_config_shadow_aggressive, __ct_config_shadow_aggressive_default) - CT_ALTNAME(__ct_config_bounds_no_abort, __ct_config_bounds_no_abort_default) - CT_ALTNAME(__ct_config_disable_alloc, __ct_config_disable_alloc_default) - CT_ALTNAME(__ct_config_disable_autofree, __ct_config_disable_autofree_default) - CT_ALTNAME(__ct_config_disable_alloc_trace, __ct_config_disable_alloc_trace_default) - CT_ALTNAME(__ct_config_vtable_diag, __ct_config_vtable_diag_default) - - extern int __ct_config_shadow; - extern int __ct_config_shadow_aggressive; - extern int __ct_config_bounds_no_abort; - extern int __ct_config_disable_alloc; - extern int __ct_config_disable_autofree; - extern int __ct_config_disable_alloc_trace; - extern int __ct_config_vtable_diag; -} - -namespace -{ - std::atomic ct_env_initialized{0}; - - CT_NOINSTR void ct_apply_compiled_config(void) - { - if (__ct_config_shadow || __ct_config_shadow_aggressive) - { - ct_set_enabled(CT_FEATURE_SHADOW, 1); - } - if (__ct_config_shadow_aggressive) - { - ct_set_enabled(CT_FEATURE_SHADOW_AGGR, 1); - } - if (__ct_config_bounds_no_abort) - { - ct_set_bounds_abort(0); - } - if (__ct_config_disable_alloc) - { - ct_set_enabled(CT_FEATURE_ALLOC, 0); - ct_alloc_disabled_by_config = 1; - } - if (__ct_config_disable_autofree) - { - ct_set_enabled(CT_FEATURE_AUTOFREE, 0); - } - if (__ct_config_disable_alloc_trace) - { - ct_set_enabled(CT_FEATURE_ALLOC_TRACE, 0); - } - if (__ct_config_vtable_diag) - { - ct_set_enabled(CT_FEATURE_VTABLE_DIAG, 1); - } - } - - CT_NOINSTR void ct_apply_env_config(void) - { - if (std::getenv("CT_DISABLE_TRACE") != nullptr) - { - ct_set_enabled(CT_FEATURE_TRACE, 0); - } - if (std::getenv("CT_DISABLE_ALLOC") != nullptr) - { - ct_set_enabled(CT_FEATURE_ALLOC, 0); - ct_alloc_disabled_by_env = 1; - } - if (std::getenv("CT_EARLY_TRACE") != nullptr) - { - ct_set_enabled(CT_FEATURE_EARLY_TRACE, 1); - } - if (std::getenv("CT_DISABLE_BOUNDS") != nullptr) - { - ct_set_enabled(CT_FEATURE_BOUNDS, 0); - } - if (std::getenv("CT_BOUNDS_NO_ABORT") != nullptr) - { - ct_set_bounds_abort(0); - } - if (std::getenv("CT_SHADOW") != nullptr) - { - ct_set_enabled(CT_FEATURE_SHADOW, 1); - } - if (std::getenv("CT_SHADOW_AGGRESSIVE") != nullptr) - { - ct_set_enabled(CT_FEATURE_SHADOW, 1); - ct_set_enabled(CT_FEATURE_SHADOW_AGGR, 1); - } - if (std::getenv("CT_DISABLE_AUTOFREE") != nullptr) - { - ct_set_enabled(CT_FEATURE_AUTOFREE, 0); - } - if (std::getenv("CT_DISABLE_ALLOC_TRACE") != nullptr) - { - ct_set_enabled(CT_FEATURE_ALLOC_TRACE, 0); - } - } - - struct CtRuntimeInit - { - CT_NOINSTR CtRuntimeInit() - { - ct_maybe_install_backtrace(); - ct_apply_compiled_config(); - ct_apply_env_config(); - } - }; - - CtRuntimeInit ct_runtime_init; -} // namespace - -CT_NOINSTR void ct_init_env_once(void) -{ - int expected = 0; - if (!ct_env_initialized.compare_exchange_strong(expected, 1, std::memory_order_acq_rel)) - { - return; - } - - ct_apply_compiled_config(); - ct_apply_env_config(); -} diff --git a/src/ThirdParty/coretrace-compiler/windows/ct_runtime_helpers.h b/src/ThirdParty/coretrace-compiler/windows/ct_runtime_helpers.h deleted file mode 100644 index b715ac5..0000000 --- a/src/ThirdParty/coretrace-compiler/windows/ct_runtime_helpers.h +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -#pragma once - -#include -#include - -#if defined(_WIN32) -#ifndef NOMINMAX -#define NOMINMAX -#endif -#include -#include - -inline bool ct_demangle(const char* name, std::string& out) -{ - if (!name || name[0] == '\0') - { - return false; - } - - char buffer[1024]; - if (UnDecorateSymbolName(name, buffer, static_cast(sizeof(buffer)), UNDNAME_COMPLETE) == - 0) - { - return false; - } - - out.assign(buffer); - return true; -} -#else -#include -#include - -__attribute__((no_instrument_function)) inline bool ct_demangle(const char* name, std::string& out) -{ - if (!name) - { - return false; - } - if (!(name[0] == '_' && name[1] == 'Z')) - { - return false; - } - - int status = 0; - size_t length = 0; - char* demangled = abi::__cxa_demangle(name, nullptr, &length, &status); - if (status == 0 && demangled) - { - out.assign(demangled); - std::free(demangled); - return true; - } - if (demangled) - { - std::free(demangled); - } - return false; -} -#endif diff --git a/src/ThirdParty/coretrace-compiler/windows/ct_runtime_internal.h b/src/ThirdParty/coretrace-compiler/windows/ct_runtime_internal.h deleted file mode 100644 index 0fcc954..0000000 --- a/src/ThirdParty/coretrace-compiler/windows/ct_runtime_internal.h +++ /dev/null @@ -1,254 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -#ifndef CT_RUNTIME_INTERNAL_H -#define CT_RUNTIME_INTERNAL_H - -#include "compilerlib/attributes.hpp" - -#include - -#include -#include -#include -#include -#include -#include -#include - -#if defined(_WIN32) -#ifndef NOMINMAX -#define NOMINMAX -#endif -#include -#include -#endif - -#if defined(_MSC_VER) -#define CT_NOINSTR - -#ifndef __ATOMIC_ACQUIRE -#define __ATOMIC_ACQUIRE 2 -#endif -#ifndef __ATOMIC_RELEASE -#define __ATOMIC_RELEASE 3 -#endif -#ifndef __ATOMIC_ACQ_REL -#define __ATOMIC_ACQ_REL 4 -#endif - -inline int ct_msvc_atomic_exchange(volatile int* object, int desired) -{ - return static_cast( - InterlockedExchange(reinterpret_cast(object), static_cast(desired))); -} - -inline void ct_msvc_atomic_store(volatile int* object, int desired) -{ - (void)InterlockedExchange(reinterpret_cast(object), - static_cast(desired)); -} - -inline bool ct_msvc_atomic_compare_exchange(volatile int* object, int* expected, int desired) -{ - const long prior = - InterlockedCompareExchange(reinterpret_cast(object), - static_cast(desired), static_cast(*expected)); - if (prior == static_cast(*expected)) - { - return true; - } - - *expected = static_cast(prior); - return false; -} - -#define __atomic_exchange_n(object, desired, order) \ - ct_msvc_atomic_exchange(reinterpret_cast(object), static_cast(desired)) -#define __atomic_store_n(object, desired, order) \ - ct_msvc_atomic_store(reinterpret_cast(object), static_cast(desired)) -#define __atomic_compare_exchange_n(object, expected, desired, weak, success_order, \ - failure_order) \ - ct_msvc_atomic_compare_exchange(reinterpret_cast(object), \ - reinterpret_cast(expected), static_cast(desired)) -#else -#define CT_NOINSTR __attribute__((no_instrument_function)) -#endif - -using CTColor = coretrace::Color; -using CTLevel = coretrace::Level; - -enum -{ - CT_ENTRY_EMPTY = 0, - CT_ENTRY_USED = 1, - CT_ENTRY_TOMB = 2, - CT_ENTRY_FREED = 3, - CT_ENTRY_AUTOFREED = 4 -}; - -extern int ct_disable_trace; -extern int ct_disable_alloc; -extern int ct_disable_bounds; -extern int ct_bounds_abort; -extern int ct_shadow_enabled; -extern int ct_shadow_aggressive; -extern int ct_autofree_enabled; -extern int ct_alloc_trace_enabled; -extern int ct_vtable_diag_enabled; -extern int ct_alloc_disabled_by_config; -extern int ct_alloc_disabled_by_env; -extern int ct_early_trace; -extern size_t ct_early_trace_count; -extern size_t ct_early_trace_limit; -extern thread_local const char* ct_current_site; - -#define CT_FEATURE_TRACE (1ull << 0) -#define CT_FEATURE_ALLOC (1ull << 1) -#define CT_FEATURE_BOUNDS (1ull << 2) -#define CT_FEATURE_SHADOW (1ull << 3) -#define CT_FEATURE_SHADOW_AGGR (1ull << 4) -#define CT_FEATURE_AUTOFREE (1ull << 5) -#define CT_FEATURE_ALLOC_TRACE (1ull << 6) -#define CT_FEATURE_VTABLE_DIAG (1ull << 7) -#define CT_FEATURE_EARLY_TRACE (1ull << 8) - -extern "C" -{ - CT_NODISCARD CT_NOINSTR int ct_is_enabled(uint64_t feature); - CT_NOINSTR void ct_set_enabled(uint64_t feature, int enabled); - CT_NODISCARD CT_NOINSTR uint64_t ct_get_features(void); - - CT_NODISCARD CT_NOINSTR int ct_bounds_abort_enabled(void); - CT_NOINSTR void ct_set_bounds_abort(int enabled); - - CT_NODISCARD CT_NOINSTR int ct_early_trace_should_log(void); -} - -CT_NODISCARD CT_NOINSTR size_t ct_strlen(const char* str); -CT_NODISCARD CT_NOINSTR int ct_streq(const char* lhs, const char* rhs); -CT_NODISCARD CT_NOINSTR const char* ct_site_name(const char* site); -CT_NOINSTR void ct_maybe_install_backtrace(void); -CT_NOINSTR void ct_init_env_once(void); -CT_NOINSTR void ct_lock_acquire(void); -CT_NOINSTR void ct_lock_release(void); -CT_NODISCARD CT_NOINSTR int ct_table_insert(void* ptr, size_t req_size, size_t size, - const char* site, unsigned char kind); -CT_NODISCARD CT_NOINSTR int ct_table_remove(void* ptr, size_t* size_out, size_t* req_size_out, - const char** site_out); -CT_NODISCARD CT_NOINSTR int ct_table_lookup(const void* ptr, size_t* size_out, size_t* req_size_out, - const char** site_out, unsigned char* state_out); -CT_NODISCARD CT_NOINSTR int ct_table_lookup_containing(const void* ptr, void** base_out, - size_t* size_out, size_t* req_size_out, - const char** site_out, - unsigned char* state_out); -CT_NOINSTR void ct_shadow_poison_range(const void* addr, size_t size); -CT_NOINSTR void ct_shadow_unpoison_range(const void* addr, size_t size); -CT_NODISCARD CT_NOINSTR int ct_shadow_check_access(const void* ptr, size_t access_size, - const void* base, size_t req_size, - size_t alloc_size, const char* alloc_site, - const char* site, int is_write, - unsigned char state); -CT_NOINSTR void ct_report_bounds_error(const void* base, const void* ptr, size_t access_size, - const char* site, int is_write, size_t req_size, - size_t alloc_size, const char* alloc_site, - unsigned char state); - -CT_NODISCARD CT_NOINSTR inline std::string_view ct_color(CTColor color) -{ - return coretrace::color(color); -} - -CT_NODISCARD CT_NOINSTR inline std::string_view ct_level_label(CTLevel level) -{ - return coretrace::level_label(level); -} - -CT_NODISCARD CT_NOINSTR inline std::string_view ct_level_color(CTLevel level) -{ - return coretrace::level_color(level); -} - -CT_NODISCARD CT_NOINSTR inline int ct_pid(void) -{ - return coretrace::pid(); -} - -CT_NODISCARD CT_NOINSTR inline unsigned long long ct_thread_id(void) -{ - return coretrace::thread_id(); -} - -CT_NODISCARD CT_NOINSTR inline int ct_log_is_enabled(void) -{ - return coretrace::log_is_enabled() ? 1 : 0; -} - -CT_NOINSTR inline void ct_enable_logging(void) -{ - coretrace::enable_logging(); -} - -CT_NOINSTR inline void ct_disable_logging(void) -{ - coretrace::disable_logging(); -} - -CT_NOINSTR inline void ct_write_raw(const char* data, size_t size) -{ - coretrace::write_raw(data, size); -} - -CT_NOINSTR inline void ct_write_str(std::string_view str) -{ - coretrace::write_str(str); -} - -CT_NOINSTR inline void ct_write_cstr(const char* str) -{ - if (!str) - { - return; - } - coretrace::write_str(std::string_view(str)); -} - -CT_NOINSTR inline void ct_write_dec(size_t value) -{ - coretrace::write_dec(value); -} - -CT_NOINSTR inline void ct_write_hex(uintptr_t value) -{ - coretrace::write_hex(value); -} - -CT_NOINSTR inline void ct_write_prefix(CTLevel level) -{ - coretrace::write_prefix(level); -} - -template -CT_NOINSTR inline void ct_log(CTLevel level, std::string_view fmt, Args&&... args) -{ - if (!coretrace::log_is_enabled()) - { - return; - } - - try - { - std::string msg = std::vformat(fmt, std::make_format_args(args...)); - if (msg.empty()) - { - return; - } - - coretrace::write_log_line(level, {}, msg, std::source_location::current()); - } - catch (...) - { - static constexpr char fallback[] = "ct: log format error\n"; - coretrace::write_raw(fallback, sizeof(fallback) - 1); - } -} - -#endif // CT_RUNTIME_INTERNAL_H diff --git a/src/ThirdParty/coretrace-compiler/windows/ct_runtime_vtable.cpp b/src/ThirdParty/coretrace-compiler/windows/ct_runtime_vtable.cpp deleted file mode 100644 index b2af2a7..0000000 --- a/src/ThirdParty/coretrace-compiler/windows/ct_runtime_vtable.cpp +++ /dev/null @@ -1,672 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -#include "ct_runtime_internal.h" - -#include "ct_runtime_helpers.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifndef NOMINMAX -#define NOMINMAX -#endif -#include -#include - -#pragma comment(lib, "Dbghelp.lib") - -namespace -{ - constexpr size_t kBoxMaxValueWidth = 60; - - struct CtRttiCompleteObjectLocator - { - std::uint32_t signature; - std::uint32_t offset; - std::uint32_t cd_offset; - std::int32_t type_descriptor_rva; - std::int32_t class_descriptor_rva; - std::int32_t self_rva; - }; - - struct CtRttiTypeDescriptor - { - const void* vftable; - void* spare; - char name[1]; - }; - - struct CtVtableInfo - { - const void* vtable = nullptr; - ptrdiff_t offset_to_top = 0; - std::string dynamic_type = ""; - bool has_typeinfo = false; - }; - - struct CtBoxLine - { - std::string label; - std::string value; - }; - - struct CtModuleInfo - { - bool resolved = false; - bool is_main = false; - bool exec_known = false; - bool is_exec = false; - HMODULE handle = nullptr; - std::string path; - std::string basename; - }; - - struct CtAddrInfo - { - bool has_module = false; - bool exec_known = false; - bool is_exec = false; - bool on_stack = false; - CtModuleInfo module; - }; - - std::once_flag ct_symbols_once; - std::atomic ct_vtable_state_logged{0}; - - CT_NOINSTR void ct_ensure_symbols(void) - { - std::call_once(ct_symbols_once, [] - { - HANDLE process = GetCurrentProcess(); - SymSetOptions(SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); - (void)SymInitialize(process, nullptr, TRUE); - }); - } - - CT_NODISCARD CT_NOINSTR std::string ct_basename(std::string_view path) - { - if (path.empty()) - { - return {}; - } - - const size_t pos = path.find_last_of("/\\"); - if (pos == std::string_view::npos) - { - return std::string(path); - } - return std::string(path.substr(pos + 1)); - } - - CT_NODISCARD CT_NOINSTR bool ct_is_executable_protection(DWORD protect) - { - const DWORD normalized = protect & 0xFFu; - return normalized == PAGE_EXECUTE || normalized == PAGE_EXECUTE_READ || - normalized == PAGE_EXECUTE_READWRITE || normalized == PAGE_EXECUTE_WRITECOPY; - } - - CT_NODISCARD CT_NOINSTR bool ct_is_readable(const void* addr, size_t size) - { - if (!addr || size == 0) - { - return false; - } - - MEMORY_BASIC_INFORMATION mbi = {}; - if (VirtualQuery(addr, &mbi, sizeof(mbi)) == 0) - { - return false; - } - - if (mbi.State != MEM_COMMIT) - { - return false; - } - if ((mbi.Protect & PAGE_NOACCESS) != 0 || (mbi.Protect & PAGE_GUARD) != 0) - { - return false; - } - - const uintptr_t start = reinterpret_cast(addr); - const uintptr_t end = start + size; - const uintptr_t region_end = reinterpret_cast(mbi.BaseAddress) + mbi.RegionSize; - return end >= start && end <= region_end; - } - - CT_NODISCARD CT_NOINSTR bool ct_lookup_symbol_name(const void* addr, std::string& out) - { - if (!addr) - { - return false; - } - - ct_ensure_symbols(); - - char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME] = {}; - auto* symbol = reinterpret_cast(buffer); - symbol->SizeOfStruct = sizeof(SYMBOL_INFO); - symbol->MaxNameLen = MAX_SYM_NAME; - - DWORD64 displacement = 0; - if (SymFromAddr(GetCurrentProcess(), reinterpret_cast(addr), &displacement, symbol) == - FALSE) - { - return false; - } - - out.assign(symbol->Name); - return true; - } - - CT_NODISCARD CT_NOINSTR bool ct_address_on_stack(const void* addr) - { - if (!addr) - { - return false; - } - - using GetCurrentThreadStackLimitsFn = VOID(WINAPI*)(PULONG_PTR, PULONG_PTR); - static auto* fn = reinterpret_cast( - GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "GetCurrentThreadStackLimits")); - if (!fn) - { - return false; - } - - ULONG_PTR low = 0; - ULONG_PTR high = 0; - fn(&low, &high); - - const auto value = reinterpret_cast(addr); - return value >= low && value < high; - } - - CT_NODISCARD CT_NOINSTR bool ct_resolve_module(const void* addr, CtModuleInfo& out) - { - if (!addr) - { - return false; - } - - MEMORY_BASIC_INFORMATION mbi = {}; - if (VirtualQuery(addr, &mbi, sizeof(mbi)) != 0) - { - out.exec_known = true; - out.is_exec = ct_is_executable_protection(mbi.Protect); - } - - HMODULE module = nullptr; - if (!GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | - GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, - reinterpret_cast(addr), &module)) - { - return false; - } - - char path_buffer[MAX_PATH] = {}; - const DWORD length = GetModuleFileNameA(module, path_buffer, MAX_PATH); - - out.resolved = true; - out.handle = module; - out.is_main = (module == GetModuleHandleA(nullptr)); - if (length != 0) - { - out.path.assign(path_buffer, length); - out.basename = ct_basename(out.path); - } - return true; - } - - CT_NODISCARD CT_NOINSTR CtAddrInfo ct_resolve_address(const void* addr) - { - CtAddrInfo info; - if (!addr) - { - return info; - } - - if (ct_resolve_module(addr, info.module)) - { - info.has_module = true; - info.exec_known = info.module.exec_known; - info.is_exec = info.module.is_exec; - return info; - } - - info.on_stack = ct_address_on_stack(addr); - if (info.on_stack) - { - info.exec_known = true; - info.is_exec = false; - } - return info; - } - - CT_NODISCARD CT_NOINSTR bool ct_modules_match(const CtModuleInfo& lhs, const CtModuleInfo& rhs) - { - return lhs.resolved && rhs.resolved && lhs.handle != nullptr && lhs.handle == rhs.handle; - } - - CT_NODISCARD CT_NOINSTR std::string ct_module_display_name(const CtModuleInfo& info) - { - if (!info.resolved) - { - return ""; - } - if (info.is_main) - { - return "main"; - } - if (!info.basename.empty()) - { - return info.basename; - } - if (!info.path.empty()) - { - return info.path; - } - return ""; - } - - CT_NODISCARD CT_NOINSTR const CtRttiCompleteObjectLocator* - ct_get_complete_object_locator(const void* vtable) - { - if (!vtable) - { - return nullptr; - } - - auto* table = reinterpret_cast(vtable); - const void* locator_ptr = table[-1]; - if (!ct_is_readable(locator_ptr, sizeof(CtRttiCompleteObjectLocator))) - { - return nullptr; - } - - return reinterpret_cast(locator_ptr); - } - - CT_NODISCARD CT_NOINSTR const CtRttiTypeDescriptor* - ct_get_type_descriptor(const CtRttiCompleteObjectLocator* locator) - { - if (!locator) - { - return nullptr; - } - - const uintptr_t image_base = - reinterpret_cast(locator) - static_cast(locator->self_rva); - const uintptr_t type_addr = - image_base + static_cast(locator->type_descriptor_rva); - if (!ct_is_readable(reinterpret_cast(type_addr), sizeof(CtRttiTypeDescriptor))) - { - return nullptr; - } - - return reinterpret_cast(type_addr); - } - - CT_NODISCARD CT_NOINSTR std::string ct_format_type_name(const CtRttiTypeDescriptor* type_descriptor) - { - if (!type_descriptor || !ct_is_readable(type_descriptor, sizeof(CtRttiTypeDescriptor))) - { - return ""; - } - - const char* raw_name = type_descriptor->name; - if (!raw_name || raw_name[0] == '\0') - { - return ""; - } - - std::string demangled; - if (ct_demangle(raw_name, demangled)) - { - return demangled; - } - - return raw_name; - } - - CT_NODISCARD CT_NOINSTR bool ct_read_vtable_info(void* this_ptr, CtVtableInfo& info) - { - if (!this_ptr) - { - return false; - } - - const void* vtable = *reinterpret_cast(this_ptr); - if (!vtable) - { - return false; - } - - info.vtable = vtable; - - const CtRttiCompleteObjectLocator* locator = ct_get_complete_object_locator(vtable); - if (!locator) - { - return true; - } - - info.offset_to_top = static_cast(locator->offset); - info.dynamic_type = ct_format_type_name(ct_get_type_descriptor(locator)); - info.has_typeinfo = info.dynamic_type != ""; - return true; - } - - CT_NODISCARD CT_NOINSTR bool ct_is_unknown_type(const char* type_name) - { - return !type_name || type_name[0] == '\0' || ct_streq(type_name, ""); - } - - CT_NOINSTR void ct_append_box_line(std::vector& lines, std::string label, - std::string value) - { - lines.push_back({std::move(label), std::move(value)}); - } - - CT_NOINSTR void ct_log_box(CTLevel level, const char* tag, const char* title, - const std::vector& lines) - { - if (lines.empty()) - { - return; - } - - size_t label_width = 0; - size_t value_width = 0; - for (const auto& line : lines) - { - label_width = std::max(label_width, line.label.size()); - value_width = std::max(value_width, std::min(line.value.size(), kBoxMaxValueWidth)); - } - value_width = std::max(value_width, 1); - - const size_t inner_width = label_width + value_width + 5; - std::string border(inner_width, '-'); - - ct_log(level, "[{}]\n", tag ? tag : "BOX"); - ct_log(level, "+{}+\n", border); - ct_log(level, "| {}{}\n", title ? title : "box", - std::string(inner_width > ct_strlen(title ? title : "box") + 1 - ? inner_width - ct_strlen(title ? title : "box") - 1 - : 0, - ' ')); - ct_log(level, "+{}+\n", border); - - for (const auto& line : lines) - { - std::string value = line.value.empty() ? "" : line.value; - size_t offset = 0; - bool first = true; - while (offset < value.size()) - { - const size_t remaining = value.size() - offset; - const size_t chunk = std::min(remaining, value_width); - std::string part = value.substr(offset, chunk); - - std::string label = first ? line.label : std::string(); - if (label.size() < label_width) - { - label.append(label_width - label.size(), ' '); - } - if (part.size() < value_width) - { - part.append(value_width - part.size(), ' '); - } - - ct_log(level, "| {} : {} |\n", label, part); - offset += chunk; - first = false; - } - } - - ct_log(level, "+{}+\n", border); - } - - CT_NOINSTR void ct_log_vtable_diag_state(void) - { - if (!ct_is_enabled(CT_FEATURE_VTABLE_DIAG)) - { - return; - } - - int expected = 0; - if (!ct_vtable_state_logged.compare_exchange_strong(expected, 1, std::memory_order_acq_rel)) - { - return; - } - - if (ct_is_enabled(CT_FEATURE_ALLOC)) - { - ct_log(CTLevel::Info, "[VTABLE-DIAG]: alloc-tracking=enabled\n"); - return; - } - - std::string reason = "unknown"; - if (ct_alloc_disabled_by_env) - { - reason = "env CT_DISABLE_ALLOC"; - } - else if (ct_alloc_disabled_by_config) - { - reason = "compile-time --ct-no-alloc/--ct-modules"; - } - ct_log(CTLevel::Info, "[VTABLE-DIAG]: alloc-tracking=disabled (reason={})\n", reason); - } -} // namespace - -extern "C" -{ - CT_NOINSTR void __ct_vtable_dump(void* this_ptr, const char* site, const char* static_type) - { - ct_init_env_once(); - if (!ct_log_is_enabled()) - { - ct_enable_logging(); - ct_maybe_install_backtrace(); - } - ct_log_vtable_diag_state(); - - CtVtableInfo info; - const bool has_vtable = ct_read_vtable_info(this_ptr, info); - const char* site_name = ct_site_name(site); - - std::vector lines; - ct_append_box_line(lines, "site", site_name ? site_name : ""); - ct_append_box_line(lines, "this", - this_ptr ? std::format("{:p}", this_ptr) : std::string("")); - ct_append_box_line(lines, "vtable", - (has_vtable && info.vtable) ? std::format("{:p}", info.vtable) - : std::string("")); - if (has_vtable) - { - ct_append_box_line(lines, "off_top", std::to_string(info.offset_to_top)); - } - ct_append_box_line(lines, "type", info.dynamic_type); - if (ct_is_enabled(CT_FEATURE_VTABLE_DIAG) && !ct_is_unknown_type(static_type)) - { - ct_append_box_line(lines, "static", static_type); - } - - std::vector warnings; - if (!this_ptr) - { - warnings.push_back("null this pointer"); - } - if (!has_vtable) - { - warnings.push_back("no vptr"); - } - if (has_vtable && !info.has_typeinfo) - { - warnings.push_back("missing RTTI"); - } - - if (has_vtable) - { - const CtAddrInfo vtable_addr = ct_resolve_address(info.vtable); - if (vtable_addr.has_module) - { - ct_append_box_line(lines, "vmod", ct_module_display_name(vtable_addr.module)); - } - else - { - warnings.push_back("vtable resolve failed"); - } - } - - if (ct_is_enabled(CT_FEATURE_ALLOC)) - { - unsigned char state = 0; - ct_lock_acquire(); - const int found = - ct_table_lookup_containing(this_ptr, nullptr, nullptr, nullptr, nullptr, &state); - ct_lock_release(); - if (found && state == CT_ENTRY_FREED) - { - warnings.push_back("vptr on freed object"); - } - } - - if (!ct_is_unknown_type(static_type) && info.dynamic_type != "" && - info.dynamic_type != static_type) - { - warnings.push_back("static!=dynamic type"); - } - - for (const auto& warning : warnings) - { - ct_append_box_line(lines, "warn", warning); - } - - ct_log_box(warnings.empty() ? CTLevel::Info : CTLevel::Warn, "VTABLE", "vtable", lines); - } - - CT_NOINSTR void __ct_vcall_trace(void* this_ptr, void* target, const char* site, - const char* static_type) - { - ct_init_env_once(); - if (!ct_log_is_enabled()) - { - ct_enable_logging(); - ct_maybe_install_backtrace(); - } - ct_log_vtable_diag_state(); - - CtVtableInfo info; - const bool has_vtable = ct_read_vtable_info(this_ptr, info); - const char* site_name = ct_site_name(site); - - std::string symbol_name = ""; - std::string demangled_name = ""; - if (target && ct_lookup_symbol_name(target, symbol_name)) - { - std::string pretty; - if (ct_demangle(symbol_name.c_str(), pretty)) - { - demangled_name = pretty; - } - else - { - demangled_name = symbol_name; - } - } - - std::vector lines; - ct_append_box_line(lines, "site", site_name ? site_name : ""); - ct_append_box_line(lines, "this", - this_ptr ? std::format("{:p}", this_ptr) : std::string("")); - ct_append_box_line(lines, "vtable", - (has_vtable && info.vtable) ? std::format("{:p}", info.vtable) - : std::string("")); - ct_append_box_line(lines, "type", info.dynamic_type); - ct_append_box_line(lines, "target", - target ? std::format("{:p}", target) : std::string("")); - ct_append_box_line(lines, "symbol", symbol_name); - ct_append_box_line(lines, "demangled", demangled_name); - if (ct_is_enabled(CT_FEATURE_VTABLE_DIAG) && !ct_is_unknown_type(static_type)) - { - ct_append_box_line(lines, "static", static_type); - } - - std::vector warnings; - if (!this_ptr) - { - warnings.push_back("null this pointer"); - } - if (!has_vtable) - { - warnings.push_back("no vptr"); - } - if (has_vtable && !info.has_typeinfo) - { - warnings.push_back("missing RTTI"); - } - - const CtAddrInfo vtable_addr = has_vtable ? ct_resolve_address(info.vtable) : CtAddrInfo{}; - const CtAddrInfo target_addr = target ? ct_resolve_address(target) : CtAddrInfo{}; - - if (vtable_addr.has_module) - { - ct_append_box_line(lines, "vmod", ct_module_display_name(vtable_addr.module)); - } - else if (has_vtable) - { - warnings.push_back("vtable resolve failed"); - } - - if (target_addr.has_module) - { - ct_append_box_line(lines, "tmod", ct_module_display_name(target_addr.module)); - } - else if (target_addr.exec_known && !target_addr.is_exec) - { - warnings.push_back("target in non-exec memory"); - } - else if (target_addr.on_stack) - { - warnings.push_back("target points to stack memory"); - } - - if (ct_is_enabled(CT_FEATURE_ALLOC)) - { - unsigned char state = 0; - ct_lock_acquire(); - const int found = - ct_table_lookup_containing(this_ptr, nullptr, nullptr, nullptr, nullptr, &state); - ct_lock_release(); - if (found && state == CT_ENTRY_FREED) - { - warnings.push_back("vptr on freed object"); - } - } - - if (!ct_is_unknown_type(static_type) && info.dynamic_type != "" && - info.dynamic_type != static_type) - { - warnings.push_back("static!=dynamic type"); - } - - if (vtable_addr.has_module && target_addr.has_module && - !ct_modules_match(vtable_addr.module, target_addr.module)) - { - warnings.push_back("module mismatch between vtable and target"); - } - - for (const auto& warning : warnings) - { - ct_append_box_line(lines, "warn", warning); - } - - ct_log_box(warnings.empty() ? CTLevel::Info : CTLevel::Warn, "VCALL", "vcall", lines); - } -} diff --git a/src/ThirdParty/coretrace-stack-analyzer/windows/mangle.cpp b/src/ThirdParty/coretrace-stack-analyzer/windows/mangle.cpp deleted file mode 100644 index 33021c2..0000000 --- a/src/ThirdParty/coretrace-stack-analyzer/windows/mangle.cpp +++ /dev/null @@ -1,91 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -#include "mangle.hpp" - -namespace ctrace_tools -{ - std::string mangleFunction(const std::string& namespaceName, const std::string& functionName, - const std::vector& paramTypes) - { - std::stringstream mangled; - - mangled << "_Z"; - - if (!namespaceName.empty()) - { - mangled << "N"; - mangled << namespaceName.length() << namespaceName; - } - - mangled << functionName.length() << functionName; - - for (const std::string& param : paramTypes) - { - if (param == "int") - { - mangled << "i"; - } - else if (param == "double") - { - mangled << "d"; - } - else if (param == "char") - { - mangled << "c"; - } - else if (param == "std::string") - { - mangled << "Ss"; - } - else if (param == "float") - { - mangled << "f"; - } - else if (param == "bool") - { - mangled << "b"; - } - else if (param == "void") - { - mangled << "v"; - } - else - { - mangled << param.length() << param; - } - } - - if (!namespaceName.empty()) - { - mangled << "E"; - } - - return mangled.str(); - } - - std::string demangle(const char* name) - { - if (name == nullptr) - { - return {}; - } - - return llvm::demangle(name); - } - - std::string canonicalizeMangledName(std::string_view name) - { - std::string result(name); - - for (std::size_t pos = 0; (pos = result.find("St3__1", pos)) != std::string::npos;) - { - result.replace(pos, 6, "St"); - } - - for (std::size_t pos = 0; (pos = result.find("St7__cxx11", pos)) != std::string::npos;) - { - result.replace(pos, 10, "St"); - } - - return result; - } -} // namespace ctrace_tools diff --git a/src/ThirdParty/coretrace-stack-analyzer/windows/mangle.hpp b/src/ThirdParty/coretrace-stack-analyzer/windows/mangle.hpp deleted file mode 100644 index a448317..0000000 --- a/src/ThirdParty/coretrace-stack-analyzer/windows/mangle.hpp +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -#pragma once - -#include -#include -#include -#include -#include - -#include - -namespace ctrace_tools -{ - template concept StringLike = std::convertible_to; - - [[nodiscard]] inline bool isMangled(StringLike auto name) noexcept - { - const std::string_view sv{name}; - if (sv.empty()) - { - return false; - } - - // Itanium names usually start with _Z, while MSVC names commonly - // start with ? or ??. Use a cheap prefix check before calling into - // LLVM's demangler. - const bool looksMangled = - sv.starts_with("_Z") || sv.starts_with("?") || sv.starts_with(".?"); - if (!looksMangled) - { - return false; - } - - return llvm::demangle(sv) != sv; - } - - [[nodiscard]] std::string mangleFunction(const std::string& namespaceName, - const std::string& functionName, - const std::vector& paramTypes); - - [[nodiscard]] std::string demangle(const char* name); - - [[nodiscard]] std::string canonicalizeMangledName(std::string_view name); -} // namespace ctrace_tools From 7e3c1b31f65b98dfc8d60253077c7be3f6629f61 Mon Sep 17 00:00:00 2001 From: shookapic Date: Wed, 15 Apr 2026 14:26:21 +0900 Subject: [PATCH 3/6] fix: windows build script, added build folder to gitignore --- .gitignore | 2 ++ scripts/build-windows.ps1 | 37 +++++++++++++++++++++++++++++++++---- 2 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d8cce8f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +dist +build-win \ No newline at end of file diff --git a/scripts/build-windows.ps1 b/scripts/build-windows.ps1 index 9b7e01c..2bc5705 100644 --- a/scripts/build-windows.ps1 +++ b/scripts/build-windows.ps1 @@ -142,6 +142,8 @@ if (-not (Test-Path $clangClPath)) throw "clang-cl.exe was not found in '$llvmBinDir'." } +$clangExePath = Join-Path $llvmBinDir "clang.exe" + $vswhere = Join-Path ${env:ProgramFiles(x86)} "Microsoft Visual Studio\Installer\vswhere.exe" if (-not (Test-Path $vswhere)) { @@ -183,6 +185,20 @@ if ((Test-Path $llvmExportsPath) -and (Test-Path $diaguidsCandidate)) New-PatchedCMakePackageDir -SourceDir $installedLLVMDir -DestinationDir $patchedLLVMDir -OldValue $staleDiaPath -NewValue $replacementDiaPath -ImportPrefix $importPrefix New-PatchedCMakePackageDir -SourceDir $installedClangDir -DestinationDir $patchedClangDir -OldValue $staleDiaPath -NewValue $replacementDiaPath -ImportPrefix $importPrefix + # LLVMConfig.cmake computes LLVM_INSTALL_PREFIX relative to its own location, + # which resolves to __llvm_cmake_patched instead of C:\LLVM. + # Create junctions so paths under the patched root still resolve correctly. + $patchedIncludeDir = Join-Path $patchedRoot "include" + if (-not (Test-Path $patchedIncludeDir)) + { + cmd /c mklink /J "$patchedIncludeDir" "$llvmRoot\include" | Out-Null + } + $patchedBinDir = Join-Path $patchedRoot "bin" + if (-not (Test-Path $patchedBinDir)) + { + cmd /c mklink /J "$patchedBinDir" "$llvmBinDir" | Out-Null + } + $resolvedLLVMDir = $patchedLLVMDir $resolvedClangDir = $patchedClangDir } @@ -196,7 +212,9 @@ $cmakeArgs = @( "-DUSE_THREAD_SANITIZER=OFF", "-DUSE_ADDRESS_SANITIZER=OFF", "-DLLVM_DIR=$resolvedLLVMDir", - "-DClang_DIR=$resolvedClangDir" + "-DClang_DIR=$resolvedClangDir", + "-DCMAKE_PREFIX_PATH=$llvmRoot", + "-DCLANG_EXECUTABLE=$clangExePath" ) if ($CompilerSourceDir -ne "") @@ -245,12 +263,23 @@ if ($Generator -like "Visual Studio*") else { $devShell = Join-Path $vsPath "Common7\Tools\Launch-VsDevShell.ps1" - if (-not (Test-Path $devShell)) + + if (Test-Path $devShell) + { + try + { + . $devShell -Arch amd64 -HostArch amd64 | Out-Null + } + catch + { + Write-Warning "Could not start Developer PowerShell using script path '$devShell'. Attempting to continue." + } + } + else { - throw "Unable to find Launch-VsDevShell.ps1 at '$devShell'." + Write-Warning "Launch-VsDevShell.ps1 not found at '$devShell'. Attempting to continue." } - . $devShell -Arch amd64 -HostArch amd64 | Out-Null Invoke-NativeCommand cmake @cmakeArgs } From 76ca1e8771e92c247c6c6615d5e697d90c7fffbc Mon Sep 17 00:00:00 2001 From: shookapic Date: Wed, 15 Apr 2026 17:45:04 +0900 Subject: [PATCH 4/6] fix(cicd): llvm20.1.0 installation --- .github/workflows/build.yml | 86 +++++++++++++++++++++++++++++++------ 1 file changed, 72 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 83c4cf8..27dbe49 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -158,31 +158,89 @@ jobs: path: deps/coretrace-log fetch-depth: 1 - - name: Install LLVM 20.1.0 to fixed location + - name: Install LLVM 20.1.0 archive with 7-Zip shell: pwsh run: | $ErrorActionPreference = "Stop" + $llvmVersion = "20.1.0" - $installerUrl = "https://github.com/llvm/llvm-project/releases/download/llvmorg-$llvmVersion/LLVM-$llvmVersion-win64.exe" - $installerPath = "$env:RUNNER_TEMP\LLVM-$llvmVersion-win64.exe" + $archiveName = "clang+llvm-$llvmVersion-x86_64-pc-windows-msvc.tar.xz" + $archiveUrl = "https://github.com/llvm/llvm-project/releases/download/llvmorg-$llvmVersion/$archiveName" + $archivePath = Join-Path $env:RUNNER_TEMP $archiveName + $extractRoot = Join-Path $env:RUNNER_TEMP "llvm-extract" $installRoot = "C:\Program Files\LLVM-$llvmVersion" - $llvmCmakeDir = "$installRoot\lib\cmake\llvm" - $clangCmakeDir = "$installRoot\lib\cmake\clang" + $sevenZip = "${env:ProgramFiles}\7-Zip\7z.exe" + $tarPath = Join-Path $env:RUNNER_TEMP "clang+llvm-$llvmVersion-x86_64-pc-windows-msvc.tar" + + if (-not (Test-Path $sevenZip)) { + throw "7z.exe not found at $sevenZip" + } + + Write-Host "Downloading LLVM archive from $archiveUrl" + Invoke-WebRequest -Uri $archiveUrl -OutFile $archivePath + Write-Host "Download finished" + Get-Item $archivePath | Select-Object FullName, Length | Format-Table -AutoSize + + if (Test-Path $extractRoot) { + Remove-Item -Recurse -Force $extractRoot + } + New-Item -ItemType Directory -Force -Path $extractRoot | Out-Null + + if (Test-Path $tarPath) { + Remove-Item -Force $tarPath + } + + Write-Host "Extracting .xz to .tar with 7-Zip" + & $sevenZip x $archivePath "-o$env:RUNNER_TEMP" -y + if ($LASTEXITCODE -ne 0) { + throw "7-Zip failed while extracting .xz archive" + } + + if (-not (Test-Path $tarPath)) { + throw "Expected tar file not found after xz extraction: $tarPath" + } + + Write-Host "Extracting .tar to $extractRoot" + & $sevenZip x $tarPath "-o$extractRoot" -y + if ($LASTEXITCODE -ne 0) { + throw "7-Zip failed while extracting .tar archive" + } + + Write-Host "Listing extracted directories" + Get-ChildItem $extractRoot -Directory | Select-Object Name, FullName | Format-Table -AutoSize + + $extractedDir = Get-ChildItem $extractRoot -Directory | Select-Object -First 1 + if (-not $extractedDir) { + throw "Failed to find extracted LLVM directory under $extractRoot" + } + + if (Test-Path $installRoot) { + Remove-Item -Recurse -Force $installRoot + } + Move-Item -Path $extractedDir.FullName -Destination $installRoot + + $clangClPath = Join-Path $installRoot "bin\clang-cl.exe" + $llvmCmakeDir = Join-Path $installRoot "lib\cmake\llvm" + $clangCmakeDir = Join-Path $installRoot "lib\cmake\clang" + $llvmConfigPath = Join-Path $llvmCmakeDir "LLVMConfig.cmake" + $clangConfigPath = Join-Path $clangCmakeDir "ClangConfig.cmake" - Invoke-WebRequest -Uri $installerUrl -OutFile $installerPath - Start-Process -FilePath $installerPath -ArgumentList @("/S", "/D=$installRoot") -Wait -NoNewWindow + Write-Host "Checking extracted LLVM files" + Write-Host "clang-cl path: $clangClPath" + Write-Host "LLVMConfig path: $llvmConfigPath" + Write-Host "ClangConfig path: $clangConfigPath" - if (-not (Test-Path "$installRoot\bin\clang-cl.exe")) { - throw "clang-cl.exe not found after LLVM install: $installRoot\bin\clang-cl.exe" + if (-not (Test-Path $clangClPath)) { + throw "clang-cl.exe not found after LLVM archive extraction: $clangClPath" } - if (-not (Test-Path $llvmCmakeDir)) { - throw "LLVM CMake package dir not found: $llvmCmakeDir" + if (-not (Test-Path $llvmConfigPath)) { + throw "LLVMConfig.cmake not found after LLVM archive extraction: $llvmConfigPath" } - if (-not (Test-Path $clangCmakeDir)) { - throw "Clang CMake package dir not found: $clangCmakeDir" + if (-not (Test-Path $clangConfigPath)) { + throw "ClangConfig.cmake not found after LLVM archive extraction: $clangConfigPath" } - $clangVersion = & "$installRoot\bin\clang-cl.exe" --version + $clangVersion = (& $clangClPath --version) -join " " if ($clangVersion -notmatch "clang version 20\.1\.0") { throw "Unexpected clang-cl version. Got: $clangVersion" } From dce0da03fa750ad6c5d091fce62498fa6222a3fa Mon Sep 17 00:00:00 2001 From: shookapic Date: Wed, 15 Apr 2026 21:33:23 +0900 Subject: [PATCH 5/6] fix(cicd): LLVM 21.1.0 should now install correctly --- .github/workflows/release-binaries.yml | 86 +++++++++++++++++++++----- 1 file changed, 72 insertions(+), 14 deletions(-) diff --git a/.github/workflows/release-binaries.yml b/.github/workflows/release-binaries.yml index 7cb8e5c..e5fca88 100644 --- a/.github/workflows/release-binaries.yml +++ b/.github/workflows/release-binaries.yml @@ -185,31 +185,89 @@ jobs: fi echo "value=${version}" >> "${GITHUB_OUTPUT}" - - name: Install LLVM 20.1.0 to fixed location + - name: Install LLVM 20.1.0 archive with 7-Zip shell: pwsh run: | $ErrorActionPreference = "Stop" + $llvmVersion = "20.1.0" - $installerUrl = "https://github.com/llvm/llvm-project/releases/download/llvmorg-$llvmVersion/LLVM-$llvmVersion-win64.exe" - $installerPath = "$env:RUNNER_TEMP\LLVM-$llvmVersion-win64.exe" + $archiveName = "clang+llvm-$llvmVersion-x86_64-pc-windows-msvc.tar.xz" + $archiveUrl = "https://github.com/llvm/llvm-project/releases/download/llvmorg-$llvmVersion/$archiveName" + $archivePath = Join-Path $env:RUNNER_TEMP $archiveName + $extractRoot = Join-Path $env:RUNNER_TEMP "llvm-extract" $installRoot = "C:\Program Files\LLVM-$llvmVersion" - $llvmCmakeDir = "$installRoot\lib\cmake\llvm" - $clangCmakeDir = "$installRoot\lib\cmake\clang" + $sevenZip = "${env:ProgramFiles}\7-Zip\7z.exe" + $tarPath = Join-Path $env:RUNNER_TEMP "clang+llvm-$llvmVersion-x86_64-pc-windows-msvc.tar" + + if (-not (Test-Path $sevenZip)) { + throw "7z.exe not found at $sevenZip" + } + + Write-Host "Downloading LLVM archive from $archiveUrl" + Invoke-WebRequest -Uri $archiveUrl -OutFile $archivePath + Write-Host "Download finished" + Get-Item $archivePath | Select-Object FullName, Length | Format-Table -AutoSize + + if (Test-Path $extractRoot) { + Remove-Item -Recurse -Force $extractRoot + } + New-Item -ItemType Directory -Force -Path $extractRoot | Out-Null + + if (Test-Path $tarPath) { + Remove-Item -Force $tarPath + } + + Write-Host "Extracting .xz to .tar with 7-Zip" + & $sevenZip x $archivePath "-o$env:RUNNER_TEMP" -y + if ($LASTEXITCODE -ne 0) { + throw "7-Zip failed while extracting .xz archive" + } + + if (-not (Test-Path $tarPath)) { + throw "Expected tar file not found after xz extraction: $tarPath" + } + + Write-Host "Extracting .tar to $extractRoot" + & $sevenZip x $tarPath "-o$extractRoot" -y + if ($LASTEXITCODE -ne 0) { + throw "7-Zip failed while extracting .tar archive" + } + + Write-Host "Listing extracted directories" + Get-ChildItem $extractRoot -Directory | Select-Object Name, FullName | Format-Table -AutoSize + + $extractedDir = Get-ChildItem $extractRoot -Directory | Select-Object -First 1 + if (-not $extractedDir) { + throw "Failed to find extracted LLVM directory under $extractRoot" + } + + if (Test-Path $installRoot) { + Remove-Item -Recurse -Force $installRoot + } + Move-Item -Path $extractedDir.FullName -Destination $installRoot + + $clangClPath = Join-Path $installRoot "bin\clang-cl.exe" + $llvmCmakeDir = Join-Path $installRoot "lib\cmake\llvm" + $clangCmakeDir = Join-Path $installRoot "lib\cmake\clang" + $llvmConfigPath = Join-Path $llvmCmakeDir "LLVMConfig.cmake" + $clangConfigPath = Join-Path $clangCmakeDir "ClangConfig.cmake" - Invoke-WebRequest -Uri $installerUrl -OutFile $installerPath - Start-Process -FilePath $installerPath -ArgumentList @("/S", "/D=$installRoot") -Wait -NoNewWindow + Write-Host "Checking extracted LLVM files" + Write-Host "clang-cl path: $clangClPath" + Write-Host "LLVMConfig path: $llvmConfigPath" + Write-Host "ClangConfig path: $clangConfigPath" - if (-not (Test-Path "$installRoot\bin\clang-cl.exe")) { - throw "clang-cl.exe not found after LLVM install: $installRoot\bin\clang-cl.exe" + if (-not (Test-Path $clangClPath)) { + throw "clang-cl.exe not found after LLVM archive extraction: $clangClPath" } - if (-not (Test-Path $llvmCmakeDir)) { - throw "LLVM CMake package dir not found: $llvmCmakeDir" + if (-not (Test-Path $llvmConfigPath)) { + throw "LLVMConfig.cmake not found after LLVM archive extraction: $llvmConfigPath" } - if (-not (Test-Path $clangCmakeDir)) { - throw "Clang CMake package dir not found: $clangCmakeDir" + if (-not (Test-Path $clangConfigPath)) { + throw "ClangConfig.cmake not found after LLVM archive extraction: $clangConfigPath" } - $clangVersion = & "$installRoot\bin\clang-cl.exe" --version + $clangVersion = (& $clangClPath --version) -join " " if ($clangVersion -notmatch "clang version 20\.1\.0") { throw "Unexpected clang-cl version. Got: $clangVersion" } From 63f73067e70f93681acec29b5d234c7c8baef76d Mon Sep 17 00:00:00 2001 From: shookapic Date: Wed, 15 Apr 2026 22:09:09 +0900 Subject: [PATCH 6/6] fix(clang format): code is now clang format compliant --- include/App/ToolResolver.hpp | 15 ++-- include/Config/config.hpp | 111 +++++++++++++----------- include/Process/Ipc/IpcStrategy.hpp | 3 +- include/Process/Tools/AnalysisTools.hpp | 9 +- include/Process/WinProcess.hpp | 26 +++--- 5 files changed, 81 insertions(+), 83 deletions(-) diff --git a/include/App/ToolResolver.hpp b/include/App/ToolResolver.hpp index 2043acb..1536444 100644 --- a/include/App/ToolResolver.hpp +++ b/include/App/ToolResolver.hpp @@ -54,11 +54,10 @@ namespace ctrace #ifdef _WIN32 constexpr std::string_view fallback = "tscancode"; #else - const std::string fallback = detail::repoLocalExecutable("./tscancode/src/tscancode/trunk/tscancode"); + const std::string fallback = + detail::repoLocalExecutable("./tscancode/src/tscancode/trunk/tscancode"); #endif - return {detail::envOrDefault("CORETRACE_TSCANCODE_BIN", - fallback), - {}}; + return {detail::envOrDefault("CORETRACE_TSCANCODE_BIN", fallback), {}}; } [[nodiscard]] inline ResolvedToolCommand resolveIkosCommand() @@ -68,9 +67,7 @@ namespace ctrace #else const std::string fallback = detail::repoLocalExecutable("./ikos/src/ikos-build/bin/ikos"); #endif - return {detail::envOrDefault("CORETRACE_IKOS_BIN", - fallback), - {}}; + return {detail::envOrDefault("CORETRACE_IKOS_BIN", fallback), {}}; } [[nodiscard]] inline ResolvedToolCommand resolveFlawfinderCommand() @@ -82,8 +79,8 @@ namespace ctrace const std::string pythonExe = detail::envOrDefault("CORETRACE_PYTHON_BIN", "python3"); #endif - ResolvedToolCommand command{detail::envOrDefault("CORETRACE_FLAWFINDER_LAUNCHER", pythonExe), - {}}; + ResolvedToolCommand command{ + detail::envOrDefault("CORETRACE_FLAWFINDER_LAUNCHER", pythonExe), {}}; const std::string scriptPath = detail::envOrDefault( "CORETRACE_FLAWFINDER_SCRIPT", "./flawfinder/src/flawfinder-build/flawfinder.py"); diff --git a/include/Config/config.hpp b/include/Config/config.hpp index ce8fd3b..dab347d 100644 --- a/include/Config/config.hpp +++ b/include/Config/config.hpp @@ -23,54 +23,61 @@ static void printHelp(void) { - std::cout << "ctrace - Static & Dynamic C/C++ Code Analysis Tool\n\n" - << "Usage:\n" - << " ctrace [options]\n\n" - << "Options:\n" - << " --help Displays this help message.\n" - << " --version Displays build version information.\n" - << " --verbose Enables detailed (verbose) output.\n" - << " --sarif-format Generates a report in SARIF format.\n" - << " --report-file Specifies the path to the report file (default: ctrace-report.txt).\n" - << " --output-file Specifies the output file for the analysed binary (default: ctrace.out).\n" - << " --entry-points Sets the entry points for analysis (default: main). Accepts a comma-separated list.\n" - << " --config Loads settings from a JSON config file.\n" - << " --compile-commands Path to compile_commands.json for tools that support it.\n" - << " --include-compdb-deps Includes dependency entries (e.g. _deps) when auto-loading files from compile_commands.json.\n" - << " --analysis-profile

Stack analyzer profile: fast|full.\n" - << " --smt Enables/disables SMT refinement in stack analyzer.\n" - << " --smt-backend Primary SMT backend (e.g. z3, interval).\n" - << " --smt-secondary-backend Secondary backend for multi-solver modes.\n" - << " --smt-mode SMT mode: single|portfolio|cross-check|dual-consensus.\n" - << " --smt-timeout-ms SMT timeout in milliseconds.\n" - << " --smt-budget-nodes SMT node budget per query.\n" - << " --smt-rules Comma-separated SMT-enabled rules.\n" - << " --resource-model Path to the resource lifetime model for stack analyzer.\n" - << " --escape-model Path to the stack escape model for stack analyzer.\n" - << " --buffer-model Path to the buffer overflow model for stack analyzer.\n" - << " --demangle Displays demangled function names in supported tools.\n" - << " --static Enables static analysis.\n" - << " --dyn Enables dynamic analysis.\n" - << " --invoke Invokes specific tools (comma-separated).\n" - << " Available tools: flawfinder, ikos, cppcheck, tscancode, ctrace_stack_analyzer.\n" - << " --input Specifies the source files to analyse (comma-separated).\n" - << " --timing Enables stack analyzer timing output.\n" - << " --ipc Specifies the IPC method to use (e.g., socket).\n" - << " --ipc-path Specifies the IPC path (default: " - << ctrace::platform::defaultIpcPath() << ").\n" - << " --serve-host HTTP server host when --ipc=serve.\n" - << " --serve-port HTTP server port when --ipc=serve.\n" - << " --async Enables asynchronous execution.\n" - << " --shutdown-token Token required for POST /shutdown (server mode).\n" - << " --shutdown-timeout-ms Graceful shutdown timeout in ms (0 = wait indefinitely).\n\n" - << "Examples:\n" - << " ctrace --input main.cpp,util.cpp --static --invoke=cppcheck,flawfinder\n" - << " ctrace --verbose --report-file=analysis.txt --sarif-format\n\n" - << "Description:\n" - << " ctrace is a modular C/C++ code analysis tool that combines both static and dynamic\n" - << " analysis. It can be finely configured to detect vulnerabilities, security issues,\n" - << " and memory misuse.\n" - << std::endl; + std::cout + << "ctrace - Static & Dynamic C/C++ Code Analysis Tool\n\n" + << "Usage:\n" + << " ctrace [options]\n\n" + << "Options:\n" + << " --help Displays this help message.\n" + << " --version Displays build version information.\n" + << " --verbose Enables detailed (verbose) output.\n" + << " --sarif-format Generates a report in SARIF format.\n" + << " --report-file Specifies the path to the report file (default: " + "ctrace-report.txt).\n" + << " --output-file Specifies the output file for the analysed binary (default: " + "ctrace.out).\n" + << " --entry-points Sets the entry points for analysis (default: main). Accepts " + "a comma-separated list.\n" + << " --config Loads settings from a JSON config file.\n" + << " --compile-commands Path to compile_commands.json for tools that support it.\n" + << " --include-compdb-deps Includes dependency entries (e.g. _deps) when auto-loading " + "files from compile_commands.json.\n" + << " --analysis-profile

Stack analyzer profile: fast|full.\n" + << " --smt Enables/disables SMT refinement in stack analyzer.\n" + << " --smt-backend Primary SMT backend (e.g. z3, interval).\n" + << " --smt-secondary-backend Secondary backend for multi-solver modes.\n" + << " --smt-mode SMT mode: single|portfolio|cross-check|dual-consensus.\n" + << " --smt-timeout-ms SMT timeout in milliseconds.\n" + << " --smt-budget-nodes SMT node budget per query.\n" + << " --smt-rules Comma-separated SMT-enabled rules.\n" + << " --resource-model Path to the resource lifetime model for stack analyzer.\n" + << " --escape-model Path to the stack escape model for stack analyzer.\n" + << " --buffer-model Path to the buffer overflow model for stack analyzer.\n" + << " --demangle Displays demangled function names in supported tools.\n" + << " --static Enables static analysis.\n" + << " --dyn Enables dynamic analysis.\n" + << " --invoke Invokes specific tools (comma-separated).\n" + << " Available tools: flawfinder, ikos, cppcheck, tscancode, " + "ctrace_stack_analyzer.\n" + << " --input Specifies the source files to analyse (comma-separated).\n" + << " --timing Enables stack analyzer timing output.\n" + << " --ipc Specifies the IPC method to use (e.g., socket).\n" + << " --ipc-path Specifies the IPC path (default: " + << ctrace::platform::defaultIpcPath() << ").\n" + << " --serve-host HTTP server host when --ipc=serve.\n" + << " --serve-port HTTP server port when --ipc=serve.\n" + << " --async Enables asynchronous execution.\n" + << " --shutdown-token Token required for POST /shutdown (server mode).\n" + << " --shutdown-timeout-ms Graceful shutdown timeout in ms (0 = wait " + "indefinitely).\n\n" + << "Examples:\n" + << " ctrace --input main.cpp,util.cpp --static --invoke=cppcheck,flawfinder\n" + << " ctrace --verbose --report-file=analysis.txt --sarif-format\n\n" + << "Description:\n" + << " ctrace is a modular C/C++ code analysis tool that combines both static and dynamic\n" + << " analysis. It can be finely configured to detect vulnerabilities, security issues,\n" + << " and memory misuse.\n" + << std::endl; } static void printVersion(void) @@ -118,11 +125,11 @@ namespace ctrace bool hasDynamicAnalysis = false; ///< Indicates if dynamic analysis is enabled. bool hasInvokedSpecificTools = false; ///< Indicates if specific tools are invoked. std::string ipc = - ctrace_defs::IPC_TYPES.front(); ///< IPC method to use (e.g., fifo, socket). + ctrace_defs::IPC_TYPES.front(); ///< IPC method to use (e.g., fifo, socket). std::string ipcPath = ctrace::platform::defaultIpcPath(); ///< Path for IPC communication. - std::string serverHost = "127.0.0.1"; ///< Host for server IPC (if applicable). - int serverPort = 8080; ///< Port for server IPC (if applicable). - std::string shutdownToken; ///< Token required for POST /shutdown. + std::string serverHost = "127.0.0.1"; ///< Host for server IPC (if applicable). + int serverPort = 8080; ///< Port for server IPC (if applicable). + std::string shutdownToken; ///< Token required for POST /shutdown. int shutdownTimeoutMs = 0; ///< Shutdown timeout in milliseconds (0 = wait indefinitely). std::vector specificTools; ///< List of specific tools to invoke. diff --git a/include/Process/Ipc/IpcStrategy.hpp b/include/Process/Ipc/IpcStrategy.hpp index 45c24c8..15dea57 100644 --- a/include/Process/Ipc/IpcStrategy.hpp +++ b/include/Process/Ipc/IpcStrategy.hpp @@ -118,8 +118,7 @@ class LocalSocketStrategy : public IpcStrategy { const std::string error = ctrace::ipc::lastSocketError(); close(); - throw ctrace::ipc::SocketError("Error connecting to socket '" + path_ + - "': " + error); + throw ctrace::ipc::SocketError("Error connecting to socket '" + path_ + "': " + error); } } diff --git a/include/Process/Tools/AnalysisTools.hpp b/include/Process/Tools/AnalysisTools.hpp index de8eff0..d9abfae 100644 --- a/include/Process/Tools/AnalysisTools.hpp +++ b/include/Process/Tools/AnalysisTools.hpp @@ -136,8 +136,7 @@ namespace ctrace const auto command = ctrace::resolveIkosCommand(); argsProcess.insert(argsProcess.begin(), command.prefixArguments.begin(), command.prefixArguments.end()); - auto process = - ProcessFactory::createProcess(command.executable, argsProcess); + auto process = ProcessFactory::createProcess(command.executable, argsProcess); // std::this_thread::sleep_for(std::chrono::seconds(5)); process->execute(); ctrace::Thread::Output::tool_out(process->logOutput); @@ -198,8 +197,7 @@ namespace ctrace const auto command = ctrace::resolveFlawfinderCommand(); argsProcess.insert(argsProcess.begin(), command.prefixArguments.begin(), command.prefixArguments.end()); - auto process = - ProcessFactory::createProcess(command.executable, argsProcess); + auto process = ProcessFactory::createProcess(command.executable, argsProcess); process->execute(); if (config.global.ipc == "standardIO") @@ -259,8 +257,7 @@ namespace ctrace const auto command = ctrace::resolveCppcheckCommand(); argsProcess.insert(argsProcess.begin(), command.prefixArguments.begin(), command.prefixArguments.end()); - auto process = - ProcessFactory::createProcess(command.executable, argsProcess); + auto process = ProcessFactory::createProcess(command.executable, argsProcess); process->execute(); ctrace::Thread::Output::tool_out(process->logOutput); } diff --git a/include/Process/WinProcess.hpp b/include/Process/WinProcess.hpp index b54cf95..a34b16d 100644 --- a/include/Process/WinProcess.hpp +++ b/include/Process/WinProcess.hpp @@ -59,9 +59,8 @@ class WindowsProcess : public Process std::vector mutableCommandLine(commandLine.begin(), commandLine.end()); mutableCommandLine.push_back('\0'); - const BOOL created = - CreateProcessA(nullptr, mutableCommandLine.data(), nullptr, nullptr, TRUE, - CREATE_NO_WINDOW, nullptr, nullptr, &si, &pi); + const BOOL created = CreateProcessA(nullptr, mutableCommandLine.data(), nullptr, nullptr, + TRUE, CREATE_NO_WINDOW, nullptr, nullptr, &si, &pi); CloseHandle(stdoutWrite); if (!created) @@ -102,8 +101,7 @@ class WindowsProcess : public Process logOutput.clear(); char buffer[4096]; DWORD bytesRead = 0; - while (ReadFile(m_stdoutRead, buffer, sizeof(buffer), &bytesRead, nullptr) && - bytesRead > 0) + while (ReadFile(m_stdoutRead, buffer, sizeof(buffer), &bytesRead, nullptr) && bytesRead > 0) { logOutput.append(buffer, buffer + bytesRead); } @@ -168,15 +166,15 @@ class WindowsProcess : public Process { const DWORD error = GetLastError(); LPSTR messageBuffer = nullptr; - const DWORD size = FormatMessageA( - FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - nullptr, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - reinterpret_cast(&messageBuffer), 0, nullptr); - - std::string message = - (size > 0 && messageBuffer != nullptr) ? std::string(messageBuffer, size) - : ("Windows error " + std::to_string(error)); + const DWORD size = + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&messageBuffer), 0, nullptr); + + std::string message = (size > 0 && messageBuffer != nullptr) + ? std::string(messageBuffer, size) + : ("Windows error " + std::to_string(error)); if (messageBuffer != nullptr) { LocalFree(messageBuffer);