1289 lines
47 KiB
Python
1289 lines
47 KiB
Python
# Copyright (c) 2005-2013, Alexander Belchenko
|
|
# All rights reserved.
|
|
#
|
|
# Redistribution and use in source and binary forms,
|
|
# with or without modification, are permitted provided
|
|
# that the following conditions are met:
|
|
#
|
|
# * Redistributions of source code must retain
|
|
# the above copyright notice, this list of conditions
|
|
# and the following disclaimer.
|
|
# * Redistributions in binary form must reproduce
|
|
# the above copyright notice, this list of conditions
|
|
# and the following disclaimer in the documentation
|
|
# and/or other materials provided with the distribution.
|
|
# * Neither the name of the author nor the names
|
|
# of its contributors may be used to endorse
|
|
# or promote products derived from this software
|
|
# without specific prior written permission.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
|
|
# BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
# AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
# IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
|
# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
|
# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
|
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
|
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
'''Intel HEX file format reader and converter.
|
|
|
|
@author Alexander Belchenko (alexander dot belchenko at gmail dot com)
|
|
@version 1.5
|
|
'''
|
|
|
|
|
|
__docformat__ = "javadoc"
|
|
|
|
|
|
from array import array
|
|
from binascii import hexlify, unhexlify
|
|
from bisect import bisect_right
|
|
import os
|
|
import sys
|
|
|
|
# from compat import asbytes, asstr
|
|
from . import compat
|
|
asbytes = compat.asbytes
|
|
asstr = compat.asstr
|
|
|
|
class _DeprecatedParam(object):
|
|
pass
|
|
|
|
_DEPRECATED = _DeprecatedParam()
|
|
|
|
|
|
class IntelHex(object):
|
|
''' Intel HEX file reader. '''
|
|
|
|
def __init__(self, source=None):
|
|
''' Constructor. If source specified, object will be initialized
|
|
with the contents of source. Otherwise the object will be empty.
|
|
|
|
@param source source for initialization
|
|
(file name of HEX file, file object, addr dict or
|
|
other IntelHex object)
|
|
'''
|
|
# public members
|
|
self.padding = 0x0FF
|
|
# Start Address
|
|
self.start_addr = None
|
|
|
|
# private members
|
|
self._buf = {}
|
|
self._offset = 0
|
|
|
|
if source is not None:
|
|
if isinstance(source, basestring) or getattr(source, "read", None):
|
|
# load hex file
|
|
self.loadhex(source)
|
|
elif isinstance(source, dict):
|
|
self.fromdict(source)
|
|
elif isinstance(source, IntelHex):
|
|
self.padding = source.padding
|
|
if source.start_addr:
|
|
self.start_addr = source.start_addr.copy()
|
|
self._buf = source._buf.copy()
|
|
else:
|
|
raise ValueError("source: bad initializer type")
|
|
|
|
def _decode_record(self, s, line=0):
|
|
'''Decode one record of HEX file.
|
|
|
|
@param s line with HEX record.
|
|
@param line line number (for error messages).
|
|
|
|
@raise EndOfFile if EOF record encountered.
|
|
'''
|
|
s = s.rstrip('\r\n')
|
|
if not s:
|
|
return # empty line
|
|
|
|
if s[0] == ':':
|
|
try:
|
|
bin = array('B', unhexlify(asbytes(s[1:])))
|
|
except (TypeError, ValueError):
|
|
# this might be raised by unhexlify when odd hexascii digits
|
|
raise HexRecordError(line=line)
|
|
length = len(bin)
|
|
if length < 5:
|
|
raise HexRecordError(line=line)
|
|
else:
|
|
raise HexRecordError(line=line)
|
|
|
|
record_length = bin[0]
|
|
if length != (5 + record_length):
|
|
raise RecordLengthError(line=line)
|
|
|
|
addr = bin[1]*256 + bin[2]
|
|
|
|
record_type = bin[3]
|
|
if not (0 <= record_type <= 5):
|
|
raise RecordTypeError(line=line)
|
|
|
|
crc = sum(bin)
|
|
crc &= 0x0FF
|
|
if crc != 0:
|
|
raise RecordChecksumError(line=line)
|
|
|
|
if record_type == 0:
|
|
# data record
|
|
addr += self._offset
|
|
for i in range(4, 4+record_length):
|
|
if not self._buf.get(addr, None) is None:
|
|
raise AddressOverlapError(address=addr, line=line)
|
|
self._buf[addr] = bin[i]
|
|
addr += 1 # FIXME: addr should be wrapped
|
|
# BUT after 02 record (at 64K boundary)
|
|
# and after 04 record (at 4G boundary)
|
|
|
|
elif record_type == 1:
|
|
# end of file record
|
|
if record_length != 0:
|
|
raise EOFRecordError(line=line)
|
|
raise _EndOfFile
|
|
|
|
elif record_type == 2:
|
|
# Extended 8086 Segment Record
|
|
if record_length != 2 or addr != 0:
|
|
raise ExtendedSegmentAddressRecordError(line=line)
|
|
self._offset = (bin[4]*256 + bin[5]) * 16
|
|
|
|
elif record_type == 4:
|
|
# Extended Linear Address Record
|
|
if record_length != 2 or addr != 0:
|
|
raise ExtendedLinearAddressRecordError(line=line)
|
|
self._offset = (bin[4]*256 + bin[5]) * 65536
|
|
|
|
elif record_type == 3:
|
|
# Start Segment Address Record
|
|
if record_length != 4 or addr != 0:
|
|
raise StartSegmentAddressRecordError(line=line)
|
|
if self.start_addr:
|
|
raise DuplicateStartAddressRecordError(line=line)
|
|
self.start_addr = {'CS': bin[4]*256 + bin[5],
|
|
'IP': bin[6]*256 + bin[7],
|
|
}
|
|
|
|
elif record_type == 5:
|
|
# Start Linear Address Record
|
|
if record_length != 4 or addr != 0:
|
|
raise StartLinearAddressRecordError(line=line)
|
|
if self.start_addr:
|
|
raise DuplicateStartAddressRecordError(line=line)
|
|
self.start_addr = {'EIP': (bin[4]*16777216 +
|
|
bin[5]*65536 +
|
|
bin[6]*256 +
|
|
bin[7]),
|
|
}
|
|
|
|
def loadhex(self, fobj):
|
|
"""Load hex file into internal buffer. This is not necessary
|
|
if object was initialized with source set. This will overwrite
|
|
addresses if object was already initialized.
|
|
|
|
@param fobj file name or file-like object
|
|
"""
|
|
if getattr(fobj, "read", None) is None:
|
|
fobj = open(fobj, "r")
|
|
fclose = fobj.close
|
|
else:
|
|
fclose = None
|
|
|
|
self._offset = 0
|
|
line = 0
|
|
|
|
try:
|
|
decode = self._decode_record
|
|
try:
|
|
for s in fobj:
|
|
line += 1
|
|
decode(s, line)
|
|
except _EndOfFile:
|
|
pass
|
|
finally:
|
|
if fclose:
|
|
fclose()
|
|
|
|
def loadbin(self, fobj, offset=0):
|
|
"""Load bin file into internal buffer. Not needed if source set in
|
|
constructor. This will overwrite addresses without warning
|
|
if object was already initialized.
|
|
|
|
@param fobj file name or file-like object
|
|
@param offset starting address offset
|
|
"""
|
|
fread = getattr(fobj, "read", None)
|
|
if fread is None:
|
|
f = open(fobj, "rb")
|
|
fread = f.read
|
|
fclose = f.close
|
|
else:
|
|
fclose = None
|
|
|
|
try:
|
|
self.frombytes(array('B', asbytes(fread())), offset=offset)
|
|
finally:
|
|
if fclose:
|
|
fclose()
|
|
|
|
def loadfile(self, fobj, format):
|
|
"""Load data file into internal buffer. Preferred wrapper over
|
|
loadbin or loadhex.
|
|
|
|
@param fobj file name or file-like object
|
|
@param format file format ("hex" or "bin")
|
|
"""
|
|
if format == "hex":
|
|
self.loadhex(fobj)
|
|
elif format == "bin":
|
|
self.loadbin(fobj)
|
|
else:
|
|
raise ValueError('format should be either "hex" or "bin";'
|
|
' got %r instead' % format)
|
|
|
|
# alias (to be consistent with method tofile)
|
|
fromfile = loadfile
|
|
|
|
def fromdict(self, dikt):
|
|
"""Load data from dictionary. Dictionary should contain int keys
|
|
representing addresses. Values should be the data to be stored in
|
|
those addresses in unsigned char form (i.e. not strings).
|
|
The dictionary may contain the key, ``start_addr``
|
|
to indicate the starting address of the data as described in README.
|
|
|
|
The contents of the dict will be merged with this object and will
|
|
overwrite any conflicts. This function is not necessary if the
|
|
object was initialized with source specified.
|
|
"""
|
|
s = dikt.copy()
|
|
start_addr = s.get('start_addr')
|
|
if start_addr is not None:
|
|
del s['start_addr']
|
|
for k in s.keys():
|
|
if type(k) not in (int, long) or k < 0:
|
|
raise ValueError('Source dictionary should have only int keys')
|
|
self._buf.update(s)
|
|
if start_addr is not None:
|
|
self.start_addr = start_addr
|
|
|
|
def frombytes(self, bytes, offset=0):
|
|
"""Load data from array or list of bytes.
|
|
Similar to loadbin() method but works directly with iterable bytes.
|
|
"""
|
|
for b in bytes:
|
|
self._buf[offset] = b
|
|
offset += 1
|
|
|
|
def _get_start_end(self, start=None, end=None, size=None):
|
|
"""Return default values for start and end if they are None.
|
|
If this IntelHex object is empty then it's error to
|
|
invoke this method with both start and end as None.
|
|
"""
|
|
if (start,end) == (None,None) and self._buf == {}:
|
|
raise EmptyIntelHexError
|
|
if size is not None:
|
|
if None not in (start, end):
|
|
raise ValueError("tobinarray: you can't use start,end and size"
|
|
" arguments in the same time")
|
|
if (start, end) == (None, None):
|
|
start = self.minaddr()
|
|
if start is not None:
|
|
end = start + size - 1
|
|
else:
|
|
start = end - size + 1
|
|
if start < 0:
|
|
raise ValueError("tobinarray: invalid size (%d) "
|
|
"for given end address (%d)" % (size,end))
|
|
else:
|
|
if start is None:
|
|
start = self.minaddr()
|
|
if end is None:
|
|
end = self.maxaddr()
|
|
if start > end:
|
|
start, end = end, start
|
|
return start, end
|
|
|
|
def tobinarray(self, start=None, end=None, pad=_DEPRECATED, size=None):
|
|
''' Convert this object to binary form as array. If start and end
|
|
unspecified, they will be inferred from the data.
|
|
@param start start address of output bytes.
|
|
@param end end address of output bytes (inclusive).
|
|
@param pad [DEPRECATED PARAMETER, please use self.padding instead]
|
|
fill empty spaces with this value
|
|
(if pad is None then this method uses self.padding).
|
|
@param size size of the block, used with start or end parameter.
|
|
@return array of unsigned char data.
|
|
'''
|
|
if not isinstance(pad, _DeprecatedParam):
|
|
print ("IntelHex.tobinarray: 'pad' parameter is deprecated.")
|
|
if pad is not None:
|
|
print ("Please, use IntelHex.padding attribute instead.")
|
|
else:
|
|
print ("Please, don't pass it explicitly.")
|
|
print ("Use syntax like this: ih.tobinarray(start=xxx, end=yyy, size=zzz)")
|
|
else:
|
|
pad = None
|
|
return self._tobinarray_really(start, end, pad, size)
|
|
|
|
def _tobinarray_really(self, start, end, pad, size):
|
|
if pad is None:
|
|
pad = self.padding
|
|
|
|
bin = array('B')
|
|
|
|
if self._buf == {} and None in (start, end):
|
|
return bin
|
|
|
|
if size is not None and size <= 0:
|
|
raise ValueError("tobinarray: wrong value for size")
|
|
|
|
start, end = self._get_start_end(start, end, size)
|
|
|
|
for i in range(start, end+1):
|
|
bin.append(self._buf.get(i, pad))
|
|
|
|
return bin
|
|
|
|
def tobinstr(self, start=None, end=None, pad=_DEPRECATED, size=None):
|
|
''' Convert to binary form and return as a string.
|
|
@param start start address of output bytes.
|
|
@param end end address of output bytes (inclusive).
|
|
@param pad [DEPRECATED PARAMETER, please use self.padding instead]
|
|
fill empty spaces with this value
|
|
(if pad is None then this method uses self.padding).
|
|
@param size size of the block, used with start or end parameter.
|
|
@return string of binary data.
|
|
'''
|
|
if not isinstance(pad, _DeprecatedParam):
|
|
print ("IntelHex.tobinstr: 'pad' parameter is deprecated.")
|
|
if pad is not None:
|
|
print ("Please, use IntelHex.padding attribute instead.")
|
|
else:
|
|
print ("Please, don't pass it explicitly.")
|
|
print ("Use syntax like this: ih.tobinstr(start=xxx, end=yyy, size=zzz)")
|
|
else:
|
|
pad = None
|
|
return self._tobinstr_really(start, end, pad, size)
|
|
|
|
def _tobinstr_really(self, start, end, pad, size):
|
|
return asstr(self._tobinarray_really(start, end, pad, size).tostring())
|
|
|
|
def tobinfile(self, fobj, start=None, end=None, pad=_DEPRECATED, size=None):
|
|
'''Convert to binary and write to file.
|
|
|
|
@param fobj file name or file object for writing output bytes.
|
|
@param start start address of output bytes.
|
|
@param end end address of output bytes (inclusive).
|
|
@param pad [DEPRECATED PARAMETER, please use self.padding instead]
|
|
fill empty spaces with this value
|
|
(if pad is None then this method uses self.padding).
|
|
@param size size of the block, used with start or end parameter.
|
|
'''
|
|
if not isinstance(pad, _DeprecatedParam):
|
|
print ("IntelHex.tobinfile: 'pad' parameter is deprecated.")
|
|
if pad is not None:
|
|
print ("Please, use IntelHex.padding attribute instead.")
|
|
else:
|
|
print ("Please, don't pass it explicitly.")
|
|
print ("Use syntax like this: ih.tobinfile(start=xxx, end=yyy, size=zzz)")
|
|
else:
|
|
pad = None
|
|
if getattr(fobj, "write", None) is None:
|
|
fobj = open(fobj, "wb")
|
|
close_fd = True
|
|
else:
|
|
close_fd = False
|
|
|
|
fobj.write(self._tobinstr_really(start, end, pad, size))
|
|
|
|
if close_fd:
|
|
fobj.close()
|
|
|
|
def todict(self):
|
|
'''Convert to python dictionary.
|
|
|
|
@return dict suitable for initializing another IntelHex object.
|
|
'''
|
|
r = {}
|
|
r.update(self._buf)
|
|
if self.start_addr:
|
|
r['start_addr'] = self.start_addr
|
|
return r
|
|
|
|
def addresses(self):
|
|
'''Returns all used addresses in sorted order.
|
|
@return list of occupied data addresses in sorted order.
|
|
'''
|
|
aa = self._buf.keys()
|
|
aa.sort()
|
|
return aa
|
|
|
|
def minaddr(self):
|
|
'''Get minimal address of HEX content.
|
|
@return minimal address or None if no data
|
|
'''
|
|
aa = self._buf.keys()
|
|
if aa == []:
|
|
return None
|
|
else:
|
|
return min(aa)
|
|
|
|
def maxaddr(self):
|
|
'''Get maximal address of HEX content.
|
|
@return maximal address or None if no data
|
|
'''
|
|
aa = self._buf.keys()
|
|
if aa == []:
|
|
return None
|
|
else:
|
|
return max(aa)
|
|
|
|
def __getitem__(self, addr):
|
|
''' Get requested byte from address.
|
|
@param addr address of byte.
|
|
@return byte if address exists in HEX file, or self.padding
|
|
if no data found.
|
|
'''
|
|
t = type(addr)
|
|
if t in (int, long):
|
|
if addr < 0:
|
|
raise TypeError('Address should be >= 0.')
|
|
return self._buf.get(addr, self.padding)
|
|
elif t == slice:
|
|
addresses = self._buf.keys()
|
|
ih = IntelHex()
|
|
if addresses:
|
|
addresses.sort()
|
|
start = addr.start or addresses[0]
|
|
stop = addr.stop or (addresses[-1]+1)
|
|
step = addr.step or 1
|
|
for i in range(start, stop, step):
|
|
x = self._buf.get(i)
|
|
if x is not None:
|
|
ih[i] = x
|
|
return ih
|
|
else:
|
|
raise TypeError('Address has unsupported type: %s' % t)
|
|
|
|
def __setitem__(self, addr, byte):
|
|
"""Set byte at address."""
|
|
t = type(addr)
|
|
if t in (int, long):
|
|
if addr < 0:
|
|
raise TypeError('Address should be >= 0.')
|
|
self._buf[addr] = byte
|
|
elif t == slice:
|
|
if not isinstance(byte, (list, tuple)):
|
|
raise ValueError('Slice operation expects sequence of bytes')
|
|
start = addr.start
|
|
stop = addr.stop
|
|
step = addr.step or 1
|
|
if None not in (start, stop):
|
|
ra = range(start, stop, step)
|
|
if len(ra) != len(byte):
|
|
raise ValueError('Length of bytes sequence does not match '
|
|
'address range')
|
|
elif (start, stop) == (None, None):
|
|
raise TypeError('Unsupported address range')
|
|
elif start is None:
|
|
start = stop - len(byte)
|
|
elif stop is None:
|
|
stop = start + len(byte)
|
|
if start < 0:
|
|
raise TypeError('start address cannot be negative')
|
|
if stop < 0:
|
|
raise TypeError('stop address cannot be negative')
|
|
j = 0
|
|
for i in range(start, stop, step):
|
|
self._buf[i] = byte[j]
|
|
j += 1
|
|
else:
|
|
raise TypeError('Address has unsupported type: %s' % t)
|
|
|
|
def __delitem__(self, addr):
|
|
"""Delete byte at address."""
|
|
t = type(addr)
|
|
if t in (int, long):
|
|
if addr < 0:
|
|
raise TypeError('Address should be >= 0.')
|
|
del self._buf[addr]
|
|
elif t == slice:
|
|
addresses = self._buf.keys()
|
|
if addresses:
|
|
addresses.sort()
|
|
start = addr.start or addresses[0]
|
|
stop = addr.stop or (addresses[-1]+1)
|
|
step = addr.step or 1
|
|
for i in range(start, stop, step):
|
|
x = self._buf.get(i)
|
|
if x is not None:
|
|
del self._buf[i]
|
|
else:
|
|
raise TypeError('Address has unsupported type: %s' % t)
|
|
|
|
def __len__(self):
|
|
"""Return count of bytes with real values."""
|
|
return len(self._buf.keys())
|
|
|
|
def write_hex_file(self, f, write_start_addr=True):
|
|
"""Write data to file f in HEX format.
|
|
|
|
@param f filename or file-like object for writing
|
|
@param write_start_addr enable or disable writing start address
|
|
record to file (enabled by default).
|
|
If there is no start address in obj, nothing
|
|
will be written regardless of this setting.
|
|
"""
|
|
fwrite = getattr(f, "write", None)
|
|
if fwrite:
|
|
fobj = f
|
|
fclose = None
|
|
else:
|
|
fobj = open(f, 'w')
|
|
fwrite = fobj.write
|
|
fclose = fobj.close
|
|
|
|
# Translation table for uppercasing hex ascii string.
|
|
# timeit shows that using hexstr.translate(table)
|
|
# is faster than hexstr.upper():
|
|
# 0.452ms vs. 0.652ms (translate vs. upper)
|
|
if sys.version_info[0] >= 3:
|
|
table = bytes(range(256)).upper()
|
|
else:
|
|
table = ''.join(chr(i).upper() for i in range(256))
|
|
|
|
|
|
|
|
# start address record if any
|
|
if self.start_addr and write_start_addr:
|
|
keys = self.start_addr.keys()
|
|
keys.sort()
|
|
bin = array('B', asbytes('\0'*9))
|
|
if keys == ['CS','IP']:
|
|
# Start Segment Address Record
|
|
bin[0] = 4 # reclen
|
|
bin[1] = 0 # offset msb
|
|
bin[2] = 0 # offset lsb
|
|
bin[3] = 3 # rectyp
|
|
cs = self.start_addr['CS']
|
|
bin[4] = (cs >> 8) & 0x0FF
|
|
bin[5] = cs & 0x0FF
|
|
ip = self.start_addr['IP']
|
|
bin[6] = (ip >> 8) & 0x0FF
|
|
bin[7] = ip & 0x0FF
|
|
bin[8] = (-sum(bin)) & 0x0FF # chksum
|
|
fwrite(':' +
|
|
asstr(hexlify(bin.tostring()).translate(table)) +
|
|
'\n')
|
|
elif keys == ['EIP']:
|
|
# Start Linear Address Record
|
|
bin[0] = 4 # reclen
|
|
bin[1] = 0 # offset msb
|
|
bin[2] = 0 # offset lsb
|
|
bin[3] = 5 # rectyp
|
|
eip = self.start_addr['EIP']
|
|
bin[4] = (eip >> 24) & 0x0FF
|
|
bin[5] = (eip >> 16) & 0x0FF
|
|
bin[6] = (eip >> 8) & 0x0FF
|
|
bin[7] = eip & 0x0FF
|
|
bin[8] = (-sum(bin)) & 0x0FF # chksum
|
|
fwrite(':' +
|
|
asstr(hexlify(bin.tostring()).translate(table)) +
|
|
'\n')
|
|
else:
|
|
if fclose:
|
|
fclose()
|
|
raise InvalidStartAddressValueError(start_addr=self.start_addr)
|
|
|
|
# data
|
|
addresses = self._buf.keys()
|
|
addresses.sort()
|
|
addr_len = len(addresses)
|
|
if addr_len:
|
|
minaddr = addresses[0]
|
|
maxaddr = addresses[-1]
|
|
|
|
if maxaddr > 65535:
|
|
need_offset_record = True
|
|
else:
|
|
need_offset_record = False
|
|
high_ofs = 0
|
|
|
|
cur_addr = minaddr
|
|
cur_ix = 0
|
|
|
|
while cur_addr <= maxaddr:
|
|
if need_offset_record:
|
|
bin = array('B', asbytes('\0'*7))
|
|
bin[0] = 2 # reclen
|
|
bin[1] = 0 # offset msb
|
|
bin[2] = 0 # offset lsb
|
|
bin[3] = 4 # rectyp
|
|
high_ofs = int(cur_addr>>16)
|
|
b = divmod(high_ofs, 256)
|
|
bin[4] = b[0] # msb of high_ofs
|
|
bin[5] = b[1] # lsb of high_ofs
|
|
bin[6] = (-sum(bin)) & 0x0FF # chksum
|
|
fwrite(':' +
|
|
asstr(hexlify(bin.tostring()).translate(table)) +
|
|
'\n')
|
|
|
|
while True:
|
|
# produce one record
|
|
low_addr = cur_addr & 0x0FFFF
|
|
# chain_len off by 1
|
|
chain_len = min(15, 65535-low_addr, maxaddr-cur_addr)
|
|
|
|
# search continuous chain
|
|
stop_addr = cur_addr + chain_len
|
|
if chain_len:
|
|
ix = bisect_right(addresses, stop_addr,
|
|
cur_ix,
|
|
min(cur_ix+chain_len+1, addr_len))
|
|
chain_len = ix - cur_ix # real chain_len
|
|
# there could be small holes in the chain
|
|
# but we will catch them by try-except later
|
|
# so for big continuous files we will work
|
|
# at maximum possible speed
|
|
else:
|
|
chain_len = 1 # real chain_len
|
|
|
|
bin = array('B', asbytes('\0'*(5+chain_len)))
|
|
b = divmod(low_addr, 256)
|
|
bin[1] = b[0] # msb of low_addr
|
|
bin[2] = b[1] # lsb of low_addr
|
|
bin[3] = 0 # rectype
|
|
try: # if there is small holes we'll catch them
|
|
for i in range(chain_len):
|
|
bin[4+i] = self._buf[cur_addr+i]
|
|
except KeyError:
|
|
# we catch a hole so we should shrink the chain
|
|
chain_len = i
|
|
bin = bin[:5+i]
|
|
bin[0] = chain_len
|
|
bin[4+chain_len] = (-sum(bin)) & 0x0FF # chksum
|
|
fwrite(':' +
|
|
asstr(hexlify(bin.tostring()).translate(table)) +
|
|
'\n')
|
|
|
|
# adjust cur_addr/cur_ix
|
|
cur_ix += chain_len
|
|
if cur_ix < addr_len:
|
|
cur_addr = addresses[cur_ix]
|
|
else:
|
|
cur_addr = maxaddr + 1
|
|
break
|
|
high_addr = int(cur_addr>>16)
|
|
if high_addr > high_ofs:
|
|
break
|
|
|
|
# end-of-file record
|
|
fwrite(":00000001FF\n")
|
|
if fclose:
|
|
fclose()
|
|
|
|
def tofile(self, fobj, format):
|
|
"""Write data to hex or bin file. Preferred method over tobin or tohex.
|
|
|
|
@param fobj file name or file-like object
|
|
@param format file format ("hex" or "bin")
|
|
"""
|
|
if format == 'hex':
|
|
self.write_hex_file(fobj)
|
|
elif format == 'bin':
|
|
self.tobinfile(fobj)
|
|
else:
|
|
raise ValueError('format should be either "hex" or "bin";'
|
|
' got %r instead' % format)
|
|
|
|
def gets(self, addr, length):
|
|
"""Get string of bytes from given address. If any entries are blank
|
|
from addr through addr+length, a NotEnoughDataError exception will
|
|
be raised. Padding is not used."""
|
|
a = array('B', asbytes('\0'*length))
|
|
try:
|
|
for i in range(length):
|
|
a[i] = self._buf[addr+i]
|
|
except KeyError:
|
|
raise NotEnoughDataError(address=addr, length=length)
|
|
return asstr(a.tostring())
|
|
|
|
def puts(self, addr, s):
|
|
"""Put string of bytes at given address. Will overwrite any previous
|
|
entries.
|
|
"""
|
|
a = array('B', asbytes(s))
|
|
for i in range(len(a)):
|
|
self._buf[addr+i] = a[i]
|
|
|
|
def getsz(self, addr):
|
|
"""Get zero-terminated string from given address. Will raise
|
|
NotEnoughDataError exception if a hole is encountered before a 0.
|
|
"""
|
|
i = 0
|
|
try:
|
|
while True:
|
|
if self._buf[addr+i] == 0:
|
|
break
|
|
i += 1
|
|
except KeyError:
|
|
raise NotEnoughDataError(msg=('Bad access at 0x%X: '
|
|
'not enough data to read zero-terminated string') % addr)
|
|
return self.gets(addr, i)
|
|
|
|
def putsz(self, addr, s):
|
|
"""Put string in object at addr and append terminating zero at end."""
|
|
self.puts(addr, s)
|
|
self._buf[addr+len(s)] = 0
|
|
|
|
def dump(self, tofile=None):
|
|
"""Dump object content to specified file object or to stdout if None.
|
|
Format is a hexdump with some header information at the beginning,
|
|
addresses on the left, and data on right.
|
|
|
|
@param tofile file-like object to dump to
|
|
"""
|
|
|
|
if tofile is None:
|
|
tofile = sys.stdout
|
|
# start addr possibly
|
|
if self.start_addr is not None:
|
|
cs = self.start_addr.get('CS')
|
|
ip = self.start_addr.get('IP')
|
|
eip = self.start_addr.get('EIP')
|
|
if eip is not None and cs is None and ip is None:
|
|
tofile.write('EIP = 0x%08X\n' % eip)
|
|
elif eip is None and cs is not None and ip is not None:
|
|
tofile.write('CS = 0x%04X, IP = 0x%04X\n' % (cs, ip))
|
|
else:
|
|
tofile.write('start_addr = %r\n' % start_addr)
|
|
# actual data
|
|
addresses = self._buf.keys()
|
|
if addresses:
|
|
addresses.sort()
|
|
minaddr = addresses[0]
|
|
maxaddr = addresses[-1]
|
|
startaddr = int(minaddr>>4)*16
|
|
endaddr = int((maxaddr>>4)+1)*16
|
|
maxdigits = max(len(str(endaddr)), 4)
|
|
templa = '%%0%dX' % maxdigits
|
|
range16 = range(16)
|
|
for i in range(startaddr, endaddr, 16):
|
|
tofile.write(templa % i)
|
|
tofile.write(' ')
|
|
s = []
|
|
for j in range16:
|
|
x = self._buf.get(i+j)
|
|
if x is not None:
|
|
tofile.write(' %02X' % x)
|
|
if 32 <= x < 127: # GNU less does not like 0x7F (128 decimal) so we'd better show it as dot
|
|
s.append(chr(x))
|
|
else:
|
|
s.append('.')
|
|
else:
|
|
tofile.write(' --')
|
|
s.append(' ')
|
|
tofile.write(' |' + ''.join(s) + '|\n')
|
|
|
|
def merge(self, other, overlap='error'):
|
|
"""Merge content of other IntelHex object into current object (self).
|
|
@param other other IntelHex object.
|
|
@param overlap action on overlap of data or starting addr:
|
|
- error: raising OverlapError;
|
|
- ignore: ignore other data and keep current data
|
|
in overlapping region;
|
|
- replace: replace data with other data
|
|
in overlapping region.
|
|
|
|
@raise TypeError if other is not instance of IntelHex
|
|
@raise ValueError if other is the same object as self
|
|
(it can't merge itself)
|
|
@raise ValueError if overlap argument has incorrect value
|
|
@raise AddressOverlapError on overlapped data
|
|
"""
|
|
# check args
|
|
if not isinstance(other, IntelHex):
|
|
raise TypeError('other should be IntelHex object')
|
|
if other is self:
|
|
raise ValueError("Can't merge itself")
|
|
if overlap not in ('error', 'ignore', 'replace'):
|
|
raise ValueError("overlap argument should be either "
|
|
"'error', 'ignore' or 'replace'")
|
|
# merge data
|
|
this_buf = self._buf
|
|
other_buf = other._buf
|
|
for i in other_buf:
|
|
if i in this_buf:
|
|
if overlap == 'error':
|
|
raise AddressOverlapError(
|
|
'Data overlapped at address 0x%X' % i)
|
|
elif overlap == 'ignore':
|
|
continue
|
|
this_buf[i] = other_buf[i]
|
|
# merge start_addr
|
|
if self.start_addr != other.start_addr:
|
|
if self.start_addr is None: # set start addr from other
|
|
self.start_addr = other.start_addr
|
|
elif other.start_addr is None: # keep existing start addr
|
|
pass
|
|
else: # conflict
|
|
if overlap == 'error':
|
|
raise AddressOverlapError(
|
|
'Starting addresses are different')
|
|
elif overlap == 'replace':
|
|
self.start_addr = other.start_addr
|
|
#/IntelHex
|
|
|
|
|
|
class IntelHex16bit(IntelHex):
|
|
"""Access to data as 16-bit words. Intended to use with Microchip HEX files."""
|
|
|
|
def __init__(self, source=None):
|
|
"""Construct class from HEX file
|
|
or from instance of ordinary IntelHex class. If IntelHex object
|
|
is passed as source, the original IntelHex object should not be used
|
|
again because this class will alter it. This class leaves padding
|
|
alone unless it was precisely 0xFF. In that instance it is sign
|
|
extended to 0xFFFF.
|
|
|
|
@param source file name of HEX file or file object
|
|
or instance of ordinary IntelHex class.
|
|
Will also accept dictionary from todict method.
|
|
"""
|
|
if isinstance(source, IntelHex):
|
|
# from ihex8
|
|
self.padding = source.padding
|
|
self.start_addr = source.start_addr
|
|
# private members
|
|
self._buf = source._buf
|
|
self._offset = source._offset
|
|
elif isinstance(source, dict):
|
|
raise IntelHexError("IntelHex16bit does not support initialization from dictionary yet.\n"
|
|
"Patches are welcome.")
|
|
else:
|
|
IntelHex.__init__(self, source)
|
|
|
|
if self.padding == 0x0FF:
|
|
self.padding = 0x0FFFF
|
|
|
|
def __getitem__(self, addr16):
|
|
"""Get 16-bit word from address.
|
|
Raise error if only one byte from the pair is set.
|
|
We assume a Little Endian interpretation of the hex file.
|
|
|
|
@param addr16 address of word (addr8 = 2 * addr16).
|
|
@return word if bytes exists in HEX file, or self.padding
|
|
if no data found.
|
|
"""
|
|
addr1 = addr16 * 2
|
|
addr2 = addr1 + 1
|
|
byte1 = self._buf.get(addr1, None)
|
|
byte2 = self._buf.get(addr2, None)
|
|
|
|
if byte1 != None and byte2 != None:
|
|
return byte1 | (byte2 << 8) # low endian
|
|
|
|
if byte1 == None and byte2 == None:
|
|
return self.padding
|
|
|
|
raise BadAccess16bit(address=addr16)
|
|
|
|
def __setitem__(self, addr16, word):
|
|
"""Sets the address at addr16 to word assuming Little Endian mode.
|
|
"""
|
|
addr_byte = addr16 * 2
|
|
b = divmod(word, 256)
|
|
self._buf[addr_byte] = b[1]
|
|
self._buf[addr_byte+1] = b[0]
|
|
|
|
def minaddr(self):
|
|
'''Get minimal address of HEX content in 16-bit mode.
|
|
|
|
@return minimal address used in this object
|
|
'''
|
|
aa = self._buf.keys()
|
|
if aa == []:
|
|
return 0
|
|
else:
|
|
return min(aa)>>1
|
|
|
|
def maxaddr(self):
|
|
'''Get maximal address of HEX content in 16-bit mode.
|
|
|
|
@return maximal address used in this object
|
|
'''
|
|
aa = self._buf.keys()
|
|
if aa == []:
|
|
return 0
|
|
else:
|
|
return max(aa)>>1
|
|
|
|
def tobinarray(self, start=None, end=None, size=None):
|
|
'''Convert this object to binary form as array (of 2-bytes word data).
|
|
If start and end unspecified, they will be inferred from the data.
|
|
@param start start address of output data.
|
|
@param end end address of output data (inclusive).
|
|
@param size size of the block (number of words),
|
|
used with start or end parameter.
|
|
@return array of unsigned short (uint16_t) data.
|
|
'''
|
|
bin = array('H')
|
|
|
|
if self._buf == {} and None in (start, end):
|
|
return bin
|
|
|
|
if size is not None and size <= 0:
|
|
raise ValueError("tobinarray: wrong value for size")
|
|
|
|
start, end = self._get_start_end(start, end, size)
|
|
|
|
for addr in range(start, end+1):
|
|
bin.append(self[addr])
|
|
|
|
return bin
|
|
|
|
|
|
#/class IntelHex16bit
|
|
|
|
|
|
def hex2bin(fin, fout, start=None, end=None, size=None, pad=None):
|
|
"""Hex-to-Bin convertor engine.
|
|
@return 0 if all OK
|
|
|
|
@param fin input hex file (filename or file-like object)
|
|
@param fout output bin file (filename or file-like object)
|
|
@param start start of address range (optional)
|
|
@param end end of address range (inclusive; optional)
|
|
@param size size of resulting file (in bytes) (optional)
|
|
@param pad padding byte (optional)
|
|
"""
|
|
try:
|
|
h = IntelHex(fin)
|
|
except HexReaderError as e:
|
|
txt = "ERROR: bad HEX file: %s" % str(e)
|
|
print(txt)
|
|
return 1
|
|
|
|
# start, end, size
|
|
if size != None and size != 0:
|
|
if end == None:
|
|
if start == None:
|
|
start = h.minaddr()
|
|
end = start + size - 1
|
|
else:
|
|
if (end+1) >= size:
|
|
start = end + 1 - size
|
|
else:
|
|
start = 0
|
|
|
|
try:
|
|
if pad is not None:
|
|
# using .padding attribute rather than pad argument to function call
|
|
h.padding = pad
|
|
h.tobinfile(fout, start, end)
|
|
except IOError as e:
|
|
txt = "ERROR: Could not write to file: %s: %s" % (fout, str(e))
|
|
print(txt)
|
|
return 1
|
|
|
|
return 0
|
|
#/def hex2bin
|
|
|
|
|
|
def bin2hex(fin, fout, offset=0):
|
|
"""Simple bin-to-hex convertor.
|
|
@return 0 if all OK
|
|
|
|
@param fin input bin file (filename or file-like object)
|
|
@param fout output hex file (filename or file-like object)
|
|
@param offset starting address offset for loading bin
|
|
"""
|
|
h = IntelHex()
|
|
try:
|
|
h.loadbin(fin, offset)
|
|
except IOError as e:
|
|
txt = 'ERROR: unable to load bin file:', str(e)
|
|
print(txt)
|
|
return 1
|
|
|
|
try:
|
|
h.tofile(fout, format='hex')
|
|
except IOError as e:
|
|
txt = "ERROR: Could not write to file: %s: %s" % (fout, str(e))
|
|
print(txt)
|
|
return 1
|
|
|
|
return 0
|
|
#/def bin2hex
|
|
|
|
|
|
def diff_dumps(ih1, ih2, tofile=None, name1="a", name2="b", n_context=3):
|
|
"""Diff 2 IntelHex objects and produce unified diff output for their
|
|
hex dumps.
|
|
|
|
@param ih1 first IntelHex object to compare
|
|
@param ih2 second IntelHex object to compare
|
|
@param tofile file-like object to write output
|
|
@param name1 name of the first hex file to show in the diff header
|
|
@param name2 name of the first hex file to show in the diff header
|
|
@param n_context number of context lines in the unidiff output
|
|
"""
|
|
def prepare_lines(ih):
|
|
from cStringIO import StringIO
|
|
sio = StringIO()
|
|
ih.dump(sio)
|
|
dump = sio.getvalue()
|
|
lines = dump.splitlines()
|
|
return lines
|
|
a = prepare_lines(ih1)
|
|
b = prepare_lines(ih2)
|
|
import difflib
|
|
result = list(difflib.unified_diff(a, b, fromfile=name1, tofile=name2, n=n_context, lineterm=''))
|
|
if tofile is None:
|
|
tofile = sys.stdout
|
|
output = '\n'.join(result)+'\n'
|
|
tofile.write(output)
|
|
|
|
|
|
class Record(object):
|
|
"""Helper methods to build valid ihex records."""
|
|
|
|
def _from_bytes(bytes):
|
|
"""Takes a list of bytes, computes the checksum, and outputs the entire
|
|
record as a string. bytes should be the hex record without the colon
|
|
or final checksum.
|
|
|
|
@param bytes list of byte values so far to pack into record.
|
|
@return String representation of one HEX record
|
|
"""
|
|
assert len(bytes) >= 4
|
|
# calculate checksum
|
|
s = (-sum(bytes)) & 0x0FF
|
|
bin = array('B', bytes + [s])
|
|
return ':' + asstr(hexlify(bin.tostring())).upper()
|
|
_from_bytes = staticmethod(_from_bytes)
|
|
|
|
def data(offset, bytes):
|
|
"""Return Data record. This constructs the full record, including
|
|
the length information, the record type (0x00), the
|
|
checksum, and the offset.
|
|
|
|
@param offset load offset of first byte.
|
|
@param bytes list of byte values to pack into record.
|
|
|
|
@return String representation of one HEX record
|
|
"""
|
|
assert 0 <= offset < 65536
|
|
assert 0 < len(bytes) < 256
|
|
b = [len(bytes), (offset>>8)&0x0FF, offset&0x0FF, 0x00] + bytes
|
|
return Record._from_bytes(b)
|
|
data = staticmethod(data)
|
|
|
|
def eof():
|
|
"""Return End of File record as a string.
|
|
@return String representation of Intel Hex EOF record
|
|
"""
|
|
return ':00000001FF'
|
|
eof = staticmethod(eof)
|
|
|
|
def extended_segment_address(usba):
|
|
"""Return Extended Segment Address Record.
|
|
@param usba Upper Segment Base Address.
|
|
|
|
@return String representation of Intel Hex USBA record.
|
|
"""
|
|
b = [2, 0, 0, 0x02, (usba>>8)&0x0FF, usba&0x0FF]
|
|
return Record._from_bytes(b)
|
|
extended_segment_address = staticmethod(extended_segment_address)
|
|
|
|
def start_segment_address(cs, ip):
|
|
"""Return Start Segment Address Record.
|
|
@param cs 16-bit value for CS register.
|
|
@param ip 16-bit value for IP register.
|
|
|
|
@return String representation of Intel Hex SSA record.
|
|
"""
|
|
b = [4, 0, 0, 0x03, (cs>>8)&0x0FF, cs&0x0FF,
|
|
(ip>>8)&0x0FF, ip&0x0FF]
|
|
return Record._from_bytes(b)
|
|
start_segment_address = staticmethod(start_segment_address)
|
|
|
|
def extended_linear_address(ulba):
|
|
"""Return Extended Linear Address Record.
|
|
@param ulba Upper Linear Base Address.
|
|
|
|
@return String representation of Intel Hex ELA record.
|
|
"""
|
|
b = [2, 0, 0, 0x04, (ulba>>8)&0x0FF, ulba&0x0FF]
|
|
return Record._from_bytes(b)
|
|
extended_linear_address = staticmethod(extended_linear_address)
|
|
|
|
def start_linear_address(eip):
|
|
"""Return Start Linear Address Record.
|
|
@param eip 32-bit linear address for the EIP register.
|
|
|
|
@return String representation of Intel Hex SLA record.
|
|
"""
|
|
b = [4, 0, 0, 0x05, (eip>>24)&0x0FF, (eip>>16)&0x0FF,
|
|
(eip>>8)&0x0FF, eip&0x0FF]
|
|
return Record._from_bytes(b)
|
|
start_linear_address = staticmethod(start_linear_address)
|
|
|
|
|
|
class _BadFileNotation(Exception):
|
|
"""Special error class to use with _get_file_and_addr_range."""
|
|
pass
|
|
|
|
def _get_file_and_addr_range(s, _support_drive_letter=None):
|
|
"""Special method for hexmerge.py script to split file notation
|
|
into 3 parts: (filename, start, end)
|
|
|
|
@raise _BadFileNotation when string cannot be safely split.
|
|
"""
|
|
if _support_drive_letter is None:
|
|
_support_drive_letter = (os.name == 'nt')
|
|
drive = ''
|
|
if _support_drive_letter:
|
|
if s[1:2] == ':' and s[0].upper() in ''.join([chr(i) for i in range(ord('A'), ord('Z')+1)]):
|
|
drive = s[:2]
|
|
s = s[2:]
|
|
parts = s.split(':')
|
|
n = len(parts)
|
|
if n == 1:
|
|
fname = parts[0]
|
|
fstart = None
|
|
fend = None
|
|
elif n != 3:
|
|
raise _BadFileNotation
|
|
else:
|
|
fname = parts[0]
|
|
def ascii_hex_to_int(ascii):
|
|
if ascii is not None:
|
|
try:
|
|
return int(ascii, 16)
|
|
except ValueError:
|
|
raise _BadFileNotation
|
|
return ascii
|
|
fstart = ascii_hex_to_int(parts[1] or None)
|
|
fend = ascii_hex_to_int(parts[2] or None)
|
|
return drive+fname, fstart, fend
|
|
|
|
|
|
##
|
|
# IntelHex Errors Hierarchy:
|
|
#
|
|
# IntelHexError - basic error
|
|
# HexReaderError - general hex reader error
|
|
# AddressOverlapError - data for the same address overlap
|
|
# HexRecordError - hex record decoder base error
|
|
# RecordLengthError - record has invalid length
|
|
# RecordTypeError - record has invalid type (RECTYP)
|
|
# RecordChecksumError - record checksum mismatch
|
|
# EOFRecordError - invalid EOF record (type 01)
|
|
# ExtendedAddressRecordError - extended address record base error
|
|
# ExtendedSegmentAddressRecordError - invalid extended segment address record (type 02)
|
|
# ExtendedLinearAddressRecordError - invalid extended linear address record (type 04)
|
|
# StartAddressRecordError - start address record base error
|
|
# StartSegmentAddressRecordError - invalid start segment address record (type 03)
|
|
# StartLinearAddressRecordError - invalid start linear address record (type 05)
|
|
# DuplicateStartAddressRecordError - start address record appears twice
|
|
# InvalidStartAddressValueError - invalid value of start addr record
|
|
# _EndOfFile - it's not real error, used internally by hex reader as signal that EOF record found
|
|
# BadAccess16bit - not enough data to read 16 bit value (deprecated, see NotEnoughDataError)
|
|
# NotEnoughDataError - not enough data to read N contiguous bytes
|
|
# EmptyIntelHexError - requested operation cannot be performed with empty object
|
|
|
|
class IntelHexError(Exception):
|
|
'''Base Exception class for IntelHex module'''
|
|
|
|
_fmt = 'IntelHex base error' #: format string
|
|
|
|
def __init__(self, msg=None, **kw):
|
|
"""Initialize the Exception with the given message.
|
|
"""
|
|
self.msg = msg
|
|
for key, value in kw.items():
|
|
setattr(self, key, value)
|
|
|
|
def __str__(self):
|
|
"""Return the message in this Exception."""
|
|
if self.msg:
|
|
return self.msg
|
|
try:
|
|
return self._fmt % self.__dict__
|
|
except (NameError, ValueError, KeyError) as e:
|
|
return 'Unprintable exception %s: %s' \
|
|
% (repr(e), str(e))
|
|
|
|
class _EndOfFile(IntelHexError):
|
|
"""Used for internal needs only."""
|
|
_fmt = 'EOF record reached -- signal to stop read file'
|
|
|
|
class HexReaderError(IntelHexError):
|
|
_fmt = 'Hex reader base error'
|
|
|
|
class AddressOverlapError(HexReaderError):
|
|
_fmt = 'Hex file has data overlap at address 0x%(address)X on line %(line)d'
|
|
|
|
# class NotAHexFileError was removed in trunk.revno.54 because it's not used
|
|
|
|
|
|
class HexRecordError(HexReaderError):
|
|
_fmt = 'Hex file contains invalid record at line %(line)d'
|
|
|
|
|
|
class RecordLengthError(HexRecordError):
|
|
_fmt = 'Record at line %(line)d has invalid length'
|
|
|
|
class RecordTypeError(HexRecordError):
|
|
_fmt = 'Record at line %(line)d has invalid record type'
|
|
|
|
class RecordChecksumError(HexRecordError):
|
|
_fmt = 'Record at line %(line)d has invalid checksum'
|
|
|
|
class EOFRecordError(HexRecordError):
|
|
_fmt = 'File has invalid End-of-File record'
|
|
|
|
|
|
class ExtendedAddressRecordError(HexRecordError):
|
|
_fmt = 'Base class for extended address exceptions'
|
|
|
|
class ExtendedSegmentAddressRecordError(ExtendedAddressRecordError):
|
|
_fmt = 'Invalid Extended Segment Address Record at line %(line)d'
|
|
|
|
class ExtendedLinearAddressRecordError(ExtendedAddressRecordError):
|
|
_fmt = 'Invalid Extended Linear Address Record at line %(line)d'
|
|
|
|
|
|
class StartAddressRecordError(HexRecordError):
|
|
_fmt = 'Base class for start address exceptions'
|
|
|
|
class StartSegmentAddressRecordError(StartAddressRecordError):
|
|
_fmt = 'Invalid Start Segment Address Record at line %(line)d'
|
|
|
|
class StartLinearAddressRecordError(StartAddressRecordError):
|
|
_fmt = 'Invalid Start Linear Address Record at line %(line)d'
|
|
|
|
class DuplicateStartAddressRecordError(StartAddressRecordError):
|
|
_fmt = 'Start Address Record appears twice at line %(line)d'
|
|
|
|
class InvalidStartAddressValueError(StartAddressRecordError):
|
|
_fmt = 'Invalid start address value: %(start_addr)s'
|
|
|
|
|
|
class NotEnoughDataError(IntelHexError):
|
|
_fmt = ('Bad access at 0x%(address)X: '
|
|
'not enough data to read %(length)d contiguous bytes')
|
|
|
|
class BadAccess16bit(NotEnoughDataError):
|
|
_fmt = 'Bad access at 0x%(address)X: not enough data to read 16 bit value'
|
|
|
|
class EmptyIntelHexError(IntelHexError):
|
|
_fmt = "Requested operation cannot be executed with empty object"
|