Repository: pyocd/pyocd
Issue: "pyocd commander --no-init" has an exception with pyOCD ver 0.28 (but not with ver 0.27.1)

We are using pyocd with daplink (on a separate debug board, i.e. a target isn't permanently attached).  

If we try running pyocd commander with the --no-init flag on pyocd v0.28 we are seeing an exception (which isn't there on pyocd ver 0.27.1).  

####
pyocd commander --no-init

0001109:WARNING:board:Generic 'cortex_m' target type is selected by default; is this intentional? You will be able to debug most devices, but not program  flash. To set the target type use the '--target' argument or 'target_override' option. Use 'pyocd list --targets' to see available targets types.
0001133:CRITICAL:__main__:uncaught exception: 
Traceback (most recent call last):
  File "/Users/abcd/Library/Python/2.7/lib/python/site-packages/pyocd/__main__.py", line 398, in run
    self._COMMANDS[self._args.cmd](self)
  File "/Users/abcd/Library/Python/2.7/lib/python/site-packages/pyocd/__main__.py", line 802, in do_commander
    PyOCDCommander(self._args, cmds).run()
  File "/Users/abcd/Library/Python/2.7/lib/python/site-packages/pyocd/commands/commander.py", line 64, in run
    if not self.connect():
  File "/Users/abcd/Library/Python/2.7/lib/python/site-packages/pyocd/commands/commander.py", line 183, in connect
    result = self.context.attach_session(self.session)
  File "/Users/abcd/Library/Python/2.7/lib/python/site-packages/pyocd/commands/execution_context.py", line 204, in attach_session
    assert session.is_open
AssertionError
####

Our workaround is to:
  pip uninstall pyocd
  pip install pyocd==0.27.1





Changed files:
pyocd/commands/values.py, pyocd/commands/base.py, pyocd/commands/execution_context.py, pyocd/commands/commands.py

diff --git a/pyocd/commands/base.py b/pyocd/commands/base.py
index 01f86cfb..61ca68e7 100755
--- a/pyocd/commands/base.py
+++ b/pyocd/commands/base.py
@@ -125,6 +125,8 @@ class CommandBase(object):
         try:
             deref = (arg[0] == '[')
             if deref:
+                if not self.context.selected_core:
+                    raise ToolError("cannot dereference when memory is not accessible")
                 arg = arg[1:-1]
                 offset = 0
                 if ',' in arg:
@@ -133,7 +135,7 @@ class CommandBase(object):
                     offset = int(offset.strip(), base=0)
 
             value = None
-            if arg.lower() in self.context.selected_core.core_registers.by_name:
+            if (self.context.selected_core) and (arg.lower() in self.context.selected_core.core_registers.by_name):
                 value = self.context.selected_core.read_core_register(arg.lower())
                 self.context.writei("%s = 0x%08x", arg.lower(), value)
             else:
diff --git a/pyocd/commands/commands.py b/pyocd/commands/commands.py
index a30ef04d..5f468fc4 100755
--- a/pyocd/commands/commands.py
+++ b/pyocd/commands/commands.py
@@ -1268,7 +1268,7 @@ class MakeApCommand(CommandBase):
     # TODO support ADIv6 AP addresses
     def parse(self, args):
         self.apsel = self._convert_value(args[0])
-        self.ap_addr = coresight.ap.APv1Address(apsel)
+        self.ap_addr = coresight.ap.APv1Address(self.apsel)
 
     def execute(self):
         if self.ap_addr in self.context.target.aps:
@@ -1463,15 +1463,20 @@ class ShowCommand(CommandBase):
         self.args = args[1:]
 
     def execute(self):
+        # Look up value class by name.
         try:
             value_class = self.context.command_set.values[self.name]
-            if 'r' not in value_class.INFO['access']:
-                raise exceptions.CommandError("value '%s' is not readable" % self.name)
-            value_object = value_class(self.context)
-            value_object.display(self.args)
         except KeyError:
             raise exceptions.CommandError("unknown value name '%s'" % self.name)
 
+        # Check readability.
+        if 'r' not in value_class.INFO['access']:
+            raise exceptions.CommandError("value '%s' is not readable" % self.name)
+        
+        # Execute show operation.
+        value_object = value_class(self.context)
+        value_object.display(self.args)
+
     @classmethod
     def format_help(cls, context, max_width=72):
         text = "Usage: {cmd} {usage}\\n".format(cmd=cls.INFO['names'][0], usage=cls.INFO['usage'])
@@ -1504,15 +1509,20 @@ class SetCommand(CommandBase):
         self.args = args[1:]
 
     def execute(self):
+        # Look up value class by name.
         try:
             value_class = self.context.command_set.values[self.name]
-            if 'w' not in value_class.INFO['access']:
-                raise exceptions.CommandError("value '%s' is not modifiable" % self.name)
-            value_object = value_class(self.context)
-            value_object.modify(self.args)
         except KeyError:
             raise exceptions.CommandError("unknown value name '%s'" % self.name)
 
+        # Check writability.
+        if 'w' not in value_class.INFO['access']:
+            raise exceptions.CommandError("value '%s' is not modifiable" % self.name)
+        
+        # Execute set operation.
+        value_object = value_class(self.context)
+        value_object.modify(self.args)
+
     @classmethod
     def format_help(cls, context, max_width=72):
         text = "Usage: {cmd} {usage}\\n".format(cmd=cls.INFO['names'][0], usage=cls.INFO['usage'])
@@ -1563,7 +1573,7 @@ Prefix line with ! to execute a shell command."""
                         ", ".join("'%s'" % c for c in matched_commands))
                 return
             elif len(matched_commands) == 0:
-                raise exceptions.CommandError("Error: unrecognized command '%s'", cmd_name)
+                raise exceptions.CommandError("Error: unrecognized command '%s'" % cmd_name)
             cmd_name = matched_commands[0]
             cmd_class = self.context.command_set.commands[cmd_name]
 
@@ -1577,7 +1587,7 @@ Prefix line with ! to execute a shell command."""
                                 ", ".join("'%s'" % c for c in matched_values))
                         return
                     elif len(matched_values) == 0:
-                        raise exceptions.CommandError("Error: unrecognized value '%s'", value_name)
+                        raise exceptions.CommandError("Error: unrecognized value '%s'" % value_name)
                     value_name = matched_values[0]
                     cmd_class = self.context.command_set.values[value_name]
                     self.context.write(cmd_class.format_help(self.context, self.term_width))
diff --git a/pyocd/commands/execution_context.py b/pyocd/commands/execution_context.py
index 0b709487..bb4ebe5f 100755
--- a/pyocd/commands/execution_context.py
+++ b/pyocd/commands/execution_context.py
@@ -23,6 +23,7 @@ from collections import namedtuple
 import subprocess
 
 from ..core import exceptions
+from ..coresight.ap import MEM_AP
 from ..utility.compatibility import get_terminal_size
 from ..utility.cmdline import (
     split_command_line,
@@ -288,7 +289,10 @@ class CommandExecutionContext(object):
     
     @property
     def selected_ap(self):
-        return self.target.aps[self.selected_ap_address]
+        if self.selected_ap_address is None:
+            return None
+        else:
+            return self.target.aps[self.selected_ap_address]
 
     def process_command_line(self, line):
         """! @brief Run a command line consisting of one or more semicolon-separated commands."""
diff --git a/pyocd/commands/values.py b/pyocd/commands/values.py
index 7bcaec18..eea6cde2 100755
--- a/pyocd/commands/values.py
+++ b/pyocd/commands/values.py
@@ -96,7 +96,26 @@ class CoresValue(ValueBase):
             for i, core in self.context.target.cores.items():
                 self.context.writei("Core %d type:  %s%s", i,
                         coresight.core_ids.CORE_TYPE_NAME[core.core_type],
-                        " (selected)" if (self.context.selected_core.core_number == i) else "")
+                        " (selected)" if ((self.context.selected_core is not None) \\
+                                            and (self.context.selected_core.core_number == i)) else "")
+
+class APsValue(ValueBase):
+    INFO = {
+            'names': ['aps'],
+            'group': 'standard',
+            'category': 'target',
+            'access': 'r',
+            'help': "List discovered Access Ports.",
+            }
+
+    def display(self, args):
+        if self.context.target.is_locked():
+            self.context.write("Target is locked")
+        else:
+            self.context.writei("%d APs:", len(self.context.target.aps))
+            for addr, ap in sorted(self.context.target.aps.items(), key=lambda x: x[0]):
+                self.context.writei("%s: %s%s", addr, ap.type_name,
+                        " (selected)" if (self.context.selected_ap_address == addr) else "")
 
 class MemoryMapValue(ValueBase):
     INFO = {
@@ -108,6 +127,10 @@ class MemoryMapValue(ValueBase):
             }
 
     def display(self, args):
+        if self.context.selected_core is None:
+            self.context.write("No core is selected")
+            return
+
         pt = prettytable.PrettyTable(["Region", "Type", "Start", "End", "Size", "Access", "Sector", "Page"])
         pt.align = 'l'
         pt.border = False
@@ -205,6 +228,10 @@ class FaultValue(ValueBase):
                 if showAll or bit != 0:
                     self.context.writei("    %s = 0x%x", name, bit)
         
+        if self.context.selected_core is None:
+            self.context.write("No core is selected")
+            return
+
         cfsr = self.context.selected_core.read32(CFSR)
         mmfsr = cfsr & 0xff
         bfsr = (cfsr >> 8) & 0xff
@@ -292,6 +319,9 @@ class MemApValue(ValueBase):
             }
 
     def display(self, args):
