mirror of
https://github.com/ldc-developers/ldc.git
synced 2025-05-03 08:30:47 +03:00
418 lines
13 KiB
D
418 lines
13 KiB
D
/**
|
|
* This module contains functions and structures required for
|
|
* exception handling.
|
|
*/
|
|
module eh;
|
|
|
|
import util.console;
|
|
import ldc.cstdarg;
|
|
|
|
// debug = EH_personality;
|
|
|
|
// current EH implementation works on x86
|
|
// if it has a working unwind runtime
|
|
version(X86) {
|
|
version(linux) version=X86_UNWIND;
|
|
version(darwin) version=X86_UNWIND;
|
|
version(solaris) version=X86_UNWIND;
|
|
}
|
|
version(X86_64) {
|
|
version(linux) version=X86_UNWIND;
|
|
version(solaris) version=X86_UNWIND;
|
|
}
|
|
|
|
//version = HP_LIBUNWIND;
|
|
|
|
private extern(C) void abort();
|
|
private extern(C) int printf(char*, ...);
|
|
private extern(C) int vprintf(char*, va_list va);
|
|
|
|
// D runtime functions
|
|
extern(C) {
|
|
int _d_isbaseof(ClassInfo oc, ClassInfo c);
|
|
}
|
|
|
|
// libunwind headers
|
|
extern(C)
|
|
{
|
|
enum _Unwind_Reason_Code
|
|
{
|
|
NO_REASON = 0,
|
|
FOREIGN_EXCEPTION_CAUGHT = 1,
|
|
FATAL_PHASE2_ERROR = 2,
|
|
FATAL_PHASE1_ERROR = 3,
|
|
NORMAL_STOP = 4,
|
|
END_OF_STACK = 5,
|
|
HANDLER_FOUND = 6,
|
|
INSTALL_CONTEXT = 7,
|
|
CONTINUE_UNWIND = 8
|
|
}
|
|
|
|
enum _Unwind_Action
|
|
{
|
|
SEARCH_PHASE = 1,
|
|
CLEANUP_PHASE = 2,
|
|
HANDLER_PHASE = 3,
|
|
FORCE_UNWIND = 4
|
|
}
|
|
|
|
alias void* _Unwind_Context_Ptr;
|
|
|
|
alias void function(_Unwind_Reason_Code, _Unwind_Exception*) _Unwind_Exception_Cleanup_Fn;
|
|
|
|
struct _Unwind_Exception
|
|
{
|
|
char[8] exception_class;
|
|
_Unwind_Exception_Cleanup_Fn exception_cleanup;
|
|
ptrdiff_t private_1;
|
|
ptrdiff_t private_2;
|
|
}
|
|
|
|
// interface to HP's libunwind from http://www.nongnu.org/libunwind/
|
|
version(HP_LIBUNWIND)
|
|
{
|
|
void __libunwind_Unwind_Resume(_Unwind_Exception *);
|
|
_Unwind_Reason_Code __libunwind_Unwind_RaiseException(_Unwind_Exception *);
|
|
ptrdiff_t __libunwind_Unwind_GetLanguageSpecificData(_Unwind_Context_Ptr
|
|
context);
|
|
ptrdiff_t __libunwind_Unwind_GetIP(_Unwind_Context_Ptr context);
|
|
ptrdiff_t __libunwind_Unwind_SetIP(_Unwind_Context_Ptr context,
|
|
ptrdiff_t new_value);
|
|
ptrdiff_t __libunwind_Unwind_SetGR(_Unwind_Context_Ptr context, int index,
|
|
ptrdiff_t new_value);
|
|
ptrdiff_t __libunwind_Unwind_GetRegionStart(_Unwind_Context_Ptr context);
|
|
|
|
alias __libunwind_Unwind_Resume _Unwind_Resume;
|
|
alias __libunwind_Unwind_RaiseException _Unwind_RaiseException;
|
|
alias __libunwind_Unwind_GetLanguageSpecificData
|
|
_Unwind_GetLanguageSpecificData;
|
|
alias __libunwind_Unwind_GetIP _Unwind_GetIP;
|
|
alias __libunwind_Unwind_SetIP _Unwind_SetIP;
|
|
alias __libunwind_Unwind_SetGR _Unwind_SetGR;
|
|
alias __libunwind_Unwind_GetRegionStart _Unwind_GetRegionStart;
|
|
}
|
|
else version(X86_UNWIND)
|
|
{
|
|
void _Unwind_Resume(_Unwind_Exception*);
|
|
_Unwind_Reason_Code _Unwind_RaiseException(_Unwind_Exception*);
|
|
ptrdiff_t _Unwind_GetLanguageSpecificData(_Unwind_Context_Ptr context);
|
|
ptrdiff_t _Unwind_GetIP(_Unwind_Context_Ptr context);
|
|
ptrdiff_t _Unwind_SetIP(_Unwind_Context_Ptr context, ptrdiff_t new_value);
|
|
ptrdiff_t _Unwind_SetGR(_Unwind_Context_Ptr context, int index,
|
|
ptrdiff_t new_value);
|
|
ptrdiff_t _Unwind_GetRegionStart(_Unwind_Context_Ptr context);
|
|
}
|
|
else
|
|
{
|
|
// runtime calls these directly
|
|
void _Unwind_Resume(_Unwind_Exception*)
|
|
{
|
|
console("_Unwind_Resume is not implemented on this platform.\n");
|
|
}
|
|
_Unwind_Reason_Code _Unwind_RaiseException(_Unwind_Exception*)
|
|
{
|
|
console("_Unwind_RaiseException is not implemented on this platform.\n");
|
|
return _Unwind_Reason_Code.FATAL_PHASE1_ERROR;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// error and exit
|
|
extern(C) private void fatalerror(char* format, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, format);
|
|
printf("Fatal error in EH code: ");
|
|
vprintf(format, args);
|
|
printf("\n");
|
|
abort();
|
|
}
|
|
|
|
|
|
// helpers for reading certain DWARF data
|
|
private ubyte* get_uleb128(ubyte* addr, ref size_t res)
|
|
{
|
|
res = 0;
|
|
size_t bitsize = 0;
|
|
|
|
// read as long as high bit is set
|
|
while(*addr & 0x80) {
|
|
res |= (*addr & 0x7f) << bitsize;
|
|
bitsize += 7;
|
|
addr += 1;
|
|
if(bitsize >= size_t.sizeof*8)
|
|
fatalerror("tried to read uleb128 that exceeded size of size_t");
|
|
}
|
|
// read last
|
|
if(bitsize != 0 && *addr >= 1 << size_t.sizeof*8 - bitsize)
|
|
fatalerror("Fatal error in EH code: tried to read uleb128 that exceeded size of size_t");
|
|
res |= (*addr) << bitsize;
|
|
|
|
return addr + 1;
|
|
}
|
|
|
|
private ubyte* get_sleb128(ubyte* addr, ref ptrdiff_t res)
|
|
{
|
|
res = 0;
|
|
size_t bitsize = 0;
|
|
|
|
// read as long as high bit is set
|
|
while(*addr & 0x80) {
|
|
res |= (*addr & 0x7f) << bitsize;
|
|
bitsize += 7;
|
|
addr += 1;
|
|
if(bitsize >= size_t.sizeof*8)
|
|
fatalerror("tried to read sleb128 that exceeded size of size_t");
|
|
}
|
|
// read last
|
|
if(bitsize != 0 && *addr >= 1 << size_t.sizeof*8 - bitsize)
|
|
fatalerror("tried to read sleb128 that exceeded size of size_t");
|
|
res |= (*addr) << bitsize;
|
|
|
|
// take care of sign
|
|
if(bitsize < size_t.sizeof*8 && ((*addr) & 0x40))
|
|
res |= cast(ptrdiff_t)(-1) ^ ((1 << (bitsize+7)) - 1);
|
|
|
|
return addr + 1;
|
|
}
|
|
|
|
|
|
// exception struct used by the runtime.
|
|
// _d_throw allocates a new instance and passes the address of its
|
|
// _Unwind_Exception member to the unwind call. The personality
|
|
// routine is then able to get the whole struct by looking at the data
|
|
// surrounding the unwind info.
|
|
struct _d_exception
|
|
{
|
|
Object exception_object;
|
|
_Unwind_Exception unwind_info;
|
|
}
|
|
|
|
// the 8-byte string identifying the type of exception
|
|
// the first 4 are for vendor, the second 4 for language
|
|
//TODO: This may be the wrong way around
|
|
char[8] _d_exception_class = "LLDCD1\0\0";
|
|
|
|
|
|
//
|
|
// x86 unwind specific implementation of personality function
|
|
// and helpers
|
|
//
|
|
version(X86_UNWIND)
|
|
{
|
|
|
|
// the personality routine gets called by the unwind handler and is responsible for
|
|
// reading the EH tables and deciding what to do
|
|
extern(C) _Unwind_Reason_Code _d_eh_personality(int ver, _Unwind_Action actions, ulong exception_class, _Unwind_Exception* exception_info, _Unwind_Context_Ptr context)
|
|
{
|
|
// check ver: the C++ Itanium ABI only allows ver == 1
|
|
if(ver != 1)
|
|
return _Unwind_Reason_Code.FATAL_PHASE1_ERROR;
|
|
|
|
// check exceptionClass
|
|
//TODO: Treat foreign exceptions with more respect
|
|
if((cast(char*)&exception_class)[0..8] != _d_exception_class)
|
|
return _Unwind_Reason_Code.FATAL_PHASE1_ERROR;
|
|
|
|
// find call site table, action table and classinfo table
|
|
// Note: callsite and action tables do not contain static-length
|
|
// data and will be parsed as needed
|
|
// Note: classinfo_table points past the end of the table
|
|
ubyte* callsite_table;
|
|
ubyte* action_table;
|
|
ClassInfo* classinfo_table;
|
|
_d_getLanguageSpecificTables(context, callsite_table, action_table, classinfo_table);
|
|
|
|
|
|
/*
|
|
find landing pad and action table index belonging to ip by walking
|
|
the callsite_table
|
|
*/
|
|
ubyte* callsite_walker = callsite_table;
|
|
|
|
// get the instruction pointer
|
|
// will be used to find the right entry in the callsite_table
|
|
// -1 because it will point past the last instruction
|
|
ptrdiff_t ip = _Unwind_GetIP(context) - 1;
|
|
|
|
// address block_start is relative to
|
|
ptrdiff_t region_start = _Unwind_GetRegionStart(context);
|
|
|
|
// table entries
|
|
uint block_start_offset, block_size;
|
|
ptrdiff_t landing_pad;
|
|
size_t action_offset;
|
|
|
|
while(true) {
|
|
// if we've gone through the list and found nothing...
|
|
if(callsite_walker >= action_table)
|
|
return _Unwind_Reason_Code.CONTINUE_UNWIND;
|
|
|
|
block_start_offset = *cast(uint*)callsite_walker;
|
|
block_size = *(cast(uint*)callsite_walker + 1);
|
|
landing_pad = *(cast(uint*)callsite_walker + 2);
|
|
if(landing_pad)
|
|
landing_pad += region_start;
|
|
callsite_walker = get_uleb128(callsite_walker + 3*uint.sizeof, action_offset);
|
|
|
|
debug(EH_personality_verbose) printf("ip=%llx %d %d %llx\n", ip, block_start_offset, block_size, landing_pad);
|
|
|
|
// since the list is sorted, as soon as we're past the ip
|
|
// there's no handler to be found
|
|
if(ip < region_start + block_start_offset)
|
|
return _Unwind_Reason_Code.CONTINUE_UNWIND;
|
|
|
|
// if we've found our block, exit
|
|
if(ip < region_start + block_start_offset + block_size)
|
|
break;
|
|
}
|
|
|
|
debug(EH_personality) printf("Found correct landing pad and actionOffset %d\n", action_offset);
|
|
|
|
// now we need the exception's classinfo to find a handler
|
|
// the exception_info is actually a member of a larger _d_exception struct
|
|
// the runtime allocated. get that now
|
|
_d_exception* exception_struct = cast(_d_exception*)(cast(ubyte*)exception_info - _d_exception.unwind_info.offsetof);
|
|
|
|
// if there's no action offset and no landing pad, continue unwinding
|
|
if(!action_offset && !landing_pad)
|
|
return _Unwind_Reason_Code.CONTINUE_UNWIND;
|
|
|
|
// if there's no action offset but a landing pad, this is a cleanup handler
|
|
else if(!action_offset && landing_pad)
|
|
return _d_eh_install_finally_context(actions, landing_pad, exception_struct, context);
|
|
|
|
/*
|
|
walk action table chain, comparing classinfos using _d_isbaseof
|
|
*/
|
|
ubyte* action_walker = action_table + action_offset - 1;
|
|
|
|
ptrdiff_t ti_offset, next_action_offset;
|
|
while(true) {
|
|
action_walker = get_sleb128(action_walker, ti_offset);
|
|
// it is intentional that we not modify action_walker here
|
|
// next_action_offset is from current action_walker position
|
|
get_sleb128(action_walker, next_action_offset);
|
|
|
|
// negative are 'filters' which we don't use
|
|
if(!(ti_offset >= 0))
|
|
fatalerror("Filter actions are unsupported");
|
|
|
|
// zero means cleanup, which we require to be the last action
|
|
if(ti_offset == 0) {
|
|
if(!(next_action_offset == 0))
|
|
fatalerror("Cleanup action must be last in chain");
|
|
return _d_eh_install_finally_context(actions, landing_pad, exception_struct, context);
|
|
}
|
|
|
|
// get classinfo for action and check if the one in the
|
|
// exception structure is a base
|
|
ClassInfo catch_ci = *(classinfo_table - ti_offset);
|
|
debug(EH_personality) printf("Comparing catch %s to exception %s\n", catch_ci.name.ptr, exception_struct.exception_object.classinfo.name.ptr);
|
|
if(_d_isbaseof(exception_struct.exception_object.classinfo, catch_ci))
|
|
return _d_eh_install_catch_context(actions, ti_offset, landing_pad, exception_struct, context);
|
|
|
|
// we've walked through all actions and found nothing...
|
|
if(next_action_offset == 0)
|
|
return _Unwind_Reason_Code.CONTINUE_UNWIND;
|
|
else
|
|
action_walker += next_action_offset;
|
|
}
|
|
|
|
fatalerror("reached unreachable");
|
|
return _Unwind_Reason_Code.FATAL_PHASE1_ERROR;
|
|
}
|
|
|
|
// These are the register numbers for SetGR that
|
|
// llvm's eh.exception and eh.selector intrinsics
|
|
// will pick up.
|
|
// Hints for these can be found by looking at the
|
|
// EH_RETURN_DATA_REGNO macro in GCC, careful testing
|
|
// is required though.
|
|
version (X86_64)
|
|
{
|
|
private int eh_exception_regno = 0;
|
|
private int eh_selector_regno = 1;
|
|
} else {
|
|
private int eh_exception_regno = 0;
|
|
private int eh_selector_regno = 2;
|
|
}
|
|
|
|
private _Unwind_Reason_Code _d_eh_install_catch_context(_Unwind_Action actions, ptrdiff_t switchval, ptrdiff_t landing_pad, _d_exception* exception_struct, _Unwind_Context_Ptr context)
|
|
{
|
|
debug(EH_personality) printf("Found catch clause!\n");
|
|
|
|
if(actions & _Unwind_Action.SEARCH_PHASE)
|
|
return _Unwind_Reason_Code.HANDLER_FOUND;
|
|
|
|
else if(actions & _Unwind_Action.HANDLER_PHASE)
|
|
{
|
|
debug(EH_personality) printf("Setting switch value to: %d!\n", switchval);
|
|
_Unwind_SetGR(context, eh_exception_regno, cast(ptrdiff_t)cast(void*)(exception_struct.exception_object));
|
|
_Unwind_SetGR(context, eh_selector_regno, cast(ptrdiff_t)switchval);
|
|
_Unwind_SetIP(context, landing_pad);
|
|
return _Unwind_Reason_Code.INSTALL_CONTEXT;
|
|
}
|
|
|
|
fatalerror("reached unreachable");
|
|
return _Unwind_Reason_Code.FATAL_PHASE2_ERROR;
|
|
}
|
|
|
|
private _Unwind_Reason_Code _d_eh_install_finally_context(_Unwind_Action actions, ptrdiff_t landing_pad, _d_exception* exception_struct, _Unwind_Context_Ptr context)
|
|
{
|
|
// if we're merely in search phase, continue
|
|
if(actions & _Unwind_Action.SEARCH_PHASE)
|
|
return _Unwind_Reason_Code.CONTINUE_UNWIND;
|
|
|
|
debug(EH_personality) printf("Calling cleanup routine...\n");
|
|
|
|
_Unwind_SetGR(context, eh_exception_regno, cast(ptrdiff_t)exception_struct);
|
|
_Unwind_SetGR(context, eh_selector_regno, 0);
|
|
_Unwind_SetIP(context, landing_pad);
|
|
return _Unwind_Reason_Code.INSTALL_CONTEXT;
|
|
}
|
|
|
|
private void _d_getLanguageSpecificTables(_Unwind_Context_Ptr context, ref ubyte* callsite, ref ubyte* action, ref ClassInfo* ci)
|
|
{
|
|
ubyte* data = cast(ubyte*)_Unwind_GetLanguageSpecificData(context);
|
|
|
|
//TODO: Do proper DWARF reading here
|
|
if(*data++ != 0xff)
|
|
fatalerror("DWARF header has unexpected format 1");
|
|
|
|
if(*data++ != 0x00)
|
|
fatalerror("DWARF header has unexpected format 2");
|
|
size_t cioffset;
|
|
data = get_uleb128(data, cioffset);
|
|
ci = cast(ClassInfo*)(data + cioffset);
|
|
|
|
if(*data++ != 0x03)
|
|
fatalerror("DWARF header has unexpected format 3");
|
|
size_t callsitelength;
|
|
data = get_uleb128(data, callsitelength);
|
|
action = data + callsitelength;
|
|
|
|
callsite = data;
|
|
}
|
|
|
|
} // end of x86 Linux specific implementation
|
|
|
|
|
|
extern(C) void _d_throw_exception(Object e)
|
|
{
|
|
if (e !is null)
|
|
{
|
|
_d_exception* exc_struct = new _d_exception;
|
|
exc_struct.unwind_info.exception_class[] = _d_exception_class;
|
|
exc_struct.exception_object = e;
|
|
_Unwind_Reason_Code ret = _Unwind_RaiseException(&exc_struct.unwind_info);
|
|
console("_Unwind_RaiseException failed with reason code: ")(ret)("\n");
|
|
}
|
|
abort();
|
|
}
|
|
|
|
extern(C) void _d_eh_resume_unwind(_d_exception* exception_struct)
|
|
{
|
|
_Unwind_Resume(&exception_struct.unwind_info);
|
|
}
|