* Clear faulting_p from a safepoint rather than inside general_error, because jumping into unwind-native-frames could blow up.
* Handle multiple faults from fatal_error by breakpointing. Is there anything else we can safely do at that point?
* Verify memory protection faults in the top half of the signal handlers because signal dispatch could fault. Treat memory faults during gc or fep as fatal errors.
* Add a function factor_vm::abort() that restores the default SIGABRT handler and ::abort()s. Use it from fatal_error() so we get useful context from gdb and so the user gets feedback from the system crash reporter that Factor blew up and didn't just disappear.
* In factorbug(), don't proceed with .s .r .c if it would be unsafe to do so.
* Don't pile on signals if we've already called fatal_error().
[ jit-jump-quot ]
\ (call) define-combinator-primitive
+: (jit-safepoint) ( -- )
+ 0 EAX MOVABS rc-absolute rel-safepoint ;
[
! Load ds and rs registers
jit-load-vm
jit-load-context
jit-restore-context
+ ! Safepoint to clear the faulting flag in the VM
+ (jit-safepoint)
+
! Windows-specific setup
ctx-reg jit-update-seh
EAX EDX [] MOV
jit-jump-quot ;
-[
- 0 EAX MOVABS rc-absolute rel-safepoint
-] \ jit-safepoint jit-define
+[ (jit-safepoint) ] \ jit-safepoint jit-define
[
jit-start-context-and-delete
[ jit-jump-quot ]
\ (call) define-combinator-primitive
+: (jit-safepoint)
+ 0 [RIP+] EAX MOV rc-relative rel-safepoint ;
+
[
! Unwind stack frames
RSP arg2 MOV
jit-load-context
jit-restore-context
+ ! Safepoint to clear the faulting flag in the VM
+ (jit-safepoint)
+
! Call quotation
jit-jump-quot
] \ unwind-native-frames define-sub-primitive
jit-push-param
jit-jump-quot ;
-[
- 0 [RIP+] EAX MOV rc-relative rel-safepoint
-] \ jit-safepoint jit-define
+[ (jit-safepoint) ] \ jit-safepoint jit-define
[
jit-start-context-and-delete
namespaces sequences tools.test math.private quotations
continuations prettyprint io.streams.string debugger assocs
sequences.private accessors locals.backend grouping words
-system ;
+system alien alien.accessors ;
IN: kernel.tests
[ 0 ] [ f size ] unit-test
[ t ] [ [ \ = \ = ] all-equal? ] unit-test
! Don't leak extra roots if error is thrown
-[ ] [ 10000 [ [ 3 throw ] ignore-errors ] times ] unit-test
+[ ] [ 1000 [ [ 3 throw ] ignore-errors ] times ] unit-test
-[ ] [ 10000 [ [ -1 f <array> ] ignore-errors ] times ] unit-test
+[ ] [ 1000 [ [ -1 f <array> ] ignore-errors ] times ] unit-test
! Make sure we report the correct error on stack underflow
[ clear drop ] [ { "kernel-error" 10 f f } = ] must-fail-with
[ t ] [ { } identity-hashcode fixnum? ] unit-test
[ 123 ] [ 123 identity-hashcode ] unit-test
[ t ] [ f identity-hashcode fixnum? ] unit-test
+
+! Make sure memory protection faults work
+[ f 0 alien-unsigned-1 ] [ vm-error? ] must-fail-with
+[ 1 <alien> 0 alien-unsigned-1 ] [ vm-error? ] must-fail-with
void factor_vm::print_datastack()
{
std::cout << "==== DATA STACK:" << std::endl;
- print_objects((cell *)ctx->datastack_seg->start,(cell *)ctx->datastack);
+ if (ctx)
+ print_objects((cell *)ctx->datastack_seg->start,(cell *)ctx->datastack);
+ else
+ std::cout << "*** Context not initialized" << std::endl;
}
void factor_vm::print_retainstack()
{
std::cout << "==== RETAIN STACK:" << std::endl;
- print_objects((cell *)ctx->retainstack_seg->start,(cell *)ctx->retainstack);
+ if (ctx)
+ print_objects((cell *)ctx->retainstack_seg->start,(cell *)ctx->retainstack);
+ else
+ std::cout << "*** Context not initialized" << std::endl;
}
struct stack_frame_printer {
void factor_vm::print_callstack()
{
std::cout << "==== CALL STACK:" << std::endl;
- stack_frame_printer printer(this);
- iterate_callstack(ctx,printer);
+ if (ctx)
+ {
+ stack_frame_printer printer(this);
+ iterate_callstack(ctx,printer);
+ }
+ else
+ std::cout << "*** Context not initialized" << std::endl;
}
struct padded_address {
std::cout << " push <addr> -- push object on data stack - NOT SAFE" << std::endl;
std::cout << " gc -- trigger full GC - NOT SAFE" << std::endl;
std::cout << " code -- code heap dump" << std::endl;
+ std::cout << " abort -- call abort()" << std::endl;
+ std::cout << " breakpoint -- trigger system breakpoint" << std::endl;
}
else
{
primitive_full_gc();
else if(strcmp(cmd,"help") == 0)
factorbug_usage(true);
+ else if(strcmp(cmd,"abort") == 0)
+ abort();
+ else if(strcmp(cmd,"breakpoint") == 0)
+ breakpoint();
else
std::cout << "unknown command" << std::endl;
}
namespace factor
{
+bool factor_vm::fatal_erroring_p;
+
+static inline void fa_diddly_atal_error()
+{
+ printf("fatal_error in fatal_error!\n");
+ breakpoint();
+ exit(86);
+}
+
void fatal_error(const char *msg, cell tagged)
{
+ if (factor_vm::fatal_erroring_p)
+ fa_diddly_atal_error();
+
+ factor_vm::fatal_erroring_p = true;
+
std::cout << "fatal_error: " << msg;
- std::cout << ": " << std::hex << tagged << std::dec;
+ std::cout << ": " << (void*)tagged;
std::cout << std::endl;
- exit(1);
+ abort();
}
void critical_error(const char *msg, cell tagged)
ctx->push(error_object);
- faulting_p = false;
+ /* Guard the safepoint, which will clear faulting_p if unwind-native-frames
+ succeeds */
+ code->guard_safepoint();
unwind_native_frames(special_objects[ERROR_HANDLER_QUOT],
ctx->callstack_top);
}
std::cout << "error: " << error << std::endl;
std::cout << "arg 1: "; print_obj(arg1); std::cout << std::endl;
std::cout << "arg 2: "; print_obj(arg2); std::cout << std::endl;
- faulting_p = false;
factorbug();
+ abort();
}
}
general_error(ERROR_NOT_IMPLEMENTED,false_object,false_object);
}
+void factor_vm::verify_memory_protection_error(cell addr)
+{
+ /* Called from the OS-specific top halves of the signal handlers to
+ make sure it's safe to dispatch to memory_protection_error */
+ if(fatal_erroring_p)
+ fa_diddly_atal_error();
+ if(faulting_p && !code->safepoint_p(addr))
+ fatal_error("Double fault", addr);
+ else if(fep_p)
+ fatal_error("Memory protection fault during low-level debugger", addr);
+ else if(atomic::load(¤t_gc_p))
+ fatal_error("Memory protection fault during gc", addr);
+}
+
void factor_vm::memory_protection_error(cell addr)
{
if(code->safepoint_p(addr))
safepoint.handle_safepoint(this);
- else if(faulting_p)
- fatal_error("Double fault", 0);
else if(ctx->datastack_seg->underflow_p(addr))
general_error(ERROR_DATASTACK_UNDERFLOW,false_object,false_object);
else if(ctx->datastack_seg->overflow_p(addr))
if(exception == EXC_BAD_ACCESS)
{
signal_fault_addr = MACH_EXC_STATE_FAULT(exc_state);
+ verify_memory_protection_error(signal_fault_addr);
handler = (cell)factor::memory_signal_handler_impl;
}
else if(exception == EXC_ARITHMETIC && code != MACH_EXC_INTEGER_DIV)
void memory_signal_handler(int signal, siginfo_t *siginfo, void *uap)
{
factor_vm *vm = current_vm();
+ vm->verify_memory_protection_error((cell)siginfo->si_addr);
vm->signal_fault_addr = (cell)siginfo->si_addr;
vm->dispatch_signal(uap,factor::memory_signal_handler_impl);
}
void synchronous_signal_handler(int signal, siginfo_t *siginfo, void *uap)
{
+ if (factor_vm::fatal_erroring_p)
+ return;
+
factor_vm *vm = current_vm_p();
if (vm)
{
vm->signal_number = signal;
vm->dispatch_signal(uap,factor::synchronous_signal_handler_impl);
- } else
+ }
+ else
fatal_error("Foreign thread received signal", signal);
}
void enqueue_signal_handler(int signal, siginfo_t *siginfo, void *uap)
{
+ if (factor_vm::fatal_erroring_p)
+ return;
+
factor_vm *vm = current_vm_p();
if (vm)
enqueue_signal(vm, signal);
void fep_signal_handler(int signal, siginfo_t *siginfo, void *uap)
{
+ if (factor_vm::fatal_erroring_p)
+ return;
+
factor_vm *vm = current_vm_p();
if (vm)
{
while(ret == -1 && errno == EINTR);
if(ret == -1)
- fatal_error("sigaction failed", 0);
+ fatal_error("sigaction failed", errno);
}
static void init_sigaction_with_handler(struct sigaction *act,
pthread_mutex_unlock(&stdin_mutex);
}
+void factor_vm::abort()
+{
+ sig_t ret;
+ do
+ {
+ ret = signal(SIGABRT, SIG_DFL);
+ }
+ while(ret == SIG_ERR && errno == EINTR);
+
+ ::abort();
+}
+
}
void move_file(const vm_char *path1, const vm_char *path2);
+static inline void breakpoint()
+{
+ __builtin_trap();
+}
+
}
Sleep((DWORD)(nsec/1000000));
}
+typedef enum _EXCEPTION_DISPOSITION
+{
+ ExceptionContinueExecution = 0,
+ ExceptionContinueSearch = 1,
+ ExceptionNestedException = 2,
+ ExceptionCollidedUnwind = 3
+} EXCEPTION_DISPOSITION;
+
LONG factor_vm::exception_handler(PEXCEPTION_RECORD e, void *frame, PCONTEXT c, void *dispatch)
{
switch (e->ExceptionCode)
{
case EXCEPTION_ACCESS_VIOLATION:
signal_fault_addr = e->ExceptionInformation[1];
+ verify_memory_protection_error(signal_fault_addr);
dispatch_signal_handler(
(cell*)&c->ESP,
(cell*)&c->EIP,
break;
}
- return 0;
+ return ExceptionContinueExecution;
}
VM_C_API LONG exception_handler(PEXCEPTION_RECORD e, void *frame, PCONTEXT c, void *dispatch)
{
+ if (factor_vm::fatal_erroring_p)
+ return ExceptionContinueSearch;
+
factor_vm *vm = current_vm_p();
if (vm)
return vm->exception_handler(e,frame,c,dispatch);
else
- {
- fatal_error("Foreign thread received exception", e->ExceptionCode);
- return 0; // to placate MSVC
- }
+ return ExceptionContinueSearch;
}
static BOOL WINAPI ctrl_handler(DWORD dwCtrlType)
sampler_thread = NULL;
}
+void factor_vm::abort()
+{
+ ::abort();
+}
+
}
return threadHandle;
}
+inline static breakpoint()
+{
+ DebugBreak();
+}
+
#define CODE_TO_FUNCTION_POINTER(code) (void)0
#define CODE_TO_FUNCTION_POINTER_CALLBACK(vm, code) (void)0
#define FUNCTION_CODE_POINTER(ptr) ptr
void safepoint_state::handle_safepoint(factor_vm *parent) volatile
{
parent->code->unguard_safepoint();
+ parent->faulting_p = false;
if (atomic::load(&fep_p))
{
/* Are we already handling a fault? Used to catch double memory faults */
bool faulting_p;
+ static bool fatal_erroring_p;
/* Safepoint state */
volatile safepoint_state safepoint;
void general_error(vm_error_type error, cell arg1, cell arg2);
void type_error(cell type, cell tagged);
void not_implemented_error();
+ void verify_memory_protection_error(cell addr);
void memory_protection_error(cell addr);
void signal_error(cell signal);
void divide_by_zero_error();
void open_console();
void lock_console();
void unlock_console();
+ static void abort();
// os-windows
#if defined(WINDOWS)