# Copyright 2025 NXP
#
# NXP Confidential and Proprietary. This software is owned or controlled by NXP and may only
# be used strictly in accordance with the applicable license terms.
#
# By expressly accepting such terms or by downloading, installing, activating
# and/or otherwise using the software, you are agreeing that you have read,
# and that you agree to comply with and are bound by, such license terms.
#
# If you do not agree to be bound by the applicable license terms, then you
# may not retain, install, activate or otherwise use the software.

import struct
import gdb
import sys
import os
import json

import mem_reg_utils as utils

from errors import Result

# Enable this to see commands and results
fr_debug_on = False

# Enable this to see the performance
fr_debug_perf = False
fr_nested_calls = 0

# GDB object cache references
fr_object_cache = {}

# Used to know what is the current thread TCB
fr_current_thread = None

# Check if that object is already in the GDB cache
def ref_get_cache(addr):
    global fr_object_cache
    if fr_object_cache:
        if str(addr) in fr_object_cache:
            return fr_object_cache[str(addr)]

    return None

# Add new object reference in the cache
def ref_add_cache(addr, ref):
    global fr_object_cache
    if ref:
        fr_object_cache[str(addr)] = ref

# Reset the GDB cache
def ref_reset_cache():
    global fr_object_cache

    if fr_object_cache:
        fr_object_cache.clear()

# This is used to meassure the call performance
def perf_monitor(func):
    def wrapper(*args, **kwargs):
        global fr_nested_calls
        global fr_debug_perf

        if not fr_debug_perf:
            return func(*args, **kwargs)

        fr_nested_calls += 1

        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()

        fr_nested_calls -= 1

        ident = ""
        for i in range(0, fr_nested_calls):
            ident += "."

        fr_debug(
            "{0}{1}{2}:{3:.3f} s".format(
                ident, func.__name__, args, end_time - start_time
            ),
            True,
        )
        return result
    return wrapper

def int64(s):
    # pylint: disable=all
    if sys.version_info.major == 2:
        return long(s)
    else:
        return int(s)

def fr_debug(s, forced=False):
    if fr_debug_on or forced:
        gdb.write(str(s))
        gdb.write("\n")

@perf_monitor
def fr_run_gdb_cmd(cmd):
    fr_debug(cmd)
    result = utils.gdb_exec(cmd)
    if result.isok():
        value = result.ok.value
        fr_debug(value)
        return value
    else:
        value = result.error.__str__()
        if "Error:No symbol" not in value:
            fr_debug(value)
        raise ValueError(value)
    
# FreeRTOS memory structure offsets - adjust based on your FreeRTOS version
class FreeRTOSOffsets:
    """FreeRTOS offsets for various structures"""
    
    def __init__(self):
        # TCB offsets
        self.STACK_PTR_OFFSET = 0    # Offset to pxTopOfStack
        self.PRIORITY_OFFSET = 0x2C  # Offset to uxPriority
        self.NAME_OFFSET = 0x34      # Offset to pcTaskName
        
        # List_t structure
        self.XLIST_T_OFFSET = 0x14        # Size of List_t structure
        self.XLIST_NUMBER_OF_ITEMS_OFFSET = 0  # Offset to uxNumberOfItems in List_t
        self.XLIST_INDEX_OFFSET = 0x4     # Offset to pxIndex in List_t
        self.XLIST_END_OFFSET = 0x8       # Offset to xListEnd in List_t
        self.XLIST_END_NEXT_OFFSET = 0xC
        
        # ListItem_t structure
        self.XLIST_ITEM_VALUE_OFFSET = 0      # Offset to xItemValue in ListItem_t
        self.XLIST_NEXT_OFFSET = 0x4          # Offset to pxNext in ListItem_t
        self.XLIST_PREV_OFFSET = 0x8          # Offset to pxPrevious in ListItem_t
        self.XLIST_ITEM_OWNER_OFFSET = 0xC    # Offset to pvOwner in ListItem_t
        self.XLIST_ITEM_PVCONT_OFFSET = 0x14  # Offset to pvContainer in ListItem_t
        self.XLIST_ITEM_SIZE = 0x10           # Size of ListItem_t structure
        
        # Queue structure offsets
        self.QUEUE_REGISTRY_NAME_OFFSET = 0x0     # pcQueueName
        self.QUEUE_REGISTRY_HANDLE_OFFSET = 0x4   # xHandle
        self.QUEUE_REGISTRY_ITEM_SIZE = 8         # 
        self.QUEUE_MUTEX_HOLDER_OFFSET = 0x8      # u.xSemaphore.xMutexHolder
        self.QUEUE_TASKS_WAITING_TO_SEND_OFFSET = 0x10   # xTasksWaitingToSend
        self.QUEUE_TASKS_WAITING_TO_RECEIVE_OFFSET = 0x24 # xTasksWaitingToReceive  
        self.QUEUE_MESSAGES_WAITING_OFFSET = 0x38  # uxMessagesWaiting
        self.QUEUE_LENGTH_OFFSET = 0x3C           # uxLength
        self.QUEUE_ITEM_SIZE_OFFSET = 0x40        # uxItemSize
        
        # Timer structure offsets (from GDB output)
        self.TIMER_NAME_OFFSET = 0x0           # pcTimerName
        self.TIMER_LIST_ITEM_OFFSET = 0x4      # xTimerListItem
        self.TIMER_PERIOD_OFFSET = 0x18        # xTimerPeriodInTicks
        self.TIMER_ID_OFFSET = 0x1c            # pvTimerID
        self.TIMER_CALLBACK_OFFSET = 0x20      # pxCallbackFunction
        self.TIMER_NUMBER_OFFSET = 0x24        # uxTimerNumber
        self.TIMER_STATUS_OFFSET = 0x28        # ucStatus
        
        # Timer status bit definitions (from FreeRTOS source)
        self.TIMER_STATUS_IS_ACTIVE = 0x01        # Bit 0: timer is active
        self.TIMER_STATUS_IS_STATICALLY_ALLOCATED = 0x02  # Bit 1: statically allocated
        self.TIMER_STATUS_IS_AUTORELOAD = 0x04    # Bit 2: auto-reload timer
        
        # Constants
        self.CONFIG_THREAD_MAX_NAME_LEN = 16  # Maximum length of task name
        
        # TCB offsets (multi-core SMP) - adjusted based on actual structure
        self.SMP_STACK_PTR_OFFSET = 0x00        # Offset to pxTopOfStack
        self.SMP_CORE_AFFINITY_OFFSET = 0x04    # Offset to uxCoreAffinityMask
        self.SMP_STATE_LIST_ITEM_OFFSET = 0x08  # Offset to xStateListItem
        self.SMP_EVENT_LIST_ITEM_OFFSET = 0x1C  # Offset to xEventListItem
        self.SMP_PRIORITY_OFFSET = 0x30         # Offset to uxPriority
        self.SMP_STACK_OFFSET = 0x34            # Offset to pxStack
        self.SMP_TASK_RUN_STATE_OFFSET = 0x38   # Offset to xTaskRunState
        self.SMP_TASK_ATTRIBUTES_OFFSET = 0x3C  # Offset to uxTaskAttributes
        self.SMP_NAME_OFFSET = 0x40             # Offset to pcTaskName

