summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorScott Shawcroft <scott.shawcroft@gmail.com>2018-01-23 16:22:43 -0800
committerScott Shawcroft <scott.shawcroft@gmail.com>2018-01-24 10:33:46 -0800
commitda330f0cab09c9150b588ee0c1cb4e09edaee9fc (patch)
treecd77f8e8e2620d6d214dcd053a045d7ad988ae5b
parent416abe33ed44e44d8f75342731080217e123278b (diff)
Polish up the heap analysis script and make it more CLI friendly.
It can now render the heap layout over a sequence of ram dumps. The mpy analysis is also better at parsing mpy files.
-rw-r--r--tools/analyze_heap_dump.py843
-rw-r--r--tools/analyze_mpy.py315
-rw-r--r--tools/gc_activity.md2
-rw-r--r--tools/output_gc_until_repl.txt5
4 files changed, 767 insertions, 398 deletions
diff --git a/tools/analyze_heap_dump.py b/tools/analyze_heap_dump.py
index 31b66b9df..d5da884e4 100644
--- a/tools/analyze_heap_dump.py
+++ b/tools/analyze_heap_dump.py
@@ -9,6 +9,10 @@ import sys
import pygraphviz as pgv
import io
import html
+import os.path
+import string
+
+import click
from analyze_mpy import Prelude
@@ -32,176 +36,211 @@ READLINE_HIST_SIZE = 8
SKIP_SYMBOLS = [".debug_ranges", ".debug_frame", ".debug_loc", ".comment", ".debug_str", ".debug_line", ".debug_abbrev", ".debug_info", "COMMON"]
-ownership_graph = pgv.AGraph(directed=True)
-
-with open(sys.argv[1], "rb") as f:
- ram = f.read()
-
-with open(sys.argv[2], "rb") as f:
- rom = f.read()
-
-symbols = {} # name -> address, size
-symbol_lookup = {} # address -> name
-manual_symbol_map = {} # autoname -> name
-
-def add_symbol(name, address=None, size=None):
- global symbols
- if address:
- address = int(address, 0)
- if size:
- size = int(size, 0)
- if name in symbols:
- if address and symbols[name][0] and symbols[name][0] != address:
- print("Conflicting symbol: {}".format(name))
- return
- if not address:
- address = symbols[name][0]
- if not size:
- size = symbols[name][1]
- symbols[name] = (address, size)
- if address:
- if not size:
- size = 4
- for offset in range(0, size, 4):
- symbol_lookup[address + offset] = "{}+{}".format(name, offset)
-
-with open(sys.argv[3], "r") as f:
- common_symbols = False
- name = None
- for line in f:
- line = line.strip()
- parts = line.split()
- if line.startswith("Common symbol"):
- common_symbols = True
- if line == "Discarded input sections":
- common_symbols = False
- if common_symbols:
- if len(parts) == 1:
- name = parts[0]
- elif len(parts) == 2 and name:
- add_symbol(name, size=parts[0])
- name = None
- elif len(parts) == 3:
- add_symbol(parts[0], size=parts[1])
- name = None
- else:
- if len(parts) == 2 and parts[0].startswith("0x") and not parts[1].startswith("0x"):
- add_symbol(parts[1], parts[0])
- if len(parts) == 4 and parts[0] not in SKIP_SYMBOLS and parts[1].startswith("0x") and parts[2].startswith("0x"):
- name, address, size, source = parts
- if name.startswith((".text", ".rodata", ".bss")) and name.count(".") > 1:
- name = name.split(".")[-1]
- add_symbol(name, address, size)
- # Linker symbols
- if len(parts) >= 4 and parts[0].startswith("0x") and parts[2] == "=" and parts[1] != ".":
- add_symbol(parts[1], parts[0])
-
-rom_start = symbols["_sfixed"][0]
-ram_start = symbols["_srelocate"][0]
-
-def load(address, size=4):
- if size is None:
- raise ValueError("You must provide a size")
- if address > ram_start:
- ram_address = address - ram_start
- if (ram_address + size) > len(ram):
- raise ValueError("Unable to read 0x{:08x} from ram.".format(address))
- return ram[ram_address:ram_address+size]
- elif address < len(rom):
- if (address + size) > len(rom):
- raise ValueError("Unable to read 0x{:08x} from rom.".format(address))
- return rom[address:address+size]
-
-def load_pointer(address):
- return struct.unpack("<I", load(address))[0]
-
-heap_start, heap_size = symbols["heap"]
-heap = load(heap_start, heap_size)
-total_byte_len = len(heap)
-
-# These change every run so we load them from the symbol table
-mp_state_ctx = symbols["mp_state_ctx"][0]
-manual_symbol_map["mp_state_ctx+24"] = "mp_state_ctx.vm.last_pool"
-last_pool = load_pointer(mp_state_ctx + 24) # (gdb) p &mp_state_ctx.vm.last_pool
-manual_symbol_map["mp_state_ctx+92"] = "mp_state_ctx.vm.dict_main.map.table"
-dict_main_table = load_pointer(mp_state_ctx + 92) # (gdb) p &mp_state_ctx.vm.dict_main.map.table
-manual_symbol_map["mp_state_ctx+72"] = "mp_state_ctx.vm.mp_loaded_modules_dict.map.table"
-imports_table = load_pointer(mp_state_ctx + 72) # (gdb) p &mp_state_ctx.vm.mp_loaded_modules_dict.map.table
-
-manual_symbol_map["mp_state_ctx+108"] = "mp_state_ctx.vm.mp_sys_path_obj.items"
-manual_symbol_map["mp_state_ctx+124"] = "mp_state_ctx.vm.mp_sys_argv_obj.items"
-
-for i in range(READLINE_HIST_SIZE):
- manual_symbol_map["mp_state_ctx+{}".format(128 + i * 4)] = "mp_state_ctx.vm.readline_hist[{}]".format(i)
-
-tuple_type = symbols["mp_type_tuple"][0]
-type_type = symbols["mp_type_type"][0]
-map_type = symbols["mp_type_map"][0]
-dict_type = symbols["mp_type_dict"][0]
-property_type = symbols["mp_type_property"][0]
-str_type = symbols["mp_type_str"][0]
-function_types = [symbols["mp_type_fun_" + x][0] for x in ["bc", "builtin_0", "builtin_1", "builtin_2", "builtin_3", "builtin_var"]]
-bytearray_type = symbols["mp_type_bytearray"][0]
-
-dynamic_type = 0x40000000 # placeholder, doesn't match any memory
-
-type_colors = {
- dict_type: "red",
- property_type: "yellow",
- map_type: "blue",
- type_type: "orange",
- tuple_type: "skyblue",
- str_type: "pink",
- bytearray_type: "purple"
- }
-
-pool_shift = heap_start % BYTES_PER_BLOCK
-
-print("Total heap length:", total_byte_len)
-atb_length = total_byte_len * BITS_PER_BYTE // (BITS_PER_BYTE + BITS_PER_BYTE * BLOCKS_PER_ATB // BLOCKS_PER_FTB + BITS_PER_BYTE * BLOCKS_PER_ATB * BYTES_PER_BLOCK)
-
-print("ATB length:", atb_length)
-pool_length = atb_length * BLOCKS_PER_ATB * BYTES_PER_BLOCK
-print("Total allocatable:", pool_length)
-
-gc_finaliser_table_byte_len = (atb_length * BLOCKS_PER_ATB + BLOCKS_PER_FTB - 1) // BLOCKS_PER_FTB
-print("FTB length:", gc_finaliser_table_byte_len)
-
-pool_start = heap_start + total_byte_len - pool_length - pool_shift
-pool = heap[-pool_length-pool_shift:]
-
-map_element_blocks = [dict_main_table, imports_table]
-string_blocks = []
-bytecode_blocks = []
-qstr_pools = []
-qstr_chunks = []
-block_data = {}
-
-# Find all the qtr pool addresses.
-prev_pool = last_pool
-while prev_pool > ram_start:
- qstr_pools.append(prev_pool)
- prev_pool = load_pointer(prev_pool)
-
-longest_free = 0
-current_free = 0
-current_allocation = 0
-total_free = 0
-for i in range(atb_length):
- # Each atb byte is four blocks worth of info
- atb = heap[i]
- for j in range(4):
- block_state = (atb >> (j * 2)) & 0x3
- if block_state != AT_FREE and current_free > 0:
- print("{} bytes free".format(current_free * BYTES_PER_BLOCK))
- current_free = 0
- if block_state != AT_TAIL and current_allocation > 0:
+@click.command()
+@click.argument("ram_filename")
+@click.argument("bin_filename")
+@click.argument("map_filename")
+@click.option("--print_block_contents", default=False,
+ help="Prints the contents of each allocated block")
+@click.option("--print_unknown_types", default=False,
+ help="Prints the micropython base type if we don't understand it.")
+@click.option("--print_block_state", default=False,
+ help="Prints the heap block states (allocated or free)")
+@click.option("--print_conflicting_symbols", default=False,
+ help="Prints conflicting symbols from the map")
+@click.option("--print-heap-structure/--no-print-heap-structure", default=False,
+ help="Print heap structure")
+@click.option("--output_directory", default="heapvis",
+ help="Destination for rendered output")
+@click.option("--draw-heap-layout/--no-draw-heap-layout", default=True,
+ help="Draw the heap layout")
+@click.option("--draw-heap-ownership/--no-draw-heap-ownership", default=False,
+ help="Draw the ownership graph of blocks on the heap")
+@click.option("--draw-heap-ownership/--no-draw-heap-ownership", default=False,
+ help="Draw the ownership graph of blocks on the heap")
+@click.option("--analyze-snapshots", default="last", type=click.Choice(['all', 'last']))
+def do_all_the_things(ram_filename, bin_filename, map_filename, print_block_contents,
+ print_unknown_types, print_block_state, print_conflicting_symbols,
+ print_heap_structure, output_directory, draw_heap_layout,
+ draw_heap_ownership, analyze_snapshots):
+ with open(ram_filename, "rb") as f:
+ ram_dump = f.read()
+
+ with open(bin_filename, "rb") as f:
+ rom = f.read()
+
+ symbols = {} # name -> address, size
+ symbol_lookup = {} # address -> name
+ manual_symbol_map = {} # autoname -> name
+
+ def add_symbol(name, address=None, size=None):
+ if "lto_priv" in name:
+ name = name.split(".")[0]
+ if address:
+ address = int(address, 0)
+ if size:
+ size = int(size, 0)
+ if name in symbols:
+ if address and symbols[name][0] and symbols[name][0] != address:
+ if print_conflicting_symbols:
+ print("Conflicting symbol: {} at addresses 0x{:08x} and 0x{:08x}".format(name, address, symbols[name][0]))
+ return
+ if not address:
+ address = symbols[name][0]
+ if not size:
+ size = symbols[name][1]
+ symbols[name] = (address, size)
+ if address:
+ if not size:
+ size = 4
+ for offset in range(0, size, 4):
+ symbol_lookup[address + offset] = "{}+{}".format(name, offset)
+
+ with open(map_filename, "r") as f:
+ common_symbols = False
+ name = None
+ for line in f:
+ line = line.strip()
+ parts = line.split()
+ if line.startswith("Common symbol"):
+ common_symbols = True
+ if line == "Discarded input sections":
+ common_symbols = False
+ if common_symbols:
+ if len(parts) == 1:
+ name = parts[0]
+ elif len(parts) == 2 and name:
+ add_symbol(name, size=parts[0])
+ name = None
+ elif len(parts) == 3:
+ add_symbol(parts[0], size=parts[1])
+ name = None
+ else:
+ if len(parts) == 1 and parts[0].startswith((".text", ".rodata", ".bss")) and parts[0].count(".") > 1 and not parts[0].isnumeric() and ".str" not in parts[0]:
+ name = parts[0].split(".")[2]
+ if len(parts) == 3 and parts[0].startswith("0x") and parts[1].startswith("0x") and name:
+ add_symbol(name, parts[0], parts[1])
+ name = None
+ if len(parts) == 2 and parts[0].startswith("0x") and not parts[1].startswith("0x"):
+ add_symbol(parts[1], parts[0])
+ if len(parts) == 4 and parts[0] not in SKIP_SYMBOLS and parts[1].startswith("0x") and parts[2].startswith("0x"):
+ name, address, size, source = parts
+ if name.startswith((".text", ".rodata", ".bss")) and name.count(".") > 1:
+ name = name.split(".")[-1]
+ add_symbol(name, address, size)
+ name = None
+ # Linker symbols
+ if len(parts) >= 4 and parts[0].startswith("0x") and parts[2] == "=" and parts[1] != ".":
+ add_symbol(parts[1], parts[0])
+
+ rom_start = symbols["_sfixed"][0]
+ ram_start = symbols["_srelocate"][0]
+ ram_end = symbols["_estack"][0]
+ ram_length = ram_end - ram_start
+ if analyze_snapshots == "all":
+ snapshots = range(len(ram_dump) // ram_length - 1, -1, -1)
+ elif analyze_snapshots == "last":
+ snapshots = range(len(ram_dump) // ram_length - 1, len(ram_dump) // ram_length - 2, -1)
+ for snapshot_num in snapshots:
+ ram = ram_dump[ram_length*snapshot_num:ram_length*(snapshot_num + 1)]
+
+ ownership_graph = pgv.AGraph(directed=True)
+ def load(address, size=4):
+ if size is None:
+ raise ValueError("You must provide a size")
+ if address > ram_start:
+ ram_address = address - ram_start
+ if (ram_address + size) > len(ram):
+ raise ValueError("Unable to read 0x{:08x} from ram.".format(address))
+ return ram[ram_address:ram_address+size]
+ elif address < len(rom):
+ if (address + size) > len(rom):
+ raise ValueError("Unable to read 0x{:08x} from rom.".format(address))
+ return rom[address:address+size]
+
+ def load_pointer(address):
+ return struct.unpack("<I", load(address))[0]
+
+ heap_start, heap_size = symbols["heap"]
+ heap = load(heap_start, heap_size)
+ total_byte_len = len(heap)
+
+ # These change every run so we load them from the symbol table
+ mp_state_ctx = symbols["mp_state_ctx"][0]
+ manual_symbol_map["mp_state_ctx+20"] = "mp_state_ctx.vm.last_pool"
+ last_pool = load_pointer(mp_state_ctx + 20) # (gdb) p &mp_state_ctx.vm.last_pool
+ manual_symbol_map["mp_state_ctx+88"] = "mp_state_ctx.vm.dict_main.map.table"
+ dict_main_table = load_pointer(mp_state_ctx + 88) # (gdb) p &mp_state_ctx.vm.dict_main.map.table
+ manual_symbol_map["mp_state_ctx+68"] = "mp_state_ctx.vm.mp_loaded_modules_dict.map.table"
+ imports_table = load_pointer(mp_state_ctx + 68) # (gdb) p &mp_state_ctx.vm.mp_loaded_modules_dict.map.table
+
+ manual_symbol_map["mp_state_ctx+104"] = "mp_state_ctx.vm.mp_sys_path_obj.items"
+ manual_symbol_map["mp_state_ctx+120"] = "mp_state_ctx.vm.mp_sys_argv_obj.items"
+
+ for i in range(READLINE_HIST_SIZE):
+ manual_symbol_map["mp_state_ctx+{}".format(128 + i * 4)] = "mp_state_ctx.vm.readline_hist[{}]".format(i)
+
+ tuple_type = symbols["mp_type_tuple"][0]
+ type_type = symbols["mp_type_type"][0]
+ map_type = symbols["mp_type_map"][0]
+ dict_type = symbols["mp_type_dict"][0]
+ property_type = symbols["mp_type_property"][0]
+ str_type = symbols["mp_type_str"][0]
+ function_types = [symbols["mp_type_fun_" + x][0] for x in ["bc", "builtin_0", "builtin_1", "builtin_2", "builtin_3", "builtin_var"]]
+ bytearray_type = symbols["mp_type_bytearray"][0]
+
+ dynamic_type = 0x40000000 # placeholder, doesn't match any memory
+
+ type_colors = {
+ dict_type: "red",
+ property_type: "yellow",
+ map_type: "blue",
+ type_type: "orange",
+ tuple_type: "skyblue",
+ str_type: "pink",
+ bytearray_type: "purple"
+ }
+
+ pool_shift = heap_start % BYTES_PER_BLOCK
+ atb_length = total_byte_len * BITS_PER_BYTE // (BITS_PER_BYTE + BITS_PER_BYTE * BLOCKS_PER_ATB // BLOCKS_PER_FTB + BITS_PER_BYTE * BLOCKS_PER_ATB * BYTES_PER_BLOCK)
+ pool_length = atb_length * BLOCKS_PER_ATB * BYTES_PER_BLOCK
+ gc_finaliser_table_byte_len = (atb_length * BLOCKS_PER_ATB + BLOCKS_PER_FTB - 1) // BLOCKS_PER_FTB
+
+ if print_heap_structure:
+ print("mp_state_ctx at 0x{:08x} and length {}".format(*symbols["mp_state_ctx"]))
+ print("Total heap length:", total_byte_len)
+ print("ATB length:", atb_length)
+ print("Total allocatable:", pool_length)
+ print("FTB length:", gc_finaliser_table_byte_len)
+
+ pool_start = heap_start + total_byte_len - pool_length - pool_shift
+ pool = heap[-pool_length-pool_shift:]
+
+ total_height = 65 * 18
+ total_width = (pool_length // (64 * 16)) * 90
+
+ map_element_blocks = [dict_main_table, imports_table]
+ string_blocks = []
+ bytecode_blocks = []
+ qstr_pools = []
+ qstr_chunks = []
+ block_data = {}
+
+ # Find all the qtr pool addresses.
+ prev_pool = last_pool
+ while prev_pool > ram_start:
+ qstr_pools.append(prev_pool)
+ prev_pool = load_pointer(prev_pool)
+
+ def save_allocated_block(end, current_allocation):
allocation_length = current_allocation * BYTES_PER_BLOCK
- end = (i * BLOCKS_PER_ATB + j) * BYTES_PER_BLOCK
start = end - allocation_length
address = pool_start + start
data = pool[start:end]
- print("0x{:x} {} bytes allocated".format(address, allocation_length))
+ if print_block_state:
+ print("0x{:x} {} bytes allocated".format(address, allocation_length))
+ if print_block_contents:
+ print(data)
rows = ""
for k in range(current_allocation - 1):
@@ -214,6 +253,7 @@ for i in range(atb_length):
ownership_graph.add_node(address, label=table, style="invisible", shape="plaintext")
potential_type = None
node = ownership_graph.get_node(address)
+ node.attr["height"] = 0.25 * current_allocation
block_data[address] = data
for k in range(len(data) // 4):
word = struct.unpack_from("<I", data, offset=(k * 4))[0]
@@ -226,9 +266,8 @@ for i in range(atb_length):
bgcolor = "green"
elif potential_type in type_colors:
bgcolor = type_colors[potential_type]
- else:
- pass
- #print("unknown type", hex(potential_type))
+ elif print_unknown_types:
+ print("unknown type", hex(potential_type))
node.attr["label"] = "<" + node.attr["label"].replace("\"gray\"", "\"" + bgcolor + "\"") + ">"
if potential_type == str_type and k == 3:
@@ -262,180 +301,294 @@ for i in range(atb_length):
if k == 2 and 0x20000000 < word < 0x20040000:
bytecode_blocks.append(word)
- current_allocation = 0
- if block_state == AT_FREE:
- current_free += 1
- total_free += 1
- elif block_state == AT_HEAD:
- current_allocation = 1
- elif block_state == AT_TAIL:
- current_allocation += 1
- longest_free = max(longest_free, current_free)
-if current_free > 0:
- print("{} bytes free".format(current_free * BYTES_PER_BLOCK))
-
-def is_qstr(obj):
- return obj & 0xff800007 == 0x00000006
-
-def find_qstr(qstr_index):
- pool_ptr = last_pool
- if not is_qstr(qstr_index):
- return "object"
- qstr_index >>= 3
- while pool_ptr != 0:
- #print(hex(pool_ptr))
- if pool_ptr in block_data:
- pool = block_data[pool_ptr]
- prev, total_prev_len, alloc, length = struct.unpack_from("<IIII", pool)
- else:
- rom_offset = pool_ptr - rom_start
- prev, total_prev_len, alloc, length = struct.unpack_from("<IIII", rom[rom_offset:rom_offset+32])
- pool = rom[rom_offset:rom_offset+length*4]
- #print("rom pool")
- #print(hex(prev), total_prev_len, alloc, length)
- #print(qstr_index, total_prev_len)
- if qstr_index >= total_prev_len:
- offset = (qstr_index - total_prev_len) * 4 + 16
- start = struct.unpack_from("<I", pool, offset=offset)[0]
- #print(hex(start))
- if start < heap_start:
- start -= rom_start
- if start > len(rom):
- return "more than rom: {:x}".format(start + rom_start)
- qstr_hash, qstr_len = struct.unpack("<BB", rom[start:start+2])
- return rom[start+2:start+2+qstr_len].decode("utf-8")
+
+ longest_free = 0
+ current_free = 0
+ current_allocation = 0
+ total_free = 0
+ for i in range(atb_length):
+ # Each atb byte is four blocks worth of info
+ atb = heap[i]
+ for j in range(4):
+ block_state = (atb >> (j * 2)) & 0x3
+ if block_state != AT_FREE and current_free > 0:
+ if print_block_state:
+ print("{} bytes free".format(current_free * BYTES_PER_BLOCK))
+ current_free = 0
+ if block_state != AT_TAIL and current_allocation > 0:
+ save_allocated_block((i * BLOCKS_PER_ATB + j) * BYTES_PER_BLOCK, current_allocation)
+ current_allocation = 0
+ if block_state == AT_FREE:
+ current_free += 1
+ total_free += 1
+ elif block_state == AT_HEAD or block_state == AT_MARK:
+ current_allocation = 1
+ elif block_state == AT_TAIL and current_allocation > 0:
+ # In gc_free the logging happens before the tail is freed. So checking
+ # current_allocation > 0 ensures we only extend an allocation thats started.
+ current_allocation += 1
+ longest_free = max(longest_free, current_free)
+ #if current_free > 0:
+ # print("{} bytes free".format(current_free * BYTES_PER_BLOCK))
+ if current_allocation > 0:
+ save_allocated_block(pool_length, current_allocation)
+
+ def is_qstr(obj):
+ return obj & 0xff800007 == 0x00000006
+
+ def find_qstr(qstr_index):
+ pool_ptr = last_pool
+ if not is_qstr(qstr_index):
+ return "object"
+ qstr_index >>= 3
+ while pool_ptr != 0:
+ if pool_ptr > ram_start:
+ if pool_ptr in block_data:
+ pool = block_data[pool_ptr]
+ prev, total_prev_len, alloc, length = struct.unpack_from("<IIII", pool)
+ else:
+ print("missing qstr pool: {:08x}".format(pool_ptr))
+ return "missing"
+ else:
+ rom_offset = pool_ptr - rom_start
+ prev, total_prev_len, alloc, length = struct.unpack_from("<IIII", rom[rom_offset:rom_offset+32])
+ pool = rom[rom_offset:rom_offset+length*4]
+
+ if qstr_index >= total_prev_len:
+ offset = (qstr_index - total_prev_len) * 4 + 16
+ start = struct.unpack_from("<I", pool, offset=offset)[0]
+ if start < heap_start:
+ start -= rom_start
+ if start > len(rom):
+ return "more than rom: {:x}".format(start + rom_start)
+ qstr_hash, qstr_len = struct.unpack("<BB", rom[start:start+2])
+ return rom[start+2:start+2+qstr_len].decode("utf-8")
+ else:
+ if start > heap_start + len(heap):
+ return "out of range: {:x}".format(start)
+ local = start - heap_start
+ qstr_hash, qstr_len = struct.unpack("<BB", heap[local:local+2])
+ return heap[local+2:local+2+qstr_len].decode("utf-8")
+
+ pool_ptr = prev
+ return "unknown"
+
+ def format(obj):
+ if obj & 1 != 0:
+ return obj >> 1
+ if is_qstr(obj):
+ return find_qstr(obj)
+ else:
+ return "0x{:08x}".format(obj)
+
+ for block in sorted(map_element_blocks):
+ if block == 0:
+ continue
+ try:
+ node = ownership_graph.get_node(block)
+ except KeyError:
+ print("Unable to find memory block for 0x{:08x}. Is there something running?".format(block))
+ continue
+ if block not in block_data:
+ continue
+ data = block_data[block]
+ cells = []
+ for i in range(len(data) // 8):
+ key, value = struct.unpack_from("<II", data, offset=(i * 8))
+ if key == MP_OBJ_NULL or key == MP_OBJ_SENTINEL:
+ cells.append(("", " "))
+ else:
+ cells.append((key, format(key)))
+ if value in block_data:
+ edge = ownership_graph.get_edge(block, value)
+ edge.attr["tailport"] = str(key)
+ rows = ""
+ for i in range(len(cells) // 2):
+ rows += "<tr><td port=\"{}\">{}</td><td port=\"{}\">{}</td></tr>".format(
+ cells[2*i][0],
+ cells[2*i][1],
+ cells[2*i+1][0],
+ cells[2*i+1][1])
+ node.attr["shape"] = "plaintext"
+ node.attr["style"] = "invisible"
+ node.attr["label"] = "<<table bgcolor=\"gold\" border=\"1\" cellpadding=\"0\" cellspacing=\"0\"><tr><td colspan=\"2\">0x{:08x}</td></tr>{}</table>>".format(block, rows)
+
+ for node, degree in ownership_graph.in_degree_iter():
+ if degree == 0:
+ address_bytes = struct.pack("<I", int(node))
+ location = -1
+ for _ in range(ram.count(address_bytes)):
+ location = ram.find(address_bytes, location + 1)
+ pointer_location = ram_start + location
+ source = "0x{:08x}".format(pointer_location)
+ if pointer_location in symbol_lookup:
+ source = symbol_lookup[pointer_location]
+ if source in manual_symbol_map:
+ source = manual_symbol_map[source]
+ if "readline_hist" in source:
+ string_blocks.append(int(node))
+ ownership_graph.add_edge(source, node)
+
+ for block in string_blocks:
+ if block == 0:
+ continue
+ node = ownership_graph.get_node(block)
+ node.attr["fillcolor"] = "hotpink"
+ if block in block_data:
+ raw_string = block_data[block]
else:
- if start > heap_start + len(heap):
- return "out of range: {:x}".format(start)
- local = start - heap_start
- qstr_hash, qstr_len = struct.unpack("<BB", heap[local:local+2])
- return heap[local+2:local+2+qstr_len].decode("utf-8")
-
- pool_ptr = prev
- return "unknown"
-
-def format(obj):
- if obj & 1 != 0:
- return obj >> 1
- if is_qstr(obj):
- return find_qstr(obj)
- else:
- return "0x{:08x}".format(obj)
-
-for block in sorted(map_element_blocks):
- try:
- node = ownership_graph.get_node(block)
- except KeyError:
- print("Unable to find memory block for 0x{:08x}. Is there something running?".format(block))
- continue
- #node.attr["fillcolor"] = "gold"
- data = block_data[block]
- #print("0x{:08x}".format(block))
- cells = []
- for i in range(len(data) // 8):
- key, value = struct.unpack_from("<II", data, offset=(i * 8))
- if key == MP_OBJ_NULL or key == MP_OBJ_SENTINEL:
- #print(" <empty slot>")
- cells.append(("", " "))
- else:
- #print(" {}, {}".format(format(key), format(value)))
- cells.append((key, format(key)))
- if value in block_data:
- edge = ownership_graph.get_edge(block, value)
- edge.attr["tailport"] = str(key)
- rows = ""
- for i in range(len(cells) // 2):
- rows += "<tr><td port=\"{}\">{}</td><td port=\"{}\">{}</td></tr>".format(
- cells[2*i][0],
- cells[2*i][1],
- cells[2*i+1][0],
- cells[2*i+1][1])
- node.attr["shape"] = "plaintext"
- node.attr["style"] = "invisible"
- node.attr["label"] = "<<table bgcolor=\"gold\" border=\"1\" cellpadding=\"0\" cellspacing=\"0\"><tr><td colspan=\"2\">0x{:08x}</td></tr>{}</table>>".format(block, rows)
-
-for node, degree in ownership_graph.in_degree_iter():
- if degree == 0:
- address_bytes = struct.pack("<I", int(node))
- location = -1
- for _ in range(ram.count(address_bytes)):
- location = ram.find(address_bytes, location + 1)
- pointer_location = ram_start + location
- source = "0x{:08x}".format(pointer_location)
- if pointer_location in symbol_lookup:
- source = symbol_lookup[pointer_location]
- if source in manual_symbol_map:
- source = manual_symbol_map[source]
- if "readline_hist" in source:
- string_blocks.append(int(node))
- ownership_graph.add_edge(source, node)
-
-for block in string_blocks:
- node = ownership_graph.get_node(block)
- node.attr["fillcolor"] = "hotpink"
- string = block_data[block].decode('utf-8')
- wrapped = []
- for i in range(0, len(string), 16):
- wrapped.append(string[i:i+16])
- node.attr["label"] = "\n".join(wrapped)
- node.attr["style"] = "filled"
- node.attr["fontname"] = "FiraCode-Medium"
- node.attr["fontpath"] = "/Users/tannewt/Library/Fonts/"
- node.attr["fontsize"] = 8
-
-for block in bytecode_blocks:
- node = ownership_graph.get_node(block)
- node.attr["fillcolor"] = "lightseagreen"
- data = block_data[block]
- prelude = Prelude(io.BufferedReader(io.BytesIO(data)))
- node.attr["shape"] = "plaintext"
- node.attr["style"] = "invisible"
- code_info_size = prelude.code_info_size
- rows = ""
- remaining_bytecode = len(data) - 16
- while code_info_size >= 16:
- rows += "<tr><td colspan=\"16\" bgcolor=\"palegreen\" height=\"18\" width=\"80\"></td></tr>"
- code_info_size -= 16
- remaining_bytecode -= 16
- if code_info_size > 0:
- rows += ("<tr><td colspan=\"{}\" bgcolor=\"palegreen\" height=\"18\" width=\"{}\"></td>"
- "<td colspan=\"{}\" bgcolor=\"seagreen\" height=\"18\" width=\"{}\"></td></tr>"
- ).format(code_info_size, code_info_size * (80 / 16), (16 - code_info_size), (80 / 16) * (16 - code_info_size))
- remaining_bytecode -= 16
- for i in range(remaining_bytecode // 16):
- rows += "<tr><td colspan=\"16\" bgcolor=\"seagreen\" height=\"18\" width=\"80\"></td></tr>"
- node.attr["label"] = "<<table border=\"1\" cellspacing=\"0\"><tr><td colspan=\"16\" bgcolor=\"lightseagreen\" height=\"18\" width=\"80\">0x{:08x}</td></tr>{}</table>>".format(block, rows)
-
-for block in qstr_chunks:
- if block not in block_data:
- ownership_graph.delete_node(block)
- continue
- data = block_data[block]
- string = ""
- offset = 0
- while offset < len(data) - 1:
- qstr_hash, qstr_len = struct.unpack_from("<BB", data, offset=offset)
- if qstr_hash == 0:
- string += " " * (len(data) - offset)
- offset = len(data)
- continue
- offset += 2 + qstr_len + 1
- string += " " + data[offset - qstr_len - 1: offset - 1].decode("utf-8")
- #print(string)
- wrapped = []
- for i in range(0, len(string), 16):
- wrapped.append(html.escape(string[i:i+16]))
- node = ownership_graph.get_node(block)
- node.attr["label"] = "<<table border=\"1\" cellspacing=\"0\" bgcolor=\"lightsalmon\" width=\"80\"><tr><td height=\"18\" >0x{:08x}</td></tr><tr><td height=\"{}\" >{}</td></tr></table>>".format(block, 18 * (len(wrapped) - 1), "<br/>".join(wrapped))
- node.attr["fontname"] = "FiraCode-Medium"
- node.attr["fontpath"] = "/Users/tannewt/Library/Fonts/"
- node.attr["fontsize"] = 8
-
-print("Total free space:", BYTES_PER_BLOCK * total_free)
-print("Longest free space:", BYTES_PER_BLOCK * longest_free)
-
-with open("heap.dot", "w") as f:
- f.write(ownership_graph.string())
-
-ownership_graph.layout(prog="dot")
-ownership_graph.draw("heap.png")
+ print("Unable to find memory block for string at 0x{:08x}.".format(block))
+ continue
+ try:
+ raw_string = block_data[block].decode('utf-8')
+ except:
+ raw_string = str(block_data[block])
+ wrapped = []
+ for i in range(0, len(raw_string), 16):
+ wrapped.append(raw_string[i:i+16])
+ node.attr["label"] = "\n".join(wrapped)
+ node.attr["style"] = "filled"
+ node.attr["fontname"] = "FiraCode-Medium"
+ node.attr["fontpath"] = "/Users/tannewt/Library/Fonts/"
+ node.attr["fontsize"] = 8
+ node.attr["height"] = len(wrapped) * 0.25
+
+ for block in bytecode_blocks:
+ node = ownership_graph.get_node(block)
+ node.attr["fillcolor"] = "lightseagreen"
+ if block in block_data:
+ data = block_data[block]
+ else:
+ print("Unable to find memory block for bytecode at 0x{:08x}.".format(block))
+ continue
+ prelude = Prelude(io.BufferedReader(io.BytesIO(data)))
+ node.attr["shape"] = "plaintext"
+ node.attr["style"] = "invisible"
+ code_info_size = prelude.code_info_size
+ rows = ""
+ remaining_bytecode = len(data) - 16
+ while code_info_size >= 16:
+ rows += "<tr><td colspan=\"16\" bgcolor=\"palegreen\" height=\"18\" width=\"80\"></td></tr>"
+ code_info_size -= 16
+ remaining_bytecode -= 16
+ if code_info_size > 0:
+ rows += ("<tr><td colspan=\"{}\" bgcolor=\"palegreen\" height=\"18\" width=\"{}\"></td>"
+ "<td colspan=\"{}\" bgcolor=\"seagreen\" height=\"18\" width=\"{}\"></td></tr>"
+ ).format(code_info_size, code_info_size * (80 / 16), (16 - code_info_size), (80 / 16) * (16 - code_info_size))
+ remaining_bytecode -= 16
+ for i in range(remaining_bytecode // 16):
+ rows += "<tr><td colspan=\"16\" bgcolor=\"seagreen\" height=\"18\" width=\"80\"></td></tr>"
+ node.attr["label"] = "<<table border=\"1\" cellspacing=\"0\"><tr><td colspan=\"16\" bgcolor=\"lightseagreen\" height=\"18\" width=\"80\">0x{:08x}</td></tr>{}</table>>".format(block, rows)
+
+ for block in qstr_chunks:
+ if block not in block_data:
+ ownership_graph.delete_node(block)
+ continue
+ data = block_data[block]
+ qstrs_in_chunk = ""
+ offset = 0
+ while offset < len(data) - 1:
+ qstr_hash, qstr_len = struct.unpack_from("<BB", data, offset=offset)
+ if qstr_hash == 0:
+ qstrs_in_chunk += " " * (len(data) - offset)
+ offset = len(data)
+ continue
+ offset += 2 + qstr_len + 1
+ qstrs_in_chunk += " " + data[offset - qstr_len - 1: offset - 1].decode("utf-8")
+ printable_qstrs = ""
+ for i in range(len(qstrs_in_chunk)):
+ c = qstrs_in_chunk[i]
+ if c not in string.printable or c in "\v\f":
+ printable_qstrs += "░"
+ else:
+ printable_qstrs += qstrs_in_chunk[i]
+ wrapped = []
+ for i in range(0, len(printable_qstrs), 16):
+ wrapped.append(html.escape(printable_qstrs[i:i+16]))
+ node = ownership_graph.get_node(block)
+ node.attr["label"] = "<<table border=\"1\" cellspacing=\"0\" bgcolor=\"lightsalmon\" width=\"80\"><tr><td height=\"18\" >0x{:08x}</td></tr><tr><td height=\"{}\" >{}</td></tr></table>>".format(block, 18 * (len(wrapped) - 1), "<br/>".join(wrapped))
+ node.attr["fontname"] = "FiraCode-Medium"
+ node.attr["fontpath"] = "/Users/tannewt/Library/Fonts/"
+ node.attr["fontsize"] = 8
+
+ print("Total free space:", BYTES_PER_BLOCK * total_free)
+ print("Longest free space:", BYTES_PER_BLOCK * longest_free)
+
+ # First render the graph of objects on the heap.
+ if draw_heap_ownership:
+ ownership_graph.layout(prog="dot")
+ fn = os.path.join(output_directory, "heap_ownership{:04d}.png".format(snapshot_num))
+ print(fn)
+ ownership_graph.draw(fn)
+
+ # Second, render the heap layout in memory order.
+ for node in ownership_graph:
+ try:
+ address = int(node.name)
+ except ValueError:
+ ownership_graph.remove_node(node)
+ continue
+ block = (address - pool_start) // 16
+ x = block // 64
+ y = 64 - block % 64
+ try:
+ height = float(node.attr["height"])
+ except:
+ height = 0.25
+ #print(hex(address), "height", height, y)
+ #if address in block_data:
+ # print(hex(address), block, len(block_data[address]), x, y, height)
+ node.attr["pos"] = "{},{}".format(x * 80, (y - (height - 0.25) * 2) * 18) # in inches
+
+ # Clear edge positioning from ownership graph layout.
+ if draw_heap_ownership:
+ for edge in ownership_graph.iteredges():
+ del edge.attr["pos"]
+
+ # Reformat block nodes so they are the correct size and do not have keys in them.
+ for block in sorted(map_element_blocks):
+ try:
+ node = ownership_graph.get_node(block)
+ except KeyError:
+ if block != 0:
+ print("Unable to find memory block for 0x{:08x}. Is there something running?".format(block))
+ continue
+ #node.attr["fillcolor"] = "gold"
+ if block not in block_data:
+ continue
+ data = block_data[block]
+ #print("0x{:08x}".format(block))
+ cells = []
+ for i in range(len(data) // 8):
+ key, value = struct.unpack_from("<II", data, offset=(i * 8))
+ if key == MP_OBJ_NULL or key == MP_OBJ_SENTINEL:
+ #print(" <empty slot>")
+ cells.append(("", " "))
+ else:
+ #print(" {}, {}".format(format(key), format(value)))
+ cells.append((key, ""))
+ if value in block_data:
+ edge = ownership_graph.get_edge(block, value)
+ edge.attr["tailport"] = str(key)
+ rows = ""
+ for i in range(len(cells) // 2):
+ rows += "<tr><td port=\"{}\" height=\"18\" width=\"40\">{}</td><td port=\"{}\" height=\"18\" width=\"40\">{}</td></tr>".format(
+ cells[2*i][0],
+ cells[2*i][1],
+ cells[2*i+1][0],
+ cells[2*i+1][1])
+ node.attr["label"] = "<<table bgcolor=\"gold\" border=\"1\" cellpadding=\"0\" cellspacing=\"0\">{}</table>>".format(rows)
+
+
+ ownership_graph.add_node("center", pos="{},{}".format(total_width // 2 - 40, total_height // 2), shape="plaintext", label=" ")
+ ownership_graph.graph_attr["viewport"] = "{},{},1,{}".format(total_width, total_height, "center")
+
+ ownership_graph.has_layout = True
+
+ if draw_heap_layout:
+ fn = os.path.join(output_directory, "heap_layout{:04d}.png".format(snapshot_num))
+ print(fn)
+ ownership_graph.draw(fn)
+
+if __name__ == "__main__":
+ do_all_the_things()
diff --git a/tools/analyze_mpy.py b/tools/analyze_mpy.py
index 00be66106..376207a24 100644
--- a/tools/analyze_mpy.py
+++ b/tools/analyze_mpy.py
@@ -13,46 +13,79 @@ bytecode_format_sizes = {
}
bytecodes = {
+ 0x00: {"name": "MP_BC_LOAD_FAST_MULTI",
+ "format": "MP_OPCODE_BYTE"},
0x10: {"name": "MP_BC_LOAD_CONST_FALSE",
"format": "MP_OPCODE_BYTE"},
-#define MP_BC_LOAD_CONST_NONE (0x11)
-#define MP_BC_LOAD_CONST_TRUE (0x12)
-#define MP_BC_LOAD_CONST_SMALL_INT (0x14) // signed var-int
-#define MP_BC_LOAD_CONST_STRING (0x16) // qstr
+ 0x11: {"name": "MP_BC_LOAD_CONST_NONE",
+ "format": "MP_OPCODE_BYTE"},
+ 0x12: {"name": "MP_BC_LOAD_CONST_TRUE",
+ "format": "MP_OPCODE_BYTE"},
+ 0x14: {"name": "MP_BC_LOAD_CONST_SMALL_INT",
+ "format": "MP_OPCODE_VAR_UINT"},
+ 0x16: {"name": "MP_BC_LOAD_CONST_STRING",
+ "format": "MP_OPCODE_QSTR"},
+ 0x17: {"name": "MP_BC_LOAD_CONST_OBJ",
+ "format": "MP_OPCODE_VAR_UINT"},
#define MP_BC_LOAD_CONST_OBJ (0x17) // ptr
-#define MP_BC_LOAD_NULL (0x18)
+ 0x18: {"name": "MP_BC_LOAD_NULL",
+ "format": "MP_OPCODE_BYTE"},
#define MP_BC_LOAD_FAST_N (0x19) // uint
-#define MP_BC_LOAD_DEREF (0x1a) // uint
-#define MP_BC_LOAD_NAME (0x1b) // qstr
-#define MP_BC_LOAD_GLOBAL (0x1c) // qstr
-#define MP_BC_LOAD_ATTR (0x1d) // qstr
-#define MP_BC_LOAD_METHOD (0x1e) // qstr
-#define MP_BC_LOAD_SUPER_METHOD (0x1f) // qstr
+ 0x1a: {"name": "MP_BC_LOAD_DEREF",
+ "format": "MP_OPCODE_VAR_UINT"},
+ 0x1b: {"name": "MP_BC_LOAD_NAME",
+ "format": "MP_OPCODE_QSTR"},
+ 0x1c: {"name": "MP_BC_LOAD_GLOBAL",
+ "format": "MP_OPCODE_QSTR"},
+ 0x1d: {"name": "MP_BC_LOAD_ATTR",
+ "format": "MP_OPCODE_QSTR"},
+ 0x1e: {"name": "MP_BC_LOAD_METHOD",
+ "format": "MP_OPCODE_QSTR"},
+ 0x1f: {"name": "MP_BC_LOAD_SUPER_METHOD",
+ "format": "MP_OPCODE_QSTR"},
+ 0x20: {"name": "MP_BC_LOAD_BUILD_CLASS",
+ "format": "MP_OPCODE_BYTE"},
#define MP_BC_LOAD_BUILD_CLASS (0x20)
#define MP_BC_LOAD_SUBSCR (0x21)
+ 0x21: {"name": "MP_BC_LOAD_SUBSCR",
+ "format": "MP_OPCODE_BYTE"},
#define MP_BC_STORE_FAST_N (0x22) // uint
#define MP_BC_STORE_DEREF (0x23) // uint
#define MP_BC_STORE_NAME (0x24) // qstr
-#define MP_BC_STORE_GLOBAL (0x25) // qstr
-#define MP_BC_STORE_ATTR (0x26) // qstr
-#define MP_BC_STORE_SUBSCR (0x27)
+ 0x24: {"name": "MP_BC_STORE_NAME",
+ "format": "MP_OPCODE_QSTR"},
+ 0x25: {"name": "MP_BC_STORE_GLOBAL",
+ "format": "MP_OPCODE_QSTR"},
+ 0x26: {"name": "MP_BC_STORE_ATTR",
+ "format": "MP_OPCODE_QSTR"},
+ 0x27: {"name": "MP_BC_LOAD_SUBSCR",
+ "format": "MP_OPCODE_BYTE"},
+ 0x28: {"name": "MP_BC_DELETE_FAST",
+ "format": "MP_OPCODE_VAR_UINT"},
#define MP_BC_DELETE_FAST (0x28) // uint
#define MP_BC_DELETE_DEREF (0x29) // uint
#define MP_BC_DELETE_NAME (0x2a) // qstr
#define MP_BC_DELETE_GLOBAL (0x2b) // qstr
-#define MP_BC_DUP_TOP (0x30)
+ 0x30: {"name": "MP_BC_DUP_TOP",
+ "format": "MP_OPCODE_BYTE"},
#define MP_BC_DUP_TOP_TWO (0x31)
-#define MP_BC_POP_TOP (0x32)
-#define MP_BC_ROT_TWO (0x33)
-#define MP_BC_ROT_THREE (0x34)
+ 0x32: {"name": "MP_BC_POP_TOP",
+ "format": "MP_OPCODE_BYTE"},
+ 0x33: {"name": "MP_BC_ROT_TWO",
+ "format": "MP_OPCODE_BYTE"},
+ 0x34: {"name": "MP_BC_ROT_THREE",
+ "format": "MP_OPCODE_BYTE"},
-#define MP_BC_JUMP (0x35) // rel byte code offset, 16-bit signed, in excess
-#define MP_BC_POP_JUMP_IF_TRUE (0x36) // rel byte code offset, 16-bit signed, in excess
-#define MP_BC_POP_JUMP_IF_FALSE (0x37) // rel byte code offset, 16-bit signed, in excess
+ 0x35: {"name": "MP_BC_JUMP",
+ "format": "MP_OPCODE_OFFSET"},
+ 0x36: {"name": "MP_BC_POP_JUMP_IF_TRUE",
+ "format": "MP_OPCODE_OFFSET"},
+ 0x37: {"name": "MP_BC_POP_JUMP_IF_FALSE",
+ "format": "MP_OPCODE_OFFSET"},
#define MP_BC_JUMP_IF_TRUE_OR_POP (0x38) // rel byte code offset, 16-bit signed, in excess
#define MP_BC_JUMP_IF_FALSE_OR_POP (0x39) // rel byte code offset, 16-bit signed, in excess
#define MP_BC_SETUP_WITH (0x3d) // rel byte code offset, 16-bit unsigned
@@ -62,44 +95,172 @@ bytecodes = {
#define MP_BC_END_FINALLY (0x41)
#define MP_BC_GET_ITER (0x42)
#define MP_BC_FOR_ITER (0x43) // rel byte code offset, 16-bit unsigned
-#define MP_BC_POP_BLOCK (0x44)
+ 0x43: {"name": "MP_BC_FOR_ITER",
+ "format": "MP_OPCODE_OFFSET"},
+
+ 0x44: {"name": "MP_BC_POP_BLOCK",
+ "format": "MP_OPCODE_BYTE"},
#define MP_BC_POP_EXCEPT (0x45)
#define MP_BC_UNWIND_JUMP (0x46) // rel byte code offset, 16-bit signed, in excess; then a byte
-#define MP_BC_GET_ITER_STACK (0x47)
+ 0x47: {"name": "MP_BC_GET_ITER_STACK",
+ "format": "MP_OPCODE_BYTE"},
-#define MP_BC_BUILD_TUPLE (0x50) // uint
-#define MP_BC_BUILD_LIST (0x51) // uint
-#define MP_BC_BUILD_MAP (0x53) // uint
-#define MP_BC_STORE_MAP (0x54)
+
+ 0x50: {"name": "MP_BC_BUILD_TUPLE",
+ "format": "MP_OPCODE_VAR_UINT"},
+ 0x51: {"name": "MP_BC_BUILD_LIST",
+ "format": "MP_OPCODE_VAR_UINT"},
+ 0x53: {"name": "MP_BC_BUILD_MAP",
+ "format": "MP_OPCODE_VAR_UINT"},
+ 0x54: {"name": "MP_BC_STORE_MAP",
+ "format": "MP_OPCODE_BYTE"},
#define MP_BC_BUILD_SET (0x56) // uint
#define MP_BC_BUILD_SLICE (0x58) // uint
#define MP_BC_STORE_COMP (0x57) // uint
+ 0x57: {"name": "MP_BC_STORE_COMP",
+ "format": "MP_OPCODE_VAR_UINT"},
#define MP_BC_UNPACK_SEQUENCE (0x59) // uint
#define MP_BC_UNPACK_EX (0x5a) // uint
-#define MP_BC_RETURN_VALUE (0x5b)
-#define MP_BC_RAISE_VARARGS (0x5c) // byte
+ 0x5b: {"name": "MP_BC_RETURN_VALUE",
+ "format": "MP_OPCODE_BYTE"},
+ 0x5c: {"name": "MP_BC_RAISE_VARARGS",
+ "format": "MP_OPCODE_BYTE_EXTRA"},
#define MP_BC_YIELD_VALUE (0x5d)
#define MP_BC_YIELD_FROM (0x5e)
#define MP_BC_MAKE_FUNCTION (0x60) // uint
-#define MP_BC_MAKE_FUNCTION_DEFARGS (0x61) // uint
-#define MP_BC_MAKE_CLOSURE (0x62) // uint
-#define MP_BC_MAKE_CLOSURE_DEFARGS (0x63) // uint
-#define MP_BC_CALL_FUNCTION (0x64) // uint
-#define MP_BC_CALL_FUNCTION_VAR_KW (0x65) // uint
-#define MP_BC_CALL_METHOD (0x66) // uint
-#define MP_BC_CALL_METHOD_VAR_KW (0x67) // uint
-
-#define MP_BC_IMPORT_NAME (0x68) // qstr
+ 0x60: {"name": "MP_BC_MAKE_FUNCTION",
+ "format": "MP_OPCODE_VAR_UINT"},
+ 0x61: {"name": "MP_BC_MAKE_FUNCTION_DEFARGS",
+ "format": "MP_OPCODE_VAR_UINT"},
+ 0x62: {"name": "MP_BC_MAKE_CLOSURE",
+ "format": "MP_OPCODE_VAR_UINT_EXTRA"},
+ 0x63: {"name": "MP_BC_MAKE_CLOSURE",
+ "format": "MP_OPCODE_VAR_UINT_EXTRA"},
+ 0x64: {"name": "MP_BC_CALL_FUNCTION",
+ "format": "MP_OPCODE_VAR_UINT"},
+ 0x65: {"name": "MP_BC_CALL_FUNCTION_VAR_KW",
+ "format": "MP_OPCODE_VAR_UINT"},
+ 0x66: {"name": "MP_BC_CALL_METHOD",
+ "format": "MP_OPCODE_VAR_UINT"},
+ 0x67: {"name": "MP_BC_CALL_METHOD_VAR_KW",
+ "format": "MP_OPCODE_VAR_UINT"},
+
+ 0x68: {"name": "MP_BC_IMPORT_NAME",
+ "format": "MP_OPCODE_QSTR"},
+ 0x69: {"name": "MP_BC_IMPORT_FROM",
+ "format": "MP_OPCODE_QSTR"},
#define MP_BC_IMPORT_FROM (0x69) // qstr
#define MP_BC_IMPORT_STAR (0x6a)
#define MP_BC_LOAD_CONST_SMALL_INT_MULTI (0x70) // + N(64)
+ 0x7f: {"name": "MP_BC_LOAD_CONST_SMALL_INT_MULTI -1",
+ "format": "MP_OPCODE_BYTE"},
+ 0x80: {"name": "MP_BC_LOAD_CONST_SMALL_INT_MULTI 0",
+ "format": "MP_OPCODE_BYTE"},
+ 0x81: {"name": "MP_BC_LOAD_CONST_SMALL_INT_MULTI 1",
+ "format": "MP_OPCODE_BYTE"},
+ 0x82: {"name": "MP_BC_LOAD_CONST_SMALL_INT_MULTI 2",
+ "format": "MP_OPCODE_BYTE"},
+ 0x83: {"name": "MP_BC_LOAD_CONST_SMALL_INT_MULTI 3",
+ "format": "MP_OPCODE_BYTE"},
+ 0x84: {"name": "MP_BC_LOAD_CONST_SMALL_INT_MULTI 4",
+ "format": "MP_OPCODE_BYTE"},
#define MP_BC_LOAD_FAST_MULTI (0xb0) // + N(16)
+ 0xb0: {"name": "MP_BC_LOAD_FAST_MULTI 0",
+ "format": "MP_OPCODE_BYTE"},
+ 0xb1: {"name": "MP_BC_LOAD_FAST_MULTI 1",
+ "format": "MP_OPCODE_BYTE"},
+ 0xb2: {"name": "MP_BC_LOAD_FAST_MULTI 2",
+ "format": "MP_OPCODE_BYTE"},
+ 0xb3: {"name": "MP_BC_LOAD_FAST_MULTI 3",
+ "format": "MP_OPCODE_BYTE"},
+ 0xb4: {"name": "MP_BC_LOAD_FAST_MULTI 4",
+ "format": "MP_OPCODE_BYTE"},
+ 0xb5: {"name": "MP_BC_LOAD_FAST_MULTI 5",
+ "format": "MP_OPCODE_BYTE"},
+ 0xb6: {"name": "MP_BC_LOAD_FAST_MULTI 6",
+ "format": "MP_OPCODE_BYTE"},
+ 0xb7: {"name": "MP_BC_LOAD_FAST_MULTI 7",
+ "format": "MP_OPCODE_BYTE"},
+ 0xb8: {"name": "MP_BC_LOAD_FAST_MULTI 8",
+ "format": "MP_OPCODE_BYTE"},
#define MP_BC_STORE_FAST_MULTI (0xc0) // + N(16)
+ 0xc0: {"name": "MP_BC_STORE_FAST_MULTI 0",
+ "format": "MP_OPCODE_BYTE"},
+ 0xc1: {"name": "MP_BC_STORE_FAST_MULTI 1",
+ "format": "MP_OPCODE_BYTE"},
+ 0xc2: {"name": "MP_BC_STORE_FAST_MULTI 2",
+ "format": "MP_OPCODE_BYTE"},
+ 0xc3: {"name": "MP_BC_STORE_FAST_MULTI 3",
+ "format": "MP_OPCODE_BYTE"},
+ 0xc4: {"name": "MP_BC_STORE_FAST_MULTI 4",
+ "format": "MP_OPCODE_BYTE"},
+ 0xc5: {"name": "MP_BC_STORE_FAST_MULTI 5",
+ "format": "MP_OPCODE_BYTE"},
+ 0xc6: {"name": "MP_BC_STORE_FAST_MULTI 6",
+ "format": "MP_OPCODE_BYTE"},
+ 0xc7: {"name": "MP_BC_STORE_FAST_MULTI 7",
+ "format": "MP_OPCODE_BYTE"},
#define MP_BC_UNARY_OP_MULTI (0xd0) // + op(<MP_UNARY_OP_NUM_BYTECODE)
+
+ # // 9 relational operations, should return a bool
+ 0xd7: {"name": "MP_BC_BINARY_OP_MULTI MP_BINARY_OP_LESS",
+ "format": "MP_OPCODE_BYTE"},
+ 0xd8: {"name": "MP_BC_BINARY_OP_MULTI MP_BINARY_OP_MORE",
+ "format": "MP_OPCODE_BYTE"},
+ 0xd9: {"name": "MP_BC_BINARY_OP_MULTI MP_BINARY_OP_EQUAL",
+ "format": "MP_OPCODE_BYTE"},
+ 0xda: {"name": "MP_BC_BINARY_OP_MULTI MP_BINARY_OP_LESS_EQUAL",
+ "format": "MP_OPCODE_BYTE"},
+ 0xdb: {"name": "MP_BC_BINARY_OP_MULTI MP_BINARY_OP_MORE_EQUAL",
+ "format": "MP_OPCODE_BYTE"},
+ 0xdc: {"name": "MP_BC_BINARY_OP_MULTI MP_BINARY_OP_NOT_EQUAL",
+ "format": "MP_OPCODE_BYTE"},
+ # dc: MP_BINARY_OP_NOT_EQUAL,
+ # dd: MP_BINARY_OP_IN,
+ # de: MP_BINARY_OP_IS,
+ # df: MP_BINARY_OP_EXCEPTION_MATCH,
+ #
+ # // 12 inplace arithmetic operations
+ # e0: MP_BINARY_OP_INPLACE_OR,
+ # e1: MP_BINARY_OP_INPLACE_XOR,
+ # e2: MP_BINARY_OP_INPLACE_AND,
+ # e3: MP_BINARY_OP_INPLACE_LSHIFT,
+ # e4: MP_BINARY_OP_INPLACE_RSHIFT,
+ # e5: MP_BINARY_OP_INPLACE_ADD,
+ 0xe5: {"name": "MP_BC_BINARY_OP_MULTI MP_BINARY_OP_INPLACE_ADD",
+ "format": "MP_OPCODE_BYTE"},
+ 0xe6: {"name": "MP_BC_BINARY_OP_MULTI MP_BINARY_OP_INPLACE_SUBTRACT",
+ "format": "MP_OPCODE_BYTE"},
+ # e7: MP_BINARY_OP_INPLACE_MULTIPLY,
+ # e8: MP_BINARY_OP_INPLACE_FLOOR_DIVIDE,
+ # e9: MP_BINARY_OP_INPLACE_TRUE_DIVIDE,
+ # ea: MP_BINARY_OP_INPLACE_MODULO,
+ # eb: MP_BINARY_OP_INPLACE_POWER,
+ #
+ # // 12 normal arithmetic operations
+ # ec: MP_BINARY_OP_OR,
+ # ed: MP_BINARY_OP_XOR,
+ # ee: MP_BINARY_OP_AND,
+ # ef: MP_BINARY_OP_LSHIFT,
+ # f0: MP_BINARY_OP_RSHIFT,
#define MP_BC_BINARY_OP_MULTI (0xd7) // + op(<MP_BINARY_OP_NUM_BYTECODE)
+ 0xf1: {"name": "MP_BC_BINARY_OP_MULTI MP_BINARY_OP_ADD",
+ "format": "MP_OPCODE_BYTE"},
+ 0xf2: {"name": "MP_BC_BINARY_OP_MULTI MP_BINARY_OP_SUBTRACT",
+ "format": "MP_OPCODE_BYTE"},
+ 0xf3: {"name": "MP_BC_BINARY_OP_MULTI MP_BINARY_OP_MULTIPLY",
+ "format": "MP_OPCODE_BYTE"},
+ 0xf4: {"name": "MP_BC_BINARY_OP_MULTI MP_BINARY_OP_FLOOR_DIVIDE",
+ "format": "MP_OPCODE_BYTE"},
+ 0xf5: {"name": "MP_BC_BINARY_OP_MULTI MP_BINARY_OP_TRUE_DIVIDE",
+ "format": "MP_OPCODE_BYTE"},
+ 0xf6: {"name": "MP_BC_BINARY_OP_MULTI MP_BINARY_OP_MODULO",
+ "format": "MP_OPCODE_BYTE"},
+ 0xf7: {"name": "MP_BC_BINARY_OP_MULTI MP_BINARY_OP_POWER",
+ "format": "MP_OPCODE_BYTE"},
}
def read_uint(encoded_uint, peek=False):
@@ -149,40 +310,94 @@ class RawCode:
def __init__(self, encoded_raw_code):
bc_len = read_uint(encoded_raw_code)
bc = encoded_raw_code.read(bc_len)
- prelude = Prelude(io.BufferedReader(io.BytesIO(bc)))
- encoded_code_info = bc[:prelude.code_info_size]
- bc_start = prelude.code_info_size
- while bc[bc_start] == 0xff:
- bc_start += 1
- bc = bc[bc_start:]
+ bc = io.BufferedReader(io.BytesIO(bc))
+ prelude = Prelude(bc)
+ encoded_code_info = bc.read(prelude.code_info_size)
+ bc.read(1)
+ while bc.peek(1)[0] == 0xff:
+ bc.read(1)
+ bc = bytearray(bc.read())
+ #print(encoded_code_info, bc)
self.qstrs = []
self.simple_name = self._load_qstr(encoded_raw_code)
self.source_file = self._load_qstr(encoded_raw_code)
# the simple name and source file qstr indexes get written back into the byte code somehow
+ #print(bc)
self._load_bytecode_qstrs(encoded_raw_code, bc)
+
+ #print(encoded_raw_code.peek(20)[:20])
+
+ n_obj = read_uint(encoded_raw_code)
+ n_raw_code = read_uint(encoded_raw_code)
+
+ self.const_table = []
+ for i in range(prelude.n_pos_args + prelude.n_kwonly_args):
+ self.const_table.append(self._load_qstr(encoded_raw_code))
+ print("load args", self.const_table[-1])
+ for i in range(n_obj):
+ self.const_table.append(self._load_obj(encoded_raw_code))
+ print("load obj", self.const_table[-1])
+ for i in range(n_raw_code):
+ print("load raw code")
+ self.const_table.append(RawCode(encoded_raw_code))
+
print(self.qstrs[self.simple_name], self.qstrs[self.source_file])
- print(binascii.hexlify(encoded_raw_code.peek(20)[:20]))
+ #print(binascii.hexlify(encoded_raw_code.peek(20)[:20]))
def _load_qstr(self, encoded_qstr):
string_len = read_uint(encoded_qstr)
string = encoded_qstr.read(string_len).decode("utf-8")
+ print(string)
if string in self.qstrs:
- return self.qstr.index(string)
+ return self.qstrs.index(string)
new_index = len(self.qstrs)
self.qstrs.append(string)
return new_index
- def _load_bytecode_qstrs(encoded_raw_code, bytecode):
+ def _load_obj(self, encoded_obj):
+ obj_type = encoded_obj.read(1)
+ if obj_type == b'e':
+ return "..."
+ else:
+ str_len = read_uint(encoded_obj)
+ s = encoded_obj.read(str_len)
+ if obj_type == b's':
+ return s.decode("utf-8")
+ elif obj_type == b'b':
+ return s
+ elif obj_type == b'i':
+ return int(s)
+ elif obj_type == b'f':
+ return float(s)
+ elif obj_type == b'c':
+ return float(s)
+ raise RuntimeError("Unknown object type {}".format(obj_type))
+
+ def _load_bytecode_qstrs(self, encoded_raw_code, bytecode):
i = 0
while i < len(bytecode):
- opcode, opcode_size = mp_opcode_format(bytecode[i])
- if opcode == MP_OPCODE_QSTR:
+ bc = bytecode[i]
+ if bc not in bytecodes:
+ raise RuntimeError("missing code 0x{:x} at {}".format(bc, i))
+ return
+ bc = bytecodes[bc]
+ opcode = bc["name"]
+ print(opcode)
+ opcode_size = bytecode_format_sizes[bc["format"]]
+ if bc["format"] == "MP_OPCODE_QSTR":
qstr_index = self._load_qstr(encoded_raw_code)
bytecode[i+1] = qstr_index
bytecode[i+2] = qstr_index >> 8
- opcode += opcode_size
+ if not opcode_size:
+ i += 2
+ while (bytecode[i] & 0x80) != 0:
+ i += 1
+ if bc["format"] == "MP_OPCODE_VAR_UINT_EXTRA":
+ i += 1
+ else:
+ i += opcode_size
class mpyFile:
def __init__(self, encoded_mpy):
diff --git a/tools/gc_activity.md b/tools/gc_activity.md
index 40252bdf7..d1a4c2803 100644
--- a/tools/gc_activity.md
+++ b/tools/gc_activity.md
@@ -13,7 +13,7 @@ correct port. GDB is usually :3333 and JLink is :2331.
Now, run gdb from your port directory:
```
-arm-none-eabi-gdb -x ../tools/output_gc_until_repl.txt build-metro_m0_flash/firmware.elf
+arm-none-eabi-gdb -x ../tools/output_gc_until_repl.txt build-metro_m0_express/firmware.elf
```
This will take a little time while it breaks, backtraces and continues for every
diff --git a/tools/output_gc_until_repl.txt b/tools/output_gc_until_repl.txt
index 28fb29219..0e9962635 100644
--- a/tools/output_gc_until_repl.txt
+++ b/tools/output_gc_until_repl.txt
@@ -10,15 +10,16 @@ set logging on
set remote hardware-breakpoint-limit 4
# gc log
-break gc.c:110
+break gc.c:103
commands
backtrace
p/x start_block
p/x length
+append binary memory ram.bin &_srelocate &_estack
continue
end
-break mp_hal_stdin_rx_chr
+break main.c:164
continue