任意长度HEX文件的解析(Python实现)
johncheapon 于 2019年03月06日 发表在 windows软件开发

1. 程序的功能

可解析大于64KB的HEX文件。限于时间条件,笔者只测试了两个样例,数据域大小分别为8332B和1.61MB

将解析得到的结果打印出来,包括:

    - 起始地址

    - 末尾地址

    - 数据域尺寸(单位:字节)

保存数据域的内容为bin文件以便查验和后续处理

HEX文件格式的介绍可以参考CSDN上的这篇博文:https://blog.csdn.net/a1037488611/article/details/43340055

2. 程序解读

HEX文件解析,相关函数:hex2bin()

考虑到HEX文件通常不会很大,所以一次性读取全部内容,每1行为1个list,然后逐行分析

通过识别每1行的TT字段来执行相应的操作,流程图如下:

(1)数据打包

相关函数:char2hex()

通过readlines函数读取到的是一行行字符串,如第一行是“:020000040800F2”,此外末尾还包含一个不可见的回车符号。

我们要将数字字符转换成对应的整型数据以便运算和存储,行首的冒号和行尾的回车符仅用于格式判断,对于数据提取是无用的,故去除。

HEX格式定义中的记录域是以字节为最小单位的,所以还需要将两个整型字符拼成1个字节。

python提供的高阶函数map()和切片操作,极大地方便了我们对HEX文本进行信息提取和格式转换。

char2hex()函数最终返回的是如下形式的整型列表:

[0x02,0x00,0x00,0x04,0x08,0x00,0xF2]

(2)校验和

相关函数:checksum()

得到了1行数据的整型列表以后,最好对该行数据校验一下。

按照规定,每行记录的倒数第1个字节是校验和(Checksum),校验算法为:

0x100-((除了冒号以外的所有数据以字节为单位相加)%256)

笔者的代码实现如下:

sum = (0x100 - (reduce(lambda x,y:x+y,line[:-1]) % 256)) % 256

之所以最后要再对256取模,是考虑到算出来的结果正好为0x100的情况,在C语言里,不做这一步当然OK,因为C语言中数据类型有自身的固定长度,

超出长度的高位部分会自动截断,对于8位无符号数,0x100==0x00是成立的,而Python中的int是动态变化的,数据在内存中的组织不像C那样读编程者那么直观,不存在数据类型的定义,0x100和0x00当然就是不相等的。

(3)保存bin文件

相关函数:wr_bin()

执行完hex2bin()后,HEX文件中数据域全都按顺序,从低字节到高字节被写入到了1个整型list中,

但按照python的要求,将每个元素转换为byte类型才能写入到二进制文件中。代码如下:

bin_buf_byte = list(map(six.int2byte,bin_buf))

(4)运行结果分析

笔者所测试的两个样例hex都是由STM32的工程编译而来。

查看打印输出的运行结果:addr_start代表起始地址,内容比较。

hex和bin文件在对应位置上分别截取1行,还可以用鼠标在输出的bin文件上右击,查看“属性”-“常规”-“大小”来验证内容是否遗漏。

从以上两个列表可以直观地看出,程序正确地解析了hex文件的内容。

读者也可以任选一个32位MCU编译出的hex来检验。

(5)使用方法

把hex2bin.py和HEX文件存放在同一个目录下,打开命令行终端,运行:

python hex2bin.py

注意:该程序只支持1个HEX文件。

3. 代码实现

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# File Name : hex2bin.py
# Version : V1.0.0001
# Date : 2019/03/06
# Author : qipeng_jiang
import os
import time
import six
from functools import reduce

# get '.hex' file name from current path
def getFileNamebyEX(path):
 f_list = os.listdir(path)
 for i in f_list:
  filename = os.path.splitext(i)[0]
  extname = os.path.splitext(i)[1]
  if extname == '.hex':
   return i
   
bin_buf = []# raw data of binary is to be stored here

def hex2bin(hex_file_name,bin_file_name):
 with open(hex_file_name,'r') as frd:
  print('Hex file \''+hex_file_name+'\''+' is opened')
  byte_num = 0
  for line in frd.readlines():
   #line.strip();#cut off CR
   if(line[0] == ':'):
    if(line[7:9] == '04'):#Extended Linear Address Record
     #print('Extended Linear Address Record');
     line = char2hex(line)
     if checksum(line) == 0:#checksum passed
      addr_h = (line[4]<<24) +(line[5]<<16)
     else:
      print('checksum failed!'+str(list(map(hex,line))))
    elif (line[7:9] == '00'):#Data Record
     line = char2hex(line)
     if checksum(line) == 0:
      addr_l = (line[1]<<8) + line[2]
      LL = line[0]
      byte_num = byte_num + LL
      for data in line[4:-1]:
       bin_buf.append(data)
      if LL!=0x10:
       addr_end = addr_h + addr_l + LL - 1
      else:
       pass
     else:
      print('checksum failed!'+str(list(map(hex,line))))
    elif(line[7:9] == '05'):
     #print('Extended Segment Address Record');
     line = char2hex(line)
     if checksum(line) == 0:
      pass
     else:
      print('checksum failed!'+str(list(map(hex,line))))
    elif(line[7:9] == '01'):#End of FileRecord
     #print('End of FileRecord');
     line = char2hex(line)
     if checksum(line) == 0:
      print('*********Hex file successed resolved**********')
      print('\taddr_start : %08X' %(addr_end + 1 - len(bin_buf)))
      print('\taddr_end : %08X' %addr_end)
      print('\tTotal : %d Bytes'%len(bin_buf))
     else:
      print('checksum failed!'+str(list(map(hex,line))))
    else:
     pass#don't care
   else:
    print('illegal format!')
    
# write data in bin_buf to '*.bin' file
def wr_bin(bin_buf):
 bin_buf_byte = list(map(six.int2byte,bin_buf))
 with open(bin_file_name,'wb') as fwrb:
  print('Bin file \''+bin_file_name+'\''+' is opened for write')
  for data in bin_buf_byte:
   fwrb.write(data)
  print('Bin file is successfully written!')
  
#one line string to hex-8 list,except ':' and CR
def char2hex(line):
 line=list(map(ord,list(line)))
 for num in range(len(line)):
  if line[num]>=0x30 and line[num]<=0x39:
   line[num] = line[num] - 0x30
  elif line[num]>=0x41 and line[num]<=0x5A:
   line[num] = line[num] - 55
  else:
   pass
 line=line[1:-1]#delete CR and ':', in terms of byte
 for i in range(0,len(line),2):
  line[i] = line[i]*16 + line[i+1]
  newline = line[::2]
 return newline
 
#checksum calculation of every line
def checksum(line):
 #considering if the checksum calculation result is 0x100
 sum = (0x100 - (reduce(lambda x,y:x+y,line[:-1]) % 256)) % 256
 if sum == line[-1]:#check if sum calculated is equal to checksum byte in hex file
  return 0
 else:
  return 1

starttime = time.clock()
hex_file_name = getFileNamebyEX('.')
bin_file_name = hex_file_name[:-4]+'.bin'
hex2bin(hex_file_name,bin_file_name)
wr_bin(bin_buf)
endtime = time.clock()

print('Time elapsed:' + str(endtime-starttime))

注意:本站所有文章除特别说明外,均为原创,转载请务必以超链接方式并注明作者出处。

标签:python,Hex