# Constants
FREERTOS_TARGET_NULL = 0
ARM_FREERTOS_REGS_NUM = 26

# Base class for all kernel objects
class KernelObject(object):
    """Base class for FreeRTOS kernel objects"""
    
    def __init__(self, address, freertos_instance=None):
        """Initialize a kernel object with the given address"""
        self.address = address
        
        # Store FreeRTOS instance
        if freertos_instance is None:
            self.freertos = FreeRTOS()
        else:
            self.freertos = freertos_instance
            
        # Get offsets from FreeRTOS instance
        self.offsets = self.freertos.offsets
        
        # Basic properties all kernel objects should have
        self.obj_id = address
        self.obj_name = "unnamed"
        self.obj_type = "unknown"
        self._filled = False
    
    def get_address(self):
        """Get the memory address of this kernel object"""
        return self.address
    
    def get_type(self):
        """Get the type of this kernel object"""
        return self.obj_type
    
    def get_name(self):
        """Get the name of this kernel object if available"""
        return self.obj_name
    
    def fill_object(self):
        """Fill the object with data from the target"""
        self._filled = True
        return True
    
    def get_info(self):
        """Get a dictionary with basic information about this object"""
        if not self._filled:
            self.fill_object()
            
        return {
            "address": self.address,
            "type": self.get_type(),
            "name": self.get_name()
        }
    
    def __str__(self):
        """String representation of this kernel object"""
        if not self._filled:
            self.fill_object()
        return "{0} {1} at 0x{2:08x}".format(self.get_type(), self.get_name(), self.address)


class Thread(KernelObject):
    """Class to represent a thread in FreeRTOS"""
    
    def __init__(self, address, rtos):
        super().__init__(address, rtos)
        self.index = 0
        self.name = None
        self.priority = None
        self.state = "Unknown"
        self.stack_ptr = None
        self.rtos = rtos
        
    def fill_object(self):
        self.get_name()
        self.get_priority()
        self.get_state()
        self.get_stack_ptr()
        
    def get_name(self):
        if self.name is not None:
            return self.name            
        try:
            # Read the name from the TCB
            name_addr = self.address + self.rtos.offsets.NAME_OFFSET
            
            # Read 4 bytes at a time to get the name
            name = ""
            for i in range(4):  # Read up to 16 bytes (4 words)
                word_addr = name_addr + (i * 4)
                word_bytes = self.rtos.mem_read(word_addr, 4)
                if word_bytes:
                    # Convert the bytes to a word
                    word = struct.unpack("<I", word_bytes)[0]
                    
                    # Extract each character from the word (little-endian)
                    for j in range(4):
                        char = (word >> (j * 8)) & 0xFF
                        if char == 0:  # Null terminator
                            break
                        if 32 <= char <= 126:  # Printable ASCII
                            name += chr(char)
                    
                    if word == 0:  # End of string
                        break
                else:
                    break
            
            if name:
                self.name = name
                return self.name
        except Exception as e:
            fr_debug("Error getting thread name: {0}".format(str(e)))
        
        # If we get here, we couldn't get a valid name
        self.name = "Task-{0}".format(self.index)
        return self.name
        
    def get_priority(self):
        """Get the priority of the thread"""
        if self.priority is not None:
            return self.priority
            
        try:
            # Read the priority from the TCB
            prio_addr = self.address + self.rtos.offsets.PRIORITY_OFFSET
            prio_bytes = self.rtos.mem_read(prio_addr, 4)
            if prio_bytes:
                self.priority = struct.unpack("<I", prio_bytes)[0]
        except Exception as e:
            fr_debug("Error getting thread priority: {0}".format(str(e)))
            self.priority = 0
            
        return self.priority
        
    def get_state(self):
        """Get the state of the thread"""
        # In FreeRTOS, the state is determined by which list the thread is in
        # For now, just check if it's the current thread
        self.state = self.rtos.determine_thread_state(self.address)
        return self.state
    
    def get_stack_ptr(self):
        """Get the stack pointer of the thread"""
        if self.stack_ptr is not None:
            return self.stack_ptr
            
        try:
            # Read the stack pointer from the TCB
            sp_addr = self.address + self.rtos.offsets.STACK_PTR_OFFSET
            sp_bytes = self.rtos.mem_read(sp_addr, 4)
            if sp_bytes:
                self.stack_ptr = struct.unpack("<I", sp_bytes)[0]
        except Exception as e:
            fr_debug("Error getting thread stack pointer: {0}".format(str(e)))
            self.stack_ptr = 0
            
        return self.stack_ptr
        
    def get_info(self):
        """Get information about the thread as a dictionary"""
        return {
            "address": "0x{0:08x}".format(self.address),
            "name": self.get_name(),
            "priority": self.get_priority(),
            "state": self.get_state(),
            "stack_ptr": "0x{0:08x}".format(self.get_stack_ptr())
        }
        
    def __str__(self):
        """String representation of the thread"""
        return "Thread {0}: {1} (0x{2:08x}), Priority: {3}, State: {4}".format(self.index, self.get_name(), self.address, self.get_priority(), self.get_state())


