You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

241 lines
6.9 KiB
C

#include <stdlib.h>
#include <dlfcn.h>
#include <stdio.h>
#include <string.h>
#include "dwarf_eh.h"
#include "objc/runtime.h"
#include "objc/hooks.h"
#include "class.h"
/**
* Class of exceptions to distinguish between this and other exception types.
*/
#define objc_exception_class (*(int64_t*)"GNUCC++\0")
/**
* Class used for C++ exceptions. Used to box them.
*/
#define cxx_exception_class (*(int64_t*)"GNUCC++\0")
/**
* Structure used as a header on thrown exceptions.
*/
struct objc_exception
{
/** The selector value to be returned when installing the catch handler.
* Used at the call site to determine which catch() block should execute.
* This is found in phase 1 of unwinding then installed in phase 2.*/
int handlerSwitchValue;
/** The cached landing pad for the catch handler.*/
void *landingPad;
/** The language-agnostic part of the exception header. */
struct _Unwind_Exception unwindHeader;
/** Thrown object. This is after the unwind header so that the C++
* exception handler can catch this as a foreign exception. */
id object;
};
/**
*
*/
void objc_exception_throw(id object)
{
struct objc_exception *ex = calloc(1, sizeof(struct objc_exception));
ex->unwindHeader.exception_class = objc_exception_class;
ex->object = object;
_Unwind_Reason_Code err = _Unwind_RaiseException(&ex->unwindHeader);
free(ex);
if (_URC_END_OF_STACK == err && 0 != _objc_unexpected_exception)
{
_objc_unexpected_exception(object);
}
abort();
}
Class get_type_table_entry(struct _Unwind_Context *context,
struct dwarf_eh_lsda *lsda,
int filter)
{
dw_eh_ptr_t record = lsda->type_table -
dwarf_size_of_fixed_size_field(lsda->type_table_encoding)*filter;
dw_eh_ptr_t start = record;
int64_t offset = read_value(lsda->type_table_encoding, &record);
if (0 == offset) { return Nil; }
// ...so we need to resolve it
char *class_name = (char*)(intptr_t)resolve_indirect_value(context,
lsda->type_table_encoding, offset, start);
if (0 == class_name) { return Nil; }
return (Class)objc_getClass(class_name);
}
static BOOL isKindOfClass(Class thrown, Class type, BOOL foreignException)
{
// Nil is a catchall, but we only want to catch things that are not foreign
// exceptions in it.
if (Nil == type)
{
return (Nil != thrown) && !foreignException;
}
do
{
if (thrown == type)
{
return YES;
}
thrown = class_getSuperclass(thrown);
} while (Nil != thrown);
return NO;
}
static BOOL check_action_record(struct _Unwind_Context *context,
BOOL foreignException,
struct dwarf_eh_lsda *lsda,
dw_eh_ptr_t action_record,
Class thrown_class,
unsigned long *selector)
{
while (action_record)
{
int filter = read_sleb128(&action_record);
dw_eh_ptr_t action_record_offset_base = action_record;
int displacement = read_sleb128(&action_record);
*selector = filter;
if (filter > 0)
{
Class type = get_type_table_entry(context, lsda, filter);
if (isKindOfClass(thrown_class, type, foreignException))
{
return YES;
}
}
else if (filter == 0)
{
// Catchall
return YES;
}
*selector = 0;
action_record = displacement ?
action_record_offset_base + displacement : 0;
}
return NO;
}
/**
* The exception personality function.
*/
_Unwind_Reason_Code __gnu_objc_personality_v0(int version,
_Unwind_Action actions,
uint64_t exceptionClass,
struct _Unwind_Exception *exceptionObject,
struct _Unwind_Context *context)
{
// This personality function is for version 1 of the ABI. If you use it
// with a future version of the ABI, it won't know what to do, so it
// reports a fatal error and give up before it breaks anything.
if (1 != version)
{
return _URC_FATAL_PHASE1_ERROR;
}
struct objc_exception *ex = 0;
// Check if this is a foreign exception. If it is a C++ exception, then we
// have to box it. If it's something else, like a LanguageKit exception
// then we ignore it (for now)
BOOL foreignException = exceptionClass != objc_exception_class;
Class thrown_class = Nil;
// If it's not a foreign exception, then we know the layout of the
// language-specific exception stuff.
if (!foreignException)
{
ex = (struct objc_exception*) ((char*)exceptionObject -
offsetof(struct objc_exception, unwindHeader));
thrown_class = ex->object->isa;
}
else if (_objc_class_for_boxing_foreign_exception)
{
thrown_class = _objc_class_for_boxing_foreign_exception(exceptionClass);
}
unsigned char *lsda_addr = (void*)_Unwind_GetLanguageSpecificData(context);
// No LSDA implies no landing pads - try the next frame
if (0 == lsda_addr) { return _URC_CONTINUE_UNWIND; }
// These two variables define how the exception will be handled.
struct dwarf_eh_action action = {0};
unsigned long selector = 0;
if (actions & _UA_SEARCH_PHASE)
{
struct dwarf_eh_lsda lsda = parse_lsda(context, lsda_addr);
action = dwarf_eh_find_callsite(context, &lsda);
BOOL found_handler = check_action_record(context, foreignException,
&lsda, action.action_record, thrown_class, &selector);
// If there's no action record, we've only found a cleanup, so keep
// searching for something real
if (found_handler)
{
// Cache the results for the phase 2 unwind, if we found a handler
// and this is not a foreign exception.
if (ex)
{
ex->handlerSwitchValue = selector;
ex->landingPad = action.landing_pad;
}
return _URC_HANDLER_FOUND;
}
return _URC_CONTINUE_UNWIND;
}
// TODO: If this is a C++ exception, we can cache the lookup and cheat a
// bit
id object = nil;
if (!(actions & _UA_HANDLER_FRAME))
{
struct dwarf_eh_lsda lsda = parse_lsda(context, lsda_addr);
action = dwarf_eh_find_callsite(context, &lsda);
if (0 == action.landing_pad) { return _URC_CONTINUE_UNWIND; }
selector = 0;
}
else if (foreignException)
{
struct dwarf_eh_lsda lsda = parse_lsda(context, lsda_addr);
action = dwarf_eh_find_callsite(context, &lsda);
check_action_record(context, foreignException, &lsda,
action.action_record, thrown_class, &selector);
//[thrown_class exceptionWithForeignException: exceptionObject];
SEL box_sel = sel_registerName("exceptionWithForeignException:");
IMP boxfunction = objc_msg_lookup((id)thrown_class, box_sel);
object = boxfunction((id)thrown_class, box_sel, exceptionObject);
}
else
{
// Restore the saved info if we saved some last time.
action.landing_pad = ex->landingPad;
selector = ex->handlerSwitchValue;
object = ex->object;
}
_Unwind_SetIP(context, (unsigned long)action.landing_pad);
_Unwind_SetGR(context, __builtin_eh_return_data_regno(0),
(unsigned long)object);
_Unwind_SetGR(context, __builtin_eh_return_data_regno(1), selector);
return _URC_INSTALL_CONTEXT;
}