diff --git a/tools/gdb/.gitignore b/tools/gdb/.gitignore new file mode 100644 index 00000000000..12681cb30e7 --- /dev/null +++ b/tools/gdb/.gitignore @@ -0,0 +1,2 @@ +dist/ +nuttxgdb.egg-info/ diff --git a/tools/gdb/gdbinit.py b/tools/gdb/gdbinit.py new file mode 100644 index 00000000000..4e106cfe4dc --- /dev/null +++ b/tools/gdb/gdbinit.py @@ -0,0 +1,35 @@ +############################################################################ +# tools/gdb/gdbinit.py +# +# SPDX-License-Identifier: Apache-2.0 +# +# 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. +# +############################################################################ + +import sys +from os import path + +here = path.dirname(path.abspath(__file__)) + +if __name__ == "__main__": + if here not in sys.path: + sys.path.insert(0, here) + + if "nuttxgdb" in sys.modules: + del sys.modules["nuttxgdb"] + + import nuttxgdb # noqa: F401 diff --git a/tools/gdb/__init__.py b/tools/gdb/nuttxgdb/__init__.py similarity index 68% rename from tools/gdb/__init__.py rename to tools/gdb/nuttxgdb/__init__.py index 1de69569eac..68e62541270 100644 --- a/tools/gdb/__init__.py +++ b/tools/gdb/nuttxgdb/__init__.py @@ -1,5 +1,5 @@ ############################################################################ -# tools/gdb/__init__.py +# tools/gdb/nuttxgdb/__init__.py # # SPDX-License-Identifier: Apache-2.0 # @@ -20,19 +20,17 @@ # ############################################################################ -import glob -import os -import sys +import importlib +from os import listdir, path import gdb -python_dir = os.path.abspath(__file__) -python_dir = os.path.dirname(python_dir) +here = path.dirname(path.abspath(__file__)) -sys.path.insert(1, python_dir) -# Search the python dir for all .py files, and source each -py_files = glob.glob(f"{python_dir}/*.py") -py_files.remove(os.path.abspath(__file__)) +# Scan dir to get all modules available +modules = [ + path.splitext(path.basename(f))[0] for f in listdir(here) if f.endswith(".py") +] def register_commands(event): @@ -41,20 +39,29 @@ def register_commands(event): register_commands.registered = True - gdb.write("Registering NuttX GDB commands...\n") + gdb.write(f"Registering NuttX GDB commands from {here}\n") gdb.execute("set pagination off") gdb.write("set pagination off\n") gdb.execute("set python print-stack full") gdb.write("set python print-stack full\n") - for py_file in py_files: - gdb.execute(f"source {py_file}") - gdb.write(f"source {py_file}\n") - - utils = __import__("utils") - utils.check_version() gdb.execute('handle SIGUSR1 "nostop" "pass" "noprint"') gdb.write('"handle SIGUSR1 "nostop" "pass" "noprint"\n') + # Register all modules + for m in modules: + if m == "__init__": + continue + + module = importlib.import_module(f"{__package__}.{m}") + + # Get all classes inherited from gdb.Command + for c in module.__dict__.values(): + if isinstance(c, type) and issubclass(c, gdb.Command): + c() + + utils = importlib.import_module(f"{__package__}.utils") + utils.check_version() + if len(gdb.objfiles()) == 0: gdb.events.new_objfile.connect(register_commands) diff --git a/tools/gdb/dmesg.py b/tools/gdb/nuttxgdb/dmesg.py similarity index 90% rename from tools/gdb/dmesg.py rename to tools/gdb/nuttxgdb/dmesg.py index 2a1d05b712d..7f8deb303eb 100644 --- a/tools/gdb/dmesg.py +++ b/tools/gdb/nuttxgdb/dmesg.py @@ -1,5 +1,7 @@ ############################################################################ -# tools/gdb/dmesg.py +# tools/gdb/nuttxgdb/dmesg.py +# +# SPDX-License-Identifier: Apache-2.0 # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with @@ -19,14 +21,16 @@ ############################################################################ import gdb -import utils + +from . import utils CONFIG_RAMLOG_SYSLOG = utils.get_symbol_value("CONFIG_RAMLOG_SYSLOG") class Dmesg(gdb.Command): def __init__(self): - super().__init__("dmesg", gdb.COMMAND_USER) + if CONFIG_RAMLOG_SYSLOG: + super().__init__("dmesg", gdb.COMMAND_USER) def invoke(self, args, from_tty): sysdev = utils.gdb_eval_or_none("g_sysdev") @@ -43,7 +47,3 @@ class Dmesg(gdb.Command): clean_data = bytes(buf).replace(b"\x00", "␀".encode("utf-8")) gdb.write(clean_data.decode("utf-8")) gdb.write("\n") - - -if CONFIG_RAMLOG_SYSLOG: - Dmesg() diff --git a/tools/gdb/fs.py b/tools/gdb/nuttxgdb/fs.py similarity index 97% rename from tools/gdb/fs.py rename to tools/gdb/nuttxgdb/fs.py index c4f6f52d33a..19ba4bf849f 100644 --- a/tools/gdb/fs.py +++ b/tools/gdb/nuttxgdb/fs.py @@ -1,5 +1,7 @@ ############################################################################ -# tools/gdb/fs.py +# tools/gdb/nuttxgdb/fs.py +# +# SPDX-License-Identifier: Apache-2.0 # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with @@ -22,7 +24,8 @@ import argparse import enum import gdb -import utils + +from . import utils FSNODEFLAG_TYPE_MASK = utils.get_symbol_value("FSNODEFLAG_TYPE_MASK") @@ -220,7 +223,8 @@ class Fdinfo(gdb.Command): class Mount(gdb.Command): def __init__(self): - super().__init__("mount", gdb.COMMAND_USER) + if not utils.get_symbol_value("CONFIG_DISABLE_MOUNTPOINT"): + super().__init__("mount", gdb.COMMAND_USER) def mountpt_filter(self, node, path): if inode_gettype(node) == InodeType.MOUNTPT: @@ -339,7 +343,8 @@ class InfoShmfs(gdb.Command): """Show share memory usage""" def __init__(self): - super().__init__("info shm", gdb.COMMAND_USER) + if CONFIG_FS_SHMFS: + super().__init__("info shm", gdb.COMMAND_USER) def shm_filter(self, node, path): if inode_gettype(node) != InodeType.SHM: @@ -352,14 +357,3 @@ class InfoShmfs(gdb.Command): def invoke(self, args, from_tty): foreach_inode(self.shm_filter) - - -Fdinfo() - -if not utils.get_symbol_value("CONFIG_DISABLE_MOUNTPOINT"): - Mount() - -ForeachInode() - -if CONFIG_FS_SHMFS: - InfoShmfs() diff --git a/tools/gdb/gcore.py b/tools/gdb/nuttxgdb/gcore.py similarity index 98% rename from tools/gdb/gcore.py rename to tools/gdb/nuttxgdb/gcore.py index b7574247071..f15a436b1a6 100644 --- a/tools/gdb/gcore.py +++ b/tools/gdb/nuttxgdb/gcore.py @@ -1,5 +1,5 @@ ############################################################################ -# tools/gdb/gcore.py +# tools/gdb/nuttxgdb/gcore.py # # SPDX-License-Identifier: Apache-2.0 # @@ -25,7 +25,8 @@ import os import shutil import gdb -import utils + +from . import utils def create_file_with_size(filename, size): @@ -133,6 +134,3 @@ class NXGcore(gdb.Command): tmpfile.close() print(f"Please run gdbserver.py to parse {corefile}") - - -NXGcore() diff --git a/tools/gdb/lists.py b/tools/gdb/nuttxgdb/lists.py similarity index 99% rename from tools/gdb/lists.py rename to tools/gdb/nuttxgdb/lists.py index a6ade107e15..5cde4685904 100644 --- a/tools/gdb/lists.py +++ b/tools/gdb/nuttxgdb/lists.py @@ -1,5 +1,5 @@ ############################################################################ -# tools/gdb/lists.py +# tools/gdb/nuttxgdb/lists.py # # SPDX-License-Identifier: Apache-2.0 # @@ -23,7 +23,8 @@ import argparse import gdb -import utils + +from . import utils list_node_type = utils.lookup_type("struct list_node") sq_queue_type = utils.lookup_type("sq_queue_t") diff --git a/tools/gdb/macros.py b/tools/gdb/nuttxgdb/macros.py similarity index 99% rename from tools/gdb/macros.py rename to tools/gdb/nuttxgdb/macros.py index 9970c1150ae..85c3e7675e0 100644 --- a/tools/gdb/macros.py +++ b/tools/gdb/nuttxgdb/macros.py @@ -1,5 +1,5 @@ ############################################################################ -# tools/gdb/macros.py +# tools/gdb/nuttxgdb/macros.py # # SPDX-License-Identifier: Apache-2.0 # diff --git a/tools/gdb/memdump.py b/tools/gdb/nuttxgdb/memdump.py similarity index 99% rename from tools/gdb/memdump.py rename to tools/gdb/nuttxgdb/memdump.py index 483f4ad2956..94edd54fa34 100644 --- a/tools/gdb/memdump.py +++ b/tools/gdb/nuttxgdb/memdump.py @@ -1,5 +1,5 @@ ############################################################################ -# tools/gdb/memdump.py +# tools/gdb/nuttxgdb/memdump.py # # SPDX-License-Identifier: Apache-2.0 # @@ -25,9 +25,10 @@ import bisect import time import gdb -import utils -from lists import NxSQueue -from utils import get_long_type, get_symbol_value, lookup_type, read_ulong + +from . import utils +from .lists import NxSQueue +from .utils import get_long_type, get_symbol_value, lookup_type, read_ulong MM_ALLOC_BIT = 0x1 MM_PREVFREE_BIT = 0x2 @@ -1014,9 +1015,3 @@ class Memfrag(gdb.Command): f"heap size: {heapsize}, free size: {freesize}, uordblks:" f"{info.__len__()} largest block: {info[0]['size']} \n" ) - - -Memfrag() -Memdump() -Memleak() -Memmap() diff --git a/tools/gdb/net.py b/tools/gdb/nuttxgdb/net.py similarity index 97% rename from tools/gdb/net.py rename to tools/gdb/nuttxgdb/net.py index 4db70c01aba..55e91003911 100644 --- a/tools/gdb/net.py +++ b/tools/gdb/nuttxgdb/net.py @@ -1,5 +1,7 @@ ############################################################################ -# tools/gdb/net.py +# tools/gdb/nuttxgdb/net.py +# +# SPDX-License-Identifier: Apache-2.0 # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with @@ -19,8 +21,9 @@ ############################################################################ import gdb -import utils -from lists import NxDQueue, NxSQueue + +from . import utils +from .lists import NxDQueue, NxSQueue socket = utils.import_check( "socket", errmsg="No socket module found, please try gdb-multiarch instead.\n" @@ -108,7 +111,8 @@ class NetStats(gdb.Command): """ def __init__(self): - super().__init__("netstats", gdb.COMMAND_USER) + if utils.get_symbol_value("CONFIG_NET") and socket: + super().__init__("netstats", gdb.COMMAND_USER) def iob_stats(self): try: @@ -242,7 +246,3 @@ class NetStats(gdb.Command): if utils.get_symbol_value("CONFIG_NET_UDP") and "udp" in args: self.udp_stats() gdb.write("\n") - - -if utils.get_symbol_value("CONFIG_NET") and socket: - NetStats() diff --git a/tools/gdb/stack.py b/tools/gdb/nuttxgdb/stack.py similarity index 99% rename from tools/gdb/stack.py rename to tools/gdb/nuttxgdb/stack.py index 5e2ce87e93f..f1bc60d4eb7 100644 --- a/tools/gdb/stack.py +++ b/tools/gdb/nuttxgdb/stack.py @@ -1,5 +1,5 @@ ############################################################################ -# tools/gdb/stack.py +# tools/gdb/nuttxgdb/stack.py # # SPDX-License-Identifier: Apache-2.0 # @@ -23,7 +23,8 @@ import traceback import gdb -import utils + +from . import utils STACK_COLORATION_PATTERN = utils.get_symbol_value( "STACK_COLOR", locspec="up_create_stack" @@ -217,6 +218,3 @@ class StackUsage(gdb.Command): continue self.format_print(pid, stack) - - -StackUsage() diff --git a/tools/gdb/thread.py b/tools/gdb/nuttxgdb/thread.py similarity index 94% rename from tools/gdb/thread.py rename to tools/gdb/nuttxgdb/thread.py index 08641e510e6..07a89a91ea2 100644 --- a/tools/gdb/thread.py +++ b/tools/gdb/nuttxgdb/thread.py @@ -1,5 +1,5 @@ ############################################################################ -# tools/gdb/thread.py +# tools/gdb/nuttxgdb/thread.py # # SPDX-License-Identifier: Apache-2.0 # @@ -25,12 +25,20 @@ import re from enum import Enum, auto import gdb -import utils -from stack import Stack + +from . import utils +from .stack import Stack UINT16_MAX = 0xFFFF SEM_TYPE_MUTEX = 4 TSTATE_TASK_RUNNING = utils.get_symbol_value("TSTATE_TASK_RUNNING") +CONFIG_SMP_NCPUS = utils.get_symbol_value("CONFIG_SMP_NCPUS") or 1 + + +def is_thread_command_supported(): + # Check if the native thread command is available by compare the number of threads. + # It should have at least CONFIG_SMP_NCPUS of idle threads. + return len(gdb.selected_inferior().threads()) > CONFIG_SMP_NCPUS class Registers: @@ -207,6 +215,8 @@ class Nxinfothreads(gdb.Command): def __init__(self): super().__init__("info nxthreads", gdb.COMMAND_USER) + if not is_thread_command_supported(): + gdb.execute("define info threads\n info nxthreads \n end\n") def invoke(self, args, from_tty): npidhash = gdb.parse_and_eval("g_npidhash") @@ -298,6 +308,8 @@ class Nxthread(gdb.Command): def __init__(self): super().__init__("nxthread", gdb.COMMAND_USER) + if not is_thread_command_supported(): + gdb.execute("define thread\n nxthread \n end\n") def invoke(self, args, from_tty): npidhash = gdb.parse_and_eval("g_npidhash") @@ -375,6 +387,11 @@ class Nxcontinue(gdb.Command): def __init__(self): super().__init__("nxcontinue", gdb.COMMAND_USER) + if not is_thread_command_supported(): + gdb.execute("define c\n nxcontinue \n end\n") + gdb.write( + "\n\x1b[31;1m if use thread command, please don't use 'continue', use 'c' instead !!!\x1b[m\n" + ) def invoke(self, args, from_tty): g_registers.restore() @@ -386,6 +403,11 @@ class Nxstep(gdb.Command): def __init__(self): super().__init__("nxstep", gdb.COMMAND_USER) + if not is_thread_command_supported(): + gdb.execute("define s\n nxstep \n end\n") + gdb.write( + "\x1b[31;1m if use thread command, please don't use 'step', use 's' instead !!!\x1b[m\n" + ) def invoke(self, args, from_tty): g_registers.restore() @@ -637,36 +659,3 @@ class DeadLock(gdb.Command): gdb.write("No deadlock detected.") gdb.write("\n") - - -def register_commands(): - DeadLock() - SetRegs() - Ps() - Nxcontinue() - Nxinfothreads() - Nxstep() - Nxthread() - - # Use custom command for thread if current target does not support it. - # The recognized threads count is less than or equal to the number of CPUs in this case. - # For coredump and gdb-stub, use native thread commands. - ncpus = utils.get_symbol_value("CONFIG_SMP_NCPUS") or 1 - nthreads = len(gdb.selected_inferior().threads()) - if nthreads <= ncpus: - # Native threads command is not available, override the threads command - gdb.execute("define info threads\n info nxthreads \n end\n") - gdb.execute("define thread\n nxthread \n end\n") - - # We can't use a user command to rename continue it will recursion - gdb.execute("define c\n nxcontinue \n end\n") - gdb.execute("define s\n nxstep \n end\n") - gdb.write( - "\n\x1b[31;1m if use thread command, please don't use 'continue', use 'c' instead !!!\x1b[m\n" - ) - gdb.write( - "\x1b[31;1m if use thread command, please don't use 'step', use 's' instead !!!\x1b[m\n" - ) - - -register_commands() diff --git a/tools/gdb/utils.py b/tools/gdb/nuttxgdb/utils.py similarity index 95% rename from tools/gdb/utils.py rename to tools/gdb/nuttxgdb/utils.py index 95378475e6f..c84912bb079 100644 --- a/tools/gdb/utils.py +++ b/tools/gdb/nuttxgdb/utils.py @@ -1,5 +1,5 @@ ############################################################################ -# tools/gdb/utils.py +# tools/gdb/nuttxgdb/utils.py # # SPDX-License-Identifier: Apache-2.0 # @@ -25,7 +25,8 @@ import shlex from typing import List, Tuple, Union import gdb -from macros import fetch_macro_info, try_expand + +from .macros import fetch_macro_info, try_expand g_symbol_cache = {} g_type_cache = {} @@ -293,90 +294,6 @@ def parse_arg(arg: str) -> Union[gdb.Value, int]: return None -class Hexdump(gdb.Command): - """hexdump address/symbol """ - - def __init__(self): - super().__init__("hexdump", gdb.COMMAND_USER) - - def invoke(self, args, from_tty): - args = shlex.split(args) - if not args or len(args) < 1: - gdb.write("Usage: hexdump address/symbol \n") - return - - address = parse_arg(args[0]) - size = parse_arg(args[1]) if len(args) > 1 else None - if not size: - size = address.type.sizeof if isinstance(address, gdb.Value) else 128 - - print(f"Dumping {size} bytes from {hex(address)}") - hexdump(address, size) - - -Hexdump() - - -class Addr2Line(gdb.Command): - """Convert addresses or expressions - - Usage: addr2line address1 address2 expression1 - Example: addr2line 0x1234 0x5678 - addr2line "0x1234 + pointer->abc" &var var->field function_name var - addr2line $pc $r1 "$r2 + var" - addr2line [24/08/29 20:51:02] [CPU1] [209] [ap] sched_dumpstack: backtrace| 0: 0x402cd484 0x4028357e - """ - - def __init__(self): - super().__init__("addr2line", gdb.COMMAND_USER) - - def invoke(self, args, from_tty): - if not args: - gdb.write(Addr2Line.__doc__ + "\n") - return - - addresses = [] - for arg in shlex.split(args): - v = parse_arg(arg) - if not v: - gdb.write(f'Ignore "{arg}"\n') - continue - - addresses.append(v) - - backtraces = backtrace(addresses) - formatter = "{:<20} {:<32} {}\n" - gdb.write(formatter.format("Address", "Symbol", "Source")) - for addr, func, source in backtraces: - gdb.write(formatter.format(hex(addr), func, source)) - - -Addr2Line() - - -class Profile(gdb.Command): - """Profile a gdb command - - Usage: profile - """ - - def __init__(self): - self.cProfile = import_check( - "cProfile", errmsg="cProfile module not found, try gdb-multiarch.\n" - ) - if not self.cProfile: - return - - super().__init__("profile", gdb.COMMAND_USER) - - def invoke(self, args, from_tty): - - self.cProfile.run(f"gdb.execute('{args}')", sort="cumulative") - - -Profile() - - def nitems(array): array_type = array.type element_type = array_type.target() @@ -681,3 +598,86 @@ def check_version(): switch_inferior(1) # Switch back suppress_cli_notifications(state) + + +class Hexdump(gdb.Command): + """hexdump address/symbol """ + + def __init__(self): + super().__init__("hexdump", gdb.COMMAND_USER) + + def invoke(self, args, from_tty): + argv = args.split(" ") + address = 0 + size = 0 + if argv[0] == "": + gdb.write("Usage: hexdump address/symbol \n") + return + + if is_decimal(argv[0]) or is_hexadecimal(argv[0]): + address = int(argv[0], 0) + size = int(argv[1], 0) + else: + var = gdb.parse_and_eval(f"{argv[0]}") + address = int(var.address) + size = int(var.type.sizeof) + gdb.write(f"{argv[0]} {hex(address)} {int(size)}\n") + + hexdump(address, size) + + +class Addr2Line(gdb.Command): + """Convert addresses or expressions + + Usage: addr2line address1 address2 expression1 + Example: addr2line 0x1234 0x5678 + addr2line "0x1234 + pointer->abc" &var var->field function_name var + addr2line $pc $r1 "$r2 + var" + addr2line [24/08/29 20:51:02] [CPU1] [209] [ap] sched_dumpstack: backtrace| 0: 0x402cd484 0x4028357e + """ + + def __init__(self): + super().__init__("addr2line", gdb.COMMAND_USER) + + def invoke(self, args, from_tty): + if not args: + gdb.write(Addr2Line.__doc__ + "\n") + return + + addresses = [] + for arg in shlex.split(args): + if is_decimal(arg): + addresses.append(int(arg)) + elif is_hexadecimal(arg): + addresses.append(int(arg, 16)) + else: + try: + var = gdb.parse_and_eval(f"{arg}") + addresses.append(var) + except gdb.error as e: + gdb.write(f"Ignore {arg}: {e}\n") + + backtraces = backtrace(addresses) + formatter = "{:<20} {:<32} {}\n" + gdb.write(formatter.format("Address", "Symbol", "Source")) + for addr, func, source in backtraces: + gdb.write(formatter.format(hex(addr), func, source)) + + +class Profile(gdb.Command): + """Profile a gdb command + + Usage: profile + """ + + def __init__(self): + self.cProfile = import_check( + "cProfile", errmsg="cProfile module not found, try gdb-multiarch.\n" + ) + if not self.cProfile: + return + + super().__init__("profile", gdb.COMMAND_USER) + + def invoke(self, args, from_tty): + self.cProfile.run(f"gdb.execute('{args}')", sort="cumulative") diff --git a/tools/gdb/pyproject.toml b/tools/gdb/pyproject.toml new file mode 100644 index 00000000000..b8b271cbd90 --- /dev/null +++ b/tools/gdb/pyproject.toml @@ -0,0 +1,22 @@ +[build-system] +requires = ["setuptools>=61", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +version = "0.0.1" +name = 'nuttxgdb' +description = 'NuttX RTOS GDB extensions' +readme = "README.md" +license = { file = 'LICENSE' } +classifiers = [ + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python", + "Programming Language :: Python :: 3", +] + +dependencies = ["matplotlib", "numpy", "pyelftools"] +requires-python = ">=3.8" + +[tool.setuptools.package-dir] +nuttxgdb = "nuttxgdb"