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