/* * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "config.h" #include "wine/port.h" #include #include #include #include #include #ifdef HAVE_SYS_MMAN_H # include #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%08lx) returns %p.\n", x, lin ); return lin; }