# -*- coding: utf-8 -*-
"""
    Custom GDB commands for thread awareness.


 Copyright 2023-2024 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.


"""

# This is the extended Zephyr thread awareness support based on Zephyr's.
# kernel object tracking support.
# These configuration should be added to the project:
#
# CONFIG_TRACING=y
# CONFIG_TRACING_OBJECT_TRACKING=y
# CONFIG_TRACING_NONE=y
#
# The peak (max) stack usage requires this config:
# CONFIG_INIT_STACKS=y
#
# The thread stack info requires this config:
# CONFIG_THREAD_STACK_INFO=y
#
# The thread tracking requires this config:
# CONFIG_DEBUG_THREAD_INFO=y
#
# The standard support for thread awareness (thread's call stack, frame's unwinding)
# is not subject of this script.

import os
import sys
import time
import re
import json

CURRENT_DIR = "".join([os.path.dirname(os.path.realpath(__file__)), "/"])
sys.path.insert(0, CURRENT_DIR)
sys.path.insert(0, CURRENT_DIR + "../utils")

import gdb
import mem_reg_utils as utils

from errors import Result

# Enable this to see commands and results
z_debug_on = False

# Enable this to see the performance
z_debug_perf = False
z_nested_calls = 0

# GDB object cache references
z_object_cache = {}

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

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

    return None


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


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

    if z_object_cache:
        z_object_cache.clear()


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

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

        z_nested_calls += 1

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

        z_nested_calls -= 1

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

        z_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 z_debug(s, forced=False):
    if z_debug_on or forced:
        gdb.write(str(s))
        gdb.write("\n")


@perf_monitor
def z_run_gdb_cmd(cmd):
    z_debug(cmd)
    result = utils.gdb_exec(cmd)
    if result.isok():
        value = result.ok.value
        z_debug(value)
        return value
    else:
        value = result.error.__str__()
        if "Error:No symbol" not in value:
            z_debug(value)
        raise ValueError(value)


