1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-03-21 10:35:43 +00:00

feat(core): dump GC arena on OOM

Enabled for debug firmware and non-frozen emulator.

JSON dump can be extracted from debug log and analyzed using:

$ awk '/^\[$/,/^\]$/' <debug.log >dump.json
$ core/tools/analyze-memory-dump.py dump.json

[no changelog]
This commit is contained in:
Roman Zeyde 2025-03-16 12:48:46 +02:00
parent 2333a6a262
commit 5fd168c363
6 changed files with 88 additions and 41 deletions

View File

@ -396,7 +396,7 @@ ui.init_ui(TREZOR_MODEL, "firmware", RUST_UI_FEATURES)
SOURCE_QSTR = SOURCE_MOD + SOURCE_MICROPYTHON + SOURCE_MICROPYTHON_SPEED
if PYOPT == '0':
DEBUG_FLAGS = "-DMICROPY_OOM_CALLBACK=1"
DEBUG_FLAGS = "-DMICROPY_OOM_CALLBACK=1 -DSTATIC="
else:
DEBUG_FLAGS = "-DMICROPY_OOM_CALLBACK=0"

View File

@ -2,7 +2,7 @@
#include "librust_qstr.h"
#ifdef TREZOR_EMULATOR
#if !PYOPT
mp_obj_t protobuf_debug_msg_type();
mp_obj_t protobuf_debug_msg_def_type();
#endif
@ -11,6 +11,6 @@ extern mp_obj_module_t mp_module_trezorproto;
extern mp_obj_module_t mp_module_trezorui_api;
extern mp_obj_module_t mp_module_trezortranslate;
#ifdef TREZOR_EMULATOR
#if !PYOPT
mp_obj_t ui_debug_layout_type();
#endif

View File

