Add supervisor.get_previous_traceback() function.

Useful for #1084.
This commit is contained in:
Christian Walther 2020-09-27 12:58:51 +02:00 committed by Lucian Copeland
parent bba611336c
commit cf97793af8
8 changed files with 140 additions and 12 deletions

View File

@ -167,7 +167,7 @@ STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input
result->return_code = ret; result->return_code = ret;
if (ret != 0) { if (ret != 0) {
mp_obj_t return_value = (mp_obj_t)nlr.ret_val; mp_obj_t return_value = (mp_obj_t)nlr.ret_val;
result->exception_type = mp_obj_get_type(return_value); result->exception = return_value;
result->exception_line = -1; result->exception_line = -1;
if (mp_obj_is_exception_instance(return_value)) { if (mp_obj_is_exception_instance(return_value)) {

View File

@ -35,7 +35,7 @@ typedef enum {
typedef struct { typedef struct {
int return_code; int return_code;
const mp_obj_type_t *exception_type; mp_obj_t exception;
int exception_line; int exception_line;
} pyexec_result_t; } pyexec_result_t;

52
main.c
View File

@ -56,6 +56,7 @@
#include "supervisor/shared/safe_mode.h" #include "supervisor/shared/safe_mode.h"
#include "supervisor/shared/stack.h" #include "supervisor/shared/stack.h"
#include "supervisor/shared/status_leds.h" #include "supervisor/shared/status_leds.h"
#include "supervisor/shared/traceback.h"
#include "supervisor/shared/translate.h" #include "supervisor/shared/translate.h"
#include "supervisor/shared/workflow.h" #include "supervisor/shared/workflow.h"
#include "supervisor/usb.h" #include "supervisor/usb.h"
@ -227,7 +228,41 @@ STATIC bool maybe_run_list(const char * const * filenames, pyexec_result_t* exec
return true; return true;
} }
STATIC void cleanup_after_vm(supervisor_allocation* heap) { STATIC void count_strn(void *data, const char *str, size_t len) {
*(size_t*)data += len;
}
STATIC void cleanup_after_vm(supervisor_allocation* heap, mp_obj_t exception) {
// Get the traceback of any exception from this run off the heap.
// MP_OBJ_SENTINEL means "this run does not contribute to traceback storage, don't touch it"
// MP_OBJ_NULL (=0) means "this run completed successfully, clear any stored traceback"
if (exception != MP_OBJ_SENTINEL) {
free_memory(prev_traceback_allocation);
// ReloadException is exempt from traceback printing in pyexec_file(), so treat it as "no
// traceback" here too.
if (exception && exception != MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_reload_exception))) {
size_t traceback_len = 0;
mp_print_t print_count = {&traceback_len, count_strn};
mp_obj_print_exception(&print_count, exception);
prev_traceback_allocation = allocate_memory(align32_size(traceback_len + 1), false, true);
// Empirically, this never fails in practice - even when the heap is totally filled up
// with single-block-sized objects referenced by a root pointer, exiting the VM frees
// up several hundred bytes, sufficient for the traceback (which tends to be shortened
// because there wasn't memory for the full one). There may be convoluted ways of
// making it fail, but at this point I believe they are not worth spending code on.
if (prev_traceback_allocation != NULL) {
vstr_t vstr;
vstr_init_fixed_buf(&vstr, traceback_len, (char*)prev_traceback_allocation->ptr);
mp_print_t print = {&vstr, (mp_print_strn_t)vstr_add_strn};
mp_obj_print_exception(&print, exception);
((char*)prev_traceback_allocation->ptr)[traceback_len] = '\0';
}
}
else {
prev_traceback_allocation = NULL;
}
}
// Reset port-independent devices, like CIRCUITPY_BLEIO_HCI. // Reset port-independent devices, like CIRCUITPY_BLEIO_HCI.
reset_devices(); reset_devices();
// Turn off the display and flush the filesystem before the heap disappears. // Turn off the display and flush the filesystem before the heap disappears.
@ -287,7 +322,7 @@ STATIC bool run_code_py(safe_mode_t safe_mode) {
pyexec_result_t result; pyexec_result_t result;
result.return_code = 0; result.return_code = 0;
result.exception_type = NULL; result.exception = MP_OBJ_NULL;
result.exception_line = 0; result.exception_line = 0;
bool skip_repl; bool skip_repl;
@ -357,7 +392,7 @@ STATIC bool run_code_py(safe_mode_t safe_mode) {
} }
// Finished executing python code. Cleanup includes a board reset. // Finished executing python code. Cleanup includes a board reset.
cleanup_after_vm(heap); cleanup_after_vm(heap, result.exception);
// If a new next code file was set, that is a reason to keep it (obviously). Stuff this into // If a new next code file was set, that is a reason to keep it (obviously). Stuff this into
// the options because it can be treated like any other reason-for-stickiness bit. The // the options because it can be treated like any other reason-for-stickiness bit. The
@ -674,8 +709,9 @@ STATIC void __attribute__ ((noinline)) run_boot_py(safe_mode_t safe_mode) {
usb_set_defaults(); usb_set_defaults();
#endif #endif
pyexec_result_t result = {0, MP_OBJ_NULL, 0};
if (ok_to_run) { if (ok_to_run) {
bool found_boot = maybe_run_list(boot_py_filenames, NULL); bool found_boot = maybe_run_list(boot_py_filenames, &result);
(void) found_boot; (void) found_boot;
#ifdef CIRCUITPY_BOOT_OUTPUT_FILE #ifdef CIRCUITPY_BOOT_OUTPUT_FILE
@ -687,21 +723,18 @@ STATIC void __attribute__ ((noinline)) run_boot_py(safe_mode_t safe_mode) {
#endif #endif
} }
#if CIRCUITPY_USB #if CIRCUITPY_USB
// Some data needs to be carried over from the USB settings in boot.py // Some data needs to be carried over from the USB settings in boot.py
// to the next VM, while the heap is still available. // to the next VM, while the heap is still available.
// Its size can vary, so save it temporarily on the stack, // Its size can vary, so save it temporarily on the stack,
// and then when the heap goes away, copy it in into a // and then when the heap goes away, copy it in into a
// storage_allocation. // storage_allocation.
size_t size = usb_boot_py_data_size(); size_t size = usb_boot_py_data_size();
uint8_t usb_boot_py_data[size]; uint8_t usb_boot_py_data[size];
usb_get_boot_py_data(usb_boot_py_data, size); usb_get_boot_py_data(usb_boot_py_data, size);
#endif #endif
cleanup_after_vm(heap); cleanup_after_vm(heap, result.exception);
#if CIRCUITPY_USB #if CIRCUITPY_USB
// Now give back the data we saved from the heap going away. // Now give back the data we saved from the heap going away.
@ -736,12 +769,13 @@ STATIC int run_repl(void) {
} else { } else {
exit_code = pyexec_friendly_repl(); exit_code = pyexec_friendly_repl();
} }
cleanup_after_vm(heap); cleanup_after_vm(heap, MP_OBJ_SENTINEL);
#if CIRCUITPY_STATUS_LED #if CIRCUITPY_STATUS_LED
status_led_init(); status_led_init();
new_status_color(BLACK); new_status_color(BLACK);
status_led_deinit(); status_led_deinit();
#endif #endif
autoreload_resume(); autoreload_resume();
return exit_code; return exit_code;
} }

View File

@ -34,6 +34,7 @@
#include "supervisor/shared/autoreload.h" #include "supervisor/shared/autoreload.h"
#include "supervisor/shared/status_leds.h" #include "supervisor/shared/status_leds.h"
#include "supervisor/shared/stack.h" #include "supervisor/shared/stack.h"
#include "supervisor/shared/traceback.h"
#include "supervisor/shared/translate.h" #include "supervisor/shared/translate.h"
#include "supervisor/shared/workflow.h" #include "supervisor/shared/workflow.h"
@ -255,7 +256,33 @@ STATIC mp_obj_t supervisor_ticks_ms(void) {
} }
MP_DEFINE_CONST_FUN_OBJ_0(supervisor_ticks_ms_obj, supervisor_ticks_ms); MP_DEFINE_CONST_FUN_OBJ_0(supervisor_ticks_ms_obj, supervisor_ticks_ms);
//| def get_previous_traceback() -> Optional[str]:
//| """If the last vm run ended with an exception (including the KeyboardInterrupt caused by
//| CTRL-C), returns the traceback as a string.
//| Otherwise, returns ``None``.
//|
//| An exception traceback is only preserved over a soft reload, a hard reset clears it.
//|
//| Only code (main or boot) runs are considered, not REPL runs."""
//| ...
//|
STATIC mp_obj_t supervisor_get_previous_traceback(void) {
//TODO is this a safe and proper way of making a heap-allocated string object that points at long-lived (and likely long and unique) data outside the heap?
if (prev_traceback_allocation) {
size_t len = strlen((const char*)prev_traceback_allocation->ptr);
if (len > 0) {
mp_obj_str_t *o = m_new_obj(mp_obj_str_t);
o->base.type = &mp_type_str;
o->len = len;
//TODO is it a good assumption that callers probably aren't going to compare this string, so skip computing the hash?
o->hash = 0;
o->data = (const byte*)prev_traceback_allocation->ptr;
return MP_OBJ_FROM_PTR(o);
}
}
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_0(supervisor_get_previous_traceback_obj, supervisor_get_previous_traceback);
STATIC const mp_rom_map_elem_t supervisor_module_globals_table[] = { STATIC const mp_rom_map_elem_t supervisor_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_supervisor) }, { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_supervisor) },
@ -268,6 +295,7 @@ STATIC const mp_rom_map_elem_t supervisor_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_set_next_stack_limit), MP_ROM_PTR(&supervisor_set_next_stack_limit_obj) }, { MP_ROM_QSTR(MP_QSTR_set_next_stack_limit), MP_ROM_PTR(&supervisor_set_next_stack_limit_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_next_code_file), MP_ROM_PTR(&supervisor_set_next_code_file_obj) }, { MP_ROM_QSTR(MP_QSTR_set_next_code_file), MP_ROM_PTR(&supervisor_set_next_code_file_obj) },
{ MP_ROM_QSTR(MP_QSTR_ticks_ms), MP_ROM_PTR(&supervisor_ticks_ms_obj) }, { MP_ROM_QSTR(MP_QSTR_ticks_ms), MP_ROM_PTR(&supervisor_ticks_ms_obj) },
{ MP_ROM_QSTR(MP_QSTR_get_previous_traceback), MP_ROM_PTR(&supervisor_get_previous_traceback_obj) },
}; };
STATIC MP_DEFINE_CONST_DICT(supervisor_module_globals, supervisor_module_globals_table); STATIC MP_DEFINE_CONST_DICT(supervisor_module_globals, supervisor_module_globals_table);

