Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 131 additions & 72 deletions scripts/imgtool/dumpinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
"""
Parse and print header, TLV area and trailer information of a signed image.
"""
import json
import os.path
import struct
import sys

import click
import yaml
Expand Down Expand Up @@ -78,58 +80,58 @@ def parse_boot_magic(trailer_magic):
return magic


def print_in_frame(header_text, content):
def _human_format_frame(header_text, content):
sepc = " "
header = "#### " + header_text + sepc
post_header = "#" * (_LINE_LENGTH - len(header))
print(header + post_header)

print("|", sepc * (_LINE_LENGTH - 2), "|", sep="")
lines = []
lines.append(header + post_header)
lines.append("|" + sepc * (_LINE_LENGTH - 2) + "|")
offset = (_LINE_LENGTH - len(content)) // 2
pre = "|" + (sepc * (offset - 1))
post = sepc * (_LINE_LENGTH - len(pre) - len(content) - 1) + "|"
print(pre, content, post, sep="")
print("|", sepc * (_LINE_LENGTH - 2), "|", sep="")
print("#" * _LINE_LENGTH)
lines.append(pre + content + post)
lines.append("|" + sepc * (_LINE_LENGTH - 2) + "|")
lines.append("#" * _LINE_LENGTH)
return "\n".join(lines)


def print_in_row(row_text):
def _human_format_row(row_text):
row_text = "#### " + row_text + " "
fill = "#" * (_LINE_LENGTH - len(row_text))
print(row_text + fill)
return row_text + fill


def print_tlv_records(tlv_list):
def _human_format_tlv_records(tlv_list):
indent = _LINE_LENGTH // 8
lines = []
for tlv in tlv_list:
print(" " * indent, "-" * 45)
tlv_type, tlv_length, tlv_data = tlv.keys()
lines.append(" " * indent + " " + "-" * 45)

if tlv[tlv_type] in TLV_TYPES:
print(" " * indent, f"{tlv_type}: {TLV_TYPES[tlv[tlv_type]]} ({hex(tlv[tlv_type])})")
else:
print(" " * indent, "{}: {} ({})".format(
tlv_type, "UNKNOWN", hex(tlv[tlv_type])))
print(" " * indent, f"{tlv_length}: ", hex(tlv[tlv_length]))
print(" " * indent, f"{tlv_data}: ", end="")
type_name = tlv["type_name"]
type_hex = hex(tlv["type"])
lines.append(" " * indent + f" type: {type_name} ({type_hex})")
lines.append(" " * indent + f" len: {hex(tlv['len'])}")

for j, data in enumerate(tlv[tlv_data]):
print(f"{data:#04x}", end=" ")
if ((j + 1) % 8 == 0) and ((j + 1) != len(tlv[tlv_data])):
print("\n", end=" " * (indent + 7))
print()
data_line = " " * indent + " data: "
for j, data in enumerate(tlv["data"]):
data_line += f"{data:#04x} "
if ((j + 1) % 8 == 0) and ((j + 1) != len(tlv["data"])):
lines.append(data_line)
data_line = " " * (indent + 7)
lines.append(data_line)
return "\n".join(lines)


def dump_imginfo(imgfile, outfile=None, silent=False):
"""Parse a signed image binary and print/save the available information."""
def _read_imginfo(imgfile):
"""Parse a signed image binary and return the image data structure."""
trailer_magic = None
# set to INVALID by default
swap_size = 0x99
swap_info = 0x99
copy_done = 0x99
image_ok = 0x99
trailer = {}
key_field_len = None

try:
with open(imgfile, "rb") as f:
Expand Down Expand Up @@ -173,7 +175,10 @@ def dump_imginfo(imgfile, outfile=None, silent=False):
tlv_off += image.TLV_INFO_SIZE
tlv_data = b[tlv_off:(tlv_off + tlv_len)]
tlv_area["tlvs_prot"].append(
{"type": tlv_type, "len": tlv_len, "data": tlv_data})
{"type": tlv_type,
"type_name": TLV_TYPES.get(tlv_type, "UNKNOWN"),
"len": tlv_len,
"data": tlv_data})
tlv_off += tlv_len

