More ntdll impls; fix 64-bit Clang assembly

This commit is contained in:
2025-11-08 01:22:11 -07:00
parent 3dd9fb77ff
commit f56bd8e2a7
11 changed files with 896 additions and 22 deletions

View File

@@ -390,11 +390,14 @@ if (WIBO_ENABLE_FIXTURE_TESTS)
wibo_add_fixture_bin(NAME test_actctx SOURCES test/test_actctx.c)
wibo_add_fixture_bin(NAME test_overlapped_io SOURCES test/test_overlapped_io.c)
wibo_add_fixture_bin(NAME test_time SOURCES test/test_time.c)
wibo_add_fixture_bin(NAME test_ntdll_time SOURCES test/test_ntdll_time.c)
wibo_add_fixture_bin(NAME test_virtualalloc SOURCES test/test_virtualalloc.c)
wibo_add_fixture_bin(NAME test_virtualquery SOURCES test/test_virtualquery.c)
wibo_add_fixture_bin(NAME test_clsids SOURCES test/test_clsids.c COMPILE_OPTIONS -lole32)
wibo_add_fixture_bin(NAME test_rtl SOURCES test/test_rtl.c)
wibo_add_fixture_bin(NAME test_rtl_bitmap SOURCES test/test_rtl_bitmap.c)
wibo_add_fixture_bin(NAME test_ntquery SOURCES test/test_ntquery.c)
wibo_add_fixture_bin(NAME test_ntqueryfile SOURCES test/test_ntqueryfile.c)
wibo_add_fixture_bin(NAME test_ntreadfile SOURCES test/test_ntreadfile.c)
wibo_add_fixture_bin(NAME test_ntwritefile SOURCES test/test_ntwritefile.c)
wibo_add_fixture_bin(NAME test_pipe_io SOURCES test/test_pipe_io.c)

View File