# Base class for all kernel objects
class KernelObject(object):
    # This is the base class for all kernel objects supported
    def __init__(
        self, type=None, symbol=None, linked_kobj=None, next="_obj_track_next"
    ):
        self.type = type
        self.addr = None
        self.descr = None
        self.symbol_next = None
        self.dict = {"metadata": {"type": type}}
        self.next_attr = next
        self.cached_object = None

        if linked_kobj is None:
            self.symbol = symbol
        else:
            self.symbol = linked_kobj.symbol_next

    @perf_monitor
    def get_addr_and_name(self):
        info = z_run_gdb_cmd("p " + self.symbol)
        return (
            int(re.search(r"0[xX][0-9a-fA-F]+", str(info)).group(), base=16),
            self.str_clear(re.search(r"\<(.*?)\>", str(info)).group()),
        )

    def str_clear(self, str):
        filter = ["=", "\n", " ", '"', "<", ">", "$"]

        for f in filter:
            str = str.replace(f, "")

        return str

    @perf_monitor
    def get_attr_value_nc(self, attr, fmt="/d", prefix=""):
        if attr:
            attr = "->" + attr
        else:
            attr = ""

        value = z_run_gdb_cmd(
            "p{0} {1}(({2}) {3:d}){4}".format(fmt, prefix, self.type, self.addr, attr)
        )

        z_debug(value)
        return self.str_clear((re.search(r"\=(.*?)\n", str(value)).group()))

    def get_attr_value(self, attr, fmt="/d", prefix="", cached_object=None):
        if not cached_object:
            cached_object = self.cached_object

        if attr:
            attr = "." + attr
        else:
            attr = ""

        value = z_run_gdb_cmd(
            "p{0} {1}${2}{3}".format(fmt, prefix, cached_object, attr)
        )

        z_debug(value)
        return self.str_clear((re.search(r"\=(.*?)\n", str(value)).group()))

    @perf_monitor
    def get_object_ref(self, prefix="", addr=None, type=None, use_cache=True):
        if not addr:
            addr = self.addr

        if not type:
            type = self.type

        if use_cache:
            ref = ref_get_cache(addr)
            if ref:
                return ref

        value = z_run_gdb_cmd("p {0}(*({1}){2:d})".format(prefix, type, addr))

        z_debug(value)
        ref = self.str_clear((re.search(r"\$(.*?)=", str(value)).group()))

        if use_cache:
            ref_add_cache(addr, ref)

        return ref

    @perf_monitor
    def get_thread_info_by_addr(self, tid):
        try:
            # Mask last 2 bits to support spinlock
            tid = int64(tid) & ~3

            if not tid:
                return ""

            ref = ref_get_cache(tid)
            if ref:
                value = z_run_gdb_cmd("p/s ${0}.name".format(ref))
            else:
                value = z_run_gdb_cmd(
                    "p/s ((struct k_thread *) {0:d})->name".format(tid)
                )

            return self.str_clear((re.search(r"\=(.*?)\n", str(value)).group()))
        except Exception as e:
            z_debug("Cannot read name for tid: {0}".format(tid))
            z_debug(e)
            return tid

    @perf_monitor
    def get_thread_info(self, attr):
        tid = 0
        try:
            tid = int(self.get_attr_value(attr))
            return self.get_thread_info_by_addr(tid)
        except Exception as e:
            z_debug("Cannot read name for attr: {0}".format(attr))
            z_debug(e)
            return tid

    @perf_monitor
    def get_waiting_list(self, attr="wait_q"):
        list = []
        str = ""
        count = 0

        try:
            head = int64(self.get_attr_value(attr + ".waitq.next"))
            # When the list is empty it's point to itself
            end = int64(self.get_attr_value(attr, prefix="&"))

            while head != end:
                list.append(self.get_thread_info_by_addr(head))
                head_ref = self.get_object_ref(
                    addr=head, type="struct _dnode", use_cache=False
                )
                head = int64(self.get_attr_value("next", cached_object=head_ref))

        except Exception as e:
            z_debug(e)

        for s in list:
            if count > 0:
                str += ","
            count += 1
            str += s

        return str

    @perf_monitor
    def fill_object(self):
        try:
            # Check if this is not empty
            self.addr, name = self.get_addr_and_name()

            z_debug("Address: {0}".format(self.addr))

            if self.addr > 0:
                ref = ref_get_cache(self.addr)

                if not ref:
                    ref = self.get_object_ref()

                self.symbol_next = "${0}.{1}".format(ref, self.next_attr)

                self.dict["address"] = self.addr
                self.dict["name"] = name
                self.cached_object = ref

                return True
        except Exception as e:
            z_debug(e)

        return False

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


# See 'struct k_sem'
class Semaphore(KernelObject):
    def __init__(self, linked_kobj=None):
        KernelObject.__init__(self, "struct k_sem *", "_track_list_k_sem", linked_kobj)

    @perf_monitor
    def fill_object(self):
        try:
            if KernelObject.fill_object(self):
                self.dict["limit"] = int64(self.get_attr_value("limit"))
                self.dict["count"] = int64(self.get_attr_value("count"))
                self.dict["waiting"] = self.get_waiting_list()
                return True
        except Exception as e:
            z_debug(e)

        return False


# See 'struct k_mutex'
class Mutex(KernelObject):
    def __init__(self, linked_kobj=None):
        KernelObject.__init__(
            self, "struct k_mutex *", "_track_list_k_mutex", linked_kobj
        )

    @perf_monitor
    def fill_object(self):
        try:
            if KernelObject.fill_object(self):
                self.dict["owner"] = self.get_thread_info("owner")
                self.dict["lock_count"] = int64(self.get_attr_value("lock_count"))
                self.dict["owner_orig_prio"] = int(
                    self.get_attr_value("owner_orig_prio")
                )
                self.dict["waiting"] = self.get_waiting_list()
                return True
        except Exception as e:
            z_debug(e)

        return False


