This commit is contained in:
kanjitalk755 2021-06-24 14:26:16 +09:00
parent 0538eb0cc0
commit 763fe81f0d
2 changed files with 0 additions and 757 deletions

View File

@ -1,619 +0,0 @@
/*
* vm_alloc.cpp - Wrapper to various virtual memory allocation schemes
* (supports mmap, vm_allocate or fallbacks to malloc)
*
* Basilisk II (C) 1997-2008 Christian Bauer
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef HAVE_WIN32_VM
#define WIN32_LEAN_AND_MEAN /* avoid including junk */
#include <windows.h>
#endif
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include "vm_alloc.h"
#if defined(__APPLE__) && defined(__MACH__)
#include <sys/utsname.h>
#endif
#ifdef HAVE_MACH_VM
#ifndef HAVE_MACH_TASK_SELF
#ifdef HAVE_TASK_SELF
#define mach_task_self task_self
#else
#error "No task_self(), you lose."
#endif
#endif
#endif
#ifdef HAVE_WIN32_VM
/* Windows is either ILP32 or LLP64 */
typedef UINT_PTR vm_uintptr_t;
#else
/* Other systems are sane as they are either ILP32 or LP64 */
typedef unsigned long vm_uintptr_t;
#endif
/* We want MAP_32BIT, if available, for SheepShaver and BasiliskII
because the emulated target is 32-bit and this helps to allocate
memory so that branches could be resolved more easily (32-bit
displacement to code in .text), on AMD64 for example. */
#if defined(__hpux)
#define MAP_32BIT MAP_ADDR32
#endif
#ifndef MAP_32BIT
#define MAP_32BIT 0
#endif
#ifdef __FreeBSD__
#define FORCE_MAP_32BIT MAP_FIXED
#else
#define FORCE_MAP_32BIT MAP_32BIT
#endif
#ifndef MAP_ANON
#define MAP_ANON 0
#endif
#ifndef MAP_ANONYMOUS
#define MAP_ANONYMOUS 0
#endif
/* NOTE: on linux MAP_32BIT is only implemented on AMD64
it is a null op on all other architectures
thus the MAP_BASE setting below is the only thing
ensuring low addresses on aarch64 for example */
#define MAP_EXTRA_FLAGS (MAP_32BIT)
#ifdef HAVE_MMAP_VM
#if (defined(__linux__) && defined(__i386__)) || defined(__FreeBSD__) || HAVE_LINKER_SCRIPT
/* Force a reasonnable address below 0x80000000 on x86 so that we
don't get addresses above when the program is run on AMD64.
NOTE: this is empirically determined on Linux/x86. */
#define MAP_BASE 0x10000000
#elif DIRECT_ADDRESSING
/* linux does not implement any useful fallback behavior
such as allocating the next available address
and the first 4k-64k of address space is marked unavailable
for security reasons (see https://wiki.debian.org/mmap_min_addr)
so we must start requesting after the first page
or we get a high 64bit address that will crash direct addressing
leaving NULL unmapped is a good idea anyway for debugging reasons */
#define MAP_BASE 0x00010000
#else
#define MAP_BASE 0x00000000
#endif
static char * next_address = (char *)MAP_BASE;
#ifdef HAVE_MMAP_ANON
#define map_flags (MAP_ANON | MAP_EXTRA_FLAGS)
#define zero_fd -1
#else
#ifdef HAVE_MMAP_ANONYMOUS
#define map_flags (MAP_ANONYMOUS | MAP_EXTRA_FLAGS)
#define zero_fd -1
#else
#define map_flags (MAP_EXTRA_FLAGS)
static int zero_fd = -1;
#endif
#endif
#endif
/* Translate generic VM map flags to host values. */
#ifdef HAVE_MMAP_VM
static int translate_map_flags(int vm_flags)
{
int flags = 0;
if (vm_flags & VM_MAP_SHARED)
flags |= MAP_SHARED;
if (vm_flags & VM_MAP_PRIVATE)
flags |= MAP_PRIVATE;
if (vm_flags & VM_MAP_FIXED)
flags |= MAP_FIXED;
if (vm_flags & VM_MAP_32BIT)
flags |= FORCE_MAP_32BIT;
return flags;
}
#endif
/* Align ADDR and SIZE to 64K boundaries. */
#ifdef HAVE_WIN32_VM
static inline LPVOID align_addr_segment(LPVOID addr)
{
return LPVOID(vm_uintptr_t(addr) & ~vm_uintptr_t(0xFFFF));
}
static inline DWORD align_size_segment(LPVOID addr, DWORD size)
{
return size + ((vm_uintptr_t)addr - (vm_uintptr_t)align_addr_segment(addr));
}
#endif
/* Translate generic VM prot flags to host values. */
#ifdef HAVE_WIN32_VM
static int translate_prot_flags(int prot_flags)
{
int prot = PAGE_READWRITE;
if (prot_flags == (VM_PAGE_EXECUTE | VM_PAGE_READ | VM_PAGE_WRITE))
prot = PAGE_EXECUTE_READWRITE;
else if (prot_flags == (VM_PAGE_EXECUTE | VM_PAGE_READ))
prot = PAGE_EXECUTE_READ;
else if (prot_flags == (VM_PAGE_READ | VM_PAGE_WRITE))
prot = PAGE_READWRITE;
else if (prot_flags == VM_PAGE_READ)
prot = PAGE_READONLY;
else if (prot_flags == 0)
prot = PAGE_NOACCESS;
return prot;
}
#endif
/* Translate Mach return codes to POSIX errno values. */
#ifdef HAVE_MACH_VM
static int vm_error(kern_return_t ret_code)
{
switch (ret_code) {
case KERN_SUCCESS:
return 0;
case KERN_INVALID_ADDRESS:
case KERN_NO_SPACE:
return ENOMEM;
case KERN_PROTECTION_FAILURE:
return EACCES;
default:
return EINVAL;
}
}
#endif
/* Initialize the VM system. Returns 0 if successful, -1 for errors. */
int vm_init(void)
{
#ifdef HAVE_MMAP_VM
#ifndef zero_fd
zero_fd = open("/dev/zero", O_RDWR);
if (zero_fd < 0)
return -1;
#endif
#endif
// On 10.4 and earlier, reset CrashReporter's task signal handler to
// avoid having it show up for signals that get handled.
#if defined(__APPLE__) && defined(__MACH__)
struct utsname info;
if (!uname(&info) && atoi(info.release) <= 8) {
task_set_exception_ports(mach_task_self(),
EXC_MASK_BAD_ACCESS | EXC_MASK_ARITHMETIC,
MACH_PORT_NULL,
EXCEPTION_STATE_IDENTITY,
MACHINE_THREAD_STATE);
}
#endif
return 0;
}
/* Deallocate all internal data used to wrap virtual memory allocators. */
void vm_exit(void)
{
#ifdef HAVE_MMAP_VM
#ifndef zero_fd
if (zero_fd != -1) {
close(zero_fd);
zero_fd = -1;
}
#endif
#endif
}
static void *reserved_buf;
static const size_t RESERVED_SIZE = 64 * 1024 * 1024; // for 5K Retina
void *vm_acquire_reserved(size_t size) {
return reserved_buf && size <= RESERVED_SIZE ? reserved_buf : VM_MAP_FAILED;
}
int vm_init_reserved(void *hostAddress) {
int result = vm_acquire_fixed(hostAddress, RESERVED_SIZE);
if (result >= 0)
reserved_buf = hostAddress;
return result;
}
/* Allocate zero-filled memory of SIZE bytes. The mapping is private
and default protection bits are read / write. The return value
is the actual mapping address chosen or VM_MAP_FAILED for errors. */
void * vm_acquire(size_t size, int options)
{
void * addr;
errno = 0;
// VM_MAP_FIXED are to be used with vm_acquire_fixed() only
if (options & VM_MAP_FIXED)
return VM_MAP_FAILED;
#ifndef HAVE_VM_WRITE_WATCH
if (options & VM_MAP_WRITE_WATCH)
return VM_MAP_FAILED;
#endif
#if defined(HAVE_MACH_VM)
// vm_allocate() returns a zero-filled memory region
kern_return_t ret_code = vm_allocate(mach_task_self(), (vm_address_t *)&addr, reserved_buf ? size : size + RESERVED_SIZE, TRUE);
if (ret_code != KERN_SUCCESS) {
errno = vm_error(ret_code);
return VM_MAP_FAILED;
}
if (!reserved_buf)
reserved_buf = (char *)addr + size;
#elif defined(HAVE_MMAP_VM)
int fd = zero_fd;
int the_map_flags = translate_map_flags(options) | map_flags;
if ((addr = mmap((caddr_t)next_address, size, VM_PAGE_DEFAULT, the_map_flags, fd, 0)) == (void *)MAP_FAILED)
return VM_MAP_FAILED;
#if DIRECT_ADDRESSING
// If MAP_32BIT and MAP_BASE fail to ensure
// a 32-bit address crash now instead of later.
// FIXME: make everything 64-bit clean and tear this all out.
if(sizeof(void *) > 4 && (options & VM_MAP_32BIT))
assert((size_t)addr<0xffffffffL);
#endif
next_address = (char *)addr + size;
#elif defined(HAVE_WIN32_VM)
int alloc_type = MEM_RESERVE | MEM_COMMIT;
if (options & VM_MAP_WRITE_WATCH)
alloc_type |= MEM_WRITE_WATCH;
if ((addr = VirtualAlloc(NULL, size, alloc_type, PAGE_EXECUTE_READWRITE)) == NULL)
return VM_MAP_FAILED;
#else
if ((addr = calloc(size, 1)) == 0)
return VM_MAP_FAILED;
// Omit changes for protections because they are not supported in this mode
return addr;
#endif
// Explicitely protect the newly mapped region here because on some systems,
// say MacOS X, mmap() doesn't honour the requested protection flags.
if (vm_protect(addr, size, VM_PAGE_DEFAULT) != 0)
return VM_MAP_FAILED;
return addr;
}
/* Allocate zero-filled memory at exactly ADDR (which must be page-aligned).
Retuns 0 if successful, -1 on errors. */
int vm_acquire_fixed(void * addr, size_t size, int options)
{
errno = 0;
// Fixed mappings are required to be private
if (options & VM_MAP_SHARED)
return -1;
#ifndef HAVE_VM_WRITE_WATCH
if (options & VM_MAP_WRITE_WATCH)
return -1;
#endif
#if defined(HAVE_MACH_VM)
// vm_allocate() returns a zero-filled memory region
kern_return_t ret_code = vm_allocate(mach_task_self(), (vm_address_t *)&addr, size, 0);
if (ret_code != KERN_SUCCESS) {
errno = vm_error(ret_code);
return -1;
}
#elif defined(HAVE_MMAP_VM)
int fd = zero_fd;
int the_map_flags = translate_map_flags(options) | map_flags | MAP_FIXED;
if (mmap((caddr_t)addr, size, VM_PAGE_DEFAULT, the_map_flags, fd, 0) == (void *)MAP_FAILED)
return -1;
#elif defined(HAVE_WIN32_VM)
// Windows cannot allocate Low Memory
if (addr == NULL)
return -1;
int alloc_type = MEM_RESERVE | MEM_COMMIT;
if (options & VM_MAP_WRITE_WATCH)
alloc_type |= MEM_WRITE_WATCH;
// Allocate a possibly offset region to align on 64K boundaries
LPVOID req_addr = align_addr_segment(addr);
DWORD req_size = align_size_segment(addr, size);
LPVOID ret_addr = VirtualAlloc(req_addr, req_size, alloc_type, PAGE_EXECUTE_READWRITE);
if (ret_addr != req_addr)
return -1;
#else
// Unsupported
return -1;
#endif
// Explicitely protect the newly mapped region here because on some systems,
// say MacOS X, mmap() doesn't honour the requested protection flags.
if (vm_protect(addr, size, VM_PAGE_DEFAULT) != 0)
return -1;
return 0;
}
/* Deallocate any mapping for the region starting at ADDR and extending
LEN bytes. Returns 0 if successful, -1 on errors. */
int vm_release(void * addr, size_t size)
{
// Safety check: don't try to release memory that was not allocated
if (addr == VM_MAP_FAILED)
return 0;
#ifdef HAVE_MACH_VM
if (vm_deallocate(mach_task_self(), (vm_address_t)addr, size) != KERN_SUCCESS)
return -1;
#else
#ifdef HAVE_MMAP_VM
if (munmap((caddr_t)addr, size) != 0)
return -1;
#else
#ifdef HAVE_WIN32_VM
if (VirtualFree(align_addr_segment(addr), 0, MEM_RELEASE) == 0)
return -1;
#else
free(addr);
#endif
#endif
#endif
return 0;
}
/* Change the memory protection of the region starting at ADDR and
extending LEN bytes to PROT. Returns 0 if successful, -1 for errors. */
int vm_protect(void * addr, size_t size, int prot)
{
#ifdef HAVE_MACH_VM
int ret_code = vm_protect(mach_task_self(), (vm_address_t)addr, size, 0, prot);
return ret_code == KERN_SUCCESS ? 0 : -1;
#else
#ifdef HAVE_MMAP_VM
int ret_code = mprotect((caddr_t)addr, size, prot);
return ret_code == 0 ? 0 : -1;
#else
#ifdef HAVE_WIN32_VM
DWORD old_prot;
int ret_code = VirtualProtect(addr, size, translate_prot_flags(prot), &old_prot);
return ret_code != 0 ? 0 : -1;
#else
// Unsupported
return -1;
#endif
#endif
#endif
}
/* Return the addresses of the pages that got modified in the
specified range [ ADDR, ADDR + SIZE [ since the last reset of the watch
bits. Returns 0 if successful, -1 for errors. */
int vm_get_write_watch(void * addr, size_t size,
void ** pages, unsigned int * n_pages,
int options)
{
#ifdef HAVE_VM_WRITE_WATCH
#ifdef HAVE_WIN32_VM
DWORD flags = 0;
if (options & VM_WRITE_WATCH_RESET)
flags |= WRITE_WATCH_FLAG_RESET;
ULONG page_size;
ULONG_PTR count = *n_pages;
int ret_code = GetWriteWatch(flags, addr, size, pages, &count, &page_size);
if (ret_code != 0)
return -1;
*n_pages = count;
return 0;
#endif
#endif
// Unsupported
return -1;
}
/* Reset the write-tracking state for the specified range [ ADDR, ADDR
+ SIZE [. Returns 0 if successful, -1 for errors. */
int vm_reset_write_watch(void * addr, size_t size)
{
#ifdef HAVE_VM_WRITE_WATCH
#ifdef HAVE_WIN32_VM
int ret_code = ResetWriteWatch(addr, size);
return ret_code == 0 ? 0 : -1;
#endif
#endif
// Unsupported
return -1;
}
/* Returns the size of a page. */
int vm_get_page_size(void)
{
#ifdef HAVE_WIN32_VM
static vm_uintptr_t page_size = 0;
if (page_size == 0) {
SYSTEM_INFO si;
GetSystemInfo(&si);
page_size = si.dwAllocationGranularity;
}
return page_size;
#else
return getpagesize();
#endif
}
#ifdef CONFIGURE_TEST_VM_WRITE_WATCH
int main(void)
{
int i, j;
vm_init();
vm_uintptr_t page_size = vm_get_page_size();
char *area;
const int n_pages = 7;
const int area_size = n_pages * page_size;
const int map_options = VM_MAP_DEFAULT | VM_MAP_WRITE_WATCH;
if ((area = (char *)vm_acquire(area_size, map_options)) == VM_MAP_FAILED)
return 1;
unsigned int n_modified_pages_expected = 0;
static const int touch_page[n_pages] = { 0, 1, 1, 0, 1, 0, 1 };
for (i = 0; i < n_pages; i++) {
if (touch_page[i]) {
area[i * page_size] = 1;
++n_modified_pages_expected;
}
}
char *modified_pages[n_pages];
unsigned int n_modified_pages = n_pages;
if (vm_get_write_watch(area, area_size, (void **)modified_pages, &n_modified_pages) < 0)
return 2;
if (n_modified_pages != n_modified_pages_expected)
return 3;
for (i = 0, j = 0; i < n_pages; i++) {
char v = area[i * page_size];
if ((touch_page[i] && !v) || (!touch_page[i] && v))
return 4;
if (!touch_page[i])
continue;
if (modified_pages[j] != (area + i * page_size))
return 5;
++j;
}
vm_release(area, area_size);
return 0;
}
#endif
#ifdef CONFIGURE_TEST_VM_MAP
#include <stdlib.h>
#include <signal.h>
static void fault_handler(int sig)
{
exit(1);
}
/* Tests covered here:
- TEST_VM_PROT_* program slices actually succeeds when a crash occurs
- TEST_VM_MAP_ANON* program slices succeeds when it could be compiled
*/
int main(void)
{
vm_init();
signal(SIGSEGV, fault_handler);
#ifdef SIGBUS
signal(SIGBUS, fault_handler);
#endif
#define page_align(address) ((char *)((vm_uintptr_t)(address) & -page_size))
vm_uintptr_t page_size = vm_get_page_size();
const int area_size = 6 * page_size;
volatile char * area = (volatile char *) vm_acquire(area_size);
volatile char * fault_address = area + (page_size * 7) / 2;
#if defined(TEST_VM_MMAP_ANON) || defined(TEST_VM_MMAP_ANONYMOUS)
if (area == VM_MAP_FAILED)
return 1;
if (vm_release((char *)area, area_size) < 0)
return 1;
return 0;
#endif
#if defined(TEST_VM_PROT_NONE_READ) || defined(TEST_VM_PROT_NONE_WRITE)
if (area == VM_MAP_FAILED)
return 0;
if (vm_protect(page_align(fault_address), page_size, VM_PAGE_NOACCESS) < 0)
return 0;
#endif
#if defined(TEST_VM_PROT_RDWR_WRITE)
if (area == VM_MAP_FAILED)
return 1;
if (vm_protect(page_align(fault_address), page_size, VM_PAGE_READ) < 0)
return 1;
if (vm_protect(page_align(fault_address), page_size, VM_PAGE_READ | VM_PAGE_WRITE) < 0)
return 1;
#endif
#if defined(TEST_VM_PROT_READ_WRITE)
if (vm_protect(page_align(fault_address), page_size, VM_PAGE_READ) < 0)
return 0;
#endif
#if defined(TEST_VM_PROT_NONE_READ)
// this should cause a core dump
char foo = *fault_address;
return 0;
#endif
#if defined(TEST_VM_PROT_NONE_WRITE) || defined(TEST_VM_PROT_READ_WRITE)
// this should cause a core dump
*fault_address = 'z';
return 0;
#endif
#if defined(TEST_VM_PROT_RDWR_WRITE)
// this should not cause a core dump
*fault_address = 'z';
return 0;
#endif
}
#endif