# Threads (for multi-core SMP) - this is necessary because a different structure is used to store information about each individual Task
class Threads(KernelObject):
    """Class to represent a thread in FreeRTOS (multi-core SMP)"""
    
    def __init__(self, address, rtos, core_id=None):
        super().__init__(address, rtos)
        self.index = 0
        self.name = None
        self.priority = None
        self.state = "Unknown"
        self.stack_ptr = None
        self.core_affinity = None
        self.task_run_state = None
        self.core_id = core_id  # Which core this task is associated with
        self.rtos = rtos
        
    def fill_object(self):
        self.get_name()
        self.get_priority()
        self.get_state()
        self.get_stack_ptr()
        self.get_core_affinity()
        self.get_task_run_state()
        
    def get_name(self):
        if self.name is not None:
            return self.name            
        try:
            # Read the name from the TCB (SMP offset)
            name_addr = self.address + self.rtos.offsets.SMP_NAME_OFFSET
            
            # Read the name byte by byte (pcTaskName[10])
            name = ""
            for i in range(10):  # pcTaskName is 10 bytes in this structure
                byte_data = self.rtos.mem_read(name_addr + i, 1)
                if not byte_data:
                    break
                # Handle both Python 2 and 3
                if sys.version_info[0] == 2:
                    byte_val = ord(byte_data[0])
                else:
                    byte_val = byte_data[0] if isinstance(byte_data[0], int) else ord(byte_data[0])
                
                if byte_val == 0:  # Null terminator
                    break
                if 32 <= byte_val <= 126:  # Printable ASCII
                    name += chr(byte_val)
            
            if name:
                self.name = name
                return self.name
                
        except Exception as e:
            fr_debug("Error getting thread name: {0}".format(str(e)))
        
        # If we get here, we couldn't get a valid name
        self.name = "Task-{0}".format(self.index)
        return self.name
        
    def get_priority(self):
        """Get the priority of the thread"""
        if self.priority is not None:
            return self.priority
            
        try:
            # Read the priority from the TCB (SMP offset)
            prio_addr = self.address + self.rtos.offsets.SMP_PRIORITY_OFFSET
            prio_bytes = self.rtos.mem_read(prio_addr, 4)
            if prio_bytes:
                self.priority = struct.unpack("<I", prio_bytes)[0]
        except Exception as e:
            fr_debug("Error getting thread priority: {0}".format(str(e)))
            self.priority = 0
            
        return self.priority
        
    def get_core_affinity(self):
        """Get the core affinity mask of the thread"""
        if self.core_affinity is not None:
            return self.core_affinity
            
        try:
            # Read the core affinity from the TCB
            affinity_addr = self.address + self.rtos.offsets.SMP_CORE_AFFINITY_OFFSET
            affinity_bytes = self.rtos.mem_read(affinity_addr, 4)
            if affinity_bytes:
                self.core_affinity = struct.unpack("<I", affinity_bytes)[0]
        except Exception as e:
            fr_debug("Error getting thread core affinity: {0}".format(str(e)))
            self.core_affinity = 0
            
        return self.core_affinity
        
    def get_task_run_state(self):
        """Get the task run state (which core it's running on, or -1/-2 if not running)"""
        if self.task_run_state is not None:
            return self.task_run_state
            
        try:
            # Read the task run state from the TCB
            run_state_addr = self.address + self.rtos.offsets.SMP_TASK_RUN_STATE_OFFSET
            run_state_bytes = self.rtos.mem_read(run_state_addr, 4)
            if run_state_bytes:
                self.task_run_state = struct.unpack("<i", run_state_bytes)[0]  # signed int
        except Exception as e:
            fr_debug("Error getting thread run state: {0}".format(str(e)))
            self.task_run_state = -1
            
        return self.task_run_state
        
    def get_state(self):
        """Get the state of the thread"""
        # In FreeRTOS SMP, check the task run state first
        run_state = self.get_task_run_state()
        
        if run_state >= 0:
            self.state = "Running (Core {0})".format(run_state)
        else:
            # Check which list the task is in
            self.state = self.rtos.determine_thread_state(self.address, is_smp=True)
            
        return self.state
    
    def get_stack_ptr(self):
        """Get the stack pointer of the thread"""
        if self.stack_ptr is not None:
            return self.stack_ptr
            
        try:
            # Read the stack pointer from the TCB (SMP offset is the same)
            sp_addr = self.address + self.rtos.offsets.SMP_STACK_PTR_OFFSET
            sp_bytes = self.rtos.mem_read(sp_addr, 4)
            if sp_bytes:
                self.stack_ptr = struct.unpack("<I", sp_bytes)[0]
        except Exception as e:
            fr_debug("Error getting thread stack pointer: {0}".format(str(e)))
            self.stack_ptr = 0
            
        return self.stack_ptr
        
    def get_info(self):
        """Get information about the thread as a dictionary"""
        info = {
            "address": "0x{0:08x}".format(self.address),
            "name": self.get_name(),
            "priority": self.get_priority(),
            "state": self.get_state(),
            "stack_ptr": "0x{0:08x}".format(self.get_stack_ptr()),
            "core_affinity": self.get_core_affinity(),
            "task_run_state": self.get_task_run_state()
        }
        
        if self.core_id is not None:
            info["core_id"] = self.core_id
            
        return info
        
    def __str__(self):
        """String representation of the thread"""
        return "Thread {0}: {1} (0x{2:08x}), Priority: {3}, State: {4}".format(
            self.index, self.get_name(), self.address, self.get_priority(), self.get_state()
        )


