From 205168a9bc96f8a53f2d8c36441dcc578e31034e Mon Sep 17 00:00:00 2001 From: shookapic Date: Fri, 10 Apr 2026 18:10:31 +0900 Subject: [PATCH 1/5] Add: Windows implementation for coretrace-compiler --- .github/workflows/build.yml | 102 ++- CMakeLists.txt | 292 +++--- README.md | 9 + extern-project/CMakeLists.txt | 4 + scripts/build-windows.ps1 | 256 ++++++ src/runtime/ct_runtime_env.cpp | 14 + src/runtime/ct_runtime_helpers.h | 29 +- src/runtime/ct_runtime_internal.h | 95 +- src/runtime/windows/ct_runtime_alloc.cpp | 901 +++++++++++++++++++ src/runtime/windows/ct_runtime_backtrace.cpp | 97 ++ src/runtime/windows/ct_runtime_env.cpp | 142 +++ src/runtime/windows/ct_runtime_vtable.cpp | 672 ++++++++++++++ test/examples/test_extern_project.py | 123 ++- test/examples/test_smoke.py | 119 ++- 14 files changed, 2669 insertions(+), 186 deletions(-) create mode 100644 scripts/build-windows.ps1 create mode 100644 src/runtime/windows/ct_runtime_alloc.cpp create mode 100644 src/runtime/windows/ct_runtime_backtrace.cpp create mode 100644 src/runtime/windows/ct_runtime_env.cpp create mode 100644 src/runtime/windows/ct_runtime_vtable.cpp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 34b073f..6ff9f60 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,7 +18,7 @@ jobs: os: [ubuntu-latest, macos-latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 @@ -100,3 +100,103 @@ jobs: run: | docker buildx build --platform linux/amd64 -f test/docker/Dockerfile . --progress=plain --output=type=cacheonly docker buildx build --platform linux/arm64 -f test/docker/Dockerfile . --progress=plain --output=type=cacheonly + + build-windows: + name: Build on Windows (pinned LLVM 20.1.0) + runs-on: windows-2022 + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - 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-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}" ` + -LoggerSourceDir "${PWD}\deps\coretrace-log" ` + -Configuration Release + + - name: Smoke test + shell: pwsh + run: | + $ErrorActionPreference = "Stop" + Set-Content -Path test.cc -Value 'int main() { return 0; }' + .\dist\windows\bin\cc.exe -S -emit-llvm test.cc + if (-not (Test-Path test.ll)) { + throw "test.ll was not produced" + } + Get-Content test.ll -TotalCount 10 + + - name: Run Python smoke tests (Windows) + shell: pwsh + run: | + $ErrorActionPreference = "Stop" + python -m venv .venv + .\.venv\Scripts\python -m pip install --upgrade pip + .\.venv\Scripts\python -m pip install git+https://github.com/CoreTrace/coretrace-testkit.git + $env:CORETRACE_COMPILER_TEST_CC = (Resolve-Path .\dist\windows\bin\cc.exe).Path + $env:CORETRACE_LOGGER_SOURCE_DIR = (Resolve-Path .\deps\coretrace-log).Path + .\.venv\Scripts\python .\test\examples\test_smoke.py + .\.venv\Scripts\python .\test\examples\test_extern_project.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e6a696..3f92822 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,14 +1,98 @@ # SPDX-License-Identifier: Apache-2.0 cmake_minimum_required(VERSION 3.16) +cmake_policy(SET CMP0091 NEW) +if(POLICY CMP0169) + cmake_policy(SET CMP0169 OLD) +endif() project(cc) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) +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() + +function(coretrace_disable_rtti target_name) + if(NOT TARGET "${target_name}" OR LLVM_ENABLE_RTTI) + return() + endif() + + if(MSVC) + target_compile_options("${target_name}" PRIVATE /GR-) + else() + target_compile_options("${target_name}" PRIVATE -fno-rtti) + endif() +endfunction() + +function(coretrace_patch_imported_link_property target_name property_name stale_value replacement_value) + if(NOT TARGET "${target_name}") + return() + endif() + + get_target_property(current_value "${target_name}" "${property_name}") + 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_name}" PROPERTIES + ${property_name} "${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() + find_package(LLVM REQUIRED CONFIG) find_package(Clang REQUIRED CONFIG) +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) + coretrace_patch_llvm_diaguids("${coretrace_diaguids_path}") + endif() +endif() + include_directories( ${LLVM_INCLUDE_DIRS} ${CLANG_INCLUDE_DIRS} @@ -45,33 +129,63 @@ set(LIB_SOURCES src/compilerlib/instrumentation/vtable.cpp ) -# ── coretrace-logger (external logging library) ── +set(CT_RUNTIME_PLATFORM_SOURCES + src/runtime/ct_runtime_alloc.cpp + src/runtime/ct_runtime_backtrace.cpp + src/runtime/ct_runtime_env.cpp + src/runtime/ct_runtime_vtable.cpp +) +if(WIN32) + set(CT_RUNTIME_PLATFORM_SOURCES + src/runtime/windows/ct_runtime_alloc.cpp + src/runtime/windows/ct_runtime_backtrace.cpp + src/runtime/windows/ct_runtime_env.cpp + src/runtime/windows/ct_runtime_vtable.cpp + ) +endif() + +set(CORETRACE_LOGGER_BUILD_EXAMPLES OFF CACHE BOOL "Disable logger examples" FORCE) +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_MakeAvailable(coretrace-logger) + +FetchContent_GetProperties(coretrace_logger) +if(NOT coretrace_logger_POPULATED) + FetchContent_Populate(coretrace_logger) +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) add_library(ct_instrument_runtime STATIC src/runtime/ct_instrument_runtime.cpp - src/runtime/ct_runtime_alloc.cpp - src/runtime/ct_runtime_backtrace.cpp + ${CT_RUNTIME_PLATFORM_SOURCES} src/runtime/ct_runtime_bounds.cpp - src/runtime/ct_runtime_env.cpp src/runtime/ct_runtime_logging.cpp src/runtime/ct_runtime_shadow.cpp src/runtime/ct_runtime_state.cpp src/runtime/ct_runtime_trace.cpp - src/runtime/ct_runtime_vtable.cpp ) +target_include_directories(ct_instrument_runtime PRIVATE src/runtime) target_link_libraries(ct_instrument_runtime PRIVATE coretrace_logger) +if(WIN32) + target_link_libraries(ct_instrument_runtime PRIVATE dbghelp) +endif() set_target_properties(ct_instrument_runtime PROPERTIES OUTPUT_NAME "ct_instrument_runtime") +coretrace_force_msvc_runtime(ct_instrument_runtime) if(ENABLE_DEBUG_ASAN) target_compile_options(ct_instrument_runtime PRIVATE -fno-sanitize=address) endif() add_library(compilerlib_static STATIC ${LIB_SOURCES}) +coretrace_force_msvc_runtime(compilerlib_static) # Generic detection of Clang resource directory. # You can override this with -DCLANG_RESOURCE_DIR=/custom/path when configuring CMake. @@ -124,15 +238,49 @@ if(NOT CLANG_RESOURCE_DIR) ) endif() +file(TO_CMAKE_PATH "${CLANG_RESOURCE_DIR}" CLANG_RESOURCE_DIR) +if(_LLVM_BIN_DIR) + file(TO_CMAKE_PATH "${_LLVM_BIN_DIR}" _LLVM_BIN_DIR) +endif() +if(CLANG_EXECUTABLE) + file(TO_CMAKE_PATH "${CLANG_EXECUTABLE}" CLANG_EXECUTABLE) +endif() + message(STATUS "Using Clang resource dir: ${CLANG_RESOURCE_DIR}") -# FOR USE WITH FETCHCONTENT +if(LLVM_LINK_LLVM_DYLIB) + set(CT_LLVM_LINK_LIBS LLVM) +else() + llvm_map_components_to_libnames(CT_LLVM_LINK_LIBS + all + AllTargetsInfos + AllTargetsCodeGens + AllTargetsAsmParsers + AllTargetsDescs + AllTargetsMCAs + ) +endif() + +if(CLANG_LINK_CLANG_DYLIB) + set(CT_CLANG_LINK_LIBS clang-cpp) +else() + set(CT_CLANG_LINK_LIBS + clangAST + clangBasic + clangCodeGen + clangDriver + clangFrontend + clangLex + clangParse + clangSema + ) +endif() + target_include_directories(compilerlib_static PUBLIC $ $ ) -# ALIAS FOR USE WITH FETCHCONTENT add_library(cc::compilerlib_static ALIAS compilerlib_static) target_compile_definitions(compilerlib_static @@ -141,53 +289,18 @@ target_compile_definitions(compilerlib_static CT_RUNTIME_LOGGER_LIB_PATH="$" ) add_dependencies(compilerlib_static ct_instrument_runtime) - -if(NOT LLVM_ENABLE_RTTI) - target_compile_options(compilerlib_static PRIVATE -fno-rtti) -endif() - -if(CLANG_LINK_CLANG_DYLIB) - target_link_libraries(compilerlib_static PUBLIC clang-cpp) -else() - target_link_libraries(compilerlib_static PUBLIC - clangAST - clangBasic - clangCodeGen - clangDriver - clangFrontend - clangLex - clangParse - clangSema - ) -endif() - -if(LLVM_LINK_LLVM_DYLIB) - target_link_libraries(compilerlib_static PUBLIC LLVM) -else() - target_link_libraries(compilerlib_static PUBLIC - LLVMOption - LLVMSupport - LLVMTarget - LLVMX86AsmParser - LLVMX86CodeGen - LLVMX86Desc - LLVMX86Info - LLVMObject - LLVMBinaryFormat - ) -endif() +coretrace_disable_rtti(compilerlib_static) +target_link_libraries(compilerlib_static PUBLIC ${CT_CLANG_LINK_LIBS} ${CT_LLVM_LINK_LIBS}) add_library(compilerlib_shared SHARED ${LIB_SOURCES}) - set_target_properties(compilerlib_shared PROPERTIES OUTPUT_NAME "compilerlib") +coretrace_force_msvc_runtime(compilerlib_shared) -# FOR USE WITH FETCHCONTENT target_include_directories(compilerlib_shared PUBLIC $ $ ) -# ALIAS FOR USE WITH FETCHCONTENT add_library(cc::compilerlib_shared ALIAS compilerlib_shared) target_compile_definitions(compilerlib_shared @@ -196,37 +309,13 @@ target_compile_definitions(compilerlib_shared CT_RUNTIME_LOGGER_LIB_PATH="$" ) add_dependencies(compilerlib_shared ct_instrument_runtime) - -if(NOT LLVM_ENABLE_RTTI) - target_compile_options(compilerlib_shared PRIVATE -fno-rtti) -endif() - -if(CLANG_LINK_CLANG_DYLIB) - target_link_libraries(compilerlib_shared PRIVATE clang-cpp) -else() - target_link_libraries(compilerlib_shared PRIVATE clangAST clangBasic clangCodeGen clangDriver clangFrontend clangLex clangParse clangSema ) -endif() - -if(LLVM_LINK_LLVM_DYLIB) - target_link_libraries(compilerlib_shared PRIVATE LLVM) -else() - target_link_libraries(compilerlib_shared PRIVATE - LLVMOption - LLVMSupport - LLVMTarget - LLVMX86AsmParser - LLVMX86CodeGen - LLVMX86Desc - LLVMX86Info - LLVMObject - LLVMBinaryFormat - ) -endif() +coretrace_disable_rtti(compilerlib_shared) +target_link_libraries(compilerlib_shared PRIVATE ${CT_CLANG_LINK_LIBS} ${CT_LLVM_LINK_LIBS}) add_executable(cc src/cli/main.cc src/cli/help.cc src/cli/args.cc) target_include_directories(cc PRIVATE src) +coretrace_force_msvc_runtime(cc) -# Propagate CLANG_RESOURCE_DIR as a compile definition to all relevant targets foreach(tgt compilerlib_static compilerlib_shared cc) if(TARGET ${tgt}) target_compile_definitions(${tgt} @@ -248,44 +337,14 @@ foreach(tgt compilerlib_static compilerlib_shared cc) endif() endforeach() -if(NOT LLVM_ENABLE_RTTI) - target_compile_options(cc PRIVATE -fno-rtti) -endif() - -target_link_libraries(cc PRIVATE compilerlib_static) - -if(CLANG_LINK_CLANG_DYLIB) - target_link_libraries(cc PRIVATE clang-cpp) -else() - target_link_libraries(cc PRIVATE - clangAST - clangBasic - clangCodeGen - clangDriver - clangFrontend - clangLex - clangParse - clangSema - ) -endif() - -if(LLVM_LINK_LLVM_DYLIB) - target_link_libraries(cc PRIVATE LLVM) -else() - target_link_libraries(cc PRIVATE - LLVMOption - LLVMSupport - LLVMTarget - LLVMX86AsmParser - LLVMX86CodeGen - LLVMX86Desc - LLVMX86Info - LLVMObject - LLVMBinaryFormat - ) -endif() +coretrace_disable_rtti(cc) +target_link_libraries(cc PRIVATE compilerlib_static ${CT_CLANG_LINK_LIBS} ${CT_LLVM_LINK_LIBS}) -install(TARGETS compilerlib_static compilerlib_shared cc ARCHIVE DESTINATION lib LIBRARY DESTINATION lib RUNTIME DESTINATION bin) +install(TARGETS compilerlib_static compilerlib_shared cc + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin +) install(DIRECTORY include/ DESTINATION include) set(USE_SHARED_LIB OFF CACHE BOOL "Link with shared compilerlib") @@ -293,10 +352,8 @@ set(USE_SHARED_LIB OFF CACHE BOOL "Link with shared compilerlib") if(BUILD_TESTS) if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test/test_compiler.cpp") add_executable(test_compiler test/test_compiler.cpp) - - if(NOT LLVM_ENABLE_RTTI) - target_compile_options(test_compiler PRIVATE -fno-rtti) - endif() + coretrace_force_msvc_runtime(test_compiler) + coretrace_disable_rtti(test_compiler) if(USE_SHARED_LIB) target_link_libraries(test_compiler PRIVATE compilerlib_shared) @@ -314,11 +371,6 @@ if(BUILD_TESTS) endif() endif() -# ============ -# FORMATTING -# ============ -# Only expose formatting targets when this project is the top-level build to -# avoid name clashes when used via FetchContent. if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) add_custom_target(format COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/scripts/format.sh" diff --git a/README.md b/README.md index 27e70dc..fefd1fa 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,15 @@ mkdir -p build && cd build ./build.sh ``` +Windows (native, produces `cc.exe` under `dist/windows/bin`): + +```powershell +powershell -ExecutionPolicy Bypass -File .\scripts\build-windows.ps1 ` + -LLVMDir "C:\LLVM\lib\cmake\llvm" ` + -LoggerSourceDir "C:\Users\shookapic\Documents\coretrace-log" ` + -Configuration Release +``` + macOS: ```zsh diff --git a/extern-project/CMakeLists.txt b/extern-project/CMakeLists.txt index 18027e4..e0f12d6 100644 --- a/extern-project/CMakeLists.txt +++ b/extern-project/CMakeLists.txt @@ -4,6 +4,10 @@ project(consumer_example CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) +if(MSVC) + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +endif() + include(FetchContent) FetchContent_Declare( diff --git a/scripts/build-windows.ps1 b/scripts/build-windows.ps1 new file mode 100644 index 0000000..73573a9 --- /dev/null +++ b/scripts/build-windows.ps1 @@ -0,0 +1,256 @@ +# 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]$LoggerSourceDir = "", + [switch]$BuildTests, + [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, + "-DLLVM_DIR=$resolvedLLVMDir", + "-DClang_DIR=$resolvedClangDir", + "-DBUILD_TESTS=$(if ($BuildTests) { "ON" } else { "OFF" })" +) + +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 ($LoggerSourceDir -ne "") +{ + $resolvedLoggerSourceDir = (Resolve-Path $LoggerSourceDir).Path + $cmakeArgs += "-DFETCHCONTENT_SOURCE_DIR_CORETRACE_LOGGER=$resolvedLoggerSourceDir" +} + +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\cc.exe" +if (-not (Test-Path $binaryPath)) +{ + throw "Expected output binary was not produced: $binaryPath" +} + +if ($PackageZip) +{ + $zipPath = Join-Path $repoRoot "coretrace-compiler-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/runtime/ct_runtime_env.cpp b/src/runtime/ct_runtime_env.cpp index 5f9838f..8d0a66c 100644 --- a/src/runtime/ct_runtime_env.cpp +++ b/src/runtime/ct_runtime_env.cpp @@ -67,6 +67,10 @@ CT_NOINSTR __attribute__((constructor)) static void ct_runtime_init(void) { ct_maybe_install_backtrace(); ct_apply_compiled_config(); +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4996) // MSVC warning: 'getenv': This function or variable may be unsafe +#endif if (getenv("CT_DISABLE_TRACE") != nullptr) { ct_set_enabled(CT_FEATURE_TRACE, 0); @@ -105,6 +109,9 @@ CT_NOINSTR __attribute__((constructor)) static void ct_runtime_init(void) { ct_set_enabled(CT_FEATURE_ALLOC_TRACE, 0); } +#ifdef _MSC_VER +#pragma warning(pop) +#endif } CT_NOINSTR void ct_init_env_once(void) @@ -117,6 +124,10 @@ CT_NOINSTR void ct_init_env_once(void) } ct_apply_compiled_config(); +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4996) // MSVC warning: 'getenv': This function or variable may be unsafe +#endif if (getenv("CT_DISABLE_TRACE") != nullptr) { ct_set_enabled(CT_FEATURE_TRACE, 0); @@ -155,4 +166,7 @@ CT_NOINSTR void ct_init_env_once(void) { ct_set_enabled(CT_FEATURE_ALLOC_TRACE, 0); } +#ifdef _MSC_VER +#pragma warning(pop) +#endif } diff --git a/src/runtime/ct_runtime_helpers.h b/src/runtime/ct_runtime_helpers.h index c2b5f9b..b715ac5 100644 --- a/src/runtime/ct_runtime_helpers.h +++ b/src/runtime/ct_runtime_helpers.h @@ -2,9 +2,35 @@ #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 -#include __attribute__((no_instrument_function)) inline bool ct_demangle(const char* name, std::string& out) { @@ -32,3 +58,4 @@ __attribute__((no_instrument_function)) inline bool ct_demangle(const char* name } return false; } +#endif diff --git a/src/runtime/ct_runtime_internal.h b/src/runtime/ct_runtime_internal.h index 2488e2e..0fcc954 100644 --- a/src/runtime/ct_runtime_internal.h +++ b/src/runtime/ct_runtime_internal.h @@ -8,25 +8,74 @@ #include #include -#include +#include #include +#include #include #include -#include -#define CT_NOINSTR __attribute__((no_instrument_function)) +#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; +} -// ############################################# -// Compatibility aliases: CTColor -> coretrace::Color -// ############################################# +#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; -// ############################################# -// Entry states (runtime-specific, not in logger) -// ############################################# - enum { CT_ENTRY_EMPTY = 0, @@ -36,10 +85,6 @@ enum CT_ENTRY_AUTOFREED = 4 }; -// ############################################# -// Runtime globals -// ############################################# - extern int ct_disable_trace; extern int ct_disable_alloc; extern int ct_disable_bounds; @@ -56,7 +101,6 @@ extern size_t ct_early_trace_count; extern size_t ct_early_trace_limit; extern thread_local const char* ct_current_site; -// Feature flags (C API friendly). #define CT_FEATURE_TRACE (1ull << 0) #define CT_FEATURE_ALLOC (1ull << 1) #define CT_FEATURE_BOUNDS (1ull << 2) @@ -79,10 +123,6 @@ extern "C" CT_NODISCARD CT_NOINSTR int ct_early_trace_should_log(void); } -// ############################################# -// Runtime-specific functions (NOT in coretrace-logger) -// ############################################# - 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); @@ -112,12 +152,6 @@ CT_NOINSTR void ct_report_bounds_error(const void* base, const void* ptr, size_t size_t alloc_size, const char* alloc_site, unsigned char state); -// ############################################# -// Compatibility wrappers: delegate to coretrace::* -// These allow existing runtime code to keep using -// the ct_* API without any changes. -// ############################################# - CT_NODISCARD CT_NOINSTR inline std::string_view ct_color(CTColor color) { return coretrace::color(color); @@ -171,7 +205,9 @@ CT_NOINSTR inline void ct_write_str(std::string_view str) CT_NOINSTR inline void ct_write_cstr(const char* str) { if (!str) + { return; + } coretrace::write_str(std::string_view(str)); } @@ -190,12 +226,6 @@ CT_NOINSTR inline void ct_write_prefix(CTLevel level) coretrace::write_prefix(level); } -// ############################################# -// ct_log: compatibility wrapper -// Delegates to coretrace::write_log_line for -// mutex-protected atomic output. -// ############################################# - template CT_NOINSTR inline void ct_log(CTLevel level, std::string_view fmt, Args&&... args) { @@ -203,6 +233,7 @@ CT_NOINSTR inline void ct_log(CTLevel level, std::string_view fmt, Args&&... arg { return; } + try { std::string msg = std::vformat(fmt, std::make_format_args(args...)); @@ -215,7 +246,7 @@ CT_NOINSTR inline void ct_log(CTLevel level, std::string_view fmt, Args&&... arg } catch (...) { - static const char fallback[] = "ct: log format error\n"; + static constexpr char fallback[] = "ct: log format error\n"; coretrace::write_raw(fallback, sizeof(fallback) - 1); } } diff --git a/src/runtime/windows/ct_runtime_alloc.cpp b/src/runtime/windows/ct_runtime_alloc.cpp new file mode 100644 index 0000000..7e6b103 --- /dev/null +++ b/src/runtime/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/runtime/windows/ct_runtime_backtrace.cpp b/src/runtime/windows/ct_runtime_backtrace.cpp new file mode 100644 index 0000000..b5767d3 --- /dev/null +++ b/src/runtime/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/runtime/windows/ct_runtime_env.cpp b/src/runtime/windows/ct_runtime_env.cpp new file mode 100644 index 0000000..ca82dca --- /dev/null +++ b/src/runtime/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/runtime/windows/ct_runtime_vtable.cpp b/src/runtime/windows/ct_runtime_vtable.cpp new file mode 100644 index 0000000..b2af2a7 --- /dev/null +++ b/src/runtime/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/test/examples/test_extern_project.py b/test/examples/test_extern_project.py index 82913d3..02111e0 100644 --- a/test/examples/test_extern_project.py +++ b/test/examples/test_extern_project.py @@ -10,6 +10,7 @@ from ctestfw.plan import CompilePlan from ctestfw.framework.testcase import TestCase from ctestfw.framework.reporter import ConsoleReporter +from ctestfw.assertions.core import Assertion, require from ctestfw.assertions.compiler import ( assert_exit_code, assert_output_name, @@ -24,6 +25,42 @@ BUILD = EXTERN / ".build-python" +def _platform_executable(path: Path) -> Path: + if os.name == "nt" and path.suffix.lower() != ".exe": + return path.with_suffix(".exe") + return path + + +def _powershell_quote(value: str) -> str: + return "'" + value.replace("'", "''") + "'" + + +def _read_artifact_bytes(res, path: str) -> bytes: + artifact = Path(path) + if not artifact.is_absolute(): + artifact = res.run.cwd / artifact + require(artifact.exists(), f"output does not exist: {artifact}") + return artifact.read_bytes() + + +def _is_windows_native_artifact(data: bytes) -> bool: + if data.startswith(b"MZ"): + return True + if len(data) < 2: + return False + return data[:2] in {b"\x64\x86", b"\x4c\x01", b"\x64\xaa"} + + +def assert_windows_native_artifact(path: str) -> Assertion: + def _check(res) -> None: + data = _read_artifact_bytes(res, path) + require( + _is_windows_native_artifact(data), + f"expected PE/COFF artifact at {path}, got unrecognized header", + ) + return Assertion(name=f"windows_native_artifact_{Path(path).name}", check=_check) + + def run_cmd(argv: list[str], cwd: Path) -> tuple[int, str, str]: try: p = subprocess.run( @@ -37,6 +74,49 @@ def run_cmd(argv: list[str], cwd: Path) -> tuple[int, str, str]: return 127, "", f"command not found: {argv[0]}" +def find_windows_dev_shell() -> str | None: + program_files_x86 = os.environ.get("ProgramFiles(x86)") + if not program_files_x86: + return None + + vswhere = Path(program_files_x86) / "Microsoft Visual Studio" / "Installer" / "vswhere.exe" + if not vswhere.exists(): + return None + + rc, out, _ = run_cmd([ + str(vswhere), + "-latest", + "-products", "*", + "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", + "-property", "installationPath", + ], ROOT) + if rc != 0 or not out.strip(): + return None + + dev_shell = Path(out.strip()) / "Common7" / "Tools" / "Launch-VsDevShell.ps1" + if not dev_shell.exists(): + return None + return str(dev_shell) + + +def run_cmd_in_vsdev(argv: list[str], cwd: Path) -> tuple[int, str, str]: + dev_shell = find_windows_dev_shell() + if dev_shell is None: + return 127, "", "Visual Studio developer shell not found" + + quoted_args = ", ".join(_powershell_quote(arg) for arg in argv) + script = ( + "$ErrorActionPreference = 'Stop'; " + f". {_powershell_quote(dev_shell)} -Arch amd64 -HostArch amd64 | Out-Null; " + f"$cmd = @({quoted_args}); " + "& $cmd[0] @($cmd[1..($cmd.Length - 1)])" + ) + return run_cmd( + ["powershell", "-ExecutionPolicy", "Bypass", "-Command", script], + cwd, + ) + + def read_cache_var(cache_path: Path, key: str) -> str | None: if not cache_path.exists(): return None @@ -56,9 +136,11 @@ def detect_llvm_clang_dirs() -> tuple[str | None, str | None]: # Try to reuse the main build configuration if present. if not llvm_dir or not clang_dir: - cache = ROOT / "build" / "CMakeCache.txt" - llvm_dir = llvm_dir or read_cache_var(cache, "LLVM_DIR") - clang_dir = clang_dir or read_cache_var(cache, "Clang_DIR") + for cache in (ROOT / "build" / "CMakeCache.txt", ROOT / "build-win" / "CMakeCache.txt"): + llvm_dir = llvm_dir or read_cache_var(cache, "LLVM_DIR") + clang_dir = clang_dir or read_cache_var(cache, "Clang_DIR") + if llvm_dir and clang_dir: + break # Try llvm-config if still missing. if not llvm_dir: @@ -93,6 +175,9 @@ def detect_llvm_clang_dirs() -> tuple[str | None, str | None]: def configure_and_build() -> Path: + if os.name == "nt" and BUILD.exists(): + shutil.rmtree(BUILD, ignore_errors=True) + if BUILD.exists(): cache = BUILD / "CMakeCache.txt" cached_src = read_cache_var(cache, "CMAKE_HOME_DIRECTORY") @@ -106,32 +191,50 @@ def configure_and_build() -> Path: "-DCMAKE_BUILD_TYPE=Release", f"-DFETCHCONTENT_SOURCE_DIR_CC={ROOT}", ] - llvm_dir, clang_dir = detect_llvm_clang_dirs() + runner = run_cmd + if os.name == "nt": + llvm_root = Path(llvm_dir).parents[2] if llvm_dir else None + clang_cl = llvm_root / "bin" / "clang-cl.exe" if llvm_root else None + cmake_args.extend([ + "-G", "Ninja Multi-Config", + ]) + if clang_cl and clang_cl.exists(): + cmake_args.append(f"-DCMAKE_C_COMPILER={clang_cl}") + cmake_args.append(f"-DCMAKE_CXX_COMPILER={clang_cl}") + runner = run_cmd_in_vsdev if llvm_dir: cmake_args.append(f"-DLLVM_DIR={llvm_dir}") if clang_dir: cmake_args.append(f"-DClang_DIR={clang_dir}") + logger_source_dir = os.environ.get("CORETRACE_LOGGER_SOURCE_DIR") + if logger_source_dir: + cmake_args.append(f"-DFETCHCONTENT_SOURCE_DIR_CORETRACE_LOGGER={Path(logger_source_dir).resolve()}") if not llvm_dir or not clang_dir: print("LLVM/Clang CMake dirs not found. Set LLVM_DIR and Clang_DIR, or build the main project first.") - rc, out, err = run_cmd(cmake_args, ROOT) + rc, out, err = runner(cmake_args, ROOT) if rc != 0: print("cmake configure failed") print(out) print(err) raise SystemExit(1) - rc, out, err = run_cmd(["cmake", "--build", str(BUILD), "-j"], ROOT) + build_args = ["cmake", "--build", str(BUILD), "--config", "Release"] + if os.name != "nt": + build_args.append("-j") + rc, out, err = runner(build_args, ROOT) if rc != 0: print("cmake build failed") print(out) print(err) raise SystemExit(1) - cc1 = BUILD / "cc1" + cc1 = _platform_executable(BUILD / "cc1") + if not cc1.exists(): + cc1 = _platform_executable(BUILD / "Release" / "cc1") if not cc1.exists(): - cc1 = BUILD / "Release" / "cc1" + cc1 = _platform_executable(BUILD / "Debug" / "cc1") if not cc1.exists(): print(f"cc1 binary not found after build: {cc1}") raise SystemExit(1) @@ -167,7 +270,7 @@ def main() -> int: assert_exit_code(0), assert_output_name("hello_ext_c.o"), assert_output_exists(), - assert_native_binary_kind(), + assert_windows_native_artifact("hello_ext_c.o") if os.name == "nt" else assert_native_binary_kind(), ], ) @@ -183,7 +286,7 @@ def main() -> int: assert_exit_code(0), assert_output_name("hello_ext_cpp.o"), assert_output_exists(), - assert_native_binary_kind(), + assert_windows_native_artifact("hello_ext_cpp.o") if os.name == "nt" else assert_native_binary_kind(), ], ) diff --git a/test/examples/test_smoke.py b/test/examples/test_smoke.py index f33153e..9434a5a 100644 --- a/test/examples/test_smoke.py +++ b/test/examples/test_smoke.py @@ -1,4 +1,5 @@ # SPDX-License-Identifier: Apache-2.0 +import os from pathlib import Path import shutil @@ -50,13 +51,74 @@ def _check(res) -> None: f"stderr does not contain '{text}'\nstderr:\n{res.run.stderr}") return Assertion(name=f"stderr_contains_{text}", check=_check) +def _read_artifact_bytes(res, path: str) -> bytes: + artifact = Path(path) + if not artifact.is_absolute(): + artifact = res.run.cwd / artifact + require(artifact.exists(), f"output does not exist: {artifact}") + return artifact.read_bytes() + +def _is_windows_native_artifact(data: bytes) -> bool: + if data.startswith(b"MZ"): + return True + if len(data) < 2: + return False + return data[:2] in {b"\x64\x86", b"\x4c\x01", b"\x64\xaa"} + +def assert_windows_native_artifact_at(path: str) -> Assertion: + def _check(res) -> None: + data = _read_artifact_bytes(res, path) + require( + _is_windows_native_artifact(data), + f"expected PE/COFF artifact at {path}, got unrecognized header", + ) + return Assertion(name=f"windows_native_artifact_{Path(path).name}", check=_check) + +def native_artifact_assert_at(path: str, platform_os: OS) -> Assertion: + if platform_os == OS.WINDOWS: + return assert_windows_native_artifact_at(path) + return assert_native_binary_kind_at(path) + +def resolve_compiler_binary() -> Path | None: + candidates: list[Path] = [] + + env_override = os.environ.get("CORETRACE_COMPILER_TEST_CC") + if env_override: + candidates.append(Path(env_override)) + + candidates.extend([ + ROOT / "dist" / "windows" / "bin" / "cc.exe", + ROOT / "build" / "cc", + ROOT / "build" / "Release" / "cc.exe", + ROOT / "build-win" / "cc.exe", + ROOT / "build-win" / "Release" / "cc.exe", + ]) + + for candidate in candidates: + if candidate.exists(): + return candidate.resolve() + + return None + def main() -> int: - cc_bin = (ROOT / "build" / "cc").resolve() - runner = CompilerRunner(RunnerConfig(executable=cc_bin)) - if not cc_bin.exists(): - print(f"cc binary not found: {cc_bin}") + platform = detect_platform() + cc_bin = resolve_compiler_binary() + if cc_bin is None: + print("cc binary not found. Tried:") + for candidate in [ + os.environ.get("CORETRACE_COMPILER_TEST_CC", ""), + str(ROOT / "dist" / "windows" / "bin" / "cc.exe"), + str(ROOT / "build" / "cc"), + str(ROOT / "build" / "Release" / "cc.exe"), + str(ROOT / "build-win" / "cc.exe"), + str(ROOT / "build-win" / "Release" / "cc.exe"), + ]: + if candidate: + print(f" - {candidate}") return 1 + runner = CompilerRunner(RunnerConfig(executable=cc_bin)) + # Fixtures (ex: hello.c) src = FIXTURES / "hello.c" debug_src = FIXTURES / "debug.c" @@ -65,12 +127,15 @@ def main() -> int: vtable_src = FIXTURES / "vtable.cpp" def base_out_assertions(out_name: str): - return [ + assertions = [ assert_exit_code(0), assert_argv_contains(["-o"]), # check args passed assert_output_name(out_name), # check binary name respected assert_output_exists(), ] + if platform.os == OS.WINDOWS: + assertions.append(assert_windows_native_artifact_at(out_name)) + return assertions tc_macho = TestCase( name="compile_macho_hello", @@ -107,7 +172,7 @@ def base_out_assertions(out_name: str): extra_args=[], ), assertions=base_out_assertions("hello.out") + [ - assert_native_binary_kind(), + assert_windows_native_artifact_at("hello.out") if platform.os == OS.WINDOWS else assert_native_binary_kind(), ], ) @@ -120,7 +185,7 @@ def base_out_assertions(out_name: str): extra_args=[], ), assertions=base_out_assertions("hello_cpp.out") + [ - assert_native_binary_kind(), + assert_windows_native_artifact_at("hello_cpp.out") if platform.os == OS.WINDOWS else assert_native_binary_kind(), ], ) @@ -136,7 +201,7 @@ def base_out_assertions(out_name: str): assert_exit_code(0), assert_argv_contains(["-o=main"]), assert_output_exists_at("main"), - assert_native_binary_kind_at("main"), + native_artifact_assert_at("main", platform.os), ], ) @@ -152,7 +217,7 @@ def base_out_assertions(out_name: str): assert_exit_code(0), assert_argv_contains(["-D", "DEBUG"]), assert_output_exists_at("debug_space"), - assert_native_binary_kind_at("debug_space"), + native_artifact_assert_at("debug_space", platform.os), ], ) @@ -168,7 +233,7 @@ def base_out_assertions(out_name: str): assert_exit_code(0), assert_argv_contains(["-DDEBUG"]), assert_output_exists_at("debug_compact"), - assert_native_binary_kind_at("debug_compact"), + native_artifact_assert_at("debug_compact", platform.os), ], ) @@ -184,7 +249,7 @@ def base_out_assertions(out_name: str): assert_exit_code(0), assert_argv_contains(["-x=c++"]), assert_output_exists_at("hello_xcxx.out"), - assert_native_binary_kind_at("hello_xcxx.out"), + native_artifact_assert_at("hello_xcxx.out", platform.os), ], ) @@ -198,7 +263,7 @@ def base_out_assertions(out_name: str): ), assertions=base_out_assertions("hello_instr_c.out") + [ assert_argv_contains(["--instrument"]), - assert_native_binary_kind(), + assert_windows_native_artifact_at("hello_instr_c.out") if platform.os == OS.WINDOWS else assert_native_binary_kind(), ], ) @@ -212,7 +277,7 @@ def base_out_assertions(out_name: str): ), assertions=base_out_assertions("hello_instr_cpp.out") + [ assert_argv_contains(["--instrument"]), - assert_native_binary_kind(), + assert_windows_native_artifact_at("hello_instr_cpp.out") if platform.os == OS.WINDOWS else assert_native_binary_kind(), ], ) @@ -228,7 +293,7 @@ def base_out_assertions(out_name: str): assert_exit_code(0), assert_argv_contains(["--instrument", "-x=c++"]), assert_output_exists_at("hello_instr_xcxx.out"), - assert_native_binary_kind_at("hello_instr_xcxx.out"), + native_artifact_assert_at("hello_instr_xcxx.out", platform.os), ], ) @@ -306,7 +371,7 @@ def base_out_assertions(out_name: str): assertions=[ assert_exit_code(0), assert_argv_contains(["-c"]), - assert_native_binary_kind_at("hello.o"), + native_artifact_assert_at("hello.o", platform.os), ], ) @@ -321,7 +386,7 @@ def base_out_assertions(out_name: str): assertions=[ assert_exit_code(0), assert_argv_contains(["-c", "-O2"]), - assert_native_binary_kind_at("hello.o"), + native_artifact_assert_at("hello.o", platform.os), ], ) @@ -337,7 +402,7 @@ def base_out_assertions(out_name: str): assert_exit_code(0), assert_argv_contains(["--instrument", "-o", "app"]), assert_output_exists_at("app"), - assert_native_binary_kind_at("app"), + native_artifact_assert_at("app", platform.os), ], ) @@ -353,7 +418,7 @@ def base_out_assertions(out_name: str): assert_exit_code(0), assert_argv_contains(["--instrument", "--ct-shadow"]), assert_output_exists_at("app_shadow"), - assert_native_binary_kind_at("app_shadow"), + native_artifact_assert_at("app_shadow", platform.os), ], ) @@ -369,7 +434,7 @@ def base_out_assertions(out_name: str): assert_exit_code(0), assert_argv_contains(["--instrument", "--ct-shadow-aggressive", "--ct-bounds-no-abort"]), assert_output_exists_at("app_shadow_aggr"), - assert_native_binary_kind_at("app_shadow_aggr"), + native_artifact_assert_at("app_shadow_aggr", platform.os), ], ) @@ -385,7 +450,7 @@ def base_out_assertions(out_name: str): assert_exit_code(0), assert_argv_contains(["--instrument", "--ct-modules=vtable", "--ct-vcall-trace"]), assert_output_exists_at("app_vtable"), - assert_native_binary_kind_at("app_vtable"), + native_artifact_assert_at("app_vtable", platform.os), ], ) @@ -449,7 +514,6 @@ def base_out_assertions(out_name: str): ], ) - platform = detect_platform() common_cases = [tc_o_eq, tc_d_space, tc_d_compact, tc_cpp, tc_x_cxx] instrument_cases = [ tc_instrument_c, @@ -476,7 +540,18 @@ def base_out_assertions(out_name: str): elif platform.os == OS.LINUX: cases = [tc_elf, *common_cases, *instrument_cases, *readme_cases] else: - cases = [tc_native, *common_cases] + windows_readme_cases = [ + tc_readme_emit_llvm, + tc_readme_c_obj, + tc_readme_c_obj_o2, + tc_readme_instrument, + tc_readme_shadow, + tc_readme_shadow_aggr, + tc_readme_inmem, + tc_optnone_emit_llvm, + tc_optnone_disable_o0, + ] + cases = [tc_native, *common_cases, *instrument_cases, *windows_readme_cases] suite = TestSuite(name="compiler_smoke", cases=cases) From bc99756686dde9d1999aa4b76b5a101351f972dd Mon Sep 17 00:00:00 2001 From: shookapic Date: Wed, 15 Apr 2026 22:04:28 +0900 Subject: [PATCH 2/5] fix(runtime): restore unistd.h for POSIX and guard sbrk/brk as Linux-only --- .github/workflows/build.yml | 86 ++++++++++++++++++++++++++----- src/runtime/ct_runtime_alloc.cpp | 33 ++++++++---- src/runtime/ct_runtime_internal.h | 2 + 3 files changed, 97 insertions(+), 24 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6ff9f60..4ebc12d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -136,31 +136,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" } diff --git a/src/runtime/ct_runtime_alloc.cpp b/src/runtime/ct_runtime_alloc.cpp index 8c1915b..5e8aaf5 100644 --- a/src/runtime/ct_runtime_alloc.cpp +++ b/src/runtime/ct_runtime_alloc.cpp @@ -961,6 +961,7 @@ CT_NOINSTR static void ct_autofree_do_free(const struct ct_autofree_free_item& i (void)munmap(item.ptr, item.size); break; case CT_ALLOC_KIND_SBRK: +#if defined(__linux__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" { @@ -983,6 +984,11 @@ CT_NOINSTR static void ct_autofree_do_free(const struct ct_autofree_free_item& i break; } #pragma clang diagnostic pop +#else + ct_log(CTLevel::Warn, "{}ct: auto-free skipped ptr={:p} (sbrk not supported){}\n", + ct_color(CTColor::BgBrightYellow), item.ptr, ct_color(CTColor::Reset)); + break; +#endif case CT_ALLOC_KIND_MALLOC: default: if (ct_is_enabled(CT_FEATURE_SHADOW)) @@ -2404,6 +2410,7 @@ extern "C" CT_NODISCARD CT_NOINSTR void* __ct_sbrk(size_t incr, const char* site) { +#if defined(__linux__) ct_init_env_once(); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" @@ -2455,34 +2462,35 @@ extern "C" } return prev; +#else + (void)incr; + (void)site; + return reinterpret_cast(-1); +#endif } CT_NODISCARD CT_NOINSTR void* __ct_brk(void* addr, const char* site) { +#if defined(__linux__) ct_init_env_once(); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" -#if defined(__APPLE__) - void* rc = brk(addr); - void* ret = rc; -#else int rc = brk(addr); void* ret = (rc == 0) ? addr : reinterpret_cast(-1); -#endif #pragma clang diagnostic pop if (ct_is_enabled(CT_FEATURE_ALLOC_TRACE)) { -#if defined(__APPLE__) - ct_log(CTLevel::Info, "{}tracing-brk addr={:p} rc={:p} site={}{}\n", - ct_color(CTColor::Cyan), addr, rc, ct_site_name(site), ct_color(CTColor::Reset)); -#else ct_log(CTLevel::Info, "{}tracing-brk addr={:p} rc={} ret={:p} site={}{}\n", ct_color(CTColor::Cyan), addr, rc, ret, ct_site_name(site), ct_color(CTColor::Reset)); -#endif } return ret; +#else + (void)addr; + (void)site; + return reinterpret_cast(-1); +#endif } CT_NOINSTR void __ct_autofree(void* ptr) @@ -2674,6 +2682,7 @@ extern "C" ct_color(CTColor::Reset)); return; } +#if defined(__linux__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" @@ -2695,6 +2704,10 @@ extern "C" ct_log(CTLevel::Warn, "{}ct: auto-free skipped ptr={:p} (sbrk not top){}\n", ct_color(CTColor::BgBrightYellow), ptr, ct_color(CTColor::Reset)); +#else + ct_log(CTLevel::Warn, "{}ct: auto-free skipped ptr={:p} (sbrk not supported){}\n", + ct_color(CTColor::BgBrightYellow), ptr, ct_color(CTColor::Reset)); +#endif } CT_NOINSTR void __ct_autofree_delete(void* ptr) diff --git a/src/runtime/ct_runtime_internal.h b/src/runtime/ct_runtime_internal.h index 0fcc954..c2beecf 100644 --- a/src/runtime/ct_runtime_internal.h +++ b/src/runtime/ct_runtime_internal.h @@ -20,6 +20,8 @@ #endif #include #include +#else +#include #endif #if defined(_MSC_VER) From 0dafaa424e8b29a1ea91864a5763b0db71cfd2bb Mon Sep 17 00:00:00 2001 From: shookapic Date: Wed, 15 Apr 2026 22:12:00 +0900 Subject: [PATCH 3/5] fix(clang format): code is now formatted correctly --- src/runtime/ct_runtime_env.cpp | 4 ++-- src/runtime/ct_runtime_internal.h | 12 +++++------ src/runtime/windows/ct_runtime_alloc.cpp | 13 +++++++----- src/runtime/windows/ct_runtime_backtrace.cpp | 14 +++++++------ src/runtime/windows/ct_runtime_env.cpp | 6 ++++-- src/runtime/windows/ct_runtime_vtable.cpp | 21 +++++++++++--------- 6 files changed, 39 insertions(+), 31 deletions(-) diff --git a/src/runtime/ct_runtime_env.cpp b/src/runtime/ct_runtime_env.cpp index 8d0a66c..27d0432 100644 --- a/src/runtime/ct_runtime_env.cpp +++ b/src/runtime/ct_runtime_env.cpp @@ -69,7 +69,7 @@ CT_NOINSTR __attribute__((constructor)) static void ct_runtime_init(void) ct_apply_compiled_config(); #ifdef _MSC_VER #pragma warning(push) -#pragma warning(disable : 4996) // MSVC warning: 'getenv': This function or variable may be unsafe +#pragma warning(disable : 4996) // MSVC warning: 'getenv': This function or variable may be unsafe #endif if (getenv("CT_DISABLE_TRACE") != nullptr) { @@ -126,7 +126,7 @@ CT_NOINSTR void ct_init_env_once(void) ct_apply_compiled_config(); #ifdef _MSC_VER #pragma warning(push) -#pragma warning(disable : 4996) // MSVC warning: 'getenv': This function or variable may be unsafe +#pragma warning(disable : 4996) // MSVC warning: 'getenv': This function or variable may be unsafe #endif if (getenv("CT_DISABLE_TRACE") != nullptr) { diff --git a/src/runtime/ct_runtime_internal.h b/src/runtime/ct_runtime_internal.h index c2beecf..00f22dc 100644 --- a/src/runtime/ct_runtime_internal.h +++ b/src/runtime/ct_runtime_internal.h @@ -45,8 +45,7 @@ inline int ct_msvc_atomic_exchange(volatile int* object, int desired) inline void ct_msvc_atomic_store(volatile int* object, int desired) { - (void)InterlockedExchange(reinterpret_cast(object), - static_cast(desired)); + (void)InterlockedExchange(reinterpret_cast(object), static_cast(desired)); } inline bool ct_msvc_atomic_compare_exchange(volatile int* object, int* expected, int desired) @@ -63,13 +62,12 @@ inline bool ct_msvc_atomic_compare_exchange(volatile int* object, int* expected, return false; } -#define __atomic_exchange_n(object, desired, order) \ +#define __atomic_exchange_n(object, desired, order) \ ct_msvc_atomic_exchange(reinterpret_cast(object), static_cast(desired)) -#define __atomic_store_n(object, desired, order) \ +#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), \ +#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)) diff --git a/src/runtime/windows/ct_runtime_alloc.cpp b/src/runtime/windows/ct_runtime_alloc.cpp index 7e6b103..61f8216 100644 --- a/src/runtime/windows/ct_runtime_alloc.cpp +++ b/src/runtime/windows/ct_runtime_alloc.cpp @@ -79,7 +79,8 @@ namespace } if (alloc_size > live_size) { - ct_shadow_poison_range(static_cast(ptr) + live_size, alloc_size - live_size); + ct_shadow_poison_range(static_cast(ptr) + live_size, + alloc_size - live_size); } } @@ -256,7 +257,8 @@ namespace 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); + (void)ct_table_remove_with_state(old_ptr, CT_ENTRY_FREED, nullptr, nullptr, nullptr, + nullptr); } auto& entry = ct_alloc_table[new_ptr]; @@ -315,8 +317,8 @@ namespace ::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) + 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; @@ -683,7 +685,8 @@ extern "C" return reinterpret_cast(-1); } - void* ptr = VirtualAlloc(addr, len, MEM_COMMIT | MEM_RESERVE, ct_translate_page_protection(prot)); + void* ptr = + VirtualAlloc(addr, len, MEM_COMMIT | MEM_RESERVE, ct_translate_page_protection(prot)); if (!ptr) { errno = ENOMEM; diff --git a/src/runtime/windows/ct_runtime_backtrace.cpp b/src/runtime/windows/ct_runtime_backtrace.cpp index b5767d3..353ec29 100644 --- a/src/runtime/windows/ct_runtime_backtrace.cpp +++ b/src/runtime/windows/ct_runtime_backtrace.cpp @@ -21,12 +21,14 @@ namespace 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); - }); + 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) diff --git a/src/runtime/windows/ct_runtime_env.cpp b/src/runtime/windows/ct_runtime_env.cpp index ca82dca..899b149 100644 --- a/src/runtime/windows/ct_runtime_env.cpp +++ b/src/runtime/windows/ct_runtime_env.cpp @@ -5,9 +5,11 @@ #include #if defined(_M_IX86) -#define CT_ALTNAME(symbol, fallback) __pragma(comment(linker, "/alternatename:_" #symbol "=_" #fallback)) +#define CT_ALTNAME(symbol, fallback) \ + __pragma(comment(linker, "/alternatename:_" #symbol "=_" #fallback)) #else -#define CT_ALTNAME(symbol, fallback) __pragma(comment(linker, "/alternatename:" #symbol "=" #fallback)) +#define CT_ALTNAME(symbol, fallback) \ + __pragma(comment(linker, "/alternatename:" #symbol "=" #fallback)) #endif extern "C" diff --git a/src/runtime/windows/ct_runtime_vtable.cpp b/src/runtime/windows/ct_runtime_vtable.cpp index b2af2a7..772208e 100644 --- a/src/runtime/windows/ct_runtime_vtable.cpp +++ b/src/runtime/windows/ct_runtime_vtable.cpp @@ -82,12 +82,14 @@ namespace 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); - }); + 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) @@ -155,8 +157,8 @@ namespace symbol->MaxNameLen = MAX_SYM_NAME; DWORD64 displacement = 0; - if (SymFromAddr(GetCurrentProcess(), reinterpret_cast(addr), &displacement, symbol) == - FALSE) + if (SymFromAddr(GetCurrentProcess(), reinterpret_cast(addr), &displacement, + symbol) == FALSE) { return false; } @@ -313,7 +315,8 @@ namespace return reinterpret_cast(type_addr); } - CT_NODISCARD CT_NOINSTR std::string ct_format_type_name(const CtRttiTypeDescriptor* type_descriptor) + CT_NODISCARD CT_NOINSTR std::string + ct_format_type_name(const CtRttiTypeDescriptor* type_descriptor) { if (!type_descriptor || !ct_is_readable(type_descriptor, sizeof(CtRttiTypeDescriptor))) { From 07640fdd1fb7889be7752c9a49d91f62141f8dbf Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 5 May 2026 16:22:14 +0900 Subject: [PATCH 4/5] fix(linker): derive runtime link flags from target triple --- src/compilerlib/compiler.cpp | 78 +++++++++++++++++++++++++++++------- 1 file changed, 63 insertions(+), 15 deletions(-) diff --git a/src/compilerlib/compiler.cpp b/src/compilerlib/compiler.cpp index 312b6c3..889c4f1 100644 --- a/src/compilerlib/compiler.cpp +++ b/src/compilerlib/compiler.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -148,6 +149,64 @@ namespace compilerlib return false; } + CT_NODISCARD llvm::Triple effectiveTargetTriple(const std::vector& args) + { + std::string target = kTargetTriple.str(); + for (size_t i = 0; i < args.size(); ++i) + { + llvm::StringRef arg = args[i]; + if ((arg == "-target" || arg == "--target") && i + 1 < args.size()) + { + target = args[i + 1]; + ++i; + continue; + } + if (arg.starts_with("--target=")) + { + target = arg.substr(std::strlen("--target=")).str(); + continue; + } + if (arg.starts_with("-target=")) + { + target = arg.substr(std::strlen("-target=")).str(); + continue; + } + } + + return llvm::Triple(llvm::Triple::normalize(target)); + } + + CT_NODISCARD bool needsDlfcn(const RuntimeConfig& runtimeConfig) + { + return runtimeConfig.vtable_enabled || runtimeConfig.vcall_trace_enabled || + runtimeConfig.vtable_diag_enabled; + } + + void appendRuntimeLinkOptions(std::vector& clangArgs, + const llvm::Triple& targetTriple, + const RuntimeConfig& runtimeConfig) + { + if (targetTriple.isOSDarwin()) + { + clangArgs.push_back("-lc++"); + return; + } + if (targetTriple.isOSLinux()) + { + clangArgs.push_back("-lstdc++"); + if (needsDlfcn(runtimeConfig)) + { + clangArgs.push_back("-ldl"); + } + return; + } + if (targetTriple.isWindowsGNUEnvironment()) + { + clangArgs.push_back("-lstdc++"); + clangArgs.push_back("-ldbghelp"); + } + } + void appendDiagnostics(std::string& out, const std::string& extra) { if (extra.empty()) @@ -239,6 +298,7 @@ namespace compilerlib ctx_.clang_path = driverCfg.clang_path; ctx_.clang_resource_dir = driverCfg.resource_dir; ctx_.clang_sysroot = driverCfg.sysroot; + const llvm::Triple targetTriple = effectiveTargetTriple(ctx_.filtered_args); ctx_.clang_args.clear(); ctx_.clang_args.push_back(ctx_.clang_path.c_str()); @@ -272,7 +332,7 @@ namespace compilerlib ctx_.clang_args.push_back("-fno-builtin-free"); // Ensure position-independent code for Linux targets // to avoid relocation errors with PIE-enabled distributions - if (kTargetTriple.contains("linux")) + if (targetTriple.isOSLinux()) { if (!hasArg(ctx_.filtered_args, "-fPIE") && !hasArg(ctx_.filtered_args, "-fPIC")) @@ -286,7 +346,7 @@ namespace compilerlib { #ifdef CT_RUNTIME_LIB_PATH // Ensure position-independent executable linking on Linux - if (kTargetTriple.contains("linux")) + if (targetTriple.isOSLinux()) { if (!hasArg(ctx_.filtered_args, "-pie")) { @@ -299,19 +359,7 @@ namespace compilerlib #ifdef CT_RUNTIME_LOGGER_LIB_PATH ctx_.clang_args.push_back(CT_RUNTIME_LOGGER_LIB_PATH); #endif -#ifdef __APPLE__ - ctx_.clang_args.push_back("-lc++"); -#else - ctx_.clang_args.push_back("-lstdc++"); -#if defined(__linux__) - if (ctx_.runtimeConfig.vtable_enabled || - ctx_.runtimeConfig.vcall_trace_enabled || - ctx_.runtimeConfig.vtable_diag_enabled) - { - ctx_.clang_args.push_back("-ldl"); - } -#endif -#endif + appendRuntimeLinkOptions(ctx_.clang_args, targetTriple, ctx_.runtimeConfig); #else error = "instrumentation runtime path not configured"; return false; From 45f18a2c1df0cf6a2eed0490972cac1d94f85fec Mon Sep 17 00:00:00 2001 From: Hugo Date: Fri, 8 May 2026 20:52:50 +0900 Subject: [PATCH 5/5] fix(runtime): preserve Windows deallocation API semantics --- src/runtime/windows/ct_runtime_alloc.cpp | 313 ++++++++++++++--------- 1 file changed, 188 insertions(+), 125 deletions(-) diff --git a/src/runtime/windows/ct_runtime_alloc.cpp b/src/runtime/windows/ct_runtime_alloc.cpp index 61f8216..6ffa0f4 100644 --- a/src/runtime/windows/ct_runtime_alloc.cpp +++ b/src/runtime/windows/ct_runtime_alloc.cpp @@ -27,6 +27,17 @@ namespace CT_ALLOC_KIND_ALIGNED = 5 }; + enum class CtReleaseApi : unsigned char + { + Free, + Delete, + DeleteArray, + DeleteNothrow, + DeleteArrayNothrow, + DeleteDestroying, + DeleteArrayDestroying + }; + struct CtAllocEntry { size_t size = 0; @@ -110,6 +121,91 @@ namespace ct_log(CTLevel::Warn, "ct: {} skipped ptr={:p} ({})\n", action, ptr, reason); } + CT_NODISCARD CT_NOINSTR const char* ct_release_api_name(CtReleaseApi api) + { + switch (api) + { + case CtReleaseApi::Free: + return "free"; + case CtReleaseApi::Delete: + return "delete"; + case CtReleaseApi::DeleteArray: + return "delete[]"; + case CtReleaseApi::DeleteNothrow: + return "delete-nothrow"; + case CtReleaseApi::DeleteArrayNothrow: + return "delete[]-nothrow"; + case CtReleaseApi::DeleteDestroying: + return "destroying-delete"; + case CtReleaseApi::DeleteArrayDestroying: + return "destroying-delete[]"; + } + return "release"; + } + + CT_NODISCARD CT_NOINSTR const char* ct_expected_kind_label(CtReleaseApi api) + { + switch (api) + { + case CtReleaseApi::Free: + return "malloc/aligned"; + case CtReleaseApi::Delete: + case CtReleaseApi::DeleteNothrow: + case CtReleaseApi::DeleteDestroying: + return "new"; + case CtReleaseApi::DeleteArray: + case CtReleaseApi::DeleteArrayNothrow: + case CtReleaseApi::DeleteArrayDestroying: + return "new[]"; + } + return "unknown"; + } + + CT_NODISCARD CT_NOINSTR unsigned char ct_default_kind_for_release_api(CtReleaseApi api) + { + switch (api) + { + case CtReleaseApi::Free: + return CT_ALLOC_KIND_MALLOC; + case CtReleaseApi::DeleteArray: + case CtReleaseApi::DeleteArrayNothrow: + case CtReleaseApi::DeleteArrayDestroying: + return CT_ALLOC_KIND_NEW_ARRAY; + case CtReleaseApi::Delete: + case CtReleaseApi::DeleteNothrow: + case CtReleaseApi::DeleteDestroying: + return CT_ALLOC_KIND_NEW; + } + return CT_ALLOC_KIND_MALLOC; + } + + CT_NODISCARD CT_NOINSTR bool ct_release_api_matches_kind(CtReleaseApi api, unsigned char kind) + { + switch (api) + { + case CtReleaseApi::Free: + return kind == CT_ALLOC_KIND_MALLOC || kind == CT_ALLOC_KIND_ALIGNED; + case CtReleaseApi::Delete: + case CtReleaseApi::DeleteNothrow: + case CtReleaseApi::DeleteDestroying: + return kind == CT_ALLOC_KIND_NEW; + case CtReleaseApi::DeleteArray: + case CtReleaseApi::DeleteArrayNothrow: + case CtReleaseApi::DeleteArrayDestroying: + return kind == CT_ALLOC_KIND_NEW_ARRAY; + } + return false; + } + + CT_NOINSTR void ct_log_deallocator_mismatch(const char* action, void* ptr, + unsigned char recorded_kind, const char* expected, + const char* site) + { + ct_log(CTLevel::Warn, + "ct: deallocator mismatch action={} ptr={:p} expected={} recorded={} site={}\n", + action, ptr, expected, ct_kind_name(recorded_kind), ct_site_name(site)); + } + 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, @@ -153,7 +249,7 @@ namespace return 1; } - CT_NOINSTR void ct_release_memory(void* ptr, unsigned char kind) + CT_NOINSTR void ct_release_autofree_memory(void* ptr, unsigned char kind) { if (!ptr) { @@ -180,21 +276,94 @@ namespace } } - CT_NOINSTR void ct_release_unknown_delete(void* ptr, bool is_array) + CT_NOINSTR void ct_release_by_called_api(void* ptr, CtReleaseApi api, + unsigned char recorded_kind) { if (!ptr) { return; } - if (is_array) + switch (api) { + case CtReleaseApi::Free: + if (recorded_kind == CT_ALLOC_KIND_ALIGNED) + { + _aligned_free(ptr); + return; + } + std::free(ptr); + return; + case CtReleaseApi::Delete: + ::operator delete(ptr); + return; + case CtReleaseApi::DeleteArray: + ::operator delete[](ptr); + return; + case CtReleaseApi::DeleteNothrow: + ::operator delete(ptr, std::nothrow); + return; + case CtReleaseApi::DeleteArrayNothrow: + ::operator delete[](ptr, std::nothrow); + return; + case CtReleaseApi::DeleteDestroying: + ::operator delete(ptr); + return; + case CtReleaseApi::DeleteArrayDestroying: ::operator delete[](ptr); + return; } - else + } + + 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); + + CT_NOINSTR void ct_release_tracked_pointer(void* ptr, CtReleaseApi api) + { + ct_init_env_once(); + + const char* action = ct_release_api_name(api); + unsigned char kind = ct_default_kind_for_release_api(api); + if (!ct_is_enabled(CT_FEATURE_ALLOC)) { - ::operator delete(ptr); + ct_release_by_called_api(ptr, api, kind); + return; } + + if (!ptr) + { + ct_log_skip_event(action, ptr, "null"); + ct_release_by_called_api(ptr, api, kind); + return; + } + + size_t size = 0; + const char* site = nullptr; + + 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(action, ptr, "already freed"); + return; + } + if (found == 0) + { + ct_log_skip_event(action, ptr, "unknown"); + ct_release_by_called_api(ptr, api, kind); + return; + } + + ct_track_shadow_free(ptr, size); + ct_log_alloc_event(action, ptr, size, site, kind); + if (!ct_release_api_matches_kind(api, kind)) + { + ct_log_deallocator_mismatch(action, ptr, kind, ct_expected_kind_label(api), site); + } + ct_release_by_called_api(ptr, api, kind); } CT_NODISCARD CT_NOINSTR DWORD ct_translate_page_protection(int prot) @@ -281,7 +450,7 @@ namespace 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_NOINSTR void ct_autofree_impl(void* ptr) { ct_init_env_once(); if (!ct_is_enabled(CT_FEATURE_ALLOC) || !ct_is_enabled(CT_FEATURE_AUTOFREE)) @@ -312,19 +481,7 @@ namespace 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); + ct_release_autofree_memory(ptr, kind); } struct CtLeakReporter @@ -750,155 +907,61 @@ extern "C" CT_NOINSTR void __ct_autofree(void* ptr) { - ct_autofree_impl(ptr, false); + ct_autofree_impl(ptr); } CT_NOINSTR void __ct_autofree_munmap(void* ptr) { - ct_autofree_impl(ptr, false); + ct_autofree_impl(ptr); } CT_NOINSTR void __ct_autofree_sbrk(void* ptr) { - ct_autofree_impl(ptr, false); + ct_autofree_impl(ptr); } CT_NOINSTR void __ct_autofree_delete(void* ptr) { - ct_autofree_impl(ptr, false); + ct_autofree_impl(ptr); } CT_NOINSTR void __ct_autofree_delete_array(void* ptr) { - ct_autofree_impl(ptr, true); + ct_autofree_impl(ptr); } 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_release_tracked_pointer(ptr, CtReleaseApi::Free); } 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_release_tracked_pointer(ptr, CtReleaseApi::Delete); } 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_release_tracked_pointer(ptr, CtReleaseApi::DeleteArray); } CT_NOINSTR void __ct_delete_nothrow(void* ptr) { - __ct_delete(ptr); + ct_release_tracked_pointer(ptr, CtReleaseApi::DeleteNothrow); } CT_NOINSTR void __ct_delete_array_nothrow(void* ptr) { - __ct_delete_array(ptr); + ct_release_tracked_pointer(ptr, CtReleaseApi::DeleteArrayNothrow); } CT_NOINSTR void __ct_delete_destroying(void* ptr) { - __ct_delete(ptr); + ct_release_tracked_pointer(ptr, CtReleaseApi::DeleteDestroying); } CT_NOINSTR void __ct_delete_array_destroying(void* ptr) { - __ct_delete_array(ptr); + ct_release_tracked_pointer(ptr, CtReleaseApi::DeleteArrayDestroying); } }