# See 'struct k_timer'
class Timer(KernelObject):
    def __init__(self, linked_kobj=None):
        KernelObject.__init__(
            self, "struct k_timer *", "_track_list_k_timer", linked_kobj
        )

    def fill_object(self):
        try:
            if KernelObject.fill_object(self):
                self.dict["timeout"] = int64(self.get_attr_value("timeout.dticks"))
                self.dict["period"] = int64(self.get_attr_value("period.ticks"))
                self.dict["status"] = int64(self.get_attr_value("status"))
                self.dict["waiting"] = self.get_waiting_list()
                return True
        except Exception as e:
            z_debug(e)

        return False


# See 'struct k_stack'
class Stack(KernelObject):
    def __init__(self, linked_kobj=None):
        KernelObject.__init__(
            self, "struct k_stack *", "_track_list_k_stack", linked_kobj
        )

    def fill_object(self):
        try:
            if KernelObject.fill_object(self):
                self.dict["base"] = int64(self.get_attr_value("base"))
                self.dict["next"] = int64(self.get_attr_value("next"))
                self.dict["top"] = int64(self.get_attr_value("top"))
                self.dict["flags"] = int(self.get_attr_value("flags"))
                self.dict["locked"] = self.get_thread_info("lock.thread_cpu")
                self.dict["waiting"] = self.get_waiting_list()
                return True
        except Exception as e:
            z_debug(e)

        return False


# See 'struct k_msgq'
class MsgQ(KernelObject):
    def __init__(self, linked_kobj=None):
        KernelObject.__init__(
            self, "struct k_msgq *", "_track_list_k_msgq", linked_kobj
        )

    def fill_object(self):
        try:
            if KernelObject.fill_object(self):
                self.dict["msg_size"] = int64(self.get_attr_value("msg_size"))
                self.dict["max_msgs"] = int64(self.get_attr_value("max_msgs"))
                self.dict["buffer_start"] = int64(self.get_attr_value("buffer_start"))
                self.dict["buffer_end"] = int64(self.get_attr_value("buffer_end"))
                self.dict["read_ptr"] = int64(self.get_attr_value("read_ptr"))
                self.dict["write_ptr"] = int64(self.get_attr_value("write_ptr"))
                self.dict["used_msgs"] = int64(self.get_attr_value("used_msgs"))
                self.dict["flags"] = int(self.get_attr_value("flags"))
                self.dict["locked"] = self.get_thread_info("lock.thread_cpu")
                self.dict["waiting"] = self.get_waiting_list()
                return True
        except Exception as e:
            z_debug(e)

        return False


# See 'struct k_mbox'
class MBox(KernelObject):
    def __init__(self, linked_kobj=None):
        KernelObject.__init__(
            self, "struct k_mbox *", "_track_list_k_mbox", linked_kobj
        )

    def fill_object(self):
        try:
            if KernelObject.fill_object(self):
                self.dict["locked"] = self.get_thread_info("lock.thread_cpu")
                self.dict["waiting_tx"] = self.get_waiting_list("tx_msg_queue")
                self.dict["waiting_rx"] = self.get_waiting_list("rx_msg_queue")
                return True
        except Exception as e:
            z_debug(e)

        return False


class Pipe(KernelObject):
    def __init__(self, linked_kobj=None):
        KernelObject.__init__(
            self, "struct k_pipe *", "_track_list_k_pipe", linked_kobj
        )

    def fill_object(self):
        try:
            if KernelObject.fill_object(self):
                self.dict["buffer"] = int64(self.get_attr_value("buffer"))
                self.dict["size"] = int64(self.get_attr_value("size"))
                self.dict["bytes_used"] = int64(self.get_attr_value("bytes_used"))
                self.dict["read_index"] = int64(self.get_attr_value("read_index"))
                self.dict["write_index"] = int64(self.get_attr_value("write_index"))
                self.dict["flags"] = int(self.get_attr_value("flags"))
                self.dict["locked"] = self.get_thread_info("lock.thread_cpu")
                self.dict["waiting_tx"] = self.get_waiting_list("wait_q.readers")
                self.dict["waiting_rx"] = self.get_waiting_list("wait_q.writers")
                return True
        except Exception as e:
            z_debug(e)

        return False