class Queue(KernelObject):
    """Class to represent a queue in FreeRTOS"""
    
    def __init__(self, address, rtos):
        super().__init__(address, rtos)
        self.rtos = rtos  # Store the RTOS object directly
        self.index = 0
        self.name = None
        self.length = None
        self.items_waiting = None
        self.item_size = None
        self.mutex_holder = None
        self.send_waiting_tasks = []
        self.receive_waiting_tasks = []
        
    def fill_object(self):
        """Fill the object with data from the target"""
        try:
            # Get the queue length
            length_addr = self.address + self.rtos.offsets.QUEUE_LENGTH_OFFSET
            length_bytes = self.rtos.mem_read(length_addr, 4)
            if length_bytes:
                self.length = struct.unpack("<I", length_bytes)[0]
            
            # Get the items waiting (messages waiting)
            items_waiting_addr = self.address + self.rtos.offsets.QUEUE_MESSAGES_WAITING_OFFSET
            items_waiting_bytes = self.rtos.mem_read(items_waiting_addr, 4)
            if items_waiting_bytes:
                self.items_waiting = struct.unpack("<I", items_waiting_bytes)[0]
            
            # Get the item size
            item_size_addr = self.address + self.rtos.offsets.QUEUE_ITEM_SIZE_OFFSET
            item_size_bytes = self.rtos.mem_read(item_size_addr, 4)
            if item_size_bytes:
                self.item_size = struct.unpack("<I", item_size_bytes)[0]
            
            # Get the mutex holder
            mutex_holder_addr = self.address + self.rtos.offsets.QUEUE_MUTEX_HOLDER_OFFSET
            mutex_holder_bytes = self.rtos.mem_read(mutex_holder_addr, 4)
            if mutex_holder_bytes:
                self.mutex_holder = struct.unpack("<I", mutex_holder_bytes)[0]
            
            # Get the tasks waiting to send
            send_waiting_list_addr = self.address + self.rtos.offsets.QUEUE_TASKS_WAITING_TO_SEND_OFFSET
            self._fill_waiting_tasks_list(send_waiting_list_addr, self.send_waiting_tasks)
            
            # Get the tasks waiting to receive
            receive_waiting_list_addr = self.address + self.rtos.offsets.QUEUE_TASKS_WAITING_TO_RECEIVE_OFFSET
            self._fill_waiting_tasks_list(receive_waiting_list_addr, self.receive_waiting_tasks)
                                
            self._filled = True
        except Exception as e:
            fr_debug("Error filling queue object: {0}".format(str(e)))

    def _fill_waiting_tasks_list(self, list_addr, task_list):
        """Helper method to fill a list of waiting tasks"""
        try:
            # Read the number of items in the list
            num_items_addr = list_addr + self.rtos.offsets.XLIST_NUMBER_OF_ITEMS_OFFSET
            num_items_bytes = self.rtos.mem_read(num_items_addr, 4)
            if not num_items_bytes:
                return
                
            num_items = struct.unpack("<I", num_items_bytes)[0]
            
            if num_items > 0:
                # Get the list end address
                list_end_addr = list_addr + self.rtos.offsets.XLIST_END_OFFSET
                
                # Get the first item in the list
                next_item_addr = list_end_addr + self.rtos.offsets.XLIST_NEXT_OFFSET
                next_item_bytes = self.rtos.mem_read(next_item_addr, 4)
                if next_item_bytes:
                    list_item = struct.unpack("<I", next_item_bytes)[0]
                    
                    # Traverse the list to find all tasks
                    visited = set()
                    while list_item != list_end_addr and list_item not in visited:
                        visited.add(list_item)
                        
                        # Get the owner (TCB) of this list item
                        owner_addr = list_item + self.rtos.offsets.XLIST_ITEM_OWNER_OFFSET
                        owner_bytes = self.rtos.mem_read(owner_addr, 4)
                        if owner_bytes:
                            owner = struct.unpack("<I", owner_bytes)[0]
                            if owner != 0:
                                task_list.append(owner)
                        
                        # Move to the next item
                        next_item_addr = list_item + self.rtos.offsets.XLIST_NEXT_OFFSET
                        next_item_bytes = self.rtos.mem_read(next_item_addr, 4)
                        if not next_item_bytes:
                            break
                        list_item = struct.unpack("<I", next_item_bytes)[0]
                        
                        # Safety check to avoid infinite loops
                        if len(visited) > 100:
                            break
        except Exception as e:
            fr_debug("Error filling waiting tasks list: {0}".format(str(e)))

    def get_length(self):
        """Get the length of the queue"""
        if self.length is not None:
            return self.length
            
        self.fill_object()
        return self.length or 0
        
    def get_items_waiting(self):
        """Get the number of items waiting in the queue"""
        if self.items_waiting is not None:
            return self.items_waiting
            
        self.fill_object()
        return self.items_waiting or 0
        
    def get_item_size(self):
        """Get the size of items in the queue"""
        if self.item_size is not None:
            return self.item_size
            
        self.fill_object()
        return self.item_size or 0
        
    def get_mutex_holder(self):
        """Get the mutex holder of the queue"""
        if self.mutex_holder is not None:
            return self.mutex_holder
            
        self.fill_object()
        return self.mutex_holder or 0
        
    def get_info(self):
        """Get information about the queue as a dictionary"""
        return {
            "address": "0x{0:08x}".format(self.address),
            "name": self.name if self.name else "Queue {0}".format(self.index),
            "length": self.get_length(),
            "items_waiting": self.get_items_waiting(),
            "item_size": self.get_item_size(),
            "mutex_holder": "0x{0:08x}".format(self.get_mutex_holder()) if self.get_mutex_holder() else "None",
            "send_waiting_tasks": ["0x{0:08x}".format(task) for task in self.send_waiting_tasks],
            "receive_waiting_tasks": ["0x{0:08x}".format(task) for task in self.receive_waiting_tasks]
        }
        
    def __str__(self):
        """String representation of the queue"""
        name_str = "{0}".format(self.name) if self.name else "Queue {0}".format(self.index)
        
        # Get queue information
        length = self.get_length()
        items_waiting = self.get_items_waiting()
        item_size = self.get_item_size()
        
        # Format the output
        output = "{0}: (0x{1:08x})\n".format(name_str, self.address)
        output += "  Length: {0}, Items Waiting: {1}, Item Size: {2} bytes\n".format(length, items_waiting, item_size)
        # Add information about tasks waiting to send
        if self.send_waiting_tasks:
            output += "  Tasks Waiting to Send: {0}\n".format(len(self.send_waiting_tasks))
            for i, task in enumerate(self.send_waiting_tasks):
                output += "    Task {0}: 0x{1:08x}\n".format(i+1, task)
        else:
            output += "  No Tasks Waiting to Send\n"
        
        # Add information about tasks waiting to receive
        if self.receive_waiting_tasks:
            output += "  Tasks Waiting to Receive: {0}\n".format(len(self.receive_waiting_tasks))
            for i, task in enumerate(self.receive_waiting_tasks):
                output += "    Task {0}: 0x{1:08x}\n".format(i+1, task)
        else:
            output += "  No Tasks Waiting to Receive\n"
        
        return output

class Timer(KernelObject):
    """Class to represent a timer in FreeRTOS"""
    
    def __init__(self, address, rtos):
        super().__init__(address, rtos)
        self.rtos = rtos  # Store the RTOS object directly
        self.index = 0
        self.name = None
        self.period = None
        self.id = None
        self.callback = None
        self.number = None      # Timer number (for tracing)
        self.status = None      # Timer status byte
        self.auto_reload = None # Auto-reload flag (decoded from status)
        self.is_active = None   # Active flag (decoded from status)
        self.is_static = None   # Static allocation flag (decoded from status)
        
    def fill_object(self):
        """Fill the object with data from the target using direct memory access"""
        try:
            # Read the timer name pointer (pcTimerName at offset 0x0)
            name_ptr_addr = self.address + self.rtos.offsets.TIMER_NAME_OFFSET
            name_ptr_bytes = self.rtos.mem_read(name_ptr_addr, 4)
            if name_ptr_bytes:
                name_ptr = struct.unpack("<I", name_ptr_bytes)[0]
                if name_ptr != 0:
                    # Read the actual name string
                    self.name = self.rtos.read_string(name_ptr, 32)
            
            # Read the timer period (xTimerPeriodInTicks at offset 0x18)
            period_addr = self.address + self.rtos.offsets.TIMER_PERIOD_OFFSET
            period_bytes = self.rtos.mem_read(period_addr, 4)
            if period_bytes:
                self.period = struct.unpack("<I", period_bytes)[0]
            
            # Read the timer ID (pvTimerID at offset 0x1c)
            id_addr = self.address + self.rtos.offsets.TIMER_ID_OFFSET
            id_bytes = self.rtos.mem_read(id_addr, 4)
            if id_bytes:
                self.id = struct.unpack("<I", id_bytes)[0]
            
            # Read the timer callback (pxCallbackFunction at offset 0x20)
            callback_addr = self.address + self.rtos.offsets.TIMER_CALLBACK_OFFSET
            callback_bytes = self.rtos.mem_read(callback_addr, 4)
            if callback_bytes:
                self.callback = struct.unpack("<I", callback_bytes)[0]
            
            # Read the timer number (uxTimerNumber at offset 0x24)
            number_addr = self.address + self.rtos.offsets.TIMER_NUMBER_OFFSET
            number_bytes = self.rtos.mem_read(number_addr, 4)
            if number_bytes:
                self.number = struct.unpack("<I", number_bytes)[0]
            
            # Read the timer status (ucStatus at offset 0x28)
            status_addr = self.address + self.rtos.offsets.TIMER_STATUS_OFFSET
            status_bytes = self.rtos.mem_read(status_addr, 1)  # uint8_t is 1 byte
            if status_bytes:
                self.status = struct.unpack("<B", status_bytes)[0]  # B for unsigned char
                
                # Parse status bits (FreeRTOS timer status bits):
                # Bit 0: Timer is active
                # Bit 1: Timer is static (vs dynamic)  
                # Bit 2: Timer is auto-reload (vs one-shot)
                self.is_active = bool(self.status & 0x01)
                self.is_static = bool(self.status & 0x02)
                self.auto_reload = bool(self.status & 0x04)

        except Exception as e:
            fr_debug("Error filling timer object with direct memory access: {0}".format(str(e)))

    def get_name(self):
        """Get the name of the timer"""
        if self.name is not None:
            return self.name
            
        self.fill_object()
        return self.name or "Timer-{0}".format(self.index)
        
    def get_period(self):
        """Get the period of the timer"""
        if self.period is not None:
            return self.period
            
        self.fill_object()
        return self.period or 0
        
    def get_id(self):
        """Get the ID of the timer"""
        if self.id is not None:
            return self.id
            
        self.fill_object()
        return self.id or 0
        
    def get_callback(self):
        """Get the callback function of the timer"""
        if self.callback is not None:
            return self.callback
            
        self.fill_object()
        return self.callback or 0

    def get_info(self):
        """Get information about the timer as a dictionary"""
        info = {
            "address": "0x{0:08x}".format(self.address),
            "name": self.get_name() if self.get_name() else "Timer {0}".format(self.index),
            "period": self.get_period(),
            "id": "0x{0:08x}".format(self.get_id()) if self.get_id() else "None",
            "callback": "0x{0:08x}".format(self.get_callback()) if self.get_callback() else "None",
            "number": self.number if self.number is not None else "N/A",
            "auto_reload": self.auto_reload if self.auto_reload is not None else "Unknown",
            "is_active": self.is_active if self.is_active is not None else "Unknown",
            "is_static": self.is_static if self.is_static is not None else "Unknown"
        }
        
        # Add variable name if available
        if hasattr(self, 'variable_name'):
            info["variable_name"] = self.variable_name
        
        return info

    def __str__(self):
        """String representation of the timer"""
        # Use the actual timer name (from xTimerCreate) if available, otherwise use variable name
        if self.get_name():
            name_str = self.get_name()
        elif hasattr(self, 'variable_name'):
            name_str = self.variable_name
        else:
            name_str = "Timer {0}".format(self.index)
        
        # Get timer information
        period = self.get_period()
        timer_id = self.get_id()
        callback = self.get_callback()
        
        # Format the output
        output = "{0}: (0x{1:08x})".format(name_str, self.address)
        
        # Add variable name if it's different from the timer name
        if hasattr(self, 'variable_name') and self.name and self.name != self.variable_name:
            output += " [var: {0}]".format(self.variable_name)
        
        output += "\n  Period: {0} ticks\n".format(period)
        output += "  ID: 0x{0:08x}\n".format(timer_id)
        output += "  Callback: 0x{0:08x}\n".format(callback)
        
        if self.auto_reload is not None:
            output += "  Auto-reload: {0}\n".format('Yes' if self.auto_reload else 'No')
        if self.is_active is not None:
            output += "  Active: {0}\n".format('Yes' if self.is_active else 'No')
        
        return output

