/* * This file is part of the TREZOR project, https://trezor.io/ * * Copyright (c) SatoshiLabs * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #if !TREZOR_EMULATOR || PYOPT #define MEMINFO_DICT_ENTRIES /* empty */ #else #include "py/bc.h" #include "py/gc.h" #include "py/nlr.h" #include "py/objarray.h" #include "py/objfun.h" #include "py/objgenerator.h" #include "py/objlist.h" #include "py/objstr.h" #include "py/objtype.h" #include "embed/extmod/trezorobj.h" #include "embed/rust/librust.h" #include "embed/trezorhal/usb.h" #include #include #define WORDS_PER_BLOCK ((MICROPY_BYTES_PER_GC_BLOCK) / MP_BYTES_PER_OBJ_WORD) #define BYTES_PER_BLOCK (MICROPY_BYTES_PER_GC_BLOCK) // ATB = allocation table byte // 0b00 = FREE -- free block // 0b01 = HEAD -- head of a chain of blocks // 0b10 = TAIL -- in the tail of a chain of blocks // 0b11 = MARK -- marked head block #define AT_FREE (0) #define AT_HEAD (1) #define AT_TAIL (2) #define AT_MARK (3) #define BLOCKS_PER_ATB (4) #define ATB_MASK_0 (0x03) #define ATB_MASK_1 (0x0c) #define ATB_MASK_2 (0x30) #define ATB_MASK_3 (0xc0) #define ATB_0_IS_FREE(a) (((a)&ATB_MASK_0) == 0) #define ATB_1_IS_FREE(a) (((a)&ATB_MASK_1) == 0) #define ATB_2_IS_FREE(a) (((a)&ATB_MASK_2) == 0) #define ATB_3_IS_FREE(a) (((a)&ATB_MASK_3) == 0) #define BLOCK_SHIFT(block) (2 * ((block) & (BLOCKS_PER_ATB - 1))) #define ATB_GET_KIND(block) \ ((MP_STATE_MEM(gc_alloc_table_start)[(block) / BLOCKS_PER_ATB] >> \ BLOCK_SHIFT(block)) & \ 3) #define ATB_ANY_TO_FREE(block) \ do { \ MP_STATE_MEM(gc_alloc_table_start) \ [(block) / BLOCKS_PER_ATB] &= (~(AT_MARK << BLOCK_SHIFT(block))); \ } while (0) #define ATB_FREE_TO_HEAD(block) \ do { \ MP_STATE_MEM(gc_alloc_table_start) \ [(block) / BLOCKS_PER_ATB] |= (AT_HEAD << BLOCK_SHIFT(block)); \ } while (0) #define ATB_FREE_TO_TAIL(block) \ do { \ MP_STATE_MEM(gc_alloc_table_start) \ [(block) / BLOCKS_PER_ATB] |= (AT_TAIL << BLOCK_SHIFT(block)); \ } while (0) #define ATB_HEAD_TO_MARK(block) \ do { \ MP_STATE_MEM(gc_alloc_table_start) \ [(block) / BLOCKS_PER_ATB] |= (AT_MARK << BLOCK_SHIFT(block)); \ } while (0) #define ATB_MARK_TO_HEAD(block) \ do { \ MP_STATE_MEM(gc_alloc_table_start) \ [(block) / BLOCKS_PER_ATB] &= (~(AT_TAIL << BLOCK_SHIFT(block))); \ } while (0) #define BLOCK_FROM_PTR(ptr) \ (((byte *)(ptr)-MP_STATE_MEM(gc_pool_start)) / BYTES_PER_BLOCK) #define PTR_FROM_BLOCK(block) \ (((block)*BYTES_PER_BLOCK + (uintptr_t)MP_STATE_MEM(gc_pool_start))) #define ATB_FROM_BLOCK(bl) ((bl) / BLOCKS_PER_ATB) // ptr should be of type void* #define VERIFY_PTR(ptr) \ (((uintptr_t)(ptr) & (BYTES_PER_BLOCK - 1)) == \ 0 /* must be aligned on a block */ \ && ptr >= (void *)MP_STATE_MEM( \ gc_pool_start) /* must be above start of pool */ \ && ptr < (void *)MP_STATE_MEM(gc_pool_end) /* must be below end of pool */ \ ) #define Q_GET_DATA(q) \ ((q) + MICROPY_QSTR_BYTES_IN_HASH + MICROPY_QSTR_BYTES_IN_LEN) extern const qstr_pool_t mp_qstr_const_pool; size_t find_allocated_size(void const *const ptr) { if (!ptr) { return 0; } if (!VERIFY_PTR(ptr)) { // printf("failed to verify ptr: %p\n", ptr); return 0; } size_t block = BLOCK_FROM_PTR(ptr); if (ATB_GET_KIND(block) == AT_TAIL) { return 0; } size_t n = 0; do { ++n; } while (ATB_GET_KIND(block + n) == AT_TAIL); return n; } void dump_value(FILE *out, mp_const_obj_t value); void mark(void const *const ptr) { if (!VERIFY_PTR(ptr)) return; size_t block = BLOCK_FROM_PTR(ptr); if (ATB_GET_KIND(block) == AT_HEAD) { ATB_HEAD_TO_MARK(block); } } bool is_short(mp_const_obj_t value) { return value == NULL || value == MP_OBJ_NULL || mp_obj_is_qstr(value) || mp_obj_is_small_int(value) || !VERIFY_PTR(value); } static void print_type(FILE *out, const char *typename, const char *shortval, const void *ptr, bool end) { static char unescaped[1000]; size_t size = 0; if (!is_short(ptr)) { size = find_allocated_size(ptr); } fprintf(out, "{\"type\": \"%s\", \"alloc\": %ld, \"ptr\": \"%p\"", typename, size, ptr); if (shortval) { assert(strlen(shortval) < 1000); char *c = unescaped; while (*shortval) { if (*shortval == '\\' || *shortval == '"') *c++ = '\\'; *c++ = *shortval++; } *c = 0; fprintf(out, ", \"shortval\": \"%s\"", unescaped); } else { fprintf(out, ", \"shortval\": null"); } if (end) fprintf(out, "}"); } static void print_repr(FILE *out, const char *strbuf, size_t buflen) { fprintf(out, "\""); for (size_t i = 0; i < buflen; ++i) { if (strbuf[i] == '\\') fprintf(out, "\\\\"); else if (strbuf[i] == '"') fprintf(out, "\\\""); else if (strbuf[i] >= 0x20 && strbuf[i] <= 0x7e) fprintf(out, "%c", strbuf[i]); else fprintf(out, "\\\\x%02x", (unsigned char)strbuf[i]); } fprintf(out, "\""); } void dump_short(FILE *out, mp_const_obj_t value) { fflush(out); if (value == NULL || value == MP_OBJ_NULL) { fprintf(out, "null"); } else if (mp_obj_is_qstr(value)) { mp_int_t q = MP_OBJ_QSTR_VALUE(value); print_type(out, "qstr", qstr_str(q), NULL, true); } else if (mp_obj_is_small_int(value)) { static char num_buf[100]; snprintf(num_buf, 100, "%ld", MP_OBJ_SMALL_INT_VALUE(value)); print_type(out, "smallint", num_buf, NULL, true); } else if (!VERIFY_PTR(value)) { print_type(out, "romdata", NULL, value, true); } } void dump_short_or_ptr(FILE *out, mp_const_obj_t value) { if (is_short(value)) dump_short(out, value); else fprintf(out, "\"%p\"", value); } void dump_map_as_children(FILE *out, const mp_map_t *map) { fprintf(out, ", \"children\": ["); bool first = true; for (size_t i = 0; i < map->alloc; ++i) { if (!mp_map_slot_is_filled(map, i)) continue; if (!first) fprintf(out, ",\n"); first = false; fprintf(out, "{\"key\": "); dump_short_or_ptr(out, map->table[i].key); fprintf(out, ",\n\"value\": "); dump_short_or_ptr(out, map->table[i].value); fprintf(out, "}"); } fprintf(out, "]"); } void dump_map_as_values(FILE *out, const void *const owner, const mp_map_t *map) { print_type(out, "mapitems", NULL, map->table, false); fprintf(out, ",\n\"owner\": \"%p\"", owner); fprintf(out, "},\n"); for (size_t i = 0; i < map->alloc; ++i) { if (!mp_map_slot_is_filled(map, i)) continue; dump_value(out, map->table[i].key); dump_value(out, map->table[i].value); } } void dump_dict_inner(FILE *out, const mp_obj_dict_t *dict) { print_type(out, "dict", NULL, dict, false); dump_map_as_children(out, &dict->map); fprintf(out, "},\n"); dump_map_as_values(out, dict, &dict->map); } void dump_function(FILE *out, const mp_obj_fun_bc_t *func) { print_type(out, "function", NULL, func, false); fprintf(out, ",\n\"globals\": \"%p\"", func->globals); fprintf(out, ",\n\"code_alloc\": %ld", find_allocated_size(func->bytecode)); fprintf(out, ",\n\"code_ptr\": \"%p\"", func->bytecode); fprintf(out, ",\n\"const_table_alloc\": %ld", find_allocated_size(func->const_table)); fprintf(out, ",\n\"const_table_ptr\": \"%p\"", func->const_table); mark(func->bytecode); mark(func->const_table); fprintf(out, "},\n"); dump_value(out, func->globals); } typedef struct _mp_obj_bound_meth_t { mp_obj_base_t base; mp_obj_t meth; mp_obj_t self; } mp_obj_bound_meth_t; typedef struct _mp_obj_closure_t { mp_obj_base_t base; mp_obj_t fun; size_t n_closed; mp_obj_t closed[]; } mp_obj_closure_t; extern const mp_obj_type_t mp_type_bound_meth; extern const mp_obj_type_t mp_type_closure; extern const mp_obj_type_t mp_type_cell; extern const mp_obj_type_t mod_trezorio_WebUSB_type; extern const mp_obj_type_t mod_trezorio_USB_type; extern const mp_obj_type_t mod_trezorio_VCP_type; extern const mp_obj_type_t mod_trezorio_HID_type; extern const mp_obj_type_t mod_trezorui_Display_type; typedef struct _mp_obj_WebUSB_t { mp_obj_base_t base; usb_webusb_info_t info; } mp_obj_WebUSB_t; typedef struct _mp_obj_VCP_t { mp_obj_base_t base; usb_vcp_info_t info; } mp_obj_VCP_t; typedef struct _mp_obj_HID_t { mp_obj_base_t base; usb_hid_info_t info; } mp_obj_HID_t; typedef struct _mp_obj_protomsg_t { mp_obj_base_t base; mp_map_t map; } mp_obj_protomsg_t; typedef struct _mp_obj_uilayout_t { mp_obj_base_t base; ssize_t _refcell_borrow_flag; void *inner; } mp_obj_uilayout_t; void dump_bound_method(FILE *out, const mp_obj_bound_meth_t *meth) { print_type(out, "method", NULL, meth, false); fprintf(out, ",\n\"self\": \"%p\"", meth->self); fprintf(out, ",\n\"body\": \"%p\"", meth->meth); fprintf(out, "},"); dump_value(out, meth->self); dump_value(out, meth->meth); } void dump_static_method(FILE *out, const mp_obj_static_class_method_t *meth) { print_type(out, "staticmethod", NULL, meth, false); fprintf(out, ",\n\"body\": \"%p\"", meth->fun); fprintf(out, "},"); dump_value(out, meth->fun); } void dump_closure(FILE *out, const mp_obj_closure_t *closure) { size_t size = find_allocated_size(closure); for (size_t i = 0; i < closure->n_closed; ++i) { // XXX this is unimportant to track properly, hopefully size += find_allocated_size(closure->closed[i]); assert(mp_obj_is_type(closure->closed[i], &mp_type_cell)); } print_type(out, "closure", NULL, closure, false); fprintf(out, ",\n\"function\": \"%p\"", closure->fun); fprintf(out, ",\n\"closed\": [\n"); bool first = true; for (size_t i = 0; i < closure->n_closed; ++i) { if (!first) fprintf(out, ",\n"); first = false; dump_short_or_ptr(out, mp_obj_cell_get(closure->closed[i])); } fprintf(out, "]},"); dump_value(out, closure->fun); for (size_t i = 0; i < closure->n_closed; ++i) { dump_value(out, mp_obj_cell_get(closure->closed[i])); } } typedef struct _mp_obj_gen_instance_t { mp_obj_base_t base; // mp_const_none: Not-running, no exception. // MP_OBJ_NULL: Running, no exception. // other: Not running, pending exception. mp_obj_t pend_exc; mp_code_state_t code_state; } mp_obj_gen_instance_t; void dump_generator(FILE *out, const mp_obj_gen_instance_t *gen) { print_type(out, "generator", NULL, gen, false); fprintf(out, ",\n\"pending_exception\": \"%p\"", gen->pend_exc); fprintf(out, ",\n\"function\": \"%p\"", gen->code_state.fun_bc); fprintf(out, ",\n\"old_globals\": \"%p\"", gen->code_state.old_globals); fprintf(out, ",\n\"state\": [\n"); bool first = true; for (size_t i = 0; i < gen->code_state.n_state; ++i) { if (!first) fprintf(out, ",\n"); first = false; dump_short_or_ptr(out, gen->code_state.state[i]); } fprintf(out, "]},\n"); dump_value(out, gen->pend_exc); dump_value(out, gen->code_state.fun_bc); dump_value(out, gen->code_state.old_globals); for (size_t i = 0; i < gen->code_state.n_state; ++i) { dump_value(out, gen->code_state.state[i]); } } void dump_instance(FILE *out, const mp_obj_instance_t *obj) { print_type(out, "instance", NULL, obj, false); fprintf(out, ",\n\"base\": \"%p\"", obj->base.type); dump_map_as_children(out, &obj->members); fprintf(out, "},\n"); dump_value(out, obj->base.type); dump_map_as_values(out, obj, &obj->members); } void dump_type(FILE *out, const mp_obj_type_t *type) { print_type(out, "type", qstr_str(type->name), type, false); fprintf(out, ",\n\"locals\": \"%p\"", type->locals_dict); fprintf(out, ",\n\"parent\": \"%p\"},\n", type->parent); dump_value(out, type->parent); dump_value(out, type->locals_dict); } void dump_list(FILE *out, const mp_obj_list_t *list) { print_type(out, "list", NULL, list, false); fprintf(out, ",\n\"items\": [\n"); bool first = true; for (size_t i = 0; i < list->len; ++i) { if (!first) fprintf(out, ",\n"); first = false; dump_short_or_ptr(out, list->items[i]); } fprintf(out, "]},\n"); print_type(out, "listitems", NULL, list->items, false); fprintf(out, ",\n\"owner\": \"%p\"},\n", list); for (size_t i = 0; i < list->len; ++i) { dump_value(out, list->items[i]); } } void dump_tuple(FILE *out, const mp_obj_tuple_t *tuple) { print_type(out, "tuple", NULL, tuple, false); fprintf(out, ",\n\"items\": [\n"); bool first = true; for (size_t i = 0; i < tuple->len; ++i) { if (!first) fprintf(out, ",\n"); first = false; dump_short_or_ptr(out, tuple->items[i]); } fprintf(out, "]},\n"); for (size_t i = 0; i < tuple->len; ++i) { dump_value(out, tuple->items[i]); } } typedef struct _mp_obj_set_t { mp_obj_base_t base; mp_set_t set; } mp_obj_set_t; bool is_set_or_frozenset(mp_const_obj_t o); void dump_set(FILE *out, const mp_obj_set_t *set) { print_type(out, "set", NULL, set, false); fprintf(out, ",\n\"items\": [\n"); bool first = true; for (size_t i = 0; i < set->set.alloc; ++i) { if (!mp_set_slot_is_filled(&set->set, i)) continue; if (!first) fprintf(out, ",\n"); first = false; dump_short_or_ptr(out, set->set.table[i]); } fprintf(out, "]},\n"); print_type(out, "setitems", NULL, set->set.table, false); fprintf(out, ",\n\"owner\": \"%p\"},\n", set); for (size_t i = 0; i < set->set.alloc; ++i) { if (!mp_set_slot_is_filled(&set->set, i)) continue; dump_value(out, set->set.table[i]); } } void dump_trezor_hid(FILE *out, const mp_obj_HID_t *hid) { print_type(out, "trezor-hid", NULL, hid, false); fprintf(out, ",\n\"rx_buffer\": \"%p\"},\n", hid->info.rx_buffer); print_type(out, "rawbuffer", NULL, hid->info.rx_buffer, true); fprintf(out, ",\n"); } void dump_trezor_webusb(FILE *out, const mp_obj_WebUSB_t *webusb) { print_type(out, "trezor-webusb", NULL, webusb, false); fprintf(out, ",\n\"rx_buffer\": \"%p\"},\n", webusb->info.rx_buffer); print_type(out, "rawbuffer", NULL, webusb->info.rx_buffer, true); fprintf(out, ",\n"); } void dump_trezor_vcp(FILE *out, const mp_obj_VCP_t *vcp) { print_type(out, "trezor-vcp", NULL, vcp, false); fprintf(out, ",\n\"tx_packet\": \"%p\"", vcp->info.tx_packet); fprintf(out, ",\n\"tx_buffer\": \"%p\"", vcp->info.tx_buffer); fprintf(out, ",\n\"rx_packet\": \"%p\"", vcp->info.rx_packet); fprintf(out, ",\n\"rx_buffer\": \"%p\"},\n", vcp->info.rx_buffer); print_type(out, "rawbuffer", NULL, vcp->info.tx_packet, true); fprintf(out, ",\n"); print_type(out, "rawbuffer", NULL, vcp->info.tx_buffer, true); fprintf(out, ",\n"); print_type(out, "rawbuffer", NULL, vcp->info.rx_packet, true); fprintf(out, ",\n"); print_type(out, "rawbuffer", NULL, vcp->info.rx_buffer, true); fprintf(out, ",\n"); } void dump_protomsg(FILE *out, const mp_obj_protomsg_t *value) { mp_obj_t name[2] = {MP_OBJ_NULL, MP_OBJ_NULL}; mp_obj_type_t *type = protobuf_debug_msg_type(); type->attr((mp_obj_t)value, MP_QSTR_MESSAGE_NAME, name); print_type(out, "protomsg", NULL, value, false); fprintf(out, ",\n\"message_name\": "); dump_short(out, name[0]); dump_map_as_children(out, &value->map); fprintf(out, "},\n"); } void dump_protodef(FILE *out, const mp_obj_t *value) { mp_obj_t name[2] = {MP_OBJ_NULL, MP_OBJ_NULL}; mp_obj_type_t *type = protobuf_debug_msg_def_type(); type->attr((mp_obj_t)value, MP_QSTR_MESSAGE_NAME, name); print_type(out, "protodef", NULL, value, false); fprintf(out, ",\n\"message_name\": "); dump_short(out, name[0]); fprintf(out, "},\n"); } void dump_uilayout(FILE *out, const mp_obj_uilayout_t *value) { print_type(out, "uilayout", NULL, value, false); fprintf(out, ",\n\"inner\": \"%p\"},\n", value->inner); print_type(out, "uilayoutinner", NULL, value->inner, true); fprintf(out, ",\n"); } void dump_value_opt(FILE *out, mp_const_obj_t value, bool eval_short) { if (!eval_short && is_short(value)) return; if (!eval_short || VERIFY_PTR(value)) { size_t block = BLOCK_FROM_PTR(value); switch (ATB_GET_KIND(block)) { case AT_HEAD: // all is ok ATB_HEAD_TO_MARK(block); break; case AT_TAIL: printf("===== pointer to tail???\n"); break; case AT_MARK: // print_type(out, "already_dumped", 0, NULL, value); return; } } if (mp_obj_is_str_or_bytes(value)) { const mp_obj_str_t *strvalue = (mp_obj_str_t *)value; print_type(out, "anystr", NULL, value, false); fprintf(out, ", \"val\": "); print_repr(out, (const char *)strvalue->data, strvalue->len); fprintf(out, ", \"data\": \"%p\"", strvalue->data); fprintf(out, "},\n"); print_type(out, "strdata", NULL, strvalue->data, true); fprintf(out, ",\n"); } else if (mp_obj_is_type(value, &mp_type_bytearray)) { const mp_obj_array_t *array = (mp_obj_array_t *)value; print_type(out, "array", NULL, array, true); fprintf(out, ",\n"); print_type(out, "arrayitems", NULL, array->items, false); fprintf(out, ", \"owner\": \"%p\"}", array); fprintf(out, ",\n"); } else if (mp_obj_is_type(value, &mp_type_dict)) { dump_dict_inner(out, value); } else if (mp_obj_is_type(value, &mp_type_module)) { print_type(out, "module", NULL, value, false); mp_obj_module_t *module = MP_OBJ_TO_PTR(value); fprintf(out, ", \"globals\": \"%p\"", module->globals); fprintf(out, "},\n"); dump_value(out, module->globals); } else if (mp_obj_is_type(value, &mp_type_fun_bc) || mp_obj_is_type(value, &mp_type_gen_wrap)) { dump_function(out, value); } else if (mp_obj_is_type(value, &mp_type_bound_meth)) { dump_bound_method(out, value); } else if (mp_obj_is_type(value, &mp_type_closure)) { dump_closure(out, value); } else if (mp_obj_is_type(value, &mp_type_staticmethod) || mp_obj_is_type(value, &mp_type_classmethod)) { dump_static_method(out, value); } else if (mp_obj_is_instance_type(mp_obj_get_type(value))) { dump_instance(out, value); } else if (mp_obj_is_type(value, &mp_type_object)) { print_type(out, "object", NULL, value, true); fprintf(out, ",\n"); } else if (mp_obj_is_type(value, &mp_type_type)) { dump_type(out, value); } else if (mp_obj_is_type(value, &mp_type_list)) { dump_list(out, value); } else if (mp_obj_is_type(value, &mp_type_tuple)) { dump_tuple(out, value); } else if (is_set_or_frozenset(value)) { dump_set(out, value); } else if (mp_obj_is_type(value, &mp_type_gen_instance)) { dump_generator(out, value); } else if (mp_obj_is_type(value, &mod_trezorio_WebUSB_type)) { dump_trezor_webusb(out, value); } else if (mp_obj_is_type(value, &mod_trezorio_VCP_type)) { dump_trezor_vcp(out, value); } else if (mp_obj_is_type(value, &mod_trezorio_HID_type)) { dump_trezor_hid(out, value); } else if (mp_obj_is_type(value, &mod_trezorio_USB_type) || mp_obj_is_type(value, &mod_trezorui_Display_type)) { print_type(out, "trezor", NULL, value, true); fprintf(out, ",\n"); } else if (mp_obj_is_type(value, protobuf_debug_msg_type())) { dump_protomsg(out, value); } else if (mp_obj_is_type(value, protobuf_debug_msg_def_type())) { dump_protodef(out, value); } #ifdef TREZOR_UI2 else if (mp_obj_is_type(value, ui_debug_layout_type())) { dump_uilayout(out, value); } #endif else { print_type(out, "unknown", NULL, value, true); fprintf(out, ",\n"); } fflush(out); } void dump_value(FILE *out, mp_const_obj_t value) { dump_value_opt(out, value, false); } void dump_qstr_pool(FILE *out, qstr_pool_t *pool) { print_type(out, "qstrpool", NULL, pool, false); fprintf(out, ", \"qstrs\": [\n"); for (const byte **q = pool->qstrs, **q_top = pool->qstrs + pool->len; q < q_top; q++) { if (q < (q_top - 1)) fprintf(out, "\"%s\",\n", Q_GET_DATA(*q)); else fprintf(out, "\"%s\"]\n", Q_GET_DATA(*q)); } fprintf(out, "},\n"); for (const byte **q = pool->qstrs, **q_top = pool->qstrs + pool->len; q < q_top; q++) { print_type(out, "qstrdata", NULL, *q, false); fprintf(out, ", \"pool\": \"%p\"},\n", pool); } } void dump_qstrdata(FILE *out) { qstr_pool_t *pool = MP_STATE_VM(last_pool); while (pool != NULL) { for (const byte **q = pool->qstrs, **q_top = pool->qstrs + pool->len; q < q_top; q++) { if ((void *)*q > (void *)mp_state_ctx.mem.gc_pool_start) { print_type(out, "qstrdata", NULL, q, false); fprintf(out, ", \"pool\": \"%p\"},\n", pool); } } pool = pool->prev; } } /// def meminfo(filename: str) -> None: /// """Dumps map of micropython GC arena to a file. /// The JSON file can be decoded by analyze.py /// Only available in the emulator. /// """ STATIC mp_obj_t mod_trezorutils_meminfo(mp_obj_t filename) { size_t fn_len; FILE *out = fopen(mp_obj_str_get_data(filename, &fn_len), "w"); fprintf(out, "["); // void **ptrs = (void **)(void *)&mp_state_ctx; // size_t root_start = offsetof(mp_state_ctx_t, thread.dict_locals); // size_t root_end = offsetof(mp_state_ctx_t, vm.qstr_last_chunk); // for (size_t i = root_start; i < root_end; i++) { // void *ptr = ptrs[i]; // if (i == 55) continue; // mp_loaded_modules_dict // if (i == 62) continue; // dict_main // if (i == 66) continue; // mp_sys_path_obj // if (i == 70) continue; // mp_sys_argv_obj // if (i == 123) continue; // qstr_last_chunk // if (VERIFY_PTR(ptr)) { // size_t block = BLOCK_FROM_PTR(ptr); // if (ATB_GET_KIND(block) == AT_HEAD) { // fprintf(out, "\"root_ofs: %ld\",\n", i); // dump_value(out, ptr); // // // An unmarked head: mark it, and mark all its children // // TRACE_MARK(block, ptr); // // ATB_HEAD_TO_MARK(block); // // gc_mark_subtree(block); // } // } // } fprintf(out, "\"dict_locals\",\n"); dump_value(out, MP_STATE_THREAD(dict_locals)); fprintf(out, "\"mp_loaded_modules_dict\",\n"); dump_value_opt(out, &MP_STATE_VM(mp_loaded_modules_dict), true); fprintf(out, "\"dict_main\",\n"); dump_value_opt(out, &MP_STATE_VM(dict_main), true); fprintf(out, "\"mp_sys_path_obj\",\n"); dump_value_opt(out, &MP_STATE_VM(mp_sys_path_obj), true); fprintf(out, "\"mp_sys_argv_obj\",\n"); dump_value_opt(out, &MP_STATE_VM(mp_sys_argv_obj), true); fprintf(out, "\"ui_wait_callback\",\n"); dump_value(out, MP_STATE_VM(trezorconfig_ui_wait_callback)); fprintf(out, "\"qstr_pools\",\n"); qstr_pool_t *pool = MP_STATE_VM(last_pool); while (VERIFY_PTR((void *)pool)) { dump_qstr_pool(out, pool); pool = pool->prev; } fprintf(out, "null]\n"); fclose(out); for (size_t block = 0; block < MP_STATE_MEM(gc_alloc_table_byte_len) * BLOCKS_PER_ATB; block++) { if (ATB_GET_KIND(block) == AT_MARK) { ATB_MARK_TO_HEAD(block); } } gc_dump_alloc_table(); return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorutils_meminfo_obj, mod_trezorutils_meminfo); #define MEMINFO_DICT_ENTRIES \ {MP_ROM_QSTR(MP_QSTR_meminfo), MP_ROM_PTR(&mod_trezorutils_meminfo_obj)}, #endif