Skip to content

Commit df23a95

Browse files
authored
Merge pull request #961 from flit/refactor/fault_exceptions
Clean up some exceptions
2 parents 2af118b + 49afa88 commit df23a95

File tree

11 files changed

+173
-60
lines changed

11 files changed

+173
-60
lines changed

pyocd/cache/memory.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,6 @@
2323

2424
LOG = logging.getLogger(__name__)
2525

26-
class MemoryAccessError(exceptions.Error):
27-
"""! @brief Generic failure to access memory."""
28-
pass
29-
3026
class MemoryCache(object):
3127
"""! @brief Memory cache.
3228
@@ -37,7 +33,7 @@ class MemoryCache(object):
3733
tokens). If the target is currently running, all accesses cause the cache to be invalidated.
3834
3935
The target's memory map is referenced. All memory accesses must be fully contained within a single
40-
memory region, or a MemoryAccessError will be raised. However, if an access is outside of all regions,
36+
memory region, or a TransferFaultError will be raised. However, if an access is outside of all regions,
4137
the access is passed to the underlying context unmodified. When an access is within a region, that
4238
region's cacheability flag is honoured.
4339
"""
@@ -212,7 +208,7 @@ def _update_contiguous(self, cached, addr, value):
212208
def _check_regions(self, addr, count):
213209
"""! @return A bool indicating whether the given address range is fully contained within
214210
one known memory region, and that region is cacheable.
215-
@exception MemoryAccessError Raised if the access is not entirely contained within a single region.
211+
@exception TransferFaultError Raised if the access is not entirely contained within a single region.
216212
"""
217213
regions = self._core.memory_map.get_intersecting_regions(addr, length=count)
218214

@@ -222,7 +218,7 @@ def _check_regions(self, addr, count):
222218

223219
# Raise if not fully contained within one region.
224220
if len(regions) > 1 or not regions[0].contains_range(addr, length=count):
225-
raise MemoryAccessError("individual memory accesses must not cross memory region boundaries")
221+
raise TransferFaultError("individual memory accesses must not cross memory region boundaries")
226222

227223
# Otherwise return whether the region is cacheable.
228224
return regions[0].is_cacheable

pyocd/core/exceptions.py

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,22 @@ class TransferTimeoutError(TransferError):
6262
pass
6363

6464
class TransferFaultError(TransferError):
65-
"""! @brief An SWD Fault occurred"""
66-
def __init__(self, faultAddress=None, length=None):
67-
super(TransferFaultError, self).__init__(faultAddress)
68-
self._address = faultAddress
69-
self._length = length
65+
"""! @brief A memory fault occurred.
66+
67+
This exception class is extended to optionally record the start address and an optional length of the
68+
attempted memory access that caused the fault. The address and length, if available, will be included
69+
in the description of the exception when it is converted to a string.
70+
71+
Positional arguments passed to the constructor are passed through to the superclass'
72+
constructor, and thus operate like any other standard exception class. Keyword arguments of
73+
'fault_address' and 'length' can optionally be passed to the constructor to initialize the fault
74+
start address and length. Alternatively, the corresponding property setters can be used after
75+
the exception is created.
76+
"""
77+
def __init__(self, *args, **kwargs):
78+
super(TransferFaultError, self).__init__(*args)
79+
self._address = kwargs.get('fault_address', None)
80+
self._length = kwargs.get('length', None)
7081

7182
@property
7283
def fault_address(self):
@@ -89,19 +100,30 @@ def fault_length(self, length):
89100
self._length = length
90101

91102
def __str__(self):
92-
desc = "SWD/JTAG Transfer Fault"
103+
desc = "Memory transfer fault"
104+
if self.args:
105+
if len(self.args) == 1:
106+
desc += " (" + str(self.args[0]) + ")"
107+
else:
108+
desc += " " + str(self.args) + ""
93109
if self._address is not None:
94110
desc += " @ 0x%08x" % self._address
95111
if self._length is not None:
96112
desc += "-0x%08x" % self.fault_end_address
97113
return desc
98114

99115
class FlashFailure(TargetError):
100-
"""! @brief Exception raised when flashing fails for some reason. """
101-
def __init__(self, msg, address=None, result_code=None):
102-
super(FlashFailure, self).__init__(msg)
103-
self._address = address
104-
self._result_code = result_code
116+
"""! @brief Exception raised when flashing fails for some reason.
117+
118+
Positional arguments passed to the constructor are passed through to the superclass'
119+
constructor, and thus operate like any other standard exception class. The flash address that
120+
failed and/or result code from the algorithm can optionally be recorded in the exception, if
121+
passed to the constructor as 'address' and 'result_code' keyword arguments.
122+
"""
123+
def __init__(self, *args, **kwargs):
124+
super(FlashFailure, self).__init__(*args)
125+
self._address = kwargs.get('address', None)
126+
self._result_code = kwargs.get('result_code', None)
105127

106128
@property
107129
def address(self):
@@ -111,6 +133,19 @@ def address(self):
111133
def result_code(self):
112134
return self._result_code
113135