View File

@ -1,138 +0,0 @@
/*
* vm_alloc.h - Wrapper to various virtual memory allocation schemes
* (supports mmap, vm_allocate or fallbacks to malloc)
*
* Basilisk II (C) 1997-2008 Christian Bauer
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef VM_ALLOC_H
#define VM_ALLOC_H
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_MMAN_H
#include <sys/mman.h>
#endif
#ifdef HAVE_MACH_VM
extern "C" {
#include <mach/mach.h>
#include <mach/task.h>
}
#endif
#include <cassert>
/* Return value of `vm_acquire' in case of an error. */
#ifdef HAVE_MACH_VM
#define VM_MAP_FAILED ((void *)-1)
#else
#ifdef HAVE_MMAP_VM
#define VM_MAP_FAILED ((void *)-1)
#else
#define VM_MAP_FAILED 0
#endif
#endif
/* Option to vm_get_write_watch() to reset the write-tracking state
once it was retrieved. Otherwise, you have to manually call
vm_reset_write_watch() and potentially lose some info. */
#define VM_WRITE_WATCH_RESET 0x01
/* Mapping options. */
#define VM_MAP_SHARED 0x01
#define VM_MAP_PRIVATE 0x02
#define VM_MAP_FIXED 0x04
#define VM_MAP_32BIT 0x08
#define VM_MAP_WRITE_WATCH 0x10
/* Default mapping options. */
#define VM_MAP_DEFAULT (VM_MAP_PRIVATE)
/* Protection bits. */
#ifdef HAVE_MACH_VM
#define VM_PAGE_NOACCESS VM_PROT_NONE
#define VM_PAGE_READ VM_PROT_READ
#define VM_PAGE_WRITE VM_PROT_WRITE
#define VM_PAGE_EXECUTE VM_PROT_EXECUTE
#else
#ifdef HAVE_MMAP_VM
#define VM_PAGE_NOACCESS PROT_NONE
#define VM_PAGE_READ PROT_READ
#define VM_PAGE_WRITE PROT_WRITE
#define VM_PAGE_EXECUTE PROT_EXEC
#else
#define VM_PAGE_NOACCESS 0x0
#define VM_PAGE_READ 0x1
#define VM_PAGE_WRITE 0x2
#define VM_PAGE_EXECUTE 0x4
#endif
#endif
/* Default protection bits. */
#define VM_PAGE_DEFAULT (VM_PAGE_READ | VM_PAGE_WRITE)
/* Initialize the VM system. Returns 0 if successful, -1 for errors. */
extern int vm_init(void);
/* Deallocate all internal data used to wrap virtual memory allocators. */
extern void vm_exit(void);
/* Allocate zero-filled memory of SIZE bytes. The mapping is private
and default protection bits are read / write. The return value
is the actual mapping address chosen or VM_MAP_FAILED for errors. */
extern void * vm_acquire(size_t size, int options = VM_MAP_DEFAULT);
extern void * vm_acquire_reserved(size_t size);
/* Allocate zero-filled memory at exactly ADDR (which must be page-aligned).
Returns 0 if successful, -1 on errors. */
extern int vm_acquire_fixed(void * addr, size_t size, int options = VM_MAP_DEFAULT);
/* Deallocate any mapping for the region starting at ADDR and extending
LEN bytes. Returns 0 if successful, -1 on errors. */
extern int vm_release(void * addr, size_t size);
/* Change the memory protection of the region starting at ADDR and
extending SIZE bytes to PROT. Returns 0 if successful, -1 for errors. */
extern int vm_protect(void * addr, size_t size, int prot);
/* Return the addresses of the pages that got modified since the last
reset of the write-tracking state for the specified range [ ADDR,
ADDR + SIZE [. Returns 0 if successful, -1 for errors. */
extern int vm_get_write_watch(void * addr, size_t size,
void ** pages, unsigned int * n_pages,
int options = 0);
/* Reset the write-tracking state for the specified range [ ADDR, ADDR
+ SIZE [. Returns 0 if successful, -1 for errors. */
extern int vm_reset_write_watch(void * addr, size_t size);
/* Returns the size of a page. */
extern int vm_get_page_size(void);
#endif /* VM_ALLOC_H */