class RTOS(object):
    def __init__(self):
        self.supported_kernel_types = []

    @perf_monitor
    def detect(self, fast_mode=True):
        raise NotImplementedError("Not supported")

    @perf_monitor
    def symbol_exists(self, sym_name):
        try:
            if "No symbol" not in fr_run_gdb_cmd("p/d &" + sym_name):
                return True
        except:
            fr_debug("Symbol {} not found".format(sym_name))

        return False

    # List all kernel objects supported
    def list_supported_types(self, json_format=False):
        list = []
        s = {}

        detect, descr = self.detect(fast_mode=False)

        if not detect:
            s["error"] = descr
        else:
            for t in self.supported_kernel_types:
                list.append(t)

            s["types"] = list

        if json_format:
            return self.print_json(s)
        else:
            return str(list)

    def print_json(self, dict):
        return json.dumps(dict, indent=4, sort_keys=True)

    def get_symbol_value(self, symbol_name):
        """Get the value of a symbol from GDB"""
        try:
            value = gdb.parse_and_eval(symbol_name)
            return int(value)
        except Exception as e:
            fr_debug("Error getting symbol: {0}".format(str(symbol_name)))
            fr_debug(e)
            return 0
            
    @perf_monitor
    def list_objects(self, str_types=[], json_format=False):
        """List objects of the specified type"""
        return 
        
