зеркало из https://github.com/mozilla/gecko-dev.git
1217 строки
31 KiB
C++
1217 строки
31 KiB
C++
/* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version 1.1 (the
|
|
* "License"); you may not use this file except in compliance with the License. You may obtain
|
|
* a copy of the License at http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
|
|
* WARRANTY OF ANY KIND, either express or implied. See the License for the specific
|
|
* language governing rights and limitations under the License.
|
|
*
|
|
* The Original Code is [Open Source Virtual Machine.]
|
|
*
|
|
* The Initial Developer of the Original Code is Adobe System Incorporated. Portions created
|
|
* by the Initial Developer are Copyright (C)[ 2004-2006 ] Adobe Systems Incorporated. All Rights
|
|
* Reserved.
|
|
*
|
|
* Contributor(s): Adobe AS3 Team
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of either the GNU
|
|
* General Public License Version 2 or later (the "GPL"), or the GNU Lesser General Public
|
|
* License Version 2.1 or later (the "LGPL"), in which case the provisions of the GPL or the
|
|
* LGPL are applicable instead of those above. If you wish to allow use of your version of this
|
|
* file only under the terms of either the GPL or the LGPL, and not to allow others to use your
|
|
* version of this file under the terms of the MPL, indicate your decision by deleting provisions
|
|
* above and replace them with the notice and other provisions required by the GPL or the
|
|
* LGPL. If you do not delete the provisions above, a recipient may use your version of this file
|
|
* under the terms of any one of the MPL, the GPL or the LGPL.
|
|
*
|
|
***** END LICENSE BLOCK ***** */
|
|
|
|
#include "avmplus.h"
|
|
|
|
#ifdef DARWIN
|
|
#include <Carbon/Carbon.h>
|
|
#endif
|
|
|
|
#ifdef AVMPLUS_ROSETTA
|
|
// Include sysctlbyname and getpid
|
|
#include <sys/types.h>
|
|
#include <sys/sysctl.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#ifdef AVMPLUS_LINUX
|
|
#define _GNU_SOURCE
|
|
#include <signal.h>
|
|
#endif
|
|
|
|
/* number of pages commited each time grow() is called */
|
|
#define PAGES_PER_GROW 4
|
|
|
|
#ifdef AVMPLUS_MACH_EXCEPTIONS
|
|
extern "C"
|
|
{
|
|
extern boolean_t exc_server(mach_msg_header_t *,
|
|
mach_msg_header_t *);
|
|
|
|
extern kern_return_t exception_raise(mach_port_t,
|
|
mach_port_t,
|
|
mach_port_t,
|
|
exception_type_t,
|
|
exception_data_t,
|
|
mach_msg_type_number_t);
|
|
|
|
extern kern_return_t exception_raise_state(mach_port_t,
|
|
mach_port_t,
|
|
mach_port_t,
|
|
exception_type_t,
|
|
exception_data_t,
|
|
mach_msg_type_number_t,
|
|
thread_state_flavor_t*,
|
|
thread_state_t,
|
|
mach_msg_type_number_t,
|
|
thread_state_t,
|
|
mach_msg_type_number_t*);
|
|
|
|
extern kern_return_t exception_raise_state_identity(mach_port_t,
|
|
mach_port_t,
|
|
mach_port_t,
|
|
exception_type_t,
|
|
exception_data_t,
|
|
mach_msg_type_number_t,
|
|
thread_state_flavor_t*,
|
|
thread_state_t,
|
|
mach_msg_type_number_t,
|
|
thread_state_t,
|
|
mach_msg_type_number_t*);
|
|
|
|
#ifdef USE_EXC_SERVER
|
|
kern_return_t catch_exception_raise(mach_port_t exception_port,
|
|
mach_port_t thread,
|
|
mach_port_t task,
|
|
exception_type_t exception,
|
|
exception_data_t code,
|
|
mach_msg_type_number_t code_count)
|
|
{
|
|
return avmplus::GenericGuard::catch_exception_raise(exception_port,
|
|
thread,
|
|
task,
|
|
exception,
|
|
code,
|
|
code_count);
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
namespace avmplus
|
|
{
|
|
GrowableBuffer::GrowableBuffer(MMgc::GCHeap *gcheap)
|
|
: heap(gcheap)
|
|
{
|
|
init();
|
|
AvmAssert( (size_t)MathUtils::nextPowerOfTwo(pageSize()-1) == pageSize() );
|
|
}
|
|
|
|
GrowableBuffer::~GrowableBuffer()
|
|
{
|
|
free();
|
|
}
|
|
|
|
void GrowableBuffer::init()
|
|
{
|
|
first = 0;
|
|
last = 0;
|
|
uncommit = 0;
|
|
current = 0;
|
|
}
|
|
|
|
byte* GrowableBuffer::reserve(size_t amt)
|
|
{
|
|
// attempt to reserve space
|
|
amt = (size_t)pageAfter((byte*)amt); // align to page
|
|
first = (byte*)heap->ReserveCodeMemory(0, amt);
|
|
last = first + amt;
|
|
uncommit = first;
|
|
current = first;
|
|
|
|
// commit the first page and make it a guard page
|
|
if (first)
|
|
heap->SetGuardPage(first);
|
|
|
|
return first;
|
|
}
|
|
|
|
byte* GrowableBuffer::decommitUnused()
|
|
{
|
|
// have we committed too much beyond the current location?
|
|
if (current + pageSize() < uncommit)
|
|
{
|
|
// get rid of pages
|
|
byte* after = pageAfter(current);
|
|
#ifdef MEMORY_INFO
|
|
MMgc::ChangeSizeForObject(this, -1 * (uncommit-after));
|
|
#endif
|
|
heap->DecommitCodeMemory((void*)after, uncommit-after);
|
|
uncommit = after;
|
|
|
|
heap->SetGuardPage(uncommit);
|
|
}
|
|
return uncommit;
|
|
}
|
|
|
|
byte* GrowableBuffer::grow()
|
|
{
|
|
return growBy(pageSize()*PAGES_PER_GROW);
|
|
}
|
|
|
|
byte* GrowableBuffer::growBy(size_t amt)
|
|
{
|
|
AvmAssertMsg(amt % pageSize() == 0, "amt must be multiple of pageSize");
|
|
size_t grow = ( (uncommit + amt) < last) ? amt : last - uncommit;
|
|
#ifdef MEMORY_INFO
|
|
MMgc::ChangeSizeForObject(this, grow);
|
|
#endif
|
|
void* res = heap->CommitCodeMemory((void*)uncommit, grow);
|
|
AvmAssert(res != 0);
|
|
(void)res;
|
|
uncommit += grow;
|
|
|
|
// commit the first page and make it a guard page
|
|
heap->SetGuardPage(uncommit);
|
|
|
|
return uncommit;
|
|
}
|
|
|
|
byte* GrowableBuffer::shrinkTo(size_t amt)
|
|
{
|
|
AvmAssertMsg(amt % pageSize() == 0, "amt must be multiple of pageSize");
|
|
byte* shrinkTo = start() + amt;
|
|
size_t size = (shrinkTo < uncommit) ? uncommit - shrinkTo : 0;
|
|
if (size > 0)
|
|
{
|
|
#ifdef MEMORY_INFO
|
|
MMgc::ChangeSizeForObject(this, -1 * size);
|
|
#endif
|
|
void* res = heap->DecommitCodeMemory((void*)shrinkTo, size);
|
|
AvmAssert(res != 0);
|
|
(void)res;
|
|
uncommit = shrinkTo;
|
|
|
|
// commit the first page and make it a guard page
|
|
heap->SetGuardPage(uncommit);
|
|
}
|
|
return uncommit;
|
|
}
|
|
|
|
void GrowableBuffer::free()
|
|
{
|
|
// get rid of the whole shebang
|
|
if (first != 0)
|
|
{
|
|
#ifdef MEMORY_INFO
|
|
MMgc::ChangeSizeForObject(this, -1 * (uncommit-first));
|
|
heap->DecommitCodeMemory(first, uncommit-first);
|
|
#endif
|
|
heap->ReleaseCodeMemory(first, size());
|
|
init();
|
|
}
|
|
}
|
|
|
|
#ifdef FEATURE_BUFFER_GUARD
|
|
|
|
//
|
|
// GenericGuard
|
|
//
|
|
|
|
#ifdef AVMPLUS_WIN32
|
|
|
|
#pragma warning(disable: 4733) // make sure you build with linker option /SAFESEH:NO !!!!
|
|
|
|
void GenericGuard::init()
|
|
{
|
|
record.prev = 0;
|
|
record.handler = 0;
|
|
record.instance = 0;
|
|
record.terminator = 0;
|
|
}
|
|
|
|
void GenericGuard::registerHandler()
|
|
{
|
|
if (record.instance == 0)
|
|
{
|
|
record.handler = (DWORD)guardRoutine;
|
|
record.instance = (GenericGuard*)this;
|
|
record.terminator = 0xffffffff;
|
|
/* swap in our handler record */
|
|
__asm
|
|
{
|
|
mov ecx, [this]
|
|
mov eax, fs:[0];
|
|
mov [ecx].record.prev, eax;
|
|
lea eax, [ecx].record;
|
|
mov fs:[0], eax;
|
|
}
|
|
}
|
|
}
|
|
|
|
void GenericGuard::unregisterHandler()
|
|
{
|
|
if (record.instance != 0)
|
|
{
|
|
/* swap out our handler record */
|
|
__asm
|
|
{
|
|
mov ecx, [this]
|
|
mov eax, [ecx].record.prev;
|
|
mov fs:[0], eax;
|
|
}
|
|
}
|
|
record.instance = 0;
|
|
}
|
|
|
|
/*static*/int __cdecl GenericGuard::guardRoutine(struct _EXCEPTION_RECORD *exceptionRecord,
|
|
void *establisherFrame,
|
|
struct _CONTEXT *contextRecord,
|
|
void *dispatcherContext)
|
|
{
|
|
if (exceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION || exceptionRecord->ExceptionCode == EXCEPTION_GUARD_PAGE)
|
|
{
|
|
GenericGuard* guard = (GenericGuard*)((ExceptionRegistrationRecord*)establisherFrame)->instance;
|
|
|
|
return guard->handleException(exceptionRecord,
|
|
establisherFrame,
|
|
contextRecord,
|
|
dispatcherContext);
|
|
}
|
|
return ExceptionContinueSearch;
|
|
}
|
|
|
|
#pragma warning(default: 4733) // make sure you build with linker option /SAFESEH:NO !!!!
|
|
|
|
#endif /* AVMPLUS_WIN32 */
|
|
|
|
#ifdef AVMPLUS_MACH_EXCEPTIONS
|
|
mach_port_t GenericGuard::exceptionPort = (mach_port_t)NULL;
|
|
pthread_t GenericGuard::exceptionThread;
|
|
pthread_mutex_t GenericGuard::mutex;
|
|
volatile GenericGuard* GenericGuard::guardList = NULL;
|
|
|
|
void* GenericGuard::threadMain(void* /*arg*/)
|
|
{
|
|
struct
|
|
{
|
|
mach_msg_header_t head;
|
|
NDR_record_t NDR;
|
|
kern_return_t RetCode;
|
|
}
|
|
reply;
|
|
|
|
struct
|
|
{
|
|
mach_msg_header_t head;
|
|
mach_msg_body_t msgh_body;
|
|
mach_msg_port_descriptor_t thread;
|
|
mach_msg_port_descriptor_t task;
|
|
NDR_record_t NDR;
|
|
exception_type_t exception;
|
|
mach_msg_type_number_t codeCnt;
|
|
integer_t code[2];
|
|
mach_msg_trailer_t trailer;
|
|
}
|
|
msg;
|
|
|
|
for (;;)
|
|
{
|
|
if (mach_msg(&msg.head,
|
|
MACH_RCV_MSG | MACH_RCV_LARGE,
|
|
0,
|
|
sizeof(msg),
|
|
exceptionPort,
|
|
MACH_MSG_TIMEOUT_NONE,
|
|
MACH_PORT_NULL) != MACH_MSG_SUCCESS)
|
|
{
|
|
AvmAssertMsg(false, "mach_msg failed");
|
|
return NULL;
|
|
}
|
|
|
|
if (msg.head.msgh_id == kThreadExitMsg)
|
|
{
|
|
break;
|
|
}
|
|
|
|
#ifdef USE_EXC_SERVER
|
|
// Handle the message (calls catch_exception_raise)
|
|
if (!exc_server(&msg.head, &reply.head))
|
|
{
|
|
AvmAssertMsg(false, "exc_server failed");
|
|
}
|
|
#else
|
|
reply.RetCode = catch_exception_raise(msg.head.msgh_remote_port,
|
|
msg.thread.name,
|
|
msg.task.name,
|
|
msg.exception,
|
|
msg.code,
|
|
msg.codeCnt);
|
|
reply.head.msgh_bits = MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(msg.head.msgh_bits), 0);
|
|
reply.head.msgh_remote_port = msg.head.msgh_remote_port;
|
|
reply.head.msgh_size = (mach_msg_size_t)sizeof(reply);
|
|
reply.head.msgh_local_port = MACH_PORT_NULL;
|
|
reply.head.msgh_id = msg.head.msgh_id + 100;
|
|
#endif
|
|
|
|
// Send the reply
|
|
if (mach_msg(&reply.head,
|
|
MACH_SEND_MSG,
|
|
reply.head.msgh_size,
|
|
0,
|
|
MACH_PORT_NULL,
|
|
MACH_MSG_TIMEOUT_NONE,
|
|
MACH_PORT_NULL) != MACH_MSG_SUCCESS)
|
|
{
|
|
AvmAssertMsg(false, "mach_msg failed while sending reply");
|
|
}
|
|
}
|
|
|
|
pthread_exit(NULL);
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef AVMPLUS_ROSETTA
|
|
/*-*-------------------------------------------------------------------------
|
|
/ Function
|
|
/ LoadFrameworkBundle
|
|
/
|
|
/ Purpose
|
|
/ Samething as GetSharedLibrary but for OS X and for non-CFM.
|
|
/
|
|
/ Entry
|
|
/ framework => A CFStringRef to the name of the framework you want to load.
|
|
/ bundlePtr => if non NULL upon return it will contain the reference to the framework you loaded.
|
|
/--------------------------------------------*/
|
|
static OSStatus LoadFrameworkBundle( CFStringRef framework, CFBundleRef *bundlePtr )
|
|
{
|
|
OSStatus err;
|
|
FSRef frameworksFolderRef;
|
|
CFURLRef baseURL;
|
|
CFURLRef bundleURL;
|
|
|
|
// clear out the result
|
|
*bundlePtr = NULL;
|
|
|
|
baseURL = NULL;
|
|
bundleURL = NULL;
|
|
|
|
err = ::FSFindFolder( kOnAppropriateDisk, kFrameworksFolderType, true, &frameworksFolderRef );
|
|
if( err == noErr )
|
|
{
|
|
baseURL = ::CFURLCreateFromFSRef( kCFAllocatorSystemDefault, &frameworksFolderRef );
|
|
if( !baseURL )
|
|
err = coreFoundationUnknownErr;
|
|
}
|
|
|
|
if( !err )
|
|
{
|
|
bundleURL = ::CFURLCreateCopyAppendingPathComponent( kCFAllocatorSystemDefault, baseURL, framework, false );
|
|
if( !bundleURL )
|
|
err = coreFoundationUnknownErr;
|
|
}
|
|
|
|
if( !err )
|
|
{
|
|
*bundlePtr = ::CFBundleCreate( kCFAllocatorSystemDefault, bundleURL );
|
|
if( !*bundlePtr)
|
|
err = coreFoundationUnknownErr;
|
|
}
|
|
|
|
if( !err && !::CFBundleLoadExecutable( *bundlePtr ) )
|
|
err = coreFoundationUnknownErr;
|
|
|
|
// Clean up.
|
|
if( err && *bundlePtr )
|
|
{
|
|
::CFRelease( *bundlePtr );
|
|
*bundlePtr = NULL;
|
|
}
|
|
|
|
if( bundleURL != NULL )
|
|
::CFRelease( bundleURL );
|
|
|
|
if( baseURL != NULL)
|
|
::CFRelease( baseURL );
|
|
|
|
return err;
|
|
}
|
|
|
|
typedef int (*f_sysctlnametomib)(const char *name, int *mibp, size_t *sizep);
|
|
|
|
bool GenericGuard::rosetta = false;
|
|
|
|
/**
|
|
* The following two functions, sysctlbyname_with_pid and is_pid_native,
|
|
* are taken from Apple's Universal Binary Programming Guide.
|
|
* http://developer.apple.com/documentation/MacOSX/Conceptual/universal_binary/
|
|
* universal_binary_exec_a/chapter_7_section_7.html
|
|
*/
|
|
static int sysctlbyname_with_pid(const char *name, pid_t pid,
|
|
void *oldp, size_t *oldlenp,
|
|
void *newp, size_t newlen)
|
|
{
|
|
if (pid == 0) {
|
|
if (sysctlbyname(name, oldp, oldlenp, newp, newlen) == -1) {
|
|
AvmAssertMsg(false, "sysctlbyname_with_pid(0): sysctlbyname failed");
|
|
return -1;
|
|
}
|
|
} else {
|
|
int mib[CTL_MAXNAME];
|
|
size_t len = CTL_MAXNAME;
|
|
|
|
CFBundleRef sysBundle;
|
|
if ( LoadFrameworkBundle( CFSTR("System.framework"), &sysBundle ) == noErr ) {
|
|
// gcc -pedantic insists that "ISO C++ forbids casting between pointer-to-function and pointer-to-object"
|
|
// this allows us to dodge that warning
|
|
void* tmp = CFBundleGetFunctionPointerForName( sysBundle, CFSTR("sysctlnametomib") );
|
|
f_sysctlnametomib p_sysctlnametomib = *(f_sysctlnametomib*)&tmp;
|
|
if ( p_sysctlnametomib ) {
|
|
if (p_sysctlnametomib(name, mib, &len) == -1) {
|
|
AvmAssertMsg(false, "sysctlbyname_with_pid(0): sysctlnametomib failed");
|
|
return -1;
|
|
}
|
|
} else {
|
|
AvmAssertMsg(false, "CFBundleGetFunctionPointerForName(0): CFBundleGetFunctionPointerForName failed");
|
|
return -1;
|
|
}
|
|
} else {
|
|
AvmAssertMsg(false, "LoadFrameworkBundle(0): LoadFrameworkBundle failed");
|
|
return -1;
|
|
}
|
|
mib[len] = pid;
|
|
len++;
|
|
if (sysctl(mib, len, oldp, oldlenp, newp, newlen) == -1) {
|
|
AvmAssertMsg(false, "sysctlbyname_with_pid(0): sysctl failed");
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int is_pid_native(pid_t pid)
|
|
{
|
|
int ret = 0;
|
|
size_t sz = sizeof(ret);
|
|
|
|
if (sysctlbyname_with_pid("sysctl.proc_native", pid,
|
|
&ret, &sz, NULL, 0) == -1) {
|
|
if (errno == ENOENT) {
|
|
// sysctl doesn't exist, which means that this version of Mac OS
|
|
// pre-dates Rosetta, so the application must be native.
|
|
return 1;
|
|
}
|
|
AvmAssertMsg(false, "is_pid_native: sysctlbyname_with_pid failed");
|
|
return -1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#endif /* AVMPLUS_ROSETTA */
|
|
|
|
void GenericGuard::staticInit()
|
|
{
|
|
#ifdef AVMPLUS_ROSETTA
|
|
// Detect whether we are running under Rosetta
|
|
rosetta = !is_pid_native(getpid());
|
|
#endif
|
|
|
|
guardList = NULL;
|
|
|
|
pthread_mutexattr_t mutexattr;
|
|
pthread_mutexattr_init(&mutexattr);
|
|
pthread_mutex_init(&mutex, &mutexattr);
|
|
pthread_mutexattr_destroy(&mutexattr);
|
|
|
|
// Allocate the port
|
|
mach_port_t task = mach_task_self();
|
|
|
|
kern_return_t r;
|
|
r = mach_port_allocate(task,
|
|
MACH_PORT_RIGHT_RECEIVE,
|
|
&exceptionPort);
|
|
if (r != KERN_SUCCESS)
|
|
{
|
|
AvmAssertMsg(false, "mach_port_allocate failed");
|
|
return;
|
|
}
|
|
|
|
r = mach_port_insert_right(task,
|
|
exceptionPort,
|
|
exceptionPort,
|
|
MACH_MSG_TYPE_MAKE_SEND);
|
|
if (r != KERN_SUCCESS)
|
|
{
|
|
AvmAssertMsg(false, "mach_port_insert_right failed");
|
|
return;
|
|
}
|
|
|
|
// Start the thread
|
|
pthread_attr_t attr;
|
|
if (pthread_attr_init(&attr) != 0)
|
|
{
|
|
AvmAssertMsg(false, "pthread_attr_init failed");
|
|
return;
|
|
}
|
|
|
|
if (pthread_create(&exceptionThread, &attr, threadMain, NULL) != 0)
|
|
{
|
|
AvmAssertMsg(false, "pthread_create failed");
|
|
return;
|
|
}
|
|
|
|
pthread_attr_destroy(&attr);
|
|
}
|
|
|
|
void GenericGuard::staticDestroy()
|
|
{
|
|
// Send the thread the exit notification
|
|
mach_msg_header_t msg;
|
|
msg.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0);
|
|
msg.msgh_size = sizeof(mach_msg_header_t);
|
|
msg.msgh_remote_port = exceptionPort;
|
|
msg.msgh_local_port = MACH_PORT_NULL;
|
|
msg.msgh_id = kThreadExitMsg;
|
|
|
|
mach_msg_return_t r;
|
|
r = mach_msg(&msg,
|
|
MACH_SEND_MSG,
|
|
sizeof(msg),
|
|
0,
|
|
MACH_PORT_NULL,
|
|
MACH_MSG_TIMEOUT_NONE,
|
|
MACH_PORT_NULL);
|
|
if (r != MACH_MSG_SUCCESS)
|
|
{
|
|
AvmAssertMsg(false, "mach_msg failed");
|
|
}
|
|
|
|
// Join the thread
|
|
void *status;
|
|
pthread_join(exceptionThread, &status);
|
|
|
|
// Destroy the mutex
|
|
pthread_mutex_destroy(&mutex);
|
|
}
|
|
|
|
void GenericGuard::init()
|
|
{
|
|
}
|
|
|
|
kern_return_t GenericGuard::forward_exception(mach_port_t thread,
|
|
mach_port_t task,
|
|
exception_type_t exception,
|
|
exception_data_t data,
|
|
mach_msg_type_number_t data_count,
|
|
SavedExceptionPorts *savedExceptionPorts)
|
|
{
|
|
unsigned int i;
|
|
kern_return_t r;
|
|
mach_port_t port;
|
|
exception_behavior_t behavior;
|
|
thread_state_flavor_t flavor;
|
|
|
|
#if defined (AVMPLUS_IA32) || defined(AVMPLUS_AMD64)
|
|
i386_thread_state_t thread_state;
|
|
#else
|
|
ppc_thread_state_t thread_state;
|
|
#endif
|
|
|
|
mach_msg_type_number_t thread_state_count = THREAD_STATE_MAX;
|
|
|
|
for (i=0; i<savedExceptionPorts->count; i++)
|
|
{
|
|
if (savedExceptionPorts->masks[i] & (1 << exception))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (i == savedExceptionPorts->count)
|
|
{
|
|
AvmAssertMsg(false, "No handler for exception!");
|
|
}
|
|
|
|
port = savedExceptionPorts->ports[i];
|
|
behavior = savedExceptionPorts->behaviors[i];
|
|
flavor = savedExceptionPorts->flavors[i];
|
|
|
|
if(behavior != EXCEPTION_DEFAULT)
|
|
{
|
|
r = thread_get_state(thread, flavor, (natural_t*)&thread_state, &thread_state_count);
|
|
if(r != KERN_SUCCESS)
|
|
{
|
|
AvmAssertMsg(false, "thread_get_state failed in forward_exception");
|
|
}
|
|
}
|
|
|
|
switch(behavior)
|
|
{
|
|
case EXCEPTION_DEFAULT:
|
|
r = exception_raise(port, thread, task, exception, data, data_count);
|
|
break;
|
|
case EXCEPTION_STATE:
|
|
r = exception_raise_state(port, thread, task, exception, data,
|
|
data_count, &flavor, (natural_t*)&thread_state, thread_state_count,
|
|
(natural_t*)&thread_state, &thread_state_count);
|
|
break;
|
|
case EXCEPTION_STATE_IDENTITY:
|
|
r = exception_raise_state_identity(port, thread, task, exception, data,
|
|
data_count, &flavor, (natural_t*)&thread_state, thread_state_count,
|
|
(natural_t*)&thread_state, &thread_state_count);
|
|
break;
|
|
default:
|
|
r = KERN_FAILURE;
|
|
AvmAssertMsg(false, "forward_exception: unknown behavior");
|
|
break;
|
|
}
|
|
|
|
if (behavior != EXCEPTION_DEFAULT)
|
|
{
|
|
r = thread_set_state(thread, flavor, (natural_t*)&thread_state, thread_state_count);
|
|
if (r != KERN_SUCCESS)
|
|
{
|
|
AvmAssertMsg(false, "thread_set_state failed in forward_exception");
|
|
}
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
void GenericGuard::registerHandler()
|
|
{
|
|
registered = true;
|
|
|
|
// Add self to exception thread's list
|
|
int retCode = pthread_mutex_lock(&mutex);
|
|
(void)retCode;
|
|
AvmAssert(!retCode);
|
|
|
|
thread = mach_thread_self();
|
|
|
|
next = guardList;
|
|
guardList = this;
|
|
|
|
retCode = pthread_mutex_unlock(&mutex);
|
|
AvmAssert(!retCode);
|
|
|
|
|
|
exception_mask_t mask = EXC_MASK_BAD_ACCESS;
|
|
|
|
// Save exception ports
|
|
memset(&savedExceptionPorts, 0, sizeof(SavedExceptionPorts));
|
|
|
|
kern_return_t r;
|
|
r = thread_get_exception_ports(thread,
|
|
mask,
|
|
savedExceptionPorts.masks,
|
|
&savedExceptionPorts.count,
|
|
savedExceptionPorts.ports,
|
|
savedExceptionPorts.behaviors,
|
|
savedExceptionPorts.flavors);
|
|
if (r != KERN_SUCCESS)
|
|
{
|
|
AvmAssertMsg(false, "thread_get_exception_ports failed");
|
|
return;
|
|
}
|
|
|
|
// Install exception ports
|
|
r = thread_set_exception_ports(thread,
|
|
mask,
|
|
exceptionPort,
|
|
EXCEPTION_DEFAULT,
|
|
MACHINE_THREAD_STATE);
|
|
|
|
if (r != KERN_SUCCESS)
|
|
{
|
|
AvmAssertMsg(false, "thread_set_exception_ports failed");
|
|
}
|
|
}
|
|
|
|
void GenericGuard::unregisterHandler()
|
|
{
|
|
if (!registered)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Remove self from the exception thread's list
|
|
pthread_mutex_lock(&mutex);
|
|
|
|
volatile GenericGuard* volatile *prev = &guardList;
|
|
while (*prev != this)
|
|
{
|
|
prev = &((**prev).next);
|
|
}
|
|
*prev = next;
|
|
|
|
pthread_mutex_unlock(&mutex);
|
|
|
|
// Restore thread exception ports
|
|
for (unsigned int i=0; i<savedExceptionPorts.count; i++)
|
|
{
|
|
thread_set_exception_ports(thread,
|
|
savedExceptionPorts.masks[i],
|
|
savedExceptionPorts.ports[i],
|
|
savedExceptionPorts.behaviors[i],
|
|
savedExceptionPorts.flavors[i]);
|
|
}
|
|
|
|
registered = false;
|
|
}
|
|
|
|
#ifdef AVMPLUS_ROSETTA
|
|
inline int SWAP32(int x)
|
|
{
|
|
return (((x & 0xFF) << 24) |
|
|
(((x>>8) & 0xFF) << 16) |
|
|
(((x>>16) & 0xFF) << 8) |
|
|
(((x>>24) & 0xFF)));
|
|
}
|
|
#endif
|
|
|
|
kern_return_t GenericGuard::catch_exception_raise(mach_port_t /*exception_port*/,
|
|
mach_port_t thread,
|
|
mach_port_t task,
|
|
exception_type_t exception,
|
|
exception_data_t code,
|
|
mach_msg_type_number_t code_count)
|
|
{
|
|
// Find the GenericGuard associated with thread
|
|
int retCode = pthread_mutex_lock(&mutex);
|
|
(void)retCode;
|
|
AvmAssert(!retCode);
|
|
|
|
GenericGuard *guard = (GenericGuard*) guardList;
|
|
while (guard)
|
|
{
|
|
if (guard->thread == thread)
|
|
{
|
|
break;
|
|
}
|
|
guard = (GenericGuard*) guard->next;
|
|
}
|
|
|
|
// If we couldn't find it, must be a different thread
|
|
if (!guard)
|
|
{
|
|
// reset guard to get saved exception ports
|
|
guard = (GenericGuard*) guardList;
|
|
retCode = pthread_mutex_unlock(&mutex);
|
|
AvmAssert(!retCode);
|
|
goto forward;
|
|
}
|
|
|
|
retCode = pthread_mutex_unlock(&mutex);
|
|
AvmAssert(!retCode);
|
|
|
|
// If an access violation occurred, let the GenericGuard a shot
|
|
// at handling the exception.
|
|
bool isAccessViolation = (exception == EXC_BAD_ACCESS && code[0] == KERN_PROTECTION_FAILURE);
|
|
|
|
#ifdef AVMPLUS_ROSETTA
|
|
// Under Rosetta on 10.4.6 i386, exception and code[0] come through in
|
|
// little-endian instead of big-endian. Apple might fix that at some point,
|
|
// so we'll permit either endian to match.
|
|
if (rosetta)
|
|
{
|
|
isAccessViolation = isAccessViolation || (exception == SWAP32(EXC_BAD_ACCESS) &&
|
|
code[0] == SWAP32(KERN_PROTECTION_FAILURE));
|
|
}
|
|
#endif
|
|
|
|
if (isAccessViolation)
|
|
{
|
|
kern_return_t returnCode;
|
|
if (guard->handleException(returnCode))
|
|
{
|
|
// Our handler handled the exception.
|
|
return returnCode;
|
|
}
|
|
}
|
|
|
|
forward:
|
|
|
|
// We didn't handle the exception, so pass it to the regular handler.
|
|
SavedExceptionPorts *ports = (SavedExceptionPorts*)&guard->savedExceptionPorts;
|
|
return forward_exception(thread, task, exception, code, code_count,
|
|
ports);
|
|
}
|
|
|
|
bool GrowthGuard::handleException(kern_return_t& returnCode)
|
|
{
|
|
#ifdef AVMPLUS_ROSETTA
|
|
// Under Rosetta, thread_get_state does not appear to work.
|
|
// So, we will simply assume that this exception is intended for us
|
|
// and grow the buffer.
|
|
if (rosetta)
|
|
{
|
|
GrowableBuffer* g = buffer;
|
|
g->grow();
|
|
returnCode = KERN_SUCCESS;
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
#if defined (AVMPLUS_IA32) || defined(AVMPLUS_AMD64)
|
|
i386_exception_state_t exc_state;
|
|
mach_msg_type_number_t exc_state_count = i386_EXCEPTION_STATE_COUNT;
|
|
thread_state_flavor_t flavor = i386_EXCEPTION_STATE;
|
|
#else
|
|
ppc_exception_state_t exc_state;
|
|
mach_msg_type_number_t exc_state_count = PPC_EXCEPTION_STATE_COUNT;
|
|
thread_state_flavor_t flavor = PPC_EXCEPTION_STATE;
|
|
#endif
|
|
|
|
thread_get_state(thread,
|
|
flavor,
|
|
(natural_t*)&exc_state,
|
|
&exc_state_count);
|
|
|
|
#if defined (AVMPLUS_IA32) || defined(AVMPLUS_AMD64)
|
|
#if __DARWIN_UNIX03 // Mac 10.5 SDK changed definition
|
|
byte *AccessViolationAddress = (byte*) exc_state.__faultvaddr;
|
|
#else
|
|
byte *AccessViolationAddress = (byte*) exc_state.faultvaddr;
|
|
#endif
|
|
#else
|
|
byte *AccessViolationAddress = (byte*) exc_state.dar;
|
|
#endif
|
|
|
|
GrowableBuffer* g = buffer;
|
|
byte* nextPage = g->uncommitted();
|
|
byte* nextPageAfterGrow = NULL;
|
|
|
|
if (AccessViolationAddress == nextPage)
|
|
{
|
|
// sequential write access to buffer
|
|
nextPageAfterGrow = g->grow();
|
|
}
|
|
|
|
if (AccessViolationAddress > nextPage && AccessViolationAddress < g->end())
|
|
{
|
|
// random access into buffer (commit next page after the hit)
|
|
byte* page = g->pageAfter(AccessViolationAddress);
|
|
nextPageAfterGrow = g->growBy(page - nextPage);
|
|
}
|
|
|
|
if(nextPage != nextPageAfterGrow)
|
|
{
|
|
returnCode = KERN_SUCCESS;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
#endif // AVMPLUS_MACH_EXCEPTIONS
|
|
|
|
#ifdef AVMPLUS_LINUX
|
|
static pthread_key_t guardKey = 0;
|
|
static struct sigaction orig_sa;
|
|
|
|
static void dispatchHandleException(int sig, siginfo_t *info, void *context)
|
|
{
|
|
GenericGuard *genericGuard = (GenericGuard*) pthread_getspecific(guardKey);
|
|
bool handled = false;
|
|
|
|
while (genericGuard && !handled)
|
|
{
|
|
handled = genericGuard->handleException((byte*) info->si_addr);
|
|
genericGuard = genericGuard->next;
|
|
}
|
|
|
|
if (!handled)
|
|
{
|
|
sigaction(SIGSEGV, &orig_sa, NULL);
|
|
}
|
|
}
|
|
|
|
void GenericGuard::init()
|
|
{
|
|
next = NULL;
|
|
if (!guardKey)
|
|
{
|
|
pthread_key_create(&guardKey, NULL);
|
|
}
|
|
}
|
|
|
|
void GenericGuard::registerHandler()
|
|
{
|
|
GenericGuard *genericGuard = (GenericGuard*) pthread_getspecific(guardKey);
|
|
|
|
if (!genericGuard)
|
|
{
|
|
// Make "this" the beginning.
|
|
pthread_setspecific(guardKey, this);
|
|
|
|
// install the signal handler
|
|
struct sigaction sa;
|
|
|
|
sa.sa_handler = 0;
|
|
sa.sa_sigaction = dispatchHandleException;
|
|
sigemptyset(&sa.sa_mask);
|
|
sa.sa_flags = SA_SIGINFO;
|
|
|
|
sigaction(SIGSEGV, &sa, &orig_sa);
|
|
}
|
|
else
|
|
{
|
|
// Add "this" to the end.
|
|
while (genericGuard->next)
|
|
{
|
|
genericGuard = genericGuard->next;
|
|
}
|
|
|
|
genericGuard->next = this;
|
|
}
|
|
}
|
|
|
|
void GenericGuard::unregisterHandler()
|
|
{
|
|
GenericGuard *genericGuard = (GenericGuard*) pthread_getspecific(guardKey);
|
|
|
|
if (genericGuard == this)
|
|
{
|
|
// "this" is the first in the linked list
|
|
if (genericGuard->next)
|
|
{
|
|
// Make "next" the beginning.
|
|
pthread_setspecific(guardKey, genericGuard->next);
|
|
}
|
|
else
|
|
{
|
|
// "this" is the only element of the linked list, so
|
|
// null out the thread local and remove the signal
|
|
// handler.
|
|
pthread_setspecific(guardKey, NULL);
|
|
sigaction(SIGSEGV, &orig_sa, NULL);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while (genericGuard && genericGuard->next != this)
|
|
{
|
|
genericGuard = genericGuard->next;
|
|
}
|
|
|
|
if (genericGuard && genericGuard->next == this)
|
|
{
|
|
if (genericGuard->next->next)
|
|
{
|
|
// "this" is in the middle of the linked list, so
|
|
// make the "before" point to the "after".
|
|
genericGuard->next = genericGuard->next->next;
|
|
}
|
|
else
|
|
{
|
|
// "this" is at the end of the linked list, so
|
|
// just null out the pointer to it.
|
|
genericGuard->next = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif // AVMPLUS_LINUX
|
|
|
|
// BufferGuard
|
|
#ifdef AVMPLUS_LINUX
|
|
BufferGuard::BufferGuard(jmp_buf jmpBuf)
|
|
{
|
|
this->jmpBuf[0] = *jmpBuf;
|
|
#else
|
|
BufferGuard::BufferGuard(int *jmpBuf)
|
|
{
|
|
this->jmpBuf = jmpBuf;
|
|
#endif // AVMPLUS_LINUX
|
|
init();
|
|
if (jmpBuf)
|
|
registerHandler();
|
|
}
|
|
|
|
BufferGuard::~BufferGuard()
|
|
{
|
|
if (jmpBuf)
|
|
unregisterHandler();
|
|
}
|
|
|
|
// Platform specific code follows
|
|
#ifdef AVMPLUS_WIN32
|
|
int BufferGuard::handleException(struct _EXCEPTION_RECORD* /*exceptionRecord*/,
|
|
void* /*establisherFrame*/,
|
|
struct _CONTEXT *contextRecord,
|
|
void* /*dispatcherContext*/)
|
|
{
|
|
// Set registers in contextRecord to point to the catch location when
|
|
// we return. We will *really* handle the exception there. All exceptions
|
|
// caught by this handler must be wrapped by TRY/CATCH blocks. See win32setjmp.cpp
|
|
contextRecord->Ebp = jmpBuf[0];
|
|
contextRecord->Ebx = jmpBuf[1];
|
|
contextRecord->Edi = jmpBuf[2];
|
|
contextRecord->Esi = jmpBuf[3];
|
|
contextRecord->Esp = jmpBuf[4];
|
|
contextRecord->Eip = jmpBuf[5];
|
|
|
|
return ExceptionContinueExecution;
|
|
}
|
|
#endif // AVMPLUS_WIN32
|
|
|
|
#ifdef AVMPLUS_MACH_EXCEPTIONS
|
|
|
|
bool BufferGuard::handleException(kern_return_t& returnCode)
|
|
{
|
|
#if defined (AVMPLUS_IA32) || defined(AVMPLUS_AMD64)
|
|
i386_thread_state_t thread_state;
|
|
mach_msg_type_number_t thread_state_count = i386_THREAD_STATE_COUNT;
|
|
thread_state_flavor_t flavor = i386_THREAD_STATE;
|
|
#else
|
|
ppc_thread_state_t thread_state;
|
|
mach_msg_type_number_t thread_state_count = PPC_THREAD_STATE_COUNT;
|
|
thread_state_flavor_t flavor = PPC_THREAD_STATE;
|
|
#endif
|
|
|
|
kern_return_t retVal;
|
|
|
|
retVal = thread_suspend(thread);
|
|
|
|
retVal = thread_get_state(thread,
|
|
flavor,
|
|
(natural_t*)&thread_state,
|
|
&thread_state_count);
|
|
|
|
// set the registers to point back to the CATCH block
|
|
#ifdef AVMPLUS_PPC
|
|
thread_state.srr0 = jmpBuf[21];
|
|
thread_state.r1 = jmpBuf[0];
|
|
#endif
|
|
|
|
#if defined (AVMPLUS_IA32) || defined(AVMPLUS_AMD64)
|
|
#if __DARWIN_UNIX03 // Mac 10.5 SDK changed definition
|
|
thread_state.__ebx = jmpBuf[0];
|
|
thread_state.__esi = jmpBuf[1];
|
|
thread_state.__edi = jmpBuf[2];
|
|
thread_state.__ebp = jmpBuf[3];
|
|
thread_state.__esp = jmpBuf[4];
|
|
thread_state.__eip = jmpBuf[5];
|
|
#else
|
|
thread_state.ebx = jmpBuf[0];
|
|
thread_state.esi = jmpBuf[1];
|
|
thread_state.edi = jmpBuf[2];
|
|
thread_state.ebp = jmpBuf[3];
|
|
thread_state.esp = jmpBuf[4];
|
|
thread_state.eip = jmpBuf[5];
|
|
#endif
|
|
#endif
|
|
|
|
retVal = thread_set_state(thread,
|
|
flavor,
|
|
(natural_t*)&thread_state,
|
|
thread_state_count);
|
|
|
|
retVal = thread_resume(thread);
|
|
|
|
if(retVal == KERN_INVALID_ARGUMENT)
|
|
{
|
|
returnCode = KERN_FAILURE;
|
|
return true;
|
|
}
|
|
|
|
// Handle the exception
|
|
returnCode = KERN_SUCCESS;
|
|
return true;
|
|
}
|
|
|
|
|
|
#endif /* AVMPLUS_MACH_EXCEPTIONS */
|
|
|
|
#ifdef AVMPLUS_LINUX
|
|
bool BufferGuard::handleException(byte *addr)
|
|
{
|
|
#ifdef _DEBUG
|
|
printf("BufferGuard::handleException: not implemented yet\n");
|
|
#endif
|
|
return false;
|
|
}
|
|
#endif // AVMPLUS_LINUX
|
|
|
|
// GrowthGuard
|
|
GrowthGuard::GrowthGuard(GrowableBuffer* buffer)
|
|
{
|
|
this->registered = false;
|
|
this->buffer = buffer;
|
|
init();
|
|
if (buffer)
|
|
registerHandler();
|
|
}
|
|
|
|
GrowthGuard::~GrowthGuard()
|
|
{
|
|
if (buffer)
|
|
unregisterHandler();
|
|
}
|
|
|
|
// Platform specific code follows
|
|
#ifdef AVMPLUS_WIN32
|
|
int GrowthGuard::handleException(struct _EXCEPTION_RECORD* exceptionRecord,
|
|
void* /*establisherFrame*/,
|
|
struct _CONTEXT* /*contextRecord*/,
|
|
void* /*dispatcherContext*/)
|
|
{
|
|
byte* AccessViolationAddress = (byte*) exceptionRecord->ExceptionInformation[1];
|
|
byte* nextPage = buffer->uncommitted();
|
|
if (AccessViolationAddress == nextPage)
|
|
{
|
|
// sequential write access to buffer
|
|
buffer->grow();
|
|
return ExceptionContinueExecution;
|
|
}
|
|
else if (AccessViolationAddress > nextPage && AccessViolationAddress < buffer->end())
|
|
{
|
|
// random access into buffer (commit next page after the hit)
|
|
byte* page = buffer->pageAfter(AccessViolationAddress);
|
|
buffer->growBy(page - nextPage);
|
|
return ExceptionContinueExecution;
|
|
}
|
|
else
|
|
{
|
|
// hmm something is pretty bad here
|
|
return ExceptionContinueSearch;
|
|
}
|
|
}
|
|
|
|
#endif /* AVMPLUS_WIN32 */
|
|
|
|
#ifdef AVMPLUS_LINUX
|
|
bool GrowthGuard::handleException(byte* addr)
|
|
{
|
|
GrowableBuffer* g = buffer;
|
|
byte* nextPage = g->uncommitted();
|
|
bool result = false;
|
|
|
|
if (addr == nextPage)
|
|
{
|
|
// sequential write access to buffer
|
|
g->grow();
|
|
result = true;
|
|
}
|
|
else if (addr > nextPage && addr < g->end())
|
|
{
|
|
// random access into buffer (commit next page after the hit)
|
|
byte* page = g->pageAfter(addr);
|
|
g->growBy(page - nextPage);
|
|
result = true;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
#endif /* AVMPLUS_LINUX */
|
|
|
|
#endif /* FEATURE_BUFFER_GUARD */
|
|
}
|