@@ -22,7 +22,7 @@ struct FsObject : ObjectBase {
[[nodiscard]] bool valid() const { return fd >= 0; }
protected:
explicit FsObject(ObjectType type, int fd) : ObjectBase(type), fd(fd) {}
explicit FsObject(ObjectType type, int fd) : ObjectBase(type), fd(fd) { flags |= Of_FsObject; }
};
struct FileObject : FsObject {
@@ -163,13 +163,9 @@ struct HeapObject : public ObjectBase {
inline constexpr HANDLE kPseudoCurrentProcessHandleValue = static_cast<HANDLE>(-1);
inline constexpr HANDLE kPseudoCurrentThreadHandleValue = static_cast<HANDLE>(-2);
inline bool isPseudoCurrentProcessHandle(HANDLE h) {
return h == kPseudoCurrentProcessHandleValue;
}
inline bool isPseudoCurrentProcessHandle(HANDLE h) { return h == kPseudoCurrentProcessHandleValue; }
inline bool isPseudoCurrentThreadHandle(HANDLE h) {
return h == kPseudoCurrentThreadHandleValue;
}
inline bool isPseudoCurrentThreadHandle(HANDLE h) { return h == kPseudoCurrentThreadHandleValue; }
void tryMarkExecutable(void *mem);
void setLastErrorFromErrno();
@@ -182,6 +178,10 @@ void setLastError(DWORD error);
namespace detail {
template <> constexpr bool typeMatches<kernel32::FsObject>(const ObjectBase *o) noexcept {
return o && (o->flags & Of_FsObject);
}
template <> constexpr bool typeMatches<kernel32::FileObject>(const ObjectBase *o) noexcept {
return o && (o->flags & Of_File);
}

View File

@@ -6,7 +6,9 @@
#include "files.h"
#include "handles.h"
#include "heap.h"
#include "kernel32/fileapi.h"
#include "kernel32/internal.h"
#include "kernel32/minwinbase.h"
#include "kernel32/processthreadsapi.h"
#include "modules.h"
#include "processes.h"
@@ -14,7 +16,9 @@
#include "types.h"
#include <cerrno>
#include <chrono>
#include <cstring>
#include <limits>
#include <sys/stat.h>
#include <unistd.h>
@@ -56,6 +60,93 @@ constexpr ULONG kOsBuildNumber = 0;
constexpr ULONG kOsPlatformId = 2; // VER_PLATFORM_WIN32_NT
constexpr BYTE kProductTypeWorkstation = 1; // VER_NT_WORKSTATION
constexpr ULONGLONG kHundredNanosecondsPerSecond = 10'000'000ULL;
constexpr ULONGLONG kUnixEpochAsFileTime = 116'444'736'000'000'000ULL;
struct StatFetchResult {
bool ok = false;
int err = 0;
};
#if defined(__APPLE__)
timespec accessTimespec(const struct stat &st) { return st.st_atimespec; }
timespec modifyTimespec(const struct stat &st) { return st.st_mtimespec; }
timespec changeTimespec(const struct stat &st) { return st.st_ctimespec; }
#elif defined(__linux__)
timespec accessTimespec(const struct stat &st) { return st.st_atim; }
timespec modifyTimespec(const struct stat &st) { return st.st_mtim; }
timespec changeTimespec(const struct stat &st) { return st.st_ctim; }
#else
timespec accessTimespec(const struct stat &st) { return timespec{.tv_sec = st.st_atime, .tv_nsec = 0}; }
timespec modifyTimespec(const struct stat &st) { return timespec{.tv_sec = st.st_mtime, .tv_nsec = 0}; }
timespec changeTimespec(const struct stat &st) { return timespec{.tv_sec = st.st_ctime, .tv_nsec = 0}; }
#endif
LONGLONG timespecToFileTime(const timespec &ts) {
#if defined(__SIZEOF_INT128__)
__int128 ticks = static_cast<__int128>(ts.tv_sec) * static_cast<__int128>(kHundredNanosecondsPerSecond);
ticks += static_cast<__int128>(ts.tv_nsec / 100);
ticks += static_cast<__int128>(kUnixEpochAsFileTime);
if (ticks < 0) {
return 0;
}
if (ticks > static_cast<__int128>(std::numeric_limits<LONGLONG>::max())) {
return std::numeric_limits<LONGLONG>::max();
}
return static_cast<LONGLONG>(ticks);
#else
long double ticks = static_cast<long double>(ts.tv_sec) * static_cast<long double>(kHundredNanosecondsPerSecond);
ticks += static_cast<long double>(ts.tv_nsec) / 100.0L;
ticks += static_cast<long double>(kUnixEpochAsFileTime);
if (ticks < 0.0L) {
return 0;
}
if (ticks > static_cast<long double>(std::numeric_limits<LONGLONG>::max())) {
return std::numeric_limits<LONGLONG>::max();
}
return static_cast<LONGLONG>(ticks);
#endif
}
DWORD buildFileAttributes(const struct stat &st) {
DWORD attributes = 0;
mode_t mode = st.st_mode;
if (S_ISDIR(mode)) {
attributes |= FILE_ATTRIBUTE_DIRECTORY;
}
if (S_ISREG(mode)) {
attributes |= FILE_ATTRIBUTE_ARCHIVE;
}
if ((mode & S_IWUSR) == 0) {
attributes |= FILE_ATTRIBUTE_READONLY;
}
if (attributes == 0) {
attributes = FILE_ATTRIBUTE_NORMAL;
}
return attributes;
}
StatFetchResult fetchStat(kernel32::FsObject *fs, struct stat &st) {
if (!fs) {
return {};
}
if (fs->valid()) {
if (fstat(fs->fd, &st) == 0) {
return StatFetchResult{.ok = true, .err = 0};
}
if (errno != EBADF) {
return StatFetchResult{.ok = false, .err = errno};
}
}
if (!fs->canonicalPath.empty()) {
if (stat(fs->canonicalPath.c_str(), &st) == 0) {
return StatFetchResult{.ok = true, .err = 0};
}
return StatFetchResult{.ok = false, .err = errno};
}
return StatFetchResult{};
}
bool resolveProcessDetails(HANDLE processHandle, ProcessHandleDetails &details) {
if (kernel32::isPseudoCurrentProcessHandle(processHandle)) {
details.pid = getpid();
@@ -262,7 +353,7 @@ NTSTATUS WINAPI NtWriteFile(HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE Apc
return status;
}
NTSTATUS WINAPI NtAllocateVirtualMemory(HANDLE ProcessHandle, GUEST_PTR *BaseAddress, ULONG_PTR ZeroBits,
NTSTATUS WINAPI NtAllocateVirtualMemory(HANDLE ProcessHandle, guest_ptr<> *BaseAddress, ULONG_PTR ZeroBits,
PSIZE_T RegionSize, ULONG AllocationType, ULONG Protect) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("NtAllocateVirtualMemory(%p, %p, %lu, %p, %lu, %lu) ", ProcessHandle, BaseAddress, ZeroBits, RegionSize,
@@ -276,7 +367,7 @@ NTSTATUS WINAPI NtAllocateVirtualMemory(HANDLE ProcessHandle, GUEST_PTR *BaseAdd
return STATUS_INVALID_PARAMETER;
}
void *baseAddress = fromGuestPtr(*BaseAddress);
void *baseAddress = BaseAddress->get();
size_t regionSize = static_cast<size_t>(*RegionSize);
wibo::heap::VmStatus vmStatus = wibo::heap::virtualAlloc(
&baseAddress, &regionSize, static_cast<DWORD>(AllocationType), static_cast<DWORD>(Protect));
@@ -286,14 +377,14 @@ NTSTATUS WINAPI NtAllocateVirtualMemory(HANDLE ProcessHandle, GUEST_PTR *BaseAdd
return status;
}
*BaseAddress = toGuestPtr(baseAddress);
*BaseAddress = baseAddress;
*RegionSize = static_cast<SIZE_T>(regionSize);
DEBUG_LOG("-> 0x%x\n", STATUS_SUCCESS);
return STATUS_SUCCESS;
}
NTSTATUS WINAPI NtProtectVirtualMemory(HANDLE ProcessHandle, GUEST_PTR *BaseAddress, PSIZE_T NumberOfBytesToProtect,
NTSTATUS WINAPI NtProtectVirtualMemory(HANDLE ProcessHandle, guest_ptr<> *BaseAddress, PSIZE_T NumberOfBytesToProtect,
ULONG NewAccessProtection, PULONG OldAccessProtection) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("NtProtectVirtualMemory(%p, %p, %p, %lu, %p) ", ProcessHandle, BaseAddress, NumberOfBytesToProtect,
@@ -307,7 +398,7 @@ NTSTATUS WINAPI NtProtectVirtualMemory(HANDLE ProcessHandle, GUEST_PTR *BaseAddr
return STATUS_INVALID_PARAMETER;
}
void *base = fromGuestPtr(*BaseAddress);
void *base = BaseAddress->get();
size_t length = static_cast<size_t>(*NumberOfBytesToProtect);
wibo::heap::VmStatus vmStatus =
wibo::heap::virtualProtect(base, length, static_cast<DWORD>(NewAccessProtection), OldAccessProtection);
@@ -321,6 +412,290 @@ NTSTATUS WINAPI NtProtectVirtualMemory(HANDLE ProcessHandle, GUEST_PTR *BaseAddr
return STATUS_SUCCESS;
}
NTSTATUS WINAPI NtQueryInformationFile(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID FileInformation,
ULONG Length, FILE_INFORMATION_CLASS FileInformationClass) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("NtQueryInformationFile(%p, %p, %p, %u, %u) ", FileHandle, IoStatusBlock, FileInformation, Length,
static_cast<unsigned>(FileInformationClass));
if (!IoStatusBlock) {
DEBUG_LOG("-> 0x%x\n", STATUS_ACCESS_VIOLATION);
return STATUS_ACCESS_VIOLATION;
}
IoStatusBlock->Information = 0;
if (Length != 0 && !FileInformation) {
IoStatusBlock->Status = STATUS_ACCESS_VIOLATION;
DEBUG_LOG("-> 0x%x\n", STATUS_ACCESS_VIOLATION);
return STATUS_ACCESS_VIOLATION;
}
if (reinterpret_cast<int32_t>(FileHandle) < 0) {
IoStatusBlock->Status = STATUS_OBJECT_TYPE_MISMATCH;
DEBUG_LOG("-> 0x%x\n", STATUS_OBJECT_TYPE_MISMATCH);
return STATUS_OBJECT_TYPE_MISMATCH;
}
auto obj = wibo::handles().getAs<kernel32::FsObject>(FileHandle);
if (!obj || !obj->valid()) {
IoStatusBlock->Status = STATUS_INVALID_HANDLE;
DEBUG_LOG("-> 0x%x\n", STATUS_INVALID_HANDLE);
return STATUS_INVALID_HANDLE;
}
std::lock_guard lock(obj->m);
NTSTATUS status = STATUS_SUCCESS;
switch (FileInformationClass) {
case FileBasicInformation: {
if (Length < sizeof(FILE_BASIC_INFORMATION)) {
status = STATUS_INFO_LENGTH_MISMATCH;
break;
}
struct stat st{};
StatFetchResult statRes = fetchStat(obj.get(), st);
if (!statRes.ok) {
status = wibo::statusFromErrno(statRes.err != 0 ? statRes.err : EINVAL);
break;
}
auto info = reinterpret_cast<PFILE_BASIC_INFORMATION>(FileInformation);
info->CreationTime.QuadPart = timespecToFileTime(changeTimespec(st));
info->LastAccessTime.QuadPart = timespecToFileTime(accessTimespec(st));
info->LastWriteTime.QuadPart = timespecToFileTime(modifyTimespec(st));
info->ChangeTime.QuadPart = timespecToFileTime(changeTimespec(st));
info->FileAttributes = buildFileAttributes(st);
IoStatusBlock->Information = sizeof(FILE_BASIC_INFORMATION);
break;
}
case FileStandardInformation: {
if (Length < sizeof(FILE_STANDARD_INFORMATION)) {
status = STATUS_INFO_LENGTH_MISMATCH;
break;
}
struct stat st{};
StatFetchResult statRes = fetchStat(obj.get(), st);
if (!statRes.ok) {
status = wibo::statusFromErrno(statRes.err != 0 ? statRes.err : EINVAL);
break;
}
auto info = reinterpret_cast<PFILE_STANDARD_INFORMATION>(FileInformation);
unsigned long long allocation = static_cast<unsigned long long>(st.st_blocks) * 512ULL;
info->AllocationSize.QuadPart = static_cast<LONGLONG>(allocation);
info->EndOfFile.QuadPart = static_cast<LONGLONG>(st.st_size);
info->NumberOfLinks = static_cast<ULONG>(st.st_nlink);
info->DeletePending = obj->deletePending ? TRUE : FALSE;
info->Directory = S_ISDIR(st.st_mode) ? TRUE : FALSE;
info->Reserved = 0;
IoStatusBlock->Information = sizeof(FILE_STANDARD_INFORMATION);
break;
}
case FilePositionInformation: {
auto file = std::move(obj).downcast<kernel32::FileObject>();
if (!file) {
status = STATUS_INVALID_PARAMETER;
break;
}
if (Length < sizeof(FILE_POSITION_INFORMATION)) {
status = STATUS_INFO_LENGTH_MISMATCH;
break;
}
auto info = reinterpret_cast<PFILE_POSITION_INFORMATION>(FileInformation);
info->CurrentByteOffset.QuadPart = static_cast<LONGLONG>(file->filePos);
IoStatusBlock->Information = sizeof(FILE_POSITION_INFORMATION);
break;
}
case FileNameInformation: {
if (Length < sizeof(ULONG)) {
status = STATUS_INFO_LENGTH_MISMATCH;
break;
}
std::string windowsPath;
if (!obj->canonicalPath.empty()) {
windowsPath = files::pathToWindows(obj->canonicalPath);
}
std::string volumeRelative;
if (!windowsPath.empty()) {
if (windowsPath.size() >= 2 && windowsPath[1] == ':') {
volumeRelative = windowsPath.substr(2);
if (volumeRelative.empty() || volumeRelative.front() != '\\') {
volumeRelative.insert(volumeRelative.begin(), '\\');
}
} else if (!windowsPath.empty() && windowsPath.front() != '\\') {
volumeRelative = "\\" + windowsPath;
} else {
volumeRelative = windowsPath;
}
}
auto info = reinterpret_cast<PFILE_NAME_INFORMATION>(FileInformation);
auto wide = stringToWideString(volumeRelative.c_str(), volumeRelative.size());
size_t charCount = wide.empty() ? 0 : wstrlen(wide.data());
size_t bytesRequired = charCount * sizeof(uint16_t);
if (Length < sizeof(ULONG) + bytesRequired) {
info->FileNameLength = static_cast<ULONG>(bytesRequired);
status = STATUS_INFO_LENGTH_MISMATCH;
break;
}
info->FileNameLength = static_cast<ULONG>(bytesRequired);
if (bytesRequired > 0) {
std::memcpy(info->FileName, wide.data(), bytesRequired);
}
IoStatusBlock->Information = static_cast<ULONG>(sizeof(ULONG) + bytesRequired);
break;
}
default:
DEBUG_LOG("FIXME: NtQueryInformationFile: Unsupported info class");
status = STATUS_INVALID_INFO_CLASS;
break;
}
IoStatusBlock->Status = status;
DEBUG_LOG("-> 0x%x\n", status);
return status;
}
NTSTATUS WINAPI NtQuerySystemTime(PLARGE_INTEGER SystemTime) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("NtQuerySystemTime(%p) ", SystemTime);
if (!SystemTime) {
DEBUG_LOG("-> 0x%x\n", STATUS_ACCESS_VIOLATION);
return STATUS_ACCESS_VIOLATION;
}
using HundredNanoseconds = std::chrono::duration<long long, std::ratio<1, 10000000>>;
auto now = std::chrono::system_clock::now().time_since_epoch();
auto sinceUnix = std::chrono::duration_cast<HundredNanoseconds>(now).count();
ULONGLONG fileTime = kUnixEpochAsFileTime + static_cast<ULONGLONG>(sinceUnix);
SystemTime->QuadPart = static_cast<LONGLONG>(fileTime);
DEBUG_LOG("-> 0x%x\n", STATUS_SUCCESS);
return STATUS_SUCCESS;
}
BOOLEAN WINAPI RtlTimeToSecondsSince1970(PLARGE_INTEGER Time, PULONG ElapsedSeconds) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("RtlTimeToSecondsSince1970(%p, %p) ", Time, ElapsedSeconds);
if (!Time || !ElapsedSeconds) {
DEBUG_LOG("-> %u\n", FALSE);
return FALSE;
}
LONGLONG fileTimeSigned = Time->QuadPart;
if (fileTimeSigned < 0) {
DEBUG_LOG("-> %u\n", FALSE);
return FALSE;
}
ULONGLONG fileTime = static_cast<ULONGLONG>(fileTimeSigned);
if (fileTime < kUnixEpochAsFileTime) {
DEBUG_LOG("-> %u\n", FALSE);
return FALSE;
}
ULONGLONG delta = fileTime - kUnixEpochAsFileTime;
ULONGLONG seconds = delta / kHundredNanosecondsPerSecond;
if (seconds > 0xFFFFFFFFULL) {
DEBUG_LOG("-> %u\n", FALSE);
return FALSE;
}
*ElapsedSeconds = static_cast<ULONG>(seconds);
DEBUG_LOG("-> %u\n", TRUE);
return TRUE;
}
VOID WINAPI RtlInitializeBitMap(PRTL_BITMAP BitMapHeader, PULONG BitMapBuffer, ULONG SizeOfBitMap) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("RtlInitializeBitMap(%p, %p, %u)\n", BitMapHeader, BitMapBuffer, SizeOfBitMap);
if (!BitMapHeader) {
return;
}
BitMapHeader->SizeOfBitMap = SizeOfBitMap;
BitMapHeader->Buffer = BitMapBuffer;
}
VOID WINAPI RtlSetBits(PRTL_BITMAP BitMapHeader, ULONG StartingIndex, ULONG NumberToSet) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("RtlSetBits(%p, %u, %u)\n", BitMapHeader, StartingIndex, NumberToSet);
if (!BitMapHeader || !BitMapHeader->Buffer || NumberToSet == 0) {
return;
}
ULONG size = BitMapHeader->SizeOfBitMap;
if (StartingIndex >= size) {
return;
}
ULONG available = size - StartingIndex;
if (NumberToSet > available) {
NumberToSet = available;
}
for (ULONG i = 0; i < NumberToSet; ++i) {
ULONG bitIndex = StartingIndex + i;
ULONG wordIndex = bitIndex / 32;
ULONG offset = bitIndex % 32;
BitMapHeader->Buffer[wordIndex] |= (1u << offset);
}
}
BOOLEAN WINAPI RtlAreBitsSet(PRTL_BITMAP BitMapHeader, ULONG StartingIndex, ULONG Length) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("RtlAreBitsSet(%p, %u, %u) ", BitMapHeader, StartingIndex, Length);
if (!BitMapHeader || !BitMapHeader->Buffer || Length == 0) {
DEBUG_LOG("-> %u\n", FALSE);
return FALSE;
}
ULONG size = BitMapHeader->SizeOfBitMap;
if (StartingIndex >= size || Length > size - StartingIndex) {
DEBUG_LOG("-> %u\n", FALSE);
return FALSE;
}
for (ULONG i = 0; i < Length; ++i) {
ULONG bitIndex = StartingIndex + i;
ULONG wordIndex = bitIndex / 32;
ULONG offset = bitIndex % 32;
if ((BitMapHeader->Buffer[wordIndex] & (1u << offset)) == 0) {
DEBUG_LOG("-> %u\n", FALSE);
return FALSE;
}
}
DEBUG_LOG("-> %u\n", TRUE);
return TRUE;
}
BOOLEAN WINAPI RtlAreBitsClear(PRTL_BITMAP BitMapHeader, ULONG StartingIndex, ULONG Length) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("RtlAreBitsClear(%p, %u, %u) ", BitMapHeader, StartingIndex, Length);
if (!BitMapHeader || !BitMapHeader->Buffer || Length == 0) {
DEBUG_LOG("-> %u\n", FALSE);
return FALSE;
}
ULONG size = BitMapHeader->SizeOfBitMap;
if (StartingIndex >= size || Length > size - StartingIndex) {
DEBUG_LOG("-> %u\n", FALSE);
return FALSE;
}
for (ULONG i = 0; i < Length; ++i) {
ULONG bitIndex = StartingIndex + i;
ULONG wordIndex = bitIndex / 32;
ULONG offset = bitIndex % 32;
if ((BitMapHeader->Buffer[wordIndex] & (1u << offset)) != 0) {
DEBUG_LOG("-> %u\n", FALSE);
return FALSE;
}
}
DEBUG_LOG("-> %u\n", TRUE);
return TRUE;
}
NTSTATUS WINAPI RtlGetVersion(PRTL_OSVERSIONINFOW lpVersionInformation) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("RtlGetVersion(%p) ", lpVersionInformation);

View File

@@ -38,10 +38,18 @@ NTSTATUS WINAPI NtReadFile(HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcR
NTSTATUS WINAPI NtWriteFile(HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext,
PIO_STATUS_BLOCK IoStatusBlock, PVOID Buffer, ULONG Length, PLARGE_INTEGER ByteOffset,
PULONG Key);
NTSTATUS WINAPI NtAllocateVirtualMemory(HANDLE ProcessHandle, GUEST_PTR *BaseAddress, ULONG_PTR ZeroBits,
NTSTATUS WINAPI NtAllocateVirtualMemory(HANDLE ProcessHandle, guest_ptr<> *BaseAddress, ULONG_PTR ZeroBits,
PSIZE_T RegionSize, ULONG AllocationType, ULONG Protect);
NTSTATUS WINAPI NtProtectVirtualMemory(HANDLE ProcessHandle, GUEST_PTR *BaseAddress, PSIZE_T NumberOfBytesToProtect,
NTSTATUS WINAPI NtProtectVirtualMemory(HANDLE ProcessHandle, guest_ptr<> *BaseAddress, PSIZE_T NumberOfBytesToProtect,
ULONG NewAccessProtection, PULONG OldAccessProtection);
NTSTATUS WINAPI NtQueryInformationFile(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID FileInformation,
ULONG Length, FILE_INFORMATION_CLASS FileInformationClass);
NTSTATUS WINAPI NtQuerySystemTime(PLARGE_INTEGER SystemTime);
BOOLEAN WINAPI RtlTimeToSecondsSince1970(PLARGE_INTEGER Time, PULONG ElapsedSeconds);
VOID WINAPI RtlInitializeBitMap(PRTL_BITMAP BitMapHeader, PULONG BitMapBuffer, ULONG SizeOfBitMap);
VOID WINAPI RtlSetBits(PRTL_BITMAP BitMapHeader, ULONG StartingIndex, ULONG NumberToSet);
BOOLEAN WINAPI RtlAreBitsSet(PRTL_BITMAP BitMapHeader, ULONG StartingIndex, ULONG Length);
BOOLEAN WINAPI RtlAreBitsClear(PRTL_BITMAP BitMapHeader, ULONG StartingIndex, ULONG Length);
NTSTATUS WINAPI RtlGetVersion(PRTL_OSVERSIONINFOW lpVersionInformation);
NTSTATUS WINAPI NtQueryInformationProcess(HANDLE ProcessHandle, PROCESSINFOCLASS ProcessInformationClass,
PVOID ProcessInformation, ULONG ProcessInformationLength,

View File

@@ -55,7 +55,9 @@
#define STATUS_INVALID_INFO_CLASS ((NTSTATUS)0xC0000003)
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004)
#define STATUS_NOT_IMPLEMENTED ((NTSTATUS)0xC0000002)
#define STATUS_ACCESS_VIOLATION ((NTSTATUS)0xC0000005)
#define STATUS_END_OF_FILE ((NTSTATUS)0xC0000011)
#define STATUS_OBJECT_TYPE_MISMATCH ((NTSTATUS)0xC0000024)
#define STATUS_PENDING ((NTSTATUS)0x00000103)
#define STATUS_NOT_SUPPORTED ((NTSTATUS)0xC00000BB)
#define STATUS_UNEXPECTED_IO_ERROR ((NTSTATUS)0xC00000E9)

View File

@@ -32,7 +32,8 @@ enum class ObjectType : uint16_t {
enum ObjectFlags : uint16_t {
Of_None = 0x0,
Of_Waitable = 0x1,
Of_File = 0x2,
Of_FsObject = 0x2,
Of_File = 0x4,
};
struct ObjectBase {

View File

@@ -29,9 +29,10 @@
.endm
.macro LJMP64
push CS_64 # 64-bit code segment (Linux)
push offset 1f # 64-bit code offset
retf # far jump into 64-bit code
// Annoyingly, we can't assemble this in Intel syntax
.att_syntax prefix
ljmp $CS_64, $1f
.intel_syntax noprefix
.code64
1:
endbr64

View File

@@ -50,11 +50,10 @@ inline GUEST_PTR toGuestPtr(const void *addr) {
__builtin_unreachable();
return static_cast<GUEST_PTR>(addr64);
}
inline void *fromGuestPtr(GUEST_PTR addr) { return reinterpret_cast<void *>(addr); }
#else
inline GUEST_PTR toGuestPtr(const void *addr) { return static_cast<GUEST_PTR>(reinterpret_cast<unsigned long>(addr)); }
inline void *fromGuestPtr(GUEST_PTR addr) { return reinterpret_cast<void *>(addr); }
#endif
template <typename T = void> inline T *fromGuestPtr(GUEST_PTR addr) { return reinterpret_cast<T *>(addr); }
using VOID = void;
using HANDLE = int;
@@ -81,8 +80,8 @@ using LONG = int;
using PLONG = LONG *;
using ULONG = unsigned int;
using PULONG = ULONG *;
using LONGLONG = long long;
using ULONGLONG = unsigned long long;
using LONGLONG __attribute__((aligned(8))) = long long;
using ULONGLONG __attribute__((aligned(8))) = unsigned long long;
using LONG_PTR = int;
using ULONG_PTR = unsigned int;
using UINT_PTR = unsigned int;
@@ -129,6 +128,48 @@ constexpr HANDLE NO_HANDLE = 0;
using NTSTATUS = LONG;
using HRESULT = LONG;
template <typename T = void> struct guest_ptr {
GUEST_PTR ptr;
explicit guest_ptr(GUEST_PTR p) : ptr(p) {}
explicit guest_ptr(const T *p) : ptr(toGuestPtr(p)) {}
guest_ptr(const guest_ptr &p) = default;
guest_ptr(guest_ptr &&p) : ptr(p.ptr) {}
guest_ptr &operator=(T *p) {
ptr = toGuestPtr(p);
return *this;
}
guest_ptr &operator=(guest_ptr p) {
ptr = p.ptr;
return *this;
}
[[nodiscard]] T *get() const { return reinterpret_cast<T *>(ptr); }
T &operator*() const { return *reinterpret_cast<T *>(ptr); }
T *operator->() const { return reinterpret_cast<T *>(ptr); }
operator T *() const { return reinterpret_cast<T *>(ptr); } // NOLINT(google-explicit-constructor)
operator bool() const { return ptr != GUEST_NULL; } // NOLINT(google-explicit-constructor)
T &operator[](SIZE_T index) const { return get()[index]; }
};
template <> struct guest_ptr<void> {
GUEST_PTR ptr;
explicit guest_ptr(GUEST_PTR p) : ptr(p) {}
explicit guest_ptr(void *p) : ptr(toGuestPtr(p)) {}
guest_ptr(const guest_ptr &p) = default;
guest_ptr(guest_ptr &&p) : ptr(p.ptr) {}
guest_ptr &operator=(void *p) {
ptr = toGuestPtr(p);
return *this;
}
guest_ptr &operator=(guest_ptr p) {
ptr = p.ptr;
return *this;
}
[[nodiscard]] void *get() const { return reinterpret_cast<void *>(ptr); }
operator bool() const { return ptr != GUEST_NULL; } // NOLINT(google-explicit-constructor)
};
typedef union _LARGE_INTEGER {
struct {
DWORD LowPart;
@@ -153,6 +194,60 @@ typedef union _ULARGE_INTEGER {
ULONGLONG QuadPart;
} ULARGE_INTEGER, *PULARGE_INTEGER;
typedef struct _RTL_BITMAP {
ULONG SizeOfBitMap;
guest_ptr<ULONG> Buffer;
} RTL_BITMAP, *PRTL_BITMAP;
enum FILE_INFORMATION_CLASS {
FileDirectoryInformation = 1,
FileFullDirectoryInformation = 2,
FileBothDirectoryInformation = 3,
FileBasicInformation = 4,
FileStandardInformation = 5,
FileInternalInformation = 6,
FileEaInformation = 7,
FileAccessInformation = 8,
FileNameInformation = 9,
FileRenameInformation = 10,
FileLinkInformation = 11,
FileNamesInformation = 12,
FileDispositionInformation = 13,
FilePositionInformation = 14,
FileFullEaInformation = 15,
FileModeInformation = 16,
FileAlignmentInformation = 17,
FileAllInformation = 18,
FileAllocationInformation = 19,
FileEndOfFileInformation = 20,
};
typedef struct _FILE_BASIC_INFORMATION {
LARGE_INTEGER CreationTime;
LARGE_INTEGER LastAccessTime;
LARGE_INTEGER LastWriteTime;
LARGE_INTEGER ChangeTime;
ULONG FileAttributes;
} FILE_BASIC_INFORMATION, *PFILE_BASIC_INFORMATION;
typedef struct _FILE_STANDARD_INFORMATION {
LARGE_INTEGER AllocationSize;
LARGE_INTEGER EndOfFile;
ULONG NumberOfLinks;
BOOLEAN DeletePending;
BOOLEAN Directory;
USHORT Reserved;
} FILE_STANDARD_INFORMATION, *PFILE_STANDARD_INFORMATION;
typedef struct _FILE_POSITION_INFORMATION {
LARGE_INTEGER CurrentByteOffset;
} FILE_POSITION_INFORMATION, *PFILE_POSITION_INFORMATION;
typedef struct _FILE_NAME_INFORMATION {
ULONG FileNameLength;
WCHAR FileName[1];
} FILE_NAME_INFORMATION, *PFILE_NAME_INFORMATION;
struct GUID {
DWORD Data1;
WORD Data2;

106
test/test_ntdll_time.c Normal file
View File

@@ -0,0 +1,106 @@
#include <stdint.h>
#include <string.h>
#include <windows.h>
#include <winternl.h>
#include "test_assert.h"
#ifndef STATUS_SUCCESS
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
#endif
static const ULONGLONG kUnixEpochAsFileTime = 116444736000000000ULL;
static const ULONGLONG kHundredNsPerSecond = 10000000ULL;
typedef NTSTATUS(WINAPI *NtQuerySystemTimeFn)(PLARGE_INTEGER SystemTime);
typedef BOOLEAN(WINAPI *RtlTimeToSecondsSince1970Fn)(PLARGE_INTEGER Time, PULONG ElapsedSeconds);
static struct {
NtQuerySystemTimeFn query_system_time;
RtlTimeToSecondsSince1970Fn time_to_seconds;
} gFns;
static FARPROC load_ntdll_proc(const char *name) {
static HMODULE ntdll;
if (!ntdll) {
ntdll = GetModuleHandleW(L"ntdll.dll");
if (!ntdll) {
ntdll = LoadLibraryW(L"ntdll.dll");
}
}
TEST_CHECK(ntdll != NULL);
FARPROC proc = GetProcAddress(ntdll, name);
TEST_CHECK(proc != NULL);
return proc;
}
static void ensure_functions_loaded(void) {
if (gFns.query_system_time) {
return;
}
FARPROC proc = load_ntdll_proc("NtQuerySystemTime");
TEST_CHECK(sizeof(gFns.query_system_time) == sizeof(proc));
memcpy(&gFns.query_system_time, &proc, sizeof(gFns.query_system_time));
proc = load_ntdll_proc("RtlTimeToSecondsSince1970");
TEST_CHECK(sizeof(gFns.time_to_seconds) == sizeof(proc));
memcpy(&gFns.time_to_seconds, &proc, sizeof(gFns.time_to_seconds));
}
static ULONGLONG filetime_to_u64(const FILETIME *ft) {
ULARGE_INTEGER li;
li.LowPart = ft->dwLowDateTime;
li.HighPart = ft->dwHighDateTime;
return li.QuadPart;
}
static void test_nt_query_system_time_matches_filetime(void) {
LARGE_INTEGER system_time = {.QuadPart = 0};
NTSTATUS status = gFns.query_system_time(&system_time);
TEST_CHECK_EQ(STATUS_SUCCESS, status);
FILETIME ft = {0};
GetSystemTimeAsFileTime(&ft);
ULONGLONG api_time = filetime_to_u64(&ft);
ULONGLONG query_time = (ULONGLONG)system_time.QuadPart;
ULONGLONG delta = (api_time > query_time) ? (api_time - query_time) : (query_time - api_time);
TEST_CHECK_MSG(delta < 1000000ULL, "NtQuerySystemTime skew too large: %llu", (unsigned long long)delta);
}
static void test_nt_query_system_time_null(void) {
NTSTATUS status = gFns.query_system_time(NULL);
TEST_CHECK_EQ((ULONG)STATUS_ACCESS_VIOLATION, (ULONG)status);
}
static void test_rtl_time_to_seconds_success(void) {
LARGE_INTEGER system_time = {.QuadPart = 0};
TEST_CHECK_EQ(STATUS_SUCCESS, gFns.query_system_time(&system_time));
ULONG seconds = 0;
TEST_CHECK(gFns.time_to_seconds(&system_time, &seconds));
ULONGLONG expected = ((ULONGLONG)system_time.QuadPart - kUnixEpochAsFileTime) / kHundredNsPerSecond;
TEST_CHECK_EQ(expected, seconds);
}
static void test_rtl_time_to_seconds_invalid_inputs(void) {
LARGE_INTEGER before_epoch = {.QuadPart = (LONGLONG)(kUnixEpochAsFileTime - kHundredNsPerSecond)};
ULONG seconds = 0;
TEST_CHECK_EQ(FALSE, gFns.time_to_seconds(&before_epoch, &seconds));
LARGE_INTEGER beyond_range = {
.QuadPart = (LONGLONG)(kUnixEpochAsFileTime + (0x1'00000000ULL * kHundredNsPerSecond))};
TEST_CHECK_EQ(FALSE, gFns.time_to_seconds(&beyond_range, &seconds));
}
int main(void) {
ensure_functions_loaded();
TEST_CHECK(gFns.query_system_time);
TEST_CHECK(gFns.time_to_seconds);
test_nt_query_system_time_matches_filetime();
test_nt_query_system_time_null();
test_rtl_time_to_seconds_success();
test_rtl_time_to_seconds_invalid_inputs();
return 0;
}

166
test/test_ntqueryfile.c Normal file
View File

@@ -0,0 +1,166 @@
#include <string.h>
#include <windows.h>
#include <winternl.h>
#include "test_assert.h"
#ifndef STATUS_SUCCESS
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
#endif
#ifndef STATUS_INFO_LENGTH_MISMATCH
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
#endif
#ifndef STATUS_INVALID_HANDLE
#define STATUS_INVALID_HANDLE ((NTSTATUS)0xC0000008L)
#endif
#ifndef STATUS_INVALID_PARAMETER
#define STATUS_INVALID_PARAMETER ((NTSTATUS)0xC000000DL)
#endif
#ifndef STATUS_OBJECT_TYPE_MISMATCH
#define STATUS_OBJECT_TYPE_MISMATCH ((NTSTATUS)0xC0000024L)
#endif
typedef NTSTATUS(WINAPI *NtQueryInformationFileFn)(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock,
PVOID FileInformation, ULONG Length,
FILE_INFORMATION_CLASS FileInformationClass);
static NtQueryInformationFileFn gNtQueryInformationFile;
static char gTempPath[MAX_PATH];
static char gTempFile[MAX_PATH];
static FARPROC load_ntdll_proc(const char *name) {
static HMODULE ntdll;
if (!ntdll) {
ntdll = GetModuleHandleW(L"ntdll.dll");
if (!ntdll) {
ntdll = LoadLibraryW(L"ntdll.dll");
}
}
TEST_CHECK(ntdll != NULL);
FARPROC proc = GetProcAddress(ntdll, name);
TEST_CHECK(proc != NULL);
return proc;
}
static void ensure_loaded(void) {
if (gNtQueryInformationFile) {
return;
}
FARPROC proc = load_ntdll_proc("NtQueryInformationFile");
TEST_CHECK(sizeof(gNtQueryInformationFile) == sizeof(proc));
memcpy(&gNtQueryInformationFile, &proc, sizeof(gNtQueryInformationFile));
}
static HANDLE create_temp_file(void) {
DWORD pathLen = GetTempPathA(sizeof(gTempPath), gTempPath);
TEST_CHECK(pathLen > 0 && pathLen < sizeof(gTempPath));
UINT unique = GetTempFileNameA(gTempPath, "NQI", 0, gTempFile);
TEST_CHECK(unique != 0);
HANDLE file = CreateFileA(gTempFile, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL);
TEST_CHECK(file != INVALID_HANDLE_VALUE);
return file;
}
static void test_basic_and_standard_information(HANDLE file, size_t bytesWritten) {
IO_STATUS_BLOCK iosb;
FILE_BASIC_INFORMATION basic;
NTSTATUS status =
gNtQueryInformationFile(file, &iosb, &basic, sizeof(basic), FileBasicInformation);
TEST_CHECK_EQ(STATUS_SUCCESS, status);
TEST_CHECK_EQ(sizeof(basic), iosb.Information);
TEST_CHECK((basic.FileAttributes & FILE_ATTRIBUTE_ARCHIVE) != 0);
TEST_CHECK(basic.CreationTime.QuadPart != 0);
TEST_CHECK(basic.LastWriteTime.QuadPart != 0);
FILE_STANDARD_INFORMATION standardInfo;
status = gNtQueryInformationFile(file, &iosb, &standardInfo, sizeof(standardInfo),
FileStandardInformation);
TEST_CHECK_EQ(STATUS_SUCCESS, status);
TEST_CHECK_EQ(sizeof(standardInfo), iosb.Information);
TEST_CHECK_EQ((LONGLONG)bytesWritten, standardInfo.EndOfFile.QuadPart);
TEST_CHECK(standardInfo.AllocationSize.QuadPart >= (LONGLONG)bytesWritten);
TEST_CHECK_EQ(FALSE, standardInfo.DeletePending);
TEST_CHECK_EQ(FALSE, standardInfo.Directory);
TEST_CHECK(standardInfo.NumberOfLinks >= 1);
}
static void test_position_information(HANDLE file, size_t expectedOffset) {
IO_STATUS_BLOCK iosb;
FILE_POSITION_INFORMATION positionInfo;
NTSTATUS status = gNtQueryInformationFile(file, &iosb, &positionInfo, sizeof(positionInfo),
FilePositionInformation);
TEST_CHECK_EQ(STATUS_SUCCESS, status);
TEST_CHECK_EQ(sizeof(positionInfo), iosb.Information);
TEST_CHECK_EQ((LONGLONG)expectedOffset, positionInfo.CurrentByteOffset.QuadPart);
}
static void test_file_name_information(HANDLE file) {
unsigned char buffer[sizeof(FILE_NAME_INFORMATION) + 512];
IO_STATUS_BLOCK iosb;
NTSTATUS status = gNtQueryInformationFile(file, &iosb, buffer, sizeof(buffer),
FileNameInformation);
TEST_CHECK_EQ(STATUS_SUCCESS, status);
PFILE_NAME_INFORMATION nameInfo = (PFILE_NAME_INFORMATION)buffer;
TEST_CHECK(nameInfo->FileNameLength > 0);
TEST_CHECK_EQ(sizeof(ULONG) + nameInfo->FileNameLength, iosb.Information);
size_t chars = nameInfo->FileNameLength / sizeof(WCHAR);
char narrow[512];
TEST_CHECK(chars < sizeof(narrow));
for (size_t i = 0; i < chars; ++i) {
WCHAR ch = nameInfo->FileName[i];
TEST_CHECK(ch < 0x80);
narrow[i] = (char)ch;
}
narrow[chars] = '\0';
const char *expected = strchr(gTempFile, ':');
if (expected) {
++expected;
} else {
expected = gTempFile;
}
TEST_CHECK_STR_EQ(expected, narrow);
}
static void test_invalid_cases(HANDLE file) {
IO_STATUS_BLOCK iosb;
FILE_BASIC_INFORMATION basic;
NTSTATUS status = gNtQueryInformationFile(file, &iosb, &basic, sizeof(basic) - 1,
FileBasicInformation);
TEST_CHECK_EQ((NTSTATUS)STATUS_INFO_LENGTH_MISMATCH, status);
status = gNtQueryInformationFile(INVALID_HANDLE_VALUE, &iosb, &basic, sizeof(basic),
FileBasicInformation);
TEST_CHECK_EQ((NTSTATUS)STATUS_OBJECT_TYPE_MISMATCH, status);
}
int main(void) {
ensure_loaded();
TEST_CHECK(gNtQueryInformationFile != NULL);
HANDLE file = create_temp_file();
const char *data = "ntqueryfile";
DWORD written = 0;
TEST_CHECK(WriteFile(file, data, (DWORD)strlen(data), &written, NULL));
test_basic_and_standard_information(file, written);
test_position_information(file, written);
TEST_CHECK(SetFilePointer(file, 3, NULL, FILE_BEGIN) != INVALID_SET_FILE_POINTER);
test_position_information(file, 3);
test_file_name_information(file);
test_invalid_cases(file);
CloseHandle(file);
DeleteFileA(gTempFile);
return 0;
}

117
test/test_rtl_bitmap.c Normal file
View File

@@ -0,0 +1,117 @@
#include <windows.h>
#include <string.h>
#include "test_assert.h"
typedef struct _TEST_RTL_BITMAP {
ULONG SizeOfBitMap;
PULONG Buffer;
} TEST_RTL_BITMAP;
typedef VOID(WINAPI *RtlInitializeBitMapFn)(TEST_RTL_BITMAP *, PULONG, ULONG);
typedef VOID(WINAPI *RtlSetBitsFn)(TEST_RTL_BITMAP *, ULONG, ULONG);
typedef BOOLEAN(WINAPI *RtlAreBitsSetFn)(TEST_RTL_BITMAP *, ULONG, ULONG);
typedef BOOLEAN(WINAPI *RtlAreBitsClearFn)(TEST_RTL_BITMAP *, ULONG, ULONG);
static struct {
RtlInitializeBitMapFn initialize;
RtlSetBitsFn set_bits;
RtlAreBitsSetFn are_bits_set;
RtlAreBitsClearFn are_bits_clear;
} gRtlBitmapFns;
static FARPROC load_ntdll_proc(const char *name) {
static HMODULE ntdll;
if (!ntdll) {
ntdll = GetModuleHandleW(L"ntdll.dll");
if (!ntdll) {
ntdll = LoadLibraryW(L"ntdll.dll");
}
}
TEST_CHECK(ntdll != NULL);
FARPROC proc = GetProcAddress(ntdll, name);
TEST_CHECK(proc != NULL);
return proc;
}
static void load_rtl_bitmap_functions(void) {
if (gRtlBitmapFns.initialize) {
return;
}
FARPROC proc = load_ntdll_proc("RtlInitializeBitMap");
TEST_CHECK(sizeof(gRtlBitmapFns.initialize) == sizeof(proc));
memcpy(&gRtlBitmapFns.initialize, &proc, sizeof(gRtlBitmapFns.initialize));
proc = load_ntdll_proc("RtlSetBits");
TEST_CHECK(sizeof(gRtlBitmapFns.set_bits) == sizeof(proc));
memcpy(&gRtlBitmapFns.set_bits, &proc, sizeof(gRtlBitmapFns.set_bits));
proc = load_ntdll_proc("RtlAreBitsSet");
TEST_CHECK(sizeof(gRtlBitmapFns.are_bits_set) == sizeof(proc));
memcpy(&gRtlBitmapFns.are_bits_set, &proc, sizeof(gRtlBitmapFns.are_bits_set));
proc = load_ntdll_proc("RtlAreBitsClear");
TEST_CHECK(sizeof(gRtlBitmapFns.are_bits_clear) == sizeof(proc));
memcpy(&gRtlBitmapFns.are_bits_clear, &proc, sizeof(gRtlBitmapFns.are_bits_clear));
}
static void test_initialize_sets_header(void) {
ULONG buffer[2] = {0};
TEST_RTL_BITMAP bitmap;
memset(&bitmap, 0xCC, sizeof(bitmap));
gRtlBitmapFns.initialize(&bitmap, buffer, 64);
TEST_CHECK_EQ(64u, bitmap.SizeOfBitMap);
TEST_CHECK(bitmap.Buffer == buffer);
}
static void test_set_bits_and_queries(void) {
ULONG buffer[2] = {0};
TEST_RTL_BITMAP bitmap;
gRtlBitmapFns.initialize(&bitmap, buffer, 64);
TEST_CHECK_EQ(FALSE, gRtlBitmapFns.are_bits_set(&bitmap, 1, 3));
gRtlBitmapFns.set_bits(&bitmap, 1, 3);
TEST_CHECK_EQ(TRUE, gRtlBitmapFns.are_bits_set(&bitmap, 1, 3));
TEST_CHECK_EQ(FALSE, gRtlBitmapFns.are_bits_set(&bitmap, 0, 4));
TEST_CHECK_EQ(FALSE, gRtlBitmapFns.are_bits_set(&bitmap, 0, 0));
TEST_CHECK_EQ(TRUE, gRtlBitmapFns.are_bits_clear(&bitmap, 4, 4));
TEST_CHECK_EQ(FALSE, gRtlBitmapFns.are_bits_clear(&bitmap, 4, 0));
TEST_CHECK_EQ(FALSE, gRtlBitmapFns.are_bits_clear(&bitmap, 1, 1));
TEST_CHECK_EQ(0x0000000Eu, buffer[0]);
TEST_CHECK_EQ(0x00000000u, buffer[1]);
ULONG snapshot = buffer[0];
gRtlBitmapFns.set_bits(&bitmap, 0, 0);
TEST_CHECK_EQ(snapshot, buffer[0]);
TEST_CHECK_EQ(FALSE, gRtlBitmapFns.are_bits_set(&bitmap, 60, 8));
TEST_CHECK_EQ(TRUE, gRtlBitmapFns.are_bits_clear(&bitmap, 8, 8));
}
static void test_are_bits_clear_after_setting(void) {
ULONG buffer[2] = {0};
TEST_RTL_BITMAP bitmap;
gRtlBitmapFns.initialize(&bitmap, buffer, 64);
TEST_CHECK_EQ(TRUE, gRtlBitmapFns.are_bits_clear(&bitmap, 16, 8));
gRtlBitmapFns.set_bits(&bitmap, 16, 8);
TEST_CHECK_EQ(FALSE, gRtlBitmapFns.are_bits_clear(&bitmap, 16, 8));
TEST_CHECK_EQ(TRUE, gRtlBitmapFns.are_bits_set(&bitmap, 16, 8));
}
int main(void) {
load_rtl_bitmap_functions();
TEST_CHECK(gRtlBitmapFns.initialize);
TEST_CHECK(gRtlBitmapFns.set_bits);
TEST_CHECK(gRtlBitmapFns.are_bits_set);
TEST_CHECK(gRtlBitmapFns.are_bits_clear);
test_initialize_sets_header();
test_set_bits_and_queries();
test_are_bits_clear_after_setting();
return 0;
}