class Queue(KernelObject):
    def __init__(self, linked_kobj=None):
        KernelObject.__init__(
            self, "struct k_queue *", "_track_list_k_queue", linked_kobj
        )

    def fill_object(self):
        try:
            if KernelObject.fill_object(self):
                self.dict["locked"] = self.get_thread_info("lock.thread_cpu")
                self.dict["waiting"] = self.get_waiting_list()
                return True
        except Exception as e:
            z_debug(e)

        return False


class MemSlab(KernelObject):
    def __init__(self, linked_kobj=None):
        KernelObject.__init__(
            self, "struct k_mem_slab *", "_track_list_k_mem_slab", linked_kobj
        )

    def fill_object(self):
        try:
            if KernelObject.fill_object(self):
                self.dict["num_blocks"] = int64(self.get_attr_value("num_blocks"))
                self.dict["block_size"] = int64(self.get_attr_value("block_size"))
                self.dict["buffer"] = int64(self.get_attr_value("buffer"))
                self.dict["free_list"] = int64(self.get_attr_value("free_list"))
                self.dict["num_used"] = int64(self.get_attr_value("num_used"))
                self.dict["locked"] = self.get_thread_info("lock.thread_cpu")
                self.dict["waiting"] = self.get_waiting_list()
                try:
                    self.dict["max_used"] = int64(self.get_attr_value("max_used"))
                except Exception as e:
                    z_debug(e)

                return True
        except Exception as e:
            z_debug(e)

        return False


