Skip to content

Commit 5fd168c

Browse files
committed
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]
1 parent 2333a6a commit 5fd168c

File tree

6 files changed

+88
-41
lines changed

6 files changed

+88
-41
lines changed

core/SConscript.firmware

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ ui.init_ui(TREZOR_MODEL, "firmware", RUST_UI_FEATURES)
396396
SOURCE_QSTR = SOURCE_MOD + SOURCE_MICROPYTHON + SOURCE_MICROPYTHON_SPEED
397397

398398
if PYOPT == '0':
399-
DEBUG_FLAGS = "-DMICROPY_OOM_CALLBACK=1"
399+
DEBUG_FLAGS = "-DMICROPY_OOM_CALLBACK=1 -DSTATIC="
400400
else:
401401
DEBUG_FLAGS = "-DMICROPY_OOM_CALLBACK=0"
402402

core/embed/rust/librust.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
#include "librust_qstr.h"
44

5-
#ifdef TREZOR_EMULATOR
5+
#if !PYOPT
66
mp_obj_t protobuf_debug_msg_type();
77
mp_obj_t protobuf_debug_msg_def_type();
88
#endif
@@ -11,6 +11,6 @@ extern mp_obj_module_t mp_module_trezorproto;
1111
extern mp_obj_module_t mp_module_trezorui_api;
1212
extern mp_obj_module_t mp_module_trezortranslate;
1313

14-
#ifdef TREZOR_EMULATOR
14+
#if !PYOPT
1515
mp_obj_t ui_debug_layout_type();
1616
#endif

core/embed/upymod/modtrezorutils/modtrezorutils-meminfo.h

Lines changed: 68 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
* along with this program. If not, see <http://www.gnu.org/licenses/>.
1818
*/
1919

20-
#if !TREZOR_EMULATOR || PYOPT
20+
#if PYOPT
2121
#define MEMINFO_DICT_ENTRIES /* empty */
2222

2323
#else
@@ -38,6 +38,14 @@
3838
#include "embed/rust/librust.h"
3939
#include "embed/upymod/trezorobj.h"
4040

41+
#if !TREZOR_EMULATOR
42+
#define fopen(path, mode) &mp_plat_print
43+
#define fprintf mp_printf
44+
#define fflush(f)
45+
#define fclose(f)
46+
#define FILE const mp_print_t
47+
#endif
48+
4149
#define WORDS_PER_BLOCK ((MICROPY_BYTES_PER_GC_BLOCK) / MP_BYTES_PER_OBJ_WORD)
4250
#define BYTES_PER_BLOCK (MICROPY_BYTES_PER_GC_BLOCK)
4351

@@ -149,24 +157,38 @@ bool is_short(mp_const_obj_t value) {
149157
mp_obj_is_small_int(value) || !VERIFY_PTR(value);
150158
}
151159