136+
def __str__(self):
137+
desc = super(FlashFailure, self).__str__()
138+
parts = []
139+
if self.address is not None:
140+
parts.append("address 0x%08x" % self.address)
141+
if self.result_code is not None:
142+
parts.append("result code 0x%x" % self.result_code)
143+
if parts:
144+
if desc:
145+
desc += " "
146+
desc += "(%s)" % ("; ".join(parts))
147+
return desc
148+
114149
class FlashEraseFailure(FlashFailure):
115150
"""! @brief An attempt to erase flash failed. """
116151
pass

pyocd/debug/svd/loader.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# pyOCD debugger
2-
# Copyright (c) 2015-2019 Arm Limited
2+
# Copyright (c) 2015-2020 Arm Limited
33
# SPDX-License-Identifier: Apache-2.0
44
#
55
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -20,7 +20,10 @@
2020
import zipfile
2121

2222
from .parser import SVDParser
23-
from ...utility.compatibility import FileNotFoundError_
23+
from ...utility.compatibility import (
24+
FileNotFoundError,
25+
BadZipFile,
26+
)
2427

2528
LOG = logging.getLogger(__name__)
2629

@@ -34,7 +37,7 @@ def from_builtin(cls, svd_name):
3437
zip_stream = pkg_resources.resource_stream("pyocd", BUILTIN_SVD_DATA_PATH)
3538
zip = zipfile.ZipFile(zip_stream, 'r')
3639
return SVDFile(zip.open(svd_name))
37-
except (FileNotFoundError_, zipfile.BadZipFile) as err:
40+
except (KeyError, FileNotFoundError, BadZipFile) as err:
3841
from ...core.session import Session
3942
LOG.warning("unable to open builtin SVD file: %s", err, exc_info=Session.get_current().log_tracebacks)
4043
return None

pyocd/flash/file_programmer.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# pyOCD debugger
2-
# Copyright (c) 2018-2019 Arm Limited
2+
# Copyright (c) 2018-2020 Arm Limited
33
# SPDX-License-Identifier: Apache-2.0
44
#
55
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -25,7 +25,7 @@
2525
from .loader import FlashLoader
2626
from ..core import exceptions
2727
from ..debug.elf.elf import (ELFBinaryFile, SH_FLAGS)
28-
from ..utility.compatibility import FileNotFoundError_
28+
from ..utility.compatibility import FileNotFoundError
2929

3030
LOG = logging.getLogger(__name__)
3131

@@ -115,7 +115,7 @@ def program(self, file_or_path, file_format=None, **kwargs):
115115

116116
# Check for valid path first.
117117
if isPath and not os.path.isfile(file_or_path):
118-
raise FileNotFoundError_(errno.ENOENT, "No such file: '{}'".format(file_or_path))
118+
raise FileNotFoundError(errno.ENOENT, "No such file: '{}'".format(file_or_path))
119119

120120
# If no format provided, use the file's extension.
121121
if not file_format:

pyocd/gdbserver/gdbserver.py

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
from ..utility.sockets import ListenerSocket
3636
from .syscall import GDBSyscallIOHandler
3737
from ..debug import semihost
38-
from ..cache.memory import MemoryAccessError
3938
from .context_facade import GDBDebugContextFacade
4039
from .symbols import GDBSymbolProvider
4140
from ..rtos import RTOS
@@ -761,10 +760,7 @@ def get_memory(self, data):
761760
# Flush so an exception is thrown now if invalid memory was accesses
762761
self.target_context.flush()
763762
val = hex_encode(bytearray(mem))
764-
except exceptions.TransferError:
765-
LOG.debug("get_memory failed at 0x%x" % addr)
766-
val = b'E01' #EPERM
767-
except MemoryAccessError as e:
763+
except exceptions.TransferError as e:
768764
LOG.debug("get_memory failed at 0x%x: %s", addr, str(e))
769765
val = b'E01' #EPERM
770766
return self.create_rsp_packet(val)
@@ -787,12 +783,9 @@ def write_memory_hex(self, data):
787783
# Flush so an exception is thrown now if invalid memory was accessed
788784
self.target_context.flush()
789785
resp = b"OK"
790-
except exceptions.TransferError:
791-
LOG.debug("write_memory failed at 0x%x" % addr)
786+
except exceptions.TransferError as e:
787+
LOG.debug("write_memory_hex failed at 0x%x: %s", addr, str(e))
792788
resp = b'E01' #EPERM
793-
except MemoryAccessError as e:
794-
LOG.debug("get_memory failed at 0x%x: %s", addr, str(e))
795-
val = b'E01' #EPERM
796789

797790
return self.create_rsp_packet(resp)
798791

@@ -813,12 +806,9 @@ def write_memory(self, data):
813806
# Flush so an exception is thrown now if invalid memory was accessed
814807
self.target_context.flush()
815808
resp = b"OK"
816-
except exceptions.TransferError:
817-
LOG.debug("write_memory failed at 0x%x" % addr)
809+
except exceptions.TransferError as e:
810+
LOG.debug("write_memory failed at 0x%x: %s", addr, str(e))
818811
resp = b'E01' #EPERM
819-
except MemoryAccessError as e:
820-
LOG.debug("get_memory failed at 0x%x: %s", addr, str(e))
821-
val = b'E01' #EPERM
822812

