mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-22 14:28:07 +00:00
582e1318c4
[no changelog]
371 lines
8.8 KiB
Python
Executable File
371 lines
8.8 KiB
Python
Executable File
#!/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 is_ignored_ptr(ptr):
|
|
return (ptr == "(nil)" or ptr.startswith("0x5") or ptr.startswith("0x6"))
|
|
|
|
|
|
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 not is_ignored_ptr(item.ptr)
|
|
)
|
|
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",
|
|
"protomsg": "P",
|
|
"protodef": "p",
|
|
}
|
|
|
|
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 is_ignored_ptr(item.ptr):
|
|
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 is_ignored_ptr(item.ptr):
|
|
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))
|