+        if self.context.selected_ap is None:
+            self.context.write("No MEM-AP is selected")
+            return
         self.context.writef("{} is selected", self.context.selected_ap.short_description)
 
     def modify(self, args):
@@ -322,6 +352,9 @@ class HnonsecValue(ValueBase):
             }
 
     def display(self, args):
+        if self.context.selected_ap is None:
+            self.context.write("No MEM-AP is selected")
+            return
         self.context.writef("{} HNONSEC = {} ({})",
             self.context.selected_ap.short_description,
             self.context.selected_ap.hnonsec,
@@ -330,6 +363,9 @@ class HnonsecValue(ValueBase):
     def modify(self, args):
         if len(args) == 0:
             raise exceptions.CommandError("missing argument")
+        if self.context.selected_ap is None:
+            self.context.write("No MEM-AP is selected")
+            return
         value = int(args[0], base=0)
         self.context.selected_ap.hnonsec = value
 
@@ -343,6 +379,9 @@ class HprotValue(ValueBase):
             }
 
     def display(self, args):
+        if self.context.selected_ap is None:
+            self.context.write("No MEM-AP is selected")
+            return
         hprot = self.context.selected_ap.hprot
         self.context.writef("{} HPROT = {:#x}",
             self.context.selected_ap.short_description,
@@ -359,6 +398,9 @@ class HprotValue(ValueBase):
     def modify(self, args):
         if len(args) == 0:
             raise exceptions.CommandError("missing argument")
+        if self.context.selected_ap is None:
+            self.context.write("No MEM-AP is selected")
+            return
         value = int(args[0], base=0)
         self.context.selected_ap.hprot = value
 
@@ -399,6 +441,9 @@ class RegisterGroupsValue(ValueBase):
             }
 
     def display(self, args):
+        if self.context.selected_core is None:
+            self.context.write("No core is selected")
+            return
         for g in sorted(self.context.selected_core.core_registers.groups):
             self.context.write(g)
 
@@ -415,6 +460,10 @@ class VectorCatchValue(ValueBase):
             }
 
     def display(self, args):
+        if self.context.selected_core is None:
+            self.context.write("No core is selected")
+            return
+
         catch = self.context.selected_core.get_vector_catch()
 
         self.context.write("Vector catch:")
@@ -426,6 +475,10 @@ class VectorCatchValue(ValueBase):
     def modify(self, args):
         if len(args) == 0:
             raise exceptions.CommandError("missing vector catch setting")
+
+        if self.context.selected_core is None:
+            self.context.write("No core is selected")
+            return
     
         try:
             self.context.selected_core.set_vector_catch(convert_vector_catch(args[0]))