1# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton,
2# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
3#
4# SPDX-License-Identifier: GPL-2.0-or-later
5
6import binascii
7import copy
8import hashlib
9import io
10import os
11import re
12import struct
13import tempfile
14from typing import IO, Optional
15
16from intelhex import HexRecordError, IntelHex
17
18from .loader import ESPLoader
19from .targets import (
20    ESP32C2ROM,
21    ESP32C3ROM,
22    ESP32C5ROM,
23    ESP32C5BETA3ROM,
24    ESP32C6BETAROM,
25    ESP32C6ROM,
26    ESP32C61ROM,
27    ESP32H2BETA1ROM,
28    ESP32H2BETA2ROM,
29    ESP32H2ROM,
30    ESP32P4ROM,
31    ESP32ROM,
32    ESP32S2ROM,
33    ESP32S3BETA2ROM,
34    ESP32S3ROM,
35    ESP8266ROM,
36)
37from .util import FatalError, byte, pad_to
38
39
40def align_file_position(f, size):
41    """Align the position in the file to the next block of specified size"""
42    align = (size - 1) - (f.tell() % size)
43    f.seek(align, 1)
44
45
46def intel_hex_to_bin(file: IO[bytes], start_addr: Optional[int] = None) -> IO[bytes]:
47    """Convert IntelHex file to temp binary file with padding from start_addr
48    If hex file was detected return temp bin file object; input file otherwise"""
49    INTEL_HEX_MAGIC = b":"
50    magic = file.read(1)
51    file.seek(0)
52    try:
53        if magic == INTEL_HEX_MAGIC:
54            ih = IntelHex()
55            ih.loadhex(file.name)
56            file.close()
57            bin = tempfile.NamedTemporaryFile(suffix=".bin", delete=False)
58            ih.tobinfile(bin, start=start_addr)
59            return bin
60        else:
61            return file
62    except (HexRecordError, UnicodeDecodeError):
63        # file started with HEX magic but the rest was not according to the standard
64        return file
65
66
67def LoadFirmwareImage(chip, image_file):
68    """
69    Load a firmware image. Can be for any supported SoC.
70
71    ESP8266 images will be examined to determine if they are original ROM firmware
72    images (ESP8266ROMFirmwareImage) or "v2" OTA bootloader images.
73
74    Returns a BaseFirmwareImage subclass, either ESP8266ROMFirmwareImage (v1)
75    or ESP8266V2FirmwareImage (v2).
76    """
77
78    def select_image_class(f, chip):
79        chip = re.sub(r"[-()]", "", chip.lower())
80        if chip != "esp8266":
81            return {
82                "esp32": ESP32FirmwareImage,
83                "esp32s2": ESP32S2FirmwareImage,
84                "esp32s3beta2": ESP32S3BETA2FirmwareImage,
85                "esp32s3": ESP32S3FirmwareImage,
86                "esp32c3": ESP32C3FirmwareImage,
87                "esp32c6beta": ESP32C6BETAFirmwareImage,
88                "esp32h2beta1": ESP32H2BETA1FirmwareImage,
89                "esp32h2beta2": ESP32H2BETA2FirmwareImage,
90                "esp32c2": ESP32C2FirmwareImage,
91                "esp32c6": ESP32C6FirmwareImage,
92                "esp32c61": ESP32C61FirmwareImage,
93                "esp32c5": ESP32C5FirmwareImage,
94                "esp32c5beta3": ESP32C5BETA3FirmwareImage,
95                "esp32h2": ESP32H2FirmwareImage,
96                "esp32p4": ESP32P4FirmwareImage,
97            }[chip](f)
98        else:  # Otherwise, ESP8266 so look at magic to determine the image type
99            magic = ord(f.read(1))
100            f.seek(0)
101            if magic == ESPLoader.ESP_IMAGE_MAGIC:
102                return ESP8266ROMFirmwareImage(f)
103            elif magic == ESP8266V2FirmwareImage.IMAGE_V2_MAGIC:
104                return ESP8266V2FirmwareImage(f)
105            else:
106                raise FatalError("Invalid image magic number: %d" % magic)
107
108    if isinstance(image_file, str):
109        with open(image_file, "rb") as f:
110            return select_image_class(f, chip)
111    return select_image_class(image_file, chip)
112
113
114class ImageSegment(object):
115    """Wrapper class for a segment in an ESP image
116    (very similar to a section in an ELFImage also)"""
117
118    def __init__(self, addr, data, file_offs=None, flags=0, align=4):
119        self.addr = addr
120        self.data = data
121        self.file_offs = file_offs
122        self.flags = flags
123        self.align = align
124        self.include_in_checksum = True
125        if self.addr != 0:
126            self.pad_to_alignment(
127                4
128            )  # pad all "real" ImageSegments 4 byte aligned length
129
130    def copy_with_new_addr(self, new_addr):
131        """Return a new ImageSegment with same data, but mapped at
132        a new address."""
133        return ImageSegment(new_addr, self.data, 0)
134
135    def split_image(self, split_len):
136        """Return a new ImageSegment which splits "split_len" bytes
137        from the beginning of the data. Remaining bytes are kept in
138        this segment object (and the start address is adjusted to match.)"""
139        result = copy.copy(self)
140        result.data = self.data[:split_len]
141        self.data = self.data[split_len:]
142        self.addr += split_len
143        self.file_offs = None
144        result.file_offs = None
145        return result
146
147    def __repr__(self):
148        r = "len 0x%05x load 0x%08x" % (len(self.data), self.addr)
149        if self.file_offs is not None:
150            r += " file_offs 0x%08x" % (self.file_offs)
151        return r
152
153    def get_memory_type(self, image):
154        """
155        Return a list describing the memory type(s) that is covered by this
156        segment's start address.
157        """
158        return [
159            map_range[2]
160            for map_range in image.ROM_LOADER.MEMORY_MAP
161            if map_range[0] <= self.addr < map_range[1]
162        ]
163
164    def pad_to_alignment(self, alignment):
165        self.data = pad_to(self.data, alignment, b"\x00")
166
167    def addr_align(self, alignment):
168        end_addr = self.addr + len(self.data)
169        addr_mod = end_addr % alignment
170        if addr_mod != 0:
171            end_addr += alignment - addr_mod
172        return end_addr
173
174    def pad_to_addr(self, addr):
175        pad = addr - (self.addr + len(self.data))
176        if pad > 0:
177            self.data += b"\x00" * pad
178
179
180class ELFSection(ImageSegment):
181    """Wrapper class for a section in an ELF image, has a section
182    name as well as the common properties of an ImageSegment."""
183
184    def __init__(self, name, addr, data, flags, align=4):
185        super(ELFSection, self).__init__(addr, data, flags=flags, align=align)
186        self.name = name.decode("utf-8")
187
188    def __repr__(self):
189        return "%s %s" % (self.name, super(ELFSection, self).__repr__())
190
191
192class BaseFirmwareImage(object):
193    SEG_HEADER_LEN = 8
194    SHA256_DIGEST_LEN = 32
195    ELF_FLAG_WRITE = 0x1
196    ELF_FLAG_READ = 0x2
197    ELF_FLAG_EXEC = 0x4
198
199    """ Base class with common firmware image functions """
200
201    def __init__(self):
202        self.segments = []
203        self.entrypoint = 0
204        self.elf_sha256 = None
205        self.elf_sha256_offset = 0
206        self.pad_to_size = 0
207
208    def load_common_header(self, load_file, expected_magic):
209        (
210            magic,
211            segments,
212            self.flash_mode,
213            self.flash_size_freq,
214            self.entrypoint,
215        ) = struct.unpack("<BBBBI", load_file.read(8))
216
217        if magic != expected_magic:
218            raise FatalError("Invalid firmware image magic=0x%x" % (magic))
219        return segments
220
221    def verify(self):
222        if len(self.segments) > 16:
223            raise FatalError(
224                "Invalid segment count %d (max 16). "
225                "Usually this indicates a linker script problem." % len(self.segments)
226            )
227
228    def load_segment(self, f, is_irom_segment=False):
229        """Load the next segment from the image file"""
230        file_offs = f.tell()
231        (offset, size) = struct.unpack("<II", f.read(8))
232        self.warn_if_unusual_segment(offset, size, is_irom_segment)
233        segment_data = f.read(size)
234        if len(segment_data) < size:
235            raise FatalError(
236                "End of file reading segment 0x%x, length %d (actual length %d)"
237                % (offset, size, len(segment_data))
238            )
239        segment = ImageSegment(offset, segment_data, file_offs)
240        self.segments.append(segment)
241        return segment
242
243    def warn_if_unusual_segment(self, offset, size, is_irom_segment):
244        if not is_irom_segment:
245            if offset > 0x40200000 or offset < 0x3FFE0000 or size > 65536:
246                print("WARNING: Suspicious segment 0x%x, length %d" % (offset, size))
247
248    def maybe_patch_segment_data(self, f, segment_data):
249        """
250        If SHA256 digest of the ELF file needs to be inserted into this segment, do so.
251        Returns segment data.
252        """
253        segment_len = len(segment_data)
254        file_pos = f.tell()  # file_pos is position in the .bin file
255        if (
256            self.elf_sha256_offset >= file_pos
257            and self.elf_sha256_offset < file_pos + segment_len
258        ):
259            # SHA256 digest needs to be patched into this binary segment,
260            # calculate offset of the digest inside the binary segment.
261            patch_offset = self.elf_sha256_offset - file_pos
262            # Sanity checks
263            if (
264                patch_offset < self.SEG_HEADER_LEN
265                or patch_offset + self.SHA256_DIGEST_LEN > segment_len
266            ):
267                raise FatalError(
268                    "Cannot place SHA256 digest on segment boundary"
269                    "(elf_sha256_offset=%d, file_pos=%d, segment_size=%d)"
270                    % (self.elf_sha256_offset, file_pos, segment_len)
271                )
272            # offset relative to the data part
273            patch_offset -= self.SEG_HEADER_LEN
274            if (
275                segment_data[patch_offset : patch_offset + self.SHA256_DIGEST_LEN]
276                != b"\x00" * self.SHA256_DIGEST_LEN
277            ):
278                raise FatalError(
279                    "Contents of segment at SHA256 digest offset 0x%x are not all zero."
280                    " Refusing to overwrite." % self.elf_sha256_offset
281                )
282            assert len(self.elf_sha256) == self.SHA256_DIGEST_LEN
283            segment_data = (
284                segment_data[0:patch_offset]
285                + self.elf_sha256
286                + segment_data[patch_offset + self.SHA256_DIGEST_LEN :]
287            )
288        return segment_data
289
290    def save_segment(self, f, segment, checksum=None):
291        """
292        Save the next segment to the image file,
293        return next checksum value if provided
294        """
295        segment_data = self.maybe_patch_segment_data(f, segment.data)
296        f.write(struct.pack("<II", segment.addr, len(segment_data)))
297        f.write(segment_data)
298        if checksum is not None:
299            return ESPLoader.checksum(segment_data, checksum)
300
301    def save_flash_segment(self, f, segment, checksum=None):
302        """
303        Save the next segment to the image file, return next checksum value if provided
304        """
305        if self.ROM_LOADER.CHIP_NAME == "ESP32":
306            # Work around a bug in ESP-IDF 2nd stage bootloader, that it didn't map the
307            # last MMU page, if an IROM/DROM segment was < 0x24 bytes
308            # over the page boundary.
309            segment_end_pos = f.tell() + len(segment.data) + self.SEG_HEADER_LEN
310            segment_len_remainder = segment_end_pos % self.IROM_ALIGN
311            if segment_len_remainder < 0x24:
312                segment.data += b"\x00" * (0x24 - segment_len_remainder)
313        return self.save_segment(f, segment, checksum)
314
315    def read_checksum(self, f):
316        """Return ESPLoader checksum from end of just-read image"""
317        # Skip the padding. The checksum is stored in the last byte so that the
318        # file is a multiple of 16 bytes.
319        align_file_position(f, 16)
320        return ord(f.read(1))
321
322    def calculate_checksum(self):
323        """
324        Calculate checksum of loaded image, based on segments in
325        segment array.
326        """
327        checksum = ESPLoader.ESP_CHECKSUM_MAGIC
328        for seg in self.segments:
329            if seg.include_in_checksum:
330                checksum = ESPLoader.checksum(seg.data, checksum)
331        return checksum
332
333    def append_checksum(self, f, checksum):
334        """Append ESPLoader checksum to the just-written image"""
335        align_file_position(f, 16)
336        f.write(struct.pack(b"B", checksum))
337
338    def write_common_header(self, f, segments):
339        f.write(
340            struct.pack(
341                "<BBBBI",
342                ESPLoader.ESP_IMAGE_MAGIC,
343                len(segments),
344                self.flash_mode,
345                self.flash_size_freq,
346                self.entrypoint,
347            )
348        )
349
350    def is_irom_addr(self, addr):
351        """
352        Returns True if an address starts in the irom region.
353        Valid for ESP8266 only.
354        """
355        return ESP8266ROM.IROM_MAP_START <= addr < ESP8266ROM.IROM_MAP_END
356
357    def get_irom_segment(self):
358        irom_segments = [s for s in self.segments if self.is_irom_addr(s.addr)]
359        if len(irom_segments) > 0:
360            if len(irom_segments) != 1:
361                raise FatalError(
362                    "Found %d segments that could be irom0. Bad ELF file?"
363                    % len(irom_segments)
364                )
365            return irom_segments[0]
366        return None
367
368    def get_non_irom_segments(self):
369        irom_segment = self.get_irom_segment()
370        return [s for s in self.segments if s != irom_segment]
371
372    def sort_segments(self):
373        if not self.segments:
374            return  # nothing to sort
375        self.segments = sorted(self.segments, key=lambda s: s.addr)
376
377    def merge_adjacent_segments(self):
378        if not self.segments:
379            return  # nothing to merge
380
381        segments = []
382        # The easiest way to merge the sections is the browse them backward.
383        for i in range(len(self.segments) - 1, 0, -1):
384            # elem is the previous section, the one `next_elem` may need to be
385            # merged in
386            elem = self.segments[i - 1]
387            next_elem = self.segments[i]
388            elem_pad_addr = elem.addr_align(next_elem.align)
389
390            # When creating the images from 3rd-party frameworks ELFs, the merging
391            # could bring together segments with incompatible alignment requirements.
392            # At this point, we add padding so the resulting placement respects the
393            # original alignment requirements of those segments.
394            if self.ROM_LOADER != ESP8266ROM and self.ram_only_header:
395                if (
396                    elem_pad_addr != elem.addr + len(elem.data)
397                    and elem_pad_addr == next_elem.addr
398                ):
399                    print(
400                        "Info: inserting %d bytes padding between %s and %s"
401                        % (
402                            next_elem.addr - (elem.addr + len(elem.data)),
403                            elem.name,
404                            next_elem.name,
405                        )
406                    )
407                    elem.pad_to_addr(elem_pad_addr)
408            if all(
409                (
410                    elem.get_memory_type(self) == next_elem.get_memory_type(self),
411                    elem.include_in_checksum == next_elem.include_in_checksum,
412                    next_elem.addr == elem.addr + len(elem.data),
413                    next_elem.flags & self.ELF_FLAG_EXEC
414                    == elem.flags & self.ELF_FLAG_EXEC,
415                )
416            ):
417                # Merge any segment that ends where the next one starts,
418                # without spanning memory types
419                #
420                # (don't 'pad' any gaps here as they may be excluded from the image
421                # due to 'noinit' or other reasons.)
422                elem.data += next_elem.data
423            else:
424                # The section next_elem cannot be merged into the previous one,
425                # which means it needs to be part of the final segments.
426                # As we are browsing the list backward, the elements need to be
427                # inserted at the beginning of the final list.
428                segments.insert(0, next_elem)
429
430        # The first segment will always be here as it cannot be merged into any
431        # "previous" section.
432        segments.insert(0, self.segments[0])
433
434        # note: we could sort segments here as well, but the ordering of segments is
435        # sometimes important for other reasons (like embedded ELF SHA-256),
436        # so we assume that the linker script will have produced any adjacent sections
437        # in linear order in the ELF, anyhow.
438        self.segments = segments
439
440    def set_mmu_page_size(self, size):
441        """
442        If supported, this should be overridden by the chip-specific class.
443        Gets called in elf2image.
444        """
445        print(
446            "WARNING: Changing MMU page size is not supported on {}! "
447            "Defaulting to 64KB.".format(self.ROM_LOADER.CHIP_NAME)
448        )
449
450
451class ESP8266ROMFirmwareImage(BaseFirmwareImage):
452    """'Version 1' firmware image, segments loaded directly by the ROM bootloader."""
453
454    ROM_LOADER = ESP8266ROM
455
456    def __init__(self, load_file=None):
457        super(ESP8266ROMFirmwareImage, self).__init__()
458        self.flash_mode = 0
459        self.flash_size_freq = 0
460        self.version = 1
461
462        if load_file is not None:
463            segments = self.load_common_header(load_file, ESPLoader.ESP_IMAGE_MAGIC)
464
465            for _ in range(segments):
466                self.load_segment(load_file)
467            self.checksum = self.read_checksum(load_file)
468
469            self.verify()
470
471    def default_output_name(self, input_file):
472        """Derive a default output name from the ELF name."""
473        return input_file + "-"
474
475    def save(self, basename):
476        """Save a set of V1 images for flashing. Parameter is a base filename."""
477        # IROM data goes in its own plain binary file
478        irom_segment = self.get_irom_segment()
479        if irom_segment is not None:
480            with open(
481                "%s0x%05x.bin"
482                % (basename, irom_segment.addr - ESP8266ROM.IROM_MAP_START),
483                "wb",
484            ) as f:
485                f.write(irom_segment.data)
486
487        # everything but IROM goes at 0x00000 in an image file
488        normal_segments = self.get_non_irom_segments()
489        with open("%s0x00000.bin" % basename, "wb") as f:
490            self.write_common_header(f, normal_segments)
491            checksum = ESPLoader.ESP_CHECKSUM_MAGIC
492            for segment in normal_segments:
493                checksum = self.save_segment(f, segment, checksum)
494            self.append_checksum(f, checksum)
495
496
497ESP8266ROM.BOOTLOADER_IMAGE = ESP8266ROMFirmwareImage
498
499
500class ESP8266V2FirmwareImage(BaseFirmwareImage):
501    """'Version 2' firmware image, segments loaded by software bootloader stub
502    (ie Espressif bootloader or rboot)
503    """
504
505    ROM_LOADER = ESP8266ROM
506    # First byte of the "v2" application image
507    IMAGE_V2_MAGIC = 0xEA
508
509    # First 'segment' value in a "v2" application image,
510    # appears to be a constant version value?
511    IMAGE_V2_SEGMENT = 4
512
513    def __init__(self, load_file=None):
514        super(ESP8266V2FirmwareImage, self).__init__()
515        self.version = 2
516        if load_file is not None:
517            segments = self.load_common_header(load_file, self.IMAGE_V2_MAGIC)
518            if segments != self.IMAGE_V2_SEGMENT:
519                # segment count is not really segment count here,
520                # but we expect to see '4'
521                print(
522                    'Warning: V2 header has unexpected "segment" count %d (usually 4)'
523                    % segments
524                )
525
526            # irom segment comes before the second header
527            #
528            # the file is saved in the image with a zero load address
529            # in the header, so we need to calculate a load address
530            irom_segment = self.load_segment(load_file, True)
531            # for actual mapped addr, add ESP8266ROM.IROM_MAP_START + flashing_addr + 8
532            irom_segment.addr = 0
533            irom_segment.include_in_checksum = False
534
535            first_flash_mode = self.flash_mode
536            first_flash_size_freq = self.flash_size_freq
537            first_entrypoint = self.entrypoint
538            # load the second header
539
540            segments = self.load_common_header(load_file, ESPLoader.ESP_IMAGE_MAGIC)
541
542            if first_flash_mode != self.flash_mode:
543                print(
544                    "WARNING: Flash mode value in first header (0x%02x) disagrees "
545                    "with second (0x%02x). Using second value."
546                    % (first_flash_mode, self.flash_mode)
547                )
548            if first_flash_size_freq != self.flash_size_freq:
549                print(
550                    "WARNING: Flash size/freq value in first header (0x%02x) disagrees "
551                    "with second (0x%02x). Using second value."
552                    % (first_flash_size_freq, self.flash_size_freq)
553                )
554            if first_entrypoint != self.entrypoint:
555                print(
556                    "WARNING: Entrypoint address in first header (0x%08x) disagrees "
557                    "with second header (0x%08x). Using second value."
558                    % (first_entrypoint, self.entrypoint)
559                )
560
561            # load all the usual segments
562            for _ in range(segments):
563                self.load_segment(load_file)
564            self.checksum = self.read_checksum(load_file)
565
566            self.verify()
567
568    def default_output_name(self, input_file):
569        """Derive a default output name from the ELF name."""
570        irom_segment = self.get_irom_segment()
571        if irom_segment is not None:
572            irom_offs = irom_segment.addr - ESP8266ROM.IROM_MAP_START
573        else:
574            irom_offs = 0
575        return "%s-0x%05x.bin" % (
576            os.path.splitext(input_file)[0],
577            irom_offs & ~(ESPLoader.FLASH_SECTOR_SIZE - 1),
578        )
579
580    def save(self, filename):
581        with open(filename, "wb") as f:
582            # Save first header for irom0 segment
583            f.write(
584                struct.pack(
585                    b"<BBBBI",
586                    self.IMAGE_V2_MAGIC,
587                    self.IMAGE_V2_SEGMENT,
588                    self.flash_mode,
589                    self.flash_size_freq,
590                    self.entrypoint,
591                )
592            )
593
594            irom_segment = self.get_irom_segment()
595            if irom_segment is not None:
596                # save irom0 segment, make sure it has load addr 0 in the file
597                irom_segment = irom_segment.copy_with_new_addr(0)
598                irom_segment.pad_to_alignment(
599                    16
600                )  # irom_segment must end on a 16 byte boundary
601                self.save_segment(f, irom_segment)
602
603            # second header, matches V1 header and contains loadable segments
604            normal_segments = self.get_non_irom_segments()
605            self.write_common_header(f, normal_segments)
606            checksum = ESPLoader.ESP_CHECKSUM_MAGIC
607            for segment in normal_segments:
608                checksum = self.save_segment(f, segment, checksum)
609            self.append_checksum(f, checksum)
610
611        # calculate a crc32 of entire file and append
612        # (algorithm used by recent 8266 SDK bootloaders)
613        with open(filename, "rb") as f:
614            crc = esp8266_crc32(f.read())
615        with open(filename, "ab") as f:
616            f.write(struct.pack(b"<I", crc))
617
618
619def esp8266_crc32(data):
620    """
621    CRC32 algorithm used by 8266 SDK bootloader (and gen_appbin.py).
622    """
623    crc = binascii.crc32(data, 0) & 0xFFFFFFFF
624    if crc & 0x80000000:
625        return crc ^ 0xFFFFFFFF
626    else:
627        return crc + 1
628
629
630class ESP32FirmwareImage(BaseFirmwareImage):
631    """ESP32 firmware image is very similar to V1 ESP8266 image,
632    except with an additional 16 byte reserved header at top of image,
633    and because of new flash mapping capabilities the flash-mapped regions
634    can be placed in the normal image (just @ 64kB padded offsets).
635    """
636
637    ROM_LOADER = ESP32ROM
638
639    # ROM bootloader will read the wp_pin field if SPI flash
640    # pins are remapped via flash. IDF actually enables QIO only
641    # from software bootloader, so this can be ignored. But needs
642    # to be set to this value so ROM bootloader will skip it.
643    WP_PIN_DISABLED = 0xEE
644
645    EXTENDED_HEADER_STRUCT_FMT = "<BBBBHBHH" + ("B" * 4) + "B"
646
647    IROM_ALIGN = 65536
648
649    def __init__(self, load_file=None, append_digest=True, ram_only_header=False):
650        super(ESP32FirmwareImage, self).__init__()
651        self.secure_pad = None
652        self.flash_mode = 0
653        self.flash_size_freq = 0
654        self.version = 1
655        self.wp_pin = self.WP_PIN_DISABLED
656        # SPI pin drive levels
657        self.clk_drv = 0
658        self.q_drv = 0
659        self.d_drv = 0
660        self.cs_drv = 0
661        self.hd_drv = 0
662        self.wp_drv = 0
663        self.chip_id = 0
664        self.min_rev = 0
665        self.min_rev_full = 0
666        self.max_rev_full = 0
667        self.ram_only_header = ram_only_header
668
669        self.append_digest = append_digest
670        self.data_length = None
671
672        if load_file is not None:
673            start = load_file.tell()
674
675            segments = self.load_common_header(load_file, ESPLoader.ESP_IMAGE_MAGIC)
676            self.load_extended_header(load_file)
677
678            for _ in range(segments):
679                self.load_segment(load_file)
680            self.checksum = self.read_checksum(load_file)
681
682            if self.append_digest:
683                end = load_file.tell()
684                self.stored_digest = load_file.read(32)
685                load_file.seek(start)
686                calc_digest = hashlib.sha256()
687                calc_digest.update(load_file.read(end - start))
688                self.calc_digest = calc_digest.digest()  # TODO: decide what to do here?
689                self.data_length = end - start
690
691            self.verify()
692
693    def is_flash_addr(self, addr):
694        return (
695            self.ROM_LOADER.IROM_MAP_START <= addr < self.ROM_LOADER.IROM_MAP_END
696        ) or (self.ROM_LOADER.DROM_MAP_START <= addr < self.ROM_LOADER.DROM_MAP_END)
697
698    def default_output_name(self, input_file):
699        """Derive a default output name from the ELF name."""
700        return "%s.bin" % (os.path.splitext(input_file)[0])
701
702    def warn_if_unusual_segment(self, offset, size, is_irom_segment):
703        pass  # TODO: add warnings for wrong ESP32 segment offset/size combinations
704
705    def save(self, filename):
706        total_segments = 0
707        with io.BytesIO() as f:  # write file to memory first
708            self.write_common_header(f, self.segments)
709
710            # first 4 bytes of header are read by ROM bootloader for SPI
711            # config, but currently unused
712            self.save_extended_header(f)
713
714            checksum = ESPLoader.ESP_CHECKSUM_MAGIC
715
716            # split segments into flash-mapped vs ram-loaded,
717            # and take copies so we can mutate them
718            flash_segments = [
719                copy.deepcopy(s)
720                for s in sorted(self.segments, key=lambda s: s.addr)
721                if self.is_flash_addr(s.addr)
722            ]
723            ram_segments = [
724                copy.deepcopy(s)
725                for s in sorted(self.segments, key=lambda s: s.addr)
726                if not self.is_flash_addr(s.addr)
727            ]
728
729            # Patch to support ESP32-C6 union bus memmap
730            # move ".flash.appdesc" segment to the top of the flash segment
731            for segment in flash_segments:
732                if isinstance(segment, ELFSection) and segment.name == ".flash.appdesc":
733                    flash_segments.remove(segment)
734                    flash_segments.insert(0, segment)
735                    break
736
737            # For the bootloader image
738            # move ".dram0.bootdesc" segment to the top of the ram segment
739            # So bootdesc will be at the very top of the binary at 0x20 offset
740            # (in the first segment).
741            for segment in ram_segments:
742                if (
743                    isinstance(segment, ELFSection)
744                    and segment.name == ".dram0.bootdesc"
745                ):
746                    ram_segments.remove(segment)
747                    ram_segments.insert(0, segment)
748                    break
749
750            # check for multiple ELF sections that are mapped in the same
751            # flash mapping region. This is usually a sign of a broken linker script,
752            # but if you have a legitimate use case then let us know
753            if len(flash_segments) > 0:
754                last_addr = flash_segments[0].addr
755                for segment in flash_segments[1:]:
756                    if segment.addr // self.IROM_ALIGN == last_addr // self.IROM_ALIGN:
757                        raise FatalError(
758                            "Segment loaded at 0x%08x lands in same 64KB flash mapping "
759                            "as segment loaded at 0x%08x. Can't generate binary. "
760                            "Suggest changing linker script or ELF to merge sections."
761                            % (segment.addr, last_addr)
762                        )
763                    last_addr = segment.addr
764
765            def get_alignment_data_needed(segment):
766                # Actual alignment (in data bytes) required for a segment header:
767                # positioned so that after we write the next 8 byte header,
768                # file_offs % IROM_ALIGN == segment.addr % IROM_ALIGN
769                #
770                # (this is because the segment's vaddr may not be IROM_ALIGNed,
771                # more likely is aligned IROM_ALIGN+0x18
772                # to account for the binary file header
773                align_past = (segment.addr % self.IROM_ALIGN) - self.SEG_HEADER_LEN
774                pad_len = (self.IROM_ALIGN - (f.tell() % self.IROM_ALIGN)) + align_past
775                if pad_len == 0 or pad_len == self.IROM_ALIGN:
776                    return 0  # already aligned
777
778                # subtract SEG_HEADER_LEN a second time,
779                # as the padding block has a header as well
780                pad_len -= self.SEG_HEADER_LEN
781                if pad_len < 0:
782                    pad_len += self.IROM_ALIGN
783                return pad_len
784
785            if self.ram_only_header:
786                # write RAM segments first in order to get only RAM segments quantity
787                # and checksum (ROM bootloader will only care for RAM segments and its
788                # correct checksums)
789                for segment in ram_segments:
790                    checksum = self.save_segment(f, segment, checksum)
791                    total_segments += 1
792                self.append_checksum(f, checksum)
793
794                # reversing to match the same section order from linker script
795                flash_segments.reverse()
796                for segment in flash_segments:
797                    pad_len = get_alignment_data_needed(segment)
798                    # Some chips have a non-zero load offset (eg. 0x1000)
799                    # therefore we shift the ROM segments "-load_offset"
800                    # so it will be aligned properly after it is flashed
801                    if pad_len < self.ROM_LOADER.BOOTLOADER_FLASH_OFFSET:
802                        # in case pad_len does not fit minimum alignment,
803                        # pad it to next aligned boundary
804                        pad_len += self.IROM_ALIGN
805
806                    pad_len -= self.ROM_LOADER.BOOTLOADER_FLASH_OFFSET
807                    pad_segment = ImageSegment(0, b"\x00" * pad_len, f.tell())
808                    self.save_segment(f, pad_segment)
809                    total_segments += 1
810                    # check the alignment
811                    assert (f.tell() + 8 + self.ROM_LOADER.BOOTLOADER_FLASH_OFFSET) % (
812                        self.IROM_ALIGN
813                    ) == segment.addr % self.IROM_ALIGN
814                    # save the flash segment but not saving its checksum neither
815                    # saving the number of flash segments, since ROM bootloader
816                    # should "not see" them
817                    self.save_flash_segment(f, segment)
818                    total_segments += 1
819            else:  # not self.ram_only_header
820                # try to fit each flash segment on a 64kB aligned boundary
821                # by padding with parts of the non-flash segments...
822                while len(flash_segments) > 0:
823                    segment = flash_segments[0]
824                    pad_len = get_alignment_data_needed(segment)
825                    if pad_len > 0:  # need to pad
826                        if len(ram_segments) > 0 and pad_len > self.SEG_HEADER_LEN:
827                            pad_segment = ram_segments[0].split_image(pad_len)
828                            if len(ram_segments[0].data) == 0:
829                                ram_segments.pop(0)
830                        else:
831                            pad_segment = ImageSegment(0, b"\x00" * pad_len, f.tell())
832                        checksum = self.save_segment(f, pad_segment, checksum)
833                        total_segments += 1
834                    else:
835                        # write the flash segment
836                        assert (
837                            f.tell() + 8
838                        ) % self.IROM_ALIGN == segment.addr % self.IROM_ALIGN
839                        checksum = self.save_flash_segment(f, segment, checksum)
840                        flash_segments.pop(0)
841                        total_segments += 1
842
843                # flash segments all written, so write any remaining RAM segments
844                for segment in ram_segments:
845                    checksum = self.save_segment(f, segment, checksum)
846                    total_segments += 1
847
848            if self.secure_pad:
849                # pad the image so that after signing it will end on a a 64KB boundary.
850                # This ensures all mapped flash content will be verified.
851                if not self.append_digest:
852                    raise FatalError(
853                        "secure_pad only applies if a SHA-256 digest "
854                        "is also appended to the image"
855                    )
856                align_past = (f.tell() + self.SEG_HEADER_LEN) % self.IROM_ALIGN
857                # 16 byte aligned checksum
858                # (force the alignment to simplify calculations)
859                checksum_space = 16
860                if self.secure_pad == "1":
861                    # after checksum: SHA-256 digest +
862                    # (to be added by signing process) version,
863                    # signature + 12 trailing bytes due to alignment
864                    space_after_checksum = 32 + 4 + 64 + 12
865                elif self.secure_pad == "2":  # Secure Boot V2
866                    # after checksum: SHA-256 digest +
867                    # signature sector,
868                    # but we place signature sector after the 64KB boundary
869                    space_after_checksum = 32
870                pad_len = (
871                    self.IROM_ALIGN - align_past - checksum_space - space_after_checksum
872                ) % self.IROM_ALIGN
873                pad_segment = ImageSegment(0, b"\x00" * pad_len, f.tell())
874
875                checksum = self.save_segment(f, pad_segment, checksum)
876                total_segments += 1
877
878            if not self.ram_only_header:
879                # done writing segments
880                self.append_checksum(f, checksum)
881            image_length = f.tell()
882
883            if self.secure_pad:
884                assert ((image_length + space_after_checksum) % self.IROM_ALIGN) == 0
885
886            # kinda hacky: go back to the initial header and write the new segment count
887            # that includes padding segments. This header is not checksummed
888            f.seek(1)
889            if self.ram_only_header:
890                # Update the header with the RAM segments quantity as it should be
891                # visible by the ROM bootloader
892                f.write(bytes([len(ram_segments)]))
893            else:
894                f.write(bytes([total_segments]))
895
896            if self.append_digest:
897                # calculate the SHA256 of the whole file and append it
898                f.seek(0)
899                digest = hashlib.sha256()
900                digest.update(f.read(image_length))
901                f.write(digest.digest())
902
903            if self.pad_to_size:
904                image_length = f.tell()
905                if image_length % self.pad_to_size != 0:
906                    pad_by = self.pad_to_size - (image_length % self.pad_to_size)
907                    f.write(b"\xff" * pad_by)
908
909            with open(filename, "wb") as real_file:
910                real_file.write(f.getvalue())
911
912    def load_extended_header(self, load_file):
913        def split_byte(n):
914            return (n & 0x0F, (n >> 4) & 0x0F)
915
916        fields = list(
917            struct.unpack(self.EXTENDED_HEADER_STRUCT_FMT, load_file.read(16))
918        )
919
920        self.wp_pin = fields[0]
921
922        # SPI pin drive stengths are two per byte
923        self.clk_drv, self.q_drv = split_byte(fields[1])
924        self.d_drv, self.cs_drv = split_byte(fields[2])
925        self.hd_drv, self.wp_drv = split_byte(fields[3])
926
927        self.chip_id = fields[4]
928        if self.chip_id != self.ROM_LOADER.IMAGE_CHIP_ID:
929            print(
930                (
931                    "Unexpected chip id in image. Expected %d but value was %d. "
932                    "Is this image for a different chip model?"
933                )
934                % (self.ROM_LOADER.IMAGE_CHIP_ID, self.chip_id)
935            )
936
937        self.min_rev = fields[5]
938        self.min_rev_full = fields[6]
939        self.max_rev_full = fields[7]
940
941        append_digest = fields[-1]  # last byte is append_digest
942        if append_digest in [0, 1]:
943            self.append_digest = append_digest == 1
944        else:
945            raise RuntimeError(
946                "Invalid value for append_digest field (0x%02x). Should be 0 or 1.",
947                append_digest,
948            )
949
950    def save_extended_header(self, save_file):
951        def join_byte(ln, hn):
952            return (ln & 0x0F) + ((hn & 0x0F) << 4)
953
954        append_digest = 1 if self.append_digest else 0
955
956        fields = [
957            self.wp_pin,
958            join_byte(self.clk_drv, self.q_drv),
959            join_byte(self.d_drv, self.cs_drv),
960            join_byte(self.hd_drv, self.wp_drv),
961            self.ROM_LOADER.IMAGE_CHIP_ID,
962            self.min_rev,
963            self.min_rev_full,
964            self.max_rev_full,
965        ]
966        fields += [0] * 4  # padding
967        fields += [append_digest]
968
969        packed = struct.pack(self.EXTENDED_HEADER_STRUCT_FMT, *fields)
970        save_file.write(packed)
971
972
973class ESP8266V3FirmwareImage(ESP32FirmwareImage):
974    """ESP8266 V3 firmware image is very similar to ESP32 image"""
975
976    EXTENDED_HEADER_STRUCT_FMT = "B" * 16
977
978    def is_flash_addr(self, addr):
979        return addr > ESP8266ROM.IROM_MAP_START
980
981    def save(self, filename):
982        total_segments = 0
983        with io.BytesIO() as f:  # write file to memory first
984            self.write_common_header(f, self.segments)
985
986            checksum = ESPLoader.ESP_CHECKSUM_MAGIC
987
988            # split segments into flash-mapped vs ram-loaded,
989            # and take copies so we can mutate them
990            flash_segments = [
991                copy.deepcopy(s)
992                for s in sorted(self.segments, key=lambda s: s.addr)
993                if self.is_flash_addr(s.addr) and len(s.data)
994            ]
995            ram_segments = [
996                copy.deepcopy(s)
997                for s in sorted(self.segments, key=lambda s: s.addr)
998                if not self.is_flash_addr(s.addr) and len(s.data)
999            ]
1000
1001            # check for multiple ELF sections that are mapped in the same
1002            # flash mapping region. This is usually a sign of a broken linker script,
1003            # but if you have a legitimate use case then let us know
1004            if len(flash_segments) > 0:
1005                last_addr = flash_segments[0].addr
1006                for segment in flash_segments[1:]:
1007                    if segment.addr // self.IROM_ALIGN == last_addr // self.IROM_ALIGN:
1008                        raise FatalError(
1009                            "Segment loaded at 0x%08x lands in same 64KB flash mapping "
1010                            "as segment loaded at 0x%08x. Can't generate binary. "
1011                            "Suggest changing linker script or ELF to merge sections."
1012                            % (segment.addr, last_addr)
1013                        )
1014                    last_addr = segment.addr
1015
1016            # try to fit each flash segment on a 64kB aligned boundary
1017            # by padding with parts of the non-flash segments...
1018            while len(flash_segments) > 0:
1019                segment = flash_segments[0]
1020                # remove 8 bytes empty data for insert segment header
1021                if isinstance(segment, ELFSection) and segment.name == ".flash.rodata":
1022                    segment.data = segment.data[8:]
1023                # write the flash segment
1024                checksum = self.save_segment(f, segment, checksum)
1025                flash_segments.pop(0)
1026                total_segments += 1
1027
1028            # flash segments all written, so write any remaining RAM segments
1029            for segment in ram_segments:
1030                checksum = self.save_segment(f, segment, checksum)
1031                total_segments += 1
1032
1033            # done writing segments
1034            self.append_checksum(f, checksum)
1035            image_length = f.tell()
1036
1037            # kinda hacky: go back to the initial header and write the new segment count
1038            # that includes padding segments. This header is not checksummed
1039            f.seek(1)
1040            f.write(bytes([total_segments]))
1041
1042            if self.append_digest:
1043                # calculate the SHA256 of the whole file and append it
1044                f.seek(0)
1045                digest = hashlib.sha256()
1046                digest.update(f.read(image_length))
1047                f.write(digest.digest())
1048
1049            with open(filename, "wb") as real_file:
1050                real_file.write(f.getvalue())
1051
1052    def load_extended_header(self, load_file):
1053        def split_byte(n):
1054            return (n & 0x0F, (n >> 4) & 0x0F)
1055
1056        fields = list(
1057            struct.unpack(self.EXTENDED_HEADER_STRUCT_FMT, load_file.read(16))
1058        )
1059
1060        self.wp_pin = fields[0]
1061
1062        # SPI pin drive stengths are two per byte
1063        self.clk_drv, self.q_drv = split_byte(fields[1])
1064        self.d_drv, self.cs_drv = split_byte(fields[2])
1065        self.hd_drv, self.wp_drv = split_byte(fields[3])
1066
1067        if fields[15] in [0, 1]:
1068            self.append_digest = fields[15] == 1
1069        else:
1070            raise RuntimeError(
1071                "Invalid value for append_digest field (0x%02x). Should be 0 or 1.",
1072                fields[15],
1073            )
1074
1075        # remaining fields in the middle should all be zero
1076        if any(f for f in fields[4:15] if f != 0):
1077            print(
1078                "Warning: some reserved header fields have non-zero values. "
1079                "This image may be from a newer esptool.py?"
1080            )
1081
1082
1083ESP32ROM.BOOTLOADER_IMAGE = ESP32FirmwareImage
1084
1085
1086class ESP32S2FirmwareImage(ESP32FirmwareImage):
1087    """ESP32S2 Firmware Image almost exactly the same as ESP32FirmwareImage"""
1088
1089    ROM_LOADER = ESP32S2ROM
1090
1091
1092ESP32S2ROM.BOOTLOADER_IMAGE = ESP32S2FirmwareImage
1093
1094
1095class ESP32S3BETA2FirmwareImage(ESP32FirmwareImage):
1096    """ESP32S3 Firmware Image almost exactly the same as ESP32FirmwareImage"""
1097
1098    ROM_LOADER = ESP32S3BETA2ROM
1099
1100
1101ESP32S3BETA2ROM.BOOTLOADER_IMAGE = ESP32S3BETA2FirmwareImage
1102
1103
1104class ESP32S3FirmwareImage(ESP32FirmwareImage):
1105    """ESP32S3 Firmware Image almost exactly the same as ESP32FirmwareImage"""
1106
1107    ROM_LOADER = ESP32S3ROM
1108
1109
1110ESP32S3ROM.BOOTLOADER_IMAGE = ESP32S3FirmwareImage
1111
1112
1113class ESP32C3FirmwareImage(ESP32FirmwareImage):
1114    """ESP32C3 Firmware Image almost exactly the same as ESP32FirmwareImage"""
1115
1116    ROM_LOADER = ESP32C3ROM
1117
1118
1119ESP32C3ROM.BOOTLOADER_IMAGE = ESP32C3FirmwareImage
1120
1121
1122class ESP32C6BETAFirmwareImage(ESP32FirmwareImage):
1123    """ESP32C6 Firmware Image almost exactly the same as ESP32FirmwareImage"""
1124
1125    ROM_LOADER = ESP32C6BETAROM
1126
1127
1128ESP32C6BETAROM.BOOTLOADER_IMAGE = ESP32C6BETAFirmwareImage
1129
1130
1131class ESP32H2BETA1FirmwareImage(ESP32FirmwareImage):
1132    """ESP32H2 Firmware Image almost exactly the same as ESP32FirmwareImage"""
1133
1134    ROM_LOADER = ESP32H2BETA1ROM
1135
1136
1137ESP32H2BETA1ROM.BOOTLOADER_IMAGE = ESP32H2BETA1FirmwareImage
1138
1139
1140class ESP32H2BETA2FirmwareImage(ESP32FirmwareImage):
1141    """ESP32H2 Firmware Image almost exactly the same as ESP32FirmwareImage"""
1142
1143    ROM_LOADER = ESP32H2BETA2ROM
1144
1145
1146ESP32H2BETA2ROM.BOOTLOADER_IMAGE = ESP32H2BETA2FirmwareImage
1147
1148
1149class ESP32C2FirmwareImage(ESP32FirmwareImage):
1150    """ESP32C2 Firmware Image almost exactly the same as ESP32FirmwareImage"""
1151
1152    ROM_LOADER = ESP32C2ROM
1153
1154    def set_mmu_page_size(self, size):
1155        if size not in [16384, 32768, 65536]:
1156            raise FatalError(
1157                "{} bytes is not a valid ESP32-C2 page size, "
1158                "select from 64KB, 32KB, 16KB.".format(size)
1159            )
1160        self.IROM_ALIGN = size
1161
1162
1163ESP32C2ROM.BOOTLOADER_IMAGE = ESP32C2FirmwareImage
1164
1165
1166class ESP32C6FirmwareImage(ESP32FirmwareImage):
1167    """ESP32C6 Firmware Image almost exactly the same as ESP32FirmwareImage"""
1168
1169    ROM_LOADER = ESP32C6ROM
1170
1171    def set_mmu_page_size(self, size):
1172        if size not in [8192, 16384, 32768, 65536]:
1173            raise FatalError(
1174                "{} bytes is not a valid ESP32-C6 page size, "
1175                "select from 64KB, 32KB, 16KB, 8KB.".format(size)
1176            )
1177        self.IROM_ALIGN = size
1178
1179
1180ESP32C6ROM.BOOTLOADER_IMAGE = ESP32C6FirmwareImage
1181
1182
1183class ESP32C61FirmwareImage(ESP32C6FirmwareImage):
1184    """ESP32C61 Firmware Image almost exactly the same as ESP32C6FirmwareImage"""
1185
1186    ROM_LOADER = ESP32C61ROM
1187
1188
1189ESP32C61ROM.BOOTLOADER_IMAGE = ESP32C61FirmwareImage
1190
1191
1192class ESP32C5FirmwareImage(ESP32C6FirmwareImage):
1193    """ESP32C5 Firmware Image almost exactly the same as ESP32C6FirmwareImage"""
1194
1195    ROM_LOADER = ESP32C5ROM
1196
1197
1198ESP32C5ROM.BOOTLOADER_IMAGE = ESP32C5FirmwareImage
1199
1200
1201class ESP32C5BETA3FirmwareImage(ESP32C6FirmwareImage):
1202    """ESP32C5BETA3 Firmware Image almost exactly the same as ESP32C6FirmwareImage"""
1203
1204    ROM_LOADER = ESP32C5BETA3ROM
1205
1206
1207ESP32C5BETA3ROM.BOOTLOADER_IMAGE = ESP32C5BETA3FirmwareImage
1208
1209
1210class ESP32P4FirmwareImage(ESP32FirmwareImage):
1211    """ESP32P4 Firmware Image almost exactly the same as ESP32FirmwareImage"""
1212
1213    ROM_LOADER = ESP32P4ROM
1214
1215
1216ESP32P4ROM.BOOTLOADER_IMAGE = ESP32P4FirmwareImage
1217
1218
1219class ESP32H2FirmwareImage(ESP32C6FirmwareImage):
1220    """ESP32H2 Firmware Image almost exactly the same as ESP32FirmwareImage"""
1221
1222    ROM_LOADER = ESP32H2ROM
1223
1224
1225ESP32H2ROM.BOOTLOADER_IMAGE = ESP32H2FirmwareImage
1226
1227
1228class ELFFile(object):
1229    SEC_TYPE_PROGBITS = 0x01
1230    SEC_TYPE_STRTAB = 0x03
1231    SEC_TYPE_NOBITS = 0x08  # e.g. .bss section
1232    SEC_TYPE_INITARRAY = 0x0E
1233    SEC_TYPE_FINIARRAY = 0x0F
1234
1235    PROG_SEC_TYPES = (SEC_TYPE_PROGBITS, SEC_TYPE_INITARRAY, SEC_TYPE_FINIARRAY)
1236
1237    LEN_SEC_HEADER = 0x28
1238
1239    SEG_TYPE_LOAD = 0x01
1240    LEN_SEG_HEADER = 0x20
1241
1242    def __init__(self, name):
1243        # Load sections from the ELF file
1244        self.name = name
1245        with open(self.name, "rb") as f:
1246            self._read_elf_file(f)
1247
1248    def get_section(self, section_name):
1249        for s in self.sections:
1250            if s.name == section_name:
1251                return s
1252        raise ValueError("No section %s in ELF file" % section_name)
1253
1254    def _read_elf_file(self, f):
1255        # read the ELF file header
1256        LEN_FILE_HEADER = 0x34
1257        try:
1258            (
1259                ident,
1260                _type,
1261                machine,
1262                _version,
1263                self.entrypoint,
1264                _phoff,
1265                shoff,
1266                _flags,
1267                _ehsize,
1268                _phentsize,
1269                _phnum,
1270                shentsize,
1271                shnum,
1272                shstrndx,
1273            ) = struct.unpack("<16sHHLLLLLHHHHHH", f.read(LEN_FILE_HEADER))
1274        except struct.error as e:
1275            raise FatalError(
1276                "Failed to read a valid ELF header from %s: %s" % (self.name, e)
1277            )
1278
1279        if byte(ident, 0) != 0x7F or ident[1:4] != b"ELF":
1280            raise FatalError("%s has invalid ELF magic header" % self.name)
1281        if machine not in [0x5E, 0xF3]:
1282            raise FatalError(
1283                "%s does not appear to be an Xtensa or an RISCV ELF file. "
1284                "e_machine=%04x" % (self.name, machine)
1285            )
1286        if shentsize != self.LEN_SEC_HEADER:
1287            raise FatalError(
1288                "%s has unexpected section header entry size 0x%x (not 0x%x)"
1289                % (self.name, shentsize, self.LEN_SEC_HEADER)
1290            )
1291        if shnum == 0:
1292            raise FatalError("%s has 0 section headers" % (self.name))
1293        self._read_sections(f, shoff, shnum, shstrndx)
1294        self._read_segments(f, _phoff, _phnum, shstrndx)
1295
1296    def _read_sections(self, f, section_header_offs, section_header_count, shstrndx):
1297        f.seek(section_header_offs)
1298        len_bytes = section_header_count * self.LEN_SEC_HEADER
1299        section_header = f.read(len_bytes)
1300        if len(section_header) == 0:
1301            raise FatalError(
1302                "No section header found at offset %04x in ELF file."
1303                % section_header_offs
1304            )
1305        if len(section_header) != (len_bytes):
1306            raise FatalError(
1307                "Only read 0x%x bytes from section header (expected 0x%x.) "
1308                "Truncated ELF file?" % (len(section_header), len_bytes)
1309            )
1310
1311        # walk through the section header and extract all sections
1312        section_header_offsets = range(0, len(section_header), self.LEN_SEC_HEADER)
1313
1314        def read_section_header(offs):
1315            (
1316                name_offs,
1317                sec_type,
1318                _flags,
1319                lma,
1320                sec_offs,
1321                size,
1322                link,
1323                info,
1324                align,
1325            ) = struct.unpack_from("<LLLLLLLLL", section_header[offs:])
1326            return (name_offs, sec_type, lma, size, sec_offs, _flags, align)
1327
1328        all_sections = [read_section_header(offs) for offs in section_header_offsets]
1329        prog_sections = [s for s in all_sections if s[1] in ELFFile.PROG_SEC_TYPES]
1330        nobits_secitons = [s for s in all_sections if s[1] == ELFFile.SEC_TYPE_NOBITS]
1331
1332        # search for the string table section
1333        if (shstrndx * self.LEN_SEC_HEADER) not in section_header_offsets:
1334            raise FatalError("ELF file has no STRTAB section at shstrndx %d" % shstrndx)
1335        _, sec_type, _, sec_size, sec_offs, _, _ = read_section_header(
1336            shstrndx * self.LEN_SEC_HEADER
1337        )
1338        if sec_type != ELFFile.SEC_TYPE_STRTAB:
1339            print(
1340                "WARNING: ELF file has incorrect STRTAB section type 0x%02x" % sec_type
1341            )
1342        f.seek(sec_offs)
1343        string_table = f.read(sec_size)
1344
1345        # build the real list of ELFSections by reading the actual section names from
1346        # the string table section, and actual data for each section
1347        # from the ELF file itself
1348        def lookup_string(offs):
1349            raw = string_table[offs:]
1350            return raw[: raw.index(b"\x00")]
1351
1352        def read_data(offs, size):
1353            f.seek(offs)
1354            return f.read(size)
1355
1356        prog_sections = [
1357            ELFSection(
1358                lookup_string(n_offs),
1359                lma,
1360                read_data(offs, size),
1361                flags=_flags,
1362                align=align,
1363            )
1364            for (n_offs, _type, lma, size, offs, _flags, align) in prog_sections
1365            if lma != 0 and size > 0
1366        ]
1367        self.sections = prog_sections
1368        self.nobits_sections = [
1369            ELFSection(lookup_string(n_offs), lma, b"", flags=_flags, align=align)
1370            for (n_offs, _type, lma, size, offs, _flags, align) in nobits_secitons
1371            if lma != 0 and size > 0
1372        ]
1373
1374    def _read_segments(self, f, segment_header_offs, segment_header_count, shstrndx):
1375        f.seek(segment_header_offs)
1376        len_bytes = segment_header_count * self.LEN_SEG_HEADER
1377        segment_header = f.read(len_bytes)
1378        if len(segment_header) == 0:
1379            raise FatalError(
1380                "No segment header found at offset %04x in ELF file."
1381                % segment_header_offs
1382            )
1383        if len(segment_header) != (len_bytes):
1384            raise FatalError(
1385                "Only read 0x%x bytes from segment header (expected 0x%x.) "
1386                "Truncated ELF file?" % (len(segment_header), len_bytes)
1387            )
1388
1389        # walk through the segment header and extract all segments
1390        segment_header_offsets = range(0, len(segment_header), self.LEN_SEG_HEADER)
1391
1392        def read_segment_header(offs):
1393            (
1394                seg_type,
1395                seg_offs,
1396                _vaddr,
1397                lma,
1398                size,
1399                _memsize,
1400                _flags,
1401                _align,
1402            ) = struct.unpack_from("<LLLLLLLL", segment_header[offs:])
1403            return (seg_type, lma, size, seg_offs, _flags)
1404
1405        all_segments = [read_segment_header(offs) for offs in segment_header_offsets]
1406        prog_segments = [s for s in all_segments if s[0] == ELFFile.SEG_TYPE_LOAD]
1407
1408        def read_data(offs, size):
1409            f.seek(offs)
1410            return f.read(size)
1411
1412        prog_segments = [
1413            ELFSection(b"PHDR", lma, read_data(offs, size), flags=_flags)
1414            for (_type, lma, size, offs, _flags) in prog_segments
1415            if lma != 0 and size > 0
1416        ]
1417        self.segments = prog_segments
1418
1419    def sha256(self):
1420        # return SHA256 hash of the input ELF file
1421        sha256 = hashlib.sha256()
1422        with open(self.name, "rb") as f:
1423            sha256.update(f.read())
1424        return sha256.digest()
1425