mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-01-11 07:50:57 +00:00
feat(core/emulator): JSON memory map dump
use `trezor.utils.mem_dump("somefile.json")` in a key place, then `analyze.py src/somefile.json` to look at what is going on
This commit is contained in:
parent
fe6c131b14
commit
b41d4c71f0
1
core/.changelog.d/1557.added
Normal file
1
core/.changelog.d/1557.added
Normal file
@ -0,0 +1 @@
|
||||
[emulator] Added option to dump detailed Micropython memory layout
|
@ -25,6 +25,7 @@
|
||||
|
||||
#if MICROPY_PY_TREZORUI
|
||||
|
||||
#include "embed/extmod/trezorobj.h"
|
||||
#include "modtrezorui-display.h"
|
||||
|
||||
STATIC const mp_rom_map_elem_t mp_module_trezorui_globals_table[] = {
|
||||
|
728
core/embed/extmod/modtrezorutils/modtrezorutils-meminfo.h
Normal file
728
core/embed/extmod/modtrezorutils/modtrezorutils-meminfo.h
Normal file
@ -0,0 +1,728 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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/trezorhal/usb.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#define WORDS_PER_BLOCK ((MICROPY_BYTES_PER_GC_BLOCK) / BYTES_PER_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 closure_type;
|
||||
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;
|
||||
|
||||
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_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, &closure_type)) {
|
||||
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 {
|
||||
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
|
@ -24,6 +24,7 @@
|
||||
|
||||
#if MICROPY_PY_TREZORUTILS
|
||||
|
||||
#include "embed/extmod/modtrezorutils/modtrezorutils-meminfo.h"
|
||||
#include "embed/extmod/trezorobj.h"
|
||||
|
||||
#include <string.h>
|
||||
@ -144,6 +145,7 @@ STATIC const mp_rom_map_elem_t mp_module_trezorutils_globals_table[] = {
|
||||
{MP_ROM_QSTR(MP_QSTR_MODEL), MP_ROM_QSTR(MP_QSTR(TREZOR_MODEL))},
|
||||
#ifdef TREZOR_EMULATOR
|
||||
{MP_ROM_QSTR(MP_QSTR_EMULATOR), mp_const_true},
|
||||
MEMINFO_DICT_ENTRIES
|
||||
#else
|
||||
{MP_ROM_QSTR(MP_QSTR_EMULATOR), mp_const_false},
|
||||
#endif
|
||||
|
@ -1,6 +1,14 @@
|
||||
from typing import *
|
||||
|
||||
|
||||
# extmod/modtrezorutils/modtrezorutils-meminfo.h
|
||||
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.
|
||||
"""
|
||||
|
||||
|
||||
# extmod/modtrezorutils/modtrezorutils.c
|
||||
def consteq(sec: bytes, pub: bytes) -> bool:
|
||||
"""
|
||||
|
@ -3,7 +3,7 @@ from typing import TypeVar
|
||||
C = TypeVar("C", bound=int)
|
||||
|
||||
def const(c: C) -> C: ...
|
||||
def mem_info() -> None: ...
|
||||
def mem_info(verbose: bool | None = None) -> None: ...
|
||||
def mem_current() -> int: ...
|
||||
def mem_total() -> int: ...
|
||||
def mem_peak() -> int: ...
|
||||
|
@ -96,6 +96,24 @@ def presize_module(modname: str, size: int) -> None:
|
||||
delattr(module, "___PRESIZE_MODULE_%d" % i)
|
||||
|
||||
|
||||
if __debug__:
|
||||
|
||||
def mem_dump(filename: str) -> None:
|
||||
from micropython import mem_info
|
||||
|
||||
print("### sysmodules (%d):" % len(sys.modules))
|
||||
for mod in sys.modules:
|
||||
print("*", mod)
|
||||
if EMULATOR:
|
||||
from trezorutils import meminfo
|
||||
|
||||
print("### dumping to", filename)
|
||||
meminfo(filename)
|
||||
mem_info()
|
||||
else:
|
||||
mem_info(True)
|
||||
|
||||
|
||||
def ensure(cond: bool, msg: str | None = None) -> None:
|
||||
if not cond:
|
||||
if msg is None:
|
||||
|
364
core/tools/analyze-memory-dump.py
Executable file
364
core/tools/analyze-memory-dump.py
Executable file
@ -0,0 +1,364 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import sys
|
||||
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print("""\
|
||||
USAGE: ./analyze-memory-dump.py somefile.json
|
||||
|
||||
Where "somefile.json" was produced by using `trezor.utils.mem_dump("somefile.json")`
|
||||
somewhere in emulator source code.
|
||||
|
||||
Outputs a memory dump similar to `micropython.mem_info(True)`, except with complete
|
||||
(hopefully :) ) understanding of concrete objects at given memory addresses. Also
|
||||
outputs `memorymap.html`, which is a clickable memory dump with cross-references
|
||||
between the objects, for detailed examination of what is going on.
|
||||
|
||||
The "inferred name" feature works by looking up the closest dict containing the object
|
||||
as a value, with a string key. This sometimes works amazingly and sometimes not so much.
|
||||
|
||||
Certain kinds of objects have a separately allocated storage space. Such storage space
|
||||
is given an "owner" backreference that will point you back to the object that you
|
||||
actually care about.
|
||||
|
||||
Modules are nothing but a link to a globals dict. The dict must be examined separately.
|
||||
|
||||
Generators and closures are painful :(
|
||||
""")
|
||||
|
||||
|
||||
|
||||
with open(sys.argv[1]) as f:
|
||||
MEMMAP = json.load(f)
|
||||
|
||||
|
||||
# filter out notices and comments
|
||||
MEMMAP = [m for m in MEMMAP if isinstance(m, dict)]
|
||||
|
||||
MEMORY = {}
|
||||
|
||||
|
||||
def is_ptr(maybe_ptr):
|
||||
return isinstance(maybe_ptr, str) and maybe_ptr.startswith("0x")
|
||||
|
||||
|
||||
def is_gc_ptr(maybe_ptr):
|
||||
return is_ptr(maybe_ptr) and maybe_ptr.startswith("0x7f")
|
||||
|
||||
|
||||
def ptr_or_shortval(maybe_ptr):
|
||||
if is_ptr(maybe_ptr):
|
||||
return maybe_ptr
|
||||
else:
|
||||
assert isinstance(maybe_ptr, dict), f"maybe_ptr is {type(maybe_ptr)}"
|
||||
assert "shortval" in maybe_ptr, f"maybe_ptr does not have shortval: {maybe_ptr}"
|
||||
return maybe_ptr["shortval"]
|
||||
|
||||
|
||||
def deref_or_shortval(maybe_ptr):
|
||||
if is_ptr(maybe_ptr) and maybe_ptr in MEMORY:
|
||||
return MEMORY[maybe_ptr]
|
||||
else:
|
||||
return ptr_or_shortval(maybe_ptr)
|
||||
|
||||
|
||||
class Item:
|
||||
def __init__(self, item):
|
||||
self.item = item
|
||||
self.backlinks = []
|
||||
self.dict = {}
|
||||
self.visited = False
|
||||
self.type = item["type"]
|
||||
self.ptr = item["ptr"]
|
||||
|
||||
def backlinkify(self):
|
||||
if "children" in self.item:
|
||||
for child in self.item["children"]:
|
||||
key_str = ptr_or_shortval(child["key"])
|
||||
value_deref = deref_or_shortval(child["value"])
|
||||
self.dict[key_str] = value_deref
|
||||
|
||||
for ptr in self.find_pointers():
|
||||
if ptr not in MEMORY:
|
||||
continue
|
||||
MEMORY[ptr].backlinks.append(self)
|
||||
|
||||
def find_pointers(self):
|
||||
if "children" in self.item:
|
||||
for child in self.item["children"]:
|
||||
if is_ptr(child["key"]):
|
||||
yield child["key"]
|
||||
if is_ptr(child["value"]):
|
||||
yield child["value"]
|
||||
|
||||
for k, v in self.item.items():
|
||||
if k in ("ptr", "owner", "children"):
|
||||
continue
|
||||
if not v:
|
||||
continue
|
||||
if isinstance(v, list):
|
||||
yield from (p for p in v if is_ptr(p))
|
||||
if is_ptr(v):
|
||||
yield v
|
||||
|
||||
def __getattr__(self, key):
|
||||
if key not in self.item:
|
||||
raise AttributeError
|
||||
return self.item[key]
|
||||
|
||||
def find_modules(self):
|
||||
return [it for it in self.backlinks if it.type == "module"]
|
||||
|
||||
def name(self):
|
||||
if "__name__" in self.dict:
|
||||
return self.dict["__name__"]
|
||||
|
||||
if "__qualname__" in self.dict:
|
||||
return self.dict["__module__"] + "::" + self.dict["__qualname__"]
|
||||
|
||||
if self.type == "type":
|
||||
return MEMORY[self.item["locals"]].name()
|
||||
|
||||
if self.type == "instance":
|
||||
return MEMORY[self.item["base"]].name() + "()"
|
||||
|
||||
if self.type == "module":
|
||||
return MEMORY[self.item["globals"]].name()
|
||||
|
||||
if self.type == "generator":
|
||||
return MEMORY[self.item["function"]].name()
|
||||
|
||||
for item in self.backlinks:
|
||||
if item.type == "dict":
|
||||
for k, v in item.dict.items():
|
||||
if v == self:
|
||||
return k
|
||||
|
||||
return None
|
||||
|
||||
def ptrval(self):
|
||||
return int(self.ptr[2:], 16)
|
||||
|
||||
|
||||
for item_data in MEMMAP:
|
||||
item = Item(item_data)
|
||||
MEMORY[item.ptr] = item
|
||||
|
||||
for item in MEMORY.values():
|
||||
item.backlinkify()
|
||||
|
||||
|
||||
allobjs = list(MEMORY.values())
|
||||
allobjs.sort(key=lambda x: x.ptr)
|
||||
min_ptr = min(
|
||||
item.ptrval()
|
||||
for item in allobjs
|
||||
if item.ptr != "(nil)" and not item.ptr.startswith("0x5")
|
||||
)
|
||||
max_ptr = max(item.ptrval() for item in allobjs if item.ptr != "(nil)")
|
||||
|
||||
|
||||
types = {
|
||||
"anystr": "S",
|
||||
"strdata": "s",
|
||||
"array": "A",
|
||||
"arrayitems": "a",
|
||||
"closure": "c",
|
||||
"dict": "D",
|
||||
"function": "B",
|
||||
"generator": "G",
|
||||
"instance": "I",
|
||||
"list": "L",
|
||||
"listitems": "l",
|
||||
"mapitems": "m",
|
||||
"method": "C",
|
||||
"module": "M",
|
||||
"object": "o",
|
||||
"set": "E",
|
||||
"setitems": "e",
|
||||
"staticmethod": "C",
|
||||
"trezor": "t",
|
||||
"tuple": "T",
|
||||
"type": "y",
|
||||
"unknown": "h",
|
||||
"trezor-webusb": "t",
|
||||
"trezor-vcp": "t",
|
||||
"trezor-hid": "t",
|
||||
"rawbuffer": "R",
|
||||
"qstrpool": "Q",
|
||||
"qstrdata": "q",
|
||||
}
|
||||
|
||||
pixels_per_line = len(
|
||||
"................................................................"
|
||||
)
|
||||
pixelsize = 0x800 // pixels_per_line
|
||||
maxline = ((max_ptr - min_ptr) & ~0x7FF) + (0x800 * 2)
|
||||
pixelmap = [None] * (maxline // pixelsize)
|
||||
|
||||
|
||||
def pixel_index(ptrval):
|
||||
ptridx = ptrval - min_ptr
|
||||
# assert ptridx >= 0
|
||||
return ptridx // pixelsize
|
||||
|
||||
|
||||
for item in MEMORY.values():
|
||||
if item.alloc == 0:
|
||||
continue
|
||||
if item.ptr.startswith("0x5"):
|
||||
continue
|
||||
ptridx = pixel_index(item.ptrval())
|
||||
assert ptridx >= 0, item.item
|
||||
for i in range(ptridx, ptridx + item.alloc):
|
||||
pixelmap[i] = item
|
||||
|
||||
for item in MEMORY.values():
|
||||
if item.alloc > 0:
|
||||
continue
|
||||
if item.ptr == "(nil)" or item.ptr.startswith("0x5"):
|
||||
continue
|
||||
ptridx = pixel_index(item.ptrval())
|
||||
if ptridx < 0:
|
||||
continue
|
||||
for i in range(ptridx, ptridx + item.alloc):
|
||||
pixelmap[i] = item
|
||||
|
||||
ctr = 0
|
||||
newline = True
|
||||
previtem = None
|
||||
for pixel in pixelmap:
|
||||
if ctr % pixels_per_line == 0:
|
||||
print()
|
||||
print(f"{ctr * pixelsize:05x}: ", end="")
|
||||
if pixel is None:
|
||||
c = "."
|
||||
elif pixel is previtem:
|
||||
c = "="
|
||||
else:
|
||||
c = types[pixel.type]
|
||||
print(c, end="")
|
||||
ctr += 1
|
||||
previtem = pixel
|
||||
print()
|
||||
|
||||
|
||||
import dominate
|
||||
import dominate.tags as t
|
||||
|
||||
doc = dominate.document(title="memory map")
|
||||
with doc.head:
|
||||
t.meta(charset="utf-8")
|
||||
t.style(
|
||||
"""\
|
||||
span, a {
|
||||
font-family: monospace;
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: darkblue;
|
||||
}
|
||||
|
||||
.entry a:target {
|
||||
display: block;
|
||||
background-color: navy;
|
||||
}
|
||||
|
||||
#memorymap a:target {
|
||||
color: red;
|
||||
}
|
||||
|
||||
span.leadin {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
dl { border-left: 1px solid grey; padding-left: 0.4rem; }
|
||||
dt { font-weight: bold }
|
||||
|
||||
div.
|
||||
"""
|
||||
)
|
||||
|
||||
ctr = 0
|
||||
newline = True
|
||||
previtem = None
|
||||
container = t.div(id="memorymap")
|
||||
doc.add(container)
|
||||
line = t.div()
|
||||
for pixel in pixelmap:
|
||||
if ctr % pixels_per_line == 0:
|
||||
container.add(line)
|
||||
line = t.div()
|
||||
line.add(t.span(f"{ctr * pixelsize:05x}: ", cls="leadin"))
|
||||
if pixel is None:
|
||||
line.add(t.span("."))
|
||||
elif pixel is previtem:
|
||||
line.add(t.a("=", href=f"#{pixel.ptr}"))
|
||||
else:
|
||||
c = types[pixel.type]
|
||||
line.add(t.a(c, href=f"#{pixel.ptr}", name=f"mapentry-{pixel.ptr}"))
|
||||
ctr += 1
|
||||
previtem = pixel
|
||||
|
||||
|
||||
def text_or_ptr(s):
|
||||
if s.startswith("0x7"):
|
||||
sp = t.span()
|
||||
sp.add(t.a(s, href=f"#{s}"))
|
||||
sp.add(" (")
|
||||
sp.add(t.a("M", href=f"#mapentry-{s}"))
|
||||
sp.add(")")
|
||||
return sp
|
||||
else:
|
||||
return t.span(s)
|
||||
|
||||
|
||||
def dump_single_val(value):
|
||||
if isinstance(value, str):
|
||||
return text_or_ptr(value)
|
||||
elif isinstance(value, dict):
|
||||
if value.get("shortval"):
|
||||
return value["shortval"]
|
||||
elif value.get("type") == "romdata":
|
||||
return "romdata"
|
||||
sdl = t.dl()
|
||||
dump_dict(sdl, value)
|
||||
return sdl
|
||||
elif isinstance(value, list):
|
||||
ul = t.ul()
|
||||
for subval in value:
|
||||
ul.add(t.li(dump_single_val(subval)))
|
||||
return ul
|
||||
else:
|
||||
return str(value)
|
||||
|
||||
|
||||
def dump_dict(dl, d):
|
||||
for key, value in d.items():
|
||||
dl.add(t.dt(key))
|
||||
dl.add(t.dd(dump_single_val(value)))
|
||||
|
||||
|
||||
for item in allobjs:
|
||||
div = t.div(cls="entry")
|
||||
div.add(t.a("{", name=item.ptr))
|
||||
dl = t.dl()
|
||||
dl.add(t.dt("Inferred name:"))
|
||||
dl.add(t.dd(str(item.name())))
|
||||
dl.add(t.dt("Backrefs:"))
|
||||
refs = t.dd()
|
||||
for backref in item.backlinks:
|
||||
refs.add(text_or_ptr(backref.ptr))
|
||||
refs.add(", ")
|
||||
dl.add(refs)
|
||||
dump_dict(dl, item.item)
|
||||
div.add(dl)
|
||||
doc.add(div)
|
||||
|
||||
print("Writing to memorymap.html...")
|
||||
with open("memorymap.html", "w") as f:
|
||||
f.write(doc.render(pretty=False))
|
Loading…
Reference in New Issue
Block a user