mcuboot/scripts/imgtool/dumpinfo.py

291 lines
11 KiB
Python

# Copyright 2023 Arm Limited
#
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Parse and print header, TLV area and trailer information of a signed image.
"""
from imgtool import image
import click
import struct
import yaml
import os.path
import sys
HEADER_ITEMS = ("magic", "load_addr", "hdr_size", "protected_tlv_size",
"img_size", "flags", "version")
TLV_TYPES = dict((value, key) for key, value in image.TLV_VALUES.items())
BOOT_MAGIC = bytes([
0x77, 0xc2, 0x95, 0xf3,
0x60, 0xd2, 0xef, 0x7f,
0x35, 0x52, 0x50, 0x0f,
0x2c, 0xb6, 0x79, 0x80, ])
BOOT_MAGIC_2 = bytes([
0x2d, 0xe1, 0x5d, 0x29,
0x41, 0x0b, 0x8d, 0x77,
0x67, 0x9c, 0x11, 0x0f,
0x1f, 0x8a, ])
BOOT_MAGIC_SIZE = len(BOOT_MAGIC)
_LINE_LENGTH = 60
def print_tlv_records(tlv_list):
indent = _LINE_LENGTH // 8
for tlv in tlv_list:
print(" " * indent, "-" * 45)
tlv_type, tlv_length, tlv_data = tlv.keys()
print(" " * indent, "{}: {} ({})".format(
tlv_type, TLV_TYPES[tlv[tlv_type]], hex(tlv[tlv_type])))
print(" " * indent, "{}: ".format(tlv_length), hex(tlv[tlv_length]))
print(" " * indent, "{}: ".format(tlv_data), end="")
for j, data in enumerate(tlv[tlv_data]):
print("{0:#04x}".format(data), end=" ")
if ((j+1) % 8 == 0) and ((j+1) != len(tlv[tlv_data])):
print("\n", end=" " * (indent+7))
print()
def dump_imginfo(imgfile, outfile=None, silent=False):
'''Parse a signed image binary and print/save the available information.'''
try:
with open(imgfile, "rb") as f:
b = f.read()
except FileNotFoundError:
raise click.UsageError("Image file not found ({})".format(imgfile))
# Parsing the image header
_header = struct.unpack('IIHHIIBBHI', b[:28])
# Image version consists of the last 4 item ('BBHI')
_version = _header[-4:]
header = {}
for i, key in enumerate(HEADER_ITEMS):
if key == "version":
header[key] = "{}.{}.{}+{}".format(*_version)
else:
header[key] = _header[i]
# Parsing the TLV area
tlv_area = {"tlv_hdr_prot": {},
"tlvs_prot": [],
"tlv_hdr": {},
"tlvs": []}
tlv_off = header["hdr_size"] + header["img_size"]
protected_tlv_size = header["protected_tlv_size"]
if protected_tlv_size != 0:
_tlv_prot_head = struct.unpack(
'HH',
b[tlv_off:(tlv_off + image.TLV_INFO_SIZE)])
tlv_area["tlv_hdr_prot"]["magic"] = _tlv_prot_head[0]
tlv_area["tlv_hdr_prot"]["tlv_tot"] = _tlv_prot_head[1]
tlv_end = tlv_off + tlv_area["tlv_hdr_prot"]["tlv_tot"]
tlv_off += image.TLV_INFO_SIZE
# Iterating through the protected TLV area
while tlv_off < tlv_end:
tlv_type, tlv_len = struct.unpack(
'HH',
b[tlv_off:(tlv_off + image.TLV_INFO_SIZE)])
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})
tlv_off += tlv_len
_tlv_head = struct.unpack('HH', b[tlv_off:(tlv_off + image.TLV_INFO_SIZE)])
tlv_area["tlv_hdr"]["magic"] = _tlv_head[0]
tlv_area["tlv_hdr"]["tlv_tot"] = _tlv_head[1]
tlv_end = tlv_off + tlv_area["tlv_hdr"]["tlv_tot"]
tlv_off += image.TLV_INFO_SIZE
# Iterating through the TLV area
while tlv_off < tlv_end:
tlv_type, tlv_len = struct.unpack(
'HH',
b[tlv_off:(tlv_off + image.TLV_INFO_SIZE)])
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})
tlv_off += tlv_len
_img_pad_size = len(b) - tlv_end
if _img_pad_size:
# Parsing the image trailer
trailer = {}
trailer_off = -BOOT_MAGIC_SIZE
trailer_magic = b[trailer_off:]
trailer["magic"] = trailer_magic
max_align = None
if trailer_magic == BOOT_MAGIC:
# The maximum supported write alignment is the default 8 Bytes
max_align = 8
elif trailer_magic[-len(BOOT_MAGIC_2):] == BOOT_MAGIC_2:
# The alignment value is encoded in the magic field
max_align = int(trailer_magic[:2], 0)
else:
# Invalid magic: the rest of the image trailer cannot be processed.
print("Warning: the trailer magic value is invalid!")
if max_align is not None:
if max_align > BOOT_MAGIC_SIZE:
trailer_off -= max_align - BOOT_MAGIC_SIZE
# Parsing rest of the trailer fields
trailer_off -= max_align
image_ok = b[trailer_off]
trailer["image_ok"] = image_ok
trailer_off -= max_align
copy_done = b[trailer_off]
trailer["copy_done"] = copy_done
trailer_off -= max_align
swap_info = b[trailer_off]
trailer["swap_info"] = swap_info
trailer_off -= max_align
swap_size = int.from_bytes(b[trailer_off:(trailer_off + 4)],
"little")
trailer["swap_size"] = swap_size
# Encryption key 0/1
key_field_len = None
if ((header["flags"] & image.IMAGE_F["ENCRYPTED_AES128"]) or
(header["flags"] & image.IMAGE_F["ENCRYPTED_AES256"])):
# The image is encrypted
# 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
# 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)
###############################################################################
if silent:
sys.exit(0)
print("Printing content of signed image:", os.path.basename(imgfile), "\n")
# Image header
str1 = "#### Image header (offset: 0x0) "
str2 = "#" * (_LINE_LENGTH - len(str1))
print(str1 + str2)
for key, value in header.items():
if key == "flags":
if not value:
flag_string = hex(value)
else:
flag_string = ""
for flag in image.IMAGE_F.keys():
if (value & image.IMAGE_F[flag]):
if flag_string:
flag_string += ("\n" + (" " * 20))
flag_string += "{} ({})".format(
flag, hex(image.IMAGE_F[flag]))
value = flag_string
if type(value) != str:
value = hex(value)
print(key, ":", " " * (19-len(key)), value, sep="")
print("#" * _LINE_LENGTH)
# Image payload
_sectionoff = header["hdr_size"]
sepc = " "
str1 = "#### Payload (offset: {}) ".format(hex(_sectionoff))
str2 = "#" * (_LINE_LENGTH - len(str1))
print(str1 + str2)
print("|", sepc * (_LINE_LENGTH - 2), "|", sep="")
str1 = "FW image (size: {} Bytes)".format(hex(header["img_size"]))
numc = (_LINE_LENGTH - len(str1)) // 2
str2 = "|" + (sepc * (numc - 1))
str3 = sepc * (_LINE_LENGTH - len(str2) - len(str1) - 1) + "|"
print(str2, str1, str3, sep="")
print("|", sepc * (_LINE_LENGTH - 2), "|", sep="")
print("#" * _LINE_LENGTH)
# TLV area
_sectionoff += header["img_size"]
if protected_tlv_size != 0:
# Protected TLV area
str1 = "#### Protected TLV area (offset: {}) ".format(hex(_sectionoff))
str2 = "#" * (_LINE_LENGTH - len(str1))
print(str1 + str2)
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)
_sectionoff += protected_tlv_size
str1 = "#### TLV area (offset: {}) ".format(hex(_sectionoff))
str2 = "#" * (_LINE_LENGTH - len(str1))
print(str1 + str2)
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:
_sectionoff += tlv_area["tlv_hdr"]["tlv_tot"]
_erased_val = b[_sectionoff]
str1 = "#### Image padding (offset: {}) ".format(hex(_sectionoff))
str2 = "#" * (_LINE_LENGTH - len(str1))
print(str1 + str2)
print("|", sepc * (_LINE_LENGTH - 2), "|", sep="")
str1 = "padding ({})".format(hex(_erased_val))
numc = (_LINE_LENGTH - len(str1)) // 2
str2 = "|" + (sepc * (numc - 1))
str3 = sepc * (_LINE_LENGTH - len(str2) - len(str1) - 1) + "|"
print(str2, str1, str3, sep="")
print("|", sepc * (_LINE_LENGTH - 2), "|", sep="")
print("#" * _LINE_LENGTH)
# Image trailer
str1 = "#### Image trailer (offset: unknown) "
str2 = "#" * (_LINE_LENGTH - len(str1))
print(str1 + str2)
print("(Note: some field may not be used, \n"
" depending on the update strategy)\n")
print("swap status: (len: unknown)")
if key_field_len is not None:
print("enc. keys: (len: {}, if BOOT_SWAP_SAVE_ENCTLV is unset)"
.format(hex(key_field_len)))
print("swap size: ", hex(swap_size))
print("swap_info: ", hex(swap_info))
print("copy_done: ", hex(copy_done))
print("image_ok: ", hex(image_ok))
print("boot magic: ", end="")
for i in range(BOOT_MAGIC_SIZE):
print("{0:#04x}".format(trailer_magic[i]), end=" ")
if (i == (BOOT_MAGIC_SIZE/2 - 1)):
print("\n", end=" ")
print()
str1 = "#### End of Image "
str2 = "#" * (_LINE_LENGTH - len(str1))
print(str1 + str2)