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.
Projecteerimoq/bincopy
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
| 14 | 14 | from io import StringIO | |
| 15 | 15 | ||
| 16 | 16 | __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
fail→pass·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.