diff --git a/.github/workflows/build-reuse-winkernel.yml b/.github/workflows/build-reuse-winkernel.yml index 4ee406f..1d9c27a 100644 --- a/.github/workflows/build-reuse-winkernel.yml +++ b/.github/workflows/build-reuse-winkernel.yml @@ -58,6 +58,9 @@ jobs: - name: Build shell: pwsh run: scripts/build.ps1 -Config ${{ inputs.config }} -Platform ${{ inputs.plat }} -Arch ${{ inputs.arch }} + - name: Sign Kernel + shell: pwsh + run: scripts/sign.ps1 -Config ${{ inputs.config }} -Arch ${{ inputs.arch }} - name: Upload build artifacts uses: actions/upload-artifact@1746f4ab65b179e0ea60a494b83293b640dd5bba with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..16bcf37 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,165 @@ +name: BVT + +on: + workflow_dispatch: + push: + branches: + - main + - release/* + pull_request: + branches: + - main + - release/* + +concurrency: + # Cancel any workflow currently in progress for the same PR. + # Allow running concurrently with any other commits. + group: bvt-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +permissions: read-all + +jobs: + build-windows-kernel: + name: Build WinKernel + strategy: + fail-fast: false + matrix: + vec: [ + { config: "Debug", plat: "winkernel", os: "windows-2022", arch: "x64" }, + { config: "Release", plat: "winkernel", os: "windows-2022", arch: "x64" } + ] + uses: ./.github/workflows/build-reuse-winkernel.yml + with: + config: ${{ matrix.vec.config }} + plat: ${{ matrix.vec.plat }} + os: ${{ matrix.vec.os }} + arch: ${{ matrix.vec.arch }} + + build-windows: + name: Build WinUser + strategy: + fail-fast: false + matrix: + vec: [ + { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64" }, + { config: "Release", plat: "windows", os: "windows-2022", arch: "x64" } + ] + uses: ./.github/workflows/build-reuse-win.yml + with: + config: ${{ matrix.vec.config }} + plat: ${{ matrix.vec.plat }} + os: ${{ matrix.vec.os }} + arch: ${{ matrix.vec.arch }} + + build-unix: + name: Build Unix + strategy: + fail-fast: false + matrix: + vec: [ + { config: "Debug", plat: "linux", os: "ubuntu-20.04", arch: "x64" }, + { config: "Release", plat: "linux", os: "ubuntu-20.04", arch: "x64" }, + { config: "Debug", plat: "linux", os: "ubuntu-22.04", arch: "x64" }, + { config: "Release", plat: "linux", os: "ubuntu-22.04", arch: "x64" } + ] + uses: ./.github/workflows/build-reuse-unix.yml + with: + config: ${{ matrix.vec.config }} + plat: ${{ matrix.vec.plat }} + os: ${{ matrix.vec.os }} + arch: ${{ matrix.vec.arch }} + + bvt: + name: BVT + needs: [build-windows, build-unix] + strategy: + fail-fast: false + matrix: + vec: [ + { config: "Debug", plat: "linux", os: "ubuntu-20.04", arch: "x64" }, + { config: "Release", plat: "linux", os: "ubuntu-20.04", arch: "x64" }, + { config: "Debug", plat: "linux", os: "ubuntu-22.04", arch: "x64" }, + { config: "Release", plat: "linux", os: "ubuntu-22.04", arch: "x64" }, + { config: "Debug", plat: "windows", os: "windows-2019", arch: "x64" }, + { config: "Release", plat: "windows", os: "windows-2019", arch: "x64" }, + { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64" }, + { config: "Release", plat: "windows", os: "windows-2022", arch: "x64" } + ] + runs-on: ${{ matrix.vec.os }} + steps: + - name: Checkout repository + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 + - name: Download Build Artifacts + uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 + if: matrix.vec.plat == 'windows' + with: # note we always use binaries built on windows-2022. + name: ${{ matrix.vec.config }}-${{ matrix.vec.plat }}-windows-2022-${{ matrix.vec.arch }} + path: artifacts + - name: Download Build Artifacts + uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 + if: matrix.vec.plat == 'linux' + with: + name: ${{ matrix.vec.config }}-${{ matrix.vec.plat }}-${{ matrix.vec.os }}-${{ matrix.vec.arch }}- + path: artifacts + - name: Fix permissions for Unix + if: matrix.vec.plat == 'linux' || matrix.vec.plat == 'macos' + run: | + sudo chmod -R 777 artifacts + - name: Prepare Machine + run: scripts/prepare-machine.ps1 -ForTest + shell: pwsh + - name: Test + if: matrix.vec.os == 'WinServerPrerelease' + shell: pwsh + timeout-minutes: 5 + run: scripts/test.ps1 -Config ${{ matrix.vec.config }} -Arch ${{ matrix.vec.arch }} -GHA -GenerateXmlResults + - name: Test + if: matrix.vec.os != 'WinServerPrerelease' + shell: pwsh + timeout-minutes: 5 + run: scripts/test.ps1 -Config ${{ matrix.vec.config }} -Arch ${{ matrix.vec.arch }} -OsRunner ${{ matrix.vec.os }} -GHA -GenerateXmlResults + - name: Upload on Failure + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 + if: failure() + with: + name: BVT-${{ matrix.vec.config }}-${{ matrix.vec.plat }}-${{ matrix.vec.os }}-${{ matrix.vec.arch }} + path: artifacts + + bvt-kernel: + name: BVT Kernel + needs: [build-windows, build-windows-kernel] + strategy: + fail-fast: false + matrix: + vec: [ + { config: "Debug", plat: "winkernel", os: "windows-2022", arch: "x64" }, + { config: "Release", plat: "winkernel", os: "windows-2022", arch: "x64" } + ] + runs-on: ${{ matrix.vec.os }} + steps: + - name: Checkout repository + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 + - name: Download Build Artifacts + uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 + with: # note we always use binaries built on windows-2022. + name: ${{ matrix.vec.config }}-${{ matrix.vec.plat }}-windows-2022-${{ matrix.vec.arch }} + path: artifacts + - name: Download Build Artifacts for Testing From WinUser + uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 + with: # note we always use binaries built on windows-2022. + name: ${{ matrix.vec.config }}-windows-windows-2022-${{ matrix.vec.arch }} + path: artifacts + - name: Prepare Machine + shell: pwsh + run: scripts/prepare-machine.ps1 -ForTest -ForKernel + - name: Test + shell: pwsh + timeout-minutes: 5 + run: scripts/test.ps1 -Config ${{ matrix.vec.config }} -Arch ${{ matrix.vec.arch }} -GHA -GenerateXmlResults -Kernel + - name: Upload on Failure + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 + if: failure() + with: + name: BVT-Kernel-${{ matrix.vec.config }}-${{ matrix.vec.plat }}-${{ matrix.vec.os }}-${{ matrix.vec.arch }} + path: artifacts diff --git a/.gitignore b/.gitignore index dfcfd56..4bdb999 100644 --- a/.gitignore +++ b/.gitignore @@ -26,10 +26,12 @@ x86/ [Aa][Rr][Mm]/ [Aa][Rr][Mm]64/ bld/ -[Bb]in/ +# "bin" is used by this project +#[Bb]in/ [Oo]bj/ [Ll]og/ [Ll]ogs/ +[Bb]uild/ # Visual Studio 2015/2017 cache/options directory .vs/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..3e4ff3e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "submodules/googletest"] + path = submodules/googletest + url = https://github.com/google/googletest diff --git a/CMakeLists.txt b/CMakeLists.txt index 0affa08..52be204 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -70,6 +70,7 @@ message(STATUS "Platform: ${CX_PLATFORM}") set(FILENAME_DEP_REPLACE "get_filename_component(SELF_DIR \"$\{CMAKE_CURRENT_LIST_FILE\}\" PATH)") set(SELF_DIR "$\{SELF_DIR\}") +option(CXPLAT_BUILD_TEST "Builds the test code" OFF) option(CXPLAT_UWP_BUILD "Build for UWP" OFF) option(CXPLAT_GAMECORE_BUILD "Build for GameCore" OFF) option(CXPLAT_EMBED_GIT_HASH "Embed git commit hash in the binary" ON) @@ -87,6 +88,11 @@ if (CXPLAT_GAMECORE_BUILD) endif() endif() +if (CXPLAT_UWP_BUILD OR CXPLAT_GAMECORE_BUILD) + message(STATUS "UWP And GameCore builds disable all executables") + set(CXPLAT_BUILD_TEST OFF) +endif() + if (NOT CXPLAT_BUILD_SHARED) cmake_minimum_required(VERSION 3.20) endif() @@ -292,6 +298,8 @@ if(WIN32) endif() + list(APPEND CXPLAT_COMMON_DEFINES CX_PLATFORM_WINUSER) + set(CXPLAT_C_FLAGS ${CXPLAT_COMMON_FLAGS}) set(CXPLAT_CXX_FLAGS ${CXPLAT_COMMON_FLAGS} /EHsc /permissive-) @@ -301,6 +309,9 @@ if(WIN32) set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /LTCG /IGNORE:4075 /DEBUG /OPT:REF /OPT:ICF") set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG /IGNORE:4075 /DEBUG /OPT:REF /OPT:ICF") + message(STATUS "Configuring for statically-linked CRT") + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + else() #!WIN32 # Custom build flags. @@ -338,3 +349,45 @@ endif() # Product code add_subdirectory(src/lib/) + +# Test code +if(CXPLAT_BUILD_TEST) + include(FetchContent) + + enable_testing() + + # Build the googletest framework. + + # Enforce static builds for test artifacts + set(PREV_BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS} CACHE INTERNAL "") + set(BUILD_SHARED_LIBS OFF CACHE INTERNAL "") + set(BUILD_GMOCK OFF CACHE BOOL "Builds the googlemock subproject") + set(INSTALL_GTEST OFF CACHE BOOL "Enable installation of googletest. (Projects embedding googletest may want to turn this OFF.)") + FetchContent_Declare( + googletest + SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/submodules/googletest + ) + FetchContent_MakeAvailable(googletest) + set(BUILD_SHARED_LIBS ${PREV_BUILD_SHARED_LIBS} CACHE INTERNAL "") + + set_property(TARGET gtest PROPERTY CXX_STANDARD 17) + set_property(TARGET gtest PROPERTY FOLDER "${CXPLAT_FOLDER_PREFIX}tests") + + set_property(TARGET gtest_main PROPERTY CXX_STANDARD 17) + set_property(TARGET gtest_main PROPERTY FOLDER "${CXPLAT_FOLDER_PREFIX}tests") + set_property(TARGET gtest_main PROPERTY EXCLUDE_FROM_ALL ON) + set_property(TARGET gtest_main PROPERTY EXCLUDE_FROM_DEFAULT_BUILD ON) + + if (HAS_SPECTRE) + target_compile_options(gtest PRIVATE /Qspectre) + target_compile_options(gtest_main PRIVATE /Qspectre) + endif() + + if (HAS_GUARDCF) + target_compile_options(gtest PRIVATE /guard:cf) + target_compile_options(gtest_main PRIVATE /guard:cf) + endif() + + add_subdirectory(src/test/lib) + add_subdirectory(src/test/bin) +endif() diff --git a/cxplat.kernel.sln b/cxplat.kernel.sln index 3a6a728..359bd18 100644 --- a/cxplat.kernel.sln +++ b/cxplat.kernel.sln @@ -5,6 +5,13 @@ VisualStudioVersion = 16.0.29728.190 MinimumVisualStudioVersion = 10.0.40219.1 Project("{BC8DFCC2-43CB-481C-988F-C39903116328}") = "cxplat.kernel", "src\lib\cxplat.kernel.vcxproj", "{E680F075-FEE8-421B-A9F1-DAD0A1C537D3}" EndProject +Project("{BC8DFCC2-43CB-481C-988F-C39903116328}") = "testlib.kernel", "src\test\lib\testlib.kernel.vcxproj", "{6DBEB7D2-4BCF-46B2-95CB-88DEF2F049D7}" + ProjectSection(ProjectDependencies) = postProject + {E680F075-FEE8-421B-A9F1-DAD0A1C537D3} = {E680F075-FEE8-421B-A9F1-DAD0A1C537D3} + EndProjectSection +EndProject +Project("{BC8DFCC2-43CB-481C-988F-C39903116328}") = "cxplattest.kernel", "src\test\bin\winkernel\cxplattest.kernel.vcxproj", "{1E494654-9BFD-492F-BC31-36E2C73A782E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 @@ -25,6 +32,30 @@ Global {E680F075-FEE8-421B-A9F1-DAD0A1C537D3}.Release|x64.ActiveCfg = Release|x64 {E680F075-FEE8-421B-A9F1-DAD0A1C537D3}.Release|x64.Build.0 = Release|x64 {E680F075-FEE8-421B-A9F1-DAD0A1C537D3}.Release|x64.Deploy.0 = Release|x64 + {6DBEB7D2-4BCF-46B2-95CB-88DEF2F049D7}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {6DBEB7D2-4BCF-46B2-95CB-88DEF2F049D7}.Debug|ARM64.Build.0 = Debug|ARM64 + {6DBEB7D2-4BCF-46B2-95CB-88DEF2F049D7}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {6DBEB7D2-4BCF-46B2-95CB-88DEF2F049D7}.Debug|x64.ActiveCfg = Debug|x64 + {6DBEB7D2-4BCF-46B2-95CB-88DEF2F049D7}.Debug|x64.Build.0 = Debug|x64 + {6DBEB7D2-4BCF-46B2-95CB-88DEF2F049D7}.Debug|x64.Deploy.0 = Debug|x64 + {6DBEB7D2-4BCF-46B2-95CB-88DEF2F049D7}.Release|ARM64.ActiveCfg = Release|ARM64 + {6DBEB7D2-4BCF-46B2-95CB-88DEF2F049D7}.Release|ARM64.Build.0 = Release|ARM64 + {6DBEB7D2-4BCF-46B2-95CB-88DEF2F049D7}.Release|ARM64.Deploy.0 = Release|ARM64 + {6DBEB7D2-4BCF-46B2-95CB-88DEF2F049D7}.Release|x64.ActiveCfg = Release|x64 + {6DBEB7D2-4BCF-46B2-95CB-88DEF2F049D7}.Release|x64.Build.0 = Release|x64 + {6DBEB7D2-4BCF-46B2-95CB-88DEF2F049D7}.Release|x64.Deploy.0 = Release|x64 + {1E494654-9BFD-492F-BC31-36E2C73A782E}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {1E494654-9BFD-492F-BC31-36E2C73A782E}.Debug|ARM64.Build.0 = Debug|ARM64 + {1E494654-9BFD-492F-BC31-36E2C73A782E}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {1E494654-9BFD-492F-BC31-36E2C73A782E}.Debug|x64.ActiveCfg = Debug|x64 + {1E494654-9BFD-492F-BC31-36E2C73A782E}.Debug|x64.Build.0 = Debug|x64 + {1E494654-9BFD-492F-BC31-36E2C73A782E}.Debug|x64.Deploy.0 = Debug|x64 + {1E494654-9BFD-492F-BC31-36E2C73A782E}.Release|ARM64.ActiveCfg = Release|ARM64 + {1E494654-9BFD-492F-BC31-36E2C73A782E}.Release|ARM64.Build.0 = Release|ARM64 + {1E494654-9BFD-492F-BC31-36E2C73A782E}.Release|ARM64.Deploy.0 = Release|ARM64 + {1E494654-9BFD-492F-BC31-36E2C73A782E}.Release|x64.ActiveCfg = Release|x64 + {1E494654-9BFD-492F-BC31-36E2C73A782E}.Release|x64.Build.0 = Release|x64 + {1E494654-9BFD-492F-BC31-36E2C73A782E}.Release|x64.Deploy.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/inc/cxplat_posix.h b/inc/cxplat_posix.h index 4f63c2f..7a3e858 100644 --- a/inc/cxplat_posix.h +++ b/inc/cxplat_posix.h @@ -12,6 +12,8 @@ #ifndef CXPLAT_POSIX_H #define CXPLAT_POSIX_H +#include "cxplat_sal_stub.h" + #if defined(__cplusplus) extern "C" { #endif diff --git a/inc/cxplat_sal_stub.h b/inc/cxplat_sal_stub.h new file mode 100644 index 0000000..494a761 --- /dev/null +++ b/inc/cxplat_sal_stub.h @@ -0,0 +1,280 @@ +/*++ + + Copyright (c) Microsoft Corporation. + Licensed under the MIT License. + + --*/ + +#ifndef CXPLAT_SAL_STUB_H +#define CXPLAT_SAL_STUB_H + +// +// Necessary when SAL isn't supported to tell compiler it's not necessary. +// +#define INIT_NO_SAL(X) = X + +#ifndef _Must_inspect_result_ +#define _Must_inspect_result_ +#endif + +#ifndef _Pre_defensive_ +#define _Pre_defensive_ +#endif + +#ifndef _Ret_notnull_ +#define _Ret_notnull_ +#endif + +#ifndef _IRQL_requires_max_ +#define _IRQL_requires_max_(...) +#endif + +#ifndef _Function_class_ +#define _Function_class_(...) +#endif + +#ifndef _In_ +#define _In_ +#endif + +#ifndef _In_opt_ +#define _In_opt_ +#endif + +#ifndef _In_opt_z_ +#define _In_opt_z_ +#endif + +#ifndef _Inout_ +#define _Inout_ +#endif + +#ifndef _Inout_opt_ +#define _Inout_opt_ +#endif + +#ifndef _In_z_ +#define _In_z_ +#endif + +#ifndef _Out_ +#define _Out_ +#endif + +#ifndef _Out_range_ +#define _Out_range_(...) +#endif + +#ifndef _Field_size_bytes_ +#define _Field_size_bytes_(...) +#endif + +#ifndef _Field_size_bytes_opt_ +#define _Field_size_bytes_opt_(...) +#endif + +#ifndef _In_reads_ +#define _In_reads_(...) +#endif + +#ifndef _In_reads_bytes_ +#define _In_reads_bytes_(...) +#endif + +#ifndef _In_reads_z_ +#define _In_reads_z_(...) +#endif + +#ifndef _In_reads_opt_z_ +#define _In_reads_opt_z_(...) +#endif + +#ifndef _In_reads_or_z_opt_ +#define _In_reads_or_z_opt_(...) +#endif + +#ifndef _Out_writes_bytes_opt_ +#define _Out_writes_bytes_opt_(...) +#endif + +#ifndef _Null_terminated_ +#define _Null_terminated_ +#endif + +#ifndef _NullNull_terminated_ +#define _NullNull_terminated_ +#endif + +#ifndef _Out_writes_bytes_ +#define _Out_writes_bytes_(...) +#endif + +#ifndef _Field_size_ +#define _Field_size_(...) +#endif + +#ifndef _Success_ +#define _Success_(...) +#endif + +#ifndef _Field_range_ +#define _Field_range_(...) +#endif + +#ifndef _In_reads_bytes_opt_ +#define _In_reads_bytes_opt_(...) +#endif + +#ifndef _Out_writes_bytes_to_opt_ +#define _Out_writes_bytes_to_opt_(...) +#endif + +#ifndef _Deref_pre_opt_count_ +#define _Deref_pre_opt_count_(...) +#endif + +#ifndef _Deref_post_opt_count_ +#define _Deref_post_opt_count_(...) +#endif + +#ifndef _Outptr_result_buffer_ +#define _Outptr_result_buffer_(...) +#endif + +#ifndef _Outptr_result_buffer_maybenull_ +#define _Outptr_result_buffer_maybenull_(...) +#endif + +#ifndef _Inout_updates_bytes_ +#define _Inout_updates_bytes_(...) +#endif + +#ifndef _Inout_updates_bytes_opt_ +#define _Inout_updates_bytes_opt_(...) +#endif + +#ifndef _Inout_updates_ +#define _Inout_updates_(...) +#endif + +#ifndef _Out_opt_ +#define _Out_opt_ +#endif + +#ifndef _Outptr_ +#define _Outptr_ +#endif + +#ifndef _Ret_maybenull_ +#define _Ret_maybenull_ +#endif + +#ifndef _Must_inspect_result_ +#define _Must_inspect_result_ +#endif + +#ifndef _Post_invalid_ +#define _Post_invalid_ +#endif + +#ifndef _Post_writable_byte_size_ +#define _Post_writable_byte_size_(...) +#endif + +#ifndef __drv_allocatesMem +#define __drv_allocatesMem(...) +#endif + +#ifndef __drv_freesMem +#define __drv_freesMem(...) +#endif + +#ifndef __drv_aliasesMem +#define __drv_aliasesMem +#endif + +#ifndef _Frees_ptr_ +#define _Frees_ptr_ +#endif + +#ifndef _Frees_ptr_opt_ +#define _Frees_ptr_opt_ +#endif + +#ifndef _In_range_ +#define _In_range_(...) +#endif + +#ifndef _When_ +#define _When_(...) +#endif + +#ifndef _Post_equal_to_ +#define _Post_equal_to_(...) +#endif + +#ifndef _Deref_in_range_ +#define _Deref_in_range_(...) +#endif + +#ifndef _Deref_out_range_ +#define _Deref_out_range_(...) +#endif + +#ifndef _Out_writes_all_ +#define _Out_writes_all_(...) +#endif + +#ifndef _Out_writes_to_ +#define _Out_writes_to_(...) +#endif + +#ifndef _Out_writes_ +#define _Out_writes_(...) +#endif + +#ifndef _Field_z_ +#define _Field_z_ +#endif + +#ifndef __analysis_assume +#define __analysis_assume(expr) +#endif + +#ifndef _Out_writes_bytes_all_ +#define _Out_writes_bytes_all_(...) +#endif + +#ifndef _Analysis_assume_ +#define _Analysis_assume_(expr) +#endif + +#ifndef _Ret_range_ +#define _Ret_range_(...) +#endif + +#ifndef _Ret_writes_bytes_ +#define _Ret_writes_bytes_(...) +#endif + +#ifndef _Printf_format_string_ +#define _Printf_format_string_ +#endif + +#ifndef _Interlocked_operand_ +#define _Interlocked_operand_ +#endif + +#ifndef _In_reads_opt_ +#define _In_reads_opt_(...) +#endif + +#ifndef _At_ +#define _At_(...) +#endif + +#ifndef _Check_return_ +#define _Check_return_ +#endif + +#endif // CXPLAT_SAL_STUB_H diff --git a/scripts/build.ps1 b/scripts/build.ps1 index 8a5d79d..4ddf7bc 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -12,6 +12,9 @@ This script provides helpers for building cxplat. .PARAMETER Platform Specify which platform to build for. +.PARAMETER DisableTest + Don't build the test directory. + .PARAMETER Clean Deletes all previous build and configuration. @@ -69,6 +72,9 @@ param ( [ValidateSet("gamecore_console", "uwp", "windows", "linux", "macos", "android", "ios", "winkernel")] # For future expansion [string]$Platform = "", + [Parameter(Mandatory = $false)] + [switch]$DisableTest = $false, + [Parameter(Mandatory = $false)] [switch]$Clean = $false, @@ -306,6 +312,11 @@ function CMake-Generate { $Arguments += " -DCXPLAT_OUTPUT_DIR=""$ArtifactsDir""" + if ($Platform -ne "uwp" -and $Platform -ne "gamecore_console") { + if (!$DisableTest) { + $Arguments += " -DCXPLAT_BUILD_TEST=on" + } + } if (!$IsWindows) { $ConfigToBuild = $Config; if ($Config -eq "Release") { diff --git a/scripts/prepare-machine.ps1 b/scripts/prepare-machine.ps1 index ef8243e..afb792f 100644 --- a/scripts/prepare-machine.ps1 +++ b/scripts/prepare-machine.ps1 @@ -13,11 +13,29 @@ on the provided configuration. #> param ( + [Parameter(Mandatory = $false)] + [switch]$Force, + [Parameter(Mandatory = $false)] [switch]$ForBuild, [Parameter(Mandatory = $false)] - [switch]$InstallArm64Toolchain + [switch]$ForTest, + + [Parameter(Mandatory = $false)] + [switch]$ForKernel, + + [Parameter(Mandatory = $false)] + [switch]$InstallSigningCertificates, + + [Parameter(Mandatory = $false)] + [switch]$InstallArm64Toolchain, + + [Parameter(Mandatory = $false)] + [switch]$DisableTest, + + [Parameter(Mandatory = $false)] + [switch]$InstallCoreNetCiDeps ) # Admin is required because a lot of things are installed to the local machine @@ -35,11 +53,32 @@ if ($PSVersionTable.PSVersion.Major -lt 7) { $PrepConfig = & (Join-Path $PSScriptRoot get-buildconfig.ps1) -if (!$ForBuild) { +if (!$ForBuild -and !$ForTest) { # When no args are passed, assume we want to build and test everything # locally (i.e. a dev environment). - Write-Host "No arguments passed, defaulting -ForBuild" + Write-Host "No arguments passed, defaulting -ForBuild and -ForTest" $ForBuild = $true + $ForTest = $true +} + +if ($ForBuild) { + # When configured for building, make sure we have all possible dependencies + # enabled for any possible build. + $InstallCoreNetCiDeps = $true; # For kernel signing certs +} + +if ($ForTest) { + # Since installing signing certs also checks whether test signing is enabled, which most + # likely will fail on a devbox, do it only when we need to test kernel drivers so that + # local testing setup won't be blocked by test signing not enabled. + if ($ForKernel) { + $InstallSigningCertificates = $true; + } +} + +if ($InstallSigningCertificates) { + # Signing certs need the CoreNet-CI dependencies. + $InstallCoreNetCiDeps = $true; } # Root directory of the project. @@ -47,6 +86,57 @@ $RootDir = Split-Path $PSScriptRoot -Parent $ArtifactsPath = Join-Path $RootDir "artifacts" if (!(Test-Path $ArtifactsPath)) { mkdir $ArtifactsPath | Out-Null } +# Directory for the corenet CI install. +$CoreNetCiPath = Join-Path $ArtifactsPath "corenet-ci-main" +$SetupPath = Join-Path $CoreNetCiPath "vm-setup" + +# Downloads and caches the latest version of the corenet-ci-main repo. +function Download-CoreNet-Deps { + if (!$IsWindows) { return } # Windows only + # Download and extract https://github.com/microsoft/corenet-ci. + if ($Force) { rm -Force -Recurse $CoreNetCiPath -ErrorAction Ignore } + if (!(Test-Path $CoreNetCiPath)) { + Write-Host "Downloading CoreNet-CI" + $ZipPath = Join-Path $ArtifactsPath "corenet-ci.zip" + Invoke-WebRequest -Uri "https://github.com/microsoft/corenet-ci/archive/refs/heads/main.zip" -OutFile $ZipPath + Expand-Archive -Path $ZipPath -DestinationPath $ArtifactsPath -Force + Remove-Item -Path $ZipPath + } +} + +# Installs the certs downloaded via Download-CoreNet-Deps and used for signing +# our test drivers. +function Install-SigningCertificates { + if (!$IsWindows) { return } # Windows only + + # Check to see if test signing is enabled. + $HasTestSigning = $false + try { $HasTestSigning = ("$(bcdedit)" | Select-String -Pattern "testsigning\s+Yes").Matches.Success } catch { } + if (!$HasTestSigning) { Write-Error "Test Signing Not Enabled!" } + + Write-Host "Installing driver signing certificates" + try { + CertUtil.exe -addstore Root "$SetupPath\CoreNetSignRoot.cer" 2>&1 | Out-Null + CertUtil.exe -addstore TrustedPublisher "$SetupPath\CoreNetSignRoot.cer" 2>&1 | Out-Null + CertUtil.exe -addstore Root "$SetupPath\testroot-sha2.cer" 2>&1 | Out-Null # For duonic + } catch { + Write-Host "WARNING: Exception encountered while installing signing certs. Drivers may not start!" + } +} + +if ($ForBuild) { + + if (!$DisableTest) { + Write-Host "Initializing googletest submodule" + git submodule init submodules/googletest + } + + git submodule update --jobs=8 +} + +if ($InstallCoreNetCiDeps) { Download-CoreNet-Deps } +if ($InstallSigningCertificates) { Install-SigningCertificates } + if ($IsLinux) { if ($ForBuild) { sudo apt-add-repository ppa:lttng/stable-2.13 -y @@ -68,4 +158,32 @@ if ($IsLinux) { sudo gem install public_suffix -v 4.0.7 sudo gem install fpm } + + if ($ForTest) { + sudo apt-add-repository ppa:lttng/stable-2.13 -y + sudo apt-get update -y + sudo apt-get install -y lttng-tools + sudo apt-get install -y liblttng-ust-dev + sudo apt-get install -y gdb + + # Enable core dumps for the system. + Write-Host "Setting core dump size limit" + sudo sh -c "echo 'root soft core unlimited' >> /etc/security/limits.conf" + sudo sh -c "echo 'root hard core unlimited' >> /etc/security/limits.conf" + sudo sh -c "echo '* soft core unlimited' >> /etc/security/limits.conf" + sudo sh -c "echo '* hard core unlimited' >> /etc/security/limits.conf" + #sudo cat /etc/security/limits.conf + + # Set the core dump pattern. + Write-Host "Setting core dump pattern" + sudo sh -c "echo -n '%e.%p.%t.core' > /proc/sys/kernel/core_pattern" + #sudo cat /proc/sys/kernel/core_pattern + } +} + +if ($IsMacOS) { + if ($ForTest) { + Write-Host "Setting core dump pattern" + sudo sysctl -w kern.corefile=%N.%P.core + } } diff --git a/scripts/run-gtest.ps1 b/scripts/run-gtest.ps1 new file mode 100644 index 0000000..628f87b --- /dev/null +++ b/scripts/run-gtest.ps1 @@ -0,0 +1,808 @@ +<# + +.SYNOPSIS +This script runs a google test executable and collects logs or dumps +as necessary. + +.PARAMETER Path + The path to the test executable. + +.PARAMETER Kernel + Runs for Windows kernel mode, given the path for binaries. + +.PARAMETER Filter + A filter to include test cases from the list to execute. Multiple filters + are separated by :. Negative filters are prefixed with -. + +.PARAMETER ListTestCases + Lists all the test cases. + +.PARAMETER IsolationMode + Controls the isolation mode when running each test case. + +.PARAMETER KeepOutputOnSuccess + Don't discard console output or logs on success. + +.PARAMETER GenerateXmlResults + Generates an xml Test report for the run. + +.PARAMETER Debugger + Attaches the debugger to the process. + +.PARAMETER InitialBreak + Debugger starts broken into the process to allow setting breakpoints, etc. + +.PARAMETER BreakOnFailure + Triggers a break point on a test failure. + +.PARAMETER LogProfile + The name of the profile to use for log collection. + +.PARAMETER CompressOutput + Compresses the output files generated for failed test cases. + +.PARAMETER NoProgress + Disables the progress bar. + +.Parameter EnableAppVerifier + Enables all basic Application Verifier checks on the test binary. + +.Parameter EnableSystemVerifier + Enables TCPIP verifier in user mode tests. + +.Parameter AZP + Running this script as a part of Azure Pipelines. + +.Parameter GHA + Running this script as a part of GitHub Actions. + +.Parameter ErrorsAsWarnings + Treats all errors as warnings. + +#> + +param ( + [Parameter(Mandatory = $true)] + [string]$Path, + + [Parameter(Mandatory = $false)] + [string]$Kernel = "", + + [Parameter(Mandatory = $false)] + [string]$Filter = "", + + [Parameter(Mandatory = $false)] + [switch]$ListTestCases = $false, + + [Parameter(Mandatory = $false)] + [ValidateSet("Batch", "Isolated")] + [string]$IsolationMode = "Isolated", + + [Parameter(Mandatory = $false)] + [switch]$KeepOutputOnSuccess = $false, + + [Parameter(Mandatory = $false)] + [switch]$GenerateXmlResults = $false, + + [Parameter(Mandatory = $false)] + [switch]$Debugger = $false, + + [Parameter(Mandatory = $false)] + [switch]$InitialBreak = $false, + + [Parameter(Mandatory = $false)] + [switch]$BreakOnFailure = $false, + + [Parameter(Mandatory = $false)] + [ValidateSet("None")] + [string]$LogProfile = "None", + + [Parameter(Mandatory = $false)] + [switch]$CompressOutput = $false, + + [Parameter(Mandatory = $false)] + [switch]$NoProgress = $false, + + [Parameter(Mandatory = $false)] + [switch]$EnableAppVerifier = $false, + + [Parameter(Mandatory = $false)] + [switch]$EnableSystemVerifier = $false, + + [Parameter(Mandatory = $false)] + [switch]$AZP = $false, + + [Parameter(Mandatory = $false)] + [switch]$GHA = $false, + + [Parameter(Mandatory = $false)] + [switch]$ErrorsAsWarnings = $false, + + [Parameter(Mandatory = $false)] + [string]$ExtraArtifactDir = "", + + [Parameter(Mandatory = $false)] + [string]$OsRunner = "" +) + +Set-StrictMode -Version 'Latest' +$PSDefaultParameterValues['*:ErrorAction'] = 'Stop' + +function Test-Administrator +{ + $user = [Security.Principal.WindowsIdentity]::GetCurrent(); + (New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) +} + +function Log($msg) { + Write-Host "[$(Get-Date)] $msg" +} + +function LogWrn($msg) { + if ($AZP -and !$ErrorsAsWarnings) { + Write-Host "##vso[task.LogIssue type=warning;][$(Get-Date)] $msg" + } elseif ($GHA -and !$ErrorsAsWarnings) { + Write-Host "::warning::[$(Get-Date)] $msg" + } else { + Write-Warning "[$(Get-Date)] $msg" + } +} + +function LogErr($msg) { + if ($AZP -and !$ErrorsAsWarnings) { + Write-Host "##vso[task.LogIssue type=error;][$(Get-Date)] $msg" + } elseif ($GHA -and !$ErrorsAsWarnings) { + Write-Host "::error::[$(Get-Date)] $msg" + } else { + Write-Warning "[$(Get-Date)] $msg" + } +} + +function LogFatal($msg) { + if ($AZP -and !$ErrorsAsWarnings) { + Write-Error "##vso[task.LogIssue type=error;][$(Get-Date)] $msg" + } elseif ($GHA -and !$ErrorsAsWarnings) { + Write-Error "::error::[$(Get-Date)] $msg" + } else { + Write-Error "[$(Get-Date)] $msg" + } +} + +# Make sure the test executable is present. +if (!(Test-Path $Path)) { + Write-Error "$($Path) does not exist!" +} + +# Validate the kernel switch. +if ($Kernel -ne "" -and !$IsWindows) { + Write-Error "-Kernel switch only supported on Windows" +} + +# Root directory of the project. +$RootDir = Split-Path $PSScriptRoot -Parent + +# Script for controlling loggings. +$LogScript = Join-Path $RootDir "scripts" "log.ps1" + +# Executable name. +$TestExeName = Split-Path $Path -Leaf + +$ExeLogFolder = $TestExeName +if (![string]::IsNullOrWhiteSpace($ExtraArtifactDir)) { + $ExeLogFolder += "_$ExtraArtifactDir" +} + +# Folder for log files. +$LogDir = Join-Path $RootDir "artifacts" "logs" $ExeLogFolder (Get-Date -UFormat "%m.%d.%Y.%T").Replace(':','.') +New-Item -Path $LogDir -ItemType Directory -Force | Out-Null + +# The file path of the final XML results. +$FinalResultsPath = "$($LogDir)-results.xml" + +# Base XML results data. +$XmlResults = [xml]@" + + + +"@ +$XmlResults.testsuites.timestamp = Get-Date -UFormat "%Y-%m-%dT%T" + +# XML for creating new (failure) result data. +$FailXmlText = @" + + + + + + + + +"@ + +# Global state for tracking if any crashes occurred. +$global:CrashedProcessCount = 0 + +# Path to the WER registry key used for collecting dumps. +$WerDumpRegPath = "HKLM:\Software\Microsoft\Windows\Windows Error Reporting\LocalDumps\$TestExeName" + +# Helper script to build up a combined XML file for all test cases. This +# function just appends the given test case's xml output to the existing xml +# file. If not present (because of a crash) it generates one instead and appends +# it. +function Add-XmlResults($TestCase) { + $TestHasResults = Test-Path $TestCase.ResultsPath + $TestSuiteName = $TestCase.Name.Split(".")[0] + $TestCaseName = $TestCase.Name.Split(".")[1] + + $NewXmlResults = $null + if ($TestHasResults) { + # Get the results from the test output. + $NewXmlResults = [xml](Get-Content $TestCase.ResultsPath) + Remove-Item $TestCase.ResultsPath -Force | Out-Null + } else { + # Generate our own results xml. + $NewXmlText = $FailXmlText.Replace("TestSuiteName", $TestSuiteName) + $NewXmlText = $NewXmlText.Replace("TestCaseName", $TestCaseName) + $NewXmlText = $NewXmlText.Replace("date", $TestCase.Timestamp) + $NewXmlResults = [xml]($NewXmlText) + } + + $IsFailure = $NewXmlResults.testsuites.failures -eq 1 + $Time = $NewXmlResults.testsuites.testsuite.testcase.time -as [Decimal] + + $Node = $null + if ($XmlResults.testsuites.tests -ne 0) { + # Look for a matching test suite that might already exist. + $Node = $XmlResults.testsuites.testsuite | Where-Object { $_.Name -eq $TestSuiteName } + } + if ($null -ne $Node) { + # Already has a matching test suite. Add the test case to it. + $Node.tests = ($Node.tests -as [Int]) + 1 + if ($IsFailure) { + $Node.failures = ($Node.failures -as [Int]) + 1 + } + $Node.time = ($Node.time -as [Decimal]) + $Time + $NewNode = $XmlResults.ImportNode($NewXmlResults.testsuites.testsuite.testcase, $true) + $Node.AppendChild($NewNode) | Out-Null + } else { + # First instance of this test suite. Add the test suite. + $NewNode = $XmlResults.ImportNode($NewXmlResults.testsuites.testsuite, $true) + $XmlResults.testsuites.AppendChild($NewNode) | Out-Null + } + + # Update the top level test and failure counts. + $XmlResults.testsuites.tests = ($XmlResults.testsuites.tests -as [Int]) + 1 + if ($IsFailure) { + $XmlResults.testsuites.failures = ($XmlResults.testsuites.failures -as [Int]) + 1 + } + $XmlResults.testsuites.time = ($XmlResults.testsuites.time -as [Decimal]) + $Time +} + +# Asynchronously starts the test executable with the given arguments. +function Start-TestExecutable([String]$Arguments, [String]$OutputDir) { + $pinfo = New-Object System.Diagnostics.ProcessStartInfo + if ($IsWindows) { + if ($Debugger) { + if (Get-Command "windbgx.exe" -ErrorAction SilentlyContinue) { + $pinfo.FileName = "windbgx.exe" + } else { + $pinfo.FileName = "windbg.exe" + } + if ($InitialBreak) { + $pinfo.Arguments = "-G $($Path) $($Arguments)" + } else { + $pinfo.Arguments = "-g -G $($Path) $($Arguments)" + } + } else { + $pinfo.FileName = $Path + $pinfo.Arguments = $Arguments + if (Test-Administrator) { + # Enable WER dump collection. + New-ItemProperty -Path $WerDumpRegPath -Name DumpType -PropertyType DWord -Value 2 -Force | Out-Null + New-ItemProperty -Path $WerDumpRegPath -Name DumpFolder -PropertyType ExpandString -Value $OutputDir -Force | Out-Null + } + } + } else { + if ($Debugger) { + $pinfo.FileName = "gdb" + if ($InitialBreak) { + $pinfo.Arguments = "--args $($Path) $($Arguments)" + } else { + $pinfo.Arguments = "-ex=r --args $($Path) $($Arguments)" + } + } else { + $pinfo.FileName = "bash" + $pinfo.Arguments = "-c `"ulimit -c unlimited && LSAN_OPTIONS=report_objects=1 ASAN_OPTIONS=disable_coredump=0:abort_on_error=1 UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1 $($Path) $($Arguments) && echo Done`"" + $pinfo.WorkingDirectory = $OutputDir + } + } + if (!$Debugger) { + $pinfo.RedirectStandardOutput = $true + $pinfo.RedirectStandardError = $true + } + $pinfo.UseShellExecute = $false + $p = New-Object System.Diagnostics.Process + $p.StartInfo = $pinfo + $p.Start() | Out-Null + $p +} + +# Asynchronously starts a single test case running. +function Start-TestCase([String]$Name) { + + $InstanceName = $Name.Replace("/", "_") + $LocalLogDir = Join-Path $LogDir $InstanceName + mkdir $LocalLogDir | Out-Null + + if ($LogProfile -ne "None") { + # Start the logs + & $LogScript -Start -Profile $LogProfile | Out-Null + } + + # Build up the argument list. + $ResultsPath = Join-Path $LocalLogDir "results.xml" + $Arguments = "--gtest_catch_exceptions=0 --gtest_filter=$($Name) --gtest_output=xml:$($ResultsPath) --timeout 60000" + if ($BreakOnFailure) { + $Arguments += " --gtest_break_on_failure" + } + if ($Kernel -ne "") { + $Arguments += " --kernel" + } + if ("" -ne $OsRunner) { + $Arguments += " --osRunner=$OsRunner" + } + + # Start the test process and return some information about the test case. + [pscustomobject]@{ + Name = $Name + InstanceName = $InstanceName + LogDir = $LocalLogDir + StartTime = Get-Date + Timestamp = (Get-Date -UFormat "%Y-%m-%dT%T") + ResultsPath = $ResultsPath + Process = (Start-TestExecutable $Arguments $LocalLogDir) + } +} + +# Asynchronously start all the test cases running. +function Start-AllTestCases { + + $Name = "all" + $InstanceName = $Name + + if ($LogProfile -ne "None") { + # Start the logs + & $LogScript -Start -Profile $LogProfile | Out-Null + } + + # Build up the argument list. + $Arguments = "--gtest_catch_exceptions=0 --gtest_output=xml:$($FinalResultsPath)" + if ($Filter -ne "") { + $Arguments += " --gtest_filter=$($Filter)" + } + if ($BreakOnFailure) { + $Arguments += " --gtest_break_on_failure" + } + if ($Kernel -ne "") { + $Arguments += " --kernelPriv" + } + if ("" -ne $OsRunner) { + $Arguments += " --osRunner=$OsRunner" + } + + # Start the test process and return some information about the test case. + [pscustomobject]@{ + Name = $Name + InstanceName = $InstanceName + LogDir = $LogDir + StartTime = Get-Date + Timestamp = (Get-Date -UFormat "%Y-%m-%dT%T") + ResultsPath = $FinalResultsPath + Process = (Start-TestExecutable $Arguments $LogDir) + } +} + +# Uses CDB.exe to print the crashing callstack in the dump file. +function PrintDumpCallStack($DumpFile) { + $env:_NT_SYMBOL_PATH = Split-Path $Path + try { + if ($env:BUILD_BUILDNUMBER -ne $null) { + $env:PATH += ";c:\Program Files (x86)\Windows Kits\10\Debuggers\x64" + } + $Output = cdb.exe -z $File -c "kn;q" | Join-String -Separator "`n" + $Output = ($Output | Select-String -Pattern " # Child-SP(?s).*quit:").Matches[0].Groups[0].Value + Write-Host "==================================================================================" + Write-Host " $(Split-Path $DumpFile -Leaf)" + Write-Host "==================================================================================" + $Output -replace "quit:", "==================================================================================" + $Output | Out-File "$DumpFile.txt" + } catch { + # Silently fail + } +} + +function PrintLldbCoreCallStack($CoreFile) { + try { + $Output = lldb $Path -c $CoreFile -b -o "`"bt all`"" + Write-Host "==================================================================================" + Write-Host " $(Split-Path $CoreFile -Leaf)" + Write-Host "==================================================================================" + # Find line containing Current thread + $Found = $false + $LastThreadStart = 0 + for ($i = 0; $i -lt $Output.Length; $i++) { + if ($Output[$i] -like "*stop reason =*") { + if ($Found) { + break + } + $LastThreadStart = $i + } + if ($Output[$i] -like "*cxplat_bugcheck*") { + $Found = $true + for ($j = $LastThreadStart; $j -lt $i; $j++) { + $Output[$j] + } + } + if ($Found) { + $Output[$i] + } + } + if (!$Found) { + $Output | Join-String -Separator "`n" + } + $Output | Join-String -Separator "`n" | Out-File "$CoreFile.txt" + } catch { + # Silently Fail + } +} + +function PrintGdbCoreCallStack($CoreFile) { + try { + $Output = gdb $Path $CoreFile -batch -ex "`"bt`"" -ex "`"quit`"" + Write-Host "==================================================================================" + Write-Host " $(Split-Path $CoreFile -Leaf)" + Write-Host "==================================================================================" + # Find line containing Current thread + $Found = $false + for ($i = 0; $i -lt $Output.Length; $i++) { + if ($Output[$i] -like "*Current thread*") { + $Found = $true + } + if ($Found) { + $Output[$i] + } + } + if (!$Found) { + $Output | Join-String -Separator "`n" + } + $Output | Join-String -Separator "`n" | Out-File "$CoreFile.txt" + } catch { + # Silently Fail + } +} + +# Waits for the executable to finish and processes the results. +function Wait-TestCase($TestCase) { + $ProcessCrashed = $false + $AnyTestFailed = $false + $StdOut = $null + $StdOutTxt = $null + $StdError = $null + $StdErrorTxt = $null + $IsReadingStreams = $false + + try { + if (!$Debugger) { + $IsReadingStreams = $true + $StdOut = $TestCase.Process.StandardOutput.ReadToEndAsync() + $StdError = $TestCase.Process.StandardError.ReadToEndAsync() + } + $TestCase.Process.WaitForExit() + if ($TestCase.Process.ExitCode -ne 0) { + Log "Process had nonzero exit code: $($TestCase.Process.ExitCode)" + $ProcessCrashed = $true + } + if ($IsReadingStreams) { + [System.Threading.Tasks.Task]::WaitAll(@($StdOut, $StdError)) + $StdOutTxt = $StdOut.Result + $StdErrorTxt = $StdError.Result + + if (!$IsWindows -and !$ProcessCrashed) { + $ProcessCrashed = $StdErrorTxt.Contains("Aborted") + } + $AnyTestFailed = $StdOutTxt.Contains("[ FAILED ]") + if (!(Test-Path $TestCase.ResultsPath) -and !$ProcessCrashed) { + LogWrn "No test results generated! Treating as crash!" + $ProcessCrashed = $true + } + } + $DumpFiles = (Get-ChildItem $TestCase.LogDir) | Where-Object { $_.Extension -eq ".dmp" } + if ($DumpFiles) { + LogWrn "Dump file(s) generated" + foreach ($File in $DumpFiles) { + PrintDumpCallStack($File) + } + $ProcessCrashed = $true + } + $CoreFiles = (Get-ChildItem $TestCase.LogDir) | Where-Object { $_.Extension -eq ".core" } + if ($CoreFiles) { + LogWrn "Core file(s) generated" + foreach ($File in $CoreFiles) { + if ($IsMacOS) { + PrintLldbCoreCallStack $File + } else { + PrintGdbCoreCallStack $File + } + } + $ProcessCrashed = $true + } + } catch { + LogWrn "Treating exception as crash!" + $ProcessCrashed = $true + throw + } finally { + # Add the current test case results. + if ($IsolationMode -ne "Batch") { + try { Add-XmlResults $TestCase } catch { } + } + + if ($ProcessCrashed) { + $global:CrashedProcessCount++ + } + + if ($IsolationMode -eq "Batch") { + if ($StdOutTxt) { Write-Host $StdOutTxt } + if ($StdErrorTxt) { Write-Host $StdErrorTxt } + } else { + $Delta = (Get-Date) - $TestCase.StartTime + if ($AnyTestFailed -or $ProcessCrashed) { + LogErr "$($TestCase.Name) failed (in $($Delta.TotalSeconds) sec):" + if ($StdOutTxt) { Write-Host $StdOutTxt } + if ($StdErrorTxt) { Write-Host $StdErrorTxt } + } else { + Log "$($TestCase.Name) succeeded (in $($Delta.TotalSeconds) sec)" + } + } + + if ($KeepOutputOnSuccess -or $ProcessCrashed -or $AnyTestFailed) { + + if ($LogProfile -ne "None") { + & $LogScript -Stop -OutputPath (Join-Path $TestCase.LogDir "cxplat") + } + + if ($StdOutTxt) { + $StdOutTxt > (Join-Path $TestCase.LogDir "stdout.txt") + } + + if ($StdErrorTxt) { + $StdErrorTxt > (Join-Path $TestCase.LogDir "stderr.txt") + } + + if ($CompressOutput) { + # Zip the output. + CompressOutput-Archive -Path "$($TestCase.LogDir)\*" -DestinationPath "$($TestCase.LogDir).zip" | Out-Null + Remove-Item $TestCase.LogDir -Recurse -Force | Out-Null + } + + } else { + if ($LogProfile -ne "None") { + & $LogScript -Cancel | Out-Null + } + Remove-Item $TestCase.LogDir -Recurse -Force | Out-Null + } + } +} + +# Runs the test executable to query all available test cases, parses the console +# output and returns a list of test case names. +function GetTestCases { + $Arguments = " --gtest_list_tests" + if ($Filter -ne "") { + $Arguments = " --gtest_filter=$Filter --gtest_list_tests" + } + $stdout = Invoke-Expression ($Path + $Arguments) + + $Tests = New-Object System.Collections.ArrayList + if ($null -ne $stdout) { + $Lines = ($stdout.Split([Environment]::NewLine)) | Where-Object { $_.Length -ne 0 } + $CurTestGroup = $null + for ($i = 0; $i -lt $Lines.Length; $i++) { + if (!($Lines[$i].StartsWith(" "))) { + $CurTestGroup = $Lines[$i] + } else { + $Tests.Add($CurTestGroup + $Lines[$i].Split("#")[0].Trim()) | Out-Null + } + } + } + $Tests.ToArray() +} + +function Get-WindowsKitTool { + param ( + [string]$Arch = "x86", + [Parameter(Mandatory = $true)] + [string]$Tool + ) + + $KitBinRoot = "C:\Program Files (x86)\Windows Kits\10\bin" + if (!(Test-Path $KitBinRoot)) { + Write-Error "Windows Kit Binary Folder not Found" + return "" + } + + $FoundToolPath = $null + $FoundToolVersion = "0" + + $Subfolders = Get-ChildItem -Path $KitBinRoot -Directory + foreach ($Subfolder in $Subfolders) { + $ToolPath = Join-Path $Subfolder "$Arch\$Tool" + if (Test-Path $ToolPath) { + $KitVersion = $Subfolder.Name + + if ($KitVersion -gt $FoundToolVersion) { + $FoundToolVersion = $KitVersion + $FoundToolPath = $ToolPath + } + } + } + + if ($null -ne $FoundToolPath) { + return $FoundToolPath + } + Write-Error "Failed to find tool" + return $null +} + +############################################################## +# Main Execution # +############################################################## + +# Query all the test cases. +$TestCases = GetTestCases +if ($null -eq $TestCases) { + Log "$Path (Skipped)" + exit +} + +$TestCount = ($TestCases -as [String[]]).Length + +Log "$Path ($TestCount test case(s))" + +if ($ListTestCases) { + # List the tst cases. + $TestCases + exit +} + +# Cancel any outstanding logs that might be leftover. +# & $LogScript -Cancel | Out-Null + +# Initialize WER dump registry key if necessary. +if ($IsWindows -and !(Test-Path $WerDumpRegPath) -and (Test-Administrator)) { + New-Item -Path $WerDumpRegPath -Force | Out-Null +} + +# Initialize application verifier (Windows only). +if ($IsWindows -and $EnableAppVerifier) { + where.exe appverif.exe + if ($LastExitCode -eq 0) { + appverif.exe /verify $Path + } else { + Write-Warning "Application Verifier not installed!" + $EnableAppVerifier = $false; + } +} + +$DriverPath = (Split-Path $Path -Parent) + +# Install the kernel mode drivers. +if ($Kernel -ne "") { + if ($null -ne (Get-Service -Name "cxplattest" -ErrorAction Ignore)) { + try { net.exe stop cxplattest /y | Out-Null } catch {} + sc.exe delete cxplattest /y | Out-Null + } + Copy-Item (Join-Path $Kernel "cxplattest.sys") $DriverPath -Force + + if ($EnableSystemVerifier) { + verifier.exe /volatile /adddriver cxplattest.sys /flags 0x9BB + if ($LastExitCode) { + Log ("verifier.exe " + $LastExitCode) + } + } +} + +try { + if ($IsolationMode -eq "Batch") { + # Run the the test process once for all tests. + Wait-TestCase (Start-AllTestCases) + } else { + # Run the test cases individually. + for ($i = 0; $i -lt $TestCount; $i++) { + Wait-TestCase (Start-TestCase ($TestCases -as [String[]])[$i]) + if (!$NoProgress) { + Write-Progress -Activity "Running tests" -Status "Progress:" -PercentComplete ($i/$TestCount*100) + } + } + } +} catch { + Log "Exception Thrown" + Log $_ + Get-Error + $_ | Format-List * +} finally { + if ($LogProfile -ne "None") { + & $LogScript -Cancel | Out-Null + } + + if ($IsWindows) { + # Cleanup the WER registry. + if (Test-Administrator) { + Remove-Item -Path $WerDumpRegPath -Force | Out-Null + } + # Turn off App Verifier + if ($EnableAppVerifier) { + appverif.exe -disable * -for $Path + } + } + + if ($IsolationMode -eq "Batch") { + if (Test-Path $FinalResultsPath) { + $XmlResults = [xml](Get-Content $FinalResultsPath) + if (!$GenerateXmlResults) { + # Delete the XML results file since it's not needed. + Remove-Item $FinalResultsPath -Force | Out-Null + } + } else { + # No results file means the tests crashed most likely. + $NewXmlText = $FailXmlText.Replace("TestSuiteName", "all") + $NewXmlText = $NewXmlText.Replace("TestCaseName", "all") + $NewXmlText = $NewXmlText.Replace("date", $XmlResults.testsuites.timestamp) + $XmlResults = [xml]($NewXmlText) + if ($GenerateXmlResults) { + # Save the xml results. + $XmlResults.Save($FinalResultsPath) | Out-Null + } + } + } else { + if ($GenerateXmlResults) { + # Save the xml results. + $XmlResults.Save($FinalResultsPath) | Out-Null + } + } + + $TestCount = $XmlResults.testsuites.tests -as [Int] + $TestsFailed = $XmlResults.testsuites.failures -as [Int] + + # Uninstall the kernel mode test driver. + if ($Kernel -ne "") { + try { net.exe stop cxplattest /y | Out-Null } catch {} + sc.exe delete cxplattest | Out-Null + Remove-Item (Join-Path $DriverPath cxplattest.sys) -Force + } + + if ($IsWindows -and $EnableSystemVerifier) { + if ($Kernel -ne "") { + verifier.exe /volatile /removedriver cxplattest.sys + verifier.exe /volatile /flags 0x0 + } + } + + # Print out the results. + Log "$($TestCount) test(s) run." + if ($KeepOutputOnSuccess -or ($TestsFailed -ne 0) -or ($global:CrashedProcessCount -ne 0)) { + Log "Output can be found in $($LogDir)" + if ($ErrorsAsWarnings) { + Write-Warning "$($TestsFailed) test(s) failed." + Write-Warning "$($TestsFailed) test(s) failed, $($global:CrashedProcessCount) test(s) crashed." + } else { + Write-Error "$($TestsFailed) test(s) failed, $($global:CrashedProcessCount) test(s) crashed." + $LastExitCode = 1 + } + } elseif ($AZP -and $TestCount -eq 0) { + Write-Error "Failed to run any tests." + } else { + if (Test-Path $LogDir) { + Remove-Item $LogDir -Recurse -Force | Out-Null + } + } +} diff --git a/scripts/sign.ps1 b/scripts/sign.ps1 new file mode 100644 index 0000000..1d2f682 --- /dev/null +++ b/scripts/sign.ps1 @@ -0,0 +1,78 @@ +<# + +.SYNOPSIS +This signs and packages the drivers. + +.PARAMETER Arch + The CPU architecture to use. + +.PARAMETER Config + Specifies the build configuration to use. + +#> + +param ( + [Parameter(Mandatory = $false)] + [ValidateSet("x86", "x64", "arm", "arm64")] + [string]$Arch = "x64", + + [Parameter(Mandatory = $false)] + [ValidateSet("Debug", "Release")] + [string]$Config = "Release" +) + +Set-StrictMode -Version 'Latest' +$ErrorActionPreference = 'Stop' + +function Get-WindowsKitTool { + param ( + [string]$Arch = "x86", + [Parameter(Mandatory = $true)] + [string]$Tool + ) + + $KitBinRoot = "C:\Program Files (x86)\Windows Kits\10\bin" + if (!(Test-Path $KitBinRoot)) { + Write-Error "Windows Kit Binary Folder not Found" + return $null + } + + + $Subfolders = Get-ChildItem -Path $KitBinRoot -Directory | Sort-Object -Descending + foreach ($Subfolder in $Subfolders) { + $ToolPath = Join-Path $Subfolder.FullName "$Arch\$Tool" + if (Test-Path $ToolPath) { + return $ToolPath + } + } + + Write-Error "Failed to find tool" + return $null +} + +# Tool paths. +$SignToolPath = Get-WindowsKitTool -Tool "signtool.exe" +if (!(Test-Path $SignToolPath)) { Write-Error "$SignToolPath does not exist!" } + +# Artifact paths. +$RootDir = (Split-Path $PSScriptRoot -Parent) +$ArtifactsDir = Join-Path $RootDir "artifacts\bin\winkernel\$($Arch)_$($Config)" + +# Signing certificate path. +$CertPath = Join-Path $RootDir "artifacts\corenet-ci-main\vm-setup\CoreNetSign.pfx" +if (!(Test-Path $CertPath)) { Write-Error "$CertPath does not exist!" } + +# All the file paths. +$DriverFiles = @( + (Join-Path $ArtifactsDir "cxplattest.sys") +) + +# Sign the driver files. +foreach ($File in $DriverFiles) { + if (!(Test-Path $File)) { + Write-Host "Warning: $File does not exist! Skipping signing." + } else { + & $SignToolPath sign /f $CertPath -p "placeholder" /fd SHA256 $File + if ($LastExitCode) { Write-Error "signtool.exe exit code: $LastExitCode" } + } +} diff --git a/scripts/test.ps1 b/scripts/test.ps1 new file mode 100644 index 0000000..09c7a26 --- /dev/null +++ b/scripts/test.ps1 @@ -0,0 +1,258 @@ +<# + +.SYNOPSIS +This script runs the Cxplat tests. + +.PARAMETER Config + Specifies the build configuration to test. + +.PARAMETER Arch + The CPU architecture to test. + +.PARAMETER ExtraArtifactDir + Add an extra classifier to the artifact directory to allow publishing alternate builds of same base library + +.PARAMETER Kernel + Runs the Windows kernel mode tests. + +.PARAMETER Filter + A filter to include test cases from the list to execute. Multiple filters + are separated by :. Negative filters are prefixed with -. + +.PARAMETER ListTestCases + Lists all the test cases. + +.PARAMETER IsolationMode + Controls the isolation mode when running each test case. + +.PARAMETER KeepOutputOnSuccess + Don't discard console output or logs on success. + +.PARAMETER GenerateXmlResults + Generates an xml Test report for the run. + +.PARAMETER Debugger + Attaches the debugger to each test case run. + +.PARAMETER InitialBreak + Debugger starts broken into the process to allow setting breakpoints, etc. + +.PARAMETER BreakOnFailure + Triggers a break point on a test failure. + +.PARAMETER CompressOutput + Compresses the output files generated for failed test cases. + +.PARAMETER NoProgress + Disables the progress bar. + +.Parameter EnableAppVerifier + Enables all basic Application Verifier checks on test binaries. + +.Parameter GHA + Running this script as a part of GitHub Actions. + +.Parameter ErrorsAsWarnings + Treats all errors as warnings. + +.Parameter NumIterations + Number of times to run this particular command. Catches tricky edge cases due to random nature of networks. + +.EXAMPLE + test.ps1 + +.EXAMPLE + test.ps1 -ListTestCases + +.EXAMPLE + test.ps1 -ListTestCases -Filter ParameterValidation* + +.EXAMPLE + test.ps1 -Filter ParameterValidation* + +.EXAMPLE + test.ps1 -Filter ParameterValidation* -NumIterations 10 +#> + +param ( + [Parameter(Mandatory = $false)] + [ValidateSet("Debug", "Release")] + [string]$Config = "Debug", + + [Parameter(Mandatory = $false)] + [ValidateSet("x86", "x64", "arm", "arm64")] + [string]$Arch = "", + + [Parameter(Mandatory = $false)] + [switch]$Kernel = $false, + + [Parameter(Mandatory = $false)] + [string]$Filter = "", + + [Parameter(Mandatory = $false)] + [switch]$ListTestCases = $false, + + [Parameter(Mandatory = $false)] + [ValidateSet("Batch", "Isolated")] + [string]$IsolationMode = "Isolated", + + [Parameter(Mandatory = $false)] + [switch]$KeepOutputOnSuccess = $false, + + [Parameter(Mandatory = $false)] + [switch]$GenerateXmlResults = $false, + + [Parameter(Mandatory = $false)] + [switch]$Debugger = $false, + + [Parameter(Mandatory = $false)] + [switch]$InitialBreak = $false, + + [Parameter(Mandatory = $false)] + [switch]$BreakOnFailure = $false, + + [Parameter(Mandatory = $false)] + [switch]$CompressOutput = $false, + + [Parameter(Mandatory = $false)] + [switch]$NoProgress = $false, + + [Parameter(Mandatory = $false)] + [switch]$EnableAppVerifier = $false, + + [Parameter(Mandatory = $false)] + [switch]$EnableSystemVerifier = $false, + + [Parameter(Mandatory = $false)] + [string]$ExtraArtifactDir = "", + + [Parameter(Mandatory = $false)] + [switch]$GHA = $false, + + [Parameter(Mandatory = $false)] + [switch]$ErrorsAsWarnings = $false, + + [Parameter(Mandatory = $false)] + [string]$OsRunner = "", + + [Parameter(Mandatory = $false)] + [int]$NumIterations = 1 +) + +Set-StrictMode -Version 'Latest' +$PSDefaultParameterValues['*:ErrorAction'] = 'Stop' + +function Test-Administrator +{ + $user = [Security.Principal.WindowsIdentity]::GetCurrent(); + (New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) +} + +if ($IsWindows -and !(Test-Administrator)) { + Write-Warning "We recommend running this test as administrator. Crash dumps will not work" +} + +# Validate the kernel switch. +if ($Kernel -and !$IsWindows) { + Write-Error "-Kernel switch only supported on Windows" +} + +$BuildConfig = & (Join-Path $PSScriptRoot get-buildconfig.ps1) -Arch $Arch -ExtraArtifactDir $ExtraArtifactDir -Config $Config + +$Arch = $BuildConfig.Arch +$RootArtifactDir = $BuildConfig.ArtifactsDir + +# Root directory of the project. +$RootDir = Split-Path $PSScriptRoot -Parent + +# Path to the run-gtest Powershell script. +$RunTest = Join-Path $RootDir "scripts/run-gtest.ps1" + +if ("" -ne $ExtraArtifactDir -and $Kernel) { + Write-Error "Kernel not supported with extra artifact dir" +} + +# Path to the cxplattest executable. +$CxplatTest = $null +$KernelPath = $null; +if ($IsWindows) { + $CxplatTest = Join-Path $RootArtifactDir "cxplattest.exe" + $KernelPath = Join-Path $RootDir "\artifacts\bin\winkernel\$($Arch)_$($Config)" +} elseif ($IsLinux -or $IsMacOS) { + $CxplatTest = Join-Path $RootArtifactDir "cxplattest" +} else { + Write-Error "Unsupported platform type!" +} + +# Make sure the build is present. +if (!(Test-Path $CxplatTest)) { + $BuildScriptPath = Join-Path $RootDir "scripts" + $BuildScriptPath = Join-Path $BuildScriptPath "build.ps1" + Write-Error "Build does not exist!`n `nRun the following to generate it:`n `n $BuildScriptPath -Config $Config -Arch $Arch`n" +} +if ($Kernel) { + if (!(Test-Path (Join-Path $KernelPath "cxplattest.sys"))) { + Write-Error "Kernel binaries do not exist!" + } +} + +# Build up all the arguments to pass to the Powershell script. +$TestArguments = "-IsolationMode $IsolationMode" + +if ($Kernel) { + $TestArguments += " -Kernel $KernelPath" +} +if ("" -ne $Filter) { + $TestArguments += " -Filter $Filter" +} +if ($ListTestCases) { + $TestArguments += " -ListTestCases" +} +if ($KeepOutputOnSuccess) { + $TestArguments += " -KeepOutputOnSuccess" +} +if ($GenerateXmlResults) { + $TestArguments += " -GenerateXmlResults" +} +if ($Debugger) { + $TestArguments += " -Debugger" +} +if ($InitialBreak) { + $TestArguments += " -InitialBreak" +} +if ($BreakOnFailure) { + $TestArguments += " -BreakOnFailure" +} +if ($CompressOutput) { + $TestArguments += " -CompressOutput" +} +if ($NoProgress) { + $TestArguments += " -NoProgress" +} +if ($EnableAppVerifier) { + $TestArguments += " -EnableAppVerifier" +} +if ($EnableSystemVerifier) { + $TestArguments += " -EnableSystemVerifier" +} +if ($GHA) { + $TestArguments += " -GHA" +} +if ($ErrorsAsWarnings) { + $TestArguments += " -ErrorsAsWarnings" +} +if ("" -ne $OsRunner) { + $TestArguments += " -OsRunner $OsRunner" +} + +if (![string]::IsNullOrWhiteSpace($ExtraArtifactDir)) { + $TestArguments += " -ExtraArtifactDir $ExtraArtifactDir" +} + +for ($iteration = 1; $iteration -le $NumIterations; $iteration++) { + if ($NumIterations -gt 1) { + Write-Host "------- Iteration $iteration -------" + } + # Run the script. + Invoke-Expression ($RunTest + " -Path $CxplatTest " + $TestArguments) +} diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index 4921a97..7d1ff22 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -10,3 +10,5 @@ endif() add_library(cxplat STATIC ${SOURCES}) target_include_directories(cxplat PRIVATE ${PROJECT_SOURCE_DIR}/inc) + +target_compile_definitions(cxplat INTERFACE ${CXPLAT_COMMON_DEFINES}) diff --git a/src/lib/cxplat.kernel.vcxproj b/src/lib/cxplat.kernel.vcxproj index 9ab5b2e..e52687b 100644 --- a/src/lib/cxplat.kernel.vcxproj +++ b/src/lib/cxplat.kernel.vcxproj @@ -87,13 +87,13 @@ MultiThreadedDebugDLL - VER_BUILD_ID=$(CXPLAT_VER_BUILD_ID);VER_SUFFIX=$(CXPLAT_VER_SUFFIX);VER_GIT_HASH=$(CXPLAT_VER_GIT_HASH);SECURITY_KERNEL;SECURITY_WIN32;_DEBUG;%(PreprocessorDefinitions) + CX_PLATFORM_WINKERNEL;VER_BUILD_ID=$(CXPLAT_VER_BUILD_ID);VER_SUFFIX=$(CXPLAT_VER_SUFFIX);VER_GIT_HASH=$(CXPLAT_VER_GIT_HASH);SECURITY_KERNEL;SECURITY_WIN32;_DEBUG;%(PreprocessorDefinitions) true - VER_BUILD_ID=$(CXPLAT_VER_BUILD_ID);VER_SUFFIX=$(CXPLAT_VER_SUFFIX);VER_GIT_HASH=$(CXPLAT_VER_GIT_HASH);SECURITY_KERNEL;SECURITY_WIN32;%(PreprocessorDefinitions) + CX_PLATFORM_WINKERNEL;VER_BUILD_ID=$(CXPLAT_VER_BUILD_ID);VER_SUFFIX=$(CXPLAT_VER_SUFFIX);VER_GIT_HASH=$(CXPLAT_VER_GIT_HASH);SECURITY_KERNEL;SECURITY_WIN32;%(PreprocessorDefinitions) true diff --git a/src/test/CxplatTests.h b/src/test/CxplatTests.h new file mode 100644 index 0000000..1eacc29 --- /dev/null +++ b/src/test/CxplatTests.h @@ -0,0 +1,83 @@ +/*++ + + Copyright (c) Microsoft Corporation. + Licensed under the MIT License. + +Abstract: + + Interface for the Platform Independent Cxplat Tests + +--*/ + +#ifdef __cplusplus +extern "C" { +#endif + +void CxplatTestInitialize(); +void CxplatTestUninitialize(); + +// +// Dummy Tests +// + +void CxplatTestDummy(); + + +// +// Platform Specific Functions +// + +void +LogTestFailure( + _In_z_ const char *File, + _In_z_ const char *Function, + int Line, + _Printf_format_string_ const char *Format, + ... + ); + +#ifdef __cplusplus +} +#endif + +// +// Kernel Mode Driver Interface +// + +// +// Name of the driver service for cxplattest.sys. +// +#define CXPLAT_DRIVER_NAME "cxplattest" + +#ifdef _WIN32 + +// +// {3A37B2CB-39A6-426A-BAF4-77D0ED0070B3} +// +static const GUID CXPLAT_TEST_DEVICE_INSTANCE = +{ 0x3a37b2cb, 0x39a6, 0x426a,{ 0xba, 0xf4, 0x77, 0xd0, 0xed, 0x00, 0x70, 0xb3 } }; + +#ifndef _KERNEL_MODE +#include +#endif // _KERNEL_MODE + +#define CXPLAT_CTL_CODE(request, method, access) \ + CTL_CODE(FILE_DEVICE_NETWORK, request, method, access) + +#define IoGetFunctionCodeFromCtlCode( ControlCode ) (\ + ( ControlCode >> 2) & 0x00000FFF ) + +#else // _WIN32 + +#define CXPLAT_CTL_CODE(request, method, access) (request) + +#endif // _WIN32 + +// +// IOCTL Interface +// + +#define IOCTL_CXPLAT_RUN_DUMMY \ + CXPLAT_CTL_CODE(1, METHOD_BUFFERED, FILE_WRITE_DATA) + +#define CXPLAT_MAX_IOCTL_FUNC_CODE 1 diff --git a/src/test/bin/CMakeLists.txt b/src/test/bin/CMakeLists.txt new file mode 100644 index 0000000..f126780 --- /dev/null +++ b/src/test/bin/CMakeLists.txt @@ -0,0 +1,29 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +set(SOURCES + cxplat_gtest.cpp + cxplat_gtest.h +) + +add_executable(cxplattest ${SOURCES}) + +target_include_directories(cxplattest PRIVATE ${PROJECT_SOURCE_DIR}/src/test ${PROJECT_SOURCE_DIR}/inc ${PROJECT_SOURCE_DIR}/submodules/googletest/googletest/include) + +target_compile_definitions(cxplattest INTERFACE ${CXPLAT_COMMON_DEFINES}) + +set_property(TARGET cxplattest PROPERTY FOLDER "${CXPLAT_FOLDER_PREFIX}tests") +set_property(TARGET cxplattest APPEND PROPERTY BUILD_RPATH "$ORIGIN") + +target_link_libraries(cxplattest cxplat testlib) +target_link_libraries(cxplattest gtest) + + +# At least /W3 must be used on all windows builds to pass compliance +if(MSVC) + target_compile_options(cxplattest PRIVATE /W3 /bigobj) +endif() + +add_test(NAME cxplattest + COMMAND cxplattest + WORKING_DIRECTORY ${CXPLAT_OUTPUT_DIR}) diff --git a/src/test/bin/cxplat_driver_helpers.h b/src/test/bin/cxplat_driver_helpers.h new file mode 100644 index 0000000..56c057c --- /dev/null +++ b/src/test/bin/cxplat_driver_helpers.h @@ -0,0 +1,396 @@ +/*++ + + Copyright (c) Microsoft Corporation. + Licensed under the MIT License. + +Abstract: + + This file contains helpers for interacting with a kernel mode driver service. +--*/ + +#pragma once + +#include "cxplat_trace.h" + +#if defined(_WIN32) && !defined(CXPLAT_RESTRICTED_BUILD) + +//#define CXPLAT_DRIVER_FILE_NAME CXPLAT_DRIVER_NAME ".sys" +//#define CXPLAT_IOCTL_PATH "\\\\.\\\\" CXPLAT_DRIVER_NAME + + +class CxplatDriverService { + SC_HANDLE ScmHandle; + SC_HANDLE ServiceHandle; +public: + CxplatDriverService() : + ScmHandle(nullptr), + ServiceHandle(nullptr) { + } + bool Initialize( + _In_z_ const char* DriverName, + _In_z_ const char* DependentFileNames + ) { + unsigned long Error; + ScmHandle = OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS); + if (ScmHandle == nullptr) { + Error = GetLastError(); + CxplatTraceEvent( + "[ lib] ERROR, %u, %s.", + Error, + "GetFullPathName failed"); + return false; + } + QueryService: + ServiceHandle = + OpenServiceA( + ScmHandle, + DriverName, + SERVICE_ALL_ACCESS); + if (ServiceHandle == nullptr) { + CxplatTraceEvent( + "[ lib] ERROR, %u, %s.", + GetLastError(), + "OpenService failed"); + char DriverFilePath[MAX_PATH] = {0}; + GetModuleFileNameA(NULL, DriverFilePath, MAX_PATH); + char* PathEnd = strrchr(DriverFilePath, '\\'); + if (!PathEnd) { + CxplatTraceEvent( + "[ lib] ERROR, %s.", + "Failed to get currently executing module path"); + return false; + } + PathEnd++; + size_t RemainingLength = sizeof(DriverFilePath) - (PathEnd - DriverFilePath); + int PathResult = + snprintf( + PathEnd, + RemainingLength, + "%s.sys", + DriverName); + if (PathResult <= 0 || (size_t)PathResult > RemainingLength) { + CxplatTraceEvent( + "[ lib] ERROR, %s.", + "Failed to create driver on disk file path"); + return false; + } + if (GetFileAttributesA(DriverFilePath) == INVALID_FILE_ATTRIBUTES) { + CxplatTraceEvent( + "[ lib] ERROR, %s.", + "Failed to find driver on disk"); + return false; + } + ServiceHandle = + CreateServiceA( + ScmHandle, + DriverName, + DriverName, + SC_MANAGER_ALL_ACCESS, + SERVICE_KERNEL_DRIVER, + SERVICE_DEMAND_START, + SERVICE_ERROR_NORMAL, + DriverFilePath, + nullptr, + nullptr, + DependentFileNames, + nullptr, + nullptr); + if (ServiceHandle == nullptr) { + Error = GetLastError(); + if (Error == ERROR_SERVICE_EXISTS) { + goto QueryService; + } + CxplatTraceEvent( + "[ lib] ERROR, %u, %s.", + Error, + "CreateService failed"); + return false; + } + } + return true; + } + void Uninitialize() { + if (ServiceHandle != nullptr) { + CloseServiceHandle(ServiceHandle); + } + if (ScmHandle != nullptr) { + CloseServiceHandle(ScmHandle); + } + } + bool Start() { + if (!StartServiceA(ServiceHandle, 0, nullptr)) { + unsigned long Error = GetLastError(); + if (Error != ERROR_SERVICE_ALREADY_RUNNING) { + CxplatTraceEvent( + "[ lib] ERROR, %u, %s.", + Error, + "StartService failed"); + return false; + } + } + return true; + } +}; + +class CxplatDriverClient { + HANDLE DeviceHandle; +public: + CxplatDriverClient() : DeviceHandle(INVALID_HANDLE_VALUE) { } + ~CxplatDriverClient() { Uninitialize(); } + bool Initialize( + _In_z_ const char* DriverName + ) { + unsigned long Error; + char IoctlPath[MAX_PATH]; + int PathResult = + snprintf( + IoctlPath, + sizeof(IoctlPath), + "\\\\.\\\\%s", + DriverName); + if (PathResult < 0 || PathResult >= sizeof(IoctlPath)) { + CxplatTraceEvent( + "[ lib] ERROR, %s.", + "Creating Driver File Path failed"); + return false; + } + DeviceHandle = + CreateFileA( + IoctlPath, + GENERIC_READ | GENERIC_WRITE, + 0, + nullptr, // no SECURITY_ATTRIBUTES structure + OPEN_EXISTING, // No special create flags + FILE_FLAG_OVERLAPPED, // Allow asynchronous requests + nullptr); + if (DeviceHandle == INVALID_HANDLE_VALUE) { + Error = GetLastError(); + CxplatTraceEvent( + "[ lib] ERROR, %u, %s.", + Error, + "CreateFile failed"); + return false; + } + return true; + } + void Uninitialize() { + if (DeviceHandle != INVALID_HANDLE_VALUE) { + CloseHandle(DeviceHandle); + DeviceHandle = INVALID_HANDLE_VALUE; + } + } + bool Run( + _In_ unsigned long IoControlCode, + _In_reads_bytes_opt_(InBufferSize) + void* InBuffer, + _In_ unsigned long InBufferSize, + _In_ unsigned long TimeoutMs = 30000 + ) { + unsigned long Error; + OVERLAPPED Overlapped = { 0 }; + Overlapped.hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); + if (Overlapped.hEvent == nullptr) { + Error = GetLastError(); + CxplatTraceEvent( + "[ lib] ERROR, %u, %s.", + Error, + "CreateEvent failed"); + return false; + } + CxplatTraceLogVerbose( + "[test] Sending Write IOCTL %u with %u bytes.", + IoGetFunctionCodeFromCtlCode(IoControlCode), + InBufferSize); + if (!DeviceIoControl( + DeviceHandle, + IoControlCode, + InBuffer, InBufferSize, + nullptr, 0, + nullptr, + &Overlapped)) { + Error = GetLastError(); + if (Error != ERROR_IO_PENDING) { + CloseHandle(Overlapped.hEvent); + CxplatTraceEvent( + "[ lib] ERROR, %u, %s.", + Error, + "DeviceIoControl Write failed"); + return false; + } + } + unsigned long dwBytesReturned; + if (!GetOverlappedResultEx( + DeviceHandle, + &Overlapped, + &dwBytesReturned, + TimeoutMs, + FALSE)) { + Error = GetLastError(); + if (Error == WAIT_TIMEOUT) { + Error = ERROR_TIMEOUT; + CancelIoEx(DeviceHandle, &Overlapped); + } + CxplatTraceEvent( + "[ lib] ERROR, %u, %s.", + Error, + "GetOverlappedResultEx Write failed"); + } else { + Error = ERROR_SUCCESS; + } + CloseHandle(Overlapped.hEvent); + return Error == ERROR_SUCCESS; + } + bool Run( + _In_ unsigned long IoControlCode, + _In_ unsigned long TimeoutMs = 30000 + ) { + return Run(IoControlCode, nullptr, 0, TimeoutMs); + } + template + bool Run( + _In_ unsigned long IoControlCode, + _In_ const T& Data, + _In_ unsigned long TimeoutMs = 30000 + ) { + return Run(IoControlCode, (void*)&Data, sizeof(Data), TimeoutMs); + } + bool Read( + _In_ unsigned long IoControlCode, + _Out_writes_bytes_opt_(OutBufferSize) + void* OutBuffer, + _In_ unsigned long OutBufferSize, + _Out_opt_ unsigned long* OutBufferWritten, + _In_ unsigned long TimeoutMs = 30000 + ) { + unsigned long Error; + OVERLAPPED Overlapped = { 0 }; + Overlapped.hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); + if (!Overlapped.hEvent) { + Error = GetLastError(); + CxplatTraceEvent( + "[ lib] ERROR, %u, %s.", + Error, + "CreateEvent failed"); + return false; + } + CxplatTraceLogVerbose( + "[test] Sending Read IOCTL %u.", + IoGetFunctionCodeFromCtlCode(IoControlCode)); + if (!DeviceIoControl( + DeviceHandle, + IoControlCode, + nullptr, 0, + OutBuffer, OutBufferSize, + nullptr, + &Overlapped)) { + Error = GetLastError(); + if (Error != ERROR_IO_PENDING) { + CloseHandle(Overlapped.hEvent); + CxplatTraceEvent( + "[ lib] ERROR, %u, %s.", + Error, + "DeviceIoControl Write failed"); + return false; + } + } + unsigned long dwBytesReturned; + if (!GetOverlappedResultEx( + DeviceHandle, + &Overlapped, + &dwBytesReturned, + TimeoutMs, + FALSE)) { + Error = GetLastError(); + if (Error == WAIT_TIMEOUT) { + Error = ERROR_TIMEOUT; + if (CancelIoEx(DeviceHandle, &Overlapped)) { + GetOverlappedResult(DeviceHandle, &Overlapped, &dwBytesReturned, true); + } + } else { + CxplatTraceEvent( + "[ lib] ERROR, %u, %s.", + Error, + "GetOverlappedResultEx Read failed"); + } + } else { + Error = ERROR_SUCCESS; + *OutBufferWritten = dwBytesReturned; + } + CloseHandle(Overlapped.hEvent); + return Error == ERROR_SUCCESS; + } +}; + +#else + +#define UNREFERENCED_PARAMETER(param) + +class CxplatDriverService { +public: + bool Initialize( + _In_z_ const char* DriverName, + _In_z_ const char* DependentFileNames + ) { + UNREFERENCED_PARAMETER(DriverName); + UNREFERENCED_PARAMETER(DependentFileNames); + return false; + } + void Uninitialize() { } + bool Start() { return false; } +}; + +class CxplatDriverClient { +public: + bool Initialize( + _In_z_ const char* DriverName + ) { + UNREFERENCED_PARAMETER(DriverName); + return false; + } + void Uninitialize() { } + bool Run( + _In_ unsigned long IoControlCode, + _In_ void* InBuffer, + _In_ unsigned long InBufferSize, + _In_ unsigned long TimeoutMs = 30000 + ) { + UNREFERENCED_PARAMETER(IoControlCode); + UNREFERENCED_PARAMETER(InBuffer); + UNREFERENCED_PARAMETER(InBufferSize); + UNREFERENCED_PARAMETER(TimeoutMs); + return false; + } + bool + Run( + _In_ unsigned long IoControlCode, + _In_ unsigned long TimeoutMs = 30000 + ) { + return Run(IoControlCode, nullptr, 0, TimeoutMs); + } + template + bool + Run( + _In_ unsigned long IoControlCode, + _In_ const T& Data, + _In_ unsigned long TimeoutMs = 30000 + ) { + return Run(IoControlCode, (void*)&Data, sizeof(Data), TimeoutMs); + } + bool Read( + _In_ unsigned long IoControlCode, + _Out_writes_bytes_opt_(OutBufferSize) + void* OutBuffer, + _In_ unsigned long OutBufferSize, + _Out_ unsigned long* OutBufferWritten, + _In_ unsigned long TimeoutMs = 30000 + ) { + UNREFERENCED_PARAMETER(IoControlCode); + UNREFERENCED_PARAMETER(OutBuffer); + UNREFERENCED_PARAMETER(OutBufferSize); + UNREFERENCED_PARAMETER(OutBufferWritten); + UNREFERENCED_PARAMETER(TimeoutMs); + return false; + } +}; + +#endif diff --git a/src/test/bin/cxplat_gtest.cpp b/src/test/bin/cxplat_gtest.cpp new file mode 100644 index 0000000..b5a1874 --- /dev/null +++ b/src/test/bin/cxplat_gtest.cpp @@ -0,0 +1,133 @@ +/*++ + + Copyright (c) Microsoft Corporation. + Licensed under the MIT License. + +--*/ + +#include "cxplat_gtest.h" + +#ifndef _WIN32 +#define _vsnprintf_s(dst, dst_len, flag, format, ...) vsnprintf(dst, dst_len, format, __VA_ARGS__) +#endif + +bool TestingKernelMode = false; +const char* OsRunner = nullptr; +uint32_t Timeout = UINT32_MAX; +CxplatDriverClient DriverClient; + +class CxplatTestEnvironment : public ::testing::Environment { + CxplatDriverService DriverService; +public: + void SetUp() override { + if (TestingKernelMode) { + printf("Initializing for Kernel Mode tests\n"); + const char* DriverName = CXPLAT_DRIVER_NAME; + const char* DependentDriverNames = NULL; + ASSERT_TRUE(DriverService.Initialize(DriverName, DependentDriverNames)); + ASSERT_TRUE(DriverService.Start()); + ASSERT_TRUE(DriverClient.Initialize(DriverName)); + } else { + printf("Initializing for User Mode tests\n"); + CxplatTestInitialize(); + } + } + void TearDown() override { + if (TestingKernelMode) { + DriverClient.Uninitialize(); + DriverService.Uninitialize(); + } else { + CxplatTestUninitialize(); + } + } +}; + +// +// This function is called by the platform independent test code when it +// encounters kind of failure. Note - It may be called on any thread. +// +void +LogTestFailure( + _In_z_ const char* File, + _In_z_ const char* Function, + int Line, + _Printf_format_string_ const char* Format, + ... + ) +{ + UNREFERENCED_PARAMETER(Function); + char Buffer[256]; + va_list Args; + va_start(Args, Format); + (void)_vsnprintf_s(Buffer, sizeof(Buffer), _TRUNCATE, Format, Args); + va_end(Args); + CxplatTraceLogError( + TestLogFailure, + "[test] FAILURE - %s:%d - %s", + File, + Line, + Buffer); + GTEST_MESSAGE_AT_(File, Line, Buffer, ::testing::TestPartResult::kFatalFailure); +} + +struct TestLogger { + const char* TestName; + TestLogger(const char* Name) : TestName(Name) { + CxplatTraceLogInfo( + TestCaseStart, + "[test] START %s", + TestName); + } + ~TestLogger() { + CxplatTraceLogInfo( + TestCaseEnd, + "[test] END %s", + TestName); + } +}; + +template +struct TestLoggerT { + const char* TestName; + TestLoggerT(const char* Name, const T& Params) : TestName(Name) { + std::ostringstream stream; stream << Params; + CxplatTraceLogInfo( + TestCaseTStart, + "[test] START %s, %s", + TestName, + stream.str().c_str()); + } + ~TestLoggerT() { + CxplatTraceLogInfo( + TestCaseTEnd, + "[test] END %s", + TestName); + } +}; + +TEST(DummySuite, Dummy) { + TestLogger Logger("CxplatTestDummy"); + if (TestingKernelMode) { + ASSERT_TRUE(DriverClient.Run(IOCTL_CXPLAT_RUN_DUMMY)); + } else { + CxplatTestDummy(); + } +} + +int main(int argc, char** argv) { + for (int i = 0; i < argc; ++i) { + if (strcmp("--kernel", argv[i]) == 0) { + TestingKernelMode = true; + } else if (strstr(argv[i], "--osRunner")) { + OsRunner = argv[i] + sizeof("--osRunner"); + } else if (strcmp("--timeout", argv[i]) == 0) { + if (i + 1 < argc) { + Timeout = atoi(argv[i + 1]); + ++i; + } + } + } + ::testing::AddGlobalTestEnvironment(new CxplatTestEnvironment); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/test/bin/cxplat_gtest.h b/src/test/bin/cxplat_gtest.h new file mode 100644 index 0000000..509ac7a --- /dev/null +++ b/src/test/bin/cxplat_gtest.h @@ -0,0 +1,50 @@ +/*++ + + Copyright (c) Microsoft Corporation. + Licensed under the MIT License. + +--*/ + +#if defined(CX_PLATFORM_WINUSER) +#include +#include +#elif defined(CX_PLATFORM_LINUX) || defined(CX_PLATFORM_DARWIN) +// For FreeBSD +#if defined(__FreeBSD__) +#include +#include +#define ETIME ETIMEDOUT +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#else +#error "Unsupported Platform" +#endif + +#include "cxplat.h" +#include "CxplatTests.h" +#include "cxplat_trace.h" +#include "cxplat_driver_helpers.h" +#undef min // gtest headers conflict with previous definitions of min/max. +#undef max +#include "gtest/gtest.h" + +extern bool TestingKernelMode; diff --git a/src/test/bin/winkernel/control.cpp b/src/test/bin/winkernel/control.cpp new file mode 100644 index 0000000..04e4256 --- /dev/null +++ b/src/test/bin/winkernel/control.cpp @@ -0,0 +1,548 @@ +/*++ + + Copyright (c) Microsoft Corporation. + Licensed under the MIT License. + +Abstract: + + CXPLAT Kernel Mode Test Driver + +--*/ + +#include +#include +#include + +#include "CxplatTests.h" + +#include "cxplat_trace.h" + +#ifndef KRTL_INIT_SEGMENT +#define KRTL_INIT_SEGMENT "INIT" +#endif +#ifndef KRTL_PAGE_SEGMENT +#define KRTL_PAGE_SEGMENT "PAGE" +#endif +#ifndef KRTL_NONPAGED_SEGMENT +#define KRTL_NONPAGED_SEGMENT ".text" +#endif + +// Use on code in the INIT segment. (Code is discarded after DriverEntry returns.) +#define INITCODE __declspec(code_seg(KRTL_INIT_SEGMENT)) + +// Use on pageable functions. +#define PAGEDX __declspec(code_seg(KRTL_PAGE_SEGMENT)) + +DECLARE_CONST_UNICODE_STRING(CxplatTestCtlDeviceName, L"\\Device\\" CXPLAT_DRIVER_NAME); +DECLARE_CONST_UNICODE_STRING(CxplatTestCtlDeviceSymLink, L"\\DosDevices\\" CXPLAT_DRIVER_NAME); + +typedef struct CXPLAT_DEVICE_EXTENSION { + EX_PUSH_LOCK Lock; + + _Guarded_by_(Lock) + LIST_ENTRY ClientList; + ULONG ClientListSize; + +} CXPLAT_DEVICE_EXTENSION; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(CXPLAT_DEVICE_EXTENSION, CxplatTestCtlGetDeviceContext); + +typedef struct CXPLAT_TEST_CLIENT +{ + LIST_ENTRY Link; + bool TestFailure; + +} CXPLAT_TEST_CLIENT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(CXPLAT_TEST_CLIENT, CxplatTestCtlGetFileContext); + +EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL CxplatTestCtlEvtIoDeviceControl; +EVT_WDF_IO_QUEUE_IO_CANCELED_ON_QUEUE CxplatTestCtlEvtIoCanceled; + +PAGEDX EVT_WDF_DEVICE_FILE_CREATE CxplatTestCtlEvtFileCreate; +PAGEDX EVT_WDF_FILE_CLOSE CxplatTestCtlEvtFileClose; +PAGEDX EVT_WDF_FILE_CLEANUP CxplatTestCtlEvtFileCleanup; + +WDFDEVICE CxplatTestCtlDevice = nullptr; +CXPLAT_DEVICE_EXTENSION* CxplatTestCtlExtension = nullptr; +CXPLAT_TEST_CLIENT* CxplatTestClient = nullptr; + +_No_competing_thread_ +INITCODE +NTSTATUS +CxplatTestCtlInitialize( + _In_ WDFDRIVER Driver + ) +{ + NTSTATUS Status = STATUS_SUCCESS; + PWDFDEVICE_INIT DeviceInit = nullptr; + WDF_FILEOBJECT_CONFIG FileConfig; + WDF_OBJECT_ATTRIBUTES Attribs; + WDFDEVICE Device; + CXPLAT_DEVICE_EXTENSION* DeviceContext; + WDF_IO_QUEUE_CONFIG QueueConfig; + WDFQUEUE Queue; + + DeviceInit = + WdfControlDeviceInitAllocate( + Driver, + &SDDL_DEVOBJ_SYS_ALL_ADM_ALL); + if (DeviceInit == nullptr) { + CxplatTraceEvent( + LibraryError, + "[ lib] ERROR, %s.", + "WdfControlDeviceInitAllocate failed"); + Status = STATUS_INSUFFICIENT_RESOURCES; + goto Error; + } + + Status = + WdfDeviceInitAssignName( + DeviceInit, + &CxplatTestCtlDeviceName); + if (!NT_SUCCESS(Status)) { + CxplatTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + Status, + "WdfDeviceInitAssignName failed"); + goto Error; + } + + WDF_FILEOBJECT_CONFIG_INIT( + &FileConfig, + CxplatTestCtlEvtFileCreate, + CxplatTestCtlEvtFileClose, + CxplatTestCtlEvtFileCleanup); + FileConfig.FileObjectClass = WdfFileObjectWdfCanUseFsContext2; + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&Attribs, CXPLAT_TEST_CLIENT); + WdfDeviceInitSetFileObjectConfig( + DeviceInit, + &FileConfig, + &Attribs); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&Attribs, CXPLAT_DEVICE_EXTENSION); + + Status = + WdfDeviceCreate( + &DeviceInit, + &Attribs, + &Device); + if (!NT_SUCCESS(Status)) { + CxplatTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + Status, + "WdfDeviceCreate failed"); + goto Error; + } + + DeviceContext = CxplatTestCtlGetDeviceContext(Device); + RtlZeroMemory(DeviceContext, sizeof(CXPLAT_DEVICE_EXTENSION)); + ExInitializePushLock(&DeviceContext->Lock); + InitializeListHead(&DeviceContext->ClientList); + + Status = WdfDeviceCreateSymbolicLink(Device, &CxplatTestCtlDeviceSymLink); + if (!NT_SUCCESS(Status)) { + CxplatTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + Status, + "WdfDeviceCreateSymbolicLink failed"); + goto Error; + } + + WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&QueueConfig, WdfIoQueueDispatchParallel); + QueueConfig.EvtIoDeviceControl = CxplatTestCtlEvtIoDeviceControl; + QueueConfig.EvtIoCanceledOnQueue = CxplatTestCtlEvtIoCanceled; + + __analysis_assume(QueueConfig.EvtIoStop != 0); + Status = + WdfIoQueueCreate( + Device, + &QueueConfig, + WDF_NO_OBJECT_ATTRIBUTES, + &Queue); + __analysis_assume(QueueConfig.EvtIoStop == 0); + + if (!NT_SUCCESS(Status)) { + CxplatTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + Status, + "WdfIoQueueCreate failed"); + goto Error; + } + + CxplatTestCtlDevice = Device; + CxplatTestCtlExtension = DeviceContext; + + WdfControlFinishInitializing(Device); + + CxplatTraceLogVerbose( + TestControlInitialized, + "[test] Control interface initialized"); + +Error: + + if (DeviceInit) { + WdfDeviceInitFree(DeviceInit); + } + + return Status; +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +VOID +CxplatTestCtlUninitialize( + ) +{ + CxplatTraceLogVerbose( + TestControlUninitializing, + "[test] Control interface uninitializing"); + + if (CxplatTestCtlDevice != nullptr) { + NT_ASSERT(CxplatTestCtlExtension != nullptr); + CxplatTestCtlExtension = nullptr; + + WdfObjectDelete(CxplatTestCtlDevice); + CxplatTestCtlDevice = nullptr; + } + + CxplatTraceLogVerbose( + TestControlUninitialized, + "[test] Control interface uninitialized"); +} + +PAGEDX +_Use_decl_annotations_ +VOID +CxplatTestCtlEvtFileCreate( + _In_ WDFDEVICE /* Device */, + _In_ WDFREQUEST Request, + _In_ WDFFILEOBJECT FileObject + ) +{ + NTSTATUS Status = STATUS_SUCCESS; + + PAGED_CODE(); + + KeEnterGuardedRegion(); + ExAcquirePushLockExclusive(&CxplatTestCtlExtension->Lock); + + do + { + if (CxplatTestCtlExtension->ClientListSize >= 1) { + CxplatTraceEvent( + LibraryError, + "[ lib] ERROR, %s.", + "Already have max clients"); + Status = STATUS_TOO_MANY_SESSIONS; + break; + } + + CXPLAT_TEST_CLIENT* Client = CxplatTestCtlGetFileContext(FileObject); + if (Client == nullptr) { + CxplatTraceEvent( + LibraryError, + "[ lib] ERROR, %s.", + "nullptr File context in FileCreate"); + Status = STATUS_INVALID_PARAMETER; + break; + } + + RtlZeroMemory(Client, sizeof(CXPLAT_TEST_CLIENT)); + + // + // Insert into the client list + // + InsertTailList(&CxplatTestCtlExtension->ClientList, &Client->Link); + CxplatTestCtlExtension->ClientListSize++; + + CxplatTraceLogInfo( + TestControlClientCreated, + "[test] Client %p created", + Client); + + // + // TODO: Add multiple device client support? + // + CxplatTestClient = Client; + } + while (false); + + ExReleasePushLockExclusive(&CxplatTestCtlExtension->Lock); + KeLeaveGuardedRegion(); + + WdfRequestComplete(Request, Status); +} + +PAGEDX +_Use_decl_annotations_ +VOID +CxplatTestCtlEvtFileClose( + _In_ WDFFILEOBJECT /* FileObject */ + ) +{ + PAGED_CODE(); +} + +PAGEDX +_Use_decl_annotations_ +VOID +CxplatTestCtlEvtFileCleanup( + _In_ WDFFILEOBJECT FileObject + ) +{ + PAGED_CODE(); + + KeEnterGuardedRegion(); + + CXPLAT_TEST_CLIENT* Client = CxplatTestCtlGetFileContext(FileObject); + if (Client != nullptr) { + + ExAcquirePushLockExclusive(&CxplatTestCtlExtension->Lock); + + // + // Remove the device client from the list + // + RemoveEntryList(&Client->Link); + CxplatTestCtlExtension->ClientListSize--; + + ExReleasePushLockExclusive(&CxplatTestCtlExtension->Lock); + + CxplatTraceLogInfo( + TestControlClientCleaningUp, + "[test] Client %p cleaning up", + Client); + + CxplatTestClient = nullptr; + } + + KeLeaveGuardedRegion(); +} + +VOID +CxplatTestCtlEvtIoCanceled( + _In_ WDFQUEUE /* Queue */, + _In_ WDFREQUEST Request + ) +{ + NTSTATUS Status; + + WDFFILEOBJECT FileObject = WdfRequestGetFileObject(Request); + if (FileObject == nullptr) { + Status = STATUS_DEVICE_NOT_READY; + goto error; + } + + CXPLAT_TEST_CLIENT* Client = CxplatTestCtlGetFileContext(FileObject); + if (Client == nullptr) { + Status = STATUS_DEVICE_NOT_READY; + goto error; + } + + CxplatTraceLogWarning( + TestControlClientCanceledRequest, + "[test] Client %p canceled request %p", + Client, + Request); + + Status = STATUS_CANCELLED; + +error: + + WdfRequestComplete(Request, Status); +} + +size_t CXPLAT_IOCTL_BUFFER_SIZES[] = +{ + 0, + 0, +}; + +static_assert( + CXPLAT_MAX_IOCTL_FUNC_CODE + 1 == (sizeof(CXPLAT_IOCTL_BUFFER_SIZES)/sizeof(size_t)), + "CXPLAT_IOCTL_BUFFER_SIZES must be kept in sync with the IOCTLs"); + +typedef union { +} CXPLAT_IOCTL_PARAMS; + +#define CxplatTestCtlRun(X) \ + Client->TestFailure = false; \ + X; \ + Status = Client->TestFailure ? STATUS_FAIL_FAST_EXCEPTION : STATUS_SUCCESS; + +VOID +CxplatTestCtlEvtIoDeviceControl( + _In_ WDFQUEUE /* Queue */, + _In_ WDFREQUEST Request, + _In_ size_t /* OutputBufferLength */, + _In_ size_t InputBufferLength, + _In_ ULONG IoControlCode + ) +{ + NTSTATUS Status = STATUS_SUCCESS; + WDFFILEOBJECT FileObject = nullptr; + CXPLAT_TEST_CLIENT* Client = nullptr; + + if (KeGetCurrentIrql() > PASSIVE_LEVEL) { + Status = STATUS_NOT_SUPPORTED; + CxplatTraceEvent( + LibraryError, + "[ lib] ERROR, %s.", + "IOCTL not supported greater than PASSIVE_LEVEL"); + goto Error; + } + + FileObject = WdfRequestGetFileObject(Request); + if (FileObject == nullptr) { + Status = STATUS_DEVICE_NOT_READY; + CxplatTraceEvent( + LibraryError, + "[ lib] ERROR, %s.", + "WdfRequestGetFileObject failed"); + goto Error; + } + + Client = CxplatTestCtlGetFileContext(FileObject); + if (Client == nullptr) { + Status = STATUS_DEVICE_NOT_READY; + CxplatTraceEvent( + LibraryError, + "[ lib] ERROR, %s.", + "CxplatTestCtlGetFileContext failed"); + goto Error; + } + + ULONG FunctionCode = IoGetFunctionCodeFromCtlCode(IoControlCode); + if (FunctionCode == 0 || FunctionCode > CXPLAT_MAX_IOCTL_FUNC_CODE) { + Status = STATUS_NOT_IMPLEMENTED; + CxplatTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + FunctionCode, + "Invalid FunctionCode"); + goto Error; + } + + if (InputBufferLength < CXPLAT_IOCTL_BUFFER_SIZES[FunctionCode]) { + Status = STATUS_INSUFFICIENT_RESOURCES; + CxplatTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + FunctionCode, + "Invalid buffer size for FunctionCode"); + goto Error; + } + + CXPLAT_IOCTL_PARAMS* Params = nullptr; + if (CXPLAT_IOCTL_BUFFER_SIZES[FunctionCode] != 0) { + Status = + WdfRequestRetrieveInputBuffer( + Request, + CXPLAT_IOCTL_BUFFER_SIZES[FunctionCode], + (void**)&Params, + nullptr); + if (!NT_SUCCESS(Status)) { + CxplatTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + Status, + "WdfRequestRetrieveInputBuffer failed"); + goto Error; + } else if (Params == nullptr) { + CxplatTraceEvent( + LibraryError, + "[ lib] ERROR, %s.", + "WdfRequestRetrieveInputBuffer failed to return parameter buffer"); + Status = STATUS_INVALID_PARAMETER; + goto Error; + } + } + + CxplatTraceLogInfo( + TestControlClientIoctl, + "[test] Client %p executing IOCTL %u", + Client, + FunctionCode); + + switch (IoControlCode) { + + case IOCTL_CXPLAT_RUN_DUMMY: + CxplatTestCtlRun(CxplatTestDummy()); + break; + + default: + Status = STATUS_NOT_IMPLEMENTED; + break; + } + +Error: + + CxplatTraceLogInfo( + TestControlClientIoctlComplete, + "[test] Client %p completing request, 0x%x", + Client, + Status); + + WdfRequestComplete(Request, Status); +} + +void +LogTestFailure( + _In_z_ const char *File, + _In_z_ const char *Function, + int Line, + _Printf_format_string_ const char *Format, + ... + ) +/*++ + +Routine Description: + + Records a test failure from the platform independent test code. + +Arguments: + + File - The file where the failure occurred. + + Function - The function where the failure occurred. + + Line - The line (in File) where the failure occurred. + +Return Value: + + None + +--*/ +{ + char Buffer[128]; + + UNREFERENCED_PARAMETER(File); + UNREFERENCED_PARAMETER(Function); + UNREFERENCED_PARAMETER(Line); + + NT_ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL); + CxplatTestClient->TestFailure = true; + + va_list Args; + va_start(Args, Format); + (void)_vsnprintf_s(Buffer, sizeof(Buffer), _TRUNCATE, Format, Args); + va_end(Args); + + CxplatTraceLogError( + TestDriverFailureLocation, + "[test] File: %s, Function: %s, Line: %d", + File, + Function, + Line); + CxplatTraceLogError( + TestDriverFailure, + "[test] FAIL: %s", + Buffer); + +#if CXPLAT_BREAK_TEST + NT_FRE_ASSERT(FALSE); +#endif +} diff --git a/src/test/bin/winkernel/cxplattest.kernel.vcxproj b/src/test/bin/winkernel/cxplattest.kernel.vcxproj new file mode 100644 index 0000000..25ba007 --- /dev/null +++ b/src/test/bin/winkernel/cxplattest.kernel.vcxproj @@ -0,0 +1,113 @@ + + + + + Debug + x64 + + + Release + x64 + + + Debug + ARM64 + + + Release + ARM64 + + + + + + + + + + + + {1e494654-9bfd-492f-bc31-36e2c73a782e} + + + {6DBEB7D2-4BCF-46B2-95CB-88DEF2F049D7} + + + + {435073CF-8546-47EB-B924-B12A17FC70EB} + {1EEBC5B0-39FD-40FB-B8DB-51E252A64B3C} + v4.5 + 12.0 + true + true + + + + Windows10 + WindowsKernelModeDriver10.0 + Driver + KMDF + <_NT_TARGET_VERSION>0x0A00000A + + + true + + + false + + + + + + + + + cxplattest + DbgengKernelDebugger + false + $(SolutionDir)artifacts\bin\winkernel\$(Platform)_$(Configuration)\ + $(SolutionDir)build\winkernel\$(Platform)_$(Configuration)\obj\$(ProjectName)\ + Off + + + + + + false + + + + ..;..\..;$(SolutionDir)build\winkernel\$(Platform)_$(Configuration)\inc;$(IntDir);%(AdditionalIncludeDirectories) + Speed + true + /Gw /kernel /ZH:SHA_256 + /Gw /kernel /ZH:SHA_256 -d2jumptablerdata -d2epilogunwindrequirev2 + + + $(SolutionDir)artifacts\bin\winkernel\$(Platform)_$(Configuration)\ + cng.lib;ksecdd.lib;wdmsec.lib;uuid.lib;cxplat.lib;%(AdditionalDependencies) + UseLinkTimeCodeGeneration + + + + + MultiThreadedDebugDLL + CX_PLATFORM_WINKERNEL;SECURITY_KERNEL;SECURITY_WIN32;_DEBUG;%(PreprocessorDefinitions) + + + + + CX_PLATFORM_WINKERNEL;SECURITY_KERNEL;SECURITY_WIN32;%(PreprocessorDefinitions) + + + + + + + true + true + + + + + diff --git a/src/test/bin/winkernel/driver.cpp b/src/test/bin/winkernel/driver.cpp new file mode 100644 index 0000000..294ced9 --- /dev/null +++ b/src/test/bin/winkernel/driver.cpp @@ -0,0 +1,169 @@ +/*++ + + Copyright (c) Microsoft Corporation. + Licensed under the MIT License. + +Abstract: + + CXPLAT Kernel Mode Test Driver + +--*/ + +#include +#include + +#include "CxplatTests.h" + +#include "cxplat_trace.h" + +#ifndef KRTL_INIT_SEGMENT +#define KRTL_INIT_SEGMENT "INIT" +#endif +#ifndef KRTL_PAGE_SEGMENT +#define KRTL_PAGE_SEGMENT "PAGE" +#endif +#ifndef KRTL_NONPAGED_SEGMENT +#define KRTL_NONPAGED_SEGMENT ".text" +#endif + +// Use on code in the INIT segment. (Code is discarded after DriverEntry returns.) +#define INITCODE __declspec(code_seg(KRTL_INIT_SEGMENT)) + +// Use on pageable functions. +#define PAGEDX __declspec(code_seg(KRTL_PAGE_SEGMENT)) + +#define CXPLAT_POOL_TEST 'sTxC' // CxTs + +EVT_WDF_DRIVER_UNLOAD CxplatTestDriverUnload; + +_No_competing_thread_ +INITCODE +NTSTATUS +CxplatTestCtlInitialize( + _In_ WDFDRIVER Driver + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +VOID +CxplatTestCtlUninitialize( + ); + +extern "C" +INITCODE +_Function_class_(DRIVER_INITIALIZE) +_IRQL_requires_same_ +_IRQL_requires_(PASSIVE_LEVEL) +NTSTATUS +DriverEntry( + _In_ PDRIVER_OBJECT DriverObject, + _In_ PUNICODE_STRING RegistryPath + ) +/*++ + +Routine Description: + + DriverEntry initializes the driver and is the first routine called by the + system after the driver is loaded. DriverEntry specifies the other entry + points in the function driver, such as EvtDevice and DriverUnload. + +Parameters Description: + + DriverObject - represents the instance of the function driver that is loaded + into memory. DriverEntry must initialize members of DriverObject before it + returns to the caller. DriverObject is allocated by the system before the + driver is loaded, and it is released by the system after the system unloads + the function driver from memory. + + RegistryPath - represents the driver specific path in the Registry. + The function driver can use the path to store driver related data between + reboots. The path does not store hardware instance specific data. + +Return Value: + + A success status as determined by NT_SUCCESS macro, if successful. + +--*/ +{ + NTSTATUS Status; + WDF_DRIVER_CONFIG Config; + WDFDRIVER Driver; + + // + // Create the WdfDriver Object + // + WDF_DRIVER_CONFIG_INIT(&Config, NULL); + Config.EvtDriverUnload = CxplatTestDriverUnload; + Config.DriverInitFlags = WdfDriverInitNonPnpDriver; + Config.DriverPoolTag = CXPLAT_POOL_TEST; + + Status = + WdfDriverCreate( + DriverObject, + RegistryPath, + WDF_NO_OBJECT_ATTRIBUTES, + &Config, + &Driver); + if (!NT_SUCCESS(Status)) { + CxplatTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + Status, + "WdfDriverCreate failed"); + goto Error; + } + + // + // Initialize the device control interface. + // + Status = CxplatTestCtlInitialize(Driver); + if (!NT_SUCCESS(Status)) { + CxplatTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + Status, + "CxplatTestCtlInitialize failed"); + goto Error; + } + + CxplatTestInitialize(); + + CxplatTraceLogInfo( + TestDriverStarted, + "[test] Started"); + +Error: + + return Status; +} + +_Function_class_(EVT_WDF_DRIVER_UNLOAD) +_IRQL_requires_same_ +_IRQL_requires_max_(PASSIVE_LEVEL) +void +CxplatTestDriverUnload( + _In_ WDFDRIVER Driver + ) +/*++ + +Routine Description: + + CxplatTestDriverUnload will clean up any resources that were allocated for + this driver. + +Arguments: + + Driver - Handle to a framework driver object created in DriverEntry + +--*/ +{ + UNREFERENCED_PARAMETER(Driver); + NT_ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL); + + CxplatTestUninitialize(); + + CxplatTestCtlUninitialize(); + + CxplatTraceLogInfo( + TestDriverStopped, + "[test] Stopped"); +} diff --git a/src/test/cxplat_trace.h b/src/test/cxplat_trace.h new file mode 100644 index 0000000..e7e7f16 --- /dev/null +++ b/src/test/cxplat_trace.h @@ -0,0 +1,8 @@ +#pragma once + +#define CxplatTraceLogError(Fmt, ...) +#define CxplatTraceLogWarning(Fmt, ...) +#define CxplatTraceLogInfo(Fmt, ...) +#define CxplatTraceLogVerbose(Fmt, ...) + +#define CxplatTraceEvent(Fmt, ...) diff --git a/src/test/lib/BasicTest.cpp b/src/test/lib/BasicTest.cpp new file mode 100644 index 0000000..f2591cc --- /dev/null +++ b/src/test/lib/BasicTest.cpp @@ -0,0 +1,27 @@ +/*++ + + Copyright (c) Microsoft Corporation. + Licensed under the MIT License. + +Abstract: + + Dummy test. + +--*/ + +#include "precomp.h" + +void CxplatTestInitialize() +{ + return; +} + +void CxplatTestUninitialize() +{ + return; +} + +void CxplatTestDummy() +{ + return; +} diff --git a/src/test/lib/CMakeLists.txt b/src/test/lib/CMakeLists.txt new file mode 100644 index 0000000..5c4c845 --- /dev/null +++ b/src/test/lib/CMakeLists.txt @@ -0,0 +1,14 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +set(SOURCES + BasicTest.cpp +) + +add_library(testlib STATIC ${SOURCES}) + +target_include_directories(testlib PRIVATE ${PROJECT_SOURCE_DIR}/src/test ${PROJECT_SOURCE_DIR}/inc) + +target_compile_definitions(testlib PRIVATE ${CXPLAT_COMMON_DEFINES}) + +set_property(TARGET testlib PROPERTY FOLDER "${CXPLAT_FOLDER_PREFIX}tests") diff --git a/src/test/lib/TestAbstractionLayer.h b/src/test/lib/TestAbstractionLayer.h new file mode 100644 index 0000000..d2e1bd9 --- /dev/null +++ b/src/test/lib/TestAbstractionLayer.h @@ -0,0 +1,51 @@ +/*++ + + Copyright (c) Microsoft Corporation. + Licensed under the MIT License. + +Abstract: + + Platform independent test abstraction layer. + +--*/ + +#include "CxplatTests.h" + +#define TEST_FAILURE(Format, ...) \ + LogTestFailure(__FILE__, __FUNCTION__, __LINE__, Format, ##__VA_ARGS__) + +#define TEST_EQUAL(__expected, __condition) { \ + if (__condition != __expected) { \ + TEST_FAILURE(#__condition " not equal to " #__expected); \ + return; \ + } \ +} + +#define TEST_NOT_EQUAL(__expected, __condition) { \ + if (__condition == __expected) { \ + TEST_FAILURE(#__condition " equals " #__expected); \ + return; \ + } \ +} + +#define TEST_TRUE(__condition) { \ + if (!(__condition)) { \ + TEST_FAILURE(#__condition " not true"); \ + return; \ + } \ +} + +#define TEST_FALSE(__condition) { \ + if (__condition) { \ + TEST_FAILURE(#__condition " not false"); \ + return; \ + } \ +} + +#define TEST_HRESULT(__condition) { \ + HRESULT __hr = __condition; \ + if (FAILED(__hr)) { \ + TEST_FAILURE(#__condition " failed, 0x%x", __hr); \ + return; \ + } \ +} diff --git a/src/test/lib/precomp.h b/src/test/lib/precomp.h new file mode 100644 index 0000000..5f4777f --- /dev/null +++ b/src/test/lib/precomp.h @@ -0,0 +1,49 @@ +/*++ + + Copyright (c) Microsoft Corporation. + Licensed under the MIT License. + +--*/ + +#if defined(CX_PLATFORM_WINKERNEL) +#include +#elif defined(CX_PLATFORM_WINUSER) +#include +#elif defined(CX_PLATFORM_LINUX) || defined(CX_PLATFORM_DARWIN) +// For FreeBSD +#if defined(__FreeBSD__) +#include +#include +#define ETIME ETIMEDOUT +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#else +#error "Unsupported Platform" +#endif + +#include "cxplat.h" + +#include "TestAbstractionLayer.h" + +#if defined(_ARM64_) || defined(_ARM64EC_) +#pragma optimize("", off) +#endif diff --git a/src/test/lib/testlib.kernel.vcxproj b/src/test/lib/testlib.kernel.vcxproj new file mode 100644 index 0000000..84cb83a --- /dev/null +++ b/src/test/lib/testlib.kernel.vcxproj @@ -0,0 +1,94 @@ + + + + + Debug + x64 + + + Release + x64 + + + Debug + ARM64 + + + Release + ARM64 + + + + + + + + + + + {6DBEB7D2-4BCF-46B2-95CB-88DEF2F049D7} + {1dc1438e-ecb5-4f28-b53e-b9409525591b} + v4.5 + 12.0 + KMDF + true + true + + + + Windows10 + WindowsKernelModeDriver10.0 + StaticLibrary + <_NT_TARGET_VERSION>0x0A00000A + + + true + + + false + + + + + + + + + testlib + $(SolutionDir)build\winkernel\$(Platform)_$(Configuration)\obj\$(ProjectName)\ + $(SolutionDir)build\winkernel\$(Platform)_$(Configuration)\bin\ + + + + + + false + + + + ..\;$(SolutionDir)inc;$(SolutionDir)build\winkernel\$(Platform)_$(Configuration)\inc;$(IntDir);%(AdditionalIncludeDirectories) + Speed + true + /Gw /kernel /ZH:SHA_256 + /Gw /kernel /ZH:SHA_256 -d2jumptablerdata -d2epilogunwindrequirev2 + + + true + + + + + MultiThreadedDebugDLL + CX_PLATFORM_WINKERNEL;SECURITY_KERNEL;SECURITY_WIN32;_DEBUG;%(PreprocessorDefinitions) + true + + + + + CX_PLATFORM_WINKERNEL;SECURITY_KERNEL;SECURITY_WIN32;%(PreprocessorDefinitions) + true + + + + + diff --git a/submodules/googletest b/submodules/googletest new file mode 160000 index 0000000..5a37b51 --- /dev/null +++ b/submodules/googletest @@ -0,0 +1 @@ +Subproject commit 5a37b517ad4ab6738556f0284c256cae1466c5b4