Skip to content

Commit c331664

Browse files
Simon Frankde-nordic
authored andcommitted
[nrf fromtree] imgtool: dumpinfo -f,--format option
Add -f, --format option to support multiple output formats (human, yaml, json). Default yaml for file, default human for stdout - backward compatible. Details: - Human output is unchanged - Refactored dump_imginfo() to separate data reading from formatting - JSON output uses hex strings for binary data (e.g., "e3a333ca...") - YAML and JSON have both "type" (int) and "type_name" (str) for TLVs - Image filename (basename) is included in all output formats - Argument `--silent` is silently ignored. Doesn't make sense for a command that outputs data. i.e. No output on success. This is the same as for other sub commands Signed-off-by: Simon Frank <simon.frank@lohmega.com> (cherry picked from commit 7448115) Signed-off-by: Dominik Ermel <dominik.ermel@nordicsemi.no>
1 parent 7602652 commit c331664

2 files changed

Lines changed: 137 additions & 77 deletions

File tree

scripts/imgtool/dumpinfo.py

Lines changed: 131 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
"""
1818
Parse and print header, TLV area and trailer information of a signed image.
1919
"""
20+
import json
2021
import os.path
2122
import struct
23+
import sys
2224

2325
import click
2426
import yaml
@@ -75,58 +77,58 @@ def parse_boot_magic(trailer_magic):
7577
return magic
7678

7779

78-
def print_in_frame(header_text, content):
80+
def _human_format_frame(header_text, content):
7981
sepc = " "
8082
header = "#### " + header_text + sepc
8183
post_header = "#" * (_LINE_LENGTH - len(header))
82-
print(header + post_header)
83-
84-
print("|", sepc * (_LINE_LENGTH - 2), "|", sep="")
84+
lines = []
85+
lines.append(header + post_header)
86+
lines.append("|" + sepc * (_LINE_LENGTH - 2) + "|")
8587
offset = (_LINE_LENGTH - len(content)) // 2
8688
pre = "|" + (sepc * (offset - 1))
8789
post = sepc * (_LINE_LENGTH - len(pre) - len(content) - 1) + "|"
88-
print(pre, content, post, sep="")
89-
print("|", sepc * (_LINE_LENGTH - 2), "|", sep="")
90-
print("#" * _LINE_LENGTH)
90+
lines.append(pre + content + post)
91+
lines.append("|" + sepc * (_LINE_LENGTH - 2) + "|")
92+
lines.append("#" * _LINE_LENGTH)
93+
return "\n".join(lines)
9194

9295

93-
def print_in_row(row_text):
96+
def _human_format_row(row_text):
9497
row_text = "#### " + row_text + " "
9598
fill = "#" * (_LINE_LENGTH - len(row_text))
96-
print(row_text + fill)
99+
return row_text + fill
97100

98101

99-
def print_tlv_records(tlv_list):
102+
def _human_format_tlv_records(tlv_list):
100103
indent = _LINE_LENGTH // 8
104+
lines = []
101105
for tlv in tlv_list:
102-
print(" " * indent, "-" * 45)
103-
tlv_type, tlv_length, tlv_data = tlv.keys()
106+
lines.append(" " * indent + " " + "-" * 45)
104107

105-
if tlv[tlv_type] in TLV_TYPES:
106-
print(" " * indent, f"{tlv_type}: {TLV_TYPES[tlv[tlv_type]]} ({hex(tlv[tlv_type])})")
107-
else:
108-
print(" " * indent, "{}: {} ({})".format(
109-
tlv_type, "UNKNOWN", hex(tlv[tlv_type])))
110-
print(" " * indent, f"{tlv_length}: ", hex(tlv[tlv_length]))
111-
print(" " * indent, f"{tlv_data}: ", end="")
108+
type_name = tlv["type_name"]
109+
type_hex = hex(tlv["type"])
110+
lines.append(" " * indent + f" type: {type_name} ({type_hex})")
111+
lines.append(" " * indent + f" len: {hex(tlv['len'])}")
112112

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

119122

120-
def dump_imginfo(imgfile, outfile=None, silent=False):
121-
"""Parse a signed image binary and print/save the available information."""
123+
def _read_imginfo(imgfile):
124+
"""Parse a signed image binary and return the image data structure."""
122125
trailer_magic = None
123126
# set to INVALID by default
124127
swap_size = 0x99
125128
swap_info = 0x99
126129
copy_done = 0x99
127130
image_ok = 0x99
128131
trailer = {}
129-
key_field_len = None
130132

131133
ext = os.path.splitext(imgfile)[1][1:].lower()
132134
try:
@@ -174,7 +176,10 @@ def dump_imginfo(imgfile, outfile=None, silent=False):
174176
tlv_off += image.TLV_INFO_SIZE
175177
tlv_data = b[tlv_off:(tlv_off + tlv_len)]
176178
tlv_area["tlvs_prot"].append(
177-
{"type": tlv_type, "len": tlv_len, "data": tlv_data})
179+
{"type": tlv_type,
180+
"type_name": TLV_TYPES.get(tlv_type, "UNKNOWN"),
181+
"len": tlv_len,
182+
"data": tlv_data})
178183
tlv_off += tlv_len
179184

