feat(image_info): Image type autodetection

This commit is contained in:
radim.karnis
2022-08-02 16:01:56 +02:00
parent bf9fcdcfd5
commit 791a20bd55
4 changed files with 84 additions and 30 deletions

View File

@@ -187,13 +187,7 @@ This information corresponds to the headers described in :ref:`image-format`.
::
esptool.py --chip {IDF_TARGET_NAME} image_info --version 2 my_esp_app.bin
.. only:: not esp8266
.. note::
Note that ``--chip {IDF_TARGET_NAME}`` is required when reading {IDF_TARGET_NAME} images. Otherwise the default is ``--chip esp8266`` and the image will be interpreted as an invalid ESP8266 image.
esptool.py image_info --version 2 my_esp_app.bin
.. _merge-bin:

View File

@@ -33,20 +33,6 @@ def align_file_position(f, size):
f.seek(align, 1)
class ESPBOOTLOADER(object):
"""
These are constants related to software ESP8266 bootloader,
working with 'v2' image files
"""
# First byte of the "v2" application image
IMAGE_V2_MAGIC = 0xEA
# First 'segment' value in a "v2" application image,
# appears to be a constant version value?
IMAGE_V2_SEGMENT = 4
def LoadFirmwareImage(chip, filename):
"""
Load a firmware image. Can be for any supported SoC.
@@ -76,7 +62,7 @@ def LoadFirmwareImage(chip, filename):
f.seek(0)
if magic == ESPLoader.ESP_IMAGE_MAGIC:
return ESP8266ROMFirmwareImage(f)
elif magic == ESPBOOTLOADER.IMAGE_V2_MAGIC:
elif magic == ESP8266V2FirmwareImage.IMAGE_V2_MAGIC:
return ESP8266V2FirmwareImage(f)
else:
raise FatalError("Invalid image magic number: %d" % magic)
@@ -415,13 +401,19 @@ class ESP8266V2FirmwareImage(BaseFirmwareImage):
"""
ROM_LOADER = ESP8266ROM
# First byte of the "v2" application image
IMAGE_V2_MAGIC = 0xEA
# First 'segment' value in a "v2" application image,
# appears to be a constant version value?
IMAGE_V2_SEGMENT = 4
def __init__(self, load_file=None):
super(ESP8266V2FirmwareImage, self).__init__()
self.version = 2
if load_file is not None:
segments = self.load_common_header(load_file, ESPBOOTLOADER.IMAGE_V2_MAGIC)
if segments != ESPBOOTLOADER.IMAGE_V2_SEGMENT:
segments = self.load_common_header(load_file, self.IMAGE_V2_MAGIC)
if segments != self.IMAGE_V2_SEGMENT:
# segment count is not really segment count here,
# but we expect to see '4'
print(
@@ -489,8 +481,8 @@ class ESP8266V2FirmwareImage(BaseFirmwareImage):
f.write(
struct.pack(
b"<BBBBI",
ESPBOOTLOADER.IMAGE_V2_MAGIC,
ESPBOOTLOADER.IMAGE_V2_SEGMENT,
self.IMAGE_V2_MAGIC,
self.IMAGE_V2_SEGMENT,
self.flash_mode,
self.flash_size_freq,
self.entrypoint,

View File

@@ -33,7 +33,7 @@ from .loader import (
ESPLoader,
timeout_per_mb,
)
from .targets import CHIP_DEFS, CHIP_LIST, ROM_LIST
from .targets import CHIP_DEFS, CHIP_LIST, ESP8266ROM, ROM_LIST
from .util import (
FatalError,
NotImplementedInROMError,
@@ -708,9 +708,45 @@ def image_info(args):
except AttributeError:
pass # ESP8266 image has no append_digest field
if args.chip == "auto":
print("WARNING: --chip not specified, defaulting to ESP8266.")
args.chip = "esp8266"
with open(args.filename, "rb") as f:
# magic number
try:
common_header = f.read(8)
magic = common_header[0]
except IndexError:
raise FatalError("File is empty")
if magic not in [
ESPLoader.ESP_IMAGE_MAGIC,
ESP8266V2FirmwareImage.IMAGE_V2_MAGIC,
]:
raise FatalError(
"This is not a valid image "
"(invalid magic number: {:#x})".format(magic)
)
if args.chip == "auto":
try:
extended_header = f.read(16)
# reserved fields, should all be zero
if int.from_bytes(extended_header[7:-1], "little") != 0:
raise FatalError("Reserved fields not all zero")
# append_digest, either 0 or 1
if extended_header[-1] not in [0, 1]:
raise FatalError("Append digest field not 0 or 1")
chip_id = int.from_bytes(extended_header[4:5], "little")
for rom in [n for n in ROM_LIST if n != ESP8266ROM]:
if chip_id == rom.IMAGE_CHIP_ID:
args.chip = rom.CHIP_NAME
break
else:
raise FatalError(f"Unknown image chip ID ({chip_id})")
except FatalError:
args.chip = "esp8266"
print(f"Detected image type: {args.chip.upper()}")
image = LoadFirmwareImage(args.chip, args.filename)
if args.version == "2":

View File

@@ -132,6 +132,38 @@ class ImageInfoTests(unittest.TestCase):
"Wrong segment info",
)
def test_image_type_detection(self):
# ESP8266, version 1 and 2
out = self.run_image_info("auto", NODEMCU_FILE, "1")
self.assertTrue("Detected image type: ESP8266" in out)
self.assertTrue("Segment 1: len 0x05ed4" in out)
out = self.run_image_info("auto", NODEMCU_FILE, "2")
self.assertTrue("Detected image type: ESP8266" in out)
self.assertTrue("Flash freq: 40m" in out)
out = self.run_image_info("auto", "esp8266_deepsleep.bin", "2")
self.assertTrue("Detected image type: ESP8266" in out)
# ESP32, with and without detection
out = self.run_image_info("auto", "bootloader_esp32.bin", "2")
self.assertTrue("Detected image type: ESP32" in out)
out = self.run_image_info("auto", "helloworld-esp32_edit.bin", "2")
self.assertTrue("Detected image type: ESP32" in out)
out = self.run_image_info("esp32", "bootloader_esp32.bin", "2")
self.assertFalse("Detected image type: ESP32" in out)
# ESP32-C3
out = self.run_image_info("auto", "bootloader_esp32c3.bin", "2")
self.assertTrue("Detected image type: ESP32-C3" in out)
# ESP32-S3
out = self.run_image_info("auto", "bootloader_esp32s3.bin", "2")
self.assertTrue("Detected image type: ESP32-S3" in out)
@unittest.expectedFailure
def test_invalid_image_type_detection(self):
# Invalid image
self.run_image_info("auto", "one_kb.bin", "2")
if __name__ == "__main__":
unittest.main(buffer=True)