@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#if !TREZOR_EMULATOR || PYOPT
#if PYOPT
#define MEMINFO_DICT_ENTRIES /* empty */
#else
@ -38,6 +38,14 @@
#include "embed/rust/librust.h"
#include "embed/upymod/trezorobj.h"
#if !TREZOR_EMULATOR
#define fopen(path, mode) &mp_plat_print
#define fprintf mp_printf
#define fflush(f)
#define fclose(f)
#define FILE const mp_print_t
#endif
#define WORDS_PER_BLOCK ((MICROPY_BYTES_PER_GC_BLOCK) / MP_BYTES_PER_OBJ_WORD)
#define BYTES_PER_BLOCK (MICROPY_BYTES_PER_GC_BLOCK)
@ -149,9 +157,29 @@ bool is_short(mp_const_obj_t value) {
mp_obj_is_small_int(value) || !VERIFY_PTR(value);
}
static void escape_and_dump_string(FILE *out, const char *unescaped) {
fprintf(out, "\"");
for (; *unescaped; ++unescaped) {
char c = *unescaped;
if (c == '\n') {
fprintf(out, "\\n");
} else if (c == '\r') {
fprintf(out, "\\r");
} else if (c == '\"') {
fprintf(out, "\\\"");
} else if (c == '\\') {
fprintf(out, "\\\\");
} else if (c >= 0x20 && c < 0x7F) {
fprintf(out, "%c", c);
} else {
fprintf(out, "\\u%04x", c);
}
}
fprintf(out, "\"");
}
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);
@ -159,14 +187,8 @@ static void print_type(FILE *out, const char *typename, const char *shortval,
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);
fprintf(out, ", \"shortval\": ");
escape_and_dump_string(out, shortval);
} else {
fprintf(out, ", \"shortval\": null");
}
@ -199,7 +221,7 @@ void dump_short(FILE *out, mp_const_obj_t value) {
} else if (mp_obj_is_small_int(value)) {
static char num_buf[100];
snprintf(num_buf, 100, "%ld", MP_OBJ_SMALL_INT_VALUE(value));
snprintf(num_buf, 100, INT_FMT, MP_OBJ_SMALL_INT_VALUE(value));
print_type(out, "smallint", num_buf, NULL, true);
} else if (!VERIFY_PTR(value)) {
@ -680,10 +702,11 @@ void dump_qstr_pool(FILE *out, const qstr_pool_t *pool) {
for (const char *const *q = pool->qstrs, *const *q_top =
pool->qstrs + pool->len;
q < q_top; q++) {
escape_and_dump_string(out, Q_GET_DATA(*q));
if (q < (q_top - 1))
fprintf(out, "\"%s\",\n", Q_GET_DATA(*q));
fprintf(out, ",\n");
else
fprintf(out, "\"%s\"]\n", Q_GET_DATA(*q));
fprintf(out, "]\n");
}
fprintf(out, "},\n");
for (const char *const *q = pool->qstrs, *const *q_top =
@ -709,15 +732,19 @@ void dump_qstrdata(FILE *out) {
}
}
/// def meminfo(filename: str) -> None:
/// """Dumps map of micropython GC arena to a file.
/// The JSON file can be decoded by analyze-memory-dump.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, "[");
static void dump_meminfo_json(FILE *out) {
bool should_close = true;
if (out == NULL) {
should_close = false;
#if TREZOR_EMULATOR
out = stdout;
#else
out = &mp_plat_print;
#endif
}
fprintf(out, "\n[\n[" UINT_FMT ", " UINT_FMT ", " UINT_FMT "],\n",
(mp_uint_t)MP_STATE_MEM(gc_pool_start),
(mp_uint_t)MP_STATE_MEM(gc_pool_end), BYTES_PER_BLOCK);
// void **ptrs = (void **)(void *)&mp_state_ctx;
// size_t root_start = offsetof(mp_state_ctx_t, thread.dict_locals);
@ -768,8 +795,12 @@ STATIC mp_obj_t mod_trezorutils_meminfo(mp_obj_t filename) {
pool = pool->prev;
}
fprintf(out, "null]\n");
fclose(out);
fprintf(out, "null\n]\n");
if (should_close) {
fclose(out);
} else {
fflush(out);
}
for (size_t block = 0;
block < MP_STATE_MEM(gc_alloc_table_byte_len) * BLOCKS_PER_ATB;
block++) {
@ -779,6 +810,19 @@ STATIC mp_obj_t mod_trezorutils_meminfo(mp_obj_t filename) {
}
gc_dump_alloc_table();
}
/// def meminfo(filename: str | None) -> None:
/// """Dumps map of micropython GC arena to a file.
/// The JSON file can be decoded by analyze-memory-dump.py
/// """
STATIC mp_obj_t mod_trezorutils_meminfo(mp_obj_t filename) {
size_t fn_len;
FILE *out = (filename == mp_const_none)
? NULL
: fopen(mp_obj_str_get_data(filename, &fn_len), "w");
(void)fn_len;
dump_meminfo_json(out);
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorutils_meminfo_obj,

View File

@ -284,6 +284,9 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorutils_estimate_unused_stack_obj,
#if MICROPY_OOM_CALLBACK
static void gc_oom_callback(void) {
gc_dump_info();
#if BLOCK_ON_VCP || TREZOR_EMULATOR
dump_meminfo_json(NULL); // dump to stdout
#endif
}
/// if __debug__:

View File

@ -2,10 +2,9 @@ from typing import *
# upymod/modtrezorutils/modtrezorutils-meminfo.h
def meminfo(filename: str) -> None:
def meminfo(filename: str | None) -> None:
"""Dumps map of micropython GC arena to a file.
The JSON file can be decoded by analyze-memory-dump.py
Only available in the emulator.
"""

View File

@ -30,7 +30,8 @@ Generators and closures are painful :(
with open(sys.argv[1]) as f:
MEMMAP = json.load(f)
MEMMAP = iter(json.load(f))
(min_ptr, max_ptr, bytes_per_block) = next(MEMMAP)
# filter out notices and comments
@ -57,7 +58,13 @@ def ptr_or_shortval(maybe_ptr):
def is_ignored_ptr(ptr):
return (ptr == "(nil)" or ptr.startswith("0x5") or ptr.startswith("0x6"))
if ptr == "(nil)":
return True
if isinstance(ptr, str):
ptr = int(ptr, 16)
return not (min_ptr <= ptr < max_ptr)
def deref_or_shortval(maybe_ptr):
@ -155,13 +162,6 @@ for item in MEMORY.values():
allobjs = list(MEMORY.values())
allobjs.sort(key=lambda x: x.ptr)
min_ptr = min(
item.ptrval()
for item in allobjs
if not is_ignored_ptr(item.ptr)
)
max_ptr = max(item.ptrval() for item in allobjs if item.ptr != "(nil)")
types = {
"anystr": "S",
@ -201,9 +201,10 @@ types = {
pixels_per_line = len(
"................................................................"
)
pixelsize = 0x800 // pixels_per_line
maxline = ((max_ptr - min_ptr) & ~0x7FF) + (0x800 * 2)
pixelmap = [None] * (maxline // pixelsize)
pixelsize = bytes_per_block
bytes_per_line = bytes_per_block * pixels_per_line
maxline = ((max_ptr - min_ptr) & ~(bytes_per_line - 1)) + (bytes_per_line * 2)
pixelmap = [None] * 2*(maxline // pixelsize)
def pixel_index(ptrval):