160+
static void escape_and_dump_string(FILE *out, const char *unescaped) {
161+
fprintf(out, "\"");
162+
for (; *unescaped; ++unescaped) {
163+
char c = *unescaped;
164+
if (c == '\n') {
165+
fprintf(out, "\\n");
166+
} else if (c == '\r') {
167+
fprintf(out, "\\r");
168+
} else if (c == '\"') {
169+
fprintf(out, "\\\"");
170+
} else if (c == '\\') {
171+
fprintf(out, "\\\\");
172+
} else if (c >= 0x20 && c < 0x7F) {
173+
fprintf(out, "%c", c);
174+
} else {
175+
fprintf(out, "\\u%04x", c);
176+
}
177+
}
178+
fprintf(out, "\"");
179+
}
180+
152181
static void print_type(FILE *out, const char *typename, const char *shortval,
153182
const void *ptr, bool end) {
154-
static char unescaped[1000];
155183
size_t size = 0;
156184
if (!is_short(ptr)) {
157185
size = find_allocated_size(ptr);
158186
}
159187
fprintf(out, "{\"type\": \"%s\", \"alloc\": %ld, \"ptr\": \"%p\"", typename,
160188
size, ptr);
161189
if (shortval) {
162-
assert(strlen(shortval) < 1000);
163-
char *c = unescaped;
164-
while (*shortval) {
165-
if (*shortval == '\\' || *shortval == '"') *c++ = '\\';
166-
*c++ = *shortval++;
167-
}
168-
*c = 0;
169-
fprintf(out, ", \"shortval\": \"%s\"", unescaped);
190+
fprintf(out, ", \"shortval\": ");
191+
escape_and_dump_string(out, shortval);
170192
} else {
171193
fprintf(out, ", \"shortval\": null");
172194
}
@@ -199,7 +221,7 @@ void dump_short(FILE *out, mp_const_obj_t value) {
199221

200222
} else if (mp_obj_is_small_int(value)) {
201223
static char num_buf[100];
202-
snprintf(num_buf, 100, "%ld", MP_OBJ_SMALL_INT_VALUE(value));
224+
snprintf(num_buf, 100, INT_FMT, MP_OBJ_SMALL_INT_VALUE(value));
203225
print_type(out, "smallint", num_buf, NULL, true);
204226

205227
} else if (!VERIFY_PTR(value)) {
@@ -680,10 +702,11 @@ void dump_qstr_pool(FILE *out, const qstr_pool_t *pool) {
680702
for (const char *const *q = pool->qstrs, *const *q_top =
681703
pool->qstrs + pool->len;
682704
q < q_top; q++) {
705+
escape_and_dump_string(out, Q_GET_DATA(*q));
683706
if (q < (q_top - 1))
684-
fprintf(out, "\"%s\",\n", Q_GET_DATA(*q));
707+
fprintf(out, ",\n");
685708
else
686-
fprintf(out, "\"%s\"]\n", Q_GET_DATA(*q));
709+
fprintf(out, "]\n");
687710
}
688711
fprintf(out, "},\n");
689712
for (const char *const *q = pool->qstrs, *const *q_top =
@@ -709,15 +732,19 @@ void dump_qstrdata(FILE *out) {
709732
}
710733
}
711734

712-
/// def meminfo(filename: str) -> None:
713-
/// """Dumps map of micropython GC arena to a file.
714-
/// The JSON file can be decoded by analyze-memory-dump.py
715-
/// Only available in the emulator.
716-
/// """
717-
STATIC mp_obj_t mod_trezorutils_meminfo(mp_obj_t filename) {
718-
size_t fn_len;
719-
FILE *out = fopen(mp_obj_str_get_data(filename, &fn_len), "w");
720-
fprintf(out, "[");
735+
static void dump_meminfo_json(FILE *out) {
736+
bool should_close = true;
737+
if (out == NULL) {
738+
should_close = false;
739+
#if TREZOR_EMULATOR
740+
out = stdout;
741+
#else
742+
out = &mp_plat_print;
743+
#endif
744+
}
745+
fprintf(out, "\n[\n[" UINT_FMT ", " UINT_FMT ", " UINT_FMT "],\n",
746+
(mp_uint_t)MP_STATE_MEM(gc_pool_start),
747+
(mp_uint_t)MP_STATE_MEM(gc_pool_end), BYTES_PER_BLOCK);
721748

722749
// void **ptrs = (void **)(void *)&mp_state_ctx;
723750
// 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) {
768795
pool = pool->prev;
769796
}
770797

771-
fprintf(out, "null]\n");
772-
fclose(out);
798+
fprintf(out, "null\n]\n");
799+
if (should_close) {
800+
fclose(out);
801+
} else {
802+
fflush(out);
803+
}
773804
for (size_t block = 0;
774805
block < MP_STATE_MEM(gc_alloc_table_byte_len) * BLOCKS_PER_ATB;
775806
block++) {
@@ -779,6 +810,19 @@ STATIC mp_obj_t mod_trezorutils_meminfo(mp_obj_t filename) {
779810
}
780811

781812
gc_dump_alloc_table();
813+
}
814+
815+
/// def meminfo(filename: str | None) -> None:
816+
/// """Dumps map of micropython GC arena to a file.
817+
/// The JSON file can be decoded by analyze-memory-dump.py
818+
/// """
819+
STATIC mp_obj_t mod_trezorutils_meminfo(mp_obj_t filename) {
820+
size_t fn_len;
821+
FILE *out = (filename == mp_const_none)
822+
? NULL
823+
: fopen(mp_obj_str_get_data(filename, &fn_len), "w");
824+
(void)fn_len;
825+
dump_meminfo_json(out);
782826
return mp_const_none;
783827
}
784828
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorutils_meminfo_obj,

core/embed/upymod/modtrezorutils/modtrezorutils.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,9 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_trezorutils_estimate_unused_stack_obj,
284284
#if MICROPY_OOM_CALLBACK
285285
static void gc_oom_callback(void) {
286286
gc_dump_info();
287+
#if BLOCK_ON_VCP || TREZOR_EMULATOR
288+
dump_meminfo_json(NULL); // dump to stdout
289+
#endif
287290
}
288291

289292
/// if __debug__:

core/mocks/generated/trezorutils.pyi

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ from typing import *
22

33

44
# upymod/modtrezorutils/modtrezorutils-meminfo.h
5-
def meminfo(filename: str) -> None:
5+
def meminfo(filename: str | None) -> None:
66
"""Dumps map of micropython GC arena to a file.
77
The JSON file can be decoded by analyze-memory-dump.py
8-
Only available in the emulator.
98
"""
109

1110

core/tools/analyze-memory-dump.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030

3131

3232
with open(sys.argv[1]) as f:
33-
MEMMAP = json.load(f)
33+
MEMMAP = iter(json.load(f))
34+
(min_ptr, max_ptr, bytes_per_block) = next(MEMMAP)
3435

3536

3637
# filter out notices and comments
@@ -57,7 +58,13 @@ def ptr_or_shortval(maybe_ptr):
5758

5859

5960
def is_ignored_ptr(ptr):
60-
return (ptr == "(nil)" or ptr.startswith("0x5") or ptr.startswith("0x6"))
61+
if ptr == "(nil)":
62+
return True
63+
64+
if isinstance(ptr, str):
65+
ptr = int(ptr, 16)
66+
67+
return not (min_ptr <= ptr < max_ptr)
6168

6269

6370
def deref_or_shortval(maybe_ptr):
@@ -155,13 +162,6 @@ def ptrval(self):
155162

156163
allobjs = list(MEMORY.values())
157164
allobjs.sort(key=lambda x: x.ptr)
158-
min_ptr = min(
159-
item.ptrval()
160-
for item in allobjs
161-
if not is_ignored_ptr(item.ptr)
162-
)
163-
max_ptr = max(item.ptrval() for item in allobjs if item.ptr != "(nil)")
164-
165165

166166
types = {
167167
"anystr": "S",
@@ -201,9 +201,10 @@ def ptrval(self):
201201
pixels_per_line = len(
202202
"................................................................"
203203
)
204-
pixelsize = 0x800 // pixels_per_line
205-
maxline = ((max_ptr - min_ptr) & ~0x7FF) + (0x800 * 2)
206-
pixelmap = [None] * (maxline // pixelsize)
204+
pixelsize = bytes_per_block
205+
bytes_per_line = bytes_per_block * pixels_per_line
206+
maxline = ((max_ptr - min_ptr) & ~(bytes_per_line - 1)) + (bytes_per_line * 2)
207+
pixelmap = [None] * 2*(maxline // pixelsize)
207208

208209

209210
def pixel_index(ptrval):

0 commit comments

Comments
 (0)