Whole file

eerimoq/bincopy

The author described this change as Raise an exception on bad address range to the exclude function.. It counts as a record because the check below fails on the code as it stood at d8c91500f and passes on c8ec5ee19, with nothing else changed between the two runs.

Fix saved2016-09-01
Sharing licenceMIT · LICENSE
Change size+746 749

What the code was meant to do, written into the code itself as a save note

Raise an exception on bad address range to the exclude function.

The change

1414 from io import StringIO
1515
1616 __author__ = 'Erik Moqvist'
17-__version__ = '7.1.3'
18-
19-DEFAULT_WORD_SIZE_BITS = 8
20-
21-
22-class Error(Exception):
23- """Bincopy base exception.
24-
25- """
26-
27- pass
28-
29-
30-def crc_srec(hexstr):
31- """Calculate the CRC for given Motorola S-Record hexstring.
32-
33- """
34-
35- crc = sum(bytearray(binascii.unhexlify(hexstr)))
36- crc &= 0xff
37- crc ^= 0xff
38-
39- return crc
40-
41-
42-def crc_ihex(hexstr):
43- """Calculate crc for given Intel HEX hexstring.
44-
45- """
46-
47- crc = sum(bytearray(binascii.unhexlify(hexstr)))
48- crc &= 0xff
49- crc = ((~crc + 1) & 0xff)
50-
51- return crc
52-
53-
54-def pack_srec(type_, address, size, data):
55- """Create a Motorola S-Record record of given data.
56-
57- """
58-
59- if type_ in '0159':
60- line = '%02X%04X' % (size + 2 + 1, address)
61- elif type_ in '268':
62- line = '%02X%06X' % (size + 3 + 1, address)
63- elif type_ in '37':
64- line = '%02X%08X' % (size + 4 + 1, address)
65- else:
66- raise Error("bad type '{}'".format(type_))
67-
68- if data:
69- line += binascii.hexlify(data).decode('utf-8').upper()
70-
71- return 'S%s%s%02X' % (type_, line, crc_srec(line))
72-
73-
74-def unpack_srec(record):
75- """Unpack given Motorola S-Record record into variables.
76-
77- """
78-
79- if len(record) < 6:
80- raise Error("bad record '{}'".format(record))
81-
82- if record[0] != 'S':
83- raise Error("bad record '{}'".format(record))
84-
85- size = int(record[2:4], 16)
86- type_ = record[1:2]
87-
88- if type_ in '0159':
89- width = 4
90- elif type_ in '268':
91- width = 6
92- elif type_ in '37':
93- width = 8
94- else:
95- raise Error("bad record type '{}'".format(type_))
96-
97- address = int(record[4:4+width], 16)
98- data = binascii.unhexlify(record[4 + width:4 + 2 * size - 2])
99- real_crc = int(record[4 + 2 * size - 2:], 16)
100- calc_crc = crc_srec(record[2:4 + 2 * size - 2])
101-
102- if real_crc != calc_crc:
103- raise Error("bad crc in record '{}'".format(record))
104-
105- return (type_, address, size - 1 - width // 2, data)
106-
107-
108-def pack_ihex(type_, address, size, data):
109- """Create a Intel HEX record of given data.
110-
111- """
112-
113- line = '%02X%04X%02X' % (size, address, type_)
114-
115- if data:
116- line += binascii.hexlify(data).decode('utf-8').upper()
117-
118- return ':%s%02X' % (line, crc_ihex(line))
119-
120-
121-def unpack_ihex(record):
122- """Unpack given Intel HEX record into variables.
123-
124- """
125-
126- if len(record) < 11:
127- raise Error("bad record '{}'".format(record))
128-
129- if record[0] != ':':
130- raise Error("bad record '{}'".format(record))
131-
132- size = int(record[1:3], 16)
133- address = int(record[3:7], 16)
134- type_ = int(record[7:9], 16)
135-
136- if size > 0:
137- data = binascii.unhexlify(record[9:9 + 2 * size])
138- else:
139- data = ''
140-
141- real_crc = int(record[9 + 2 * size:], 16)
142- calc_crc = crc_ihex(record[1:9 + 2 * size])
143-
144- if real_crc != calc_crc:
145- raise Error("bad crc in record '{}'".format(record))
146-
147- return (type_, address, size, data)
148-
149-
150-class _Segment(object):
151- """A segment is a chunk data with given minimum and maximum address.
152-
153- """
154-
155- def __init__(self, minimum_address, maximum_address, data):
156- self.minimum_address = minimum_address
157- self.maximum_address = maximum_address
158- self.data = data
159-
160- def add_data(self, minimum_address, maximum_address, data, overwrite):
161- """Add given data to this segment. The added data must be adjecent to
162- the current segment data, otherwise an exception is thrown.
163-
164- """
165-
166- if minimum_address == self.maximum_address:
167- self.maximum_address = maximum_address
168- self.data += data
169- elif maximum_address == self.minimum_address:
170- self.minimum_address = minimum_address
171- self.data = data + self.data
172- elif (overwrite
173- and minimum_address < self.maximum_address
174- and maximum_address > self.minimum_address):
175- self_data_offset = minimum_address - self.minimum_address
176-
177- # prepend data
178- if self_data_offset < 0:
179- self_data_offset *= -1
180- self.data = data[:self_data_offset] + self.data
181- del data[:self_data_offset]
182- self.minimum_address = minimum_address
183-
184- # overwrite overlapping part
185- self_data_left = len(self.data) - self_data_offset
186-
187- if len(data) <= self_data_left:
188- self.data[self_data_offset:self_data_offset + len(data)] = data
189- data = bytearray()
190- else:
191- self.data[self_data_offset:] = data[:self_data_left]
192- data = data[self_data_left:]
193-
194- # append data
195- if len(data) > 0:
196- self.data += data
197- self.maximum_address = maximum_address
198- else:
199- raise Error('data added to a segment must be adjacent to or '
200- 'overlapping with the original segment data')
201-
202- def remove_data(self, minimum_address, maximum_address):
203- """Remove given data range from this segment. Returns the second
204- segment if the removed data splits this segment in two.
205-
206- """
207-
208- if ((minimum_address >= self.maximum_address)
209- and (maximum_address <= self.minimum_address)):
210- raise Error('cannot remove data that is not part of the segment')
211-
212- if minimum_address < self.minimum_address:
213- minimum_address = self.minimum_address
214-
215- if maximum_address > self.maximum_address:
216- maximum_address = self.maximum_address
217-
218- remove_size = maximum_address - minimum_address
219- part1_size = minimum_address - self.minimum_address
220- part1_data = self.data[0:part1_size]
221- part2_data = self.data[part1_size + remove_size:]
222-
223- if len(part1_data) and len(part2_data):
224- # Update this segment and return the second segment.
225- self.maximum_address = self.minimum_address + part1_size
226- self.data = part1_data
227-
228- return _Segment(maximum_address,
229- maximum_address + len(part2_data),
230- part2_data)
231- else:
232- # Update this segment.
233- if len(part1_data) > 0:
234- self.maximum_address = minimum_address
235- self.data = part1_data
236- elif len(part2_data) > 0:
237- self.minimum_address = maximum_address
238- self.data = part2_data
239- else:
240- self.maximum_address = self.minimum_address
241- self.data = bytearray()
242-
243- def __str__(self):
244- return '[%#x .. %#x]: %s' % (self.minimum_address,
245- self.maximum_address,
246- binascii.hexlify(self.data))
247-
248-
249-class _Segments(object):
250- """A list of segments.
251-
252- """
253-
254- def __init__(self):
255- self.current_segment = None
256- self.current_segment_index = None
257- self.list = []
258-
259- def add(self, segment, overwrite=False):
260- """Add segments by ascending address.
261-
262- """
263-
264- if self.list:
265- if segment.minimum_address == self.current_segment.maximum_address:
266- # fast insertion for adjecent segments
267- self.current_segment.add_data(segment.minimum_address,
268- segment.maximum_address,
269- segment.data,
270- overwrite)
271- else:
272- # linear insert
273- for i, s in enumerate(self.list):
274- if segment.minimum_address <= s.maximum_address:
275- break
276-
277- if segment.minimum_address > s.maximum_address:
278- # non-overlapping, non-adjacent after
279- self.list.append(segment)
280- elif segment.maximum_address < s.minimum_address:
281- # non-overlapping, non-adjacent before
282- self.list.insert(i, segment)
283- else:
284- # adjacent or overlapping
285- s.add_data(segment.minimum_address,
286- segment.maximum_address,
287- segment.data,
288- overwrite)
289- segment = s
290-
291- self.current_segment = segment
292- self.current_segment_index = i
293-
294- # remove overwritten and merge adjacent segments
295- while self.current_segment is not self.list[-1]:
296- s = self.list[self.current_segment_index + 1]
297-
298- if self.current_segment.maximum_address >= s.maximum_address:
299- # the whole segment is overwritten
300- del self.list[self.current_segment_index + 1]
301- elif self.current_segment.maximum_address > s.minimum_address:
302- # beginning of the segment overwritten
303- self.current_segment.add_data(
304- self.current_segment.maximum_address,
305- s.maximum_address,
306- s.data[self.current_segment.maximum_address - s.minimum_address:],
307- overwrite=False)
308- del self.list[self.current_segment_index+1]
309- break
310- else:
311- # segments are not overlapping
312- break
313- else:
314- self.list.append(segment)
315- self.current_segment = segment
316- self.current_segment_index = 0
317-
318- def remove(self, minimum_address, maximum_address):
319- new_list = []
320-
321- for segment in self.list:
322- if (segment.maximum_address <= minimum_address
323- or maximum_address < segment.minimum_address):
324- # no overlap
325- new_list.append(segment)
326- else:
327- # overlapping, remove overwritten parts segments
328- split = segment.remove_data(minimum_address, maximum_address)
329-
330- if segment.minimum_address < segment.maximum_address:
331- new_list.append(segment)
332-
333- if split:
334- new_list.append(split)
335-
336- self.list = new_list
337-
338- def iter(self, size=32):
339- """Iterate over all segments and return chunks of the data.
340-
341- """
342-
343- for segment in self.list:
344- data = segment.data
345- address = segment.minimum_address
346-
347- for offset in range(0, len(data), size):
348- yield address + offset, data[offset:offset + size]
349-
350- def get_minimum_address(self):
351- """Get the minimum address of the data.
352-
353- """
354-
355- if not self.list:
356- raise Error('cannot get minimum address from an empty file')
357-
358- return self.list[0].minimum_address
359-
360- def get_maximum_address(self):
361- """Get the maximum address of the data.
362-
363- """
364-
365- if not self.list:
366- raise Error('cannot get maximum address from an empty file')
367-
368- return self.list[-1].maximum_address
369-
370- def get_size(self):
371- """Get the size of the binary, including holes in the data.
372-
373- """
374-
375- if not self.list:
376- return 0
377-
378- return self.get_maximum_address() - self.get_minimum_address()
379-
380- def __str__(self):
381- return '\n'.join([s.__str__() for s in self.list])
382-
383-
384-class BinFile(object):
385-
386- def __init__(self, word_size_bits=DEFAULT_WORD_SIZE_BITS):
387- if (word_size_bits % 8) != 0:
388- raise Error('word size must be a multiple of 8 bits')
389- self.word_size_bits = word_size_bits
390- self.word_size_bytes = (word_size_bits // 8)
391- self.header = None
392- self.execution_start_address = None
393- self.segments = _Segments()
394-
395- def add_srec(self, records, overwrite=False):
396- """Add given Motorola S-Records. Set `overwrite` to True to allow
397- already added data to be overwritten.
398-
399- """
400-
401- for record in StringIO(records):
402- type_, address, size, data = unpack_srec(record.strip())
403-
404- if type_ == '0':
405- self.header = data
406- elif type_ in '123':
407- address *= self.word_size_bytes
408- self.segments.add(_Segment(address, address + size,
409- bytearray(data)),
410- overwrite)
411- elif type_ in '789':
412- self.execution_start_address = address
413-
1101 further changed lines not shown

The check that tells the two apart

failpass·tests/test_bincopy.py::BinCopyTest::test_exclude_crop

Check file tests/test_bincopy.py, taken without changes from the fix and copied onto the older code, so the exact same check runs against both versions.

Origin and history

The code before itd8c91500f06b5fe47139b810882357051cca1af5
Broken version dated2016-08-31
Modulebincopy
Units changedBinFile
Fingerprint741f59b20ab9aa70
Checked2026-08-18 by goldset/0.1

Every field above is generated by our program. None of it is written by hand.

Other bugs found in eerimoq/bincopy