Implement OS/2 stack-overflow detection mechanism. This is a pile of
authorChris Hanson <org/chris-hanson/cph>
Wed, 8 Mar 1995 21:38:49 +0000 (21:38 +0000)
committerChris Hanson <org/chris-hanson/cph>
Wed, 8 Mar 1995 21:38:49 +0000 (21:38 +0000)
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
v7/src/microcode/os2xcpt.c

index 37bf4cd4fbd414c1193bb658b5e34112c4fa3d42..21136f560c6369c57eff9dced558ae2bc2c807bd 100644 (file)
@@ -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);
 }
 \f
+#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);
+}
+\f
+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))));
+}
+\f
 #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
index 050a477f586702bce4debfe89a212affc3f3628c..21c2db93b5c1ccadb2e155680eb8498fd6ed6ce2 100644 (file)
@@ -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 ())
     {