180185
_tlv_head = struct.unpack('HH', b[tlv_off:(tlv_off + image.TLV_INFO_SIZE)])
@@ -192,7 +197,8 @@ def dump_imginfo(imgfile, outfile=None, silent=False):
192197
tlv_off += image.TLV_INFO_SIZE
193198
tlv_data = b[tlv_off:(tlv_off + tlv_len)]
194199
tlv_area["tlvs"].append(
195-
{"type": tlv_type, "len": tlv_len, "data": tlv_data})
200+
{"type": tlv_type, "type_name": TLV_TYPES.get(tlv_type, "UNKNOWN"),
201+
"len": tlv_len, "data": tlv_data})
196202
tlv_off += tlv_len
197203

198204
_img_pad_size = len(b) - tlv_end
@@ -241,26 +247,28 @@ def dump_imginfo(imgfile, outfile=None, silent=False):
241247
# Estimated value of key_field_len is correct if
242248
# BOOT_SWAP_SAVE_ENCTLV is unset
243249
key_field_len = image.align_up(16, max_align) * 2
250+
trailer["key_field_len"] = key_field_len
244251

245-
# Generating output yaml file
246-
if outfile is not None:
247-
imgdata = {"header": header,
248-
"tlv_area": tlv_area,
249-
"trailer": trailer}
250-
with open(outfile, "w") as outf:
251-
# sort_keys - from pyyaml 5.1
252-
yaml.dump(imgdata, outf, sort_keys=False)
252+
imginfo = {
253+
"filename": os.path.basename(imgfile),
254+
"header": header,
255+
"tlv_area": tlv_area,
256+
"trailer": trailer}
253257

254-
###############################################################################
258+
return imginfo
255259

256-
if silent:
257-
return
258260

259-
print("Printing content of signed image:", os.path.basename(imgfile), "\n")
261+
def _write_format_human(imginfo, outfile):
262+
filename = imginfo["filename"]
263+
header = imginfo["header"]
264+
tlv_area = imginfo["tlv_area"]
265+
trailer = imginfo["trailer"]
266+
267+
print("Printing content of signed image:", filename, "\n", file=outfile)
260268

261269
# Image header
262270
section_name = "Image header (offset: 0x0)"
263-
print_in_row(section_name)
271+
print(_human_format_row(section_name), file=outfile)
264272
for key, value in header.items():
265273
if key == "flags":
266274
if not value:
@@ -276,55 +284,106 @@ def dump_imginfo(imgfile, outfile=None, silent=False):
276284

277285
if not isinstance(value, str):
278286
value = hex(value)
279-
print(key, ":", " " * (19 - len(key)), value, sep="")
280-
print("#" * _LINE_LENGTH)
287+
print(key, ":", " " * (19 - len(key)), value, sep="", file=outfile)
288+
print("#" * _LINE_LENGTH, file=outfile)
281289

282290
# Image payload
283291
_sectionoff = header["hdr_size"]
284292
frame_header_text = f"Payload (offset: {hex(_sectionoff)})"
285293
frame_content = "FW image (size: {} Bytes)".format(hex(header["img_size"]))
286-
print_in_frame(frame_header_text, frame_content)
294+
print(_human_format_frame(frame_header_text, frame_content), file=outfile)
287295

288296
# TLV area
289297
_sectionoff += header["img_size"]
298+
protected_tlv_size = header["protected_tlv_size"]
290299
if protected_tlv_size != 0:
291300
# Protected TLV area
292301
section_name = f"Protected TLV area (offset: {hex(_sectionoff)})"
293-
print_in_row(section_name)
294-
print("magic: ", hex(tlv_area["tlv_hdr_prot"]["magic"]))
295-
print("area size:", hex(tlv_area["tlv_hdr_prot"]["tlv_tot"]))
296-
print_tlv_records(tlv_area["tlvs_prot"])
297-
print("#" * _LINE_LENGTH)
302+
print(_human_format_row(section_name), file=outfile)
303+
print("magic: ", hex(tlv_area["tlv_hdr_prot"]["magic"]), file=outfile)
304+
print("area size:", hex(tlv_area["tlv_hdr_prot"]["tlv_tot"]), file=outfile)
305+
print(_human_format_tlv_records(tlv_area["tlvs_prot"]), file=outfile)
306+
print("#" * _LINE_LENGTH, file=outfile)
298307

