diff options
author | Jakob Stendahl <jakobste@uio.no> | 2021-01-11 13:41:18 +0100 |
---|---|---|
committer | Jakob Stendahl <jakobste@uio.no> | 2021-01-11 13:41:18 +0100 |
commit | d17bc0fc4bb057378fadf3f9feb0de1df60d611a (patch) | |
tree | ca3069eeacb0b7379cb289d87be932956e449d9c /utils/esptool.py | |
parent | 19d65c7b2e287223113ab916e103638c5c5003f5 (diff) | |
download | hoverbit-ble-d17bc0fc4bb057378fadf3f9feb0de1df60d611a.tar.gz hoverbit-ble-d17bc0fc4bb057378fadf3f9feb0de1df60d611a.zip |
:sparkles: Add working bluetooth receiver
Diffstat (limited to 'utils/esptool.py')
-rwxr-xr-x | utils/esptool.py | 1274 |
1 files changed, 1274 insertions, 0 deletions
diff --git a/utils/esptool.py b/utils/esptool.py new file mode 100755 index 0000000..63eae28 --- /dev/null +++ b/utils/esptool.py @@ -0,0 +1,1274 @@ +#!/usr/bin/env python +# NB: Before sending a PR to change the above line to '#!/usr/bin/env python2', please read https://github.com/themadinventor/esptool/issues/21 +# +# ESP8266 ROM Bootloader Utility +# https://github.com/themadinventor/esptool +# +# Copyright (C) 2014-2016 Fredrik Ahlberg, Angus Gratton, other contributors as noted. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin +# Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import argparse +import hashlib +import inspect +import json +import os +import serial +import struct +import subprocess +import sys +import tempfile +import time + + +__version__ = "1.2" + + +class ESPROM(object): + # These are the currently known commands supported by the ROM + ESP_FLASH_BEGIN = 0x02 + ESP_FLASH_DATA = 0x03 + ESP_FLASH_END = 0x04 + ESP_MEM_BEGIN = 0x05 + ESP_MEM_END = 0x06 + ESP_MEM_DATA = 0x07 + ESP_SYNC = 0x08 + ESP_WRITE_REG = 0x09 + ESP_READ_REG = 0x0a + + # Maximum block sized for RAM and Flash writes, respectively. + ESP_RAM_BLOCK = 0x1800 + ESP_FLASH_BLOCK = 0x400 + + # Default baudrate. The ROM auto-bauds, so we can use more or less whatever we want. + ESP_ROM_BAUD = 115200 + + # First byte of the application image + ESP_IMAGE_MAGIC = 0xe9 + + # Initial state for the checksum routine + ESP_CHECKSUM_MAGIC = 0xef + + # OTP ROM addresses + ESP_OTP_MAC0 = 0x3ff00050 + ESP_OTP_MAC1 = 0x3ff00054 + ESP_OTP_MAC3 = 0x3ff0005c + + # Flash sector size, minimum unit of erase. + ESP_FLASH_SECTOR = 0x1000 + + def __init__(self, port=0, baud=ESP_ROM_BAUD): + self._port = serial.serial_for_url(port) + self._slip_reader = slip_reader(self._port) + # setting baud rate in a separate step is a workaround for + # CH341 driver on some Linux versions (this opens at 9600 then + # sets), shouldn't matter for other platforms/drivers. See + # https://github.com/themadinventor/esptool/issues/44#issuecomment-107094446 + self._port.baudrate = baud + + """ Read a SLIP packet from the serial port """ + def read(self): + return self._slip_reader.next() + + """ Write bytes to the serial port while performing SLIP escaping """ + def write(self, packet): + buf = '\xc0' \ + + (packet.replace('\xdb','\xdb\xdd').replace('\xc0','\xdb\xdc')) \ + + '\xc0' + self._port.write(buf) + + """ Calculate checksum of a blob, as it is defined by the ROM """ + @staticmethod + def checksum(data, state=ESP_CHECKSUM_MAGIC): + for b in data: + state ^= ord(b) + return state + + """ Send a request and read the response """ + def command(self, op=None, data=None, chk=0): + if op is not None: + pkt = struct.pack('<BBHI', 0x00, op, len(data), chk) + data + self.write(pkt) + + # tries to get a response until that response has the + # same operation as the request or a retries limit has + # exceeded. This is needed for some esp8266s that + # reply with more sync responses than expected. + for retry in xrange(100): + p = self.read() + if len(p) < 8: + continue + (resp, op_ret, len_ret, val) = struct.unpack('<BBHI', p[:8]) + if resp != 1: + continue + body = p[8:] + if op is None or op_ret == op: + return val, body # valid response received + + raise FatalError("Response doesn't match request") + + """ Perform a connection test """ + def sync(self): + self.command(ESPROM.ESP_SYNC, '\x07\x07\x12\x20' + 32 * '\x55') + for i in xrange(7): + self.command() + + """ Try connecting repeatedly until successful, or giving up """ + def connect(self): + print 'Connecting...' + + for _ in xrange(4): + # issue reset-to-bootloader: + # RTS = either CH_PD or nRESET (both active low = chip in reset) + # DTR = GPIO0 (active low = boot to flasher) + self._port.setDTR(False) + self._port.setRTS(True) + time.sleep(0.05) + self._port.setDTR(True) + self._port.setRTS(False) + time.sleep(0.05) + self._port.setDTR(False) + + # worst-case latency timer should be 255ms (probably <20ms) + self._port.timeout = 0.3 + for _ in xrange(4): + try: + self._port.flushInput() + self._slip_reader = slip_reader(self._port) + self._port.flushOutput() + self.sync() + self._port.timeout = 5 + return + except: + time.sleep(0.05) + raise FatalError('Failed to connect to ESP8266') + + """ Read memory address in target """ + def read_reg(self, addr): + res = self.command(ESPROM.ESP_READ_REG, struct.pack('<I', addr)) + if res[1] != "\0\0": + raise FatalError('Failed to read target memory') + return res[0] + + """ Write to memory address in target """ + def write_reg(self, addr, value, mask, delay_us=0): + if self.command(ESPROM.ESP_WRITE_REG, + struct.pack('<IIII', addr, value, mask, delay_us))[1] != "\0\0": + raise FatalError('Failed to write target memory') + + """ Start downloading an application image to RAM """ + def mem_begin(self, size, blocks, blocksize, offset): + if self.command(ESPROM.ESP_MEM_BEGIN, + struct.pack('<IIII', size, blocks, blocksize, offset))[1] != "\0\0": + raise FatalError('Failed to enter RAM download mode') + + """ Send a block of an image to RAM """ + def mem_block(self, data, seq): + if self.command(ESPROM.ESP_MEM_DATA, + struct.pack('<IIII', len(data), seq, 0, 0) + data, + ESPROM.checksum(data))[1] != "\0\0": + raise FatalError('Failed to write to target RAM') + + """ Leave download mode and run the application """ + def mem_finish(self, entrypoint=0): + if self.command(ESPROM.ESP_MEM_END, + struct.pack('<II', int(entrypoint == 0), entrypoint))[1] != "\0\0": + raise FatalError('Failed to leave RAM download mode') + + """ Start downloading to Flash (performs an erase) """ + def flash_begin(self, size, offset): + old_tmo = self._port.timeout + num_blocks = (size + ESPROM.ESP_FLASH_BLOCK - 1) / ESPROM.ESP_FLASH_BLOCK + + sectors_per_block = 16 + sector_size = self.ESP_FLASH_SECTOR + num_sectors = (size + sector_size - 1) / sector_size + start_sector = offset / sector_size + + head_sectors = sectors_per_block - (start_sector % sectors_per_block) + if num_sectors < head_sectors: + head_sectors = num_sectors + + if num_sectors < 2 * head_sectors: + erase_size = (num_sectors + 1) / 2 * sector_size + else: + erase_size = (num_sectors - head_sectors) * sector_size + + self._port.timeout = 20 + t = time.time() + result = self.command(ESPROM.ESP_FLASH_BEGIN, + struct.pack('<IIII', erase_size, num_blocks, ESPROM.ESP_FLASH_BLOCK, offset))[1] + if size != 0: + print "Took %.2fs to erase flash block" % (time.time() - t) + if result != "\0\0": + raise FatalError.WithResult('Failed to enter Flash download mode (result "%s")', result) + self._port.timeout = old_tmo + + """ Write block to flash """ + def flash_block(self, data, seq): + result = self.command(ESPROM.ESP_FLASH_DATA, + struct.pack('<IIII', len(data), seq, 0, 0) + data, + ESPROM.checksum(data))[1] + if result != "\0\0": + raise FatalError.WithResult('Failed to write to target Flash after seq %d (got result %%s)' % seq, result) + + """ Leave flash mode and run/reboot """ + def flash_finish(self, reboot=False): + pkt = struct.pack('<I', int(not reboot)) + if self.command(ESPROM.ESP_FLASH_END, pkt)[1] != "\0\0": + raise FatalError('Failed to leave Flash mode') + + """ Run application code in flash """ + def run(self, reboot=False): + # Fake flash begin immediately followed by flash end + self.flash_begin(0, 0) + self.flash_finish(reboot) + + """ Read MAC from OTP ROM """ + def read_mac(self): + mac0 = self.read_reg(self.ESP_OTP_MAC0) + mac1 = self.read_reg(self.ESP_OTP_MAC1) + mac3 = self.read_reg(self.ESP_OTP_MAC3) + if (mac3 != 0): + oui = ((mac3 >> 16) & 0xff, (mac3 >> 8) & 0xff, mac3 & 0xff) + elif ((mac1 >> 16) & 0xff) == 0: + oui = (0x18, 0xfe, 0x34) + elif ((mac1 >> 16) & 0xff) == 1: + oui = (0xac, 0xd0, 0x74) + else: + raise FatalError("Unknown OUI") + return oui + ((mac1 >> 8) & 0xff, mac1 & 0xff, (mac0 >> 24) & 0xff) + + """ Read Chip ID from OTP ROM - see http://esp8266-re.foogod.com/wiki/System_get_chip_id_%28IoT_RTOS_SDK_0.9.9%29 """ + def chip_id(self): + id0 = self.read_reg(self.ESP_OTP_MAC0) + id1 = self.read_reg(self.ESP_OTP_MAC1) + return (id0 >> 24) | ((id1 & 0xffffff) << 8) + + """ Read SPI flash manufacturer and device id """ + def flash_id(self): + self.flash_begin(0, 0) + self.write_reg(0x60000240, 0x0, 0xffffffff) + self.write_reg(0x60000200, 0x10000000, 0xffffffff) + flash_id = self.read_reg(0x60000240) + return flash_id + + """ Abuse the loader protocol to force flash to be left in write mode """ + def flash_unlock_dio(self): + # Enable flash write mode + self.flash_begin(0, 0) + # Reset the chip rather than call flash_finish(), which would have + # write protected the chip again (why oh why does it do that?!) + self.mem_begin(0,0,0,0x40100000) + self.mem_finish(0x40000080) + + """ Perform a chip erase of SPI flash """ + def flash_erase(self): + # Trick ROM to initialize SFlash + self.flash_begin(0, 0) + + # This is hacky: we don't have a custom stub, instead we trick + # the bootloader to jump to the SPIEraseChip() routine and then halt/crash + # when it tries to boot an unconfigured system. + self.mem_begin(0,0,0,0x40100000) + self.mem_finish(0x40004984) + + # Yup - there's no good way to detect if we succeeded. + # It it on the other hand unlikely to fail. + + def run_stub(self, stub, params, read_output=True): + stub = dict(stub) + stub['code'] = unhexify(stub['code']) + if 'data' in stub: + stub['data'] = unhexify(stub['data']) + + if stub['num_params'] != len(params): + raise FatalError('Stub requires %d params, %d provided' + % (stub['num_params'], len(params))) + + params = struct.pack('<' + ('I' * stub['num_params']), *params) + pc = params + stub['code'] + + # Upload + self.mem_begin(len(pc), 1, len(pc), stub['params_start']) + self.mem_block(pc, 0) + if 'data' in stub: + self.mem_begin(len(stub['data']), 1, len(stub['data']), stub['data_start']) + self.mem_block(stub['data'], 0) + self.mem_finish(stub['entry']) + + if read_output: + print 'Stub executed, reading response:' + while True: + p = self.read() + print hexify(p) + if p == '': + return + + +class ESPBOOTLOADER(object): + """ These are constants related to software ESP bootloader, working with 'v2' image files """ + + # First byte of the "v2" application image + IMAGE_V2_MAGIC = 0xea + + # First 'segment' value in a "v2" application image, appears to be a constant version value? + IMAGE_V2_SEGMENT = 4 + + +def LoadFirmwareImage(filename): + """ Load a firmware image, without knowing what kind of file (v1 or v2) it is. + + Returns a BaseFirmwareImage subclass, either ESPFirmwareImage (v1) or OTAFirmwareImage (v2). + """ + with open(filename, 'rb') as f: + magic = ord(f.read(1)) + f.seek(0) + if magic == ESPROM.ESP_IMAGE_MAGIC: + return ESPFirmwareImage(f) + elif magic == ESPBOOTLOADER.IMAGE_V2_MAGIC: + return OTAFirmwareImage(f) + else: + raise FatalError("Invalid image magic number: %d" % magic) + + +class BaseFirmwareImage(object): + """ Base class with common firmware image functions """ + def __init__(self): + self.segments = [] + self.entrypoint = 0 + + def add_segment(self, addr, data, pad_to=4): + """ Add a segment to the image, with specified address & data + (padded to a boundary of pad_to size) """ + # Data should be aligned on word boundary + l = len(data) + if l % pad_to: + data += b"\x00" * (pad_to - l % pad_to) + if l > 0: + self.segments.append((addr, len(data), data)) + + def load_segment(self, f, is_irom_segment=False): + """ Load the next segment from the image file """ + (offset, size) = struct.unpack('<II', f.read(8)) + if not is_irom_segment: + if offset > 0x40200000 or offset < 0x3ffe0000 or size > 65536: + raise FatalError('Suspicious segment 0x%x, length %d' % (offset, size)) + segment_data = f.read(size) + if len(segment_data) < size: + raise FatalError('End of file reading segment 0x%x, length %d (actual length %d)' % (offset, size, len(segment_data))) + segment = (offset, size, segment_data) + self.segments.append(segment) + return segment + + def save_segment(self, f, segment, checksum=None): + """ Save the next segment to the image file, return next checksum value if provided """ + (offset, size, data) = segment + f.write(struct.pack('<II', offset, size)) + f.write(data) + if checksum is not None: + return ESPROM.checksum(data, checksum) + + def read_checksum(self, f): + """ Return ESPROM checksum from end of just-read image """ + # Skip the padding. The checksum is stored in the last byte so that the + # file is a multiple of 16 bytes. + align_file_position(f, 16) + return ord(f.read(1)) + + def append_checksum(self, f, checksum): + """ Append ESPROM checksum to the just-written image """ + align_file_position(f, 16) + f.write(struct.pack('B', checksum)) + + def write_v1_header(self, f, segments): + f.write(struct.pack('<BBBBI', ESPROM.ESP_IMAGE_MAGIC, len(segments), + self.flash_mode, self.flash_size_freq, self.entrypoint)) + + +class ESPFirmwareImage(BaseFirmwareImage): + """ 'Version 1' firmware image, segments loaded directly by the ROM bootloader. """ + def __init__(self, load_file=None): + super(ESPFirmwareImage, self).__init__() + self.flash_mode = 0 + self.flash_size_freq = 0 + self.version = 1 + + if load_file is not None: + (magic, segments, self.flash_mode, self.flash_size_freq, self.entrypoint) = struct.unpack('<BBBBI', load_file.read(8)) + + # some sanity check + if magic != ESPROM.ESP_IMAGE_MAGIC or segments > 16: + raise FatalError('Invalid firmware image magic=%d segments=%d' % (magic, segments)) + + for i in xrange(segments): + self.load_segment(load_file) + self.checksum = self.read_checksum(load_file) + + def save(self, filename): + with open(filename, 'wb') as f: + self.write_v1_header(f, self.segments) + checksum = ESPROM.ESP_CHECKSUM_MAGIC + for segment in self.segments: + checksum = self.save_segment(f, segment, checksum) + self.append_checksum(f, checksum) + + +class OTAFirmwareImage(BaseFirmwareImage): + """ 'Version 2' firmware image, segments loaded by software bootloader stub + (ie Espressif bootloader or rboot) + """ + def __init__(self, load_file=None): + super(OTAFirmwareImage, self).__init__() + self.version = 2 + if load_file is not None: + (magic, segments, first_flash_mode, first_flash_size_freq, first_entrypoint) = struct.unpack('<BBBBI', load_file.read(8)) + + # some sanity check + if magic != ESPBOOTLOADER.IMAGE_V2_MAGIC: + raise FatalError('Invalid V2 image magic=%d' % (magic)) + if segments != 4: + # segment count is not really segment count here, but we expect to see '4' + print 'Warning: V2 header has unexpected "segment" count %d (usually 4)' % segments + + # irom segment comes before the second header + self.load_segment(load_file, True) + + (magic, segments, self.flash_mode, self.flash_size_freq, self.entrypoint) = struct.unpack('<BBBBI', load_file.read(8)) + + if first_flash_mode != self.flash_mode: + print('WARNING: Flash mode value in first header (0x%02x) disagrees with second (0x%02x). Using second value.' + % (first_flash_mode, self.flash_mode)) + if first_flash_size_freq != self.flash_size_freq: + print('WARNING: Flash size/freq value in first header (0x%02x) disagrees with second (0x%02x). Using second value.' + % (first_flash_size_freq, self.flash_size_freq)) + if first_entrypoint != self.entrypoint: + print('WARNING: Enterypoint address in first header (0x%08x) disagrees with second header (0x%08x). Using second value.' + % (first_entrypoint, self.entrypoint)) + + if magic != ESPROM.ESP_IMAGE_MAGIC or segments > 16: + raise FatalError('Invalid V2 second header magic=%d segments=%d' % (magic, segments)) + + # load all the usual segments + for _ in xrange(segments): + self.load_segment(load_file) + self.checksum = self.read_checksum(load_file) + + def save(self, filename): + with open(filename, 'wb') as f: + # Save first header for irom0 segment + f.write(struct.pack('<BBBBI', ESPBOOTLOADER.IMAGE_V2_MAGIC, ESPBOOTLOADER.IMAGE_V2_SEGMENT, + self.flash_mode, self.flash_size_freq, self.entrypoint)) + + # irom0 segment identified by load address zero + irom_segments = [segment for segment in self.segments if segment[0] == 0] + if len(irom_segments) != 1: + raise FatalError('Found %d segments that could be irom0. Bad ELF file?' % len(irom_segments)) + # save irom0 segment + irom_segment = irom_segments[0] + self.save_segment(f, irom_segment) + + # second header, matches V1 header and contains loadable segments + normal_segments = [s for s in self.segments if s != irom_segment] + self.write_v1_header(f, normal_segments) + checksum = ESPROM.ESP_CHECKSUM_MAGIC + for segment in normal_segments: + checksum = self.save_segment(f, segment, checksum) + self.append_checksum(f, checksum) + + +class ELFFile(object): + def __init__(self, name): + self.name = binutils_safe_path(name) + self.symbols = None + + def _fetch_symbols(self): + if self.symbols is not None: + return + self.symbols = {} + try: + tool_nm = "xtensa-lx106-elf-nm" + if os.getenv('XTENSA_CORE') == 'lx106': + tool_nm = "xt-nm" + proc = subprocess.Popen([tool_nm, self.name], stdout=subprocess.PIPE) + except OSError: + print "Error calling %s, do you have Xtensa toolchain in PATH?" % tool_nm + sys.exit(1) + for l in proc.stdout: + fields = l.strip().split() + try: + if fields[0] == "U": + print "Warning: ELF binary has undefined symbol %s" % fields[1] + continue + if fields[0] == "w": + continue # can skip weak symbols + self.symbols[fields[2]] = int(fields[0], 16) + except ValueError: + raise FatalError("Failed to strip symbol output from nm: %s" % fields) + + def get_symbol_addr(self, sym): + self._fetch_symbols() + return self.symbols[sym] + + def get_entry_point(self): + tool_readelf = "xtensa-lx106-elf-readelf" + if os.getenv('XTENSA_CORE') == 'lx106': + tool_readelf = "xt-readelf" + try: + proc = subprocess.Popen([tool_readelf, "-h", self.name], stdout=subprocess.PIPE) + except OSError: + print "Error calling %s, do you have Xtensa toolchain in PATH?" % tool_readelf + sys.exit(1) + for l in proc.stdout: + fields = l.strip().split() + if fields[0] == "Entry": + return int(fields[3], 0) + + def load_section(self, section): + tool_objcopy = "xtensa-lx106-elf-objcopy" + if os.getenv('XTENSA_CORE') == 'lx106': + tool_objcopy = "xt-objcopy" + tmpsection = binutils_safe_path(tempfile.mktemp(suffix=".section")) + try: + subprocess.check_call([tool_objcopy, "--only-section", section, "-Obinary", self.name, tmpsection]) + with open(tmpsection, "rb") as f: + data = f.read() + finally: + os.remove(tmpsection) + return data + + +class CesantaFlasher(object): + + # From stub_flasher.h + CMD_FLASH_WRITE = 1 + CMD_FLASH_READ = 2 + CMD_FLASH_DIGEST = 3 + CMD_FLASH_ERASE_CHIP = 5 + CMD_BOOT_FW = 6 + + def __init__(self, esp, baud_rate=0): + print 'Running Cesanta flasher stub...' + if baud_rate <= ESPROM.ESP_ROM_BAUD: # don't change baud rates if we already synced at that rate + baud_rate = 0 + self._esp = esp + esp.run_stub(json.loads(_CESANTA_FLASHER_STUB), [baud_rate], read_output=False) + if baud_rate > 0: + esp._port.baudrate = baud_rate + # Read the greeting. + p = esp.read() + if p != 'OHAI': + raise FatalError('Failed to connect to the flasher (got %s)' % hexify(p)) + + def flash_write(self, addr, data, show_progress=False): + assert addr % self._esp.ESP_FLASH_SECTOR == 0, 'Address must be sector-aligned' + assert len(data) % self._esp.ESP_FLASH_SECTOR == 0, 'Length must be sector-aligned' + sys.stdout.write('Writing %d @ 0x%x... ' % (len(data), addr)) + sys.stdout.flush() + self._esp.write(struct.pack('<B', self.CMD_FLASH_WRITE)) + self._esp.write(struct.pack('<III', addr, len(data), 1)) + num_sent, num_written = 0, 0 + while num_written < len(data): + p = self._esp.read() + if len(p) == 4: + num_written = struct.unpack('<I', p)[0] + elif len(p) == 1: + status_code = struct.unpack('<B', p)[0] + raise FatalError('Write failure, status: %x' % status_code) + else: + raise FatalError('Unexpected packet while writing: %s' % hexify(p)) + if show_progress: + progress = '%d (%d %%)' % (num_written, num_written * 100.0 / len(data)) + sys.stdout.write(progress + '\b' * len(progress)) + sys.stdout.flush() + while num_sent - num_written < 5120: + self._esp._port.write(data[num_sent:num_sent + 1024]) + num_sent += 1024 + p = self._esp.read() + if len(p) != 16: + raise FatalError('Expected digest, got: %s' % hexify(p)) + digest = hexify(p).upper() + expected_digest = hashlib.md5(data).hexdigest().upper() + print + if digest != expected_digest: + raise FatalError('Digest mismatch: expected %s, got %s' % (expected_digest, digest)) + p = self._esp.read() + if len(p) != 1: + raise FatalError('Expected status, got: %s' % hexify(p)) + status_code = struct.unpack('<B', p)[0] + if status_code != 0: + raise FatalError('Write failure, status: %x' % status_code) + + def flash_read(self, addr, length, show_progress=False): + sys.stdout.write('Reading %d @ 0x%x... ' % (length, addr)) + sys.stdout.flush() + self._esp.write(struct.pack('<B', self.CMD_FLASH_READ)) + # USB may not be able to keep up with the read rate, especially at + # higher speeds. Since we don't have flow control, this will result in + # data loss. Hence, we use small packet size and only allow small + # number of bytes in flight, which we can reasonably expect to fit in + # the on-chip FIFO. max_in_flight = 64 works for CH340G, other chips may + # have longer FIFOs and could benefit from increasing max_in_flight. + self._esp.write(struct.pack('<IIII', addr, length, 32, 64)) + data = '' + while True: + p = self._esp.read() + data += p + self._esp.write(struct.pack('<I', len(data))) + if show_progress and (len(data) % 1024 == 0 or len(data) == length): + progress = '%d (%d %%)' % (len(data), len(data) * 100.0 / length) + sys.stdout.write(progress + '\b' * len(progress)) + sys.stdout.flush() + if len(data) == length: + break + if len(data) > length: + raise FatalError('Read more than expected') + p = self._esp.read() + if len(p) != 16: + raise FatalError('Expected digest, got: %s' % hexify(p)) + expected_digest = hexify(p).upper() + digest = hashlib.md5(data).hexdigest().upper() + print + if digest != expected_digest: + raise FatalError('Digest mismatch: expected %s, got %s' % (expected_digest, digest)) + p = self._esp.read() + if len(p) != 1: + raise FatalError('Expected status, got: %s' % hexify(p)) + status_code = struct.unpack('<B', p)[0] + if status_code != 0: + raise FatalError('Write failure, status: %x' % status_code) + return data + + def flash_digest(self, addr, length, digest_block_size=0): + self._esp.write(struct.pack('<B', self.CMD_FLASH_DIGEST)) + self._esp.write(struct.pack('<III', addr, length, digest_block_size)) + digests = [] + while True: + p = self._esp.read() + if len(p) == 16: + digests.append(p) + elif len(p) == 1: + status_code = struct.unpack('<B', p)[0] + if status_code != 0: + raise FatalError('Write failure, status: %x' % status_code) + break + else: + raise FatalError('Unexpected packet: %s' % hexify(p)) + return digests[-1], digests[:-1] + + def boot_fw(self): + self._esp.write(struct.pack('<B', self.CMD_BOOT_FW)) + p = self._esp.read() + if len(p) != 1: + raise FatalError('Expected status, got: %s' % hexify(p)) + status_code = struct.unpack('<B', p)[0] + if status_code != 0: + raise FatalError('Boot failure, status: %x' % status_code) + + def flash_erase_chip(self): + self._esp.write(struct.pack('<B', self.CMD_FLASH_ERASE_CHIP)) + otimeout = self._esp._port.timeout + self._esp._port.timeout = 60 + p = self._esp.read() + self._esp._port.timeout = otimeout + if len(p) != 1: + raise FatalError('Expected status, got: %s' % hexify(p)) + status_code = struct.unpack('<B', p)[0] + if status_code != 0: + raise FatalError('Erase chip failure, status: %x' % status_code) + + +def slip_reader(port): + """Generator to read SLIP packets from a serial port. + Yields one full SLIP packet at a time, raises exception on timeout or invalid data. + + Designed to avoid too many calls to serial.read(1), which can bog + down on slow systems. + """ + partial_packet = None + in_escape = False + while True: + waiting = port.inWaiting() + read_bytes = port.read(1 if waiting == 0 else waiting) + if read_bytes == '': + raise FatalError("Timed out waiting for packet %s" % ("header" if partial_packet is None else "content")) + + for b in read_bytes: + if partial_packet is None: # waiting for packet header + if b == '\xc0': + partial_packet = "" + else: + raise FatalError('Invalid head of packet (%r)' % b) + elif in_escape: # part-way through escape sequence + in_escape = False + if b == '\xdc': + partial_packet += '\xc0' + elif b == '\xdd': + partial_packet += '\xdb' + else: + raise FatalError('Invalid SLIP escape (%r%r)' % ('\xdb', b)) + elif b == '\xdb': # start of escape sequence + in_escape = True + elif b == '\xc0': # end of packet + yield partial_packet + partial_packet = None + else: # normal byte in packet + partial_packet += b + + +def arg_auto_int(x): + return int(x, 0) + + +def div_roundup(a, b): + """ Return a/b rounded up to nearest integer, + equivalent result to int(math.ceil(float(int(a)) / float(int(b))), only + without possible floating point accuracy errors. + """ + return (int(a) + int(b) - 1) / int(b) + + +def binutils_safe_path(p): + """Returns a 'safe' version of path 'p' to pass to binutils + + Only does anything under Cygwin Python, where cygwin paths need to + be translated to Windows paths if the binutils wasn't compiled + using Cygwin (should also work with binutils compiled using + Cygwin, see #73.) + """ + if sys.platform == "cygwin": + try: + return subprocess.check_output(["cygpath", "-w", p]).rstrip('\n') + except subprocess.CalledProcessError: + print "WARNING: Failed to call cygpath to sanitise Cygwin path." + return p + + +def align_file_position(f, size): + """ Align the position in the file to the next block of specified size """ + align = (size - 1) - (f.tell() % size) + f.seek(align, 1) + + +def hexify(s): + return ''.join('%02X' % ord(c) for c in s) + + +def unhexify(hs): + s = '' + for i in range(0, len(hs) - 1, 2): + s += chr(int(hs[i] + hs[i + 1], 16)) + return s + + +class FatalError(RuntimeError): + """ + Wrapper class for runtime errors that aren't caused by internal bugs, but by + ESP8266 responses or input content. + """ + def __init__(self, message): + RuntimeError.__init__(self, message) + + @staticmethod + def WithResult(message, result): + """ + Return a fatal error object that includes the hex values of + 'result' as a string formatted argument. + """ + return FatalError(message % ", ".join(hex(ord(x)) for x in result)) + + +# "Operation" commands, executable at command line. One function each +# +# Each function takes either two args (<ESPROM instance>, <args>) or a single <args> +# argument. + +def load_ram(esp, args): + image = LoadFirmwareImage(args.filename) + + print 'RAM boot...' + for (offset, size, data) in image.segments: + print 'Downloading %d bytes at %08x...' % (size, offset), + sys.stdout.flush() + esp.mem_begin(size, div_roundup(size, esp.ESP_RAM_BLOCK), esp.ESP_RAM_BLOCK, offset) + + seq = 0 + while len(data) > 0: + esp.mem_block(data[0:esp.ESP_RAM_BLOCK], seq) + data = data[esp.ESP_RAM_BLOCK:] + seq += 1 + print 'done!' + + print 'All segments done, executing at %08x' % image.entrypoint + esp.mem_finish(image.entrypoint) + + +def read_mem(esp, args): + print '0x%08x = 0x%08x' % (args.address, esp.read_reg(args.address)) + + +def write_mem(esp, args): + esp.write_reg(args.address, args.value, args.mask, 0) + print 'Wrote %08x, mask %08x to %08x' % (args.value, args.mask, args.address) + + +def dump_mem(esp, args): + f = file(args.filename, 'wb') + for i in xrange(args.size / 4): + d = esp.read_reg(args.address + (i * 4)) + f.write(struct.pack('<I', d)) + if f.tell() % 1024 == 0: + print '\r%d bytes read... (%d %%)' % (f.tell(), + f.tell() * 100 / args.size), + sys.stdout.flush() + print 'Done!' + + +def detect_flash_size(esp, args): + if args.flash_size == 'detect': + flash_id = esp.flash_id() + size_id = flash_id >> 16 + args.flash_size = {18: '2m', 19: '4m', 20: '8m', 21: '16m', 22: '32m'}.get(size_id) + if args.flash_size is None: + print 'Warning: Could not auto-detect Flash size (FlashID=0x%x, SizeID=0x%x), defaulting to 4m' % (flash_id, size_id) + args.flash_size = '4m' + else: + print 'Auto-detected Flash size:', args.flash_size + + +def write_flash(esp, args): + detect_flash_size(esp, args) + flash_mode = {'qio':0, 'qout':1, 'dio':2, 'dout': 3}[args.flash_mode] + flash_size_freq = {'4m':0x00, '2m':0x10, '8m':0x20, '16m':0x30, '32m':0x40, '16m-c1': 0x50, '32m-c1':0x60, '32m-c2':0x70}[args.flash_size] + flash_size_freq += {'40m':0, '26m':1, '20m':2, '80m': 0xf}[args.flash_freq] + flash_params = struct.pack('BB', flash_mode, flash_size_freq) + + flasher = CesantaFlasher(esp, args.baud) + + for address, argfile in args.addr_filename: + image = argfile.read() + argfile.seek(0) # rewind in case we need it again + if address + len(image) > int(args.flash_size.split('m')[0]) * (1 << 17): + print 'WARNING: Unlikely to work as data goes beyond end of flash. Hint: Use --flash_size' + # Fix sflash config data. + if address == 0 and image[0] == '\xe9': + print 'Flash params set to 0x%02x%02x' % (flash_mode, flash_size_freq) + image = image[0:2] + flash_params + image[4:] + # Pad to sector size, which is the minimum unit of writing (erasing really). + if len(image) % esp.ESP_FLASH_SECTOR != 0: + image += '\xff' * (esp.ESP_FLASH_SECTOR - (len(image) % esp.ESP_FLASH_SECTOR)) + t = time.time() + flasher.flash_write(address, image, not args.no_progress) + t = time.time() - t + print ('\rWrote %d bytes at 0x%x in %.1f seconds (%.1f kbit/s)...' + % (len(image), address, t, len(image) / t * 8 / 1000)) + print 'Leaving...' + if args.verify: + print 'Verifying just-written flash...' + _verify_flash(flasher, args, flash_params) + flasher.boot_fw() + + +def image_info(args): + image = LoadFirmwareImage(args.filename) + print('Image version: %d' % image.version) + print('Entry point: %08x' % image.entrypoint) if image.entrypoint != 0 else 'Entry point not set' + print '%d segments' % len(image.segments) + print + checksum = ESPROM.ESP_CHECKSUM_MAGIC + for (idx, (offset, size, data)) in enumerate(image.segments): + if image.version == 2 and idx == 0: + print 'Segment 1: %d bytes IROM0 (no load address)' % size + else: + print 'Segment %d: %5d bytes at %08x' % (idx + 1, size, offset) + checksum = ESPROM.checksum(data, checksum) + print + print 'Checksum: %02x (%s)' % (image.checksum, 'valid' if image.checksum == checksum else 'invalid!') + + +def make_image(args): + image = ESPFirmwareImage() + if len(args.segfile) == 0: + raise FatalError('No segments specified') + if len(args.segfile) != len(args.segaddr): + raise FatalError('Number of specified files does not match number of specified addresses') + for (seg, addr) in zip(args.segfile, args.segaddr): + data = file(seg, 'rb').read() + image.add_segment(addr, data) + image.entrypoint = args.entrypoint + image.save(args.output) + + +def elf2image(args): + e = ELFFile(args.input) + if args.version == '1': + image = ESPFirmwareImage() + else: + image = OTAFirmwareImage() + irom_data = e.load_section('.irom0.text') + if len(irom_data) == 0: + raise FatalError(".irom0.text section not found in ELF file - can't create V2 image.") + image.add_segment(0, irom_data, 16) + image.entrypoint = e.get_entry_point() + for section, start in ((".text", "_text_start"), (".data", "_data_start"), (".rodata", "_rodata_start")): + data = e.load_section(section) + image.add_segment(e.get_symbol_addr(start), data) + + image.flash_mode = {'qio':0, 'qout':1, 'dio':2, 'dout': 3}[args.flash_mode] + image.flash_size_freq = {'4m':0x00, '2m':0x10, '8m':0x20, '16m':0x30, '32m':0x40, '16m-c1': 0x50, '32m-c1':0x60, '32m-c2':0x70}[args.flash_size] + image.flash_size_freq += {'40m':0, '26m':1, '20m':2, '80m': 0xf}[args.flash_freq] + + irom_offs = e.get_symbol_addr("_irom0_text_start") - 0x40200000 + + if args.version == '1': + if args.output is None: + args.output = os.path.splitext(args.input)[-1] + '-' + image.save(args.output + "0x00000.bin") + data = e.load_section(".irom0.text") + if irom_offs < 0: + raise FatalError('Address of symbol _irom0_text_start in ELF is located before flash mapping address. Bad linker script?') + if (irom_offs & 0xFFF) != 0: # irom0 isn't flash sector aligned + print "WARNING: irom0 section offset is 0x%08x. ELF is probably linked for 'elf2image --version=2'" % irom_offs + with open(args.output + "0x%05x.bin" % irom_offs, "wb") as f: + f.write(data) + f.close() + else: # V2 OTA image + + if args.output is None: + args.output = "%s-0x%05x.bin" % (os.path.splitext(args.input)[-1], irom_offs & ~(ESPROM.ESP_FLASH_SECTOR - 1)) + image.save(args.output) + + +def read_mac(esp, args): + mac = esp.read_mac() + print 'MAC: %s' % ':'.join(map(lambda x: '%02x' % x, mac)) + + +def chip_id(esp, args): + chipid = esp.chip_id() + print 'Chip ID: 0x%08x' % chipid + + +def erase_flash(esp, args): + flasher = CesantaFlasher(esp, args.baud) + print 'Erasing flash (this may take a while)...' + t = time.time() + flasher.flash_erase_chip() + t = time.time() - t + print 'Erase took %.1f seconds' % t + + +def run(esp, args): + esp.run() + + +def flash_id(esp, args): + flash_id = esp.flash_id() + esp.flash_finish(False) + print 'Manufacturer: %02x' % (flash_id & 0xff) + print 'Device: %02x%02x' % ((flash_id >> 8) & 0xff, (flash_id >> 16) & 0xff) + + +def read_flash(esp, args): + flasher = CesantaFlasher(esp, args.baud) + t = time.time() + data = flasher.flash_read(args.address, args.size, not args.no_progress) + t = time.time() - t + print ('\rRead %d bytes at 0x%x in %.1f seconds (%.1f kbit/s)...' + % (len(data), args.address, t, len(data) / t * 8 / 1000)) + file(args.filename, 'wb').write(data) + + +def _verify_flash(flasher, args, flash_params=None): + differences = False + for address, argfile in args.addr_filename: + image = argfile.read() + argfile.seek(0) # rewind in case we need it again + if address == 0 and image[0] == '\xe9' and flash_params is not None: + image = image[0:2] + flash_params + image[4:] + image_size = len(image) + print 'Verifying 0x%x (%d) bytes @ 0x%08x in flash against %s...' % (image_size, image_size, address, argfile.name) + # Try digest first, only read if there are differences. + digest, _ = flasher.flash_digest(address, image_size) + digest = hexify(digest).upper() + expected_digest = hashlib.md5(image).hexdigest().upper() + if digest == expected_digest: + print '-- verify OK (digest matched)' + continue + else: + differences = True + if getattr(args, 'diff', 'no') != 'yes': + print '-- verify FAILED (digest mismatch)' + continue + + flash = flasher.flash_read(address, image_size) + assert flash != image + diff = [i for i in xrange(image_size) if flash[i] != image[i]] + print '-- verify FAILED: %d differences, first @ 0x%08x' % (len(diff), address + diff[0]) + for d in diff: + print ' %08x %02x %02x' % (address + d, ord(flash[d]), ord(image[d])) + if differences: + raise FatalError("Verify failed.") + + +def verify_flash(esp, args, flash_params=None): + flasher = CesantaFlasher(esp) + _verify_flash(flasher, args, flash_params) + + +def version(args): + print __version__ + +# +# End of operations functions +# + + +def main(): + parser = argparse.ArgumentParser(description='esptool.py v%s - ESP8266 ROM Bootloader Utility' % __version__, prog='esptool') + + parser.add_argument( + '--port', '-p', + help='Serial port device', + default=os.environ.get('ESPTOOL_PORT', '/dev/ttyUSB0')) + + parser.add_argument( + '--baud', '-b', + help='Serial port baud rate used when flashing/reading', + type=arg_auto_int, + default=os.environ.get('ESPTOOL_BAUD', ESPROM.ESP_ROM_BAUD)) + + subparsers = parser.add_subparsers( + dest='operation', + help='Run esptool {command} -h for additional help') + + parser_load_ram = subparsers.add_parser( + 'load_ram', + help='Download an image to RAM and execute') + parser_load_ram.add_argument('filename', help='Firmware image') + + parser_dump_mem = subparsers.add_parser( + 'dump_mem', + help='Dump arbitrary memory to disk') + parser_dump_mem.add_argument('address', help='Base address', type=arg_auto_int) + parser_dump_mem.add_argument('size', help='Size of region to dump', type=arg_auto_int) + parser_dump_mem.add_argument('filename', help='Name of binary dump') + + parser_read_mem = subparsers.add_parser( + 'read_mem', + help='Read arbitrary memory location') + parser_read_mem.add_argument('address', help='Address to read', type=arg_auto_int) + + parser_write_mem = subparsers.add_parser( + 'write_mem', + help='Read-modify-write to arbitrary memory location') + parser_write_mem.add_argument('address', help='Address to write', type=arg_auto_int) + parser_write_mem.add_argument('value', help='Value', type=arg_auto_int) + parser_write_mem.add_argument('mask', help='Mask of bits to write', type=arg_auto_int) + + def add_spi_flash_subparsers(parent, auto_detect=False): + """ Add common parser arguments for SPI flash properties """ + parent.add_argument('--flash_freq', '-ff', help='SPI Flash frequency', + choices=['40m', '26m', '20m', '80m'], + default=os.environ.get('ESPTOOL_FF', '40m')) + parent.add_argument('--flash_mode', '-fm', help='SPI Flash mode', + choices=['qio', 'qout', 'dio', 'dout'], + default=os.environ.get('ESPTOOL_FM', 'qio')) + choices = ['4m', '2m', '8m', '16m', '32m', '16m-c1', '32m-c1', '32m-c2'] + default = '4m' + if auto_detect: + default = 'detect' + choices.insert(0, 'detect') + parent.add_argument('--flash_size', '-fs', help='SPI Flash size in Mbit', type=str.lower, + choices=choices, + default=os.environ.get('ESPTOOL_FS', default)) + + parser_write_flash = subparsers.add_parser( + 'write_flash', + help='Write a binary blob to flash') + parser_write_flash.add_argument('addr_filename', metavar='<address> <filename>', help='Address followed by binary filename, separated by space', + action=AddrFilenamePairAction) + add_spi_flash_subparsers(parser_write_flash, auto_detect=True) + parser_write_flash.add_argument('--no-progress', '-p', help='Suppress progress output', action="store_true") + parser_write_flash.add_argument('--verify', help='Verify just-written data (only necessary if very cautious, data is already CRCed', action='store_true') + + subparsers.add_parser( + 'run', + help='Run application code in flash') + + parser_image_info = subparsers.add_parser( + 'image_info', + help='Dump headers from an application image') + parser_image_info.add_argument('filename', help='Image file to parse') + + parser_make_image = subparsers.add_parser( + 'make_image', + help='Create an application image from binary files') + parser_make_image.add_argument('output', help='Output image file') + parser_make_image.add_argument('--segfile', '-f', action='append', help='Segment input file') + parser_make_image.add_argument('--segaddr', '-a', action='append', help='Segment base address', type=arg_auto_int) + parser_make_image.add_argument('--entrypoint', '-e', help='Address of entry point', type=arg_auto_int, default=0) + + parser_elf2image = subparsers.add_parser( + 'elf2image', + help='Create an application image from ELF file') + parser_elf2image.add_argument('input', help='Input ELF file') + parser_elf2image.add_argument('--output', '-o', help='Output filename prefix (for version 1 image), or filename (for version 2 single image)', type=str) + parser_elf2image.add_argument('--version', '-e', help='Output image version', choices=['1','2'], default='1') + add_spi_flash_subparsers(parser_elf2image) + + subparsers.add_parser( + 'read_mac', + help='Read MAC address from OTP ROM') + + subparsers.add_parser( + 'chip_id', + help='Read Chip ID from OTP ROM') + + subparsers.add_parser( + 'flash_id', + help='Read SPI flash manufacturer and device ID') + + parser_read_flash = subparsers.add_parser( + 'read_flash', + help='Read SPI flash content') + parser_read_flash.add_argument('address', help='Start address', type=arg_auto_int) + parser_read_flash.add_argument('size', help='Size of region to dump', type=arg_auto_int) + parser_read_flash.add_argument('filename', help='Name of binary dump') + parser_read_flash.add_argument('--no-progress', '-p', help='Suppress progress output', action="store_true") + + parser_verify_flash = subparsers.add_parser( + 'verify_flash', + help='Verify a binary blob against flash') + parser_verify_flash.add_argument('addr_filename', help='Address and binary file to verify there, separated by space', + action=AddrFilenamePairAction) + parser_verify_flash.add_argument('--diff', '-d', help='Show differences', + choices=['no', 'yes'], default='no') + + subparsers.add_parser( + 'erase_flash', + help='Perform Chip Erase on SPI flash') + + subparsers.add_parser( + 'version', help='Print esptool version') + + # internal sanity check - every operation matches a module function of the same name + for operation in subparsers.choices.keys(): + assert operation in globals(), "%s should be a module function" % operation + + args = parser.parse_args() + + print 'esptool.py v%s' % __version__ + + # operation function can take 1 arg (args), 2 args (esp, arg) + # or be a member function of the ESPROM class. + + operation_func = globals()[args.operation] + operation_args,_,_,_ = inspect.getargspec(operation_func) + if operation_args[0] == 'esp': # operation function takes an ESPROM connection object + initial_baud = min(ESPROM.ESP_ROM_BAUD, args.baud) # don't sync faster than the default baud rate + esp = ESPROM(args.port, initial_baud) + esp.connect() + operation_func(esp, args) + else: + operation_func(args) + + +class AddrFilenamePairAction(argparse.Action): + """ Custom parser class for the address/filename pairs passed as arguments """ + def __init__(self, option_strings, dest, nargs='+', **kwargs): + super(AddrFilenamePairAction, self).__init__(option_strings, dest, nargs, **kwargs) + + def __call__(self, parser, namespace, values, option_string=None): + # validate pair arguments + pairs = [] + for i in range(0,len(values),2): + try: + address = int(values[i],0) + except ValueError as e: + raise argparse.ArgumentError(self,'Address "%s" must be a number' % values[i]) + try: + argfile = open(values[i + 1], 'rb') + except IOError as e: + raise argparse.ArgumentError(self, e) + except IndexError: + raise argparse.ArgumentError(self,'Must be pairs of an address and the binary filename to write there') + pairs.append((address, argfile)) + setattr(namespace, self.dest, pairs) + +# This is "wrapped" stub_flasher.c, to be loaded using run_stub. +_CESANTA_FLASHER_STUB = """\ +{"code_start": 1074790404, "code": "080000601C000060000000601000006031FCFF71FCFF\ +81FCFFC02000680332D218C020004807404074DCC48608005823C0200098081BA5A9239245005803\ +1B555903582337350129230B446604DFC6F3FF21EEFFC0200069020DF0000000010078480040004A\ +0040B449004012C1F0C921D911E901DD0209312020B4ED033C2C56C2073020B43C3C56420701F5FF\ +C000003C4C569206CD0EEADD860300202C4101F1FFC0000056A204C2DCF0C02DC0CC6CCAE2D1EAFF\ +0606002030F456D3FD86FBFF00002020F501E8FFC00000EC82D0CCC0C02EC0C73DEB2ADC46030020\ +2C4101E1FFC00000DC42C2DCF0C02DC056BCFEC602003C5C8601003C6C4600003C7C08312D0CD811\ +C821E80112C1100DF0000C180000140010400C0000607418000064180000801800008C1800008418\ +0000881800009018000018980040880F0040A80F0040349800404C4A0040740F0040800F0040980F\ +00400099004012C1E091F5FFC961CD0221EFFFE941F9310971D9519011C01A223902E2D1180C0222\ +6E1D21E4FF31E9FF2AF11A332D0F42630001EAFFC00000C030B43C2256A31621E1FF1A2228022030\ +B43C3256B31501ADFFC00000DD023C4256ED1431D6FF4D010C52D90E192E126E0101DDFFC0000021\ +D2FF32A101C020004802303420C0200039022C0201D7FFC00000463300000031CDFF1A333803D023\ +C03199FF27B31ADC7F31CBFF1A3328030198FFC0000056C20E2193FF2ADD060E000031C6FF1A3328\ +030191FFC0000056820DD2DD10460800000021BEFF1A2228029CE231BCFFC020F51A33290331BBFF\ +C02C411A332903C0F0F4222E1D22D204273D9332A3FFC02000280E27B3F721ABFF381E1A2242A400\ +01B5FFC00000381E2D0C42A40001B3FFC0000056120801B2FFC00000C02000280EC2DC0422D2FCC0\ +2000290E01ADFFC00000222E1D22D204226E1D281E22D204E7B204291E860000126E012198FF32A0\ +042A21C54C003198FF222E1D1A33380337B202C6D6FF2C02019FFFC000002191FF318CFF1A223A31\ +019CFFC00000218DFF1C031A22C549000C02060300003C528601003C624600003C72918BFF9A1108\ +71C861D851E841F83112C1200DF00010000068100000581000007010000074100000781000007C10\ +0000801000001C4B0040803C004091FDFF12C1E061F7FFC961E941F9310971D9519011C01A662906\ +21F3FFC2D1101A22390231F2FF0C0F1A33590331EAFFF26C1AED045C2247B3028636002D0C016DFF\ +C0000021E5FF41EAFF2A611A4469040622000021E4FF1A222802F0D2C0D7BE01DD0E31E0FF4D0D1A\ +3328033D0101E2FFC00000561209D03D2010212001DFFFC000004D0D2D0C3D01015DFFC0000041D5\ +FFDAFF1A444804D0648041D2FF1A4462640061D1FF106680622600673F1331D0FF10338028030C43\ +853A002642164613000041CAFF222C1A1A444804202FC047328006F6FF222C1A273F3861C2FF222C\ +1A1A6668066732B921BDFF3D0C1022800148FFC0000021BAFF1C031A2201BFFFC000000C02460300\ +5C3206020000005C424600005C5291B7FF9A110871C861D851E841F83112C1200DF0B0100000C010\ +0000D010000012C1E091FEFFC961D951E9410971F931CD039011C0ED02DD0431A1FF9C1422A06247\ +B302062D0021F4FF1A22490286010021F1FF1A223902219CFF2AF12D0F011FFFC00000461C0022D1\ +10011CFFC0000021E9FFFD0C1A222802C7B20621E6FF1A22F8022D0E3D014D0F0195FFC000008C52\ +22A063C6180000218BFF3D01102280F04F200111FFC00000AC7D22D1103D014D0F010DFFC0000021\ +D6FF32D110102280010EFFC0000021D3FF1C031A220185FFC00000FAEEF0CCC056ACF821CDFF317A\ +FF1A223A310105FFC0000021C9FF1C031A22017CFFC000002D0C91C8FF9A110871C861D851E841F8\ +3112C1200DF0000200600000001040020060FFFFFF0012C1E00C02290131FAFF21FAFF026107C961\ +C02000226300C02000C80320CC10564CFF21F5FFC02000380221F4FF20231029010C432D010163FF\ +C0000008712D0CC86112C1200DF00080FE3F8449004012C1D0C9A109B17CFC22C1110C13C51C0026\ +1202463000220111C24110B68202462B0031F5FF3022A02802A002002D011C03851A0066820A2801\ +32210105A6FF0607003C12C60500000010212032A01085180066A20F2221003811482105B3FF2241\ +10861A004C1206FDFF2D011C03C5160066B20E280138114821583185CFFF06F7FF005C1286F5FF00\ +10212032A01085140066A20D2221003811482105E1FF06EFFF0022A06146EDFF45F0FFC6EBFF0000\ +01D2FFC0000006E9FF000C022241100C1322C110C50F00220111060600000022C1100C13C50E0022\ +011132C2FA303074B6230206C8FF08B1C8A112C1300DF0000000000010404F484149007519031027\ +000000110040A8100040BC0F0040583F0040CC2E00401CE20040D83900408000004021F4FF12C1E0\ +C961C80221F2FF097129010C02D951C91101F4FFC0000001F3FFC00000AC2C22A3E801F2FFC00000\ +21EAFFC031412A233D0C01EFFFC000003D0222A00001EDFFC00000C1E4FF2D0C01E8FFC000002D01\ +32A004450400C5E7FFDD022D0C01E3FFC00000666D1F4B2131DCFF4600004B22C0200048023794F5\ +31D9FFC0200039023DF08601000001DCFFC000000871C861D85112C1200DF000000012C1F0026103\ +01EAFEC00000083112C1100DF000643B004012C1D0E98109B1C9A1D991F97129013911E2A0C001FA\ +FFC00000CD02E792F40C0DE2A0C0F2A0DB860D00000001F4FFC00000204220E71240F7921C226102\ +01EFFFC0000052A0DC482157120952A0DD571205460500004D0C3801DA234242001BDD3811379DC5\ +C6000000000C0DC2A0C001E3FFC00000C792F608B12D0DC8A1D891E881F87112C1300DF00000", "\ +entry": 1074792180, "num_params": 1, "params_start": 1074790400, "data": "FE0510\ +401A0610403B0610405A0610407A061040820610408C0610408C061040", "data_start": 10736\ +43520} +""" + +if __name__ == '__main__': + try: + main() + except FatalError as e: + print '\nA fatal error occurred: %s' % e + sys.exit(2) |