class Thread(KernelObject):
    def __init__(self, linked_kobj=None):
        KernelObject.__init__(
            self,
            "struct k_thread *",
            "_kernel->threads",
            linked_kobj,
            next="next_thread",
        )
        self.state_names = [
            "DUMMY",
            "PENDING",
            "PRERESTART",
            "DEAD",
            "SUSPENDED",
            "ABORTING",
            "UNKNOWN",
            "QUEUED",
        ]
        self.current = None

    def is_current_thread(self):
        global z_current_thread

        if not z_current_thread:
            value = z_run_gdb_cmd("p/d _kernel.cpus[0].current")
            z_current_thread = self.str_clear(
                (re.search(r"\=(.*?)\n", str(value)).group())
            )

        instance = self.get_attr_value(attr=None, prefix="&")
        z_debug("Current: {} vs. Instance {}".format(z_current_thread, instance))

        return instance == z_current_thread

    @perf_monitor
    def get_state_name(self, state):
        i = 0

        # Is this the current thread?
        if self.is_current_thread():
            return "STOPPED"

        while i < len(self.state_names):
            if (1 << i) & state:
                return self.state_names[i]
            i += 1

        return "UNKNOWN"

    def get_sp(self, thread_num):
        sp_val = z_run_gdb_cmd("thread apply {} p/d $sp".format(thread_num))
        return int64(self.str_clear((re.search(r"\=(.*?)\n", str(sp_val)).group())))

    # get CURRENT stack usage based on SP register
    @perf_monitor
    def get_stack_usage(self):
        base = self.dict["stack_addr"] + self.dict["stack_delta"]
        size = self.dict["stack_size"]
        thread_tid = self.dict["address"]
        thread_num = None
        sp = None

        # Get the thread num
        threads = gdb.selected_inferior().threads()
        for t in threads:
            z_debug(
                "Current tid {} vs {}/{}".format(thread_tid, t.ptid[1], t.global_num)
            )
            if thread_tid == t.ptid[1]:
                thread_num = t.global_num
                break

        if thread_num:
            sp = self.get_sp(thread_num)
        elif len(threads) == 1:
            # This can happened if the thread awareness standard support
            # is not available.
            sp = self.get_sp(threads[0].global_num)

        if sp:
            z_debug("SP: {}".format(sp))
            if (sp >= base) and (sp <= (base + size)):
                current = sp - base
                # The assumption is the stack grows normally
                return 100 - round((current * 100) / size, 2)
            else:
                z_debug("SP out of scope. Diff {}".format(sp - base))

        return None

    # get MAX stack usage based on stack marker
    @perf_monitor
    def get_max_stack_usage(self):
        base = self.dict["stack_addr"] + self.dict["stack_delta"]
        size = self.dict["stack_size"]
        not_used = 0

        mem = gdb.selected_inferior().read_memory(base, size)

        # Looking for first byte that is not equal with stack marker
        for m in mem:
            if b"\xaa" == m:
                not_used += 1
            else:
                break

        used = size - not_used

        return (used * 100) / size

    @perf_monitor
    def fill_object(self):
        try:
            if KernelObject.fill_object(self):
                self.dict["user_options"] = int(
                    self.get_attr_value("base.user_options")
                )
                self.dict["state"] = self.get_state_name(
                    int(self.get_attr_value("base.thread_state"))
                )
                self.dict["prio"] = int(self.get_attr_value("base.prio"))
                # All stack info depends on CONFIG_THREAD_STACK_INFO
                try:
                    self.dict["stack_addr"] = int64(
                        self.get_attr_value("stack_info.start")
                    )
                    self.dict["stack_size"] = int64(
                        self.get_attr_value("stack_info.size")
                    )
                    self.dict["stack_delta"] = int(
                        self.get_attr_value("stack_info.delta")
                    )
                    self.dict["stack_usage_max"] = round(self.get_max_stack_usage(), 2)
                    self.dict["stack_usage"] = self.get_stack_usage()
                except Exception as e:
                    z_debug(e)

                try:
                    self.dict["tname"] = self.get_attr_value("name", "/s").replace(
                        '"', ""
                    )
                except Exception as e:
                    z_debug(e)

                return True
        except Exception as e:
            z_debug(e)

        return False


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 z_run_gdb_cmd("p/d &" + sym_name):
                return True
        except:
            z_debug("Symbol {} not found".format(sym_name))

        return False

    def get_objects_per_type(self, ctor):
        list = []
        prev_elem = None

        while True:
            elem = ctor(prev_elem)
            if elem.fill_object():
                list.append(elem.dict)
                prev_elem = elem
            else:
                break

        return list

    # 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 print_horiz_line(self, table_len):
        # print table horiz separator
        line = ""
        for i in range(0, table_len):
            line += "-"

        return line + "\n"

    def key_to_skip(self, key):
        if "metadata" in str(key):
            return True

        return False

    @perf_monitor
    def print_table(self, name, objs):
        t = {}
        cols = {}
        output = ""

        if objs == None or len(objs) == 0:
            return

        # Compute column width
        for c in sorted(objs[0].keys()):
            if self.key_to_skip(c):
                continue

            col = len(str(c))
            for o in objs:
                string = str(o[c])
                lenght = len(string)

                if lenght > col:
                    col = lenght

            cols[c] = col

        table_len = 0
        for c in sorted(objs[0].keys()):
            if not self.key_to_skip(c):
                table_len += cols[c] + 2

        table_len += 1

        # print table horiz separator
        output += "\n"
        output += self.print_horiz_line(table_len)

        # print table name
        fmt = "{:^" + str(table_len) + "}"
        output += fmt.format(name)
        output += "\n"

        # print table horiz separator
        output += self.print_horiz_line(table_len)

        # Print table header
        for c in sorted(objs[0].keys()):
            if self.key_to_skip(c):
                continue

            fmt = "| {:^" + str(cols[c]) + "}"
            output += fmt.format(c)
        output += "|\n"

        # print table horiz separator
        output += self.print_horiz_line(table_len)

        # Print all rows
        for o in objs:
            for c in sorted(objs[0].keys()):
                if self.key_to_skip(c):
                    continue

                fmt = "| {:<" + str(cols[c]) + "}"
                output += fmt.format(str(o[c]))
            output += "|\n"

        # print table horiz separator
        output += self.print_horiz_line(table_len)
        output += "\n"

        return output

    # List all kernel objects detected
    @perf_monitor
    def list_objects(self, str_types=[], json_format=False):
        global z_current_thread
        zobjs = {}

        # clear the GDB objects cache
        ref_reset_cache()

        # Force re-read of the current thread
        z_current_thread = None

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

        if not detect:
            zobjs["error"] = descr
        else:
            found = 0
            if not str_types:
                # Get all supported types
                str_types = self.supported_kernel_types
            else:
                z_debug(
                    "Requested {} vs. supported {}".format(
                        str_types, self.supported_kernel_types
                    )
                )

            # Convert strings to class names
            for s in str_types:
                if s not in self.supported_kernel_types:
                    z_debug(
                        "Requested type {} not in supported list {}".format(
                            s, self.supported_kernel_types
                        )
                    )
                    # zobjs[s] = []
                    continue
                try:
                    found += 1
                    zobjs[s] = self.get_objects_per_type(globals()[s])
                except Exception as e:
                    z_debug(e)
                    zobjs = {}
                    zobjs["error"] = "Unsupported object type {0}".format(s)
                    break

            # Check the situation when the user asks for objects not tracked
            if not found:
                zobjs["error"] = "No kernel object tracking enabled"

            z_debug(str(zobjs))

        if not json_format:
            output = ""
            for k in zobjs.keys():
                if type(zobjs[k]) is list:
                    table = self.print_table(k, zobjs[k])
                    if table is not None:
                        output += table
                else:
                    output += str(zobjs[k])
            return output
        else:
            return self.print_json(zobjs)


