From ff9f39c7d29ad444bf165abb43b9c46418904a3b Mon Sep 17 00:00:00 2001 From: =?utf8?q?Pali=20Roh=C3=A1r?= Date: Mon, 8 May 2023 21:25:12 +0200 Subject: [PATCH] libpci: Add Windows physmem support for PCIe ECAM access It requires either access to NT Section \Device\PhysicalMemory (or compatible) or to have available kernel32.dll VxDCall2 function or w32skrnl.dll DPMI function. --- lib/Makefile | 6 +- lib/configure | 7 +- lib/ecam.c | 92 +++- lib/physmem-windows.c | 1028 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1126 insertions(+), 7 deletions(-) create mode 100644 lib/physmem-windows.c diff --git a/lib/Makefile b/lib/Makefile index 84e25b5..8dba53e 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -82,14 +82,14 @@ OBJS += win32-helpers endif ifdef PCI_USE_PHYSMEM -ifndef PCI_OS_WINDOWS -ifdef PCI_OS_DJGPP +ifdef PCI_OS_WINDOWS +OBJS += physmem-windows +else ifdef PCI_OS_DJGPP OBJS += physmem-djgpp else OBJS += physmem-posix endif endif -endif ifdef PCI_HAVE_PM_AOS_EXPANSION OBJS += aos-expansion diff --git a/lib/configure b/lib/configure index a6baeea..a4909e1 100755 --- a/lib/configure +++ b/lib/configure @@ -176,11 +176,16 @@ case $sys in EXEEXT=.exe ;; cygwin|windows) - echo_n " win32-cfgmgr32 win32-kldbg win32-sysdbg" + echo_n " win32-cfgmgr32 win32-kldbg win32-sysdbg mem-ports ecam" echo >>$c '#define PCI_HAVE_64BIT_ADDRESS' echo >>$c '#define PCI_HAVE_PM_WIN32_CFGMGR32' echo >>$c '#define PCI_HAVE_PM_WIN32_KLDBG' echo >>$c '#define PCI_HAVE_PM_WIN32_SYSDBG' + echo >>$c '#define PCI_HAVE_PM_MMIO_CONF' + echo >>$c '#define PCI_HAVE_PM_ECAM' + echo >>$c '#define PCI_PATH_DEVMEM_DEVICE "\\\\Device\\\\PhysicalMemory"' + echo >>$c '#define PCI_PATH_ACPI_MCFG "GetSystemFirmwareTable()"' + echo >>$c '#define PCI_PATH_EFI_SYSTAB ""' # Warning: MinGW-w64 (incorrectly) provides cfgmgr32 functions # also in other import libraries, not only in libcfgmgr32.a. # So always set -lcfgmgr32 as a first library parameter which diff --git a/lib/ecam.c b/lib/ecam.c index fdeec07..f953e10 100644 --- a/lib/ecam.c +++ b/lib/ecam.c @@ -19,8 +19,10 @@ #include #include +#ifndef PCI_OS_WINDOWS #include #include +#endif #if defined (__FreeBSD__) || defined (__DragonFly__) || defined(__NetBSD__) #include @@ -30,6 +32,10 @@ #include #endif +#ifdef _WIN32 +#include "win32-helpers.h" +#endif + struct acpi_rsdp { char signature[8]; u8 checksum; @@ -371,6 +377,62 @@ find_rsdp_address(struct pci_access *a, const char *efisystab, int use_bsd UNUSE return 0; } +#ifdef PCI_OS_WINDOWS +#ifndef ERROR_NOT_FOUND +#define ERROR_NOT_FOUND 1168 +#endif +static struct acpi_mcfg * +get_system_firmware_table_acpi_mcfg(struct pci_access *a) +{ + UINT (*WINAPI MyGetSystemFirmwareTable)(DWORD FirmwareTableProviderSignature, DWORD FirmwareTableID, PVOID pFirmwareTableBuffer, DWORD BufferSize); + struct acpi_mcfg *mcfg; + HMODULE kernel32; + DWORD error; + DWORD size; + + kernel32 = GetModuleHandle(TEXT("kernel32.dll")); + if (!kernel32) + return NULL; + + /* Function GetSystemFirmwareTable() is available since Windows Vista. */ + MyGetSystemFirmwareTable = (void *)GetProcAddress(kernel32, "GetSystemFirmwareTable"); + if (!MyGetSystemFirmwareTable) + return NULL; + + /* 0x41435049 = 'ACPI', 0x4746434D = 'MCFG' */ + size = MyGetSystemFirmwareTable(0x41435049, 0x4746434D, NULL, 0); + if (size == 0) + { + error = GetLastError(); + if (error == ERROR_INVALID_FUNCTION) /* ACPI is not present. */ + return NULL; + else if (error == ERROR_NOT_FOUND) /* MCFG table is not present. */ + return NULL; + a->debug("Cannot retrieve ACPI MCFG table: %s.\n", win32_strerror(error)); + return NULL; + } + + mcfg = pci_malloc(a, size); + + if (MyGetSystemFirmwareTable(0x41435049, 0x4746434D, mcfg, size) != size) + { + error = GetLastError(); + a->debug("Cannot retrieve ACPI MCFG table: %s.\n", win32_strerror(error)); + pci_mfree(mcfg); + return NULL; + } + + if (size < sizeof(*mcfg) || size < mcfg->sdt.length || calculate_checksum((u8 *)mcfg, mcfg->sdt.length) != 0) + { + a->debug("ACPI MCFG table is broken.\n"); + pci_mfree(mcfg); + return NULL; + } + + return mcfg; +} +#endif + static struct acpi_mcfg * find_mcfg(struct pci_access *a, const char *acpimcfg, const char *efisystab, int use_bsd, int use_x86bios) { @@ -392,18 +454,38 @@ find_mcfg(struct pci_access *a, const char *acpimcfg, const char *efisystab, int long length; FILE *mcfg_file; const char *path; +#ifndef PCI_OS_WINDOWS glob_t mcfg_glob; int ret; +#endif if (acpimcfg[0]) { +#ifdef PCI_OS_WINDOWS + if (strcmp(acpimcfg, "GetSystemFirmwareTable()") == 0) + { + a->debug("reading acpi mcfg via GetSystemFirmwareTable()..."); + mcfg = get_system_firmware_table_acpi_mcfg(a); + if (mcfg) + return mcfg; + a->debug("failed..."); + } + else + { + path = acpimcfg; +#else ret = glob(acpimcfg, GLOB_NOCHECK, NULL, &mcfg_glob); - if (ret == 0) + if (ret != 0) + a->debug("glob(%s) failed: %d...", acpimcfg, ret); + else { path = mcfg_glob.gl_pathv[0]; +#endif a->debug("reading acpi mcfg file: %s...", path); mcfg_file = fopen(path, "rb"); +#ifndef PCI_OS_WINDOWS globfree(&mcfg_glob); +#endif if (mcfg_file) { if (fseek(mcfg_file, 0, SEEK_END) == 0) @@ -427,8 +509,6 @@ find_mcfg(struct pci_access *a, const char *acpimcfg, const char *efisystab, int } a->debug("failed..."); } - else - a->debug("glob(%s) failed: %d...", acpimcfg, ret); } a->debug("searching for ACPI RSDP..."); @@ -809,8 +889,10 @@ ecam_detect(struct pci_access *a) #endif const char *addrs = pci_get_param(a, "ecam.addrs"); struct ecam_access *eacc; +#ifndef PCI_OS_WINDOWS glob_t mcfg_glob; int ret; +#endif if (!*addrs) { @@ -820,6 +902,7 @@ ecam_detect(struct pci_access *a) if (acpimcfg[0]) { +#ifndef PCI_OS_WINDOWS ret = glob(acpimcfg, GLOB_NOCHECK, NULL, &mcfg_glob); if (ret == 0) { @@ -835,16 +918,19 @@ ecam_detect(struct pci_access *a) a->debug("glob(%s) failed: %d...", acpimcfg, ret); use_acpimcfg = 0; } +#endif } else use_acpimcfg = 0; +#ifndef PCI_OS_WINDOWS if (!efisystab[0] || access(efisystab, R_OK)) { if (efisystab[0]) a->debug("cannot access efisystab: %s: %s...", efisystab, strerror(errno)); use_efisystab = 0; } +#endif #if defined (__FreeBSD__) || defined (__DragonFly__) || defined(__NetBSD__) if (strcmp(bsd, "0") == 0) diff --git a/lib/physmem-windows.c b/lib/physmem-windows.c new file mode 100644 index 0000000..06f094c --- /dev/null +++ b/lib/physmem-windows.c @@ -0,0 +1,1028 @@ +/* + * The PCI Library -- Physical memory mapping for Windows systems + * + * Copyright (c) 2023 Pali Rohár + * + * Can be freely distributed and used under the terms of the GNU GPL v2+ + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "internal.h" + +#include +#include +#include + +#include "physmem.h" +#include "win32-helpers.h" + +#ifndef NTSTATUS +#define NTSTATUS LONG +#endif +#ifndef STATUS_INVALID_HANDLE +#define STATUS_INVALID_HANDLE ((NTSTATUS)0xC0000008) +#endif +#ifndef STATUS_INVALID_PARAMETER +#define STATUS_INVALID_PARAMETER ((NTSTATUS)0xC000000D) +#endif +#ifndef STATUS_CONFLICTING_ADDRESSES +#define STATUS_CONFLICTING_ADDRESSES ((NTSTATUS)0xC0000018) +#endif +#ifndef STATUS_NOT_MAPPED_VIEW +#define STATUS_NOT_MAPPED_VIEW ((NTSTATUS)0xC0000019) +#endif +#ifndef STATUS_INVALID_VIEW_SIZE +#define STATUS_INVALID_VIEW_SIZE ((NTSTATUS)0xC000001F) +#endif +#ifndef STATUS_ACCESS_DENIED +#define STATUS_ACCESS_DENIED ((NTSTATUS)0xC0000022) +#endif +#ifndef STATUS_OBJECT_NAME_NOT_FOUND +#define STATUS_OBJECT_NAME_NOT_FOUND ((NTSTATUS)0xC0000034) +#endif +#ifndef STATUS_INVALID_PAGE_PROTECTION +#define STATUS_INVALID_PAGE_PROTECTION ((NTSTATUS)0xC0000045) +#endif +#ifndef STATUS_SECTION_PROTECTION +#define STATUS_SECTION_PROTECTION ((NTSTATUS)0xC000004E) +#endif +#ifndef STATUS_INSUFFICIENT_RESOURCES +#define STATUS_INSUFFICIENT_RESOURCES ((NTSTATUS)0xC000009A) +#endif +#ifndef STATUS_INVALID_PARAMETER_3 +#define STATUS_INVALID_PARAMETER_3 ((NTSTATUS)0xC00000F1) +#endif +#ifndef STATUS_INVALID_PARAMETER_4 +#define STATUS_INVALID_PARAMETER_4 ((NTSTATUS)0xC00000F2) +#endif +#ifndef STATUS_INVALID_PARAMETER_5 +#define STATUS_INVALID_PARAMETER_5 ((NTSTATUS)0xC00000F3) +#endif +#ifndef STATUS_INVALID_PARAMETER_8 +#define STATUS_INVALID_PARAMETER_8 ((NTSTATUS)0xC00000F6) +#endif +#ifndef STATUS_INVALID_PARAMETER_9 +#define STATUS_INVALID_PARAMETER_9 ((NTSTATUS)0xC00000F7) +#endif +#ifndef STATUS_MAPPED_ALIGNMENT +#define STATUS_MAPPED_ALIGNMENT ((NTSTATUS)0xC0000220) +#endif + +#ifndef OBJ_CASE_INSENSITIVE +#define OBJ_CASE_INSENSITIVE 0x00000040L +#endif + +#ifndef SECTION_INHERIT +#define SECTION_INHERIT ULONG +#endif +#ifndef ViewUnmap +#define ViewUnmap ((SECTION_INHERIT)2) +#endif + +#ifndef IMAGE_NT_OPTIONAL_HDR_MAGIC +#ifdef _WIN64 +#define IMAGE_NT_OPTIONAL_HDR_MAGIC 0x20b +#else +#define IMAGE_NT_OPTIONAL_HDR_MAGIC 0x10b +#endif +#endif + +#ifndef EOVERFLOW +#define EOVERFLOW 132 +#endif + +#if _WIN32_WINNT < 0x0500 +typedef ULONG ULONG_PTR; +typedef ULONG_PTR SIZE_T, *PSIZE_T; +#endif + +#ifndef __UNICODE_STRING_DEFINED +#define __UNICODE_STRING_DEFINED +typedef struct _UNICODE_STRING { + USHORT Length; + USHORT MaximumLength; + PWSTR Buffer; +} UNICODE_STRING, *PUNICODE_STRING; +#endif + +#ifndef __OBJECT_ATTRIBUTES_DEFINED +#define __OBJECT_ATTRIBUTES_DEFINED +typedef struct _OBJECT_ATTRIBUTES { + ULONG Length; + HANDLE RootDirectory; + PUNICODE_STRING ObjectName; + ULONG Attributes; + PVOID SecurityDescriptor; + PVOID SecurityQualityOfService; +} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES; +#endif + +#ifndef InitializeObjectAttributes +#define InitializeObjectAttributes(p, n, a, r, s) \ +{ \ + (p)->Length = sizeof(OBJECT_ATTRIBUTES); \ + (p)->RootDirectory = (r); \ + (p)->Attributes = (a); \ + (p)->ObjectName = (n); \ + (p)->SecurityDescriptor = (s); \ + (p)->SecurityQualityOfService = NULL; \ +} +#endif + +#ifndef RtlInitUnicodeString +#define RtlInitUnicodeString(d, s) \ +{ \ + (d)->Length = wcslen(s) * sizeof(WCHAR); \ + (d)->MaximumLength = (d)->Length + sizeof(WCHAR); \ + (d)->Buffer = (PWCHAR)(s); \ +} +#endif + +#define VWIN32_DEVICE_ID 0x0002A /* from vmm.h */ +#define WIN32_SERVICE_ID(device, function) (((device) << 16) | (function)) +#define VWIN32_Int31Dispatch WIN32_SERVICE_ID(VWIN32_DEVICE_ID, 0x29) +#define DPMI_PHYSICAL_ADDRESS_MAPPING 0x0800 + +struct physmem { + HMODULE ntdll; + HANDLE section_handle; + NTSTATUS (NTAPI *NtOpenSection)(PHANDLE SectionHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes); + NTSTATUS (NTAPI *NtMapViewOfSection)(HANDLE SectionHandle, HANDLE ProcessHandle, PVOID *BaseAddress, ULONG_PTR ZeroBits, SIZE_T CommitSize, PLARGE_INTEGER SectionOffset, PSIZE_T ViewSize, SECTION_INHERIT InheritDisposition, ULONG AllocationType, ULONG Win32Protect); + NTSTATUS (NTAPI *NtUnmapViewOfSection)(HANDLE ProcessHandle, PVOID BaseAddress); + ULONG (NTAPI *RtlNtStatusToDosError)(NTSTATUS Status); +#if defined(__i386__) || defined(_M_IX86) + DWORD (WINAPI *VxDCall2)(DWORD Service, DWORD Arg1, DWORD Arg2); + LPVOID w32skrnl_dpmi_lcall_ptr; + DWORD base_addr_offset; +#endif +}; + +#if defined(__i386__) || defined(_M_IX86) + +static BOOL +w32skrnl_physical_address_mapping(struct physmem *physmem, DWORD phys_addr, DWORD size, DWORD *virt_addr) +{ + DWORD address_hi = phys_addr >> 16; + DWORD address_lo = phys_addr & 0xffff; + DWORD size_hi = size >> 16; + DWORD size_lo = size & 0xffff; + BYTE failed; + + /* + * Physical address mapping via w32skrnl.dll on Windows maps physical memory + * and translates it to the virtual space of the current process memory. + * Works only for aligned address / length and first 1 MB cannot be mapped + * by this method. Expect that first 1 MB is already 1:1 mapped by the OS. + * So accept request for physical memory range which is whole below 1 MB + * without error and return virtual address same as the physical one. + */ + if (phys_addr < 1*1024*1024UL) + { + if ((u64)phys_addr + size > 1*1024*1024UL) + { + errno = ENXIO; + return FALSE; + } + + *virt_addr = phys_addr; + return TRUE; + } + + /* + * Unfortunately w32skrnl.dll provides only 48-bit fword pointer to physical + * address mapping function and such pointer type is not supported by GCC, + * nor by MSVC and therefore it is not possible call this function in C code. + * So call this function with all parameters passed via inline assembly. + */ +#if defined(__GNUC__) + asm volatile ( + "stc\n\t" + "lcall *(%3)\n\t" + "setc %0\n\t" + : "=qm" (failed), "+b" (address_hi), "+c" (address_lo) + : "r" (physmem->w32skrnl_dpmi_lcall_ptr), "a" (DPMI_PHYSICAL_ADDRESS_MAPPING), "S" (size_hi), "D" (size_lo) + : "cc", "memory" + ); +#elif defined(_MSC_VER) + __asm { + mov esi, size_hi + mov edi, size_lo + mov ebx, address_hi + mov ecx, address_lo + mov eax, DPMI_PHYSICAL_ADDRESS_MAPPING + stc + mov edx, physmem + mov edx, [edx]physmem.w32skrnl_dpmi_lcall_ptr + call fword ptr [edx] + setc failed + mov address_hi, ebx + mov address_lo, ecx + } +#else +#error "Unsupported compiler" +#endif + + /* + * Windows does not provide any error code when this function call fails. + * So set errno just to the generic EACCES value. + */ + if (failed) + { + errno = EACCES; + return FALSE; + } + + *virt_addr = ((address_hi & 0xffff) << 16) | (address_lo & 0xffff); + return TRUE; +} + +#if defined(__GNUC__) && ((__GNUC__ == 4 && __GNUC_MINOR__ >= 8) || (__GNUC__ > 4)) && (__GNUC__ <= 11) +/* + * GCC versions 4.8 - 11 are buggy and throw error "'asm' operand has impossible + * constraints" for inline assembly when optimizations (O1+) are enabled. So for + * these GCC versions disable buggy optimizations by enforcing O0 optimize flag + * affecting just this one function. + */ +__attribute__((optimize("O0"))) +#endif +static BOOL +vdxcall_physical_address_mapping(struct physmem *physmem, DWORD phys_addr, DWORD size, DWORD *virt_addr) +{ + DWORD address_hi = phys_addr >> 16; + DWORD address_lo = phys_addr & 0xffff; + DWORD size_hi = size >> 16; + DWORD size_lo = size & 0xffff; + BYTE failed; + + /* + * Physical address mapping via VxDCall2() on Windows maps physical memory + * and translates it to the virtual space of the current process memory. + * There are no restrictions for aligning or physical address ranges. + * Works with any (unaligned) address or length, including low 1MB range. + */ + + /* + * Function VxDCall2() has strange calling convention. First 3 arguments are + * passed on stack, which callee pops (same as stdcall convention) but rest + * of the function arguments are passed in ESI, EDI and EBX registers. And + * return value is in carry flag (CF) and AX, BX and CX registers. GCC and + * neither MSVC do not support this strange calling convention, so call this + * function via inline assembly. + * + * Pseudocode with stdcall calling convention of that function looks like: + * ESI = size_hi + * EDI = size_lo + * EBX = address_hi + * VxDCall2(VWIN32_Int31Dispatch, DPMI_PHYSICAL_ADDRESS_MAPPING, address_lo) + * failed = CF + * address_hi = BX (if not failed) + * address_lo = CX (if not failed) + */ + +#if defined(__GNUC__) + asm volatile ( + "pushl %6\n\t" + "pushl %5\n\t" + "pushl %4\n\t" + "stc\n\t" + "call *%P3\n\t" + "setc %0\n\t" + : "=qm" (failed), "+b" (address_hi), "=c" (address_lo) + : "rmi" (physmem->VxDCall2), "rmi" (VWIN32_Int31Dispatch), "rmi" (DPMI_PHYSICAL_ADDRESS_MAPPING), + "rmi" (address_lo), "S" (size_hi), "D" (size_lo) +/* Specify all call clobbered scratch registers for stdcall calling convention. */ + : "eax", "edx", +/* + * Since GCC version 4.9.0 it is possible to specify x87 registers as clobbering + * if they are not disabled by -mno-80387 switch (which defines _SOFT_FLOAT). + */ +#if ((__GNUC__ == 4 && __GNUC_MINOR__ >= 9) || (__GNUC__ > 4)) && !defined(_SOFT_FLOAT) + "st", "st(1)", "st(2)", "st(3)", "st(4)", "st(5)", "st(6)", "st(7)", +#endif +#ifdef __MMX__ + "mm0", "mm1", "mm2", "mm3", "mm4", "mm5", "mm6", "mm6", +#endif +#ifdef __SSE__ + "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", +#endif + "cc", "memory" + ); +#elif defined(_MSC_VER) + __asm { + mov esi, size_hi + mov edi, size_lo + mov ebx, address_hi + push address_lo + push DPMI_PHYSICAL_ADDRESS_MAPPING + push VWIN32_Int31Dispatch + stc + mov eax, physmem + call [eax]physmem.VxDCall2 + setc failed + mov address_hi, ebx + mov address_lo, ecx + } +#else +#error "Unsupported compiler" +#endif + + /* + * Windows does not provide any error code when this function call fails. + * So set errno just to the generic EACCES value. + */ + if (failed) + { + errno = EACCES; + return FALSE; + } + + *virt_addr = ((address_hi & 0xffff) << 16) | (address_lo & 0xffff); + return TRUE; +} + +static BOOL +win32_get_physmem_offset(DWORD *offset) +{ + WORD DSsel; + LDT_ENTRY DSentry; + + /* + * Read DS selector. For this purpose there is WinAPI function and when called + * as GetThreadContext(GetCurrentThread(), ...) with CONTEXT_SEGMENTS param, + * it fills SegDs value. But on some Windows versions, GetThreadContext() can + * be called only for threads attached to debugger. Hence we cannot use it for + * our current thread. So instead read DS selector directly from ds register + * via inline assembly code. + */ +#if defined(__GNUC__) + asm ("movw %%ds, %w0" : "=rm" (DSsel)); +#elif defined(_MSC_VER) + __asm { mov DSsel, ds } +#else +#error "Unsupported compiler" +#endif + + if (!GetThreadSelectorEntry(GetCurrentThread(), DSsel, &DSentry)) + return FALSE; + + *offset = DSentry.BaseLow | (DSentry.HighWord.Bytes.BaseMid << 0x10) | (DSentry.HighWord.Bytes.BaseHi << 0x18); + return TRUE; +} + +static BYTE * +win32_get_baseaddr_from_hmodule(HMODULE module) +{ + WORD (WINAPI *ImteFromHModule)(HMODULE); + BYTE *(WINAPI *BaseAddrFromImte)(WORD); + HMODULE w32skrnl; + WORD imte; + + if ((GetVersion() & 0xC0000000) != 0x80000000) + return (BYTE *)module; + + w32skrnl = GetModuleHandleA("w32skrnl.dll"); + if (!w32skrnl) + return NULL; + + ImteFromHModule = (LPVOID)GetProcAddress(w32skrnl, "_ImteFromHModule@4"); + BaseAddrFromImte = (LPVOID)GetProcAddress(w32skrnl, "_BaseAddrFromImte@4"); + if (!ImteFromHModule || !BaseAddrFromImte) + return NULL; + + imte = ImteFromHModule(module); + if (imte == 0xffff) + return NULL; + + return BaseAddrFromImte(imte); +} + +static FARPROC +win32_get_proc_address_by_ordinal(HMODULE module, DWORD ordinal, BOOL must_be_without_name) +{ + BYTE *baseaddr; + IMAGE_DOS_HEADER *dos_header; + IMAGE_NT_HEADERS *nt_header; + DWORD export_dir_offset, export_dir_size; + IMAGE_EXPORT_DIRECTORY *export_dir; + DWORD base_ordinal, func_count; + DWORD *func_addrs; + FARPROC func_ptr; + DWORD names_count, i; + USHORT *names_idxs; + UINT prev_error_mode; + char module_name[MAX_PATH]; + DWORD module_name_len; + char *export_name; + char *endptr; + long num; + + baseaddr = win32_get_baseaddr_from_hmodule(module); + if (!baseaddr) + return NULL; + + dos_header = (IMAGE_DOS_HEADER *)baseaddr; + if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) + return NULL; + + nt_header = (IMAGE_NT_HEADERS *)((BYTE *)dos_header + dos_header->e_lfanew); + if (nt_header->Signature != IMAGE_NT_SIGNATURE) + return NULL; + + if (nt_header->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC) + return NULL; + + if (nt_header->OptionalHeader.NumberOfRvaAndSizes <= IMAGE_DIRECTORY_ENTRY_EXPORT) + return NULL; + + export_dir_offset = nt_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; + export_dir_size = nt_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size; + + if (!export_dir_offset || !export_dir_size) + return NULL; + + export_dir = (IMAGE_EXPORT_DIRECTORY *)(baseaddr + export_dir_offset); + base_ordinal = export_dir->Base; + func_count = export_dir->NumberOfFunctions; + func_addrs = (DWORD *)(baseaddr + (DWORD)export_dir->AddressOfFunctions); + + if (ordinal < base_ordinal || ordinal - base_ordinal > func_count) + return NULL; + + if (must_be_without_name) + { + /* Check that function with ordinal number does not have any name. */ + names_count = export_dir->NumberOfNames; + names_idxs = (USHORT *)(baseaddr + (DWORD)export_dir->AddressOfNameOrdinals); + for (i = 0; i < names_count; i++) + { + if (names_idxs[i] == ordinal - base_ordinal) + return NULL; + } + } + + func_ptr = (FARPROC)(baseaddr + func_addrs[ordinal - base_ordinal]); + if ((BYTE *)func_ptr >= (BYTE *)export_dir && (BYTE *)func_ptr < (BYTE *)export_dir + export_dir_size) + { + /* + * We need to locate the _last_ dot character (separator of library name + * and export symbol name) because library name may contain dot character + * (used when specifying file name with explicit extension). For example + * wine is using this kind of strange symbol redirection to different + * library with non-standard file name extension (different than .dll). + */ + export_name = strrchr((char *)func_ptr, '.'); + if (!export_name) + return NULL; + export_name++; + + module_name_len = export_name - 1 - (char *)func_ptr; + if (module_name_len >= sizeof(module_name)) + return NULL; + + memcpy(module_name, func_ptr, module_name_len); + module_name[module_name_len] = 0; + + prev_error_mode = win32_change_error_mode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX); + module = LoadLibraryA(module_name); + win32_change_error_mode(prev_error_mode); + if (!module) + { + FreeLibrary(module); + return NULL; + } + + if (*export_name == '#') + { + export_name++; + errno = 0; + num = strtol(export_name, &endptr, 10); + if (*export_name < '0' || *export_name > '9' || errno || *endptr || num < 0 || (unsigned long)num >= ((DWORD)-1)/2) + { + FreeLibrary(module); + return NULL; + } + ordinal = num; + func_ptr = win32_get_proc_address_by_ordinal(module, ordinal, FALSE); + } + else + { + func_ptr = GetProcAddress(module, export_name); + } + + if (!func_ptr) + FreeLibrary(module); + } + + return func_ptr; +} + +static int +init_physmem_w32skrnl(struct physmem *physmem, struct pci_access *a) +{ + HMODULE w32skrnl; + LPVOID (WINAPI *GetThunkBuff)(VOID); + OSVERSIONINFOA version; + LPVOID buf_ptr; + + a->debug("resolving DPMI function via GetThunkBuff() function from w32skrnl.dll..."); + w32skrnl = GetModuleHandleA("w32skrnl.dll"); + if (!w32skrnl) + { + a->debug("failed: library not present."); + errno = ENOENT; + return 0; + } + + GetThunkBuff = (LPVOID)GetProcAddress(w32skrnl, "_GetThunkBuff@0"); + if (!GetThunkBuff) + { + a->debug("failed: symbol not found."); + errno = ENOENT; + return 0; + } + + version.dwOSVersionInfoSize = sizeof(version); + if (!GetVersionExA(&version)) + { + a->debug("failed: cannot detect version."); + errno = EINVAL; + return 0; + } + + /* Versions before 1.1 (1.1.88) are not supported. */ + if (version.dwMajorVersion < 1 || (version.dwMajorVersion == 1 && version.dwMinorVersion < 1)) + { + a->debug("failed: found old incompatible version."); + errno = ENOENT; + return 0; + } + + if (!win32_get_physmem_offset(&physmem->base_addr_offset)) + { + a->debug("failed: cannot retrieve physical address offset: %s.", win32_strerror(GetLastError())); + errno = EINVAL; + return 0; + } + + buf_ptr = GetThunkBuff(); + if (!buf_ptr) + { + a->debug("failed: cannot retrieve DPMI function pointer."); + errno = EINVAL; + return 0; + } + + /* + * Versions 1.1 (1.1.88) - 1.15 (1.15.103) have DPMI function at offset 0xa0. + * Versions 1.15a (1.15.111) - 1.30c (1.30.172) have DPMI function at offset 0xa4. + */ + if (version.dwMajorVersion > 1 || + (version.dwMajorVersion == 1 && version.dwMinorVersion > 15) || + (version.dwMajorVersion == 1 && version.dwMinorVersion == 15 && version.dwBuildNumber >= 111)) + physmem->w32skrnl_dpmi_lcall_ptr = (LPVOID)((BYTE *)buf_ptr + 0xa4); + else + physmem->w32skrnl_dpmi_lcall_ptr = (LPVOID)((BYTE *)buf_ptr + 0xa0); + + a->debug("success."); + return 1; +} + +static int +init_physmem_vxdcall(struct physmem *physmem, struct pci_access *a) +{ + HMODULE kernel32; + BOOL success; + DWORD addr; + + a->debug("resolving VxDCall2() function from kernel32.dll..."); + kernel32 = GetModuleHandleA("kernel32.dll"); + if (!kernel32) + { + a->debug("failed: library not present."); + errno = ENOENT; + return 0; + } + + /* + * New Windows versions do not export VxDCall2 symbol by name anymore, + * so try also locating this symbol by its ordinal number, which is 3. + * Old Windows versions prevents using GetProcAddress() for locating + * kernel32.dll symbol by ordinal number, so use our own custom function. + * When locating via ordinal number, check that this ordinal number does + * not have any name assigned (to ensure that it is really VxDCall2). + */ + physmem->VxDCall2 = (LPVOID)GetProcAddress(kernel32, "VxDCall2"); + if (!physmem->VxDCall2) + physmem->VxDCall2 = (LPVOID)win32_get_proc_address_by_ordinal(kernel32, 3, TRUE); + + if (!physmem->VxDCall2) + { + a->debug("failed: symbol not found."); + errno = ENOENT; + return 0; + } + + /* + * Wine implementation of VxDCall2() does not support physical address + * mapping but returns success with virtual address same as passed physical + * address. Detect this broken wine behavior by trying to map zero address + * of zero range. Broken wine implementation returns NULL pointer. This + * prevents accessing unmapped memory or dereferencing NULL pointer. + */ + success = vdxcall_physical_address_mapping(physmem, 0x0, 0x0, &addr); + if (success && addr == 0x0) + { + a->debug("failed: physical address mapping via VxDCall2() is broken."); + physmem->VxDCall2 = NULL; + errno = EINVAL; + return 0; + } + else if (!success) + { + a->debug("failed: physical address mapping via VxDCall2() is unsupported."); + physmem->VxDCall2 = NULL; + errno = ENOENT; + return 0; + } + + /* Retrieve base address - offset for all addresses returned by VxDCall2(). */ + if (!win32_get_physmem_offset(&physmem->base_addr_offset)) + { + a->debug("failed: cannot retrieve physical address offset: %s.", win32_strerror(GetLastError())); + physmem->VxDCall2 = NULL; + errno = EINVAL; + return 0; + } + + a->debug("success."); + return 1; +} + +#endif + +static int +init_physmem_ntdll(struct physmem *physmem, struct pci_access *a, const char *filename, int w) +{ + wchar_t *wide_filename; + UNICODE_STRING unicode_filename; + OBJECT_ATTRIBUTES attributes; + UINT prev_error_mode; + NTSTATUS status; + int len; + + a->debug("resolving section functions from ntdll.dll..."); + prev_error_mode = win32_change_error_mode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX); + physmem->ntdll = LoadLibrary(TEXT("ntdll.dll")); + win32_change_error_mode(prev_error_mode); + if (!physmem->ntdll) + { + a->debug("failed: cannot open ntdll.dll library: %s.", win32_strerror(GetLastError())); + errno = ENOENT; + return 0; + } + + physmem->RtlNtStatusToDosError = (LPVOID)GetProcAddress(physmem->ntdll, "RtlNtStatusToDosError"); + + physmem->NtOpenSection = (LPVOID)GetProcAddress(physmem->ntdll, "NtOpenSection"); + if (!physmem->NtOpenSection) + { + a->debug("failed: function NtOpenSection() not found."); + FreeLibrary(physmem->ntdll); + physmem->ntdll = NULL; + errno = ENOENT; + return 0; + } + + physmem->NtMapViewOfSection = (LPVOID)GetProcAddress(physmem->ntdll, "NtMapViewOfSection"); + if (!physmem->NtMapViewOfSection) + { + a->debug("failed: function NtMapViewOfSection() not found."); + FreeLibrary(physmem->ntdll); + physmem->ntdll = NULL; + errno = ENOENT; + return 0; + } + + physmem->NtUnmapViewOfSection = (LPVOID)GetProcAddress(physmem->ntdll, "NtUnmapViewOfSection"); + if (!physmem->NtUnmapViewOfSection) + { + a->debug("failed: function NtUnmapViewOfSection() not found."); + FreeLibrary(physmem->ntdll); + physmem->ntdll = NULL; + errno = ENOENT; + return 0; + } + + a->debug("success."); + + /* + * Note: It is not possible to use WinAPI function OpenFileMappingA() because + * it takes path relative to the NT base path \\Sessions\\X\\BaseNamedObjects\\ + * and so it does not support to open sections outside that NT directory. + * NtOpenSection() does not have this restriction and supports specifying any + * path, including path in absolute format. Unfortunately NtOpenSection() + * takes path in UNICODE_STRING structure, unlike OpenFileMappingA() which + * takes path in 8-bit char*. So first it is needed to do conversion from + * char* string to wchar_t* string via function MultiByteToWideChar() and + * then fill UNICODE_STRING structure from that wchar_t* string via function + * RtlInitUnicodeString(). + */ + + len = MultiByteToWideChar(CP_ACP, 0, filename, -1, NULL, 0); + if (len <= 0) + { + a->debug("Option devmem.path '%s' is invalid multibyte string.", filename); + FreeLibrary(physmem->ntdll); + physmem->ntdll = NULL; + errno = EINVAL; + return 0; + } + + wide_filename = pci_malloc(a, len * sizeof(wchar_t)); + len = MultiByteToWideChar(CP_ACP, 0, filename, -1, wide_filename, len); + if (len <= 0) + { + a->debug("Option devmem.path '%s' is invalid multibyte string.", filename); + pci_mfree(wide_filename); + FreeLibrary(physmem->ntdll); + physmem->ntdll = NULL; + errno = EINVAL; + return 0; + } + + RtlInitUnicodeString(&unicode_filename, wide_filename); + InitializeObjectAttributes(&attributes, &unicode_filename, OBJ_CASE_INSENSITIVE, NULL, NULL); + + a->debug("trying to open NT Section %s in %s mode...", filename, w ? "read/write" : "read-only"); + physmem->section_handle = INVALID_HANDLE_VALUE; + status = physmem->NtOpenSection(&physmem->section_handle, SECTION_MAP_READ | (w ? SECTION_MAP_WRITE : 0), &attributes); + + pci_mfree(wide_filename); + + if (status < 0 || physmem->section_handle == INVALID_HANDLE_VALUE) + { + FreeLibrary(physmem->ntdll); + physmem->ntdll = NULL; + physmem->section_handle = INVALID_HANDLE_VALUE; + if (status == 0) + a->debug("failed."); + else if (physmem->RtlNtStatusToDosError) + a->debug("failed: %s (0x%lx).", win32_strerror(physmem->RtlNtStatusToDosError(status)), status); + else + a->debug("failed: 0x%lx.", status); + switch (status) + { + case STATUS_INVALID_PARAMETER: /* SectionHandle or ObjectAttributes parameter is invalid */ + errno = EINVAL; + break; + case STATUS_OBJECT_NAME_NOT_FOUND: /* Section name in ObjectAttributes.ObjectName does not exist */ + errno = ENOENT; + break; + case STATUS_ACCESS_DENIED: /* No permission to access Section name in ObjectAttributes.ObjectName */ + errno = EACCES; + break; + default: /* Other unspecified error */ + errno = EINVAL; + break; + } + return 0; + } + + a->debug("success."); + return 1; +} + +void +physmem_init_config(struct pci_access *a) +{ + pci_define_param(a, "devmem.path", PCI_PATH_DEVMEM_DEVICE, "NT path to the PhysicalMemory NT Section" +#if defined(__i386__) || defined(_M_IX86) + " or \"vxdcall\" or \"w32skrnl\"" +#endif + ); +} + +int +physmem_access(struct pci_access *a, int w) +{ + struct physmem *physmem = physmem_open(a, w); + if (!physmem) + return -1; + physmem_close(physmem); + return 0; +} + +struct physmem * +physmem_open(struct pci_access *a, int w) +{ + const char *devmem = pci_get_param(a, "devmem.path"); +#if defined(__i386__) || defined(_M_IX86) + int force_vxdcall = strcmp(devmem, "vxdcall") == 0; + int force_w32skrnl = strcmp(devmem, "w32skrnl") == 0; +#endif + struct physmem *physmem = pci_malloc(a, sizeof(*physmem)); + + memset(physmem, 0, sizeof(*physmem)); + physmem->section_handle = INVALID_HANDLE_VALUE; + + errno = ENOENT; + + if ( +#if defined(__i386__) || defined(_M_IX86) + !force_vxdcall && !force_w32skrnl && +#endif + init_physmem_ntdll(physmem, a, devmem, w)) + return physmem; + +#if defined(__i386__) || defined(_M_IX86) + if (!force_w32skrnl && init_physmem_vxdcall(physmem, a)) + return physmem; + + if (!force_vxdcall && init_physmem_w32skrnl(physmem, a)) + return physmem; +#endif + + a->debug("no windows method for physical memory access."); + pci_mfree(physmem); + return NULL; +} + +void +physmem_close(struct physmem *physmem) +{ + if (physmem->section_handle != INVALID_HANDLE_VALUE) + CloseHandle(physmem->section_handle); + if (physmem->ntdll) + FreeLibrary(physmem->ntdll); + pci_mfree(physmem); +} + +long +physmem_get_pagesize(struct physmem *physmem UNUSED) +{ + SYSTEM_INFO system_info; + system_info.dwPageSize = 0; + GetSystemInfo(&system_info); + return system_info.dwPageSize; +} + +void * +physmem_map(struct physmem *physmem, u64 addr, size_t length, int w) +{ + LARGE_INTEGER section_offset; + NTSTATUS status; + SIZE_T view_size; + VOID *ptr; + + if (physmem->section_handle != INVALID_HANDLE_VALUE) + { + /* + * Note: Do not use WinAPI function MapViewOfFile() because it makes memory + * mapping available also for all child processes that are spawned in the + * future. NtMapViewOfSection() allows to specify ViewUnmap parameter which + * creates mapping just for this process and not for future child processes. + * For security reasons we do not want this physical address mapping to be + * present also in future spawned processes. + */ + ptr = NULL; + section_offset.QuadPart = addr; + view_size = length; + status = physmem->NtMapViewOfSection(physmem->section_handle, GetCurrentProcess(), &ptr, 0, 0, §ion_offset, &view_size, ViewUnmap, 0, w ? PAGE_READWRITE : PAGE_READONLY); + if (status < 0) + { + switch (status) + { + case STATUS_INVALID_HANDLE: /* Invalid SectionHandle (physmem->section_handle) */ + case STATUS_INVALID_PARAMETER_3: /* Invalid BaseAddress parameter (&ptr) */ + case STATUS_CONFLICTING_ADDRESSES: /* Invalid value of BaseAddress pointer (ptr) */ + case STATUS_MAPPED_ALIGNMENT: /* Invalid value of BaseAddress pointer (ptr) or SectionOffset (section_offset) */ + case STATUS_INVALID_PARAMETER_4: /* Invalid ZeroBits parameter (0) */ + case STATUS_INVALID_PARAMETER_5: /* Invalid CommitSize parameter (0) */ + case STATUS_INVALID_PARAMETER_8: /* Invalid InheritDisposition parameter (ViewUnmap) */ + case STATUS_INVALID_PARAMETER_9: /* Invalid AllocationType parameter (0) */ + case STATUS_SECTION_PROTECTION: /* Invalid Protect parameter (based on w) */ + case STATUS_INVALID_PAGE_PROTECTION: /* Invalid Protect parameter (based on w) */ + errno = EINVAL; + break; + case STATUS_INVALID_VIEW_SIZE: /* Invalid SectionOffset / ViewSize range (section_offset, view_size) */ + errno = ENXIO; + break; + case STATUS_INSUFFICIENT_RESOURCES: /* Quota limit exceeded */ + case STATUS_NO_MEMORY: /* Memory limit exceeded */ + errno = ENOMEM; + break; + case STATUS_ACCESS_DENIED: /* No permission to create mapping */ + errno = EPERM; + break; + default: /* Other unspecified error */ + errno = EACCES; + break; + } + return (void *)-1; + } + + return ptr; + } +#if defined(__i386__) || defined(_M_IX86) + else if (physmem->VxDCall2 || physmem->w32skrnl_dpmi_lcall_ptr) + { + BOOL success; + DWORD virt; + + /* + * These two methods support mapping only the first 4 GB of physical memory + * and mapped memory is always read/write. There is no way to create + * read-only mapping, so argument "w" is ignored. + */ + if (addr >= 0xffffffffUL || addr + length > 0xffffffffUL) + { + errno = EOVERFLOW; + return (void *)-1; + } + + if (physmem->VxDCall2) + success = vdxcall_physical_address_mapping(physmem, (DWORD)addr, length, &virt); + else + success = w32skrnl_physical_address_mapping(physmem, (DWORD)addr, length, &virt); + + /* Both above functions set errno on failure. */ + if (!success) + return (void *)-1; + + /* Virtual address from our view is calculated from the base offset. */ + ptr = (VOID *)(virt - physmem->base_addr_offset); + return ptr; + } +#endif + + + /* invalid physmem parameter */ + errno = EBADF; + return (void *)-1; +} + +int +physmem_unmap(struct physmem *physmem, void *ptr, size_t length) +{ + long pagesize = physmem_get_pagesize(physmem); + MEMORY_BASIC_INFORMATION info; + NTSTATUS status; + + if (physmem->section_handle != INVALID_HANDLE_VALUE) + { + /* + * NtUnmapViewOfSection() unmaps entire memory range previously mapped by + * NtMapViewOfSection(). The specified ptr (BaseAddress) does not have to + * point to the beginning of the mapped memory range. + * + * So verify that the ptr argument is the beginning of the mapped range + * and length argument is the length of mapped range. + */ + + if (VirtualQuery(ptr, &info, sizeof(info)) != sizeof(info)) + { + errno = EINVAL; + return -1; + } + + /* RegionSize is already page aligned, but length does not have to be. */ + if (info.AllocationBase != ptr || info.RegionSize != ((length + pagesize-1) & ~(pagesize-1))) + { + errno = EINVAL; + return -1; + } + + status = physmem->NtUnmapViewOfSection(GetCurrentProcess(), ptr); + if (status < 0) + { + switch (status) + { + case STATUS_NOT_MAPPED_VIEW: /* BaseAddress parameter (ptr) not mapped */ + errno = EINVAL; + break; + case STATUS_ACCESS_DENIED: /* No permission to unmap BaseAddress (ptr) */ + errno = EPERM; + break; + default: /* Other unspecified error */ + errno = EACCES; + break; + } + return -1; + } + + return 0; + } +#if defined(__i386__) || defined(_M_IX86) + else if (physmem->VxDCall2 || physmem->w32skrnl_dpmi_lcall_ptr) + { + /* There is no way to unmap physical memory mapped by these methods. */ + errno = ENOSYS; + return -1; + } +#endif + + /* invalid physmem parameter */ + errno = EBADF; + return -1; +} -- 2.39.2