1# 2# Copyright 2021 Espressif Systems (Shanghai) CO., LTD 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15# 16 17import base64 18import binascii 19import hashlib 20import logging 21import os 22import subprocess 23import sys 24import tempfile 25 26from construct import AlignedStruct, Bytes, GreedyRange, Int32ul, Padding, Struct, abs_, this 27 28from . import ESPCoreDumpLoaderError 29from .elf import (TASK_STATUS_CORRECT, TASK_STATUS_TCB_CORRUPTED, ElfFile, ElfSegment, ESPCoreDumpElfFile, 30 EspTaskStatus, NoteSection) 31from .riscv import Esp32c3Methods 32from .xtensa import Esp32Methods, Esp32S2Methods, Esp32S3Methods 33 34try: 35 from typing import Optional, Tuple 36except ImportError: 37 pass 38 39IDF_PATH = os.getenv('IDF_PATH', '') 40PARTTOOL_PY = os.path.join(IDF_PATH, 'components', 'partition_table', 'parttool.py') 41ESPTOOL_PY = os.path.join(IDF_PATH, 'components', 'esptool_py', 'esptool', 'esptool.py') 42 43# Following structs are based on source code 44# components/espcoredump/include_core_dump/esp_core_dump_priv.h 45 46EspCoreDumpV1Header = Struct( 47 'tot_len' / Int32ul, 48 'ver' / Int32ul, 49 'task_num' / Int32ul, 50 'tcbsz' / Int32ul, 51) 52 53EspCoreDumpV2Header = Struct( 54 'tot_len' / Int32ul, 55 'ver' / Int32ul, 56 'task_num' / Int32ul, 57 'tcbsz' / Int32ul, 58 'segs_num' / Int32ul, 59) 60 61CRC = Int32ul 62SHA256 = Bytes(32) 63 64TaskHeader = Struct( 65 'tcb_addr' / Int32ul, 66 'stack_top' / Int32ul, 67 'stack_end' / Int32ul, 68) 69 70MemSegmentHeader = Struct( 71 'mem_start' / Int32ul, 72 'mem_sz' / Int32ul, 73 'data' / Bytes(this.mem_sz), 74) 75 76 77class EspCoreDumpVersion(object): 78 """Core dump version class, it contains all version-dependent params 79 """ 80 # Chip IDs should be in sync with components/esp_hw_support/include/esp_chip_info.h 81 ESP32 = 0 82 ESP32S2 = 2 83 ESP32S3 = 9 84 XTENSA_CHIPS = [ESP32, ESP32S2, ESP32S3] 85 86 ESP32C3 = 5 87 RISCV_CHIPS = [ESP32C3] 88 89 COREDUMP_SUPPORTED_TARGETS = XTENSA_CHIPS + RISCV_CHIPS 90 91 def __init__(self, version=None): # type: (int) -> None 92 """Constructor for core dump version 93 """ 94 super(EspCoreDumpVersion, self).__init__() 95 if version is None: 96 self.version = 0 97 else: 98 self.set_version(version) 99 100 @staticmethod 101 def make_dump_ver(major, minor): # type: (int, int) -> int 102 return ((major & 0xFF) << 8) | ((minor & 0xFF) << 0) 103 104 def set_version(self, version): # type: (int) -> None 105 self.version = version 106 107 @property 108 def chip_ver(self): # type: () -> int 109 return (self.version & 0xFFFF0000) >> 16 110 111 @property 112 def dump_ver(self): # type: () -> int 113 return self.version & 0x0000FFFF 114 115 @property 116 def major(self): # type: () -> int 117 return (self.version & 0x0000FF00) >> 8 118 119 @property 120 def minor(self): # type: () -> int 121 return self.version & 0x000000FF 122 123 124class EspCoreDumpLoader(EspCoreDumpVersion): 125 # "legacy" stands for core dumps v0.1 (before IDF v4.1) 126 BIN_V1 = EspCoreDumpVersion.make_dump_ver(0, 1) 127 BIN_V2 = EspCoreDumpVersion.make_dump_ver(0, 2) 128 ELF_CRC32 = EspCoreDumpVersion.make_dump_ver(1, 0) 129 ELF_SHA256 = EspCoreDumpVersion.make_dump_ver(1, 1) 130 131 def __init__(self): # type: () -> None 132 super(EspCoreDumpLoader, self).__init__() 133 self.core_src_file = None # type: Optional[str] 134 self.core_src_struct = None 135 self.core_src = None 136 137 self.core_elf_file = None # type: Optional[str] 138 139 self.header = None 140 self.header_struct = EspCoreDumpV1Header 141 self.checksum_struct = CRC 142 143 # target classes will be assigned in ``_reload_coredump`` 144 self.target_methods = Esp32Methods() 145 146 self.temp_files = [] # type: list[str] 147 148 def _create_temp_file(self): # type: () -> str 149 t = tempfile.NamedTemporaryFile('wb', delete=False) 150 # Here we close this at first to make sure the read/write is wrapped in context manager 151 # Otherwise the result will be wrong if you read while open in another session 152 t.close() 153 self.temp_files.append(t.name) 154 return t.name 155 156 def _load_core_src(self): # type: () -> str 157 """ 158 Write core elf into ``self.core_src``, 159 Return the target str by reading core elf 160 """ 161 with open(self.core_src_file, 'rb') as fr: # type: ignore 162 coredump_bytes = fr.read() 163 164 _header = EspCoreDumpV1Header.parse(coredump_bytes) # first we use V1 format to get version 165 self.set_version(_header.ver) 166 if self.dump_ver == self.ELF_CRC32: 167 self.checksum_struct = CRC 168 self.header_struct = EspCoreDumpV2Header 169 elif self.dump_ver == self.ELF_SHA256: 170 self.checksum_struct = SHA256 171 self.header_struct = EspCoreDumpV2Header 172 elif self.dump_ver == self.BIN_V1: 173 self.checksum_struct = CRC 174 self.header_struct = EspCoreDumpV1Header 175 elif self.dump_ver == self.BIN_V2: 176 self.checksum_struct = CRC 177 self.header_struct = EspCoreDumpV2Header 178 else: 179 raise ESPCoreDumpLoaderError('Core dump version "0x%x" is not supported!' % self.dump_ver) 180 181 self.core_src_struct = Struct( 182 'header' / self.header_struct, 183 'data' / Bytes(this.header.tot_len - self.header_struct.sizeof() - self.checksum_struct.sizeof()), 184 'checksum' / self.checksum_struct, 185 ) 186 self.core_src = self.core_src_struct.parse(coredump_bytes) # type: ignore 187 188 # Reload header if header struct changes after parsing 189 if self.header_struct != EspCoreDumpV1Header: 190 self.header = EspCoreDumpV2Header.parse(coredump_bytes) 191 192 if self.chip_ver in self.COREDUMP_SUPPORTED_TARGETS: 193 if self.chip_ver == self.ESP32: 194 self.target_methods = Esp32Methods() # type: ignore 195 elif self.chip_ver == self.ESP32S2: 196 self.target_methods = Esp32S2Methods() # type: ignore 197 elif self.chip_ver == self.ESP32C3: 198 self.target_methods = Esp32c3Methods() # type: ignore 199 elif self.chip_ver == self.ESP32S3: 200 self.target_methods = Esp32S3Methods() # type: ignore 201 else: 202 raise NotImplementedError 203 else: 204 raise ESPCoreDumpLoaderError('Core dump chip "0x%x" is not supported!' % self.chip_ver) 205 206 return self.target_methods.TARGET # type: ignore 207 208 def _validate_dump_file(self): # type: () -> None 209 if self.chip_ver not in self.COREDUMP_SUPPORTED_TARGETS: 210 raise ESPCoreDumpLoaderError('Invalid core dump chip version: "{}", should be <= "0x{:X}"' 211 .format(self.chip_ver, self.ESP32S2)) 212 213 if self.checksum_struct == CRC: 214 self._crc_validate() 215 elif self.checksum_struct == SHA256: 216 self._sha256_validate() 217 218 def _crc_validate(self): # type: () -> None 219 data_crc = binascii.crc32( 220 EspCoreDumpV2Header.build(self.core_src.header) + self.core_src.data) & 0xffffffff # type: ignore 221 if data_crc != self.core_src.checksum: # type: ignore 222 raise ESPCoreDumpLoaderError( 223 'Invalid core dump CRC %x, should be %x' % (data_crc, self.core_src.crc)) # type: ignore 224 225 def _sha256_validate(self): # type: () -> None 226 data_sha256 = hashlib.sha256( 227 EspCoreDumpV2Header.build(self.core_src.header) + self.core_src.data) # type: ignore 228 data_sha256_str = data_sha256.hexdigest() 229 sha256_str = binascii.hexlify(self.core_src.checksum).decode('ascii') # type: ignore 230 if data_sha256_str != sha256_str: 231 raise ESPCoreDumpLoaderError('Invalid core dump SHA256 "{}", should be "{}"' 232 .format(data_sha256_str, sha256_str)) 233 234 def create_corefile(self, exe_name=None, e_machine=ESPCoreDumpElfFile.EM_XTENSA): 235 # type: (Optional[str], int) -> None 236 """ 237 Creates core dump ELF file 238 """ 239 self._validate_dump_file() 240 self.core_elf_file = self._create_temp_file() 241 242 if self.dump_ver in [self.ELF_CRC32, 243 self.ELF_SHA256]: 244 self._extract_elf_corefile(exe_name, e_machine) 245 elif self.dump_ver in [self.BIN_V1, 246 self.BIN_V2]: 247 self._extract_bin_corefile(e_machine) 248 else: 249 raise NotImplementedError 250 251 def _extract_elf_corefile(self, exe_name=None, e_machine=ESPCoreDumpElfFile.EM_XTENSA): # type: (str, int) -> None 252 """ 253 Reads the ELF formatted core dump image and parse it 254 """ 255 with open(self.core_elf_file, 'wb') as fw: # type: ignore 256 fw.write(self.core_src.data) # type: ignore 257 258 core_elf = ESPCoreDumpElfFile(self.core_elf_file, e_machine=e_machine) # type: ignore 259 260 # Read note segments from core file which are belong to tasks (TCB or stack) 261 for seg in core_elf.note_segments: 262 for note_sec in seg.note_secs: 263 # Check for version info note 264 if note_sec.name == 'ESP_CORE_DUMP_INFO' \ 265 and note_sec.type == ESPCoreDumpElfFile.PT_INFO \ 266 and exe_name: 267 exe_elf = ElfFile(exe_name) 268 app_sha256 = binascii.hexlify(exe_elf.sha256) 269 coredump_sha256_struct = Struct( 270 'ver' / Int32ul, 271 'sha256' / Bytes(64) # SHA256 as hex string 272 ) 273 coredump_sha256 = coredump_sha256_struct.parse(note_sec.desc[:coredump_sha256_struct.sizeof()]) 274 if coredump_sha256.sha256 != app_sha256: 275 raise ESPCoreDumpLoaderError( 276 'Invalid application image for coredump: coredump SHA256({!r}) != app SHA256({!r}).' 277 .format(coredump_sha256, app_sha256)) 278 if coredump_sha256.ver != self.version: 279 raise ESPCoreDumpLoaderError( 280 'Invalid application image for coredump: coredump SHA256 version({}) != app SHA256 version({}).' 281 .format(coredump_sha256.ver, self.version)) 282 283 @staticmethod 284 def _get_aligned_size(size, align_with=4): # type: (int, int) -> int 285 if size % align_with: 286 return align_with * (size // align_with + 1) 287 return size 288 289 @staticmethod 290 def _build_note_section(name, sec_type, desc): # type: (str, int, str) -> bytes 291 b_name = bytearray(name, encoding='ascii') + b'\0' 292 return NoteSection.build({ # type: ignore 293 'namesz': len(b_name), 294 'descsz': len(desc), 295 'type': sec_type, 296 'name': b_name, 297 'desc': desc, 298 }) 299 300 def _extract_bin_corefile(self, e_machine=ESPCoreDumpElfFile.EM_XTENSA): # type: (int) -> None 301 """ 302 Creates core dump ELF file 303 """ 304 coredump_data_struct = Struct( 305 'tasks' / GreedyRange( 306 AlignedStruct( 307 4, 308 'task_header' / TaskHeader, 309 'tcb' / Bytes(self.header.tcbsz), # type: ignore 310 'stack' / Bytes(abs_(this.task_header.stack_top - this.task_header.stack_end)), # type: ignore 311 ) 312 ), 313 'mem_seg_headers' / MemSegmentHeader[self.core_src.header.segs_num] # type: ignore 314 ) 315 core_elf = ESPCoreDumpElfFile(e_machine=e_machine) 316 notes = b'' 317 core_dump_info_notes = b'' 318 task_info_notes = b'' 319 320 coredump_data = coredump_data_struct.parse(self.core_src.data) # type: ignore 321 for i, task in enumerate(coredump_data.tasks): 322 stack_len_aligned = self._get_aligned_size(abs(task.task_header.stack_top - task.task_header.stack_end)) 323 task_status_kwargs = { 324 'task_index': i, 325 'task_flags': TASK_STATUS_CORRECT, 326 'task_tcb_addr': task.task_header.tcb_addr, 327 'task_stack_start': min(task.task_header.stack_top, task.task_header.stack_end), 328 'task_stack_end': max(task.task_header.stack_top, task.task_header.stack_end), 329 'task_stack_len': stack_len_aligned, 330 'task_name': Padding(16).build({}) # currently we don't have task_name, keep it as padding 331 } 332 333 # Write TCB 334 try: 335 if self.target_methods.tcb_is_sane(task.task_header.tcb_addr, self.header.tcbsz): # type: ignore 336 core_elf.add_segment(task.task_header.tcb_addr, 337 task.tcb, 338 ElfFile.PT_LOAD, 339 ElfSegment.PF_R | ElfSegment.PF_W) 340 elif task.task_header.tcb_addr and self.target_methods.addr_is_fake(task.task_header.tcb_addr): 341 task_status_kwargs['task_flags'] |= TASK_STATUS_TCB_CORRUPTED 342 except ESPCoreDumpLoaderError as e: 343 logging.warning('Skip TCB {} bytes @ 0x{:x}. (Reason: {})' 344 .format(self.header.tcbsz, task.task_header.tcb_addr, e)) # type: ignore 345 346 # Write stack 347 try: 348 if self.target_methods.stack_is_sane(task_status_kwargs['task_stack_start'], 349 task_status_kwargs['task_stack_end']): 350 core_elf.add_segment(task_status_kwargs['task_stack_start'], 351 task.stack, 352 ElfFile.PT_LOAD, 353 ElfSegment.PF_R | ElfSegment.PF_W) 354 elif (task_status_kwargs['task_stack_start'] 355 and self.target_methods.addr_is_fake(task_status_kwargs['task_stack_start'])): 356 task_status_kwargs['task_flags'] |= TASK_STATUS_TCB_CORRUPTED 357 core_elf.add_segment(task_status_kwargs['task_stack_start'], 358 task.stack, 359 ElfFile.PT_LOAD, 360 ElfSegment.PF_R | ElfSegment.PF_W) 361 except ESPCoreDumpLoaderError as e: 362 logging.warning('Skip task\'s ({:x}) stack {} bytes @ 0x{:x}. (Reason: {})' 363 .format(task_status_kwargs['tcb_addr'], 364 task_status_kwargs['stack_len_aligned'], 365 task_status_kwargs['stack_base'], 366 e)) 367 368 try: 369 logging.debug('Stack start_end: 0x{:x} @ 0x{:x}' 370 .format(task.task_header.stack_top, task.task_header.stack_end)) 371 task_regs, extra_regs = self.target_methods.get_registers_from_stack( 372 task.stack, 373 task.task_header.stack_end > task.task_header.stack_top 374 ) 375 except Exception as e: 376 raise ESPCoreDumpLoaderError(str(e)) 377 378 task_info_notes += self._build_note_section('TASK_INFO', 379 ESPCoreDumpElfFile.PT_TASK_INFO, 380 EspTaskStatus.build(task_status_kwargs)) 381 notes += self._build_note_section('CORE', 382 ElfFile.PT_LOAD, 383 self.target_methods.build_prstatus_data(task.task_header.tcb_addr, 384 task_regs)) 385 386 if len(core_dump_info_notes) == 0: # the first task is the crashed task 387 core_dump_info_notes += self._build_note_section('ESP_CORE_DUMP_INFO', 388 ESPCoreDumpElfFile.PT_INFO, 389 Int32ul.build(self.header.ver)) # type: ignore 390 _regs = [task.task_header.tcb_addr] 391 392 # For xtensa, we need to put the exception registers into the extra info as well 393 if e_machine == ESPCoreDumpElfFile.EM_XTENSA and extra_regs: 394 for reg_id in extra_regs: 395 _regs.extend([reg_id, extra_regs[reg_id]]) 396 397 core_dump_info_notes += self._build_note_section( 398 'EXTRA_INFO', 399 ESPCoreDumpElfFile.PT_EXTRA_INFO, 400 Int32ul[len(_regs)].build(_regs) 401 ) 402 403 if self.dump_ver == self.BIN_V2: 404 for header in coredump_data.mem_seg_headers: 405 logging.debug('Read memory segment {} bytes @ 0x{:x}'.format(header.mem_sz, header.mem_start)) 406 core_elf.add_segment(header.mem_start, header.data, ElfFile.PT_LOAD, ElfSegment.PF_R | ElfSegment.PF_W) 407 408 # add notes 409 try: 410 core_elf.add_segment(0, notes, ElfFile.PT_NOTE, 0) 411 except ESPCoreDumpLoaderError as e: 412 logging.warning('Skip NOTES segment {:d} bytes @ 0x{:x}. (Reason: {})'.format(len(notes), 0, e)) 413 # add core dump info notes 414 try: 415 core_elf.add_segment(0, core_dump_info_notes, ElfFile.PT_NOTE, 0) 416 except ESPCoreDumpLoaderError as e: 417 logging.warning('Skip core dump info NOTES segment {:d} bytes @ 0x{:x}. (Reason: {})' 418 .format(len(core_dump_info_notes), 0, e)) 419 try: 420 core_elf.add_segment(0, task_info_notes, ElfFile.PT_NOTE, 0) 421 except ESPCoreDumpLoaderError as e: 422 logging.warning('Skip failed tasks info NOTES segment {:d} bytes @ 0x{:x}. (Reason: {})' 423 .format(len(task_info_notes), 0, e)) 424 # dump core ELF 425 core_elf.e_type = ElfFile.ET_CORE 426 core_elf.dump(self.core_elf_file) # type: ignore 427 428 429class ESPCoreDumpFlashLoader(EspCoreDumpLoader): 430 ESP_COREDUMP_PART_TABLE_OFF = 0x8000 431 432 def __init__(self, offset, target=None, port=None, baud=None): 433 # type: (int, Optional[str], Optional[str], Optional[int]) -> None 434 super(ESPCoreDumpFlashLoader, self).__init__() 435 self.port = port 436 self.baud = baud 437 438 self._get_core_src(offset, target) 439 self.target = self._load_core_src() 440 441 def _get_core_src(self, off, target=None): # type: (int, Optional[str]) -> None 442 """ 443 Loads core dump from flash using parttool or elftool (if offset is set) 444 """ 445 try: 446 if off: 447 logging.info('Invoke esptool to read image.') 448 self._invoke_esptool(off=off, target=target) 449 else: 450 logging.info('Invoke parttool to read image.') 451 self._invoke_parttool() 452 except subprocess.CalledProcessError as e: 453 if e.output: 454 logging.info(e.output) 455 logging.error('Error during the subprocess execution') 456 457 def _invoke_esptool(self, off=None, target=None): # type: (Optional[int], Optional[str]) -> None 458 """ 459 Loads core dump from flash using elftool 460 """ 461 if target is None: 462 target = 'auto' 463 tool_args = [sys.executable, ESPTOOL_PY, '-c', target] 464 if self.port: 465 tool_args.extend(['-p', self.port]) 466 if self.baud: 467 tool_args.extend(['-b', str(self.baud)]) 468 469 self.core_src_file = self._create_temp_file() 470 try: 471 (part_offset, part_size) = self._get_core_dump_partition_info() 472 if not off: 473 off = part_offset # set default offset if not specified 474 logging.warning('The core dump image offset is not specified. Use partition offset: %d.', part_offset) 475 if part_offset != off: 476 logging.warning('Predefined image offset: %d does not match core dump partition offset: %d', off, 477 part_offset) 478 479 # Here we use V1 format to locate the size 480 tool_args.extend(['read_flash', str(off), str(EspCoreDumpV1Header.sizeof())]) 481 tool_args.append(self.core_src_file) # type: ignore 482 483 # read core dump length 484 et_out = subprocess.check_output(tool_args) 485 if et_out: 486 logging.info(et_out.decode('utf-8')) 487 488 header = EspCoreDumpV1Header.parse(open(self.core_src_file, 'rb').read()) # type: ignore 489 if not header or not 0 < header.tot_len <= part_size: 490 logging.error('Incorrect size of core dump image: {}, use partition size instead: {}' 491 .format(header.tot_len, part_size)) 492 coredump_len = part_size 493 else: 494 coredump_len = header.tot_len 495 # set actual size of core dump image and read it from flash 496 tool_args[-2] = str(coredump_len) 497 et_out = subprocess.check_output(tool_args) 498 if et_out: 499 logging.info(et_out.decode('utf-8')) 500 except subprocess.CalledProcessError as e: 501 logging.error('esptool script execution failed with err %d', e.returncode) 502 logging.debug("Command ran: '%s'", e.cmd) 503 logging.debug('Command out:') 504 logging.debug(e.output) 505 raise e 506 507 def _invoke_parttool(self): # type: () -> None 508 """ 509 Loads core dump from flash using parttool 510 """ 511 tool_args = [sys.executable, PARTTOOL_PY] 512 if self.port: 513 tool_args.extend(['--port', self.port]) 514 tool_args.extend(['read_partition', '--partition-type', 'data', '--partition-subtype', 'coredump', '--output']) 515 516 self.core_src_file = self._create_temp_file() 517 try: 518 tool_args.append(self.core_src_file) # type: ignore 519 # read core dump partition 520 et_out = subprocess.check_output(tool_args) 521 if et_out: 522 logging.info(et_out.decode('utf-8')) 523 except subprocess.CalledProcessError as e: 524 logging.error('parttool script execution failed with err %d', e.returncode) 525 logging.debug("Command ran: '%s'", e.cmd) 526 logging.debug('Command out:') 527 logging.debug(e.output) 528 raise e 529 530 def _get_core_dump_partition_info(self, part_off=None): # type: (Optional[int]) -> Tuple[int, int] 531 """ 532 Get core dump partition info using parttool 533 """ 534 logging.info('Retrieving core dump partition offset and size...') 535 if not part_off: 536 part_off = self.ESP_COREDUMP_PART_TABLE_OFF 537 try: 538 tool_args = [sys.executable, PARTTOOL_PY, '-q', '--partition-table-offset', str(part_off)] 539 if self.port: 540 tool_args.extend(['--port', self.port]) 541 invoke_args = tool_args + ['get_partition_info', '--partition-type', 'data', 542 '--partition-subtype', 'coredump', 543 '--info', 'offset', 'size'] 544 res = subprocess.check_output(invoke_args).strip() 545 (offset_str, size_str) = res.rsplit(b'\n')[-1].split(b' ') 546 size = int(size_str, 16) 547 offset = int(offset_str, 16) 548 logging.info('Core dump partition offset=%d, size=%d', offset, size) 549 except subprocess.CalledProcessError as e: 550 logging.error('parttool get partition info failed with err %d', e.returncode) 551 logging.debug("Command ran: '%s'", e.cmd) 552 logging.debug('Command out:') 553 logging.debug(e.output) 554 logging.error('Check if the coredump partition exists in partition table.') 555 raise e 556 return offset, size 557 558 559class ESPCoreDumpFileLoader(EspCoreDumpLoader): 560 def __init__(self, path, is_b64=False): # type: (str, bool) -> None 561 super(ESPCoreDumpFileLoader, self).__init__() 562 self.is_b64 = is_b64 563 564 self._get_core_src(path) 565 self.target = self._load_core_src() 566 567 def _get_core_src(self, path): # type: (str) -> None 568 """ 569 Loads core dump from (raw binary or base64-encoded) file 570 """ 571 logging.debug('Load core dump from "%s", %s format', path, 'b64' if self.is_b64 else 'raw') 572 if not self.is_b64: 573 self.core_src_file = path 574 else: 575 self.core_src_file = self._create_temp_file() 576 with open(self.core_src_file, 'wb') as fw: 577 with open(path, 'rb') as fb64: 578 while True: 579 line = fb64.readline() 580 if len(line) == 0: 581 break 582 data = base64.standard_b64decode(line.rstrip(b'\r\n')) 583 fw.write(data) # type: ignore 584