14.5 Implementing thread awareness

Thread awareness is probably the most significant part of the implementation.

The corresponding call on the API is getOSContextProvider(), where context here means execution context, as in a thread or a task. The API expects an instance of the Java interface IOSContextProvider to be returned by getOSContextProvider(). This interface can be found in package com.arm.debug.extension.os.context within the same JAR file as IOSProvider mentioned earlier.

Given the following C types for myos tasks:

typedef enum {
    UNINITIALIZED = 0,
    READY
} tstatus_t ;

typedef struct {
    uint32_t           id;
    char               *name;
    volatile tstatus_t status;
    uint32_t           stack[STACK_SIZE];
    uint32_t           *sp;
} task_t;

And assuming the OS always stores the currently running task at the first element of the tasks array, further callbacks can be implemented to return the currently running (or scheduled) task and all the tasks (both scheduled and unscheduled) in a new contexts.py file:

<some folder>
    /mydb
        /OS
            /myos
                /extension.xml
                /messages.properties
                /provider.py
                /contexts.py
  • provider.py
    # this script implements the Java interface IOSProvider
    from osapi import DebugSessionException
    from contexts import ContextsProvider
    
    def areOSSymbolsLoaded(debugger):
        […]
    
    def isOSInitialised(debugger):
        […]
    
    def getOSContextProvider():
        # returns an instance of the Java interface IOSContextProvider
        return ContextsProvider()
    
    def getDataModel():
        […]
  • contexts.py
    from osapi import ExecutionContext
    from osapi import ExecutionContextsProvider
    
    # this class implements the Java interface IOSContextProvider
    class ContextsProvider(ExecutionContextsProvider):
        def getCurrentOSContext(self, debugger):
            task = debugger.evaluateExpression("tasks[0]")
            return self.createContext(debugger, task)
            
        def getAllOSContexts(self, debugger):
            tasks = debugger.evaluateExpression("tasks").getArrayElements()
            contexts = []
            for task in tasks:
                if task.getStructureMembers()["status"].readAsNumber() > 0:
                    contexts.append(self.createContext(debugger, task))
            return contexts
            
        def getOSContextSavedRegister(self, debugger, context, name):
            return None
    
        def createContext(self, debugger, task):
            members = task.getStructureMembers()
            id = members["id"].readAsNumber()
            name = members["name"].readAsNullTerminatedString()
            context = ExecutionContext(id, name, None)
            return context

Although getOSContextSavedRegister() is not yet implemented, this is enough for the debugger to now populate the Debug Control view with the OS tasks as soon as the OS awareness is enabled:

Figure 14-7 myos Debug Control view data
myos Debug Control view data


Decoding the call stack of the currently running task and inspecting local variables at specific stack frames for that task works without further changes since the task's registers values are read straight from the core's registers. For unscheduled tasks, however, getOSContextSavedRegister() must be implemented to read the registers values saved by the OS on switching contexts. How to read those values depends entirely on the OS context switching logic.

Here is the implementation for myos, based on a typical context switching routine for M-class ARM processors where registers are pushed onto the stack when a task is switched out by the OS scheduler:

from osapi import ExecutionContext
from osapi import ExecutionContextsProvider

STACK_POINTER = "stack pointer"
REGISTER_OFFSET_MAP = {"R4":0L,    "R5":4L,  "R6":8L,   "R7":12L,
                       "R8":16L,   "R9":20L, "R10":24L, "R11":28L,
                       "R0":32L,   "R1":36L, "R2":40L,  "R3":44L,
                       "R12":48L,  "LR":52L, "PC":56L,  "XPSR":60L,
                       "SP":64L}

# this class implements the Java interface IOSContextProvider
class ContextsProvider(ExecutionContextsProvider):

    def getCurrentOSContext(self, debugger):
        […]

    def getAllOSContexts(self, debugger):
        […]

    def getOSContextSavedRegister(self, debugger, context, name):
        offset = REGISTER_OFFSET_MAP.get(name)
        base = context.getAdditionalData()[STACK_POINTER]
        addr = base.addOffset(offset)

        if name == "SP":
            # SP itself isn’t pushed onto the stack: return its computed value
            return debugger.evaluateExpression("(long)" + str(addr))
        else:
            # for any other register, return the value at the computed address
            return debugger.evaluateExpression("(long*)" + str(addr))

    def createContext(self, debugger, task):
        members = task.getStructureMembers()
        id = members["id"].readAsNumber()
        name = members["name"].readAsNullTerminatedString()
        context = ExecutionContext(id, name, None)
        # record the stack address for this task in the context’s 
        # additional data; this saves having to look it up later in 
        # getOSContextSavedRegister()
        stackPointer = members["sp"].readAsAddress()
        context.getAdditionalData()[STACK_POINTER] = stackPointer
        
        return context

The debugger can now get the values of saved registers, allowing unwinding the stack of unscheduled tasks.

Note:

Enter info threads in the Commands view to display similar information as displayed in the Debug Control view.
Non-ConfidentialPDF file icon PDF versionARM DUI0446Z
Copyright © 2010-2016 ARM Limited or its affiliates. All rights reserved.