823813
return self.create_rsp_packet(resp)
824814

pyocd/probe/stlink/stlink.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ def _read_mem(self, addr, size, memcmd, max, apsel):
339339
# Clear sticky errors.
340340
self._clear_sticky_error()
341341

342-
exc = exceptions.TransferFaultError()
342+
exc = exceptions.TransferFaultError("read")
343343
exc.fault_address = faultAddr
344344
exc.fault_length = thisTransferSize - (faultAddr - addr)
345345
raise exc
@@ -373,7 +373,7 @@ def _write_mem(self, addr, data, memcmd, max, apsel):
373373
# Clear sticky errors.
374374
self._clear_sticky_error()
375375

376-
exc = exceptions.TransferFaultError()
376+
exc = exceptions.TransferFaultError("write")
377377
exc.fault_address = faultAddr
378378
exc.fault_length = thisTransferSize - (faultAddr - addr)
379379
raise exc

pyocd/probe/tcp_client_probe.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -191,10 +191,6 @@ def _perform_request(self, request, *args):
191191

192192
def _create_exception_from_status_code(self, status, message):
193193
"""! @brief Convert a status code into an exception instance."""
194-
# The TransferFaultError class requires special handling because it doesn't take a
195-
# message as the first argument.
196-
if status == self.StatusCode.TRANSFER_FAULT:
197-
return exceptions.TransferFaultError()
198194
# Other status codes can use the map.
199195
return self.STATUS_CODE_CLASS_MAP.get(status, exceptions.ProbeError)(message)
200196

pyocd/target/pack/cmsis_pack.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,12 @@
2525
import six
2626
import struct
2727

28-
# zipfile from Python 2 has a misspelled BadZipFile exception class.
29-
try:
30-
from zipfile import BadZipFile
31-
except ImportError:
32-
from zipfile import BadZipfile as BadZipFile
33-
3428
from .flash_algo import PackFlashAlgo
3529
from ... import core
3630
from ...core import exceptions
3731
from ...core.target import Target
3832
from ...core.memory_map import (MemoryMap, MemoryType, MEMORY_TYPE_CLASS_MAP, FlashRegion)
33+
from ...utility.compatibility import BadZipFile
3934

4035
LOG = logging.getLogger(__name__)
4136

pyocd/target/pack/pack_target.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# pyOCD debugger
2-
# Copyright (c) 2017-2019 Arm Limited
2+
# Copyright (c) 2017-2020 Arm Limited
33
# SPDX-License-Identifier: Apache-2.0
44
#
55
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,7 +24,7 @@
2424
from .. import TARGET
2525
from ...coresight.coresight_target import CoreSightTarget
2626
from ...debug.svd.loader import SVDFile
27-
from ...utility.compatibility import FileNotFoundError_
27+
from ...utility.compatibility import FileNotFoundError
2828

2929
try:
3030
import cmsis_pack_manager
@@ -162,7 +162,7 @@ def _generate_pack_target_class(dev):
162162
"set_default_reset_type": _PackTargetMethods._pack_target_set_default_reset_type,
163163
})
164164
return targetClass
165-
except (MalformedCmsisPackError, FileNotFoundError_) as err:
165+
except (MalformedCmsisPackError, FileNotFoundError) as err:
166166
LOG.warning(err)
167167
return None
168168

@@ -183,7 +183,7 @@ def populate_device(dev):
183183
# Make sure there isn't a duplicate target name.
184184
if part not in TARGET:
185185
TARGET[part] = tgt
186-
except (MalformedCmsisPackError, FileNotFoundError_) as err:
186+
except (MalformedCmsisPackError, FileNotFoundError) as err:
187187
LOG.warning(err)
188188

189189
@staticmethod

pyocd/utility/compatibility.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# pyOCD debugger
2-
# Copyright (c) 2018-2019 Arm Limited
2+
# Copyright (c) 2018-2020 Arm Limited
33
# SPDX-License-Identifier: Apache-2.0
44
#
55
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -61,11 +61,16 @@ def to_str_safe(v):
6161
return v
6262

6363
# Make FileNotFoundError available to Python 2.x.
64-
if not PY3:
65-
FileNotFoundError = IOError
64+
try:
65+
FileNotFoundError = FileNotFoundError
66+
except NameError:
67+
FileNotFoundError = OSError
6668

67-
# Symbol to reference either the builtin FNF or our custom subclass.
68-
FileNotFoundError_ = FileNotFoundError
69+
# zipfile from Python 2 has a misspelled BadZipFile exception class.
70+
try:
71+
from zipfile import BadZipFile
72+
except ImportError:
73+
from zipfile import BadZipfile as BadZipFile
6974

7075
try:
7176
from shutil import get_terminal_size

0 commit comments

Comments
 (0)