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