From d3f49abd7357b2b1ca45748b76dd3790393db932 Mon Sep 17 00:00:00 2001 From: fpagliughi Date: Fri, 8 Dec 2023 16:28:02 -0500 Subject: [PATCH 1/2] Added option/parameters to strip padding from the end of binary data. --- intelhex/__init__.py | 35 +++++++++++++++++++++-------------- intelhex/scripts/hex2bin.py | 8 ++++++-- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/intelhex/__init__.py b/intelhex/__init__.py index 34c28c6..d07ba77 100644 --- a/intelhex/__init__.py +++ b/intelhex/__init__.py @@ -315,7 +315,7 @@ def _get_start_end(self, start=None, end=None, size=None): start, end = end, start return start, end - def tobinarray(self, start=None, end=None, pad=_DEPRECATED, size=None): + def tobinarray(self, start=None, end=None, pad=_DEPRECATED, size=None, strip=False): ''' 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. @@ -324,9 +324,10 @@ def tobinarray(self, start=None, end=None, pad=_DEPRECATED, size=None): 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. + @param strip whether to strip the padding from the end of the binary data @return array of unsigned char data. ''' - if not isinstance(pad, _DeprecatedParam): + if not (isinstance(pad, _DeprecatedParam) or pad is None): print ("IntelHex.tobinarray: 'pad' parameter is deprecated.") if pad is not None: print ("Please, use IntelHex.padding attribute instead.") @@ -335,9 +336,9 @@ def tobinarray(self, start=None, end=None, pad=_DEPRECATED, size=None): print ("Use syntax like this: ih.tobinarray(start=xxx, end=yyy, size=zzz)") else: pad = None - return self._tobinarray_really(start, end, pad, size) + return self._tobinarray_really(start, end, pad, size, strip) - def _tobinarray_really(self, start, end, pad, size): + def _tobinarray_really(self, start, end, pad, size, strip): """Return binary array.""" if pad is None: pad = self.padding @@ -349,9 +350,12 @@ def _tobinarray_really(self, start, end, pad, size): start, end = self._get_start_end(start, end, size) for i in range_g(start, end+1): bin.append(self._buf.get(i, pad)) + if strip: + while len(bin) != 0 and bin[-1] == pad: + bin.pop() return bin - def tobinstr(self, start=None, end=None, pad=_DEPRECATED, size=None): + def tobinstr(self, start=None, end=None, pad=_DEPRECATED, size=None, strip=False): ''' Convert to binary form and return as binary string. @param start start address of output bytes. @param end end address of output bytes (inclusive). @@ -359,9 +363,10 @@ def tobinstr(self, start=None, end=None, pad=_DEPRECATED, size=None): 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. + @param strip whether to strip the padding from the end of the binary data @return bytes string of binary data. ''' - if not isinstance(pad, _DeprecatedParam): + if not (isinstance(pad, _DeprecatedParam) or pad is None): print ("IntelHex.tobinstr: 'pad' parameter is deprecated.") if pad is not None: print ("Please, use IntelHex.padding attribute instead.") @@ -370,12 +375,12 @@ def tobinstr(self, start=None, end=None, pad=_DEPRECATED, size=None): print ("Use syntax like this: ih.tobinstr(start=xxx, end=yyy, size=zzz)") else: pad = None - return self._tobinstr_really(start, end, pad, size) + return self._tobinstr_really(start, end, pad, size, strip) - def _tobinstr_really(self, start, end, pad, size): - return array_tobytes(self._tobinarray_really(start, end, pad, size)) + def _tobinstr_really(self, start, end, pad, size, strip): + return array_tobytes(self._tobinarray_really(start, end, pad, size, strip)) - def tobinfile(self, fobj, start=None, end=None, pad=_DEPRECATED, size=None): + def tobinfile(self, fobj, start=None, end=None, pad=_DEPRECATED, size=None, strip=False): '''Convert to binary and write to file. @param fobj file name or file object for writing output bytes. @@ -385,8 +390,9 @@ def tobinfile(self, fobj, start=None, end=None, pad=_DEPRECATED, size=None): 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. + @param strip whether to strip the padding from the end of the binary data ''' - if not isinstance(pad, _DeprecatedParam): + if not (isinstance(pad, _DeprecatedParam) or pad is None): print ("IntelHex.tobinfile: 'pad' parameter is deprecated.") if pad is not None: print ("Please, use IntelHex.padding attribute instead.") @@ -401,7 +407,7 @@ def tobinfile(self, fobj, start=None, end=None, pad=_DEPRECATED, size=None): else: close_fd = False - fobj.write(self._tobinstr_really(start, end, pad, size)) + fobj.write(self._tobinstr_really(start, end, pad, size, strip)) if close_fd: fobj.close() @@ -1035,7 +1041,7 @@ def tobinarray(self, start=None, end=None, size=None): #/class IntelHex16bit -def hex2bin(fin, fout, start=None, end=None, size=None, pad=None): +def hex2bin(fin, fout, start=None, end=None, size=None, pad=None, strip=False): """Hex-to-Bin convertor engine. @return 0 if all OK @@ -1045,6 +1051,7 @@ def hex2bin(fin, fout, start=None, end=None, size=None, pad=None): @param end end of address range (inclusive; optional) @param size size of resulting file (in bytes) (optional) @param pad padding byte (optional) + @param strip whether to strip padding from the end of the binary data (optional) """ try: h = IntelHex(fin) @@ -1070,7 +1077,7 @@ def hex2bin(fin, fout, start=None, end=None, size=None, pad=None): if pad is not None: # using .padding attribute rather than pad argument to function call h.padding = pad - h.tobinfile(fout, start, end) + h.tobinfile(fout, start, end, strip=strip) except IOError: e = sys.exc_info()[1] # current exception txt = "ERROR: Could not write to file: %s: %s" % (fout, str(e)) diff --git a/intelhex/scripts/hex2bin.py b/intelhex/scripts/hex2bin.py index 9533837..75f3872 100755 --- a/intelhex/scripts/hex2bin.py +++ b/intelhex/scripts/hex2bin.py @@ -60,17 +60,19 @@ def main(): Range can be in form 'START:' or ':END'. -l, --length=NNNN, -s, --size=NNNN size of output (decimal value). + --strip strip padding from the end of the binary data. ''' pad = None start = None end = None size = None + strip = False try: opts, args = getopt.getopt(sys.argv[1:], "hvp:r:l:s:", ["help", "version", "pad=", "range=", - "length=", "size="]) + "length=", "size=", "strip"]) for o, a in opts: if o in ("-h", "--help"): @@ -98,6 +100,8 @@ def main(): size = int(a, 10) except: raise getopt.GetoptError('Bad size value') + elif o in ("--strip"): + strip = True if start != None and end != None and size != None: raise getopt.GetoptError('Cannot specify START:END and SIZE simultaneously') @@ -129,7 +133,7 @@ def main(): fout = compat.get_binary_stdout() from intelhex import hex2bin - sys.exit(hex2bin(fin, fout, start, end, size, pad)) + sys.exit(hex2bin(fin, fout, start, end, size, pad, strip)) if __name__ == '__main__': main() From 431b2a6f637dd16ef821c359ae961cbda8a0f10a Mon Sep 17 00:00:00 2001 From: fpagliughi Date: Mon, 11 Dec 2023 19:34:34 -0500 Subject: [PATCH 2/2] - Changed nomenclature from 'strip' to 'filter'. - Limiting end to last address in range when filtering. - Added addresses_in(), maxaddr_in(), and minaddr_in() methods. --- intelhex/__init__.py | 94 +++++++++++++++++++++++++------------ intelhex/scripts/hex2bin.py | 35 +++++++++----- 2 files changed, 86 insertions(+), 43 deletions(-) diff --git a/intelhex/__init__.py b/intelhex/__init__.py index d07ba77..20705dc 100644 --- a/intelhex/__init__.py +++ b/intelhex/__init__.py @@ -144,7 +144,7 @@ def _decode_record(self, s, line=0): 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 + addr += 1 # FIXME: addr should be wrapped # BUT after 02 record (at 64K boundary) # and after 04 record (at 4G boundary) @@ -286,10 +286,10 @@ def frombytes(self, bytes, offset=0): self._buf[offset] = b offset += 1 - def _get_start_end(self, start=None, end=None, size=None): + def _get_start_end(self, start=None, end=None, size=None, filter=False): """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. + invoke this method with both start and end as None. """ if (start,end) == (None,None) and self._buf == {}: raise EmptyIntelHexError @@ -311,12 +311,14 @@ def _get_start_end(self, start=None, end=None, size=None): start = self.minaddr() if end is None: end = self.maxaddr() + elif filter: + end = self.maxaddr_in(start, end) if start > end: start, end = end, start return start, end - def tobinarray(self, start=None, end=None, pad=_DEPRECATED, size=None, strip=False): - ''' Convert this object to binary form as array. If start and end + def tobinarray(self, start=None, end=None, pad=_DEPRECATED, size=None, filter=False): + ''' 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). @@ -324,7 +326,7 @@ def tobinarray(self, start=None, end=None, pad=_DEPRECATED, size=None, strip=Fal 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. - @param strip whether to strip the padding from the end of the binary data + @param filter whether to filter the input range to avoid padding @return array of unsigned char data. ''' if not (isinstance(pad, _DeprecatedParam) or pad is None): @@ -336,9 +338,9 @@ def tobinarray(self, start=None, end=None, pad=_DEPRECATED, size=None, strip=Fal print ("Use syntax like this: ih.tobinarray(start=xxx, end=yyy, size=zzz)") else: pad = None - return self._tobinarray_really(start, end, pad, size, strip) + return self._tobinarray_really(start, end, pad, size, filter) - def _tobinarray_really(self, start, end, pad, size, strip): + def _tobinarray_really(self, start, end, pad, size, filter): """Return binary array.""" if pad is None: pad = self.padding @@ -347,15 +349,12 @@ def _tobinarray_really(self, start, end, pad, size, strip): 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) + start, end = self._get_start_end(start, end, size, filter) for i in range_g(start, end+1): bin.append(self._buf.get(i, pad)) - if strip: - while len(bin) != 0 and bin[-1] == pad: - bin.pop() return bin - def tobinstr(self, start=None, end=None, pad=_DEPRECATED, size=None, strip=False): + def tobinstr(self, start=None, end=None, pad=_DEPRECATED, size=None, filter=False): ''' Convert to binary form and return as binary string. @param start start address of output bytes. @param end end address of output bytes (inclusive). @@ -363,7 +362,7 @@ def tobinstr(self, start=None, end=None, pad=_DEPRECATED, size=None, strip=False 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. - @param strip whether to strip the padding from the end of the binary data + @param filter whether to filter the input range to avoid padding @return bytes string of binary data. ''' if not (isinstance(pad, _DeprecatedParam) or pad is None): @@ -375,12 +374,12 @@ def tobinstr(self, start=None, end=None, pad=_DEPRECATED, size=None, strip=False print ("Use syntax like this: ih.tobinstr(start=xxx, end=yyy, size=zzz)") else: pad = None - return self._tobinstr_really(start, end, pad, size, strip) + return self._tobinstr_really(start, end, pad, size, filter) - def _tobinstr_really(self, start, end, pad, size, strip): - return array_tobytes(self._tobinarray_really(start, end, pad, size, strip)) + def _tobinstr_really(self, start, end, pad, size, filter): + return array_tobytes(self._tobinarray_really(start, end, pad, size, filter)) - def tobinfile(self, fobj, start=None, end=None, pad=_DEPRECATED, size=None, strip=False): + def tobinfile(self, fobj, start=None, end=None, pad=_DEPRECATED, size=None, filter=False): '''Convert to binary and write to file. @param fobj file name or file object for writing output bytes. @@ -390,7 +389,7 @@ def tobinfile(self, fobj, start=None, end=None, pad=_DEPRECATED, size=None, stri 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. - @param strip whether to strip the padding from the end of the binary data + @param filter whether to filter the input range to avoid padding ''' if not (isinstance(pad, _DeprecatedParam) or pad is None): print ("IntelHex.tobinfile: 'pad' parameter is deprecated.") @@ -407,7 +406,7 @@ def tobinfile(self, fobj, start=None, end=None, pad=_DEPRECATED, size=None, stri else: close_fd = False - fobj.write(self._tobinstr_really(start, end, pad, size, strip)) + fobj.write(self._tobinstr_really(start, end, pad, size, filter)) if close_fd: fobj.close() @@ -425,12 +424,21 @@ def todict(self): def addresses(self): '''Returns all used addresses in sorted order. - @return list of occupied data addresses in sorted order. + @return list of occupied data addresses in sorted order. ''' aa = dict_keys(self._buf) aa.sort() return aa + def addresses_in(self, start, end): + '''Returns all used addresses in a given range in sorted order. + @param start the start address of the range + @param end the end address of the range + @return list of occupied data addresses in sorted order. + ''' + # TODO: end is inclusive, correct? + return [a for a in self.addresses() if a >= start and a <= end] + def minaddr(self): '''Get minimal address of HEX content. @return minimal address or None if no data @@ -441,6 +449,18 @@ def minaddr(self): else: return min(aa) + def minaddr_in(self, start, end): + '''Get minimal address of HEX content within the specified range. + @param start the start address of the range + @param end the end address of the range + @return minimal address or None if no data + ''' + aa = self.addresses_in(start, end) + 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 @@ -451,6 +471,18 @@ def maxaddr(self): else: return max(aa) + def maxaddr_in(self, start, end): + '''Get maximal address of HEX content within the specified range. + @param start the start address of the range + @param end the end address of the range + @return maximal address or None if no data + ''' + aa = self.addresses_in(start, end) + if aa == []: + return None + else: + return max(aa) + def __getitem__(self, addr): ''' Get requested byte from address. @param addr address of byte. @@ -755,7 +787,7 @@ def puts(self, addr, s): self._buf[addr+i] = a[i] def getsz(self, addr): - """Get zero-terminated bytes string from given address. Will raise + """Get zero-terminated bytes string from given address. Will raise NotEnoughDataError exception if a hole is encountered before a 0. """ i = 0 @@ -777,7 +809,7 @@ def putsz(self, addr, s): def find(self, sub, start=None, end=None): """Return the lowest index in self[start:end] where subsection sub is found. Optional arguments start and end are interpreted as in slice notation. - + @param sub bytes-like subsection to find @param start start of section to search within (optional) @param end end of section to search within (optional) @@ -807,7 +839,7 @@ def dump(self, tofile=None, width=16, withpadding=False): width = int(width) if tofile is None: tofile = sys.stdout - + # start addr possibly if self.start_addr is not None: cs = self.start_addr.get('CS') @@ -862,7 +894,7 @@ def merge(self, other, overlap='error'): in overlapping region. @raise TypeError if other is not instance of IntelHex - @raise ValueError if other is the same object as self + @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 @@ -917,7 +949,7 @@ def segments(self, min_gap=1): beginnings = [addresses[b+1] for b in breaks] beginnings.insert(0, addresses[0]) return [(a, b+1) for (a, b) in zip(beginnings, endings)] - + def get_memory_size(self): """Returns the approximate memory footprint for data.""" n = sys.getsizeof(self) @@ -1005,7 +1037,7 @@ def minaddr(self): def maxaddr(self): '''Get maximal address of HEX content in 16-bit mode. - @return maximal address used in this object + @return maximal address used in this object ''' aa = dict_keys(self._buf) if aa == []: @@ -1041,7 +1073,7 @@ def tobinarray(self, start=None, end=None, size=None): #/class IntelHex16bit -def hex2bin(fin, fout, start=None, end=None, size=None, pad=None, strip=False): +def hex2bin(fin, fout, start=None, end=None, size=None, pad=None, filter=False): """Hex-to-Bin convertor engine. @return 0 if all OK @@ -1051,7 +1083,7 @@ def hex2bin(fin, fout, start=None, end=None, size=None, pad=None, strip=False): @param end end of address range (inclusive; optional) @param size size of resulting file (in bytes) (optional) @param pad padding byte (optional) - @param strip whether to strip padding from the end of the binary data (optional) + @param filter whether to filter the input range to avoid padding (optional) """ try: h = IntelHex(fin) @@ -1077,7 +1109,7 @@ def hex2bin(fin, fout, start=None, end=None, size=None, pad=None, strip=False): if pad is not None: # using .padding attribute rather than pad argument to function call h.padding = pad - h.tobinfile(fout, start, end, strip=strip) + h.tobinfile(fout, start, end, filter=filter) except IOError: e = sys.exc_info()[1] # current exception txt = "ERROR: Could not write to file: %s: %s" % (fout, str(e)) @@ -1180,7 +1212,7 @@ def data(offset, bytes): def eof(): """Return End of File record as a string. - @return String representation of Intel Hex EOF record + @return String representation of Intel Hex EOF record """ return ':00000001FF' eof = staticmethod(eof) diff --git a/intelhex/scripts/hex2bin.py b/intelhex/scripts/hex2bin.py index 75f3872..b520fbf 100755 --- a/intelhex/scripts/hex2bin.py +++ b/intelhex/scripts/hex2bin.py @@ -37,6 +37,15 @@ VERSION = '2.3.0' +def split_range(a): + l = a.split(":") + if l[0] != '': + start = int(l[0], 16) + if l[1] != '': + end = int(l[1], 16) + return start, end + + def main(): import getopt import os @@ -58,21 +67,23 @@ def main(): -r, --range=START:END specify address range for writing output (ascii hex value). Range can be in form 'START:' or ':END'. + -f, --filter=START:END specify address range for filtering input + (ascii hex value). + Filter range can be in form 'START:' or ':END'. -l, --length=NNNN, -s, --size=NNNN size of output (decimal value). - --strip strip padding from the end of the binary data. ''' pad = None start = None end = None size = None - strip = False + filter = False try: - opts, args = getopt.getopt(sys.argv[1:], "hvp:r:l:s:", + opts, args = getopt.getopt(sys.argv[1:], "hvp:r:f:l:s:", ["help", "version", "pad=", "range=", - "length=", "size=", "strip"]) + "filter", "length=", "size="]) for o, a in opts: if o in ("-h", "--help"): @@ -88,20 +99,20 @@ def main(): 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) + start, end = split_range(a) except: raise getopt.GetoptError('Bad range value(s)') + elif o in ("-f", "--filter"): + filter = True + try: + start, end = split_range(a) + except: + raise getopt.GetoptError('Bad filter range value(s)') elif o in ("-l", "--lenght", "-s", "--size"): try: size = int(a, 10) except: raise getopt.GetoptError('Bad size value') - elif o in ("--strip"): - strip = True if start != None and end != None and size != None: raise getopt.GetoptError('Cannot specify START:END and SIZE simultaneously') @@ -133,7 +144,7 @@ def main(): fout = compat.get_binary_stdout() from intelhex import hex2bin - sys.exit(hex2bin(fin, fout, start, end, size, pad, strip)) + sys.exit(hex2bin(fin, fout, start, end, size, pad, filter)) if __name__ == '__main__': main()