Skip to content

Commit 5f137af

Browse files
committed
Add supervisor.get_previous_traceback() function.
Useful for adafruit#1084.
1 parent 72f1489 commit 5f137af

File tree

8 files changed

+140
-8
lines changed

8 files changed

+140
-8
lines changed

lib/utils/pyexec.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input
167167
result->return_code = ret;
168168
if (ret != 0) {
169169
mp_obj_t return_value = (mp_obj_t)nlr.ret_val;
170-
result->exception_type = mp_obj_get_type(return_value);
170+
result->exception = return_value;
171171
result->exception_line = -1;
172172

173173
if (mp_obj_is_exception_instance(return_value)) {

lib/utils/pyexec.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ typedef enum {
3535

3636
typedef struct {
3737
int return_code;
38-
const mp_obj_type_t *exception_type;
38+
mp_obj_t exception;
3939
int exception_line;
4040
} pyexec_result_t;
4141

main.c

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
#include "supervisor/shared/safe_mode.h"
5757
#include "supervisor/shared/stack.h"
5858
#include "supervisor/shared/status_leds.h"
59+
#include "supervisor/shared/traceback.h"
5960
#include "supervisor/shared/translate.h"
6061
#include "supervisor/shared/workflow.h"
6162
#include "supervisor/usb.h"
@@ -227,7 +228,41 @@ STATIC bool maybe_run_list(const char * const * filenames, pyexec_result_t* exec
227228
return true;
228229
}
229230

230-
STATIC void cleanup_after_vm(supervisor_allocation* heap) {
231+
STATIC void count_strn(void *data, const char *str, size_t len) {
232+
*(size_t*)data += len;
233+
}
234+
235+
STATIC void cleanup_after_vm(supervisor_allocation* heap, mp_obj_t exception) {
236+
// Get the traceback of any exception from this run off the heap.
237+
// MP_OBJ_SENTINEL means "this run does not contribute to traceback storage, don't touch it"
238+
// MP_OBJ_NULL (=0) means "this run completed successfully, clear any stored traceback"
239+
if (exception != MP_OBJ_SENTINEL) {
240+
free_memory(prev_traceback_allocation);
241+
// ReloadException is exempt from traceback printing in pyexec_file(), so treat it as "no
242+
// traceback" here too.
243+
if (exception && exception != MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_reload_exception))) {
244+
size_t traceback_len = 0;
245+
mp_print_t print_count = {&traceback_len, count_strn};
246+
mp_obj_print_exception(&print_count, exception);
247+
prev_traceback_allocation = allocate_memory(align32_size(traceback_len + 1), false, true);
248+
// Empirically, this never fails in practice - even when the heap is totally filled up
249+
// with single-block-sized objects referenced by a root pointer, exiting the VM frees
250+
// up several hundred bytes, sufficient for the traceback (which tends to be shortened
251+
// because there wasn't memory for the full one). There may be convoluted ways of
252+
// making it fail, but at this point I believe they are not worth spending code on.
253+
if (prev_traceback_allocation != NULL) {
254+
vstr_t vstr;
255+
vstr_init_fixed_buf(&vstr, traceback_len, (char*)prev_traceback_allocation->ptr);
256+
mp_print_t print = {&vstr, (mp_print_strn_t)vstr_add_strn};
257+
mp_obj_print_exception(&print, exception);
258+
((char*)prev_traceback_allocation->ptr)[traceback_len] = '\0';
259+
}
260+
}
261+
else {
262+
prev_traceback_allocation = NULL;
263+
}
264+
}
265+
231266
// Reset port-independent devices, like CIRCUITPY_BLEIO_HCI.
232267
reset_devices();
233268
// 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) {
287322
pyexec_result_t result;
288323

289324
result.return_code = 0;
290-
result.exception_type = NULL;
325+
result.exception = MP_OBJ_NULL;
291326
result.exception_line = 0;
292327

293328
bool skip_repl;
@@ -349,7 +384,7 @@ STATIC bool run_code_py(safe_mode_t safe_mode) {
349384
}
350385

351386
// Finished executing python code. Cleanup includes a board reset.
352-
cleanup_after_vm(heap);
387+
cleanup_after_vm(heap, result.exception);
353388

354389
// If a new next code file was set, that is a reason to keep it (obviously). Stuff this into
355390
// 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) {
674709
usb_set_defaults();
675710
#endif
676711

712+
pyexec_result_t result = {0, MP_OBJ_NULL, 0};
677713
if (ok_to_run) {
678-
bool found_boot = maybe_run_list(boot_py_filenames, NULL);
714+
bool found_boot = maybe_run_list(boot_py_filenames, &result);
679715
(void) found_boot;
680716

681717
#ifdef CIRCUITPY_BOOT_OUTPUT_FILE
@@ -701,7 +737,7 @@ STATIC void __attribute__ ((noinline)) run_boot_py(safe_mode_t safe_mode) {
701737
usb_get_boot_py_data(usb_boot_py_data, size);
702738
#endif
703739

704-
cleanup_after_vm(heap);
740+
cleanup_after_vm(heap, result.exception);
705741

706742
#if CIRCUITPY_USB
707743
// Now give back the data we saved from the heap going away.
@@ -736,7 +772,7 @@ STATIC int run_repl(void) {
736772
} else {
737773
exit_code = pyexec_friendly_repl();
738774
}
739-
cleanup_after_vm(heap);
775+
cleanup_after_vm(heap, MP_OBJ_SENTINEL);
740776
#if CIRCUITPY_STATUS_LED
741777
status_led_init();
742778
new_status_color(BLACK);

shared-bindings/supervisor/__init__.c

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include "supervisor/shared/autoreload.h"
3535
#include "supervisor/shared/status_leds.h"
3636
#include "supervisor/shared/stack.h"
37+
#include "supervisor/shared/traceback.h"
3738
#include "supervisor/shared/translate.h"
3839
#include "supervisor/shared/workflow.h"
3940

@@ -207,6 +208,34 @@ STATIC mp_obj_t supervisor_set_next_code_file(size_t n_args, const mp_obj_t *pos
207208
}
208209
MP_DEFINE_CONST_FUN_OBJ_KW(supervisor_set_next_code_file_obj, 0, supervisor_set_next_code_file);
209210

211+
//| def get_previous_traceback() -> Optional[str]:
212+
//| """If the last vm run ended with an exception (including the KeyboardInterrupt caused by
213+
//| CTRL-C), returns the traceback as a string.
214+
//| Otherwise, returns ``None``.
215+
//|
216+
//| An exception traceback is only preserved over a soft reload, a hard reset clears it.
217+
//|
218+
//| Only code (main or boot) runs are considered, not REPL runs."""
219+
//| ...
220+
//|
221+
STATIC mp_obj_t supervisor_get_previous_traceback(void) {
222+
//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?
223+
if (prev_traceback_allocation) {
224+
size_t len = strlen((const char*)prev_traceback_allocation->ptr);
225+
if (len > 0) {
226+
mp_obj_str_t *o = m_new_obj(mp_obj_str_t);
227+
o->base.type = &mp_type_str;
228+
o->len = len;
229+
//TODO is it a good assumption that callers probably aren't going to compare this string, so skip computing the hash?
230+
o->hash = 0;
231+
o->data = (const byte*)prev_traceback_allocation->ptr;
232+
return MP_OBJ_FROM_PTR(o);
233+
}
234+
}
235+
return mp_const_none;
236+
}
237+
MP_DEFINE_CONST_FUN_OBJ_0(supervisor_get_previous_traceback_obj, supervisor_get_previous_traceback);
238+
210239
STATIC const mp_rom_map_elem_t supervisor_module_globals_table[] = {
211240
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_supervisor) },
212241
{ MP_ROM_QSTR(MP_QSTR_enable_autoreload), MP_ROM_PTR(&supervisor_enable_autoreload_obj) },
@@ -217,6 +246,7 @@ STATIC const mp_rom_map_elem_t supervisor_module_globals_table[] = {
217246
{ MP_ROM_QSTR(MP_QSTR_RunReason), MP_ROM_PTR(&supervisor_run_reason_type) },
218247
{ MP_ROM_QSTR(MP_QSTR_set_next_stack_limit), MP_ROM_PTR(&supervisor_set_next_stack_limit_obj) },
219248
{ MP_ROM_QSTR(MP_QSTR_set_next_code_file), MP_ROM_PTR(&supervisor_set_next_code_file_obj) },
249+
{ MP_ROM_QSTR(MP_QSTR_get_previous_traceback), MP_ROM_PTR(&supervisor_get_previous_traceback_obj) },
220250
};
221251

222252
STATIC MP_DEFINE_CONST_DICT(supervisor_module_globals, supervisor_module_globals_table);

supervisor/shared/memory.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ enum {
3838
2
3939
// next_code_allocation
4040
+ 1
41+
// prev_traceback_allocation
42+
+ 1
4143

4244
#if INTERNAL_FLASH_FILESYSTEM == 0
4345
+ 1

supervisor/shared/traceback.c

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

supervisor/shared/traceback.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* This file is part of the MicroPython project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2020 Christian Walther
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in
16+
* all copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
* THE SOFTWARE.
25+
*/
26+
27+
#ifndef MICROPY_INCLUDED_SUPERVISOR_TRACEBACK_H
28+
#define MICROPY_INCLUDED_SUPERVISOR_TRACEBACK_H
29+
30+
#include "supervisor/memory.h"
31+
32+
extern supervisor_allocation* prev_traceback_allocation;
33+
34+
#endif // MICROPY_INCLUDED_SUPERVISOR_TRACEBACK_H

supervisor/supervisor.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ SRC_SUPERVISOR = \
1414
supervisor/shared/stack.c \
1515
supervisor/shared/status_leds.c \
1616
supervisor/shared/tick.c \
17+
supervisor/shared/traceback.c \
1718
supervisor/shared/translate.c
1819

1920
NO_USB ?= $(wildcard supervisor/usb.c)

0 commit comments

Comments
 (0)