class FreeRTOS(RTOS):
    """Main class for FreeRTOS thread awareness"""
    def __init__(self):
        RTOS.__init__(self)
        self.sym_dependencies = {
            "Thread": "pxCurrentTCB",
            "Threads": "pxCurrentTCBs",
            "Queue": "xQueueRegistry",
            "Timer": "xTimerQueue"
        }
        self.offsets = FreeRTOSOffsets()
        self.current_tcb_addr = FREERTOS_TARGET_NULL
        self.threads = []
        self.tasks_lists = []
        self.ready_tasks = 0
        self._scheduler_active = None
        self.is_smp = False  # Flag to track if SMP is detected
        self.current_tcbs = []  # List of current TCBs for each
        
    def set_debug(self, on=False):
        global fr_debug_on
        fr_debug_on = on

    def set_perf(self, on=False):
        global fr_debug_perf
        fr_debug_perf = on

    def no_sym_check(self, sym, filters):
        for f in filters:
            if sym is f:
                return False

        return True

    def mem_read(self, address, size):
        """
        Read memory from the target
        
        Args:
            address (int): Address to read from
            size (int): Number of bytes to read
        
        Returns:
            bytes: The bytes read from memory, or None if the read failed
        """
        try:    
            # Use GDB's inferior.read_memory to read memory
            inferior = gdb.selected_inferior()
            data = inferior.read_memory(address, size)
            # Ensure we get bytes in both Python 2 and 3
            if sys.version_info[0] == 2:
                return str(data)
            else:
                return bytes(data)
        except Exception as e:
            fr_debug("Error reading memory at 0x{:08x}: {}".format(address, str(e)))
            return None
        
    @perf_monitor
    def detect(self, fast_mode=True):
        fr_run_gdb_cmd("set print null-stop")

        if self.supported_kernel_types:
            return (True, "")
    
        # Only one of these symbols must exist
        symbol_exists = False
        if self.symbol_exists("pxCurrentTCB"):
            symbol_exists = True
        elif self.symbol_exists("pxCurrentTCBs"):
            symbol_exists = True   
        
        if symbol_exists:
            fr_debug("FreeRTOS OS detected")
        else:
            return (symbol_exists, "FreeRTOS OS not detected")

        if fast_mode:
            return (True, "")

        # Detect the supported objects
        for k in self.sym_dependencies.keys():
            if self.symbol_exists(self.sym_dependencies[k]):
                fr_debug("Object type {} supported".format(k))
                self.supported_kernel_types.append(str(k))

        if not self.supported_kernel_types:
            return (False, "No configuration debug thread information enabled.")
        else:
            fr_debug(
                "Number of object types found: {}".format(
                    len(self.supported_kernel_types)
                )
            )
    
        return (True, "")

    @perf_monitor
    def list_objects(self, types, json_format=False):
        """List objects of the specified types
        
        Args:
            types (list): List of object types to list
            json_format (bool): Whether to return JSON format
            
        Returns:
            str: String representation of the objects
        """
        result = {}
        
        # Get all threads if 'Thread' or 'Threads' is in types
        if 'Thread' in types or 'Threads' in types:
            threads = self.get_all_threads()
            # Use the appropriate key based on SMP mode
            if self.is_smp:
                result['Threads'] = threads
            else:
                result['Thread'] = threads
        
        # Get all queues if 'Queue' is in types
        if 'Queue' in types:
            queues = self.get_all_queues()
            result['Queue'] = queues
        
        # Get all timers if 'Timer' is in types
        if 'Timer' in types:
            timers = self.get_all_timers()
            result['Timer'] = timers
        
        # Convert the result to a string
        if json_format:
            # Convert to JSON format
            import json
            
            # Convert objects to dictionaries
            json_data = {}
            for obj_type, obj_list in result.items():
                json_data[obj_type] = [obj.get_info() for obj in obj_list]
            
            # Convert to JSON string
            return json.dumps(json_data, indent=2)
        else:
            # Format as text
            output = ""
            for obj_type, obj_list in result.items():
                output += "\n{0} objects:\n".format(obj_type)
                for obj in obj_list:
                    output += "  {0}\n".format(obj)
            
            return output
 
    def is_scheduler_active(self):
        """Check if the FreeRTOS scheduler is active"""
        if self._scheduler_active is not None:
            return self._scheduler_active
            
        try:
            # Try to get xSchedulerRunning
            scheduler_running = self.get_symbol_value("xSchedulerRunning")
            self._scheduler_active = scheduler_running != 0
        except:
            # If xSchedulerRunning is not available, check if pxCurrentTCB is non-NULL
            try:
                current_tcb = self.get_symbol_value(self.sym_dependencies["Thread"])
                self._scheduler_active = current_tcb != 0
            except:
                self._scheduler_active = False
                
        return self._scheduler_active
              
    def find_current_thread(self):
        """Find the currently running thread in FreeRTOS"""
        try:
            # First try single-core pxCurrentTCB
            if self.symbol_exists("pxCurrentTCB"):
                self.is_smp = False
                current_tcb = self.get_symbol_value("pxCurrentTCB")
                return current_tcb
            # Try multi-core pxCurrentTCBs
            elif self.symbol_exists("pxCurrentTCBs"):
                self.is_smp = True
                # Get the array of current TCBs
                try:
                    # Get the address of pxCurrentTCBs array
                    tcbs_array_addr = self.get_symbol_value("&pxCurrentTCBs")
                    
                    # Read up to 4 core entries (adjust if needed)
                    self.current_tcbs = []
                    for core_id in range(4):
                        tcb_ptr_addr = tcbs_array_addr + (core_id * 4)  # 4 bytes per pointer
                        tcb_ptr_bytes = self.mem_read(tcb_ptr_addr, 4)
                        if tcb_ptr_bytes:
                            tcb_addr = struct.unpack("<I", tcb_ptr_bytes)[0]
                            if tcb_addr != 0:
                                self.current_tcbs.append((core_id, tcb_addr))
                    
                    # Return the first valid TCB for compatibility
                    if self.current_tcbs:
                        return self.current_tcbs[0][1]
                except Exception as e:
                    fr_debug("Error reading pxCurrentTCBs array: {}".format(str(e)))
                    
            return FREERTOS_TARGET_NULL
        except Exception as e:
            fr_debug("Error finding current thread: {}".format(str(e)))
            return FREERTOS_TARGET_NULL
    
    def find_tasks_in_list(self, list_addr):
        """Find all tasks in a list
        
        Args:
            list_addr (int): Address of the list
            
        Returns:
            list: List of task addresses
        """
        tasks = []
        
        try:
            # Read the uxNumberOfItems field
            num_items_addr = list_addr
            num_items_bytes = self.mem_read(num_items_addr, 4)
            if not num_items_bytes:
                return tasks
                
            num_items = struct.unpack("<I", num_items_bytes)[0]
            
            # If the list is empty, return an empty list
            if num_items == 0:
                return tasks
            
            # Get the xListEnd address
            list_end_addr = list_addr + self.offsets.XLIST_END_OFFSET
            
            # Read the xItemValue field of xListEnd
            item_value_addr = list_end_addr
            item_value_bytes = self.mem_read(item_value_addr, 4)
            if not item_value_bytes:
                return tasks
                
            item_value = struct.unpack("<I", item_value_bytes)[0]
            
            # Read the pxNext field of xListEnd
            next_addr = list_end_addr + self.offsets.XLIST_NEXT_OFFSET
            next_bytes = self.mem_read(next_addr, 4)
            if not next_bytes:
                return tasks
                
            next_ptr = struct.unpack("<I", next_bytes)[0]
            
            # Read the pxPrevious field of xListEnd
            prev_addr = list_end_addr + self.offsets.XLIST_PREV_OFFSET
            prev_bytes = self.mem_read(prev_addr, 4)
            if not prev_bytes:
                return tasks
                
            prev_ptr = struct.unpack("<I", prev_bytes)[0]
            
            # If the list is empty, pxNext and pxPrevious will point to xListEnd
            if next_ptr == list_end_addr:
                return tasks
                
            # Start at the first item
            current_item = next_ptr
            
            # Traverse the list
            visited = set()
            while current_item != list_end_addr and current_item not in visited:
                visited.add(current_item)
                
                # Read the xItemValue field
                item_value_addr = current_item
                item_value_bytes = self.mem_read(item_value_addr, 4)
                if not item_value_bytes:
                    break
                    
                item_value = struct.unpack("<I", item_value_bytes)[0]
                
                # Read the pxNext field
                next_addr = current_item + self.offsets.XLIST_NEXT_OFFSET
                next_bytes = self.mem_read(next_addr, 4)
                if not next_bytes:
                    break
                    
                next_ptr = struct.unpack("<I", next_bytes)[0]
                
                # Read the pxPrevious field
                prev_addr = current_item + self.offsets.XLIST_PREV_OFFSET
                prev_bytes = self.mem_read(prev_addr, 4)
                if not prev_bytes:
                    break
                    
                prev_ptr = struct.unpack("<I", prev_bytes)[0]
                
                # Read the pvOwner field
                owner_addr = current_item + self.offsets.XLIST_ITEM_OWNER_OFFSET
                owner_bytes = self.mem_read(owner_addr, 4)
                if not owner_bytes:
                    break
                    
                owner_ptr = struct.unpack("<I", owner_bytes)[0]
                
                # If the owner is a valid TCB, add it to the list
                if owner_ptr != 0 and owner_ptr not in tasks:
                    tasks.append(owner_ptr)
                    
                # Move to the next item
                current_item = next_ptr
                
                # Safety check to avoid infinite loops
                if len(visited) > 100:
                    break
        except Exception as e:
            fr_debug("Error finding tasks in list: {}".format(str(e)))
            
        return tasks
        
    def get_all_threads(self):
        """Get all threads in the system"""
        raw_threads = []
        self._scheduler_active = None  # Reset cached value
        
        if not self.is_scheduler_active():
            print("Scheduler is not active")
            return []
            
        self.current_tcb_addr = self.find_current_thread()
 
        # Find ready tasks list
        try:
            self.ready_tasks = self.get_symbol_value("&pxReadyTasksLists")

            # Get the number of priorities
            max_priorities = self.compute_max_priorities()

            # Iterate through all priority levels in the ready tasks list
            for priority in range(max_priorities):
                # Calculate the address of the list for this priority
                list_addr = self.ready_tasks + (priority * self.offsets.XLIST_T_OFFSET)

                # Check if there are tasks in this list
                tasks_in_list = self.find_tasks_in_list(list_addr)
                for task_addr in tasks_in_list:
                    if task_addr not in raw_threads:
                        raw_threads.append(task_addr)
        except Exception as e:
            fr_debug("Error finding ready tasks: {}".format(str(e)))
            
        # Find other task lists
        self.tasks_lists = []
        try:
            # Delayed tasks list 1
            delayed_tasks = self.get_symbol_value("&xDelayedTaskList1")
            self.tasks_lists.append(delayed_tasks)
            
            # Delayed tasks list 2
            overflow_delayed_tasks = self.get_symbol_value("&xDelayedTaskList2")
            self.tasks_lists.append(overflow_delayed_tasks)
            
            # Pending ready tasks list
            pending_ready_tasks = self.get_symbol_value("&xPendingReadyList")
            self.tasks_lists.append(pending_ready_tasks)
            
            # Suspended tasks list
            suspended_tasks = self.get_symbol_value("&xSuspendedTaskList")
            self.tasks_lists.append(suspended_tasks)
            
            # Tasks waiting termination list
            if "TasksWaitingTermination" in self.sym_dependencies:
                tasks_waiting_termination = self.get_symbol_value(self.sym_dependencies["TasksWaitingTermination"])
                self.tasks_lists.append(tasks_waiting_termination)
            
            # Check each list for tasks
            for i, task_list in enumerate(self.tasks_lists):
                tasks_in_list = self.find_tasks_in_list(task_list)
                for task_addr in tasks_in_list:
                    if task_addr not in raw_threads:
                        raw_threads.append(task_addr)
        except Exception as e:
            fr_debug("Error finding task lists: {}".format(str(e)))
                
        # Add the current TCB(s) if not already in the list
        if self.is_smp:
            # For SMP, add all current TCBs from all cores
            for core_id, tcb_addr in self.current_tcbs:
                if tcb_addr and tcb_addr not in raw_threads:
                    raw_threads.append(tcb_addr)
        else:
            # For single-core, add the current TCB
            if self.current_tcb_addr and self.current_tcb_addr not in raw_threads:
                raw_threads.append(self.current_tcb_addr)
                
        # Convert raw TCB addresses to Thread/Threads objects
        thread_objects = []
        for i, tcb_addr in enumerate(raw_threads):
            # Use Threads class for SMP, Thread class for single-core
            if self.is_smp:
                thread = Threads(tcb_addr, self)
                # Check if this TCB is current on any core
                for core_id, current_tcb in self.current_tcbs:
                    if tcb_addr == current_tcb:
                        thread.core_id = core_id
                        break
            else:
                thread = Thread(tcb_addr, self)
                # If this is the current TCB, mark it as running
                if tcb_addr == self.current_tcb_addr:
                    thread.state = "Running"
            
            thread.index = i + 1  # Set the index for display purposes
            thread_objects.append(thread)
            
        return thread_objects

    def compute_max_priorities(self):
        """Compute the maximum number of priorities"""
        try:
            max_priorities_val = gdb.parse_and_eval("configMAX_PRIORITIES")
            return int(max_priorities_val)
        except Exception as e:
            fr_debug("Error computing max priorities: {}".format(str(e)))
            return 10
            
    def determine_thread_state(self, tcb_addr, is_smp=False):
        """Determine the state of a thread based on which list it's in"""
        # For SMP, check if it's running on any core
        if is_smp:
            for core_id, current_tcb in self.current_tcbs:
                if tcb_addr == current_tcb:
                    return "Running (Core {0})".format(core_id)
        else:
            if tcb_addr == self.current_tcb_addr:
                return "Running"
            
        # Check if the thread is in the ready list
        config_max_prio = self.compute_max_priorities()
        ready_lists = []
        for prio in range(config_max_prio):
            ready_lists.append(self.ready_tasks + (prio * self.offsets.XLIST_T_OFFSET))
        
        # Determine the offset for pvContainer based on SMP or not
        if is_smp:
            # For SMP, pvContainer is at a different offset in xStateListItem
            pvcont_offset = self.offsets.SMP_STATE_LIST_ITEM_OFFSET + 0xC  # pvContainer within ListItem_t
        else:
            pvcont_offset = self.offsets.XLIST_ITEM_PVCONT_OFFSET
            
        # Check if the thread is in the delayed list
        pvCont_addr = tcb_addr + pvcont_offset
        container_bytes = self.mem_read(pvCont_addr, 4)
        if container_bytes:
            container_addr = struct.unpack("<I", container_bytes)[0]
            if container_addr in ready_lists:
                return "Ready"
            for i, task_list in enumerate(self.tasks_lists):
                if container_addr == task_list:
                    if i == 0 or i == 1:  # Delayed lists
                        return "Blocked"
                    elif i == 2:  # Pending ready list
                        return "Pending"
                    elif i == 3:  # Suspended list
                        return "Suspended"
        # If we couldn't determine the state
        return "Unknown"
        
    def get_all_queues(self):
        """Get all queues in the system using direct memory access"""
        raw_queues = []
        queue_names = {}
        
        try:
            # Get the queue registry address
            queue_registry_addr = self.get_symbol_value("&xQueueRegistry")
            if queue_registry_addr == 0:
                return []
            
            # Calculate the number of registry items
            # From GDB: sizeof(xQueueRegistry) = 16, sizeof(QueueRegistryItem_t) = 8
            # So we have 16/8 = 2 registry items
            registry_size_bytes = 16  # You could also get this with get_symbol_value("sizeof(xQueueRegistry)")
            num_registry_items = registry_size_bytes // self.offsets.QUEUE_REGISTRY_ITEM_SIZE
            
            # Iterate through each registry slot
            for i in range(num_registry_items):
                # Calculate the address of this registry item
                registry_item_addr = queue_registry_addr + (i * self.offsets.QUEUE_REGISTRY_ITEM_SIZE)
                
                # Read the queue handle
                handle_addr = registry_item_addr + self.offsets.QUEUE_REGISTRY_HANDLE_OFFSET
                handle_bytes = self.mem_read(handle_addr, 4)
                if not handle_bytes:
                    continue
                    
                queue_handle = struct.unpack("<I", handle_bytes)[0]
                
                # If the handle is null, this slot is empty
                if queue_handle == 0:
                    continue
                
                # Read the queue name pointer
                name_ptr_addr = registry_item_addr + self.offsets.QUEUE_REGISTRY_NAME_OFFSET
                name_ptr_bytes = self.mem_read(name_ptr_addr, 4)
                if name_ptr_bytes:
                    name_ptr = struct.unpack("<I", name_ptr_bytes)[0]
                    if name_ptr != 0:
                        # Read the actual name string
                        queue_name = self.read_string(name_ptr, 32)
                        queue_names[queue_handle] = queue_name
                
                raw_queues.append(queue_handle)
                
        except Exception as e:
            fr_debug("Error finding queues using direct memory access: {}".format(str(e)))
            import traceback
            traceback.print_exc()
            
        # Convert raw queue addresses to Queue objects
        queue_objects = []
        for i, queue_addr in enumerate(raw_queues):
            queue = Queue(queue_addr, self)
            queue.index = i + 1  # Set the index for display purposes
            if queue_addr in queue_names:
                queue.name = queue_names[queue_addr]
            queue_objects.append(queue)
            

        return queue_objects

    def read_string(self, addr, max_length=32):
        """Read a null-terminated string from memory"""
        result = ""
        for i in range(max_length):
            byte_data = self.mem_read(addr + i, 1)
            if not byte_data:
                break
            # Handle both Python 2 and 3
            if sys.version_info[0] == 2:
                byte_val = ord(byte_data[0])
            else:
                byte_val = byte_data[0] if isinstance(byte_data[0], int) else ord(byte_data[0])
            
            if byte_val == 0:
                break
            result += chr(byte_val)
        return result
            
    def find_timer_symbols(self):
        """Automatically find all timer symbols using GDB commands"""
        timer_symbols = []
        
        try:
            # Use GDB command to find all variables with "timer" in the name
            
            # Try different search patterns
            search_patterns = ["timer", "Timer", "tmr", "Tmr"]
            
            for pattern in search_patterns:
                try:
                    cmd = "info variables {0}".format(pattern)
                    output = gdb.execute(cmd, to_string=True)
                    
                    # Parse the output to extract timer variable names
                    lines = output.split('\n')
                    for line in lines:
                        line = line.strip()
                        
                        # Look for lines with timer type declarations
                        # Format: "37:	TimerHandle_t one_shot_timer;"
                        if 'TimerHandle_t' in line or 'Timer_t' in line or 'xTimerHandle' in line:
                            # Extract the variable name (last word before semicolon)
                            import re
                            
                            # Pattern to match: "number: type variable_name;"
                            match = re.search(r':\s*(TimerHandle_t|Timer_t|xTimerHandle)\s+(\w+);', line)
                            if match:
                                timer_name = match.group(2)
                                if timer_name not in timer_symbols:
                                    timer_symbols.append(timer_name)
                            else:
                                # Alternative pattern: just "type variable_name;"
                                match = re.search(r'(TimerHandle_t|Timer_t|xTimerHandle)\s+(\w+);', line)
                                if match:
                                    timer_name = match.group(2)
                                    if timer_name not in timer_symbols:
                                        timer_symbols.append(timer_name)
                    
                except Exception as e:
                    # Command failed, try next pattern
                    continue
                
        except Exception as e:
            fr_debug("Error in timer symbol discovery")
            fr_debug(e)
            
        return timer_symbols
        
    def find_timers_in_list(self, list_addr):
        """Find all timers in a timer list (similar to find_tasks_in_list but for timers)
        
        Args:
            list_addr (int): Address of the timer list
            
        Returns:
            list: List of timer addresses
        """
        timers = []
        
        try:
            # Read the uxNumberOfItems field
            num_items_addr = list_addr
            num_items_bytes = self.mem_read(num_items_addr, 4)
            if not num_items_bytes:
                return timers
                
            num_items = struct.unpack("<I", num_items_bytes)[0]
            
            # If the list is empty, return an empty list
            if num_items == 0:
                return timers
            
            # Get the xListEnd address
            list_end_addr = list_addr + self.offsets.XLIST_END_OFFSET
            
            # Read the pxNext field of xListEnd to get the first item
            next_addr = list_end_addr + self.offsets.XLIST_NEXT_OFFSET
            next_bytes = self.mem_read(next_addr, 4)
            if not next_bytes:
                return timers
                
            next_ptr = struct.unpack("<I", next_bytes)[0]
            
            # If the list is empty, pxNext will point to xListEnd
            if next_ptr == list_end_addr:
                return timers
                
            # Start at the first item
            current_item = next_ptr
            
            # Traverse the list
            visited = set()
            while current_item != list_end_addr and current_item not in visited:
                visited.add(current_item)
                
                # Read the pxNext field for next iteration
                next_addr = current_item + self.offsets.XLIST_NEXT_OFFSET
                next_bytes = self.mem_read(next_addr, 4)
                if not next_bytes:
                    break
                    
                next_ptr = struct.unpack("<I", next_bytes)[0]
                
                # Read the pvOwner field - this should point to the timer structure
                owner_addr = current_item + self.offsets.XLIST_ITEM_OWNER_OFFSET
                owner_bytes = self.mem_read(owner_addr, 4)
                if not owner_bytes:
                    break
                    
                timer_ptr = struct.unpack("<I", owner_bytes)[0]
                
                # If the owner is a valid timer, add it to the list
                if timer_ptr != 0 and timer_ptr not in timers:
                    timers.append(timer_ptr)
                    
                # Move to the next item
                current_item = next_ptr
                
                # Safety check to avoid infinite loops
                if len(visited) > 100:
                    break
                    
        except Exception as e:
            fr_debug("Error finding timers in list: {0}".format(str(e)))
            
        return timers
        
    def get_all_timers(self):
        """Get all timers in the system using direct memory access with automatic discovery"""
        raw_timers = []
        timer_variable_names = {}  # Store variable names separately
        
        try:
            # Get the active timer lists addresses
            active_timer_list1_addr = self.get_symbol_value("&xActiveTimerList1")
            active_timer_list2_addr = self.get_symbol_value("&xActiveTimerList2")
            
            # Check both active timer lists for running timers
            timer_lists = [active_timer_list1_addr, active_timer_list2_addr]
            
            for i, timer_list_addr in enumerate(timer_lists):
                if timer_list_addr == 0:
                    continue
                    
                timers_in_list = self.find_timers_in_list(timer_list_addr)
                for timer_addr in timers_in_list:
                    if timer_addr not in raw_timers:
                        raw_timers.append(timer_addr)
            
            # Automatically discover timer variable symbols
            timer_symbol_names = self.find_timer_symbols()
            
            for timer_name in timer_symbol_names:
                if self.symbol_exists(timer_name):
                    try:
                        timer_handle = self.get_symbol_value(timer_name)
                        if timer_handle != 0 and timer_handle not in raw_timers:
                            raw_timers.append(timer_handle)
                            timer_variable_names[timer_handle] = timer_name  # Store variable name separately
                    except Exception as e:
                        fr_debug("Error getting timer variable {0}".format(timer_name))
                        fr_debug(e)
                        
        except Exception as e:
            fr_debug("Error finding timers using direct memory access: {}".format(str(e)))
            
        # Convert raw timer addresses to Timer objects
        timer_objects = []
        for i, timer_addr in enumerate(raw_timers):
            timer = Timer(timer_addr, self)
            timer.index = i + 1
            
            # Store the variable name for reference, but don't overwrite the actual timer name
            if timer_addr in timer_variable_names:
                timer.variable_name = timer_variable_names[timer_addr]
            
            timer_objects.append(timer)
            
        return timer_objects