644 lines
20 KiB
Python
644 lines
20 KiB
Python
#!/usr/bin/python
|
|
|
|
# Copyright (c) 2005-2007, 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 <Alexander Belchenko>
|
|
# 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.
|
|
|
|
This script also may be used as hex2bin convertor utility.
|
|
|
|
@author Alexander Belchenko (bialix AT ukr net)
|
|
@version 0.8.6
|
|
@date 2007/04/26
|
|
'''
|
|
|
|
|
|
__docformat__ = "javadoc"
|
|
|
|
|
|
from array import array
|
|
from binascii import hexlify, unhexlify
|
|
|
|
|
|
class IntelHex:
|
|
''' Intel HEX file reader. '''
|
|
|
|
def __init__(self, fname):
|
|
''' Constructor.
|
|
@param fname file name of HEX file or file object.
|
|
'''
|
|
#public members
|
|
self.Error = None
|
|
self.AddrOverlap = None
|
|
self.padding = 0x0FF
|
|
# Start Address
|
|
self.start_addr = None
|
|
|
|
# private members
|
|
self._fname = fname
|
|
self._buf = {}
|
|
self._readed = False
|
|
self._eof = False
|
|
self._offset = 0
|
|
|
|
def readfile(self):
|
|
''' Read file into internal buffer.
|
|
@return True if successful.
|
|
'''
|
|
if self._readed:
|
|
return True
|
|
|
|
if not hasattr(self._fname, "read"):
|
|
f = file(self._fname, "rU")
|
|
fclose = f.close
|
|
else:
|
|
f = self._fname
|
|
fclose = None
|
|
|
|
self._offset = 0
|
|
self._eof = False
|
|
|
|
result = True
|
|
|
|
for s in f:
|
|
if not self.decode_record(s):
|
|
result = False
|
|
break
|
|
|
|
if self._eof:
|
|
break
|
|
|
|
if fclose:
|
|
fclose()
|
|
|
|
self._readed = result
|
|
return result
|
|
|
|
def decode_record(self, s):
|
|
''' Decode one record of HEX file.
|
|
@param s line with HEX record.
|
|
@return True if line decode OK, or this is not HEX line.
|
|
False if this is invalid HEX line or checksum error.
|
|
'''
|
|
s = s.rstrip('\r\n')
|
|
if not s:
|
|
return True # empty line
|
|
|
|
if s[0] == ':':
|
|
try:
|
|
bin = array('B', unhexlify(s[1:]))
|
|
except TypeError:
|
|
# this might be raised by unhexlify when odd hexascii digits
|
|
self.Error = "Odd hexascii digits"
|
|
return False
|
|
length = len(bin)
|
|
if length < 5:
|
|
self.Error = "Too short line"
|
|
return False
|
|
else:
|
|
return True # first char must be ':'
|
|
|
|
record_length = bin[0]
|
|
|
|
if length != (5 + record_length):
|
|
self.Error = "Invalid line length"
|
|
return False
|
|
|
|
addr = bin[1]*256 + bin[2]
|
|
|
|
record_type = bin[3]
|
|
if not (0 <= record_type <= 5):
|
|
self.Error = "Invalid type of record: %d" % record_type
|
|
return False
|
|
|
|
crc = sum(bin)
|
|
crc &= 0x0FF
|
|
if crc != 0:
|
|
self.Error = "Invalid crc"
|
|
return False
|
|
|
|
if record_type == 0:
|
|
# data record
|
|
addr += self._offset
|
|
for i in xrange(4, 4+record_length):
|
|
if not self._buf.get(addr, None) is None:
|
|
self.AddrOverlap = addr
|
|
self._buf[addr] = bin[i]
|
|
addr += 1 # FIXME: addr should be wrapped on 64K boundary
|
|
|
|
elif record_type == 1:
|
|
# end of file record
|
|
if record_length != 0:
|
|
self.Error = "Bad End-of-File Record"
|
|
return False
|
|
self._eof = True
|
|
|
|
elif record_type == 2:
|
|
# Extended 8086 Segment Record
|
|
if record_length != 2 or addr != 0:
|
|
self.Error = "Bad Extended 8086 Segment Record"
|
|
return False
|
|
self._offset = (bin[4]*256 + bin[5]) * 16
|
|
|
|
elif record_type == 4:
|
|
# Extended Linear Address Record
|
|
if record_length != 2 or addr != 0:
|
|
self.Error = "Bad Extended Linear Address Record"
|
|
return False
|
|
self._offset = (bin[4]*256 + bin[5]) * 65536
|
|
|
|
elif record_type == 3:
|
|
# Start Segment Address Record
|
|
if record_length != 4 or addr != 0:
|
|
self.Error = "Bad Start Segment Address Record"
|
|
return False
|
|
if self.start_addr:
|
|
self.Error = "Start Address Record appears twice"
|
|
return False
|
|
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:
|
|
self.Error = "Bad Start Linear Address Record"
|
|
return False
|
|
if self.start_addr:
|
|
self.Error = "Start Address Record appears twice"
|
|
return False
|
|
self.start_addr = {'EIP': (bin[4]*16777216 +
|
|
bin[5]*65536 +
|
|
bin[6]*256 +
|
|
bin[7]),
|
|
}
|
|
|
|
return True
|
|
|
|
def _get_start_end(self, start=None, end=None):
|
|
"""Return default values for start and end if they are None
|
|
"""
|
|
if start is None:
|
|
start = min(self._buf.keys())
|
|
if end is None:
|
|
end = max(self._buf.keys())
|
|
if start > end:
|
|
start, end = end, start
|
|
return start, end
|
|
|
|
def tobinarray(self, start=None, end=None, pad=None):
|
|
''' Convert to binary form.
|
|
@param start start address of output bytes.
|
|
@param end end address of output bytes.
|
|
@param pad fill empty spaces with this value
|
|
(if None used self.padding).
|
|
@return array of unsigned char data.
|
|
'''
|
|
if pad is None:
|
|
pad = self.padding
|
|
|
|
bin = array('B')
|
|
|
|
if self._buf == {}:
|
|
return bin
|
|
|
|
start, end = self._get_start_end(start, end)
|
|
|
|
for i in xrange(start, end+1):
|
|
bin.append(self._buf.get(i, pad))
|
|
|
|
return bin
|
|
|
|
def tobinstr(self, start=None, end=None, pad=0xFF):
|
|
''' Convert to binary form.
|
|
@param start start address of output bytes.
|
|
@param end end address of output bytes.
|
|
@param pad fill empty spaces with this value
|
|
(if None used self.padding).
|
|
@return string of binary data.
|
|
'''
|
|
return self.tobinarray(start, end, pad).tostring()
|
|
|
|
def tobinfile(self, fobj, start=None, end=None, pad=0xFF):
|
|
'''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.
|
|
@param pad fill empty spaces with this value
|
|
(if None used self.padding).
|
|
'''
|
|
if not hasattr(fobj, "write"):
|
|
fobj = file(fobj, "wb")
|
|
fclose = fobj.close
|
|
else:
|
|
fclose = None
|
|
|
|
fobj.write(self.tobinstr(start, end, pad))
|
|
|
|
if fclose:
|
|
fclose()
|
|
|
|
def minaddr(self):
|
|
''' Get minimal address of HEX content. '''
|
|
aa = self._buf.keys()
|
|
if aa == []:
|
|
return 0
|
|
else:
|
|
return min(aa)
|
|
|
|
def maxaddr(self):
|
|
''' Get maximal address of HEX content. '''
|
|
aa = self._buf.keys()
|
|
if aa == []:
|
|
return 0
|
|
else:
|
|
return max(aa)
|
|
|
|
def __getitem__(self, addr):
|
|
''' Get byte from address.
|
|
@param addr address of byte.
|
|
@return byte if address exists in HEX file, or self.padding
|
|
if no data found.
|
|
'''
|
|
return self._buf.get(addr, self.padding)
|
|
|
|
def __setitem__(self, addr, byte):
|
|
self._buf[addr] = byte
|
|
|
|
def writefile(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 nothing
|
|
will be written.
|
|
|
|
@return True if successful.
|
|
"""
|
|
fwrite = getattr(f, "write", None)
|
|
if fwrite:
|
|
fobj = f
|
|
fclose = None
|
|
else:
|
|
fobj = file(f, 'w')
|
|
fwrite = fobj.write
|
|
fclose = fobj.close
|
|
|
|
# start address record if any
|
|
if self.start_addr and write_start_addr:
|
|
keys = self.start_addr.keys()
|
|
keys.sort()
|
|
bin = array('B', '\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(':')
|
|
fwrite(hexlify(bin.tostring()).upper())
|
|
fwrite('\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(':')
|
|
fwrite(hexlify(bin.tostring()).upper())
|
|
fwrite('\n')
|
|
else:
|
|
self.Error = ('Invalid start address value: %r'
|
|
% self.start_addr)
|
|
return False
|
|
|
|
# data
|
|
minaddr = IntelHex.minaddr(self)
|
|
maxaddr = IntelHex.maxaddr(self)
|
|
if maxaddr > 65535:
|
|
offset = (minaddr/65536)*65536
|
|
else:
|
|
offset = None
|
|
|
|
while True:
|
|
if offset != None:
|
|
# emit 32-bit offset record
|
|
high_ofs = offset / 65536
|
|
offset_record = ":02000004%04X" % high_ofs
|
|
bytes = divmod(high_ofs, 256)
|
|
csum = 2 + 4 + bytes[0] + bytes[1]
|
|
csum = (-csum) & 0x0FF
|
|
offset_record += "%02X\n" % csum
|
|
|
|
ofs = offset
|
|
if (ofs + 65536) > maxaddr:
|
|
rng = xrange(maxaddr - ofs + 1)
|
|
else:
|
|
rng = xrange(65536)
|
|
else:
|
|
ofs = 0
|
|
offset_record = ''
|
|
rng = xrange(maxaddr + 1)
|
|
|
|
csum = 0
|
|
k = 0
|
|
record = ""
|
|
for addr in rng:
|
|
byte = self._buf.get(ofs+addr, None)
|
|
if byte != None:
|
|
if k == 0:
|
|
# optionally offset record
|
|
fobj.write(offset_record)
|
|
offset_record = ''
|
|
# start data record
|
|
record += "%04X00" % addr
|
|
bytes = divmod(addr, 256)
|
|
csum = bytes[0] + bytes[1]
|
|
|
|
k += 1
|
|
# continue data in record
|
|
record += "%02X" % byte
|
|
csum += byte
|
|
|
|
# check for length of record
|
|
if k < 16:
|
|
continue
|
|
|
|
if k != 0:
|
|
# close record
|
|
csum += k
|
|
csum = (-csum) & 0x0FF
|
|
record += "%02X" % csum
|
|
fobj.write(":%02X%s\n" % (k, record))
|
|
# cleanup
|
|
csum = 0
|
|
k = 0
|
|
record = ""
|
|
else:
|
|
if k != 0:
|
|
# close record
|
|
csum += k
|
|
csum = (-csum) & 0x0FF
|
|
record += "%02X" % csum
|
|
fobj.write(":%02X%s\n" % (k, record))
|
|
|
|
# advance offset
|
|
if offset is None:
|
|
break
|
|
|
|
offset += 65536
|
|
if offset > maxaddr:
|
|
break
|
|
|
|
# end-of-file record
|
|
fobj.write(":00000001FF\n")
|
|
if fclose:
|
|
fclose()
|
|
|
|
return True
|
|
#/IntelHex
|
|
|
|
|
|
class IntelHex16bit(IntelHex):
|
|
"""Access to data as 16-bit words."""
|
|
|
|
def __init__(self, source):
|
|
"""Construct class from HEX file
|
|
or from instance of ordinary IntelHex class.
|
|
|
|
@param source file name of HEX file or file object
|
|
or instance of ordinary IntelHex class
|
|
"""
|
|
if isinstance(source, IntelHex):
|
|
# from ihex8
|
|
self.Error = source.Error
|
|
self.AddrOverlap = source.AddrOverlap
|
|
self.padding = source.padding
|
|
|
|
# private members
|
|
self._fname = source._fname
|
|
self._buf = source._buf
|
|
self._readed = source._readed
|
|
self._eof = source._eof
|
|
self._offset = source._offset
|
|
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 found only one byte from pair.
|
|
|
|
@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 Exception, 'Bad access in 16-bit mode (not enough data)'
|
|
|
|
def __setitem__(self, addr16, word):
|
|
addr_byte = addr16 * 2
|
|
bytes = divmod(word, 256)
|
|
self._buf[addr_byte] = bytes[1]
|
|
self._buf[addr_byte+1] = bytes[0]
|
|
|
|
def minaddr(self):
|
|
'''Get minimal address of HEX content in 16-bit mode.'''
|
|
aa = self._buf.keys()
|
|
if aa == []:
|
|
return 0
|
|
else:
|
|
return min(aa)/2
|
|
|
|
def maxaddr(self):
|
|
'''Get maximal address of HEX content in 16-bit mode.'''
|
|
aa = self._buf.keys()
|
|
if aa == []:
|
|
return 0
|
|
else:
|
|
return max(aa)/2
|
|
|
|
#/class IntelHex16bit
|
|
|
|
|
|
def hex2bin(fin, fout, start=None, end=None, size=None, pad=0xFF):
|
|
"""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 (optional)
|
|
@param size size of resulting file (in bytes) (optional)
|
|
@param pad padding byte (optional)
|
|
"""
|
|
h = IntelHex(fin)
|
|
if not h.readfile():
|
|
print "Bad HEX file"
|
|
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:
|
|
h.tobinfile(fout, start, end, pad)
|
|
except IOError:
|
|
print "Could not write to file: %s" % fout
|
|
return 1
|
|
|
|
return 0
|
|
#/def hex2bin
|
|
|
|
|
|
if __name__ == '__main__':
|
|
import getopt
|
|
import os
|
|
import sys
|
|
|
|
usage = '''Hex2Bin python converting utility.
|
|
Usage:
|
|
python intelhex.py [options] file.hex [out.bin]
|
|
|
|
Arguments:
|
|
file.hex name of hex file to processing.
|
|
out.bin name of output file.
|
|
If omitted then output write to file.bin.
|
|
|
|
Options:
|
|
-h, --help this help message.
|
|
-p, --pad=FF pad byte for empty spaces (ascii hex value).
|
|
-r, --range=START:END specify address range for writing output
|
|
(ascii hex value).
|
|
Range can be in form 'START:' or ':END'.
|
|
-l, --length=NNNN,
|
|
-s, --size=NNNN size of output (decimal value).
|
|
'''
|
|
|
|
pad = 0xFF
|
|
start = None
|
|
end = None
|
|
size = None
|
|
|
|
try:
|
|
opts, args = getopt.getopt(sys.argv[1:], "hp:r:l:s:",
|
|
["help", "pad=", "range=",
|
|
"length=", "size="])
|
|
|
|
for o, a in opts:
|
|
if o in ("-h", "--help"):
|
|
print usage
|
|
sys.exit(0)
|
|
elif o in ("-p", "--pad"):
|
|
try:
|
|
pad = int(a, 16) & 0x0FF
|
|
except:
|
|
raise getopt.GetoptError, 'Bad pad value'
|
|
elif o in ("-r", "--range"):
|
|
try:
|
|
l = a.split(":")
|
|
if l[0] != '':
|
|
start = int(l[0], 16)
|
|
if l[1] != '':
|
|
end = int(l[1], 16)
|
|
except:
|
|
raise getopt.GetoptError, 'Bad range value(s)'
|
|
elif o in ("-l", "--lenght", "-s", "--size"):
|
|
try:
|
|
size = int(a, 10)
|
|
except:
|
|
raise getopt.GetoptError, 'Bad size value'
|
|
|
|
if start != None and end != None and size != None:
|
|
raise getopt.GetoptError, 'Cannot specify START:END and SIZE simultaneously'
|
|
|
|
if not args:
|
|
raise getopt.GetoptError, 'Hex file is not specified'
|
|
|
|
if len(args) > 2:
|
|
raise getopt.GetoptError, 'Too many arguments'
|
|
|
|
except getopt.GetoptError, msg:
|
|
print msg
|
|
print usage
|
|
sys.exit(2)
|
|
|
|
fin = args[0]
|
|
if len(args) == 1:
|
|
import os.path
|
|
name, ext = os.path.splitext(fin)
|
|
fout = name + ".bin"
|
|
else:
|
|
fout = args[1]
|
|
|
|
if not os.path.isfile(fin):
|
|
print "File not found"
|
|
sys.exit(1)
|
|
|
|
sys.exit(hex2bin(fin, fout, start, end, size, pad))
|