_tlv_head = struct.unpack('HH', b[tlv_off:(tlv_off + image.TLV_INFO_SIZE)])
Expand All @@ -191,7 +196,8 @@ def dump_imginfo(imgfile, outfile=None, silent=False):
tlv_off += image.TLV_INFO_SIZE
tlv_data = b[tlv_off:(tlv_off + tlv_len)]
tlv_area["tlvs"].append(
{"type": tlv_type, "len": tlv_len, "data": tlv_data})
{"type": tlv_type, "type_name": TLV_TYPES.get(tlv_type, "UNKNOWN"),
"len": tlv_len, "data": tlv_data})
tlv_off += tlv_len

_img_pad_size = len(b) - tlv_end
Expand Down Expand Up @@ -240,26 +246,28 @@ def dump_imginfo(imgfile, outfile=None, silent=False):
# Estimated value of key_field_len is correct if
# BOOT_SWAP_SAVE_ENCTLV is unset
key_field_len = image.align_up(16, max_align) * 2
trailer["key_field_len"] = key_field_len

# Generating output yaml file
if outfile is not None:
imgdata = {"header": header,
"tlv_area": tlv_area,
"trailer": trailer}
with open(outfile, "w") as outf:
# sort_keys - from pyyaml 5.1
yaml.dump(imgdata, outf, sort_keys=False)
imginfo = {
"filename": os.path.basename(imgfile),
"header": header,
"tlv_area": tlv_area,
"trailer": trailer}

###############################################################################
return imginfo

if silent:
return

print("Printing content of signed image:", os.path.basename(imgfile), "\n")
def _write_format_human(imginfo, outfile):
filename = imginfo["filename"]
header = imginfo["header"]
tlv_area = imginfo["tlv_area"]
trailer = imginfo["trailer"]

print("Printing content of signed image:", filename, "\n", file=outfile)

# Image header
section_name = "Image header (offset: 0x0)"
print_in_row(section_name)
print(_human_format_row(section_name), file=outfile)
for key, value in header.items():
if key == "flags":
if not value:
Expand All @@ -275,55 +283,106 @@ def dump_imginfo(imgfile, outfile=None, silent=False):

if not isinstance(value, str):
value = hex(value)
print(key, ":", " " * (19 - len(key)), value, sep="")
print("#" * _LINE_LENGTH)
print(key, ":", " " * (19 - len(key)), value, sep="", file=outfile)
print("#" * _LINE_LENGTH, file=outfile)

# Image payload
_sectionoff = header["hdr_size"]
frame_header_text = f"Payload (offset: {hex(_sectionoff)})"
frame_content = "FW image (size: {} Bytes)".format(hex(header["img_size"]))
print_in_frame(frame_header_text, frame_content)
print(_human_format_frame(frame_header_text, frame_content), file=outfile)

# TLV area
_sectionoff += header["img_size"]
protected_tlv_size = header["protected_tlv_size"]
if protected_tlv_size != 0:
# Protected TLV area
section_name = f"Protected TLV area (offset: {hex(_sectionoff)})"
print_in_row(section_name)
print("magic: ", hex(tlv_area["tlv_hdr_prot"]["magic"]))
print("area size:", hex(tlv_area["tlv_hdr_prot"]["tlv_tot"]))
print_tlv_records(tlv_area["tlvs_prot"])
print("#" * _LINE_LENGTH)
print(_human_format_row(section_name), file=outfile)
print("magic: ", hex(tlv_area["tlv_hdr_prot"]["magic"]), file=outfile)
print("area size:", hex(tlv_area["tlv_hdr_prot"]["tlv_tot"]), file=outfile)
print(_human_format_tlv_records(tlv_area["tlvs_prot"]), file=outfile)
print("#" * _LINE_LENGTH, file=outfile)

_sectionoff += protected_tlv_size
section_name = f"TLV area (offset: {hex(_sectionoff)})"
print_in_row(section_name)
print("magic: ", hex(tlv_area["tlv_hdr"]["magic"]))
print("area size:", hex(tlv_area["tlv_hdr"]["tlv_tot"]))
print_tlv_records(tlv_area["tlvs"])
print("#" * _LINE_LENGTH)

if _img_pad_size:
print(_human_format_row(section_name), file=outfile)
print("magic: ", hex(tlv_area["tlv_hdr"]["magic"]), file=outfile)
print("area size:", hex(tlv_area["tlv_hdr"]["tlv_tot"]), file=outfile)
print(_human_format_tlv_records(tlv_area["tlvs"]), file=outfile)
print("#" * _LINE_LENGTH, file=outfile)

