| /* | |
| * DOS memory emulation | |
| * | |
| * Copyright 1995 Alexandre Julliard | |
| * Copyright 1996 Marcus Meissner | |
| * | |
| * This library is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU Lesser General Public | |
| * License as published by the Free Software Foundation; either | |
| * version 2.1 of the License, or (at your option) any later version. | |
| * | |
| * This library is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| * Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; if not, write to the Free Software | |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA | |
| */ | |
| #include "config.h" | |
| #include "wine/port.h" | |
| #include <signal.h> | |
| #include <stdarg.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <sys/types.h> | |
| #ifdef HAVE_SYS_MMAN_H | |
| # include <sys/mman.h> | |
| #endif | |
| #include "windef.h" | |
| #include "winbase.h" | |
| #include "excpt.h" | |
| #include "winternl.h" | |
| #include "wine/winbase16.h" | |
| #include "kernel_private.h" | |
| #include "toolhelp.h" | |
| #include "wine/debug.h" | |
| WINE_DEFAULT_DEBUG_CHANNEL(dosmem); | |
| WINE_DECLARE_DEBUG_CHANNEL(selector); | |
| WORD DOSMEM_0000H; /* segment at 0:0 */ | |
| WORD DOSMEM_BiosDataSeg; /* BIOS data segment at 0x40:0 */ | |
| WORD DOSMEM_BiosSysSeg; /* BIOS ROM segment at 0xf000:0 */ | |
| /* DOS memory highest address (including HMA) */ | |
| #define DOSMEM_SIZE 0x110000 | |
| #define DOSMEM_64KB 0x10000 | |
| /* when looking at DOS and real mode memory, we activate in three different | |
| * modes, depending the situation. | |
| * 1/ By default (protected mode), the first MB of memory (actually 0x110000, | |
| * when you also look at the HMA part) is always reserved, whatever you do. | |
| * We allocated some PM selectors to this memory, even if this area is not | |
| * committed at startup | |
| * 2/ if a program tries to use the memory through the selectors, we actually | |
| * commit this memory, made of: BIOS segment, but also some system | |
| * information, usually low in memory that we map for the circumstance also | |
| * in the BIOS segment, so that we keep the low memory protected (for NULL | |
| * pointer deref catching for example). In this case, we're still in PM | |
| * mode, accessing part of the "physical" real mode memory. In fact, we don't | |
| * map all the first meg, we keep 64k uncommitted to still catch NULL | |
| * pointers dereference | |
| * 3/ if the process enters the real mode, then we (also) commit the full first | |
| * MB of memory (and also initialize the DOS structures in it). | |
| */ | |
| /* DOS memory base (linear in process address space) */ | |
| static char *DOSMEM_dosmem; | |
| /* number of bytes protected from _dosmem. 0 when DOS memory is initialized, | |
| * 64k otherwise to trap NULL pointers deref */ | |
| static DWORD DOSMEM_protect; | |
| static LONG WINAPI dosmem_handler(EXCEPTION_POINTERS* except); | |
| struct winedos_exports winedos; | |
| BOOL load_winedos(void) | |
| { | |
| static HANDLE hRunOnce /* = 0 */; | |
| static HMODULE hWineDos /* = 0 */; | |
| /* FIXME: this isn't 100% thread safe, as we won't catch access to 1MB while | |
| * loading winedos (and may return uninitialized valued) | |
| */ | |
| if (hWineDos) goto done; | |
| if (hRunOnce == 0) | |
| { | |
| HANDLE hEvent = CreateEventW( NULL, TRUE, FALSE, NULL ); | |
| if (InterlockedCompareExchangePointer( (PVOID)&hRunOnce, hEvent, 0 ) == 0) | |
| { | |
| HMODULE hModule; | |
| /* ok, we're the winning thread */ | |
| if (!VirtualProtect( DOSMEM_dosmem + DOSMEM_protect, | |
| DOSMEM_SIZE - DOSMEM_protect, | |
| PAGE_READWRITE, NULL ) || | |
| !(hModule = LoadLibraryA( "winedos.dll" ))) | |
| { | |
| ERR("Could not load winedos.dll, DOS subsystem unavailable\n"); | |
| hModule = (HMODULE)1; /* not to try to load it again */ | |
| } | |
| else | |
| { | |
| #define GET_ADDR(func) winedos.func = (void *)GetProcAddress( hModule, #func ); | |
| GET_ADDR(AllocDosBlock); | |
| GET_ADDR(FreeDosBlock); | |
| GET_ADDR(ResizeDosBlock); | |
| GET_ADDR(inport); | |
| GET_ADDR(outport); | |
| GET_ADDR(EmulateInterruptPM); | |
| GET_ADDR(CallBuiltinHandler); | |
| GET_ADDR(BiosTick); | |
| #undef GET_ADDR | |
| } | |
| RtlRemoveVectoredExceptionHandler( dosmem_handler ); | |
| hWineDos = hModule; | |
| SetEvent( hRunOnce ); | |
| goto done; | |
| } | |
| /* someone beat us here... */ | |
| CloseHandle( hEvent ); | |
| } | |
| /* and wait for the winner to have finished */ | |
| WaitForSingleObject( hRunOnce, INFINITE ); | |
| done: | |
| return (hWineDos != (HMODULE)1); | |
| } | |
| /****************************************************************** | |
| * dosmem_handler | |
| * | |
| * Handler to catch access to our 1MB address space reserved for real memory | |
| */ | |
| static LONG WINAPI dosmem_handler(EXCEPTION_POINTERS* except) | |
| { | |
| if (except->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) | |
| { | |
| DWORD addr = except->ExceptionRecord->ExceptionInformation[1]; | |
| if (addr >= (ULONG_PTR)DOSMEM_dosmem + DOSMEM_protect && | |
| addr < (ULONG_PTR)DOSMEM_dosmem + DOSMEM_SIZE) | |
| { | |
| if (load_winedos()) return EXCEPTION_CONTINUE_EXECUTION; | |
| } | |
| } | |
| return EXCEPTION_CONTINUE_SEARCH; | |
| } | |
| /********************************************************************** | |
| * setup_dos_mem | |
| * | |
| * Setup the first megabyte for DOS memory access | |
| */ | |
| static char* setup_dos_mem(void) | |
| { | |
| int sys_offset = 0; | |
| int page_size = getpagesize(); | |
| void *addr = NULL; | |
| if (wine_mmap_is_in_reserved_area( NULL, DOSMEM_SIZE ) != 1) | |
| { | |
| addr = wine_anon_mmap( (void *)page_size, DOSMEM_SIZE-page_size, | |
| PROT_READ | PROT_WRITE, 0 ); | |
| if (addr == (void *)page_size) addr = NULL; /* we got what we wanted */ | |
| else munmap( addr, DOSMEM_SIZE - page_size ); | |
| } | |
| if (!addr) | |
| { | |
| /* now reserve from address 0 */ | |
| wine_anon_mmap( NULL, DOSMEM_SIZE, PROT_NONE, MAP_FIXED ); | |
| /* inform the memory manager that there is a mapping here, but don't commit yet */ | |
| VirtualAlloc( NULL, DOSMEM_SIZE, MEM_RESERVE | MEM_SYSTEM, PAGE_NOACCESS ); | |
| sys_offset = 0xf0000; | |
| DOSMEM_protect = DOSMEM_64KB; | |
| } | |
| else | |
| { | |
| ERR("Cannot use first megabyte for DOS address space, please report\n" ); | |
| /* allocate the DOS area somewhere else */ | |
| addr = VirtualAlloc( NULL, DOSMEM_SIZE, MEM_RESERVE, PAGE_NOACCESS ); | |
| if (!addr) | |
| { | |
| ERR( "Cannot allocate DOS memory\n" ); | |
| ExitProcess(1); | |
| } | |
| } | |
| DOSMEM_dosmem = addr; | |
| RtlAddVectoredExceptionHandler(FALSE, dosmem_handler); | |
| return (char*)addr + sys_offset; | |
| } | |
| /*********************************************************************** | |
| * DOSMEM_Init | |
| * | |
| * Create the dos memory segments, and store them into the KERNEL | |
| * exported values. | |
| */ | |
| BOOL DOSMEM_Init(void) | |
| { | |
| char* sysmem = setup_dos_mem(); | |
| DOSMEM_0000H = GLOBAL_CreateBlock( GMEM_FIXED, sysmem, | |
| DOSMEM_64KB, 0, WINE_LDT_FLAGS_DATA ); | |
| DOSMEM_BiosDataSeg = GLOBAL_CreateBlock( GMEM_FIXED, sysmem + 0x400, | |
| 0x100, 0, WINE_LDT_FLAGS_DATA ); | |
| DOSMEM_BiosSysSeg = GLOBAL_CreateBlock( GMEM_FIXED, DOSMEM_dosmem + 0xf0000, | |
| DOSMEM_64KB, 0, WINE_LDT_FLAGS_DATA ); | |
| return TRUE; | |
| } | |
| /*********************************************************************** | |
| * DOSMEM_MapLinearToDos | |
| * | |
| * Linear address to the DOS address space. | |
| */ | |
| UINT DOSMEM_MapLinearToDos(LPVOID ptr) | |
| { | |
| if (((char*)ptr >= DOSMEM_dosmem) && | |
| ((char*)ptr < DOSMEM_dosmem + DOSMEM_SIZE)) | |
| return (char *)ptr - DOSMEM_dosmem; | |
| return (UINT)ptr; | |
| } | |
| /*********************************************************************** | |
| * DOSMEM_MapDosToLinear | |
| * | |
| * DOS linear address to the linear address space. | |
| */ | |
| LPVOID DOSMEM_MapDosToLinear(UINT ptr) | |
| { | |
| if (ptr < DOSMEM_SIZE) return DOSMEM_dosmem + ptr; | |
| return (LPVOID)ptr; | |
| } | |
| /*********************************************************************** | |
| * DOSMEM_MapRealToLinear | |
| * | |
| * Real mode DOS address into a linear pointer | |
| */ | |
| LPVOID DOSMEM_MapRealToLinear(DWORD x) | |
| { | |
| LPVOID lin; | |
| lin = DOSMEM_dosmem + HIWORD(x) * 16 + LOWORD(x); | |
| TRACE_(selector)("(0x%08x) returns %p.\n", x, lin ); | |
| return lin; | |
| } |