class Zephyr(RTOS):
    def __init__(self):
        RTOS.__init__(self)
        # These are the kernel types supported
        self.sym_dependencies = {
            "Thread": "_kernel->threads",
            "Semaphore": "_track_list_k_sem",
            "Mutex": "_track_list_k_mutex",
            "Timer": "_track_list_k_timer",
            "Stack": "_track_list_k_stack",
            "MsgQ": "_track_list_k_msgq",
            "MBox": "_track_list_k_mbox",
            "Pipe": "_track_list_k_pipe",
            "Queue": "_track_list_k_queue",
            "MemSlab": "_track_list_k_mem_slab",
        }

    def set_debug(self, on=False):
        global z_debug_on
        z_debug_on = on

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

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

        return True

    @perf_monitor
    def detect(self, fast_mode=True):
        z_run_gdb_cmd("set print null-stop")

        if self.supported_kernel_types:
            return (True, "")

        # This symbol must exists
        if not self.symbol_exists("_kernel"):
            return (False, "Zephyr OS not detected")
        else:
            z_debug("Zephyr OS 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]):
                z_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:
            z_debug(
                "Number of object types found: {}".format(
                    len(self.supported_kernel_types)
                )
            )

        return (True, "")

    def get_objects_per_type(self, ctor):
        # The thread stack usage may be reported 100% for all
        # when the build is not done with CONFIG_INIT_STACKS
        # Remove this info if there are >= 2 threads and all of them
        # have the stack usage 100%
        list = super(Zephyr, self).get_objects_per_type(ctor)

        try:
            if ctor.__name__ == "Thread":
                if len(list) >= 2:
                    full = 0

                    # Count how many threads are with full stack usage
                    for t in list:
                        # The stack usage might be not available
                        if "stack_usage_max" in t.keys():
                            if t["stack_usage_max"] == 100:
                                full = full + 1
                            else:
                                break
                        else:
                            break

                    if full == len(list):
                        z_debug(
                            "Full stack detected for all threads. Remove this attribute."
                        )
                        for t in list:
                            t.pop("stack_usage_max", None)
        except Exception as e:
            z_debug(e)

        return list
