From 5b7fd20fea6d4329bb3ab45f63df41f46f242688 Mon Sep 17 00:00:00 2001 From: Dave Hylands Date: Tue, 1 Jul 2014 23:46:53 -0700 Subject: [PATCH] Add support for storing args during an exception raised by an irq. The user code should call micropython.alloc_emergency_exception_buf(size) where size is the size of the buffer used to print the argument passed to the exception. With the test code from #732, and a call to micropython.alloc_emergenncy_exception_buf(100) the following error is now printed: ```python >>> import heartbeat_irq Uncaught exception in Timer(4) interrupt handler Traceback (most recent call last): File "0://heartbeat_irq.py", line 14, in heartbeat_cb NameError: name 'led' is not defined ``` --- py/modmicropython.c | 7 +++ py/mpconfig.h | 18 +++++++ py/obj.h | 2 + py/objexcept.c | 111 ++++++++++++++++++++++++++++++++++++++++-- py/qstrdefs.h | 4 ++ stmhal/main.c | 11 +++++ stmhal/mpconfigport.h | 9 ++++ unix/main.c | 3 ++ unix/mpconfigport.h | 3 ++ 9 files changed, 164 insertions(+), 4 deletions(-) diff --git a/py/modmicropython.c b/py/modmicropython.c index 0d559c42a2..78beb88afa 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 99d697f9ad..911125c8a4 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 d0284e6d21..5f347a2674 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 68992bdaed..cddc77bfeb 100644 --- a/py/objexcept.c +++ b/py/objexcept.c @@ -27,12 +27,15 @@ #include #include #include +#include #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 0deb646c7b..25311479c3 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() Q() Q() diff --git a/stmhal/main.c b/stmhal/main.c index 9e1798b3b9..2cc463462d 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 95f142ca48..cbdfcf4d07 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 d0222de0d1..45ccc23c20 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 ce4365d365..ff30eec340 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;