299308
_sectionoff += protected_tlv_size
300309
section_name = f"TLV area (offset: {hex(_sectionoff)})"
301-
print_in_row(section_name)
302-
print("magic: ", hex(tlv_area["tlv_hdr"]["magic"]))
303-
print("area size:", hex(tlv_area["tlv_hdr"]["tlv_tot"]))
304-
print_tlv_records(tlv_area["tlvs"])
305-
print("#" * _LINE_LENGTH)
306-
307-
if _img_pad_size:
310+
print(_human_format_row(section_name), file=outfile)
311+
print("magic: ", hex(tlv_area["tlv_hdr"]["magic"]), file=outfile)
312+
print("area size:", hex(tlv_area["tlv_hdr"]["tlv_tot"]), file=outfile)
313+
print(_human_format_tlv_records(tlv_area["tlvs"]), file=outfile)
314+
print("#" * _LINE_LENGTH, file=outfile)
315+
316+
# Check if trailer has data (for image padding and trailer info)
317+
if trailer.get("magic"):
318+
trailer_magic = trailer["magic"]
308319
_sectionoff += tlv_area["tlv_hdr"]["tlv_tot"]
309-
_erased_val = b[_sectionoff]
310-
frame_header_text = f"Image padding (offset: {hex(_sectionoff)})"
311-
frame_content = f"padding ({hex(_erased_val)})"
312-
print_in_frame(frame_header_text, frame_content)
320+
# Note: We don't have access to original binary data here, so skip padding details
313321

314322
# Image trailer
315323
section_name = "Image trailer (offset: unknown)"
316-
print_in_row(section_name)
324+
print(_human_format_row(section_name), file=outfile)
317325
notice = "(Note: some fields may not be used, depending on the update strategy)\n"
318326
notice = '\n'.join(notice[i:i + _LINE_LENGTH] for i in range(0, len(notice), _LINE_LENGTH))
319-
print(notice)
320-
print("swap status: (len: unknown)")
321-
print("enc. keys: ", parse_enc(key_field_len))
322-
print("swap size: ", parse_size(hex(swap_size)))
323-
print("swap_info: ", parse_status(hex(swap_info)))
324-
print("copy_done: ", parse_status(hex(copy_done)))
325-
print("image_ok: ", parse_status(hex(image_ok)))
326-
print("boot magic: ", parse_boot_magic(trailer_magic))
327-
print()
327+
print(notice, file=outfile)
328+
print("swap status: (len: unknown)", file=outfile)
329+
print("enc. keys: ", parse_enc(trailer.get("key_field_len")), file=outfile)
330+
331+
# Only print trailer fields if they exist
332+
if "swap_size" in trailer:
333+
print("swap size: ", parse_size(hex(trailer["swap_size"])), file=outfile)
334+
if "swap_info" in trailer:
335+
print("swap_info: ", parse_status(hex(trailer["swap_info"])), file=outfile)
336+
if "copy_done" in trailer:
337+
print("copy_done: ", parse_status(hex(trailer["copy_done"])), file=outfile)
338+
if "image_ok" in trailer:
339+
print("image_ok: ", parse_status(hex(trailer["image_ok"])), file=outfile)
340+
print("boot magic: ", parse_boot_magic(trailer_magic), file=outfile)
341+
print(file=outfile)
328342

329343
footer = "End of Image "
330-
print_in_row(footer)
344+
print(_human_format_row(footer), file=outfile)
345+
346+
def _json_default_serializer(obj):
347+
"""Convert non-JSON-serializable objects to JSON-serializable format."""
348+
if isinstance(obj, (bytes, bytearray)):
349+
return obj.hex()
350+
raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")
351+
352+
353+
def _write_format(imginfo, output_format, out):
354+
"""Write image info in the specified format to the output stream."""
355+
if output_format == 'human':
356+
_write_format_human(imginfo, out)
357+
358+
elif output_format == 'yaml':
359+
yaml.dump(imginfo, out, sort_keys=False)
360+
361+
elif output_format == 'json':
362+
json.dump(imginfo, out, indent=2, default=_json_default_serializer)
363+
if out == sys.stdout:
364+
print() # Add newline after JSON output to stdout
365+
else:
366+
raise ValueError(f"Invalid output format: {output_format}")
367+
368+
369+
def dump_imginfo(imgfile, outfile=None, output_format=None, silent=False):
370+
"""Parse a signed image binary and print/save the available information."""
371+
372+
# Note: silent parameter is kept for backward compatibility but is ignored.
373+
# The function's purpose is to output data, so silent doesn't make sense here.
374+
375+
# Determine output format based on backward compatibility rules
376+
if output_format is None:
377+
if outfile is None:
378+
output_format = 'human' # no --outfile defaults to human-friendly
379+
else:
380+
output_format = 'yaml' # --outfile without --format defaults to yaml
381+
382+
imginfo = _read_imginfo(imgfile)
383+
384+
if outfile:
385+
with open(outfile, "w") as out:
386+
_write_format(imginfo, output_format, out)
387+
else:
388+
_write_format(imginfo, output_format, sys.stdout)
389+

scripts/imgtool/main.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -246,15 +246,16 @@ def verify(key, imgfile):
246246

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

259260

260261
def validate_version(ctx, param, value):

0 commit comments

Comments
 (0)