From 7c034d0ea320689d532df54687b844635523a2ea Mon Sep 17 00:00:00 2001 From: Chris Reed Date: Sat, 8 Sep 2018 11:30:41 -0500 Subject: [PATCH 1/3] HandlerModeThread unique id constant, removed unused CommonThreadContext. --- pyOCD/rtos/argon.py | 2 +- pyOCD/rtos/common.py | 105 ++--------------------------------------- pyOCD/rtos/freertos.py | 2 +- pyOCD/rtos/zephyr.py | 2 +- 4 files changed, 6 insertions(+), 105 deletions(-) diff --git a/pyOCD/rtos/argon.py b/pyOCD/rtos/argon.py index 4624767fc..4669df9ba 100644 --- a/pyOCD/rtos/argon.py +++ b/pyOCD/rtos/argon.py @@ -419,7 +419,7 @@ def get_current_thread_id(self): if not self.is_enabled: return None if self.get_ipsr() > 0: - return 2 + return HandlerModeThread.UNIQUE_ID return self.get_actual_current_thread_id() def get_actual_current_thread_id(self): diff --git a/pyOCD/rtos/common.py b/pyOCD/rtos/common.py index 3274d3625..436e62c5d 100644 --- a/pyOCD/rtos/common.py +++ b/pyOCD/rtos/common.py @@ -59,109 +59,10 @@ def read_c_string(context, ptr): return s -## @brief Standard Cortex-M register stacking context. -class CommonThreadContext(DebugContext): - # SP is handled specially, so it is not in this dict. - CORE_REGISTER_OFFSETS = { - 0: 32, # r0 - 1: 36, # r1 - 2: 40, # r2 - 3: 44, # r3 - 4: 0, # r4 - 5: 4, # r5 - 6: 8, # r6 - 7: 12, # r7 - 8: 16, # r8 - 9: 20, # r9 - 10: 24, # r10 - 11: 28, # r11 - 12: 48, # r12 - 14: 52, # lr - 15: 56, # pc - 16: 60, # xpsr - } - - def __init__(self, parentContext, thread): - super(CommonThreadContext, self).__init__(parentContext.core) - self._parent = parentContext - self._thread = thread - - def readCoreRegistersRaw(self, reg_list): - reg_list = [register_name_to_index(reg) for reg in reg_list] - reg_vals = [] - - inException = self._get_ipsr() > 0 - isCurrent = self._is_current() - - sp = self._get_stack_pointer() - saveSp = sp - if not isCurrent: - sp -= 0x40 - elif inException: - sp -= 0x20 - - for reg in reg_list: - if isCurrent: - if not inException: - # Not in an exception, so just read the live register. - reg_vals.append(self._core.readCoreRegisterRaw(reg)) - continue - else: - # Check for regs we can't access. - if reg in (4, 5, 6, 7, 8, 9, 10, 11): - reg_vals.append(0) - continue - - # Must handle stack pointer specially. - if reg == 13: - reg_vals.append(saveSp) - continue - - spOffset = self.CORE_REGISTER_OFFSETS.get(reg, None) - if spOffset is None: - reg_vals.append(self._core.readCoreRegisterRaw(reg)) - continue - if isCurrent and inException: - spOffset -= 0x20 - - try: - reg_vals.append(self._core.read32(sp + spOffset)) - except DAPAccess.TransferError: - reg_vals.append(0) - - return reg_vals - - def _get_stack_pointer(self): - sp = 0 - if self._is_current(): - # Read live process stack. - sp = self._core.readCoreRegister('sp') - - # In IRQ context, we have to adjust for hw saved state. - if self._get_ipsr() > 0: - sp += 0x20 - else: - # Get stack pointer saved in thread struct. - sp = self._core.read32(self._thread._base + THREAD_STACK_POINTER_OFFSET) - - # Skip saved thread state. - sp += 0x40 - return sp - - def _get_ipsr(self): - return self._core.readCoreRegister('xpsr') & 0xff - - def _has_extended_frame(self): - return False - - def _is_current(self): - return self._thread.is_current - - def writeCoreRegistersRaw(self, reg_list, data_list): - self._core.writeCoreRegistersRaw(reg_list, data_list) - ## @brief Class representing the handler mode. class HandlerModeThread(TargetThread): + UNIQUE_ID = 2 + def __init__(self, targetContext, provider): super(HandlerModeThread, self).__init__() self._target_context = targetContext @@ -176,7 +77,7 @@ def priority(self): @property def unique_id(self): - return 2 + return self.UNIQUE_ID @property def name(self): diff --git a/pyOCD/rtos/freertos.py b/pyOCD/rtos/freertos.py index e13871b75..d84476319 100644 --- a/pyOCD/rtos/freertos.py +++ b/pyOCD/rtos/freertos.py @@ -495,7 +495,7 @@ def get_current_thread_id(self): if not self.is_enabled: return None if self.get_ipsr() > 0: - return 2 + return HandlerModeThread.UNIQUE_ID return self.get_actual_current_thread_id() def get_actual_current_thread_id(self): diff --git a/pyOCD/rtos/zephyr.py b/pyOCD/rtos/zephyr.py index 12a2bb071..297ffee0c 100644 --- a/pyOCD/rtos/zephyr.py +++ b/pyOCD/rtos/zephyr.py @@ -385,7 +385,7 @@ def get_current_thread_id(self): if not self.is_enabled: return None if self.get_ipsr() > 0: - return 2 + return HandlerModeThread.UNIQUE_ID return self.get_actual_current_thread_id() def get_actual_current_thread_id(self): From 090c570ee9f82a5f98f4efda4109af1fc3e0d9b7 Mon Sep 17 00:00:00 2001 From: Seppo Takalo Date: Tue, 14 Aug 2018 13:29:02 +0300 Subject: [PATCH 2/3] Reworked RTX5 thread provider to fix bugs. - Defined TargetList class. - Wrapped all target memory accesses with exception handlers. - Added update_state() to RTXTargetThread class. - Rewrote RTX5ThreadProvider._build_thread_list() to use TargetList, to call update_state() on preexisting threads, and to remove deleted threads. - Reading the RTX5 kernel state in get_is_active() to determine if the kernel is running yet, for the is_enabled property. - current_thread property will return None if the kernel is not running yet. - Expanded thread state names to include the upper nibble that declares what object type the thread is blocked on. - Moved offset constants into classes. --- pyOCD/rtos/argon.py | 4 +- pyOCD/rtos/rtx5.py | 222 +++++++++++++++++++++++++++++--------------- 2 files changed, 149 insertions(+), 77 deletions(-) diff --git a/pyOCD/rtos/argon.py b/pyOCD/rtos/argon.py index 4669df9ba..fbc03b1f2 100644 --- a/pyOCD/rtos/argon.py +++ b/pyOCD/rtos/argon.py @@ -405,8 +405,8 @@ def current_thread(self): try: return self._threads[id] except KeyError: - log.debug("key error getting current thread id=%x", id) - log.debug("self._threads = %s", repr(self._threads)) + log.debug("key error getting current thread id=%s; self._threads = %s", + ("%x" % id) if (id is not None) else id, repr(self._threads)) return None def is_valid_thread_id(self, threadId): diff --git a/pyOCD/rtos/rtx5.py b/pyOCD/rtos/rtx5.py index 5beecf275..d50a46150 100644 --- a/pyOCD/rtos/rtx5.py +++ b/pyOCD/rtos/rtx5.py @@ -25,6 +25,27 @@ # Create a logger for this module. log = logging.getLogger("rtx5") +class TargetList(object): + def __init__(self, context, ptr, nextOffset): + self._context = context + self._list = ptr + self._offset = nextOffset + + def __iter__(self): + # Read first item on list. + node = self._context.read32(self._list) + + while node != 0: + # Return previously read item. + yield node + + try: + # Read the next item in the list. + node = self._context.read32(node + self._offset) + except DAPAccess.TransferError as exc: + log.warning("TransferError while reading list elements (list=0x%08x, node=0x%08x), terminating list: %s", self._list, node, exc) + break + ## @brief class RTXThreadContext(DebugContext): # SP/PSP are handled specially, so it is not in these dicts. @@ -176,30 +197,55 @@ def _get_ipsr(self): def writeCoreRegistersRaw(self, reg_list, data_list): self._parent.writeCoreRegistersRaw(reg_list, data_list) -STATE_OFFSET = 1 -NAME_OFFSET = 4 -STACKFRAME_OFFSET = 34 -SP_OFFSET = 56 - ## @brief Base class representing a thread on the target. class RTXTargetThread(TargetThread): + STATE_OFFSET = 1 + NAME_OFFSET = 4 + STACKFRAME_OFFSET = 34 + SP_OFFSET = 56 + STATES = { - 0: "Inactive", - 1: "Ready", - 2: "Running", - 3: "Blocked", - 4: "Terminated", + 0x00: "Inactive", + 0x01: "Ready", + 0x02: "Running", + 0x03: "Blocked", + 0x04: "Terminated", + 0x13: "Blocked[Delay]", + 0x23: "Blocked[Join]", + 0x33: "Blocked[ThrFlg]", + 0x43: "Blocked[EvtFlg]", + 0x53: "Blocked[Mutex]", + 0x63: "Blocked[Sem]", + 0x73: "Blocked[MemPool]", + 0x83: "Blocked[MsgGet]", + 0x93: "Blocked[MsgPut]", } + def __init__(self, targetContext, provider, base): super(RTXTargetThread, self).__init__() self._target_context = targetContext self._provider = provider self._base = base + self._state = 0 self._thread_context = RTXThreadContext(self._target_context, self) self._has_fpu = self._thread_context.core.has_fpu - name_ptr = self._target_context.read32(self._base + NAME_OFFSET) - self._name = read_c_string(self._target_context, name_ptr) + try: + name_ptr = self._target_context.read32(self._base + RTXTargetThread.NAME_OFFSET) + self._name = read_c_string(self._target_context, name_ptr) + + self.update_state() + except DAPAccess.TransferError: + log.debug("Transfer error while reading thread %x name: %s", self._base, exc) + self._name = "?" log.debug('RTXTargetThread 0x%x' % base) + + def update_state(self): + try: + state = self._target_context.read8(self._base + RTXTargetThread.STATE_OFFSET) + except DAPAccess.TransferError as exc: + log.debug("Transfer error while reading thread %x state: %s", self._base, exc) + else: + self._state = state @property def unique_id(self): @@ -212,9 +258,7 @@ def context(self): @property def description(self): - state = self._target_context.read8(self._base + STATE_OFFSET) & 0x0F - - return self.STATES[state] + ' 0x%x' % self._base + return self.STATES[self._state] + '; 0x%x' % self._base @property def name(self): @@ -231,34 +275,42 @@ def get_stack_pointer(self): else: # Get stack pointer saved in thread struct. try: - sp = self._target_context.read32(self._base + SP_OFFSET) + sp = self._target_context.read32(self._base + RTXTargetThread.SP_OFFSET) except DAPAccess.TransferError: - log.debug("Transfer error while reading thread's stack pointer @ 0x%08x", self._base + SP_OFFSET) + log.debug("Transfer error while reading thread's stack pointer @ 0x%08x", self._base + RTXTargetThread.SP_OFFSET) return sp def get_stack_frame(self): - return self._target_context.read8(self._base + STACKFRAME_OFFSET) + return self._target_context.read8(self._base + RTXTargetThread.STACKFRAME_OFFSET) +## @brief Thread provider for RTX5 RTOS. +class RTX5ThreadProvider(ThreadProvider): + # Offsets in osRtxInfo_t + KERNEL_STATE_OFFSET = 8 + CURRENT_OFFSET = 20 + THREADLIST_OFFSET = 36 + DELAYLIST_OFFSET = 44 + WAITLIST_OFFSET = 48 -CURRENT_OFFSET = 20 -THREADLIST_OFFSET = 36 -DELAYLIST_OFFSET = 44 -WAITLIST_OFFSET = 48 -THREADNEXT_OFFSET = 8 -DELAYNEXT_OFFSET = 16 + # Offset in osRtxThread_t + THREADNEXT_OFFSET = 8 + DELAYNEXT_OFFSET = 16 -## @brief Base class for RTOS support plugins. -class RTX5ThreadProvider(ThreadProvider): def __init__(self, target): super(RTX5ThreadProvider, self).__init__(target) def init(self, symbolProvider): # Lookup required symbols. - self._osRtxInfo =symbolProvider.get_symbol_value('osRtxInfo') + self._osRtxInfo = symbolProvider.get_symbol_value('osRtxInfo') if self._osRtxInfo is None: return False - log.debug('init(), found osRtxInfo') + log.debug('osRtxInfo = 0x%08x', self._osRtxInfo) + self._readylist = self._osRtxInfo + RTX5ThreadProvider.THREADLIST_OFFSET + self._delaylist = self._osRtxInfo + RTX5ThreadProvider.DELAYLIST_OFFSET + self._waitlist = self._osRtxInfo + RTX5ThreadProvider.WAITLIST_OFFSET self._threads = {} + self._current = None + self._current_id = None self._target.root_target.subscribe(Target.EVENT_POST_FLASH_PROGRAM, self.event_handler) self._target.subscribe(Target.EVENT_POST_RESET, self.event_handler) return True @@ -276,71 +328,91 @@ def event_handler(self, notification): self.invalidate(); def _build_thread_list(self): - log.debug('_build_thread_list()') - self._readylist = self._osRtxInfo + THREADLIST_OFFSET - self._delaylist = self._osRtxInfo + DELAYLIST_OFFSET - self._waitlist = self._osRtxInfo + WAITLIST_OFFSET + newThreads = {} + + def create_or_update(thread): + # Check for and reuse existing thread. + if thread in self._threads: + # Thread already exists, update its state. + t = self._threads[thread] + t.update_state() + else: + # Create a new thread. + t = RTXTargetThread(self._target_context, self, thread) + newThreads[t.unique_id] = t # Currently running Thread - thread = self._target_context.read32(self._osRtxInfo + CURRENT_OFFSET) + thread = self._target_context.read32(self._osRtxInfo + RTX5ThreadProvider.CURRENT_OFFSET) if thread: - log.debug('found RUNNING thread %x' % thread) - if not thread in self._threads: - self._threads[thread] = RTXTargetThread(self._target_context, self, thread) + create_or_update(thread) self._current_id = thread - self._current = self._threads[thread] - - # Scan Ready list - thread = self._target_context.read32(self._readylist) - while thread: - log.debug('found READY thread %x' % thread) - # targetContext, provider, base - if not thread in self._threads: - self._threads[thread] = RTXTargetThread(self._target_context, self, thread) - thread = self._target_context.read32(thread+THREADNEXT_OFFSET) - - # Delay list - thread = self._target_context.read32(self._delaylist) - while thread: - log.debug('found DELAY thread %x' % thread) - if not thread in self._threads: - self._threads[thread] = RTXTargetThread(self._target_context, self, thread) - thread = self._target_context.read32(thread+DELAYNEXT_OFFSET) - - # Wait list - thread = self._target_context.read32(self._waitlist) - while thread: - log.debug('found WAIT thread %x' % thread) - if not thread in self._threads: - self._threads[thread] = RTXTargetThread(self._target_context, self, thread) - thread = self._target_context.read32(thread+DELAYNEXT_OFFSET) + self._current = newThreads[thread] + else: + self._current_id = None + self._current = None + + # List of target thread lists to examine. + threadLists = [ + TargetList(self._target_context, self._readylist, RTX5ThreadProvider.THREADNEXT_OFFSET), + TargetList(self._target_context, self._delaylist, RTX5ThreadProvider.DELAYNEXT_OFFSET), + TargetList(self._target_context, self._waitlist, RTX5ThreadProvider.DELAYNEXT_OFFSET), + ] + + # Scan thread lists. + for theList in threadLists: + for thread in theList: + create_or_update(thread) # Create fake handler mode thread. if self.get_ipsr() > 0: - log.debug("creating handler mode thread") - t = HandlerModeThread(self._target_context, self) - self._threads[t.unique_id] = t - - log.debug('found %d threads' % len(self._threads)) + newThreads[HandlerModeThread.UNIQUE_ID] = HandlerModeThread(self._target_context, self) + + self._threads = newThreads def get_thread(self, threadId): - log.debug('get_thread()') - if not threadId in self._threads: + if not self.is_enabled: return None - return self._threads[threadId] + self.update_threads() + return self._threads.get(threadId, None) @property def is_enabled(self): - log.debug('is_enabled()') - self.update_threads() # Has caching, so not a bottleneck - return len(self._threads) > 0 + return self.get_is_active() @property def current_thread(self): - return self._current + if not self.is_enabled: + return None + self.update_threads() + id = self.get_current_thread_id() + try: + return self._threads[id] + except KeyError: + log.debug("key error getting current thread id=%s; self._threads = %s", + ("%x" % id) if (id is not None) else id, repr(self._threads)) + return None def is_valid_thread_id(self, threadId): - return self._threads[threadId] is not None + if not self.is_enabled: + return False + self.update_threads() + return threadId in self._threads def get_current_thread_id(self): + if not self.is_enabled: + return None + if self.get_ipsr() > 0: + return HandlerModeThread.UNIQUE_ID + self.update_threads() return self._current_id + + def get_is_active(self): + if self._osRtxInfo is None: + return False + try: + state = self._target_context.read8(self._osRtxInfo + RTX5ThreadProvider.KERNEL_STATE_OFFSET) + except DAPAccess.TransferError as exc: + log.debug("Transfer error reading kernel state: %s", exc) + return False + else: + return state != 0 From b1620230ecb1c346e9c45263c431e95e8c9fe7d0 Mon Sep 17 00:00:00 2001 From: Chris Reed Date: Sat, 8 Sep 2018 14:09:15 -0500 Subject: [PATCH 3/3] Exception handler fix for GDBServer.handleMsg(). - The exception handler for an unrecognized GDB command was also wrapped around the invocation of the command handler, so if a KeyError or IndexError exception was raised during command execution it would be incorrectly treated as an unknown command packet. --- pyOCD/gdbserver/gdbserver.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pyOCD/gdbserver/gdbserver.py b/pyOCD/gdbserver/gdbserver.py index 5ca7a9c13..4808d5200 100644 --- a/pyOCD/gdbserver/gdbserver.py +++ b/pyOCD/gdbserver/gdbserver.py @@ -518,20 +518,21 @@ def _run_connection(self): def handleMsg(self, msg): try: - assert msg[0:1] == b'$', "invalid first char of message (!= $" + assert msg[0:1] == b'$', "invalid first char of message != $" try: handler, msgStart = self.COMMANDS[msg[1:2]] - if msgStart == 0: - reply = handler() - else: - reply = handler(msg[msgStart:]) - detach = 1 if msg[1:2] in self.DETACH_COMMANDS else 0 - return reply, detach except (KeyError, IndexError): self.log.error("Unknown RSP packet: %s", msg) return self.createRSPPacket(b""), 0 + if msgStart == 0: + reply = handler() + else: + reply = handler(msg[msgStart:]) + detach = 1 if msg[1:2] in self.DETACH_COMMANDS else 0 + return reply, detach + except Exception as e: self.log.error("Unhandled exception in handleMsg: %s", e) traceback.print_exc()