This is an automated email from the ASF dual-hosted git repository. xiaoxiang pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/nuttx.git
commit 5d86bee5c7102b90a4376e630bd7c3cdf5e8395e Author: xuxingliang <xuxingli...@xiaomi.com> AuthorDate: Mon Sep 9 14:04:18 2024 +0800 tools/gdb: add diagnose commands Run diagnostic related commands anytime to generate report. E.g `diag report -o systemreport.json` Signed-off-by: xuxingliang <xuxingli...@xiaomi.com> --- tools/gdb/nuttxgdb/__init__.py | 12 +++---- tools/gdb/nuttxgdb/diagnose.py | 78 ++++++++++++++++++++++++++++++++++++++++++ tools/gdb/nuttxgdb/memdump.py | 8 +++++ tools/gdb/nuttxgdb/thread.py | 36 ++++++++++++++----- tools/gdb/nuttxgdb/utils.py | 36 +++++++++++++++++++ 5 files changed, 155 insertions(+), 15 deletions(-) diff --git a/tools/gdb/nuttxgdb/__init__.py b/tools/gdb/nuttxgdb/__init__.py index 48fba48c0d..10cffc619b 100644 --- a/tools/gdb/nuttxgdb/__init__.py +++ b/tools/gdb/nuttxgdb/__init__.py @@ -21,17 +21,12 @@ ############################################################################ import importlib -from os import listdir, path +from os import path import gdb here = path.dirname(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): if getattr(register_commands, "registered", False): @@ -53,6 +48,10 @@ def register_commands(event): if isinstance(c, type) and issubclass(c, gdb.Command): c() + # import utils module + utils = importlib.import_module(f"{__package__}.utils") + modules = utils.gather_modules(here) + # Register prefix commands firstly init_gdb_commands("prefix") modules.remove("prefix") @@ -62,7 +61,6 @@ def register_commands(event): for m in modules: init_gdb_commands(m) - utils = importlib.import_module(f"{__package__}.utils") utils.check_version() diff --git a/tools/gdb/nuttxgdb/diagnose.py b/tools/gdb/nuttxgdb/diagnose.py new file mode 100644 index 0000000000..d7e081a68f --- /dev/null +++ b/tools/gdb/nuttxgdb/diagnose.py @@ -0,0 +1,78 @@ +############################################################################ +# tools/gdb/nuttx_gdb/diagnose.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 argparse + +import gdb + +from . import utils + + +class DiagnosePrefix(gdb.Command): + """Diagnostic related commands.""" + + def __init__(self): + super().__init__("diagnose", gdb.COMMAND_USER, prefix=True) + + +class DiagnoseReport(gdb.Command): + """Run diagnostics to generate reports.""" + + def __init__(self): + super().__init__("diagnose report", gdb.COMMAND_USER) + + def invoke(self, args, from_tty): + parser = argparse.ArgumentParser(description=self.__doc__) + parser.add_argument( + "-o", + "--output", + type=str, + help="report output file name", + ) + + try: + args = parser.parse_args(gdb.string_to_argv(args)) + except SystemExit: + return + + reportfile = ( + args.output + if args.output + else gdb.objfiles()[0].filename + ".diagnostics.json" + ) + + modules = utils.gather_modules() + modules.remove("prefix") + modules.remove("__init__") + + commands = utils.gather_gdbcommands(modules=modules) + + results = [] + for clz in commands: + if hasattr(clz, "diagnose"): + command = clz() + gdb.write(f"Run command: {clz.__name__}\n") + results.append(command.diagnose()) + + gdb.write(f"Write report to {reportfile}\n") + with open(reportfile, "w") as f: + f.write(utils.jsonify(results, indent=4)) diff --git a/tools/gdb/nuttxgdb/memdump.py b/tools/gdb/nuttxgdb/memdump.py index 94edd54fa3..4f76ab8552 100644 --- a/tools/gdb/nuttxgdb/memdump.py +++ b/tools/gdb/nuttxgdb/memdump.py @@ -762,6 +762,14 @@ class Memleak(gdb.Command): return {"simple": args.simple, "detail": args.detail} + def diagnose(self, *args, **kwargs): + output = gdb.execute("memleak", to_string=True) + return { + "title": "Memory Leak Report", + "command": "memleak", + "details": output, + } + def invoke(self, args, from_tty): if sizeof_size_t == 4: align = 11 diff --git a/tools/gdb/nuttxgdb/thread.py b/tools/gdb/nuttxgdb/thread.py index 07a89a91ea..0fb7829a67 100644 --- a/tools/gdb/nuttxgdb/thread.py +++ b/tools/gdb/nuttxgdb/thread.py @@ -616,6 +616,8 @@ class Ps(gdb.Command): class DeadLock(gdb.Command): + """Detect and report if threads have deadlock.""" + def __init__(self): super().__init__("deadlock", gdb.COMMAND_USER) @@ -638,9 +640,12 @@ class DeadLock(gdb.Command): self.holders.append(holder) return self.has_deadlock(holder) - def invoke(self, args, from_tty): + def collect(self, tcbs): + """Collect the deadlock information""" + detected = [] - for tcb in utils.get_tcbs(): + collected = [] + for tcb in tcbs: self.holders = [] # Holders for this tcb pid = tcb["pid"] if pid in detected or not self.has_deadlock(tcb["pid"]): @@ -649,13 +654,28 @@ class DeadLock(gdb.Command): # Deadlock detected detected.append(pid) detected.extend(self.holders) - gdb.write(f'Thread {pid} "{utils.get_task_name(tcb)}" has deadlocked!\n') + collected.append((pid, self.holders)) - gdb.write(f" holders: {pid}->") - gdb.write("->".join(str(pid) for pid in self.holders)) - gdb.write("\n") + return collected + + def diagnose(self, *args, **kwargs): + collected = self.collect(utils.get_tcbs()) - if not detected: + return { + "title": "Deadlock Report", + "summary": f"{'No' if not collected else len(collected)} deadlocks", + "command": "deadlock", + "deadlocks": {int(pid): [i for i in h] for pid, h in collected}, + } + + def invoke(self, args, from_tty): + collected = self.collect(utils.get_tcbs()) + if not collected: gdb.write("No deadlock detected.") + return - gdb.write("\n") + for pid, holders in collected: + gdb.write(f'Thread {pid} "{utils.get_task_name(pid)}" has deadlocked!\n') + gdb.write(f" holders: {pid}->") + gdb.write("->".join(str(pid) for pid in holders)) + gdb.write("\n") diff --git a/tools/gdb/nuttxgdb/utils.py b/tools/gdb/nuttxgdb/utils.py index c84912bb07..f425a354a4 100644 --- a/tools/gdb/nuttxgdb/utils.py +++ b/tools/gdb/nuttxgdb/utils.py @@ -20,6 +20,9 @@ # ############################################################################ +import importlib +import json +import os import re import shlex from typing import List, Tuple, Union @@ -600,6 +603,39 @@ def check_version(): suppress_cli_notifications(state) +def gather_modules(dir=None) -> List[str]: + dir = os.path.normpath(dir) if dir else os.path.dirname(__file__) + return [ + os.path.splitext(os.path.basename(f))[0] + for f in os.listdir(dir) + if f.endswith(".py") + ] + + +def gather_gdbcommands(modules=None, path=None) -> List[gdb.Command]: + modules = modules or gather_modules(path) + commands = [] + for m in modules: + module = importlib.import_module(f"{__package__}.{m}") + for c in module.__dict__.values(): + if isinstance(c, type) and issubclass(c, gdb.Command): + commands.append(c) + return commands + + +def jsonify(obj, indent=None): + if not obj: + return "{}" + + def dumper(obj): + try: + return str(obj) if isinstance(obj, gdb.Value) else obj.toJSON() + except Exception: + return obj.__dict__ + + return json.dumps(obj, default=dumper, indent=indent) + + class Hexdump(gdb.Command): """hexdump address/symbol <size>"""