visualize-storage tool --- read GP2040-CE config

this also comes with a lot of project scaffolding for (IMO) a
well-organized python project. this should get the ball rolling for
other devs
This commit is contained in:
Brian S. Stephan 2023-06-20 12:50:32 -05:00
parent df3a2a5394
commit 24617bf920
Signed by: bss
GPG Key ID: 3DE06D3180895FCB
7 changed files with 180 additions and 13 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto

View File

@ -1,3 +1,38 @@
# OpenStickCommunity Binary Tools
# GP2040-CE Binary Tools
Tools for working with GP2040-CE binary dumps.
## Installation
```
% git clone [URL to this repository]
% cd gp2040ce-binary-tools
% pip install -e .
```
At some point we may publish packages to e.g. pypi.
### Development Installation
As above, plus also `pip install -Ur requirements/requirements-dev.txt` to get linters and whatnot.
## Tools
### visualize-storage
**visualize-storage** reads a dump of a GP2040-CE board's flash storage section, where the configuration lives,
and prints it out for visual inspection or diffing with other tools. Usage is simple; just pass the tool your
binary file to analyze along with the path to the Protobuf files.
Because Protobuf relies on .proto files to convey the serialized structure, you must supply them
from the main GP2040-CE project, e.g. pointing this tool at your clone of the core project. Something like
this would suffice for a working invocation (note: you do not need to compile the files yourself):
```
% visualize-storage --proto-files-path=~/proj/GP2040-CE/proto \
--proto-files-path=~/proj/GP2040-CE/lib/nanopb/generator/proto \
memory.bin
```
In the future we will look into publishing complete packages that include the compiled `_pb2.py` files, so that you
don't need to provide them yourself.

View File

@ -0,0 +1,33 @@
"""Initialize the package and get dependencies."""
import argparse
import importlib
import logging
import os
import pathlib
import sys
import grpc
logger = logging.getLogger(__name__)
core_parser = argparse.ArgumentParser(add_help=False)
core_parser.add_argument('--proto-files-path', type=pathlib.Path, default=list(), action='append',
help="path to .proto files to read, including dependencies; you will likely need "
"to supply this twice, once for GP2040-CE's .proto files and once for nanopb's")
args, _ = core_parser.parse_known_args()
for path in args.proto_files_path:
sys.path.append(os.path.abspath(os.path.expanduser(path)))
def get_config_pb2():
"""Retrieve prebuilt _pb2 file or attempt to compile it live."""
try:
return importlib.import_module('config_pb2')
except ModuleNotFoundError:
if args.proto_files_path:
# compile the proto files in realtime, leave them in this package
package_path = pathlib.Path(__file__).parent
logger.info("Generating Protobuf Python files into %s...", package_path)
return grpc.protos('config.proto')
raise

View File

@ -0,0 +1,51 @@
"""Interact with the protobuf config from a picotool flash dump of a GP2040-CE board.
This is more manual than I'd like at the moment, but this demonstrates/documents the means
to display the config on a board. It requires a checkout that matches the version of the
firmware on the board (so that the protobuf messages match the flash, though protobuf
tolerates differences well, so it'll probably work, just be misnamed or incomplete).
Generating the Python proto code:
[env setup]
% protoc ../../proto/* -I../../proto/ -I../../lib/nanopb/generator/proto --python_out=build
Usage:
[env setup]
% picotool save -r 101FE000 101FFFF4 build/memory.bin # 101FE000 = storage start, 101FFFF4 storage end - footer
% export PYTHONPATH=../../lib/nanopb/generator/proto:build
% python visualize.py
"""
import argparse
import pprint
from gp2040ce_bintools import core_parser, get_config_pb2
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()
config_pb2 = get_config_pb2()
config = config_pb2.Config()
config.ParseFromString(content)
return config
def visualize():
"""Pretty print the contents of GP2040-CE's storage."""
parser = argparse.ArgumentParser(
prog="visualize-storage",
description="Read a the configuration storage section from a GP2040-CE board dump 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")
args, _ = parser.parse_known_args()
config = get_config(args.filename)
pprint.pprint(config)

