diff --git a/py/modmicropython.c b/py/modmicropython.c
index 0d559c42a292dc8d36010d1d469168006bb86dff..78beb88afa3a7b3bbade2de16e41a328d3885c47 100644
--- a/py/modmicropython.c
+++ b/py/modmicropython.c
@@ -51,6 +51,10 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_micropython_mem_current_obj, mp_micropython_
 STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_micropython_mem_peak_obj, mp_micropython_mem_peak);
 #endif
 
+#if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF && (MICROPY_EMERGENCY_EXCEPTION_BUF_SIZE == 0)
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_alloc_emergency_exception_buf_obj, mp_alloc_emergency_exception_buf);
+#endif
+
 STATIC const mp_map_elem_t mp_module_micropython_globals_table[] = {
     { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_micropython) },
 #if MICROPY_MEM_STATS
@@ -58,6 +62,9 @@ STATIC const mp_map_elem_t mp_module_micropython_globals_table[] = {
     { MP_OBJ_NEW_QSTR(MP_QSTR_mem_current), (mp_obj_t)&mp_micropython_mem_current_obj },
     { MP_OBJ_NEW_QSTR(MP_QSTR_mem_peak), (mp_obj_t)&mp_micropython_mem_peak_obj },
 #endif
+#if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF && (MICROPY_EMERGENCY_EXCEPTION_BUF_SIZE == 0)
+    { MP_OBJ_NEW_QSTR(MP_QSTR_alloc_emergency_exception_buf), (mp_obj_t)&mp_alloc_emergency_exception_buf_obj },
+#endif
 };
 
 STATIC const mp_obj_dict_t mp_module_micropython_globals = {
diff --git a/py/mpconfig.h b/py/mpconfig.h
index 99d697f9adc39ae98bb7a88b59de2ae8fd398ec7..911125c8a4430af61d0c143f3f89e1ebc686a26b 100644
--- a/py/mpconfig.h
+++ b/py/mpconfig.h
@@ -163,6 +163,16 @@
 #define MICROPY_STACK_CHECK (1)
 #endif
 
+// Whether to have an emergency exception buffer
+#ifndef MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF
+#define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (0)
+#endif
+#if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF
+#   ifndef MICROPY_EMERGENCY_EXCEPTION_BUF_SIZE
+#   define MICROPY_EMERGENCY_EXCEPTION_BUF_SIZE (0)   // 0 - implies dynamic allocation
+#   endif
+#endif
+
 // Whether to include REPL helper function
 #ifndef MICROPY_HELPER_REPL
 #define MICROPY_HELPER_REPL (0)
@@ -378,6 +388,14 @@ typedef double mp_float_t;
 /*****************************************************************************/
 /* Miscellaneous settings                                                    */
 
+// On embedded platforms, these will typically enable/disable irqs.
+#ifndef MICROPY_BEGIN_ATOMIC_SECTION
+#define MICROPY_BEGIN_ATOMIC_SECTION()
+#endif
+#ifndef MICROPY_END_ATOMIC_SECTION
+#define MICROPY_END_ATOMIC_SECTION()
+#endif
+
 // Allow to override static modifier for global objects, e.g. to use with
 // object code analysis tools which don't support static symbols.
 #ifndef STATIC
diff --git a/py/obj.h b/py/obj.h
index d0284e6d21826bc4486a30149adb61cf39f01cc1..5f347a267435cea14f8c487a8c3a0b0a4b5dbdd2 100644
--- a/py/obj.h
+++ b/py/obj.h
@@ -453,6 +453,8 @@ void mp_obj_exception_add_traceback(mp_obj_t self_in, qstr file, mp_uint_t line,
 void mp_obj_exception_get_traceback(mp_obj_t self_in, mp_uint_t *n, mp_uint_t **values);
 mp_obj_t mp_obj_exception_get_value(mp_obj_t self_in);
 mp_obj_t mp_obj_exception_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp_obj_t *args);
+mp_obj_t mp_alloc_emergency_exception_buf(mp_obj_t size_in);
+void mp_init_emergency_exception_buf(void);
 
 // str
 mp_obj_t mp_obj_str_builder_start(const mp_obj_type_t *type, uint len, byte **data);
diff --git a/py/objexcept.c b/py/objexcept.c
index 68992bdaed125f66fe9d391c75815698d30cffeb..cddc77bfeb5ac33b0c5e8aee6325a14b9395320d 100644
--- a/py/objexcept.c
+++ b/py/objexcept.c
@@ -27,12 +27,15 @@
 #include <string.h>
 #include <stdarg.h>
 #include <assert.h>
+#include <stdio.h>
 
 #include "mpconfig.h"
 #include "nlr.h"
 #include "misc.h"
 #include "qstr.h"
 #include "obj.h"
+#include "objlist.h"
+#include "objstr.h"
 #include "objtuple.h"
 #include "objtype.h"
 #include "runtime.h"
@@ -51,6 +54,53 @@ const mp_obj_exception_t mp_const_MemoryError_obj = {{&mp_type_MemoryError}, MP_
 // Local non-heap memory for allocating an exception when we run out of RAM
 STATIC mp_obj_exception_t mp_emergency_exception_obj;
 
+// Optionally allocated buffer for storing the first argument of an exception
+// allocated when the heap is locked.
+#if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF
+#   if MICROPY_EMERGENCY_EXCEPTION_BUF_SIZE > 0
+STATIC byte  mp_emergency_exception_buf[MICROPY_EMERGENCY_EXCEPTION_BUF_SIZE];
+#define mp_emergency_exception_buf_size MICROPY_EMERGENCY_EXCEPTION_BUF_SIZE
+
+void mp_init_emergency_exception_buf(void) {
+    // Nothing to do since the buffer was declared statically. We put this
+    // definition here so that the calling code can call this function
+    // regardless of how its configured (makes the calling code a bit cleaner).
+}
+
+#else
+STATIC mp_int_t mp_emergency_exception_buf_size = 0;
+STATIC byte *mp_emergency_exception_buf = NULL;
+
+void mp_init_emergency_exception_buf(void) {
+    mp_emergency_exception_buf_size = 0;
+    mp_emergency_exception_buf = NULL;
+}
+
+mp_obj_t mp_alloc_emergency_exception_buf(mp_obj_t size_in) {
+    mp_int_t size = mp_obj_get_int(size_in);
+    void *buf = NULL;
+    if (size > 0) {
+        buf = m_malloc(size);
+    }
+
+    int old_size = mp_emergency_exception_buf_size;
+    void *old_buf = mp_emergency_exception_buf;
+
+    // Update the 2 variables atomically so that an interrupt can't occur
+    // between the assignments.
+    MICROPY_BEGIN_ATOMIC_SECTION();
+    mp_emergency_exception_buf_size = size;
+    mp_emergency_exception_buf = buf;
+    MICROPY_END_ATOMIC_SECTION();
+
+    if (old_buf != NULL) {
+        m_free(old_buf, old_size);
+    }
+    return mp_const_none;
+}
+#endif
+#endif  // MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF
+
 // Instance of GeneratorExit exception - needed by generator.close()
 // This would belong to objgenerator.c, but to keep mp_obj_exception_t
 // definition module-private so far, have it here.
@@ -268,6 +318,50 @@ mp_obj_t mp_obj_new_exception_msg_varg(const mp_obj_type_t *exc_type, const char
         o->base.type = exc_type;
         o->traceback = MP_OBJ_NULL;
         o->args = mp_const_empty_tuple;
+
+#if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF
+        // If the user has provided a buffer, then we try to create a tuple
+        // of length 1, which has a string object and the string data.
+
+        if (mp_emergency_exception_buf_size > (sizeof(mp_obj_tuple_t) + sizeof(mp_obj_str_t) + sizeof(mp_obj_t))) {
+            mp_obj_tuple_t *tuple = (mp_obj_tuple_t *)mp_emergency_exception_buf;
+            mp_obj_str_t *str = (mp_obj_str_t *)&tuple->items[1];
+
+            tuple->base.type = &mp_type_tuple;
+            tuple->len = 1;
+            tuple->items[0] = str;
+
+            byte *str_data = (byte *)&str[1];
+            uint max_len = mp_emergency_exception_buf + mp_emergency_exception_buf_size
+                         - str_data;
+
+            va_list ap;
+            va_start(ap, fmt);
+            str->len = vsnprintf((char *)str_data, max_len, fmt, ap);
+            va_end(ap);
+
+            str->base.type = &mp_type_str;
+            str->hash = qstr_compute_hash(str_data, str->len);
+            str->data = str_data;
+
+            o->args = tuple;
+
+            uint offset = &str_data[str->len] - mp_emergency_exception_buf;
+            offset += sizeof(void *) - 1;
+            offset &= ~(sizeof(void *) - 1);
+
+            if ((mp_emergency_exception_buf_size - offset) > (sizeof(mp_obj_list_t) + sizeof(mp_obj_t) * 3)) {
+                // We have room to store some traceback.
+                mp_obj_list_t *list = (mp_obj_list_t *)((byte *)mp_emergency_exception_buf + offset);
+                list->base.type = &mp_type_list;
+                list->items = (mp_obj_t)&list[1];
+                list->alloc = (mp_emergency_exception_buf + mp_emergency_exception_buf_size - (byte *)list->items) / sizeof(list->items[0]);
+                list->len = 0;
+
+                o->traceback = list;
+            }
+        }
+#endif // MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF
     } else {
         o->base.type = exc_type;
         o->traceback = MP_OBJ_NULL;
@@ -336,15 +430,24 @@ void mp_obj_exception_clear_traceback(mp_obj_t self_in) {
 }
 
 void mp_obj_exception_add_traceback(mp_obj_t self_in, qstr file, mp_uint_t line, qstr block) {
+    GET_NATIVE_EXCEPTION(self, self_in);
+
     #if MICROPY_ENABLE_GC
     if (gc_is_locked()) {
-        // We can't allocate memory, so don't bother to try
-        return;
+        if (self->traceback == MP_OBJ_NULL) {
+            // We can't allocate any memory, and no memory has been
+            // pre-allocated, so there is nothing else we can do.
+            return;
+        }
+        mp_obj_list_t *list = self->traceback;
+        if (list->alloc <= (list->len + 3)) {
+            // There is some preallocated memory, but not enough to store an
+            // entire record.
+            return;
+        }
     }
     #endif
 
-    GET_NATIVE_EXCEPTION(self, self_in);
-
     // for traceback, we are just using the list object for convenience, it's not really a list of Python objects
     if (self->traceback == MP_OBJ_NULL) {
         self->traceback = mp_obj_new_list(0, NULL);
diff --git a/py/qstrdefs.h b/py/qstrdefs.h
index 0deb646c7b885455ea82a46aab4d965e01258e1a..25311479c31c9ee4dcc3ce70efe171df8e271a23 100644
--- a/py/qstrdefs.h
+++ b/py/qstrdefs.h
@@ -323,6 +323,10 @@ Q(mem_total)
 Q(mem_current)
 Q(mem_peak)
 
+#if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF && (MICROPY_EMERGENCY_EXCEPTION_BUF_SIZE == 0)
+Q(alloc_emergency_exception_buf)
+#endif
+
 Q(<module>)
 Q(<lambda>)
 Q(<listcomp>)
diff --git a/stmhal/main.c b/stmhal/main.c
index 9e1798b3b9844ee7fe864b9d50bc426434c5b01a..2cc463462de150344adce9184f6d753baecbc5fd 100644
--- a/stmhal/main.c
+++ b/stmhal/main.c
@@ -119,6 +119,14 @@ void MP_WEAK __assert_func(const char *file, int line, const char *func, const c
 }
 #endif
 
+void enable_irq(void) {
+    __enable_irq();
+}
+
+void disable_irq(void) {
+    __disable_irq();
+}
+
 STATIC mp_obj_t pyb_config_source_dir = MP_OBJ_NULL;
 STATIC mp_obj_t pyb_config_main = MP_OBJ_NULL;
 STATIC mp_obj_t pyb_config_usb_mode = MP_OBJ_NULL;
@@ -302,6 +310,9 @@ soft_reset:
 
     // GC init
     gc_init(&_heap_start, &_heap_end);
+#if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF
+    mp_init_emergency_exception_buf();
+#endif
 
     // Change #if 0 to #if 1 if you want REPL on UART_6 (or another uart)
     // as well as on USB VCP
diff --git a/stmhal/mpconfigport.h b/stmhal/mpconfigport.h
index 95f142ca485e9e193600d2411778b6855e15c4e6..cbdfcf4d072ff8a8e52a265db8f1ba2e12759be4 100644
--- a/stmhal/mpconfigport.h
+++ b/stmhal/mpconfigport.h
@@ -52,6 +52,15 @@
 #define MICROPY_PY_IO               (1)
 #define MICROPY_PY_IO_FILEIO        (1)
 
+#define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF   (1)
+#define MICROPY_EMERGENCY_EXCEPTION_BUF_SIZE  (0)
+
+void enable_irq(void);
+void disable_irq(void);
+
+#define MICROPY_BEGIN_ATOMIC_SECTION()  disable_irq()
+#define MICROPY_END_ATOMIC_SECTION()    enable_irq()
+
 // extra built in names to add to the global namespace
 extern const struct _mp_obj_fun_native_t mp_builtin_help_obj;
 extern const struct _mp_obj_fun_native_t mp_builtin_input_obj;
diff --git a/unix/main.c b/unix/main.c
index d0222de0d1c8a4ec34a6ae91f52def1ba2a9c77a..45ccc23c20f2ad108c7c20197780835a4bd56a7a 100644
--- a/unix/main.c
+++ b/unix/main.c
@@ -273,6 +273,9 @@ int main(int argc, char **argv) {
     char *heap = malloc(heap_size);
     gc_init(heap, heap + heap_size);
 #endif
+#if MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF
+    mp_init_emergency_exception_buf();
+#endif
 
     qstr_init();
     mp_init();
diff --git a/unix/mpconfigport.h b/unix/mpconfigport.h
index ce4365d36580116df672685289dd4f15c23e8418..ff30eec34056871439d75c6ec4e9ffc2bad50853 100644
--- a/unix/mpconfigport.h
+++ b/unix/mpconfigport.h
@@ -64,6 +64,9 @@
 #define MICROPY_GCREGS_SETJMP       (0)
 #endif
 
+#define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF   (1)
+#define MICROPY_EMERGENCY_EXCEPTION_BUF_SIZE  (128)
+
 extern const struct _mp_obj_module_t mp_module_os;
 extern const struct _mp_obj_module_t mp_module_time;
 extern const struct _mp_obj_module_t mp_module_socket;