From a59a28de5b8f681c0a87b069e9b0005fb540e0bb Mon Sep 17 00:00:00 2001 From: xuxingliang Date: Fri, 8 Nov 2024 13:28:01 +0800 Subject: [PATCH] gdb/utils: enhance utils.Value Added __format__ method to support format spec like {:>10} that gdb.Value doesn't support. For such case, gdb.Value is converted to python value firstly and then format natively. Override all methods/attributes could return gdb.Value to return utils.Value instead. Signed-off-by: xuxingliang --- tools/gdb/nuttxgdb/protocols/thread.py | 122 +++++++++++++++++++++++++ tools/gdb/nuttxgdb/protocols/value.py | 65 +++++++++++++ tools/gdb/nuttxgdb/utils.py | 110 ++++++++++++++++++++-- 3 files changed, 287 insertions(+), 10 deletions(-) create mode 100644 tools/gdb/nuttxgdb/protocols/thread.py create mode 100644 tools/gdb/nuttxgdb/protocols/value.py diff --git a/tools/gdb/nuttxgdb/protocols/thread.py b/tools/gdb/nuttxgdb/protocols/thread.py new file mode 100644 index 0000000000000..5cb05bb032c43 --- /dev/null +++ b/tools/gdb/nuttxgdb/protocols/thread.py @@ -0,0 +1,122 @@ +############################################################################ +# tools/gdb/nuttxgdb/protocols/thread.py +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +from .value import Value + + +class Group(Value): + """struct group_s""" + + tg_pid: Value + tg_ppid: Value + tg_flags: Value + tg_uid: Value + tg_gid: Value + tg_euid: Value + tg_egid: Value + tg_members: Value + tg_bininfo: Value + tg_children: Value + tg_nchildren: Value + tg_exitcode: Value + tg_nwaiters: Value + tg_waitflags: Value + tg_exitsem: Value + tg_statloc: Value + tg_joinlock: Value + tg_joinqueue: Value + tg_info: Value + tg_sigactionq: Value + tg_sigpendingq: Value + tg_sigdefault: Value + tg_envp: Value + tg_envc: Value + itimer: Value + tg_filelist: Value + tg_mm_map: Value + + +class Tcb(Value): + """struct tcb_s""" + + flink: Value + blink: Value + group: Group + member: Value + join_queue: Value + join_entry: Value + join_sem: Value + join_val: Value + addrenv_own: Value + addrenv_curr: Value + pid: Value + sched_priority: Value + init_priority: Value + start: Value + entry: Value + task_state: Value + boost_priority: Value + base_priority: Value + holdsem: Value + cpu: Value + affinity: Value + flags: Value + lockcount: Value + irqcount: Value + errcode: Value + timeslice: Value + sporadic: Value + waitdog: Value + adj_stack_size: Value + stack_alloc_ptr: Value + stack_base_ptr: Value + dspace: Value + waitobj: Value + sigprocmask: Value + sigwaitmask: Value + sigpendactionq: Value + sigpostedq: Value + sigunbinfo: Value + mhead: Value + ticks: Value + run_start: Value + run_max: Value + run_time: Value + premp_start: Value + premp_max: Value + premp_caller: Value + premp_max_caller: Value + crit_start: Value + crit_max: Value + crit_caller: Value + crit_max_caller: Value + perf_event_ctx: Value + perf_event_mutex: Value + xcp: Value + sigdeliver: Value + name: Value + stackrecord_pc: Value + stackrecord_sp: Value + stackrecord_pc_deepest: Value + stackrecord_sp_deepest: Value + sp_deepest: Value + caller_deepest: Value + level_deepest: Value + level: Value diff --git a/tools/gdb/nuttxgdb/protocols/value.py b/tools/gdb/nuttxgdb/protocols/value.py new file mode 100644 index 0000000000000..6167e5fc8a0ea --- /dev/null +++ b/tools/gdb/nuttxgdb/protocols/value.py @@ -0,0 +1,65 @@ +############################################################################ +# tools/gdb/nuttxgdb/protocols/value.py +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +from __future__ import annotations + +from typing import Protocol + +import gdb + + +class Value(Protocol): + address: Value + is_optimized_out: bool + type: gdb.Type + dynamic_type: gdb.Type + is_lazy: bool + bytes: bytes + + def cast(self, type: gdb.Type) -> Value: ... + def dereference(self) -> Value: ... + def referenced_value(self) -> Value: ... + def reference_value(self) -> Value: ... + def rvalue_reference_value(self) -> Value: ... + def const_value(self) -> Value: ... + def dynamic_cast(self, type: gdb.Type) -> Value: ... + def reinterpret_cast(self, type: gdb.Type) -> Value: ... + + def format_string( + self, + raw: bool = ..., + pretty_arrays: bool = ..., + pretty_structs: bool = ..., + array_indexes: bool = ..., + symbols: bool = ..., + unions: bool = ..., + address: bool = ..., + deref_refs: bool = ..., + actual_objects: bool = ..., + static_members: bool = ..., + max_elements: int = ..., + max_depth: int = ..., + repeat_threshold: int = ..., + format: str = ..., + ) -> str: ... + + def string( + self, encoding: str = ..., errors: str = ..., length: int = ... + ) -> str: ... diff --git a/tools/gdb/nuttxgdb/utils.py b/tools/gdb/nuttxgdb/utils.py index cff55a7300320..a4c0eebfb91a5 100644 --- a/tools/gdb/nuttxgdb/utils.py +++ b/tools/gdb/nuttxgdb/utils.py @@ -19,6 +19,7 @@ # under the License. # ############################################################################ + from __future__ import annotations import argparse @@ -28,11 +29,12 @@ import re import shlex from enum import Enum -from typing import List, Tuple, Union +from typing import List, Optional, Tuple, Union import gdb from .macros import fetch_macro_info, try_expand +from .protocols.thread import Tcb g_symbol_cache = {} g_type_cache = {} @@ -40,6 +42,88 @@ g_backtrace_cache = {} +class Value(gdb.Value): + def __init__(self, obj: Union[gdb.Value, Value]): + super().__init__(obj) + + def __isabstractmethod__(self): + # Added to avoid getting error using __getattr__ + return False + + def __getattr__(self, key): + if hasattr(super(), key): + value = super().__getattribute__(key) + else: + value = super().__getitem__(key) + + return Value(value) if not isinstance(value, Value) else value + + def __getitem__(self, key): + value = super().__getitem__(key) + return Value(value) if not isinstance(value, Value) else value + + def __format__(self, format_spec: str) -> str: + try: + return super().__format__(format_spec) + except TypeError: + # Convert GDB value to python value, and then format it + type_code_map = { + gdb.TYPE_CODE_INT: int, + gdb.TYPE_CODE_PTR: int, + gdb.TYPE_CODE_ENUM: int, + gdb.TYPE_CODE_FUNC: hex, + gdb.TYPE_CODE_BOOL: bool, + gdb.TYPE_CODE_FLT: float, + gdb.TYPE_CODE_STRING: str, + gdb.TYPE_CODE_CHAR: lambda x: chr(int(x)), + } + + t = self.type + while t.code == gdb.TYPE_CODE_TYPEDEF: + t = t.target() + + type_code = t.code + try: + converter = type_code_map[type_code] + return f"{converter(self):{format_spec}}" + except KeyError: + raise TypeError( + f"Unsupported type: {self.type}, {self.type.code} {self}" + ) + + @property + def address(self) -> Value: + value = super().address + return value and Value(value) + + def cast(self, type: str | gdb.Type, ptr: bool = False) -> Optional["Value"]: + try: + gdb_type = lookup_type(type) if isinstance(type, str) else type + if ptr: + gdb_type = gdb_type.pointer() + return Value(super().cast(gdb_type)) + except gdb.error: + return None + + def dereference(self) -> Value: + return Value(super().dereference()) + + def reference_value(self) -> Value: + return Value(super().reference_value()) + + def referenced_value(self) -> Value: + return Value(super().referenced_value()) + + def rvalue_reference_value(self) -> Value: + return Value(super().rvalue_reference_value()) + + def const_value(self) -> Value: + return Value(super().const_value()) + + def dynamic_cast(self, type: gdb.Type) -> Value: + return Value(super().dynamic_cast(type)) + + class Backtrace: """ Convert addresses to backtrace @@ -261,10 +345,16 @@ def objfile(self): return self._file +def parse_and_eval(expression: str, global_context: bool = False): + """Equivalent to gdb.parse_and_eval, but returns a Value object""" + gdb_value = gdb.parse_and_eval(expression) + return Value(gdb_value) + + def gdb_eval_or_none(expresssion): """Evaluate an expression and return None if it fails""" try: - return gdb.parse_and_eval(expresssion) + return parse_and_eval(expresssion) except gdb.error: return None @@ -405,7 +495,7 @@ def parse_arg(arg: str) -> Union[gdb.Value, int]: return int(arg, 16) try: - return gdb.parse_and_eval(f"{arg}") + return parse_and_eval(f"{arg}") except gdb.error: return None @@ -544,7 +634,7 @@ def swap64(val): def is_target_arch(arch, exact=False): """ - For non extact match, this function will + For non exact match, this function will return True if the target architecture contains keywords of an ARCH family. For example, x86 is contained in i386:x86_64. @@ -632,19 +722,19 @@ def get_pc(tcb=None): return get_register_byname("pc", tcb) -def get_tcbs(): +def get_tcbs() -> List[Tcb]: # In case we have created/deleted tasks at runtime, the tcbs will change # so keep it as fresh as possible - pidhash = gdb.parse_and_eval("g_pidhash") - npidhash = gdb.parse_and_eval("g_npidhash") + pidhash = parse_and_eval("g_pidhash") + npidhash = parse_and_eval("g_npidhash") return [pidhash[i] for i in range(0, npidhash) if pidhash[i]] -def get_tcb(pid): +def get_tcb(pid) -> Tcb: """get tcb from pid""" - g_pidhash = gdb.parse_and_eval("g_pidhash") - g_npidhash = gdb.parse_and_eval("g_npidhash") + g_pidhash = parse_and_eval("g_pidhash") + g_npidhash = parse_and_eval("g_npidhash") tcb = g_pidhash[pid & (g_npidhash - 1)] if not tcb or pid != tcb["pid"]: return None