View File

@ -1,5 +1,5 @@
[project]
name = "openstickcommunity-binary-tools"
name = "gp2040ce-binary-tools"
version = "0"
description = "Tools for working with GP2040-CE binary dumps."
readme = "README.md"
@ -8,7 +8,14 @@ authors = [
{name = "Brian S. Stephan", email = "bss@incorporeal.org"},
]
requires-python = ">=3.8"
dependencies = ["argparse", "protobuf"]
dependencies = ["grpcio-tools"]
[project.optional-dependencies]
dev = ["flake8", "pip-tools"]
dev = ["flake8", "flake8-blind-except", "flake8-builtins", "flake8-docstrings", "flake8-executable", "flake8-fixme",
"flake8-isort", "flake8-logging-format", "flake8-mutable", "flake8-pyproject", "pip-tools"]
[project.scripts]
visualize-storage = "gp2040ce_bintools.storage:visualize"
[tool.flake8]
max-line-length = 120

View File

@ -4,30 +4,65 @@
#
# pip-compile --extra=dev --output-file=requirements/requirements-dev.txt pyproject.toml
#
argparse==1.4.0
# via openstickcommunity-binary-tools (pyproject.toml)
build==0.10.0
# via pip-tools
click==8.1.3
# via pip-tools
flake8==6.0.0
# via openstickcommunity-binary-tools (pyproject.toml)
# via
# flake8-builtins
# flake8-docstrings
# flake8-executable
# flake8-isort
# flake8-mutable
# flake8-pyproject
# gp2040ce-binary-tools (pyproject.toml)
flake8-blind-except==0.2.1
# via gp2040ce-binary-tools (pyproject.toml)
flake8-builtins==2.1.0
# via gp2040ce-binary-tools (pyproject.toml)
flake8-docstrings==1.7.0
# via gp2040ce-binary-tools (pyproject.toml)
flake8-executable==2.1.3
# via gp2040ce-binary-tools (pyproject.toml)
flake8-fixme==1.1.1
# via gp2040ce-binary-tools (pyproject.toml)
flake8-isort==6.0.0
# via gp2040ce-binary-tools (pyproject.toml)
flake8-logging-format==0.9.0
# via gp2040ce-binary-tools (pyproject.toml)
flake8-mutable==1.2.0
# via gp2040ce-binary-tools (pyproject.toml)
flake8-pyproject==1.2.3
# via gp2040ce-binary-tools (pyproject.toml)
grpcio==1.54.2
# via grpcio-tools
grpcio-tools==1.54.2
# via gp2040ce-binary-tools (pyproject.toml)
isort==5.12.0
# via flake8-isort
mccabe==0.7.0
# via flake8
packaging==23.1
# via build
pip-tools==6.13.0
# via openstickcommunity-binary-tools (pyproject.toml)
# via gp2040ce-binary-tools (pyproject.toml)
protobuf==4.23.3
# via openstickcommunity-binary-tools (pyproject.toml)
# via grpcio-tools
pycodestyle==2.10.0
# via flake8
pydocstyle==6.3.0
# via flake8-docstrings
pyflakes==3.0.1
# via flake8
pyproject-hooks==1.0.0
# via build
snowballstemmer==2.2.0
# via pydocstyle
tomli==2.0.1
# via build
# via
# build
# flake8-pyproject
wheel==0.40.0
# via pip-tools

View File

@ -4,7 +4,12 @@
#
# pip-compile --output-file=requirements/requirements.txt pyproject.toml
#
argparse==1.4.0
# via openstickcommunity-binary-tools (pyproject.toml)
grpcio==1.54.2
# via grpcio-tools
grpcio-tools==1.54.2
# via gp2040ce-binary-tools (pyproject.toml)
protobuf==4.23.3
# via openstickcommunity-binary-tools (pyproject.toml)
# via grpcio-tools
# The following packages are considered to be unsafe in a requirements file:
# setuptools