UHSDR/UHSDR-active-devel/mchf-eclipse/support/hex2dfu/intelhex/__init__.py
2022-11-08 16:13:55 +01:00

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"