intelhex.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643
  1. #!/usr/bin/python
  2. # Copyright (c) 2005-2007, Alexander Belchenko
  3. # All rights reserved.
  4. #
  5. # Redistribution and use in source and binary forms,
  6. # with or without modification, are permitted provided
  7. # that the following conditions are met:
  8. #
  9. # * Redistributions of source code must retain
  10. # the above copyright notice, this list of conditions
  11. # and the following disclaimer.
  12. # * Redistributions in binary form must reproduce
  13. # the above copyright notice, this list of conditions
  14. # and the following disclaimer in the documentation
  15. # and/or other materials provided with the distribution.
  16. # * Neither the name of the <Alexander Belchenko>
  17. # nor the names of its contributors may be used to endorse
  18. # or promote products derived from this software
  19. # without specific prior written permission.
  20. #
  21. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  22. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
  23. # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
  24. # AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  25. # IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
  26. # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
  27. # OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  28. # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
  29. # OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
  30. # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  31. # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  32. # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
  33. # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  34. '''Intel HEX file format reader and converter.
  35. This script also may be used as hex2bin convertor utility.
  36. @author Alexander Belchenko (bialix AT ukr net)
  37. @version 0.8.6
  38. @date 2007/04/26
  39. '''
  40. __docformat__ = "javadoc"
  41. from array import array
  42. from binascii import hexlify, unhexlify
  43. class IntelHex:
  44. ''' Intel HEX file reader. '''
  45. def __init__(self, fname):
  46. ''' Constructor.
  47. @param fname file name of HEX file or file object.
  48. '''
  49. #public members
  50. self.Error = None
  51. self.AddrOverlap = None
  52. self.padding = 0x0FF
  53. # Start Address
  54. self.start_addr = None
  55. # private members
  56. self._fname = fname
  57. self._buf = {}
  58. self._readed = False
  59. self._eof = False
  60. self._offset = 0
  61. def readfile(self):
  62. ''' Read file into internal buffer.
  63. @return True if successful.
  64. '''
  65. if self._readed:
  66. return True
  67. if not hasattr(self._fname, "read"):
  68. f = file(self._fname, "rU")
  69. fclose = f.close
  70. else:
  71. f = self._fname
  72. fclose = None
  73. self._offset = 0
  74. self._eof = False
  75. result = True
  76. for s in f:
  77. if not self.decode_record(s):
  78. result = False
  79. break
  80. if self._eof:
  81. break
  82. if fclose:
  83. fclose()
  84. self._readed = result
  85. return result
  86. def decode_record(self, s):
  87. ''' Decode one record of HEX file.
  88. @param s line with HEX record.
  89. @return True if line decode OK, or this is not HEX line.
  90. False if this is invalid HEX line or checksum error.
  91. '''
  92. s = s.rstrip('\r\n')
  93. if not s:
  94. return True # empty line
  95. if s[0] == ':':
  96. try:
  97. bin = array('B', unhexlify(s[1:]))
  98. except TypeError:
  99. # this might be raised by unhexlify when odd hexascii digits
  100. self.Error = "Odd hexascii digits"
  101. return False
  102. length = len(bin)
  103. if length < 5:
  104. self.Error = "Too short line"
  105. return False
  106. else:
  107. return True # first char must be ':'
  108. record_length = bin[0]
  109. if length != (5 + record_length):
  110. self.Error = "Invalid line length"
  111. return False
  112. addr = bin[1]*256 + bin[2]
  113. record_type = bin[3]
  114. if not (0 <= record_type <= 5):
  115. self.Error = "Invalid type of record: %d" % record_type
  116. return False
  117. crc = sum(bin)
  118. crc &= 0x0FF
  119. if crc != 0:
  120. self.Error = "Invalid crc"
  121. return False
  122. if record_type == 0:
  123. # data record
  124. addr += self._offset
  125. for i in xrange(4, 4+record_length):
  126. if not self._buf.get(addr, None) is None:
  127. self.AddrOverlap = addr
  128. self._buf[addr] = bin[i]
  129. addr += 1 # FIXME: addr should be wrapped on 64K boundary
  130. elif record_type == 1:
  131. # end of file record
  132. if record_length != 0:
  133. self.Error = "Bad End-of-File Record"
  134. return False
  135. self._eof = True
  136. elif record_type == 2:
  137. # Extended 8086 Segment Record
  138. if record_length != 2 or addr != 0:
  139. self.Error = "Bad Extended 8086 Segment Record"
  140. return False
  141. self._offset = (bin[4]*256 + bin[5]) * 16
  142. elif record_type == 4:
  143. # Extended Linear Address Record
  144. if record_length != 2 or addr != 0:
  145. self.Error = "Bad Extended Linear Address Record"
  146. return False
  147. self._offset = (bin[4]*256 + bin[5]) * 65536
  148. elif record_type == 3:
  149. # Start Segment Address Record
  150. if record_length != 4 or addr != 0:
  151. self.Error = "Bad Start Segment Address Record"
  152. return False
  153. if self.start_addr:
  154. self.Error = "Start Address Record appears twice"
  155. return False
  156. self.start_addr = {'CS': bin[4]*256 + bin[5],
  157. 'IP': bin[6]*256 + bin[7],
  158. }
  159. elif record_type == 5:
  160. # Start Linear Address Record
  161. if record_length != 4 or addr != 0:
  162. self.Error = "Bad Start Linear Address Record"
  163. return False
  164. if self.start_addr:
  165. self.Error = "Start Address Record appears twice"
  166. return False
  167. self.start_addr = {'EIP': (bin[4]*16777216 +
  168. bin[5]*65536 +
  169. bin[6]*256 +
  170. bin[7]),
  171. }
  172. return True
  173. def _get_start_end(self, start=None, end=None):
  174. """Return default values for start and end if they are None
  175. """
  176. if start is None:
  177. start = min(self._buf.keys())
  178. if end is None:
  179. end = max(self._buf.keys())
  180. if start > end:
  181. start, end = end, start
  182. return start, end
  183. def tobinarray(self, start=None, end=None, pad=None):
  184. ''' Convert to binary form.
  185. @param start start address of output bytes.
  186. @param end end address of output bytes.
  187. @param pad fill empty spaces with this value
  188. (if None used self.padding).
  189. @return array of unsigned char data.
  190. '''
  191. if pad is None:
  192. pad = self.padding
  193. bin = array('B')
  194. if self._buf == {}:
  195. return bin
  196. start, end = self._get_start_end(start, end)
  197. for i in xrange(start, end+1):
  198. bin.append(self._buf.get(i, pad))
  199. return bin
  200. def tobinstr(self, start=None, end=None, pad=0xFF):
  201. ''' Convert to binary form.
  202. @param start start address of output bytes.
  203. @param end end address of output bytes.
  204. @param pad fill empty spaces with this value
  205. (if None used self.padding).
  206. @return string of binary data.
  207. '''
  208. return self.tobinarray(start, end, pad).tostring()
  209. def tobinfile(self, fobj, start=None, end=None, pad=0xFF):
  210. '''Convert to binary and write to file.
  211. @param fobj file name or file object for writing output bytes.
  212. @param start start address of output bytes.
  213. @param end end address of output bytes.
  214. @param pad fill empty spaces with this value
  215. (if None used self.padding).
  216. '''
  217. if not hasattr(fobj, "write"):
  218. fobj = file(fobj, "wb")
  219. fclose = fobj.close
  220. else:
  221. fclose = None
  222. fobj.write(self.tobinstr(start, end, pad))
  223. if fclose:
  224. fclose()
  225. def minaddr(self):
  226. ''' Get minimal address of HEX content. '''
  227. aa = self._buf.keys()
  228. if aa == []:
  229. return 0
  230. else:
  231. return min(aa)
  232. def maxaddr(self):
  233. ''' Get maximal address of HEX content. '''
  234. aa = self._buf.keys()
  235. if aa == []:
  236. return 0
  237. else:
  238. return max(aa)
  239. def __getitem__(self, addr):
  240. ''' Get byte from address.
  241. @param addr address of byte.
  242. @return byte if address exists in HEX file, or self.padding
  243. if no data found.
  244. '''
  245. return self._buf.get(addr, self.padding)
  246. def __setitem__(self, addr, byte):
  247. self._buf[addr] = byte
  248. def writefile(self, f, write_start_addr=True):
  249. """Write data to file f in HEX format.
  250. @param f filename or file-like object for writing
  251. @param write_start_addr enable or disable writing start address
  252. record to file (enabled by default).
  253. If there is no start address nothing
  254. will be written.
  255. @return True if successful.
  256. """
  257. fwrite = getattr(f, "write", None)
  258. if fwrite:
  259. fobj = f
  260. fclose = None
  261. else:
  262. fobj = file(f, 'w')
  263. fwrite = fobj.write
  264. fclose = fobj.close
  265. # start address record if any
  266. if self.start_addr and write_start_addr:
  267. keys = self.start_addr.keys()
  268. keys.sort()
  269. bin = array('B', '\0'*9)
  270. if keys == ['CS','IP']:
  271. # Start Segment Address Record
  272. bin[0] = 4 # reclen
  273. bin[1] = 0 # offset msb
  274. bin[2] = 0 # offset lsb
  275. bin[3] = 3 # rectyp
  276. cs = self.start_addr['CS']
  277. bin[4] = (cs >> 8) & 0x0FF
  278. bin[5] = cs & 0x0FF
  279. ip = self.start_addr['IP']
  280. bin[6] = (ip >> 8) & 0x0FF
  281. bin[7] = ip & 0x0FF
  282. bin[8] = (-sum(bin)) & 0x0FF # chksum
  283. fwrite(':')
  284. fwrite(hexlify(bin.tostring()).upper())
  285. fwrite('\n')
  286. elif keys == ['EIP']:
  287. # Start Linear Address Record
  288. bin[0] = 4 # reclen
  289. bin[1] = 0 # offset msb
  290. bin[2] = 0 # offset lsb
  291. bin[3] = 5 # rectyp
  292. eip = self.start_addr['EIP']
  293. bin[4] = (eip >> 24) & 0x0FF
  294. bin[5] = (eip >> 16) & 0x0FF
  295. bin[6] = (eip >> 8) & 0x0FF
  296. bin[7] = eip & 0x0FF
  297. bin[8] = (-sum(bin)) & 0x0FF # chksum
  298. fwrite(':')
  299. fwrite(hexlify(bin.tostring()).upper())
  300. fwrite('\n')
  301. else:
  302. self.Error = ('Invalid start address value: %r'
  303. % self.start_addr)
  304. return False
  305. # data
  306. minaddr = IntelHex.minaddr(self)
  307. maxaddr = IntelHex.maxaddr(self)
  308. if maxaddr > 65535:
  309. offset = (minaddr/65536)*65536
  310. else:
  311. offset = None
  312. while True:
  313. if offset != None:
  314. # emit 32-bit offset record
  315. high_ofs = offset / 65536
  316. offset_record = ":02000004%04X" % high_ofs
  317. bytes = divmod(high_ofs, 256)
  318. csum = 2 + 4 + bytes[0] + bytes[1]
  319. csum = (-csum) & 0x0FF
  320. offset_record += "%02X\n" % csum
  321. ofs = offset
  322. if (ofs + 65536) > maxaddr:
  323. rng = xrange(maxaddr - ofs + 1)
  324. else:
  325. rng = xrange(65536)
  326. else:
  327. ofs = 0
  328. offset_record = ''
  329. rng = xrange(maxaddr + 1)
  330. csum = 0
  331. k = 0
  332. record = ""
  333. for addr in rng:
  334. byte = self._buf.get(ofs+addr, None)
  335. if byte != None:
  336. if k == 0:
  337. # optionally offset record
  338. fobj.write(offset_record)
  339. offset_record = ''
  340. # start data record
  341. record += "%04X00" % addr
  342. bytes = divmod(addr, 256)
  343. csum = bytes[0] + bytes[1]
  344. k += 1
  345. # continue data in record
  346. record += "%02X" % byte
  347. csum += byte
  348. # check for length of record
  349. if k < 16:
  350. continue
  351. if k != 0:
  352. # close record
  353. csum += k
  354. csum = (-csum) & 0x0FF
  355. record += "%02X" % csum
  356. fobj.write(":%02X%s\n" % (k, record))
  357. # cleanup
  358. csum = 0
  359. k = 0
  360. record = ""
  361. else:
  362. if k != 0:
  363. # close record
  364. csum += k
  365. csum = (-csum) & 0x0FF
  366. record += "%02X" % csum
  367. fobj.write(":%02X%s\n" % (k, record))
  368. # advance offset
  369. if offset is None:
  370. break
  371. offset += 65536
  372. if offset > maxaddr:
  373. break
  374. # end-of-file record
  375. fobj.write(":00000001FF\n")
  376. if fclose:
  377. fclose()
  378. return True
  379. #/IntelHex
  380. class IntelHex16bit(IntelHex):
  381. """Access to data as 16-bit words."""
  382. def __init__(self, source):
  383. """Construct class from HEX file
  384. or from instance of ordinary IntelHex class.
  385. @param source file name of HEX file or file object
  386. or instance of ordinary IntelHex class
  387. """
  388. if isinstance(source, IntelHex):
  389. # from ihex8
  390. self.Error = source.Error
  391. self.AddrOverlap = source.AddrOverlap
  392. self.padding = source.padding
  393. # private members
  394. self._fname = source._fname
  395. self._buf = source._buf
  396. self._readed = source._readed
  397. self._eof = source._eof
  398. self._offset = source._offset
  399. else:
  400. IntelHex.__init__(self, source)
  401. if self.padding == 0x0FF:
  402. self.padding = 0x0FFFF
  403. def __getitem__(self, addr16):
  404. """Get 16-bit word from address.
  405. Raise error if found only one byte from pair.
  406. @param addr16 address of word (addr8 = 2 * addr16).
  407. @return word if bytes exists in HEX file, or self.padding
  408. if no data found.
  409. """
  410. addr1 = addr16 * 2
  411. addr2 = addr1 + 1
  412. byte1 = self._buf.get(addr1, None)
  413. byte2 = self._buf.get(addr2, None)
  414. if byte1 != None and byte2 != None:
  415. return byte1 | (byte2 << 8) # low endian
  416. if byte1 == None and byte2 == None:
  417. return self.padding
  418. raise Exception, 'Bad access in 16-bit mode (not enough data)'
  419. def __setitem__(self, addr16, word):
  420. addr_byte = addr16 * 2
  421. bytes = divmod(word, 256)
  422. self._buf[addr_byte] = bytes[1]
  423. self._buf[addr_byte+1] = bytes[0]
  424. def minaddr(self):
  425. '''Get minimal address of HEX content in 16-bit mode.'''
  426. aa = self._buf.keys()
  427. if aa == []:
  428. return 0
  429. else:
  430. return min(aa)/2
  431. def maxaddr(self):
  432. '''Get maximal address of HEX content in 16-bit mode.'''
  433. aa = self._buf.keys()
  434. if aa == []:
  435. return 0
  436. else:
  437. return max(aa)/2
  438. #/class IntelHex16bit
  439. def hex2bin(fin, fout, start=None, end=None, size=None, pad=0xFF):
  440. """Hex-to-Bin convertor engine.
  441. @return 0 if all OK
  442. @param fin input hex file (filename or file-like object)
  443. @param fout output bin file (filename or file-like object)
  444. @param start start of address range (optional)
  445. @param end end of address range (optional)
  446. @param size size of resulting file (in bytes) (optional)
  447. @param pad padding byte (optional)
  448. """
  449. h = IntelHex(fin)
  450. if not h.readfile():
  451. print "Bad HEX file"
  452. return 1
  453. # start, end, size
  454. if size != None and size != 0:
  455. if end == None:
  456. if start == None:
  457. start = h.minaddr()
  458. end = start + size - 1
  459. else:
  460. if (end+1) >= size:
  461. start = end + 1 - size
  462. else:
  463. start = 0
  464. try:
  465. h.tobinfile(fout, start, end, pad)
  466. except IOError:
  467. print "Could not write to file: %s" % fout
  468. return 1
  469. return 0
  470. #/def hex2bin
  471. if __name__ == '__main__':
  472. import getopt
  473. import os
  474. import sys
  475. usage = '''Hex2Bin python converting utility.
  476. Usage:
  477. python intelhex.py [options] file.hex [out.bin]
  478. Arguments:
  479. file.hex name of hex file to processing.
  480. out.bin name of output file.
  481. If omitted then output write to file.bin.
  482. Options:
  483. -h, --help this help message.
  484. -p, --pad=FF pad byte for empty spaces (ascii hex value).
  485. -r, --range=START:END specify address range for writing output
  486. (ascii hex value).
  487. Range can be in form 'START:' or ':END'.
  488. -l, --length=NNNN,
  489. -s, --size=NNNN size of output (decimal value).
  490. '''
  491. pad = 0xFF
  492. start = None
  493. end = None
  494. size = None
  495. try:
  496. opts, args = getopt.getopt(sys.argv[1:], "hp:r:l:s:",
  497. ["help", "pad=", "range=",
  498. "length=", "size="])
  499. for o, a in opts:
  500. if o in ("-h", "--help"):
  501. print usage
  502. sys.exit(0)
  503. elif o in ("-p", "--pad"):
  504. try:
  505. pad = int(a, 16) & 0x0FF
  506. except:
  507. raise getopt.GetoptError, 'Bad pad value'
  508. elif o in ("-r", "--range"):
  509. try:
  510. l = a.split(":")
  511. if l[0] != '':
  512. start = int(l[0], 16)
  513. if l[1] != '':
  514. end = int(l[1], 16)
  515. except:
  516. raise getopt.GetoptError, 'Bad range value(s)'
  517. elif o in ("-l", "--lenght", "-s", "--size"):
  518. try:
  519. size = int(a, 10)
  520. except:
  521. raise getopt.GetoptError, 'Bad size value'
  522. if start != None and end != None and size != None:
  523. raise getopt.GetoptError, 'Cannot specify START:END and SIZE simultaneously'
  524. if not args:
  525. raise getopt.GetoptError, 'Hex file is not specified'
  526. if len(args) > 2:
  527. raise getopt.GetoptError, 'Too many arguments'
  528. except getopt.GetoptError, msg:
  529. print msg
  530. print usage
  531. sys.exit(2)
  532. fin = args[0]
  533. if len(args) == 1:
  534. import os.path
  535. name, ext = os.path.splitext(fin)
  536. fout = name + ".bin"
  537. else:
  538. fout = args[1]
  539. if not os.path.isfile(fin):
  540. print "File not found"
  541. sys.exit(1)
  542. sys.exit(hex2bin(fin, fout, start, end, size, pad))