View File

@ -38,6 +38,8 @@ enum {
2 2
// next_code_allocation // next_code_allocation
+ 1 + 1
// prev_traceback_allocation
+ 1
#if INTERNAL_FLASH_FILESYSTEM == 0 #if INTERNAL_FLASH_FILESYSTEM == 0
+ 1 + 1

View File

@ -0,0 +1,29 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2020 Christian Walther
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "traceback.h"
supervisor_allocation* prev_traceback_allocation;

View File

@ -0,0 +1,34 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2020 Christian Walther
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef MICROPY_INCLUDED_SUPERVISOR_TRACEBACK_H
#define MICROPY_INCLUDED_SUPERVISOR_TRACEBACK_H
#include "supervisor/memory.h"
extern supervisor_allocation* prev_traceback_allocation;
#endif // MICROPY_INCLUDED_SUPERVISOR_TRACEBACK_H

View File

@ -14,6 +14,7 @@ SRC_SUPERVISOR = \
supervisor/shared/stack.c \ supervisor/shared/stack.c \
supervisor/shared/status_leds.c \ supervisor/shared/status_leds.c \
supervisor/shared/tick.c \ supervisor/shared/tick.c \
supervisor/shared/traceback.c \
supervisor/shared/translate.c supervisor/shared/translate.c
NO_USB ?= $(wildcard supervisor/usb.c) NO_USB ?= $(wildcard supervisor/usb.c)