mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-12 12:26:29 +00:00
Branches: main, 11.07 Fix bug #243: the management of memory zones on Windows was broken, causing a memory leak in trseg and (presumably) stseg grades. In grades that use conservative GC on most systems (e.g. Linux or Mac OS X), allocation of memory for memory zones is handled by the Boehm collector. On Windows, the situation was a bit different because we were trying to emulate mprotect style memory protection. Instead of allocating the memory for the zone using the Boehm collector, the memory was reserved using a call to VirtualAlloc and then all the memory within the zone was added to the Boehm GC's root set. We could then use VirtualAlloc and friends to emulate mprotect. In principle, this is fine, in practice there are number of problems with it. When a memory zone was deallocated the call to VirtualFree was silently failing (the return value of this call wasn't checked) -- this meant that the memory in the zone was not being freed, hence the leak. The immediate cause of this is that we are calling VirtualFree with the wrong argument values for those used in the corresponding call to VirtulAlloc. Unfortunately, putting the "correct" value in, just results in a segmentation fault. (I suspect we haven't actually been allocating the memory correctly either.) Aside from this a second problem is that while we register the words in the memory zone as GC roots when we allocate it, we do not remove them from the GC root set when we deallocate the zone. One rather obvious reason for this is that the Boehm GC doesn't support doing this on Windows! Yet another issue with all this is that while MR_protect_pages calls VirtualAlloc, the documentation in the code suggests it should be calling VirtualProtect. The fix here is to replace the VirtualAlloc way of managing memory zones on Windows with the Boehm GC approach used on all the other systems. The former is pretty much completely broken and only appeared to work because most grades only allocate a few memory zones at program startup and never deallocate them. This does mean the mprotect emulation we were doing on Windows will no longer work, but that's the lesser of two evils. (It may be possible to reinstate something like it using VirtualProtect, but that will require looking carefully at exactly how the Boehm collector allocates memory on Windows.) runtime/mercury_memory_zones.c: Use the Boehm GC for allocating memory zones on Windows when in grade that uses conservative GC. Delete the flawed approach that uses VirtualAlloc. runtime/mercury_conf_param.h: Do not define the macro MR_WIN32_VIRTUAL_ALLOC on Windows: we no longer use VirtualAlloc anywhere. Do not define MR_PROTECTPAGE and MR_CHECK_OVERFLOW_VIA_MPROTECT on Windows, since we no longer do any mprotect emulation on Windows. runtime/mercury_memory_handlers.c: Properly protect some code in the handler for Windows memory access errors that assumes that memory zones have a redzone. (After this change they won't have a redzone.)
1049 lines
30 KiB
C
1049 lines
30 KiB
C
/*
|
|
** vim: ts=4 sw=4 expandtab
|
|
*/
|
|
/*
|
|
** Copyright (C) 1998, 2000, 2002, 2005-2007, 2010-2011 The University of Melbourne.
|
|
** This file may only be copied under the terms of the GNU Library General
|
|
** Public License - see the file COPYING.LIB in the Mercury distribution.
|
|
*/
|
|
|
|
/*
|
|
** This module defines the signal handlers for memory zones.
|
|
** These handlers are invoked when memory is accessed outside of
|
|
** the memory zones, or at the protected region at the end of a
|
|
** memory zone (if available).
|
|
*/
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
#include "mercury_imp.h"
|
|
|
|
#ifdef MR_HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
/*
|
|
** This include must come before anything else that might include <signal.h>.
|
|
** See the commments in mercury_signal.h.
|
|
*/
|
|
#include "mercury_signal.h"
|
|
|
|
#ifdef MR_HAVE_SYS_SIGINFO_H
|
|
#include <sys/siginfo.h>
|
|
#endif
|
|
|
|
#ifdef MR_HAVE_SYS_SIGNAL_H
|
|
/* on FREEBSD we need to include <sys/signal.h> before <ucontext.h> */
|
|
#include <sys/signal.h>
|
|
#endif
|
|
|
|
#ifdef MR_HAVE_MPROTECT
|
|
#include <sys/mman.h>
|
|
#endif
|
|
|
|
#ifdef MR_HAVE_UCONTEXT_H
|
|
#include <ucontext.h>
|
|
#endif
|
|
|
|
#ifdef MR_HAVE_SYS_UCONTEXT_H
|
|
#include <sys/ucontext.h>
|
|
#endif
|
|
|
|
#include "mercury_trace_base.h"
|
|
#include "mercury_memory_zones.h"
|
|
#include "mercury_memory_handlers.h"
|
|
#include "mercury_faultaddr.h"
|
|
#include "mercury_threadscope.h"
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
#ifdef MR_HAVE_SIGINFO
|
|
#if defined(MR_HAVE_SIGCONTEXT_STRUCT)
|
|
#if defined(MR_HAVE_SIGCONTEXT_STRUCT_3ARG)
|
|
static void complex_sighandler_3arg(int, int,
|
|
struct sigcontext_struct);
|
|
#else
|
|
static void complex_sighandler(int, struct sigcontext_struct);
|
|
#endif
|
|
#elif defined(MR_HAVE_SIGINFO_T)
|
|
static void complex_bushandler(int, siginfo_t *, void *);
|
|
static void complex_segvhandler(int, siginfo_t *, void *);
|
|
#else
|
|
#error "MR_HAVE_SIGINFO defined but don't know how to get it"
|
|
#endif
|
|
#else
|
|
static void simple_sighandler(int);
|
|
#endif
|
|
|
|
#ifdef MR_HAVE_SIGINFO
|
|
#if defined(MR_HAVE_SIGCONTEXT_STRUCT)
|
|
#if defined(MR_HAVE_SIGCONTEXT_STRUCT_3ARG)
|
|
#define bus_handler complex_sighandler_3arg
|
|
#define segv_handler complex_sighandler_3arg
|
|
#else
|
|
#define bus_handler complex_sighandler
|
|
#define segv_handler complex_sighandler
|
|
#endif
|
|
#elif defined(MR_HAVE_SIGINFO_T)
|
|
#define bus_handler complex_bushandler
|
|
#define segv_handler complex_segvhandler
|
|
#else
|
|
#error "MR_HAVE_SIGINFO defined but don't know how to get it"
|
|
#endif
|
|
#else
|
|
#define bus_handler simple_sighandler
|
|
#define segv_handler simple_sighandler
|
|
#endif
|
|
|
|
/*
|
|
** round_up(amount, align) returns `amount' rounded up to the nearest
|
|
** alignment boundary. `align' must be a power of 2.
|
|
*/
|
|
|
|
static void MR_print_dump_stack(void);
|
|
static MR_bool MR_try_munprotect(void *address, void *context);
|
|
static char *MR_explain_context(void *context);
|
|
static MR_Code *get_pc_from_context(void *the_context);
|
|
static MR_Word *get_sp_from_context(void *the_context);
|
|
static MR_Word *get_curfr_from_context(void *the_context);
|
|
static void leave_signal_handler(int sig);
|
|
|
|
#define STDERR 2
|
|
|
|
/*
|
|
** Note that we cannot assume that the memory zones
|
|
** have been initialized here, since MR_setup_signals()
|
|
** gets called before MR_init_memory_zones().
|
|
** However, the code here will work fine if
|
|
** used_memory_zones is null.
|
|
*/
|
|
|
|
static MR_bool
|
|
MR_try_munprotect(void *addr, void *context)
|
|
{
|
|
#if !defined(MR_HAVE_SIGINFO)
|
|
return MR_FALSE;
|
|
#else
|
|
MR_Word *fault_addr;
|
|
MR_MemoryZone *zone;
|
|
|
|
fault_addr = (MR_Word *) addr;
|
|
|
|
zone = MR_get_used_memory_zones_readonly();
|
|
|
|
if (MR_memdebug) {
|
|
fprintf(stderr, "caught fault at %p\n", (void *)addr);
|
|
}
|
|
|
|
while(zone != NULL) {
|
|
#ifdef MR_CHECK_OVERFLOW_VIA_MPROTECT
|
|
if (MR_memdebug) {
|
|
fprintf(stderr, "checking %s#%" MR_INTEGER_LENGTH_MODIFIER
|
|
"d: %p - %p\n",
|
|
zone->MR_zone_name, zone->MR_zone_id,
|
|
(void *) zone->MR_zone_redzone,
|
|
(void *) zone->MR_zone_top);
|
|
}
|
|
|
|
if (zone->MR_zone_redzone <= fault_addr
|
|
&& fault_addr <= zone->MR_zone_top)
|
|
{
|
|
if (MR_memdebug) {
|
|
fprintf(stderr, "address is in %s#% "
|
|
MR_INTEGER_LENGTH_MODIFIER "d redzone\n",
|
|
zone->MR_zone_name, zone->MR_zone_id);
|
|
}
|
|
|
|
return zone->MR_zone_handler(fault_addr, zone, context);
|
|
}
|
|
#endif
|
|
zone = zone->MR_zone_next;
|
|
}
|
|
|
|
if (MR_memdebug) {
|
|
fprintf(stderr, "address not in any redzone.\n");
|
|
}
|
|
|
|
return MR_FALSE;
|
|
#endif /* MR_HAVE_SIGINFO */
|
|
}
|
|
|
|
MR_bool
|
|
MR_null_handler(MR_Word *fault_addr, MR_MemoryZone *zone, void *context)
|
|
{
|
|
return MR_FALSE;
|
|
}
|
|
|
|
/*
|
|
** MR_fatal_abort() prints an error message, possibly a stack dump,
|
|
** and then exits. It is like MR_fatal_error(), except that it is safe to call
|
|
** from a signal handler.
|
|
*/
|
|
|
|
static void
|
|
MR_fatal_abort(void *context, const char *main_msg, int dump)
|
|
{
|
|
char *context_msg;
|
|
int ret;
|
|
|
|
context_msg = MR_explain_context(context);
|
|
do {
|
|
ret = write(STDERR, main_msg, strlen(main_msg));
|
|
} while (ret == -1 && MR_is_eintr(errno));
|
|
do {
|
|
ret = write(STDERR, context_msg, strlen(context_msg));
|
|
} while (ret == -1 && MR_is_eintr(errno));
|
|
MR_trace_report_raw(STDERR);
|
|
|
|
if (dump) {
|
|
MR_print_dump_stack();
|
|
}
|
|
|
|
_exit(1);
|
|
}
|
|
|
|
MR_bool
|
|
MR_default_handler(MR_Word *fault_addr, MR_MemoryZone *zone, void *context)
|
|
{
|
|
#ifndef MR_CHECK_OVERFLOW_VIA_MPROTECT
|
|
return MR_FALSE;
|
|
#else
|
|
MR_Word *new_zone;
|
|
size_t zone_size;
|
|
|
|
new_zone = (MR_Word *) MR_round_up((MR_Unsigned) fault_addr
|
|
+ sizeof(MR_Word), MR_unit);
|
|
|
|
if (new_zone <= zone->MR_zone_hardmax) {
|
|
zone_size = (char *) new_zone - (char *) zone->MR_zone_redzone;
|
|
|
|
if (MR_memdebug) {
|
|
fprintf(stderr, "trying to unprotect %s#%"
|
|
MR_INTEGER_LENGTH_MODIFIER "d from %p to %p (%x)\n",
|
|
zone->MR_zone_name, zone->MR_zone_id,
|
|
(void *) zone->MR_zone_redzone, (void *) new_zone,
|
|
(int) zone_size);
|
|
}
|
|
if (MR_protect_pages((char *) zone->MR_zone_redzone, zone_size,
|
|
PROT_READ|PROT_WRITE) < 0)
|
|
{
|
|
char buf[2560];
|
|
sprintf(buf, "Mercury runtime: cannot unprotect %s#%"
|
|
MR_INTEGER_LENGTH_MODIFIER "d zone",
|
|
zone->MR_zone_name, zone->MR_zone_id);
|
|
perror(buf);
|
|
exit(1);
|
|
}
|
|
|
|
zone->MR_zone_redzone = new_zone;
|
|
|
|
if (MR_memdebug) {
|
|
fprintf(stderr, "successful: %s#%" MR_INTEGER_LENGTH_MODIFIER
|
|
"d redzone now %p to %p\n",
|
|
zone->MR_zone_name, zone->MR_zone_id,
|
|
(void *) zone->MR_zone_redzone, (void *) zone->MR_zone_top);
|
|
}
|
|
#if defined(MR_NATIVE_GC) && !defined(MR_HIGHLEVEL_CODE)
|
|
MR_schedule_agc(get_pc_from_context(context),
|
|
get_sp_from_context(context),
|
|
get_curfr_from_context(context));
|
|
#endif
|
|
return MR_TRUE;
|
|
} else {
|
|
char buf[2560];
|
|
if (MR_memdebug) {
|
|
fprintf(stderr, "can't unprotect last page of %s#%"
|
|
MR_INTEGER_LENGTH_MODIFIER "d\n",
|
|
zone->MR_zone_name, zone->MR_zone_id);
|
|
fflush(stdout);
|
|
}
|
|
#ifdef MR_STACK_EXTEND_DEBUG
|
|
MR_restore_transient_registers();
|
|
fprintf(stderr, "sp = %p, maxfr = %p\n", MR_sp, MR_maxfr);
|
|
MR_debug_memory_zone(stderr, zone);
|
|
#endif
|
|
sprintf(buf, "\nMercury runtime: memory zone %s#%"
|
|
MR_INTEGER_LENGTH_MODIFIER "d overflowed\n",
|
|
zone->MR_zone_name, zone->MR_zone_id);
|
|
MR_fatal_abort(context, buf, MR_TRUE);
|
|
}
|
|
|
|
return MR_FALSE;
|
|
#endif
|
|
}
|
|
|
|
void
|
|
MR_setup_signals(void)
|
|
{
|
|
/*
|
|
** When using Microsoft Visual C structured exceptions don't set any
|
|
** signal handlers.
|
|
** See mercury_wrapper.c for the reason why.
|
|
*/
|
|
#ifndef MR_MSVC_STRUCTURED_EXCEPTIONS
|
|
#ifdef SIGBUS
|
|
MR_setup_signal(SIGBUS, (MR_Code *) bus_handler, MR_TRUE,
|
|
"cannot set SIGBUS handler");
|
|
#endif
|
|
MR_setup_signal(SIGSEGV, (MR_Code *) segv_handler, MR_TRUE,
|
|
"cannot set SIGSEGV handler");
|
|
#endif
|
|
}
|
|
|
|
static char *
|
|
MR_explain_context(void *the_context)
|
|
{
|
|
static char buf[100];
|
|
|
|
#if defined(MR_HAVE_SIGCONTEXT_STRUCT)
|
|
|
|
#ifdef MR_PC_ACCESS
|
|
struct sigcontext_struct *context = the_context;
|
|
void *pc_at_signal = (void *) context->MR_PC_ACCESS;
|
|
|
|
sprintf(buf, "PC at signal: %ld (%lx)\n",
|
|
(long)pc_at_signal, (long)pc_at_signal);
|
|
#else
|
|
buf[0] = '\0';
|
|
#endif
|
|
|
|
#elif defined(MR_HAVE_SIGINFO_T)
|
|
|
|
#ifdef MR_PC_ACCESS
|
|
|
|
ucontext_t *context = the_context;
|
|
|
|
#ifdef MR_PC_ACCESS_GREG
|
|
sprintf(buf, "PC at signal: %ld (%lx)\n",
|
|
(long) context->uc_mcontext.gregs[MR_PC_ACCESS],
|
|
(long) context->uc_mcontext.gregs[MR_PC_ACCESS]);
|
|
#else
|
|
sprintf(buf, "PC at signal: %ld (%lx)\n",
|
|
(long) context->uc_mcontext.MR_PC_ACCESS,
|
|
(long) context->uc_mcontext.MR_PC_ACCESS);
|
|
#endif
|
|
|
|
#else /* not MR_PC_ACCESS */
|
|
|
|
/* if MR_PC_ACCESS is not set, we don't know the context */
|
|
/* therefore we return an empty string to be printed */
|
|
buf[0] = '\0';
|
|
|
|
#endif /* not MR_PC_ACCESS */
|
|
|
|
#else /* not MR_HAVE_SIGINFO_T && not MR_HAVE_SIGCONTEXT_STRUCT */
|
|
|
|
buf[0] = '\0';
|
|
|
|
#endif
|
|
|
|
return buf;
|
|
}
|
|
|
|
#if defined(MR_HAVE_SIGCONTEXT_STRUCT)
|
|
#if defined(MR_HAVE_SIGCONTEXT_STRUCT_3ARG)
|
|
static void
|
|
complex_sighandler_3arg(int sig, int code,
|
|
struct sigcontext_struct sigcontext)
|
|
#else
|
|
static void
|
|
complex_sighandler(int sig, struct sigcontext_struct sigcontext)
|
|
#endif
|
|
{
|
|
void *address = (void *) MR_GET_FAULT_ADDR(sigcontext);
|
|
#ifdef MR_PC_ACCESS
|
|
void *pc_at_signal = (void *) sigcontext.MR_PC_ACCESS;
|
|
#endif
|
|
|
|
switch(sig) {
|
|
case SIGSEGV:
|
|
/*
|
|
** If we're debugging, print the segv explanation
|
|
** messages before we call MR_try_munprotect. But if
|
|
** we're not debugging, only print them if
|
|
** MR_try_munprotect fails.
|
|
*/
|
|
if (MR_memdebug) {
|
|
fflush(stdout);
|
|
fprintf(stderr, "\n*** Mercury runtime: "
|
|
"caught segmentation violation ***\n");
|
|
}
|
|
if (MR_try_munprotect(address, &sigcontext)) {
|
|
if (MR_memdebug) {
|
|
fprintf(stderr, "returning from "
|
|
"signal handler\n\n");
|
|
}
|
|
return;
|
|
}
|
|
if (!MR_memdebug) {
|
|
fflush(stdout);
|
|
fprintf(stderr, "\n*** Mercury runtime: "
|
|
"caught segmentation violation ***\n");
|
|
}
|
|
break;
|
|
|
|
#ifdef SIGBUS
|
|
case SIGBUS:
|
|
fflush(stdout);
|
|
fprintf(stderr, "\n*** Mercury runtime: "
|
|
"caught bus error ***\n");
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
fflush(stdout);
|
|
fprintf(stderr, "\n*** Mercury runtime: "
|
|
"caught unknown signal %d ***\n", sig);
|
|
break;
|
|
}
|
|
|
|
#ifdef MR_PC_ACCESS
|
|
fprintf(stderr, "PC at signal: %ld (%lx)\n",
|
|
(long) pc_at_signal, (long) pc_at_signal);
|
|
#endif
|
|
fprintf(stderr, "address involved: %p\n", address);
|
|
|
|
MR_trace_report(stderr);
|
|
MR_print_dump_stack();
|
|
MR_dump_prev_locations();
|
|
leave_signal_handler(sig);
|
|
} /* end complex_sighandler() */
|
|
|
|
#elif defined(MR_HAVE_SIGINFO_T)
|
|
|
|
static void
|
|
complex_bushandler(int sig, siginfo_t *info, void *context)
|
|
{
|
|
fflush(stdout);
|
|
|
|
if (sig != SIGBUS || !info || info->si_signo != SIGBUS) {
|
|
MR_fatal_abort(context, "\n*** Mercury runtime: "
|
|
"caught strange bus error ***\n", 1);
|
|
}
|
|
|
|
fprintf(stderr, "\n*** Mercury runtime: ");
|
|
fprintf(stderr, "caught bus error ***\n");
|
|
|
|
if (info->si_code > 0) {
|
|
fprintf(stderr, "cause: ");
|
|
switch (info->si_code)
|
|
{
|
|
#ifdef BUS_ADRALN
|
|
case BUS_ADRALN:
|
|
fprintf(stderr, "invalid address alignment\n");
|
|
break;
|
|
#endif
|
|
|
|
#ifdef BUS_ADRERR
|
|
case BUS_ADRERR:
|
|
fprintf(stderr, "non-existent physical address\n");
|
|
break;
|
|
#endif
|
|
|
|
#ifdef BUS_OBJERR
|
|
case BUS_OBJERR:
|
|
fprintf(stderr, "object specific hardware error\n");
|
|
break;
|
|
#endif
|
|
|
|
#ifdef BUS_PAGE_FAULT
|
|
case BUS_PAGE_FAULT:
|
|
fprintf(stderr, "page fault protection base\n");
|
|
break;
|
|
#endif
|
|
|
|
#ifdef BUS_SEGNP_FAULT
|
|
case BUS_SEGNP_FAULT:
|
|
fprintf(stderr, "segment not present\n");
|
|
break;
|
|
#endif
|
|
|
|
#ifdef BUS_STK_FAULT
|
|
case BUS_STK_FAULT:
|
|
fprintf(stderr, "stack segment\n");
|
|
break;
|
|
#endif
|
|
|
|
#ifdef BUS_SEGM_FAULT
|
|
case BUS_SEGM_FAULT:
|
|
fprintf(stderr, "segment protection base\n");
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
fprintf(stderr, "unknown\n");
|
|
break;
|
|
|
|
} /* end switch */
|
|
|
|
fprintf(stderr, "%s", MR_explain_context(context));
|
|
fprintf(stderr, "address involved: %p\n",
|
|
(void *) info->si_addr);
|
|
} /* end if */
|
|
|
|
MR_trace_report(stderr);
|
|
MR_print_dump_stack();
|
|
MR_dump_prev_locations();
|
|
leave_signal_handler(sig);
|
|
} /* end complex_bushandler() */
|
|
|
|
static void
|
|
MR_explain_segv(siginfo_t *info, void *context)
|
|
{
|
|
fflush(stdout);
|
|
|
|
fprintf(stderr, "\n*** Mercury runtime: ");
|
|
fprintf(stderr, "caught segmentation violation ***\n");
|
|
|
|
if (!info) {
|
|
return;
|
|
}
|
|
|
|
if (info->si_code > 0) {
|
|
fprintf(stderr, "cause: ");
|
|
switch (info->si_code)
|
|
{
|
|
#ifdef SEGV_MAPERR
|
|
case SEGV_MAPERR:
|
|
fprintf(stderr, "address not mapped to object\n");
|
|
break;
|
|
#endif
|
|
|
|
#ifdef SEGV_ACCERR
|
|
case SEGV_ACCERR:
|
|
fprintf(stderr, "bad permissions for mapped object\n");
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
fprintf(stderr, "unknown\n");
|
|
break;
|
|
}
|
|
|
|
fprintf(stderr, "%s", MR_explain_context(context));
|
|
fprintf(stderr, "address involved: %p\n",
|
|
(void *) info->si_addr);
|
|
|
|
} /* end if */
|
|
}
|
|
|
|
static void
|
|
complex_segvhandler(int sig, siginfo_t *info, void *context)
|
|
{
|
|
if (sig != SIGSEGV || !info || info->si_signo != SIGSEGV) {
|
|
MR_fatal_abort(context, "\n*** Mercury runtime: "
|
|
"caught strange segmentation violation ***\n", 1);
|
|
}
|
|
|
|
/*
|
|
** If we're debugging, print the segv explanation messages
|
|
** before we call MR_try_munprotect. But if we're not debugging,
|
|
** only print them if MR_try_munprotect fails.
|
|
*/
|
|
|
|
if (MR_memdebug) {
|
|
MR_explain_segv(info, context);
|
|
}
|
|
|
|
if (MR_try_munprotect(info->si_addr, context)) {
|
|
if (MR_memdebug) {
|
|
fprintf(stderr, "returning from signal handler\n\n");
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (!MR_memdebug) {
|
|
MR_explain_segv(info, context);
|
|
}
|
|
|
|
MR_trace_report(stderr);
|
|
MR_print_dump_stack();
|
|
MR_dump_prev_locations();
|
|
leave_signal_handler(sig);
|
|
} /* end complex_segvhandler */
|
|
|
|
#else /* not MR_HAVE_SIGINFO_T && not MR_HAVE_SIGCONTEXT_STRUCT */
|
|
|
|
static void
|
|
simple_sighandler(int sig)
|
|
{
|
|
fflush(stdout);
|
|
fprintf(stderr, "*** Mercury runtime: ");
|
|
|
|
switch (sig)
|
|
{
|
|
#ifdef SIGBUS
|
|
case SIGBUS:
|
|
fprintf(stderr, "caught bus error ***\n");
|
|
break;
|
|
#endif
|
|
|
|
case SIGSEGV:
|
|
fprintf(stderr, "caught segmentation violation ***\n");
|
|
break;
|
|
|
|
default:
|
|
fprintf(stderr, "caught unknown signal %d ***\n", sig);
|
|
break;
|
|
}
|
|
|
|
MR_print_dump_stack();
|
|
MR_dump_prev_locations();
|
|
leave_signal_handler(sig);
|
|
}
|
|
|
|
#endif /* not MR_HAVE_SIGINFO_T && not MR_HAVE_SIGCONTEXT_STRUCT */
|
|
|
|
#ifdef MR_MSVC_STRUCTURED_EXCEPTIONS
|
|
static const char *MR_find_exception_name(DWORD exception_code);
|
|
static void MR_explain_exception_record(EXCEPTION_RECORD *rec);
|
|
static void MR_dump_exception_record(EXCEPTION_RECORD *rec);
|
|
static MR_bool MR_exception_record_is_access_violation(EXCEPTION_RECORD *rec,
|
|
void **address_ptr, int *access_mode_ptr);
|
|
|
|
/*
|
|
** Exception code and their string representation
|
|
*/
|
|
#define DEFINE_EXCEPTION_NAME(a) {a,#a}
|
|
|
|
typedef struct
|
|
{
|
|
DWORD exception_code;
|
|
const char *exception_name;
|
|
} MR_ExceptionName;
|
|
|
|
static const
|
|
MR_ExceptionName MR_exception_names[] =
|
|
{
|
|
DEFINE_EXCEPTION_NAME(EXCEPTION_ACCESS_VIOLATION),
|
|
DEFINE_EXCEPTION_NAME(EXCEPTION_DATATYPE_MISALIGNMENT),
|
|
DEFINE_EXCEPTION_NAME(EXCEPTION_BREAKPOINT),
|
|
DEFINE_EXCEPTION_NAME(EXCEPTION_SINGLE_STEP),
|
|
DEFINE_EXCEPTION_NAME(EXCEPTION_ARRAY_BOUNDS_EXCEEDED),
|
|
DEFINE_EXCEPTION_NAME(EXCEPTION_FLT_DENORMAL_OPERAND),
|
|
DEFINE_EXCEPTION_NAME(EXCEPTION_FLT_DIVIDE_BY_ZERO),
|
|
DEFINE_EXCEPTION_NAME(EXCEPTION_FLT_INEXACT_RESULT),
|
|
DEFINE_EXCEPTION_NAME(EXCEPTION_FLT_INVALID_OPERATION),
|
|
DEFINE_EXCEPTION_NAME(EXCEPTION_FLT_OVERFLOW),
|
|
DEFINE_EXCEPTION_NAME(EXCEPTION_FLT_STACK_CHECK),
|
|
DEFINE_EXCEPTION_NAME(EXCEPTION_FLT_UNDERFLOW),
|
|
DEFINE_EXCEPTION_NAME(EXCEPTION_INT_DIVIDE_BY_ZERO),
|
|
DEFINE_EXCEPTION_NAME(EXCEPTION_INT_OVERFLOW),
|
|
DEFINE_EXCEPTION_NAME(EXCEPTION_PRIV_INSTRUCTION),
|
|
DEFINE_EXCEPTION_NAME(EXCEPTION_IN_PAGE_ERROR),
|
|
DEFINE_EXCEPTION_NAME(EXCEPTION_ILLEGAL_INSTRUCTION),
|
|
DEFINE_EXCEPTION_NAME(EXCEPTION_NONCONTINUABLE_EXCEPTION),
|
|
DEFINE_EXCEPTION_NAME(EXCEPTION_STACK_OVERFLOW),
|
|
DEFINE_EXCEPTION_NAME(EXCEPTION_INVALID_DISPOSITION),
|
|
DEFINE_EXCEPTION_NAME(EXCEPTION_GUARD_PAGE),
|
|
DEFINE_EXCEPTION_NAME(EXCEPTION_INVALID_HANDLE)
|
|
};
|
|
|
|
/*
|
|
** Retrieve the name of a Win32 exception code as a string
|
|
*/
|
|
static const char *
|
|
MR_find_exception_name(DWORD exception_code)
|
|
{
|
|
int i;
|
|
for (i = 0; i < sizeof(MR_exception_names)
|
|
/ sizeof(MR_ExceptionName); i++)
|
|
{
|
|
if (MR_exception_names[i].exception_code == exception_code) {
|
|
return MR_exception_names[i].exception_name;
|
|
}
|
|
}
|
|
return "Unknown exception code";
|
|
}
|
|
|
|
/*
|
|
** Was a page accessed read/write? The MSDN documentation doens't define
|
|
** symbolic constants for these alternatives.
|
|
*/
|
|
#define READ 0
|
|
#define WRITE 1
|
|
|
|
/*
|
|
** Explain an EXCEPTION_RECORD content into stderr.
|
|
*/
|
|
static void
|
|
MR_explain_exception_record(EXCEPTION_RECORD *rec)
|
|
{
|
|
fprintf(stderr, "\n");
|
|
fprintf(stderr, "\n*** Explanation of the exception record");
|
|
if (rec == NULL) {
|
|
fprintf(stderr, "\n*** Cannot explain because it is NULL");
|
|
return;
|
|
} else {
|
|
void *address;
|
|
int access_mode;
|
|
|
|
/* If the exception is an access violation */
|
|
if (MR_exception_record_is_access_violation(rec,
|
|
&address, &access_mode))
|
|
{
|
|
MR_MemoryZone *zone;
|
|
|
|
/* Display AV address and access mode */
|
|
fprintf(stderr, "\n*** An access violation occured"
|
|
" at address 0x%08lx, while attempting"
|
|
" to ", (unsigned long) address);
|
|
|
|
if (access_mode == READ) {
|
|
fprintf(stderr, "\n*** read "
|
|
"inaccessible data");
|
|
} else if (access_mode == WRITE) {
|
|
fprintf(stderr, "\n*** write to an "
|
|
"inaccessible (or protected)"
|
|
" address");
|
|
} else {
|
|
fprintf(stderr, "\n*** ? [unknown access "
|
|
"mode %d (strange...)]",
|
|
access_mode);
|
|
}
|
|
|
|
#if defined(MR_CHECK_OVERFLOW_VIA_MPROTECT)
|
|
fprintf(stderr, "\n*** Trying to see if this "
|
|
"stands within a mercury zone...");
|
|
/*
|
|
** Browse the mercury memory zones to see if the
|
|
** AV address references one of them.
|
|
*/
|
|
zone = MR_get_used_memory_zones_readonly();
|
|
while(zone != NULL) {
|
|
fprintf(stderr,
|
|
"\n*** Checking zone %s#%"
|
|
MR_INTEGER_LENGTH_MODIFIER "d: "
|
|
"0x%08lx - 0x%08lx - 0x%08lx",
|
|
zone->MR_zone_name, zone->MR_zone_id,
|
|
(unsigned long) zone->MR_zone_bottom,
|
|
(unsigned long) zone->MR_zone_redzone,
|
|
(unsigned long) zone->MR_zone_top);
|
|
|
|
if ((zone->MR_zone_redzone <= address) &&
|
|
(address <= zone->MR_zone_top))
|
|
{
|
|
fprintf(stderr,
|
|
"\n*** Address is within"
|
|
" redzone of "
|
|
"%s#%" MR_INTEGER_LENGTH_MODIFIER
|
|
"d (!!zone overflowed!!)\n",
|
|
zone->MR_zone_name, zone->MR_zone_id);
|
|
} else if ((zone->MR_zone_bottom <= address) &&
|
|
(address <= zone->MR_zone_top))
|
|
{
|
|
fprintf(stderr, "\n*** Address is"
|
|
" within zone %s#%" MR_INTEGER_LENGTH_MODIFIER
|
|
"d\n",
|
|
zone->MR_zone_name, zone->MR_zone_id);
|
|
}
|
|
/*
|
|
** Don't need to call handler, because it
|
|
** has much less information than we do.
|
|
*/
|
|
/* return zone->MR_zone_handler(fault_addr,
|
|
zone, rec); */
|
|
zone = zone->MR_zone_next;
|
|
}
|
|
#endif /* MR_CHECK_OVERFLOW_VIA_MPROTECT */
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Dump an EXCEPTION_RECORD content into stderr.
|
|
*/
|
|
static void
|
|
MR_dump_exception_record(EXCEPTION_RECORD *rec)
|
|
{
|
|
int i;
|
|
|
|
if (rec == NULL) {
|
|
return;
|
|
}
|
|
|
|
fprintf(stderr, "\n*** Exception record at 0x%08lx:",
|
|
(unsigned long) rec);
|
|
fprintf(stderr, "\n*** MR_Code : 0x%08lx (%s)",
|
|
(unsigned long) rec->ExceptionCode,
|
|
MR_find_exception_name(rec->ExceptionCode));
|
|
fprintf(stderr, "\n*** Flags : 0x%08lx",
|
|
(unsigned long) rec->ExceptionFlags);
|
|
fprintf(stderr, "\n*** Address : 0x%08lx",
|
|
(unsigned long) rec->ExceptionAddress);
|
|
|
|
for (i = 0; i < rec->NumberParameters; i++) {
|
|
fprintf(stderr, "\n*** Parameter %d : 0x%08lx", i,
|
|
(unsigned long) rec->ExceptionInformation[i]);
|
|
}
|
|
fprintf(stderr, "\n*** Next record : 0x%08lx",
|
|
(unsigned long) rec->ExceptionRecord);
|
|
|
|
/* Try to explain the exception more "gracefully" */
|
|
MR_explain_exception_record(rec);
|
|
MR_dump_exception_record(rec->ExceptionRecord);
|
|
}
|
|
|
|
/*
|
|
** Return MR_TRUE iff exception_ptrs indicates an access violation.
|
|
** If MR_TRUE, the dereferenced address_ptr is set to the accessed address and
|
|
** the dereferenced access_mode_ptr is set to the desired access
|
|
** (0 = read, 1 = write)
|
|
*/
|
|
static MR_bool
|
|
MR_exception_record_is_access_violation(EXCEPTION_RECORD *rec,
|
|
void **address_ptr, int *access_mode_ptr)
|
|
{
|
|
if (rec->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
|
|
if (rec->NumberParameters >= 2) {
|
|
(*access_mode_ptr) = (int) rec->ExceptionInformation[0];
|
|
(*address_ptr) = (void *) rec->ExceptionInformation[1];
|
|
return MR_TRUE;
|
|
}
|
|
}
|
|
return MR_FALSE;
|
|
}
|
|
|
|
/*
|
|
** Filter a Win32 exception (to be called in the __except filter part).
|
|
** Possible return values are:
|
|
**
|
|
** EXCEPTION_CONTINUE_EXECUTION (-1)
|
|
** Exception is dismissed. Continue execution at the point where
|
|
** the exception occurred.
|
|
**
|
|
** EXCEPTION_CONTINUE_SEARCH (0)
|
|
** Exception is not recognized. Continue to search up the stack for
|
|
** a handler, first for containing try-except statements, then for
|
|
** handlers with the next highest precedence.
|
|
**
|
|
** EXCEPTION_EXECUTE_HANDLER (1)
|
|
** Exception is recognized. Transfer control to the exception handler
|
|
** by executing the __except compound statement, then continue
|
|
** execution at the assembly instruction that was executing
|
|
** when the exception was raised.
|
|
*/
|
|
int
|
|
MR_filter_win32_exception(LPEXCEPTION_POINTERS exception_ptrs)
|
|
{
|
|
void *address;
|
|
int access_mode;
|
|
|
|
/* If the exception is an access violation */
|
|
if (MR_exception_record_is_access_violation(
|
|
exception_ptrs->ExceptionRecord,
|
|
&address, &access_mode))
|
|
{
|
|
|
|
/* If we can unprotect the memory zone */
|
|
if (MR_try_munprotect(address, exception_ptrs)) {
|
|
if (MR_memdebug) {
|
|
fprintf(stderr, "returning from "
|
|
"signal handler\n\n");
|
|
}
|
|
/* Continue execution where it stopped */
|
|
return EXCEPTION_CONTINUE_EXECUTION;
|
|
}
|
|
}
|
|
|
|
/*
|
|
** We can't handle the exception. Just dump all the information we got
|
|
*/
|
|
fflush(stdout);
|
|
fprintf(stderr, "\n*** Mercury runtime: Unhandled exception ");
|
|
MR_dump_exception_record(exception_ptrs->ExceptionRecord);
|
|
|
|
printf("\n");
|
|
MR_print_dump_stack();
|
|
MR_dump_prev_locations();
|
|
|
|
fprintf(stderr, "\n\n*** Now passing exception to default handler\n\n");
|
|
fflush(stderr);
|
|
|
|
/*
|
|
** Pass exception back to upper handler. In most cases, this
|
|
** means activating UnhandledExceptionFilter, which will display
|
|
** a dialog box asking to user ro activate the Debugger or simply
|
|
** to kill the application
|
|
*/
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
#endif /* MR_MSVC_STRUCTURED_EXCEPTIONS */
|
|
|
|
/*
|
|
** get_pc_from_context:
|
|
** Given the signal context, return the program counter at the time
|
|
** of the signal, if available. If it is unavailable, return NULL.
|
|
*/
|
|
static MR_Code *
|
|
get_pc_from_context(void *the_context)
|
|
{
|
|
MR_Code *pc_at_signal = NULL;
|
|
#if defined(MR_HAVE_SIGCONTEXT_STRUCT)
|
|
|
|
#ifdef MR_PC_ACCESS
|
|
struct sigcontext_struct *context = the_context;
|
|
|
|
pc_at_signal = (MR_Code *) context->MR_PC_ACCESS;
|
|
#else
|
|
pc_at_signal = (MR_Code *) NULL;
|
|
#endif
|
|
|
|
#elif defined(MR_HAVE_SIGINFO_T)
|
|
|
|
#ifdef MR_PC_ACCESS
|
|
|
|
ucontext_t *context = the_context;
|
|
|
|
#ifdef MR_PC_ACCESS_GREG
|
|
pc_at_signal = (MR_Code *) context->uc_mcontext.gregs[MR_PC_ACCESS];
|
|
#else
|
|
pc_at_signal = (MR_Code *) context->uc_mcontext.MR_PC_ACCESS;
|
|
#endif
|
|
|
|
#else /* not MR_PC_ACCESS */
|
|
|
|
/* if MR_PC_ACCESS is not set, we don't know the context */
|
|
pc_at_signal = (MR_Code *) NULL;
|
|
|
|
#endif /* not MR_PC_ACCESS */
|
|
|
|
#else /* not MR_HAVE_SIGINFO_T && not MR_HAVE_SIGCONTEXT_STRUCT */
|
|
|
|
pc_at_signal = (MR_Code *) NULL;
|
|
|
|
#endif
|
|
|
|
return pc_at_signal;
|
|
}
|
|
|
|
/*
|
|
** get_sp_from_context:
|
|
** Given the signal context, return the Mercury register "MR_sp" at
|
|
** the time of the signal, if available. If it is unavailable,
|
|
** return NULL.
|
|
**
|
|
** XXX We only define this function in LLDS accurate gc grades for the moment,
|
|
** because it's unlikely to compile everywhere. It relies on
|
|
** MR_real_reg_number_sp being defined, which is the name/number of the
|
|
** machine register that is used for MR_sp.
|
|
** Need to fix this so it works when the register is in a fake reg too.
|
|
*/
|
|
static MR_Word *
|
|
get_sp_from_context(void *the_context)
|
|
{
|
|
MR_Word *sp_at_signal = NULL;
|
|
#if defined(MR_NATIVE_GC) && !defined(MR_HIGHLEVEL_CODE)
|
|
#if defined(MR_HAVE_SIGCONTEXT_STRUCT)
|
|
|
|
#ifdef MR_PC_ACCESS
|
|
struct sigcontext_struct *context = the_context;
|
|
|
|
sp_at_signal = (MR_Word *) context->MR_real_reg_number_sp;
|
|
#else
|
|
sp_at_signal = (MR_Word *) NULL;
|
|
#endif
|
|
|
|
#elif defined(MR_HAVE_SIGINFO_T)
|
|
|
|
#ifdef MR_PC_ACCESS
|
|
|
|
struct sigcontext *context = the_context;
|
|
|
|
#ifdef MR_PC_ACCESS_GREG
|
|
sp_at_signal = (MR_Word *) context->gregs[MR_real_reg_number_sp];
|
|
#else
|
|
sp_at_signal = (MR_Word *) context->sc_regs[MR_real_reg_number_sp];
|
|
#endif
|
|
|
|
#else /* not MR_PC_ACCESS */
|
|
|
|
/*
|
|
** if MR_PC_ACCESS is not set, we don't know how to get at the
|
|
** registers
|
|
*/
|
|
sp_at_signal = (MR_Word *) NULL;
|
|
|
|
#endif /* not MR_PC_ACCESS */
|
|
|
|
#else /* not MR_HAVE_SIGINFO_T && not MR_HAVE_SIGCONTEXT_STRUCT */
|
|
|
|
sp_at_signal = (MR_Word *) NULL;
|
|
|
|
#endif
|
|
#else /* !MR_NATIVE_GC */
|
|
sp_at_signal = (MR_Word *) NULL;
|
|
#endif /* !MR_NATIVE_GC */
|
|
|
|
return sp_at_signal;
|
|
}
|
|
|
|
/*
|
|
** get_sp_from_context:
|
|
** Given the signal context, return the Mercury register "MR_sp" at
|
|
** the time of the signal, if available. If it is unavailable,
|
|
** return NULL.
|
|
**
|
|
** XXX We only define this function in accurate gc grades for the moment,
|
|
** because it's unlikely to compile everywhere. It relies on
|
|
** MR_real_reg_number_sp being defined, which is the name/number of the
|
|
** machine register that is used for MR_sp.
|
|
** Need to fix this so it works when the register is in a fake reg too.
|
|
*/
|
|
static MR_Word *
|
|
get_curfr_from_context(void *the_context)
|
|
{
|
|
MR_Word *curfr_at_signal;
|
|
|
|
/*
|
|
** XXX this is implementation dependent, need a better way
|
|
** to do register accesses at signals.
|
|
**
|
|
** It's in mr8 or mr9 which is in the fake regs on some architectures,
|
|
** and is a machine register on others.
|
|
** So don't run the garbage collector on those archs.
|
|
*/
|
|
|
|
curfr_at_signal = MR_curfr;
|
|
|
|
return curfr_at_signal;
|
|
}
|
|
|
|
static void
|
|
MR_print_dump_stack(void)
|
|
{
|
|
const char *msg =
|
|
"This may have been caused by a stack overflow, due to unbounded recursion.\n";
|
|
int ret;
|
|
|
|
do {
|
|
ret = write(STDERR, msg, strlen(msg));
|
|
} while (ret == -1 && MR_is_eintr(errno));
|
|
}
|
|
|
|
static void
|
|
leave_signal_handler(int sig)
|
|
{
|
|
fprintf(stderr, "exiting from signal handler\n");
|
|
#if defined(MR_THREAD_SAFE) && defined(MR_THREADSCOPE)
|
|
if (MR_all_engine_bases) {
|
|
int i;
|
|
for (i = 0; i < MR_num_threads; i++) {
|
|
if (MR_all_engine_bases[i] &&
|
|
MR_all_engine_bases[i]->MR_eng_ts_buffer)
|
|
{
|
|
MR_threadscope_finalize_engine(MR_all_engine_bases[i]);
|
|
}
|
|
}
|
|
}
|
|
MR_finalize_threadscope();
|
|
#endif
|
|
MR_reset_signal(sig);
|
|
raise(sig);
|
|
}
|