# Check if trailer has data (for image padding and trailer info)
if trailer.get("magic"):
trailer_magic = trailer["magic"]
_sectionoff += tlv_area["tlv_hdr"]["tlv_tot"]
_erased_val = b[_sectionoff]
frame_header_text = f"Image padding (offset: {hex(_sectionoff)})"
frame_content = f"padding ({hex(_erased_val)})"
print_in_frame(frame_header_text, frame_content)
# Note: We don't have access to original binary data here, so skip padding details

# Image trailer
section_name = "Image trailer (offset: unknown)"
print_in_row(section_name)
print(_human_format_row(section_name), file=outfile)
notice = "(Note: some fields may not be used, depending on the update strategy)\n"
notice = '\n'.join(notice[i:i + _LINE_LENGTH] for i in range(0, len(notice), _LINE_LENGTH))
print(notice)
print("swap status: (len: unknown)")
print("enc. keys: ", parse_enc(key_field_len))
print("swap size: ", parse_size(hex(swap_size)))
print("swap_info: ", parse_status(hex(swap_info)))
print("copy_done: ", parse_status(hex(copy_done)))
print("image_ok: ", parse_status(hex(image_ok)))
print("boot magic: ", parse_boot_magic(trailer_magic))
print()
print(notice, file=outfile)
print("swap status: (len: unknown)", file=outfile)
print("enc. keys: ", parse_enc(trailer.get("key_field_len")), file=outfile)

# Only print trailer fields if they exist
if "swap_size" in trailer:
print("swap size: ", parse_size(hex(trailer["swap_size"])), file=outfile)
if "swap_info" in trailer:
print("swap_info: ", parse_status(hex(trailer["swap_info"])), file=outfile)
if "copy_done" in trailer:
print("copy_done: ", parse_status(hex(trailer["copy_done"])), file=outfile)
if "image_ok" in trailer:
print("image_ok: ", parse_status(hex(trailer["image_ok"])), file=outfile)
print("boot magic: ", parse_boot_magic(trailer_magic), file=outfile)
print(file=outfile)

footer = "End of Image "
print_in_row(footer)
print(_human_format_row(footer), file=outfile)

def _json_default_serializer(obj):
"""Convert non-JSON-serializable objects to JSON-serializable format."""
if isinstance(obj, (bytes, bytearray)):
return obj.hex()
raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")


def _write_format(imginfo, output_format, out):
"""Write image info in the specified format to the output stream."""
if output_format == 'human':
_write_format_human(imginfo, out)

elif output_format == 'yaml':
yaml.dump(imginfo, out, sort_keys=False)

elif output_format == 'json':
json.dump(imginfo, out, indent=2, default=_json_default_serializer)
if out == sys.stdout:
print() # Add newline after JSON output to stdout
else:
raise ValueError(f"Invalid output format: {output_format}")


def dump_imginfo(imgfile, outfile=None, output_format=None, silent=False):
"""Parse a signed image binary and print/save the available information."""

# Note: silent parameter is kept for backward compatibility but is ignored.
# The function's purpose is to output data, so silent doesn't make sense here.

# Determine output format based on backward compatibility rules
if output_format is None:
if outfile is None:
output_format = 'human' # no --outfile defaults to human-friendly
else:
output_format = 'yaml' # --outfile without --format defaults to yaml

imginfo = _read_imginfo(imgfile)

if outfile:
with open(outfile, "w") as out:
_write_format(imginfo, output_format, out)
else:
_write_format(imginfo, output_format, sys.stdout)

11 changes: 6 additions & 5 deletions scripts/imgtool/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,15 +250,16 @@ def verify(key, imgfile):

@click.argument('imgfile')
@click.option('-o', '--outfile', metavar='filename', required=False,
help='Save image information to outfile in YAML format')
help='Save image information to outfile')
@click.option('-f', '--format', 'output_format',
type=click.Choice(['human', 'yaml', 'json']),
help='Output format (human, yaml, json). Default: human for stdout, yaml for file')
@click.option('-s', '--silent', default=False, is_flag=True,
help='Do not print image information to output')
@click.command(help='Print header, TLV area and trailer information '
'of a signed image')
def dumpinfo(imgfile, outfile, silent):
dump_imginfo(imgfile, outfile, silent)
if not silent:
print("dumpinfo has run successfully")
def dumpinfo(imgfile, outfile, output_format, silent):
dump_imginfo(imgfile, outfile, output_format, silent)


def validate_version(ctx, param, value):
Expand Down
Loading