improve storage parsing by using footer for sanity

magic is checked for sanity, and then the size is used for reading the
whole section rather than simply ignoring null bytes

closes #2
This commit is contained in:
Brian S. Stephan 2023-06-20 18:49:21 -05:00
parent 8404ec0d54
commit 0c7ed1fcea
Signed by: bss
GPG Key ID: 3DE06D3180895FCB
2 changed files with 57 additions and 24 deletions

View File

@ -112,13 +112,8 @@ forcedSetupOptions {
### Dumping the storage section
The storage section of a GP2040-CE board starts at `0x101FE000`. A current limitation of the **visualize-storage** tool
is that it can only read the Protobuf serialized data, not the footer that is also used as part of the storage engine.
As such, currently, the binary is expected to be truncated slightly. This will be improved to read the storage footer in
the near future, but for now, know your `picotool` invocation will be slightly different.
To dump your board's storage:
The storage section of a GP2040-CE board is a reserved 8 KB starting at `0x101FE000`. To dump your board's storage:
```
% picotool save -r 101FE000 101FFFF4 memory.bin
% picotool save -r 101FE000 10200000 memory.bin
```

View File

@ -1,35 +1,73 @@
"""Interact with the protobuf config from a picotool flash dump of a GP2040-CE board."""
import argparse
import pprint
import logging
from gp2040ce_bintools import core_parser, get_config_pb2
logger = logging.getLogger(__name__)
def get_config(filename):
"""Load the protobuf section of an flash and display the contents."""
with open(filename, 'rb') as dump:
# read off the unused space
while True:
byte = dump.read(1)
if byte != b'\x00':
break
content = byte + dump.read()
FOOTER_SIZE = 12
FOOTER_MAGIC = b'\x65\xe3\xf1\xd2'
def get_config_footer(content: bytes) -> tuple[int, int, str]:
"""Confirm and retrieve the config footer from a series of bytes of GP2040-CE storage.
Args:
content: bytes from a GP2040-CE board's storage section
Returns:
the discovered config size, config CRC, and magic from the config footer
Raises:
ValueError: if the provided bytes are not a config footer
"""
# last 12 bytes are the footer
if len(content) < FOOTER_SIZE:
raise ValueError("provided content is not large enough to have a config footer!")
footer = content[-FOOTER_SIZE:]
if footer[-4:] != FOOTER_MAGIC:
raise ValueError("content's magic is not as expected!")
config_size = int.from_bytes(reversed(footer[:4]), 'big')
config_crc = int.from_bytes(reversed(footer[4:8]), 'big')
config_magic = f'0x{footer[8:12].hex()}'
# one last sanity check
if len(content) < config_size + FOOTER_SIZE:
raise ValueError("provided content is not large enough according to the config footer!")
logger.debug("detected footer (size:%s, crc:%s, magic:%s", config_size, config_crc, config_magic)
return config_size, config_crc, config_magic
def get_config(content: bytes) -> dict:
"""Read the config from a GP2040-CE storage section.
Args:
content: bytes from a GP2040-CE board's storage section
Returns:
the parsed configuration
"""
size, _, _ = get_config_footer(content)
config_pb2 = get_config_pb2()
config = config_pb2.Config()
config.ParseFromString(content)
config.ParseFromString(content[-(size+FOOTER_SIZE):-FOOTER_SIZE])
logger.debug("parsed: %s", config)
return config
def visualize():
"""Pretty print the contents of GP2040-CE's storage."""
"""Print the contents of GP2040-CE's storage."""
parser = argparse.ArgumentParser(
description="Read a the configuration storage section from a GP2040-CE board dump and print out its contents.",
description="Read the configuration section from a dump of a GP2040-CE board's storage section and print out "
"its contents.",
parents=[core_parser],
)
parser.add_argument('filename', help=".bin file of a GP2040-CE board's storage section, bytes 101FE000-101FFFF4 "
"(e.g. picotool save -r 101FE000 101FFFF4 memory.bin")
parser.add_argument('filename', help=".bin file of a GP2040-CE board's storage section, bytes 101FE000-10200000")
args, _ = parser.parse_known_args()
with open(args.filename, 'rb') as dump:
content = dump.read()
config = get_config(args.filename)
pprint.pprint(config)
config = get_config(content)
print(config)