From b8532e5e80e0fde42f87cbca4f0fe1fa6434fa0a Mon Sep 17 00:00:00 2001 From: Chris Hanson Date: Wed, 8 Mar 1995 21:38:49 +0000 Subject: [PATCH] Implement OS/2 stack-overflow detection mechanism. This is a pile of hair involving some tricky memory management and a little cooperation from the OS/2 exception handler. See the code for details. --- v7/src/microcode/os2top.c | 150 ++++++++++++++++++++++++++++++++++++- v7/src/microcode/os2xcpt.c | 35 +++++++-- 2 files changed, 176 insertions(+), 9 deletions(-) diff --git a/v7/src/microcode/os2top.c b/v7/src/microcode/os2top.c index 37bf4cd4f..21136f560 100644 --- a/v7/src/microcode/os2top.c +++ b/v7/src/microcode/os2top.c @@ -1,6 +1,6 @@ /* -*-C-*- -$Id: os2top.c,v 1.11 1995/02/07 23:54:55 cph Exp $ +$Id: os2top.c,v 1.12 1995/03/08 21:37:54 cph Exp $ Copyright (c) 1994-95 Massachusetts Institute of Technology @@ -33,6 +33,7 @@ promotional, or sales literature without prior written consent from MIT in each case. */ #define SCM_OS2TOP_C +#include "scheme.h" #include "os2.h" #include "ostop.h" #include "option.h" @@ -137,8 +138,144 @@ OS2_version_string (void) return (result); } +#define PAGESIZE 4096 +#define PAGE_PERMS (PAG_READ | PAG_WRITE | PAG_EXECUTE) +#define N_STACK_GUARD_PAGES 2 + +#define ROUND_UP_TO_PAGE(size) \ + (((((size) + (PAGESIZE - 1)) / PAGESIZE) + N_STACK_GUARD_PAGES + 1) \ + * PAGESIZE) + +typedef struct +{ + char * low; + unsigned int enabled_p : 1; +} guard_page_state_t; + +static guard_page_state_t guard_page_states [N_STACK_GUARD_PAGES]; + +static void * +commit_heap_helper (void * base, unsigned long size) +{ + /* Complicated arrangement to detect stack overflow with reasonable + reliability. We allocate three extra pages past the end of the + stack; the first two (adjacent to the stack) are committed as + guard pages so that OS/2 will deliver an exception when we access + them. If we overrun the first guard page, the trap handler + should recognize this and terminate Scheme gracefully, using the + second guard page as its stack. The third page, on the other + side of the guard pages, is uncommitted -- if for some reason we + overrun the second guard page, this uncommitted page will cause a + hard fault that will kill Scheme right away. + + This is slightly kludgey, because we take advantage of the fact + that the Scheme stack occupies the low-order addresses in the + allocated block, and particularly that the stack grows towards + lower addresses. Thus we can put the guard pages just below the + allocated block. If the memory layout is altered, this will have + to change. The reason for this fragile implementation is that it + requires the least change to the existing memory allocation + mechanism. */ + char * p = base; + /* Skip uncommitted page, then commit rest of memory block. */ + p += PAGESIZE; + if ((dos_set_mem (p, (size - PAGESIZE), (PAG_COMMIT | PAG_DEFAULT))) + != NO_ERROR) + return (0); + /* Initialize the stack guard pages and get pointer to first page + past the guard pages. */ + { + guard_page_state_t * scan = guard_page_states; + guard_page_state_t * end = (scan + N_STACK_GUARD_PAGES); + while (scan < end) + { + (scan -> low) = p; + (scan -> enabled_p) = 0; + scan += 1; + p += PAGESIZE; + } + OS2_stack_reset (); + } + return (p); +} + +static void +enable_stack_guard (guard_page_state_t * page, int enable_p) +{ + (void) dos_set_mem ((page -> low), + PAGESIZE, + (enable_p ? (PAGE_PERMS | PAG_GUARD) : PAGE_PERMS)); + (page -> enabled_p) = enable_p; +} + +int +OS2_disable_stack_guard (void * p) +{ + char * cp = p; + guard_page_state_t * scan = guard_page_states; + guard_page_state_t * end = (scan + N_STACK_GUARD_PAGES); + while (1) + { + if (scan == end) + return (0); + if (((scan -> low) <= cp) && (cp < ((scan -> low) + PAGESIZE))) + { + enable_stack_guard (scan, 0); + return (1); + } + scan += 1; + } +} + +void +OS2_stack_reset (void) +{ + { + guard_page_state_t * scan = guard_page_states; + guard_page_state_t * end = (scan + N_STACK_GUARD_PAGES); + while (1) + { + if (scan == end) + return; + if (! (scan -> enabled_p)) + break; + scan += 1; + } + } + enable_stack_guard ((&guard_page_states[1]), 0); + { + SCHEME_OBJECT * p = ((SCHEME_OBJECT *) ((guard_page_states[1]) . low)); + (*p) = (MAKE_POINTER_OBJECT (TC_BROKEN_HEART, p)); + } + { + guard_page_state_t * scan = guard_page_states; + guard_page_state_t * end = (scan + N_STACK_GUARD_PAGES); + while (scan < end) + enable_stack_guard ((scan++), 1); + } +} + +int +OS2_stack_overflowed_p (void) +{ + SCHEME_OBJECT * p = ((SCHEME_OBJECT *) ((guard_page_states[1]) . low)); + return + ((! ((guard_page_states[1]) . enabled_p)) + && ((*p) != (MAKE_POINTER_OBJECT (TC_BROKEN_HEART, p)))); +} + #if 0 +/* This is an attempt to allocate Scheme's memory as early as + possible, in order to obtain the lowest possible addresses before + `malloc' grabs them for some uninteresting purpose. However, there + are two reasons not to do this: first, it doesn't seem to gain any + advantage at present (in OS/2 Warp 3.0 with C Set++/2 2.1), because + the returned addresses are about the same in both cases. Second, + this sometimes causes a fatal error in the debugger, apparently + because it cares about how much memory the debugged process has + allocated, even if it's not committed. */ + static void * OS2_heap_base; void @@ -159,8 +296,7 @@ OS2_alloc_heap (void) void * OS2_commit_heap (unsigned long size) { - APIRET rc = (dos_set_mem (OS2_heap_base, size, (PAG_COMMIT | PAG_DEFAULT))); - return ((rc == NO_ERROR) ? OS2_heap_base : 0); + return (commit_heap_helper (OS2_heap_base, (ROUND_UP_TO_PAGE (size)))); } #else @@ -173,7 +309,13 @@ OS2_alloc_heap (void) void * OS2_commit_heap (unsigned long size) { - return (malloc (size)); + unsigned long actual = (ROUND_UP_TO_PAGE (size)); + void * heap_base; + APIRET rc + = (dos_alloc_mem ((& heap_base), + actual, + (PAG_EXECUTE | PAG_READ | PAG_WRITE))); + return ((rc == NO_ERROR) ? (commit_heap_helper (heap_base, actual)) : 0); } #endif diff --git a/v7/src/microcode/os2xcpt.c b/v7/src/microcode/os2xcpt.c index 050a477f5..21c2db93b 100644 --- a/v7/src/microcode/os2xcpt.c +++ b/v7/src/microcode/os2xcpt.c @@ -1,8 +1,8 @@ /* -*-C-*- -$Id: os2xcpt.c,v 1.1 1994/12/19 22:23:24 cph Exp $ +$Id: os2xcpt.c,v 1.2 1995/03/08 21:38:49 cph Exp $ -Copyright (c) 1994 Massachusetts Institute of Technology +Copyright (c) 1994-95 Massachusetts Institute of Technology This material was developed by the Scheme project at the Massachusetts Institute of Technology, Department of Electrical Engineering and @@ -40,6 +40,7 @@ MIT in each case. */ extern int pc_to_utility_index (unsigned long); extern int pc_to_builtin_index (unsigned long); extern SCHEME_OBJECT * find_constant_space_block (SCHEME_OBJECT *); +extern int OS2_disable_stack_guard (void *); extern ULONG C_Stack_Pointer; extern ULONG C_Frame_Pointer; @@ -212,22 +213,46 @@ OS2_exception_handler (PEXCEPTIONREPORTRECORD report, || ((report -> ExceptionNum) == XCPT_FLOAT_OVERFLOW) || ((report -> ExceptionNum) == XCPT_FLOAT_STACK_CHECK) || ((report -> ExceptionNum) == XCPT_FLOAT_UNDERFLOW) + || ((report -> ExceptionNum) == XCPT_GUARD_PAGE_VIOLATION) || ((report -> ExceptionNum) == XCPT_ILLEGAL_INSTRUCTION) || ((report -> ExceptionNum) == XCPT_INTEGER_DIVIDE_BY_ZERO) || ((report -> ExceptionNum) == XCPT_INTEGER_OVERFLOW) || ((report -> ExceptionNum) == XCPT_PRIVILEGED_INSTRUCTION)))) return (XCPT_CONTINUE_SEARCH); - - old_trap_state = trap_state; + exception_number = (report -> ExceptionNum); stack_overflowed_p = (STACK_OVERFLOWED_P ()); + /* If this is a guard page violation, we're only interested if it + occurred in one of the Scheme stack guard pages. Test this by + examining the second parameter, which is the address of the + access within the guard page. `OS2_disable_stack_guard' will + perform this test, additionally disabling the guard page if it is + one of ours. */ + if (exception_number == XCPT_GUARD_PAGE_VIOLATION) + { + if (!OS2_disable_stack_guard ((void *) ((report -> ExceptionInfo) [1]))) + return (XCPT_CONTINUE_SEARCH); + /* OK, we've determined that this is one of our guard pages, and + it has been disabled. If `stack_overflowed_p' is true, we + can't recover cleanly and must terminate Scheme. Otherwise, + we still have some maneuvering room -- so signal a Scheme + stack-overflow interrupt and continue. When Scheme takes the + interrupt, it will do a throw, and the throw will re-enable + the stack guard. */ + if (!stack_overflowed_p) + { + REQUEST_INTERRUPT (INT_Stack_Overflow); + return (XCPT_CONTINUE_EXECUTION); + } + } + + old_trap_state = trap_state; if (old_trap_state == trap_state_exitting_hard) _exit (1); if (old_trap_state == trap_state_exitting_soft) trap_immediate_termination (); trap_state = trap_state_trapped; - exception_number = (report -> ExceptionNum); noise_start (); if (WITHIN_CRITICAL_SECTION_P ()) { -- 2.25.1