diff --git a/.gitignore b/.gitignore index 3973a16b..f2c94199 100644 --- a/.gitignore +++ b/.gitignore @@ -334,3 +334,4 @@ ASALocalRun/ # CMake/Build output build/ +/CMakeSettings.json diff --git a/include/wil/toolhelp32.h b/include/wil/toolhelp32.h new file mode 100644 index 00000000..e5b0db9f --- /dev/null +++ b/include/wil/toolhelp32.h @@ -0,0 +1,337 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. +// +//********************************************************* +#ifndef __WIL_TOOLHELP32_INCLUDED +#define __WIL_TOOLHELP32_INCLUDED +#include +#include +#include "resource.h" +namespace wil +{ +namespace details +{ + template + auto do_enum_snapshot(HANDLE handle, TEntry& entry, TEnumApi&& enumApiFirst, TEnumApi&& enumApiNext, TCallback&& callback) + { + if (handle == INVALID_HANDLE_VALUE) + return E_HANDLE; + + using result_t = decltype(callback(TEntry{})); + bool enumResult = enumApiFirst(handle, &entry); + if (!enumResult) + return E_ABORT; + + do + { + if constexpr (wistd::is_void_v) + { + callback(entry); + } + else if constexpr (wistd::is_same_v) + { + if (callback(entry)) + return S_OK; + } + else + { + static_assert( + [] { + return false; + }(), + "Callback must return void or bool"); + } + enumResult = enumApiNext(handle, &entry); + } while (enumResult); + return S_OK; + } +} // namespace details + +#pragma region Process +template +auto for_each_process_nothrow(TCallback&& callback) +{ + PROCESSENTRY32 entry{}; + entry.dwSize = sizeof(entry); + return details::do_enum_snapshot( + unique_handle{CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)}.get(), + entry, + &Process32First, + &Process32Next, + wistd::forward(callback)); +} + +template +void for_each_process_failfast(TCallback&& callback) +{ + FAIL_FAST_IF_FAILED(for_each_process_nothrow(callback)); +} + +#ifdef WIL_ENABLE_EXCEPTIONS +template +void for_each_process(TCallback&& callback) +{ + THROW_IF_FAILED(for_each_process_nothrow(callback)); +} +#endif +#pragma endregion + +#pragma region Thread +template +auto for_each_system_thread_nothrow(TCallback&& callback) +{ + THREADENTRY32 entry{}; + entry.dwSize = sizeof(entry); + return details::do_enum_snapshot( + unique_handle{CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0)}.get(), + entry, + &Thread32First, + &Thread32Next, + wistd::forward(callback)); +} + +template +void for_each_system_thread_failfast(TCallback&& callback) +{ + FAIL_FAST_IF_FAILED(for_each_system_thread_nothrow(callback)); +} + +template +auto for_each_process_thread_nothrow(DWORD pid, TCallback&& callback) +{ + return for_each_system_thread_nothrow([&](THREADENTRY32 const& entry) { + if (entry.th32OwnerProcessID == pid) + callback(entry); + }); +} + +template +auto for_each_process_thread_nothrow(TCallback&& callback) +{ + return for_each_process_thread_nothrow(GetCurrentProcessId(), callback); +} + +template +void for_each_process_thread_failfast(DWORD pid, TCallback&& callback) +{ + FAIL_FAST_IF_FAILED(for_each_process_thread_nothrow(pid, callback)); +} + +template +void for_each_process_thread_failfast(TCallback&& callback) +{ + FAIL_FAST_IF_FAILED(for_each_process_thread_nothrow(callback)); +} + +#ifdef WIL_ENABLE_EXCEPTIONS +template +void for_each_system_thread(TCallback&& callback) +{ + THROW_IF_FAILED(for_each_system_thread_nothrow(callback)); +} + +template +void for_each_process_thread(DWORD pid, TCallback&& callback) +{ + THROW_IF_FAILED(for_each_process_thread_nothrow(pid, callback)); +} + +template +void for_each_process_thread(TCallback&& callback) +{ + THROW_IF_FAILED(for_each_process_thread_nothrow(callback)); +} + +#endif +#pragma endregion + +#pragma region Module +template +auto for_each_module_nothrow(DWORD pid, bool include32BitModule, TCallback&& callback) +{ + MODULEENTRY32 entry{}; + entry.dwSize = sizeof(entry); + return details::do_enum_snapshot( + unique_handle{CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | (include32BitModule ? TH32CS_SNAPMODULE32 : 0), pid)}.get(), + entry, + &Module32First, + &Module32Next, + wistd::forward(callback)); +} + +template +auto for_each_module_nothrow(bool include32BitModule, TCallback&& callback) +{ + return for_each_module_nothrow(0, include32BitModule, callback); +} + +template +auto for_each_module_nothrow(TCallback&& callback) +{ + return for_each_module_nothrow(true, callback); +} + +template +void for_each_module_failfast(DWORD pid, bool include32BitModule, TCallback&& callback) +{ + FAIL_FAST_IF_FAILED(for_each_module_nothrow(pid, include32BitModule, callback)); +} + +template +void for_each_module_failfast(bool include32BitModule, TCallback&& callback) +{ + FAIL_FAST_IF_FAILED(for_each_module_nothrow(0, include32BitModule, callback)); +} + +template +void for_each_module_failfast(TCallback&& callback) +{ + FAIL_FAST_IF_FAILED(for_each_module_nothrow(callback)); +} + +#ifdef WIL_ENABLE_EXCEPTIONS +template +void for_each_module(DWORD pid, bool include32BitModule, TCallback&& callback) +{ + THROW_IF_FAILED(for_each_module_nothrow(pid, include32BitModule, callback)); +} + +template +void for_each_module(bool include32BitModule, TCallback&& callback) +{ + THROW_IF_FAILED(for_each_module_nothrow(0, include32BitModule, callback)); +} + +template +void for_each_module(TCallback&& callback) +{ + THROW_IF_FAILED(for_each_module_nothrow(callback)); +} +#endif +#pragma endregion + +#pragma region HeapList +template +auto for_each_heap_list_nothrow(DWORD pid, TCallback&& callback) +{ + HEAPLIST32 entry{}; + entry.dwSize = sizeof(entry); + return details::do_enum_snapshot( + unique_handle{CreateToolhelp32Snapshot(TH32CS_SNAPHEAPLIST, pid)}.get(), + entry, + &Heap32ListFirst, + &Heap32ListNext, + wistd::forward(callback)); +} + +template +auto for_each_heap_list_nothrow(TCallback&& callback) +{ + return for_each_heap_list_nothrow(0, callback); +} + +template +void for_each_heap_list_failfast(DWORD pid, TCallback&& callback) +{ + FAIL_FAST_IF_FAILED(for_each_heap_list_nothrow(pid, callback)); +} + +template +void for_each_heap_list_failfast(TCallback&& callback) +{ + FAIL_FAST_IF_FAILED(for_each_heap_list_nothrow(callback)); +} + +#ifdef WIL_ENABLE_EXCEPTIONS +template +void for_each_heap_list(DWORD pid, TCallback&& callback) +{ + THROW_IF_FAILED(for_each_heap_list_nothrow(pid, callback)); +} + +template +void for_each_heap_list(TCallback&& callback) +{ + THROW_IF_FAILED(for_each_heap_list_nothrow(callback)); +} +#endif +#pragma endregion + +#pragma region Heap +template +auto for_each_heap_nothrow(DWORD pid, ULONG_PTR heapId, TCallback&& callback) +{ + using result_t = decltype(callback(HEAPENTRY32{})); + + HEAPENTRY32 entry{}; + entry.dwSize = sizeof(entry); + + if (!Heap32First(&entry, pid, heapId)) + return E_ABORT; + + bool enumResult = true; + do + { + if constexpr (wistd::is_void_v) + { + callback(entry); + } + else if constexpr (wistd::is_same_v) + { + if (callback(entry)) + return S_OK; + } + else + { + static_assert( + [] { + return false; + }(), + "Callback must return void or bool"); + } + enumResult = Heap32Next(&entry); + } while (enumResult); + return S_OK; +} + +template +auto for_each_heap_nothrow(ULONG_PTR heapId, TCallback&& callback) +{ + return for_each_heap_nothrow(GetCurrentProcessId(), heapId, callback); +} + +template +void for_each_heap_failfast(DWORD pid, ULONG_PTR heapId, TCallback&& callback) +{ + FAIL_FAST_IF_FAILED(for_each_heap_nothrow(pid, heapId, callback)); +} + +template +void for_each_heap_failfast(ULONG_PTR heapId, TCallback&& callback) +{ + FAIL_FAST_IF_FAILED(for_each_heap_nothrow(heapId, callback)); +} + +#ifdef WIL_ENABLE_EXCEPTIONS +template +void for_each_heap(DWORD pid, ULONG_PTR heapId, TCallback&& callback) +{ + THROW_IF_FAILED(for_each_heap_nothrow(pid, heapId, callback)); +} + +template +void for_each_heap(ULONG_PTR heapId, TCallback&& callback) +{ + THROW_IF_FAILED(for_each_heap_nothrow(heapId, callback)); +} +#endif +#pragma endregion +} // namespace wil + +#endif \ No newline at end of file diff --git a/tests/Toolhelp32Tests.cpp b/tests/Toolhelp32Tests.cpp new file mode 100644 index 00000000..2a56545d --- /dev/null +++ b/tests/Toolhelp32Tests.cpp @@ -0,0 +1,46 @@ +#include "common.h" +#include +#include +#include + +TEST_CASE("Toolhelp32", "[EnumProcesses]") +{ + wil::for_each_process([](PROCESSENTRY32 const& entry) { + REQUIRE_FALSE(std::strlen(entry.szExeFile) == 0); + }); +} + +TEST_CASE("Toolhelp32", "[EnumModules]") +{ + wil::for_each_module([](MODULEENTRY32 const& entry) { + REQUIRE_FALSE(std::strlen(entry.szExePath) == 0); + }); +} + +TEST_CASE("Toolhelp32", "[EnumThreads]") +{ + wil::for_each_system_thread([pid = GetCurrentProcessId()](THREADENTRY32 const& entry) { + if (entry.th32OwnerProcessID == pid) + { + REQUIRE_FALSE(entry.th32ThreadID == 0); + } + }); +} + +TEST_CASE("Toolhelp32", "[EnumHeapLists]") +{ + wil::for_each_heap_list([](HEAPLIST32 const& entry) { + REQUIRE_FALSE(entry.th32HeapID == 0); + }); +} + +TEST_CASE("Toolhelp32", "[EnumHeap]") +{ + wil::for_each_heap_list([](HEAPLIST32 const& heapListEntry) { + REQUIRE_FALSE(heapListEntry.th32HeapID == 0); + wil::for_each_heap(heapListEntry.th32HeapID, [](HEAPENTRY32 const& heapEntry) { + REQUIRE_FALSE(heapEntry.dwAddress == 0); + }); + return false; + }); +} \ No newline at end of file diff --git a/tests/cpplatest/CMakeLists.txt b/tests/cpplatest/CMakeLists.txt index ae22fc9f..7ae95556 100644 --- a/tests/cpplatest/CMakeLists.txt +++ b/tests/cpplatest/CMakeLists.txt @@ -30,5 +30,6 @@ target_sources(witest.cpplatest PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../WatcherTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../WinRTTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../WinVerifyTrustTest.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../Toolhelp32Tests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/app.manifest ) diff --git a/tests/normal/CMakeLists.txt b/tests/normal/CMakeLists.txt index 7b651c2f..637b5228 100644 --- a/tests/normal/CMakeLists.txt +++ b/tests/normal/CMakeLists.txt @@ -12,4 +12,5 @@ target_sources(witest PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../WatcherTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../WinRTTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../WinVerifyTrustTest.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../Toolhelp32Tests.cpp ) diff --git a/tests/win7/CMakeLists.txt b/tests/win7/CMakeLists.txt index 15445f9f..48fcc684 100644 --- a/tests/win7/CMakeLists.txt +++ b/tests/win7/CMakeLists.txt @@ -13,4 +13,5 @@ target_sources(witest.win7 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../StlTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../TokenHelpersTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../WatcherTests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../Toolhelp32Tests.cpp )